comparison artifacts/src/main/java/org/dive4elements/river/jfree/EnhancedLineAndShapeRenderer.java @ 5838:5aa05a7a34b7

Rename modules to more fitting names.
author Sascha L. Teichmann <teichmann@intevation.de>
date Thu, 25 Apr 2013 15:23:37 +0200
parents flys-artifacts/src/main/java/org/dive4elements/river/jfree/EnhancedLineAndShapeRenderer.java@bd047b71ab37
children 4897a58c8746
comparison
equal deleted inserted replaced
5837:d9901a08d0a6 5838:5aa05a7a34b7
1 package org.dive4elements.river.jfree;
2
3 import java.awt.Color;
4 import java.awt.Font;
5 import java.awt.Graphics2D;
6 import java.awt.Paint;
7 import java.awt.Shape;
8 import java.awt.geom.Rectangle2D;
9 import java.util.HashMap;
10 import java.util.Map;
11
12 import org.apache.log4j.Logger;
13 import org.jfree.chart.axis.ValueAxis;
14 import org.jfree.chart.entity.EntityCollection;
15 import org.jfree.chart.plot.CrosshairState;
16 import org.jfree.chart.plot.PlotOrientation;
17 import org.jfree.chart.plot.XYPlot;
18 import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
19 import org.jfree.data.xy.XYDataset;
20 import org.jfree.data.xy.XYSeries;
21 import org.jfree.data.xy.XYSeriesCollection;
22 import org.jfree.text.TextUtilities;
23 import org.jfree.ui.RectangleEdge;
24 import org.jfree.ui.TextAnchor;
25 import org.jfree.util.BooleanList;
26 import org.jfree.util.ShapeUtilities;
27
28 /**
29 * Renderer with additional the additional functionality of renderering minima
30 * and/or maxima of dataseries contained in datasets.
31 */
32 public class EnhancedLineAndShapeRenderer extends XYLineAndShapeRenderer {
33
34 /**
35 *
36 */
37 private static final long serialVersionUID = 1L;
38
39 /** Own logger. */
40 private static final Logger logger =
41 Logger.getLogger(EnhancedLineAndShapeRenderer.class);
42
43 protected BooleanList isMinimumShapeVisible;
44 protected BooleanList isMaximumShapeVisible;
45 protected BooleanList showLineLabel;
46
47 protected Map<Integer, Double> seriesMinimum;
48 protected Map<Integer, Double> seriesMinimumX;
49 protected Map<Integer, Double> seriesMaximum;
50
51 protected Map<Integer, Font> lineLabelFonts;
52 protected Map<Integer, Color> lineLabelTextColors;
53 protected BooleanList showLineLabelBG;
54 protected Map<Integer, Color> lineLabelBGColors;
55
56
57 public EnhancedLineAndShapeRenderer(boolean lines, boolean shapes) {
58 super(lines, shapes);
59 this.isMinimumShapeVisible = new BooleanList();
60 this.isMaximumShapeVisible = new BooleanList();
61 this.showLineLabel = new BooleanList();
62 this.showLineLabelBG = new BooleanList();
63 this.seriesMinimum = new HashMap<Integer, Double>();
64 this.seriesMaximum = new HashMap<Integer, Double>();
65 this.seriesMinimumX = new HashMap<Integer, Double>();
66 this.lineLabelFonts = new HashMap<Integer, Font>();
67 this.lineLabelTextColors = new HashMap<Integer, Color>();
68 this.lineLabelBGColors = new HashMap<Integer, Color>();
69 }
70
71
72 /**
73 * Draw a background-box of a text to render.
74 * @param g2 graphics device to use
75 * @param text text to draw
76 * @param textX x-position for text
77 * @param textY y-position for text
78 * @param bgColor color to fill box with.
79 */
80 public static void drawTextBox(Graphics2D g2,
81 String text, float textX, float textY, Color bgColor
82 ) {
83 Rectangle2D hotspotBox = g2.getFontMetrics().getStringBounds(text, g2);
84 float w = (float) hotspotBox.getWidth(), h = (float) hotspotBox.getHeight();
85 hotspotBox.setRect(textX, textY-h, w, h);
86 Color oldColor = g2.getColor();
87 g2.setColor(bgColor);
88 g2.fill(hotspotBox);
89 g2.setColor(oldColor);
90 }
91
92
93 /**
94 * Whether or not a specific item in a series (maybe the maxima) should
95 * be rendered with shape.
96 */
97 public boolean getItemShapeVisible(XYDataset dataset, int series, int item){
98 if (super.getItemShapeVisible(series, item)) {
99 return true;
100 }
101
102 if (isMinimumShapeVisible(series) && isMinimum(dataset, series, item)) {
103 return true;
104 }
105
106 if (isMaximumShapeVisible(series) && isMaximum(dataset, series, item)) {
107 return true;
108 }
109
110 return false;
111 }
112
113
114 /**
115 * Rectangle used to draw maximums shape.
116 */
117 public Shape getMaximumShape(int series, int column) {
118 return new Rectangle2D.Double(-5d, -5d, 10d, 10d);
119 }
120
121
122 /**
123 * Rectangle used to draw minimums shape.
124 */
125 public Shape getMinimumShape(int series, int column) {
126 return new Rectangle2D.Double(-5d, -5d, 10d, 10d);
127 }
128
129
130 /** Get fill paint for the maximum indicators. */
131 public Paint getMaximumFillPaint(int series, int column) {
132 Paint p = getItemPaint(series, column);
133
134 if (p instanceof Color) {
135 Color c = (Color) p;
136 Color b = c;
137
138 for (int i = 0; i < 2; i++) {
139 b = b.darker();
140 }
141
142 return b;
143 }
144
145 logger.warn("Item paint is no instance of Color!");
146 return p;
147 }
148
149
150 /** Get fill paint for the minimum indicators. */
151 public Paint getMinimumFillPaint(int series, int column) {
152 Paint p = getItemPaint(series, column);
153
154 if (p instanceof Color) {
155 Color c = (Color) p;
156 Color b = c;
157
158 for (int i = 0; i < 2; i++) {
159 b = b.darker();
160 }
161
162 return b;
163 }
164
165 logger.warn("Item paint is no instance of Color!");
166 return p;
167 }
168
169
170 /**
171 * Overrides XYLineAndShapeRenderer.drawSecondaryPass() to call an adapted
172 * method getItemShapeVisible() which now takes an XYDataset. So, 99% of
173 * code equal the code in XYLineAndShapeRenderer.
174 */
175 @Override
176 protected void drawSecondaryPass(
177 Graphics2D g2,
178 XYPlot plot,
179 XYDataset dataset,
180 int pass,
181 int series,
182 int item,
183 ValueAxis domainAxis,
184 Rectangle2D dataArea,
185 ValueAxis rangeAxis,
186 CrosshairState crosshairState,
187 EntityCollection entities
188 ) {
189 Shape entityArea = null;
190
191 // get the data point...
192 double x1 = dataset.getXValue(series, item);
193 double y1 = dataset.getYValue(series, item);
194 if (Double.isNaN(y1) || Double.isNaN(x1)) {
195 return;
196 }
197
198 PlotOrientation orientation = plot.getOrientation();
199 RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
200 RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
201 double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
202 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
203
204 if (getItemShapeVisible(dataset, series, item)) {
205 Shape shape = null;
206
207 // OPTIMIZE: instead of calculating minimum and maximum for every
208 // point, calculate it just once (assume that dataset
209 // content does not change during rendering).
210 // NOTE: Above OPTIMIZE might already be fulfilled to most extend.
211 boolean isMinimum = isMinimumShapeVisible(series)
212 && isMinimum(dataset, series, item);
213
214 boolean isMaximum = isMaximumShapeVisible(series)
215 && isMaximum(dataset, series, item);
216
217 if (isMinimum) {
218 logger.debug("Create a Minimum shape.");
219 shape = getMinimumShape(series, item);
220 }
221 else if (isMaximum) {
222 logger.debug("Create a Maximum shape.");
223 shape = getMaximumShape(series, item);
224 }
225 else {
226 shape = getItemShape(series, item);
227 }
228
229 if (orientation == PlotOrientation.HORIZONTAL) {
230 shape = ShapeUtilities.createTranslatedShape(shape, transY1,
231 transX1);
232 }
233 else if (orientation == PlotOrientation.VERTICAL) {
234 shape = ShapeUtilities.createTranslatedShape(shape, transX1,
235 transY1);
236 }
237 entityArea = shape;
238 if (shape.intersects(dataArea)) {
239 if (getItemShapeFilled(series, item)) {
240 if (getUseFillPaint()) {
241 g2.setPaint(getItemFillPaint(series, item));
242 }
243 else {
244 g2.setPaint(getItemPaint(series, item));
245 }
246 g2.fill(shape);
247 }
248 if (getDrawOutlines()) {
249 if (getUseOutlinePaint()) {
250 g2.setPaint(getItemOutlinePaint(series, item));
251 }
252 else {
253 g2.setPaint(getItemPaint(series, item));
254 }
255 g2.setStroke(getItemOutlineStroke(series, item));
256 g2.draw(shape);
257 }
258
259 if (isMinimum) {
260 g2.setPaint(getMinimumFillPaint(series, item));
261 g2.fill(shape);
262 g2.setPaint(getItemOutlinePaint(series, item));
263 g2.setStroke(getItemOutlineStroke(series, item));
264 g2.draw(shape);
265 }
266 else if (isMaximum) {
267 g2.setPaint(getMaximumFillPaint(series, item));
268 g2.fill(shape);
269 g2.setPaint(getItemOutlinePaint(series, item));
270 g2.setStroke(getItemOutlineStroke(series, item));
271 g2.draw(shape);
272 }
273 }
274 } // if (getItemShapeVisible(dataset, series, item))
275
276 double xx = transX1;
277 double yy = transY1;
278 if (orientation == PlotOrientation.HORIZONTAL) {
279 xx = transY1;
280 yy = transX1;
281 }
282
283 // Draw the item label if there is one...
284 if (isItemLabelVisible(series, item)) {
285 drawItemLabel(g2, orientation, dataset, series, item, xx, yy,
286 (y1 < 0.0));
287 }
288
289 // Draw label of line.
290 if (dataset instanceof XYSeriesCollection
291 && isShowLineLabel(series)
292 && isMinimumX (dataset, series, item)
293 ) {
294 XYSeries xYSeries = ((XYSeriesCollection) dataset).getSeries(series);
295 String waterlevelLabel = (xYSeries instanceof HasLabel)
296 ? ((HasLabel)xYSeries).getLabel()
297 : xYSeries.getKey().toString();
298 // TODO Force water of some German rivers to flow direction mountains.
299
300 Font oldFont = g2.getFont();
301
302 Color oldColor = g2.getColor();
303 g2.setFont(this.getLineLabelFont(series));
304 g2.setColor(this.getLineLabelTextColor(series));
305 g2.setBackground(Color.black);
306
307 // Try to always display label if the data is visible.
308 if (!isPointInRect(dataArea, xx, yy)) {
309 // Move into the data area.
310 xx = Math.max(xx, dataArea.getMinX());
311 xx = Math.min(xx, dataArea.getMaxX());
312 yy = Math.max(yy, dataArea.getMinY());
313 yy = Math.min(yy, dataArea.getMaxY());
314 }
315
316 // Move to right until no collisions exist anymore
317 Shape hotspot = TextUtilities.calculateRotatedStringBounds(
318 waterlevelLabel, g2, (float)xx, (float)yy-3f,
319 TextAnchor.CENTER_LEFT,
320 0f, TextAnchor.CENTER_LEFT);
321 while (JFreeUtil.collides(hotspot, entities,
322 CollisionFreeLineLabelEntity.class)) {
323 xx += 5f;
324 hotspot = TextUtilities.calculateRotatedStringBounds(
325 waterlevelLabel, g2, (float)xx, (float)yy-3f, TextAnchor.CENTER_LEFT,
326 0f, TextAnchor.CENTER_LEFT);
327 }
328
329 // Register to avoid collissions.
330 entities.add(new CollisionFreeLineLabelEntity(hotspot,
331 1, "", ""));
332
333 // Fill background.
334 if (isShowLineLabelBG(series)) {
335 drawTextBox(g2, waterlevelLabel, (float)xx, (float)yy-3f,
336 getLineLabelBGColor(series));
337 }
338
339 g2.drawString(waterlevelLabel, (float)xx, (float)yy-3f);
340
341 g2.setFont(oldFont);
342 g2.setColor(oldColor);
343 }
344
345 int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
346 int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
347 updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
348 rangeAxisIndex, transX1, transY1, orientation);
349
350 // Add an entity for the item, but only if it falls within the data
351 // area...
352 if (entities != null && isPointInRect(dataArea, xx, yy)) {
353 addEntity(entities, entityArea, dataset, series, item, xx, yy);
354 }
355 }
356
357
358 /**
359 * Sets whether or not the minimum should be rendered with shape.
360 */
361 public void setIsMinimumShapeVisisble(int series, boolean isVisible) {
362 this.isMinimumShapeVisible.setBoolean(series, isVisible);
363 }
364
365
366 /**
367 * Whether or not the minimum should be rendered with shape.
368 */
369 public boolean isMinimumShapeVisible(int series) {
370 if (this.isMinimumShapeVisible.size() <= series) {
371 return false;
372 }
373
374 return isMinimumShapeVisible.getBoolean(series);
375 }
376
377
378 /**
379 * Sets whether or not the maximum should be rendered with shape.
380 */
381 public void setIsMaximumShapeVisible(int series, boolean isVisible) {
382 this.isMaximumShapeVisible.setBoolean(series, isVisible);
383 }
384
385
386 /**
387 * Whether or not the maximum should be rendered with shape.
388 */
389 public boolean isMaximumShapeVisible(int series) {
390 if (this.isMaximumShapeVisible.size() <= series) {
391 return false;
392 }
393
394 return isMaximumShapeVisible.getBoolean(series);
395 }
396
397 /** Whether or not a label should be shown for series. */
398 public boolean isShowLineLabel(int series) {
399 if (this.showLineLabel.size() <= series) {
400 return false;
401 }
402
403 return showLineLabel.getBoolean(series);
404 }
405
406
407 /** Sets whether or not a label should be shown for series. */
408 public void setShowLineLabel(boolean showLineLabel, int series) {
409 this.showLineLabel.setBoolean(series, showLineLabel);
410 }
411
412
413 /** Whether or not a label should be shown for series. */
414 public boolean isShowLineLabelBG(int series) {
415 if (this.showLineLabelBG.size() <= series) {
416 return false;
417 }
418
419 return showLineLabelBG.getBoolean(series);
420 }
421
422
423 public void setShowLineLabelBG(int series, boolean doShow) {
424 this.showLineLabelBG.setBoolean(series, doShow);
425 }
426
427 public Color getLineLabelBGColor(int series) {
428 if (this.lineLabelBGColors.size() <= series) {
429 return null;
430 }
431
432 return this.lineLabelBGColors.get(series);
433 }
434
435 public void setLineLabelBGColor(int series, Color color) {
436 this.lineLabelBGColors.put(series, color);
437 }
438
439 public Color getLineLabelTextColor(int series) {
440 if (this.lineLabelTextColors.size() <= series) {
441 return null;
442 }
443
444 return this.lineLabelTextColors.get(series);
445 }
446
447 public void setLineLabelTextColor(int series, Color color) {
448 this.lineLabelTextColors.put(series, color);
449 }
450
451 public void setLineLabelFont(Font font, int series) {
452 this.lineLabelFonts.put(series, font);
453 }
454
455 public Font getLineLabelFont(int series) {
456 return this.lineLabelFonts.get(series);
457 }
458
459
460 /**
461 * True if the given item of given dataset has the smallest
462 * X value within this set.
463 */
464 public boolean isMinimumX(XYDataset dataset, int series, int item) {
465 return dataset.getXValue(series, item) == getMinimumX(dataset, series);
466 }
467
468
469 /**
470 * Get Minimum X Value of a given series in a dataset.
471 * The value is stored for later use if queried the first time.
472 */
473 public double getMinimumX(XYDataset dataset, int series) {
474 Integer key = Integer.valueOf(series);
475 Double old = seriesMinimumX.get(key);
476
477 if (old != null) {
478 return old.doubleValue();
479 }
480
481 logger.debug("Compute minimum of Series: " + series);
482
483 double min = Double.MAX_VALUE;
484
485 for (int i = 0, n = dataset.getItemCount(series); i < n; i++) {
486 double tmpValue = dataset.getXValue(series, i);
487
488 if (tmpValue < min) {
489 min = tmpValue;
490 }
491 }
492
493 seriesMinimumX.put(key, Double.valueOf(min));
494
495 return min;
496 }
497
498
499 /**
500 * True if the given item of given dataset has the smallest
501 * Y value within this set.
502 */
503 public boolean isMinimum(XYDataset dataset, int series, int item) {
504 return dataset.getYValue(series, item) == getMinimum(dataset, series);
505 }
506
507
508 /**
509 * Get Minimum Y Value of a given series in a dataset.
510 * The value is stored for later use if queried the first time.
511 */
512 public double getMinimum(XYDataset dataset, int series) {
513 Integer key = Integer.valueOf(series);
514 Double old = seriesMinimum.get(key);
515
516 if (old != null) {
517 return old.doubleValue();
518 }
519
520 logger.debug("Compute minimum of Series: " + series);
521
522 double min = Double.MAX_VALUE;
523
524 for (int i = 0, n = dataset.getItemCount(series); i < n; i++) {
525 double tmpValue = dataset.getYValue(series, i);
526
527 if (tmpValue < min) {
528 min = tmpValue;
529 }
530 }
531
532 seriesMinimum.put(key, Double.valueOf(min));
533
534 return min;
535 }
536
537
538 /**
539 * True if the given item of given dataset has the biggest
540 * Y value within this set.
541 */
542 public boolean isMaximum(XYDataset dataset, int series, int item) {
543 return dataset.getYValue(series, item) == getMaximum(dataset, series);
544 }
545
546
547 /**
548 * Get maximum Y Value of a given series in a dataset.
549 * The value is stored for later use if queried the first time.
550 */
551 public double getMaximum(XYDataset dataset, int series) {
552 Integer key = Integer.valueOf(series);
553 Double old = seriesMaximum.get(key);
554
555 if (old != null) {
556 return old.doubleValue();
557 }
558
559 logger.debug("Compute maximum of Series: " + series);
560
561 double max = -Double.MAX_VALUE;
562
563 for (int i = 0, n = dataset.getItemCount(series); i < n; i++) {
564 double tmpValue = dataset.getYValue(series, i);
565
566 if (tmpValue > max) {
567 max = tmpValue;
568 }
569 }
570
571 seriesMaximum.put(key, Double.valueOf(max));
572
573 return max;
574 }
575 }
576 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org