comparison flys-artifacts/src/main/java/de/intevation/flys/jfree/EnhancedLineAndShapeRenderer.java @ 3468:f37e7e8907cb

merged flys-artifacts/2.8.1
author Thomas Arendsen Hein <thomas@intevation.de>
date Fri, 28 Sep 2012 12:14:39 +0200
parents 0b9b2a0c4e64
children f2a5fe968b98
comparison
equal deleted inserted replaced
3387:5ffad8bde8ad 3468:f37e7e8907cb
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 // Move to right until no collisions exist anymore
308 Shape hotspot = TextUtilities.calculateRotatedStringBounds(
309 waterlevelLabel, g2, (float)xx, (float)yy-3f,
310 TextAnchor.CENTER_LEFT,
311 0f, TextAnchor.CENTER_LEFT);
312 while (JFreeUtil.collides(hotspot, entities,
313 CollisionFreeLineLabelEntity.class)) {
314 xx += 5f;
315 hotspot = TextUtilities.calculateRotatedStringBounds(
316 waterlevelLabel, g2, (float)xx, (float)yy-3f, TextAnchor.CENTER_LEFT,
317 0f, TextAnchor.CENTER_LEFT);
318 }
319
320 // Register to avoid collissions.
321 entities.add(new CollisionFreeLineLabelEntity(hotspot,
322 1, "", ""));
323
324 // Fill background.
325 if (isShowLineLabelBG(series)) {
326 drawTextBox(g2, waterlevelLabel, (float)xx, (float)yy-3f,
327 getLineLabelBGColor(series));
328 }
329
330 g2.drawString(waterlevelLabel, (float)xx, (float)yy-3f);
331
332 g2.setFont(oldFont);
333 g2.setColor(oldColor);
334 }
335
336 int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
337 int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
338 updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
339 rangeAxisIndex, transX1, transY1, orientation);
340
341 // Add an entity for the item, but only if it falls within the data
342 // area...
343 if (entities != null && isPointInRect(dataArea, xx, yy)) {
344 addEntity(entities, entityArea, dataset, series, item, xx, yy);
345 }
346 }
347
348
349 /**
350 * Sets whether or not the minimum should be rendered with shape.
351 */
352 public void setIsMinimumShapeVisisble(int series, boolean isVisible) {
353 this.isMinimumShapeVisible.setBoolean(series, isVisible);
354 }
355
356
357 /**
358 * Whether or not the minimum should be rendered with shape.
359 */
360 public boolean isMinimumShapeVisible(int series) {
361 if (this.isMinimumShapeVisible.size() <= series) {
362 return false;
363 }
364
365 return isMinimumShapeVisible.getBoolean(series);
366 }
367
368
369 /**
370 * Sets whether or not the maximum should be rendered with shape.
371 */
372 public void setIsMaximumShapeVisible(int series, boolean isVisible) {
373 this.isMaximumShapeVisible.setBoolean(series, isVisible);
374 }
375
376
377 /**
378 * Whether or not the maximum should be rendered with shape.
379 */
380 public boolean isMaximumShapeVisible(int series) {
381 if (this.isMaximumShapeVisible.size() <= series) {
382 return false;
383 }
384
385 return isMaximumShapeVisible.getBoolean(series);
386 }
387
388 /** Whether or not a label should be shown for series. */
389 public boolean isShowLineLabel(int series) {
390 if (this.showLineLabel.size() <= series) {
391 return false;
392 }
393
394 return showLineLabel.getBoolean(series);
395 }
396
397
398 /** Sets whether or not a label should be shown for series. */
399 public void setShowLineLabel(boolean showLineLabel, int series) {
400 this.showLineLabel.setBoolean(series, showLineLabel);
401 }
402
403
404 /** Whether or not a label should be shown for series. */
405 public boolean isShowLineLabelBG(int series) {
406 if (this.showLineLabelBG.size() <= series) {
407 return false;
408 }
409
410 return showLineLabelBG.getBoolean(series);
411 }
412
413
414 public void setShowLineLabelBG(int series, boolean doShow) {
415 this.showLineLabelBG.setBoolean(series, doShow);
416 }
417
418 public Color getLineLabelBGColor(int series) {
419 if (this.lineLabelBGColors.size() <= series) {
420 return null;
421 }
422
423 return this.lineLabelBGColors.get(series);
424 }
425
426 public void setLineLabelBGColor(int series, Color color) {
427 this.lineLabelBGColors.put(series, color);
428 }
429
430 public Color getLineLabelTextColor(int series) {
431 if (this.lineLabelTextColors.size() <= series) {
432 return null;
433 }
434
435 return this.lineLabelTextColors.get(series);
436 }
437
438 public void setLineLabelTextColor(int series, Color color) {
439 this.lineLabelTextColors.put(series, color);
440 }
441
442 public void setLineLabelFont(Font font, int series) {
443 this.lineLabelFonts.put(series, font);
444 }
445
446 public Font getLineLabelFont(int series) {
447 return this.lineLabelFonts.get(series);
448 }
449
450
451 /**
452 * True if the given item of given dataset has the smallest
453 * X value within this set.
454 */
455 public boolean isMinimumX(XYDataset dataset, int series, int item) {
456 return dataset.getXValue(series, item) == getMinimumX(dataset, series);
457 }
458
459
460 /**
461 * Get Minimum X Value of a given series in a dataset.
462 * The value is stored for later use if queried the first time.
463 */
464 public double getMinimumX(XYDataset dataset, int series) {
465 Integer key = Integer.valueOf(series);
466 Double old = seriesMinimumX.get(key);
467
468 if (old != null) {
469 return old.doubleValue();
470 }
471
472 logger.debug("Compute minimum of Series: " + series);
473
474 double min = Double.MAX_VALUE;
475
476 for (int i = 0, n = dataset.getItemCount(series); i < n; i++) {
477 double tmpValue = dataset.getXValue(series, i);
478
479 if (tmpValue < min) {
480 min = tmpValue;
481 }
482 }
483
484 seriesMinimumX.put(key, Double.valueOf(min));
485
486 return min;
487 }
488
489
490 /**
491 * True if the given item of given dataset has the smallest
492 * Y value within this set.
493 */
494 public boolean isMinimum(XYDataset dataset, int series, int item) {
495 return dataset.getYValue(series, item) == getMinimum(dataset, series);
496 }
497
498
499 /**
500 * Get Minimum Y Value of a given series in a dataset.
501 * The value is stored for later use if queried the first time.
502 */
503 public double getMinimum(XYDataset dataset, int series) {
504 Integer key = Integer.valueOf(series);
505 Double old = seriesMinimum.get(key);
506
507 if (old != null) {
508 return old.doubleValue();
509 }
510
511 logger.debug("Compute minimum of Series: " + series);
512
513 double min = Double.MAX_VALUE;
514
515 for (int i = 0, n = dataset.getItemCount(series); i < n; i++) {
516 double tmpValue = dataset.getYValue(series, i);
517
518 if (tmpValue < min) {
519 min = tmpValue;
520 }
521 }
522
523 seriesMinimum.put(key, Double.valueOf(min));
524
525 return min;
526 }
527
528
529 /**
530 * True if the given item of given dataset has the biggest
531 * Y value within this set.
532 */
533 public boolean isMaximum(XYDataset dataset, int series, int item) {
534 return dataset.getYValue(series, item) == getMaximum(dataset, series);
535 }
536
537
538 /**
539 * Get maximum Y Value of a given series in a dataset.
540 * The value is stored for later use if queried the first time.
541 */
542 public double getMaximum(XYDataset dataset, int series) {
543 Integer key = Integer.valueOf(series);
544 Double old = seriesMaximum.get(key);
545
546 if (old != null) {
547 return old.doubleValue();
548 }
549
550 logger.debug("Compute maximum of Series: " + series);
551
552 double max = -Double.MAX_VALUE;
553
554 for (int i = 0, n = dataset.getItemCount(series); i < n; i++) {
555 double tmpValue = dataset.getYValue(series, i);
556
557 if (tmpValue > max) {
558 max = tmpValue;
559 }
560 }
561
562 seriesMaximum.put(key, Double.valueOf(max));
563
564 return max;
565 }
566 }
567 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org