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