Mercurial > dive4elements > river
comparison flys-artifacts/src/main/java/de/intevation/flys/jfree/EnhancedLineAndShapeRenderer.java @ 3938:c0cab28ba1ea
merged flys-artifacts
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Fri, 28 Sep 2012 12:15:03 +0200 |
parents | f2a5fe968b98 |
children |
comparison
equal
deleted
inserted
replaced
3865:436eec3be6ff | 3938:c0cab28ba1ea |
---|---|
1 package de.intevation.flys.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 : |