comparison flys-artifacts/src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.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 2a8919e0ed28
children a5f65e8983be a65eb6d44122
comparison
equal deleted inserted replaced
3387:5ffad8bde8ad 3468:f37e7e8907cb
1 /* ===========================================================
2 * JFreeChart : a free chart library for the Java(tm) platform
3 * ===========================================================
4 *
5 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
6 *
7 * Project Info: http://www.jfree.org/jfreechart/index.html
8 *
9 * This library is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 of the License, or
12 * (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17 * License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
22 * USA.
23 *
24 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
25 * in the United States and other countries.]
26 *
27 * -------------------------
28 * StableXYDifferenceRenderer.java
29 * -------------------------
30 * (C) Copyright 2003-2008, by Object Refinery Limited and Contributors.
31 *
32 * Original Author: David Gilbert (for Object Refinery Limited);
33 * Contributor(s): Richard West, Advanced Micro Devices, Inc. (major rewrite
34 * of difference drawing algorithm);
35 *
36 * Changes:
37 * --------
38 * 30-Apr-2003 : Version 1 (DG);
39 * 30-Jul-2003 : Modified entity constructor (CZ);
40 * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
41 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
42 * 09-Feb-2004 : Updated to support horizontal plot orientation (DG);
43 * 10-Feb-2004 : Added default constructor, setter methods and updated
44 * Javadocs (DG);
45 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
46 * 30-Mar-2004 : Fixed bug in getNegativePaint() method (DG);
47 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
48 * getYValue() (DG);
49 * 25-Aug-2004 : Fixed a bug preventing the use of crosshairs (DG);
50 * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
51 * 19-Jan-2005 : Now accesses only primitive values from dataset (DG);
52 * 22-Feb-2005 : Override getLegendItem(int, int) to return "line" items (DG);
53 * 13-Apr-2005 : Fixed shape positioning bug (id = 1182062) (DG);
54 * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
55 * 04-May-2005 : Override equals() method, renamed get/setPlotShapes() -->
56 * get/setShapesVisible (DG);
57 * 09-Jun-2005 : Updated equals() to handle GradientPaint (DG);
58 * 16-Jun-2005 : Fix bug (1221021) affecting stroke used for each series (DG);
59 * ------------- JFREECHART 1.0.x ---------------------------------------------
60 * 24-Jan-2007 : Added flag to allow rounding of x-coordinates, and fixed
61 * bug in clone() (DG);
62 * 05-Feb-2007 : Added an extra call to updateCrosshairValues() in
63 * drawItemPass1(), to fix bug 1564967 (DG);
64 * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
65 * 08-Mar-2007 : Fixed entity generation (DG);
66 * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
67 * 23-Apr-2007 : Rewrite of difference drawing algorithm to allow use of
68 * series with disjoint x-values (RW);
69 * 04-May-2007 : Set processVisibleItemsOnly flag to false (DG);
70 * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
71 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
72 * 05-Nov-2007 : Draw item labels if visible (RW);
73 * 17-Jun-2008 : Apply legend shape, font and paint attributes (DG);
74 */
75 /*
76 * For further changes within the FLYS project, refer to the ChangeLog.
77 */
78 package de.intevation.flys.jfree;
79
80 import java.awt.BasicStroke;
81 import java.awt.Color;
82 import java.awt.Graphics2D;
83 import java.awt.Font;
84 import java.awt.Paint;
85 import java.awt.geom.Point2D;
86 import java.awt.Shape;
87 import java.awt.Stroke;
88 import java.awt.geom.GeneralPath;
89 import java.awt.geom.Line2D;
90 import java.awt.geom.Rectangle2D;
91 import java.io.IOException;
92 import java.io.ObjectInputStream;
93 import java.io.ObjectOutputStream;
94 import java.util.ArrayList;
95 import java.util.Collections;
96 import java.util.LinkedList;
97 import java.util.List;
98
99 import org.jfree.chart.LegendItem;
100 import org.jfree.chart.axis.ValueAxis;
101 import org.jfree.chart.entity.EntityCollection;
102 import org.jfree.chart.entity.XYItemEntity;
103 import org.jfree.chart.event.RendererChangeEvent;
104 import org.jfree.chart.labels.XYToolTipGenerator;
105 import org.jfree.chart.plot.CrosshairState;
106 import org.jfree.chart.plot.PlotOrientation;
107 import org.jfree.chart.plot.PlotRenderingInfo;
108 import org.jfree.chart.plot.XYPlot;
109 import org.jfree.chart.urls.XYURLGenerator;
110 import org.jfree.data.xy.XYDataset;
111 import org.jfree.data.xy.DefaultXYDataset;
112 import org.jfree.io.SerialUtilities;
113 import org.jfree.ui.RectangleEdge;
114 import org.jfree.util.PaintUtilities;
115 import org.jfree.util.PublicCloneable;
116 import org.jfree.util.ShapeUtilities;
117
118 import org.jfree.chart.renderer.xy.AbstractXYItemRenderer;
119 import org.jfree.chart.renderer.xy.XYItemRenderer;
120 import org.jfree.chart.renderer.xy.XYItemRendererState;
121
122 import gnu.trove.TDoubleArrayList;
123
124 import de.intevation.flys.artifacts.math.Linear;
125
126 import org.apache.log4j.Logger;
127
128 /**
129 * A renderer for an {@link XYPlot} that highlights the differences between two
130 * series. The example shown here is generated by the
131 * <code>DifferenceChartDemo1.java</code> program included in the JFreeChart
132 * demo collection:
133 * <br><br>
134 * <img src="../../../../../images/StableXYDifferenceRendererSample.png"
135 * alt="StableXYDifferenceRendererSample.png" />
136 */
137 public class StableXYDifferenceRenderer extends AbstractXYItemRenderer
138 implements XYItemRenderer, PublicCloneable {
139
140 private static Logger log = Logger.getLogger(StableXYDifferenceRenderer.class);
141
142 public static final int CALCULATE_POSITIVE_AREA = 1;
143 public static final int CALCULATE_NEGATIVE_AREA = 2;
144 public static final int CALCULATE_ALL_AREA =
145 CALCULATE_POSITIVE_AREA | CALCULATE_NEGATIVE_AREA;
146
147 /** For serialization. */
148 private static final long serialVersionUID = -8447915602375584857L;
149
150 /** The paint used to highlight positive differences (y(0) > y(1)). */
151 private transient Paint positivePaint;
152
153 /** The paint used to highlight negative differences (y(0) < y(1)). */
154 private transient Paint negativePaint;
155
156 /** Display shapes at each point? */
157 private boolean shapesVisible;
158
159 /** Display shapes at each point? */
160 protected boolean drawOutline;
161
162 /** Which stroke to draw outline with? */
163 protected Stroke outlineStroke;
164
165 /** Which paint to draw outline with? */
166 protected Paint outlinePaint;
167
168 /** The shape to display in the legend item. */
169 private transient Shape legendShape;
170
171 protected boolean drawOriginalSeries;
172
173 /** The color of the label showing the calculated area. */
174 protected Color labelColor;
175
176 /** The background color of the label showing the calculated area. */
177 protected Color labelBGColor;
178
179 /** Font to draw label of calculated area with. */
180 protected Font labelFont;
181
182 protected int areaCalculationMode;
183
184 protected double positiveArea;
185 protected double negativeArea;
186
187 /** Whether or not to draw a label in the area. */
188 protected boolean labelArea = true;
189
190
191 /** Arithmetic centroid of drawn polygons. */
192 protected Point2D.Double centroid;
193
194
195 /** Number of points that contributed to the centroid. */
196 protected int centroidNPoints = 0;
197
198
199 /**
200 * This flag controls whether or not the x-coordinates (in Java2D space)
201 * are rounded to integers. When set to true, this can avoid the vertical
202 * striping that anti-aliasing can generate. However, the rounding may not
203 * be appropriate for output in high resolution formats (for example,
204 * vector graphics formats such as SVG and PDF).
205 *
206 * @since 1.0.4
207 */
208 private boolean roundXCoordinates;
209
210 /**
211 * Creates a new renderer with default attributes.
212 */
213 public StableXYDifferenceRenderer() {
214 this(Color.green, Color.red, false /*, null */);
215 }
216
217 public StableXYDifferenceRenderer(Paint positivePaint, Paint negativePaint,
218 boolean shapes) {
219 this(positivePaint, negativePaint, shapes, CALCULATE_ALL_AREA);
220 }
221
222 /**
223 * Creates a new renderer.
224 *
225 * @param positivePaint the highlight color for positive differences
226 * (<code>null</code> not permitted).
227 * @param negativePaint the highlight color for negative differences
228 * (<code>null</code> not permitted).
229 * @param shapes draw shapes?
230 */
231 public StableXYDifferenceRenderer(Paint positivePaint, Paint negativePaint,
232 boolean shapes, int areaCalculationMode) {
233 if (positivePaint == null) {
234 throw new IllegalArgumentException(
235 "Null 'positivePaint' argument.");
236 }
237 if (negativePaint == null) {
238 throw new IllegalArgumentException(
239 "Null 'negativePaint' argument.");
240 }
241 this.positivePaint = positivePaint;
242 this.negativePaint = negativePaint;
243 this.shapesVisible = shapes;
244 this.legendShape = new Rectangle2D.Double(-3.0, -3.0, 10.0, 10.0);
245 this.roundXCoordinates = false;
246 this.drawOutline = true;
247 this.outlineStroke = new BasicStroke(1);
248 this.outlinePaint = Color.black;
249 this.drawOriginalSeries = false;
250 this.areaCalculationMode = areaCalculationMode;
251 this.labelBGColor = null;
252 this.centroid = new Point2D.Double(0,0);
253 }
254
255 public int getAreaCalculationMode() {
256 return areaCalculationMode;
257 }
258
259 public void setAreaCalculationMode(int areaCalculationMode) {
260 this.areaCalculationMode = areaCalculationMode;
261 }
262
263 public boolean isLabelArea() {
264 return this.labelArea;
265 }
266
267 public void setLabelArea(boolean label) {
268 this.labelArea = label;
269 }
270
271
272 /** Set font to paint label with. */
273 public void setLabelFont(Font font) {
274 this.labelFont = font;
275 }
276
277
278 /** Get font with which label is painted. */
279 public Font getLabelFont() {
280 return this.labelFont;
281 }
282
283
284 /** Set color with which to paint label. */
285 public void setLabelColor(Color color) {
286 this.labelColor = color;
287 }
288
289
290 /** Get color with which label is painted. */
291 public Color getLabelColor() {
292 return this.labelColor;
293 }
294
295
296 /** Set color with which to paint label bg. */
297 public void setLabelBGColor(Color color) {
298 this.labelBGColor = color;
299 }
300
301
302 /** Get color with which label is painted. */
303 public Color getLabelBGColor() {
304 return this.labelBGColor;
305 }
306
307
308 public double getCalculatedArea() {
309 return positiveArea + negativeArea;
310 }
311
312 /**
313 * Sets color that is used if drawOutline is true.
314 */
315 public void setOutlinePaint(Paint outlinePaint) {
316 this.outlinePaint = outlinePaint;
317 }
318
319
320 /**
321 * Gets color which is used if drawOutline is true.
322 */
323 public Paint getOutlinePaint() {
324 return this.outlinePaint;
325 }
326
327
328 /**
329 * Sets Stroke that is used if drawOutline is true.
330 */
331 public void setOutlineStroke(Stroke stroke) {
332 this.outlineStroke = stroke;
333 }
334
335
336 /**
337 * Returns Stroke that is used if drawOutline is true.
338 */
339 public Stroke getOutlineStroke() {
340 return this.outlineStroke;
341 }
342
343
344 /**
345 * Whether or not to draw the 'Shape' of the area (in contrast to
346 * shapes at data items).
347 */
348 public void setDrawOutline(boolean doDrawOutline) {
349 this.drawOutline = doDrawOutline;
350 }
351
352
353 /**
354 * Returns whether or not to draw the shape of the outline.
355 */
356 public boolean getDrawOutline() {
357 return this.drawOutline;
358 }
359
360
361 /**
362 * Returns the paint used to highlight positive differences.
363 *
364 * @return The paint (never <code>null</code>).
365 *
366 * @see #setPositivePaint(Paint)
367 */
368 public Paint getPositivePaint() {
369 return this.positivePaint;
370 }
371
372 /**
373 * Sets the paint used to highlight positive differences and sends a
374 * {@link RendererChangeEvent} to all registered listeners.
375 *
376 * @param paint the paint (<code>null</code> not permitted).
377 *
378 * @see #getPositivePaint()
379 */
380 public void setPositivePaint(Paint paint) {
381 if (paint == null) {
382 throw new IllegalArgumentException("Null 'paint' argument.");
383 }
384 this.positivePaint = paint;
385 fireChangeEvent();
386 }
387
388 /**
389 * Returns the paint used to highlight negative differences.
390 *
391 * @return The paint (never <code>null</code>).
392 *
393 * @see #setNegativePaint(Paint)
394 */
395 public Paint getNegativePaint() {
396 return this.negativePaint;
397 }
398
399 /**
400 * Sets the paint used to highlight negative differences.
401 *
402 * @param paint the paint (<code>null</code> not permitted).
403 *
404 * @see #getNegativePaint()
405 */
406 public void setNegativePaint(Paint paint) {
407 if (paint == null) {
408 throw new IllegalArgumentException("Null 'paint' argument.");
409 }
410 this.negativePaint = paint;
411 notifyListeners(new RendererChangeEvent(this));
412 }
413
414 /**
415 * Returns a flag that controls whether or not shapes are drawn for each
416 * data value.
417 *
418 * @return A boolean.
419 *
420 * @see #setShapesVisible(boolean)
421 */
422 public boolean getShapesVisible() {
423 return this.shapesVisible;
424 }
425
426 /**
427 * Sets a flag that controls whether or not shapes are drawn for each
428 * data value, and sends a {@link RendererChangeEvent} to all registered
429 * listeners.
430 *
431 * @param flag the flag.
432 *
433 * @see #getShapesVisible()
434 */
435 public void setShapesVisible(boolean flag) {
436 this.shapesVisible = flag;
437 fireChangeEvent();
438 }
439
440 /**
441 * Returns the shape used to represent a line in the legend.
442 *
443 * @return The legend line (never <code>null</code>).
444 *
445 * @see #setLegendLine(Shape)
446 */
447 public Shape getLegendLine() {
448 return this.legendShape;
449 }
450
451 /**
452 * Sets the shape used as a line in each legend item and sends a
453 * {@link RendererChangeEvent} to all registered listeners.
454 *
455 * @param line the line (<code>null</code> not permitted).
456 *
457 * @see #getLegendLine()
458 */
459 public void setLegendLine(Shape line) {
460 if (line == null) {
461 throw new IllegalArgumentException("Null 'line' argument.");
462 }
463 this.legendShape = line;
464 fireChangeEvent();
465 }
466
467 /**
468 * Returns the flag that controls whether or not the x-coordinates (in
469 * Java2D space) are rounded to integer values.
470 *
471 * @return The flag.
472 *
473 * @since 1.0.4
474 *
475 * @see #setRoundXCoordinates(boolean)
476 */
477 public boolean getRoundXCoordinates() {
478 return this.roundXCoordinates;
479 }
480
481 /**
482 * Sets the flag that controls whether or not the x-coordinates (in
483 * Java2D space) are rounded to integer values, and sends a
484 * {@link RendererChangeEvent} to all registered listeners.
485 *
486 * @param round the new flag value.
487 *
488 * @since 1.0.4
489 *
490 * @see #getRoundXCoordinates()
491 */
492 public void setRoundXCoordinates(boolean round) {
493 this.roundXCoordinates = round;
494 fireChangeEvent();
495 }
496
497 /**
498 * Initialises the renderer and returns a state object that should be
499 * passed to subsequent calls to the drawItem() method. This method will
500 * be called before the first item is rendered, giving the renderer an
501 * opportunity to initialise any state information it wants to maintain.
502 * The renderer can do nothing if it chooses.
503 *
504 * @param g2 the graphics device.
505 * @param dataArea the (visible) area inside the axes.
506 * @param plot the plot.
507 * @param data the data.
508 * @param info an optional info collection object to return data back to
509 * the caller.
510 *
511 * @return A state object.
512 */
513 public XYItemRendererState initialise(Graphics2D g2,
514 Rectangle2D dataArea,
515 XYPlot plot,
516 XYDataset data,
517 PlotRenderingInfo info) {
518
519 XYItemRendererState state = super.initialise(g2, dataArea, plot, data,
520 info);
521 state.setProcessVisibleItemsOnly(false);
522 return state;
523 }
524
525 /**
526 * Returns <code>2</code>, the number of passes required by the renderer.
527 * The {@link XYPlot} will run through the dataset this number of times.
528 *
529 * @return The number of passes required by the renderer.
530 */
531 public int getPassCount() {
532 return 2;
533 }
534
535
536 /**
537 * Adds x/y data to series.
538 */
539 private static final void addSeries(
540 DefaultXYDataset ds,
541 Comparable key,
542 TDoubleArrayList xs,
543 TDoubleArrayList ys
544 ) {
545 ds.addSeries(
546 key,
547 new double [][] {
548 xs.toNativeArray(),
549 ys.toNativeArray()
550 });
551 }
552
553 protected static List<XYDataset> splitByNaNsOneSeries(
554 XYDataset dataset
555 ) {
556 List<XYDataset> datasets = new ArrayList<XYDataset>();
557
558 int N = dataset.getItemCount(0);
559 TDoubleArrayList xs = new TDoubleArrayList(N);
560 TDoubleArrayList ys = new TDoubleArrayList(N);
561 for (int i = 0; i < N; ++i) {
562 double x = dataset.getXValue(0, i);
563 double y = dataset.getYValue(0, i);
564 if (Double.isNaN(x) || Double.isNaN(y)) {
565 if (!xs.isEmpty()) {
566 DefaultXYDataset ds = new DefaultXYDataset();
567 addSeries(ds, dataset.getSeriesKey(0), xs, ys);
568 datasets.add(ds);
569 xs.resetQuick();
570 ys.resetQuick();
571 }
572 }
573 else {
574 xs.add(x);
575 ys.add(y);
576 }
577 }
578 if (!xs.isEmpty()) {
579 DefaultXYDataset ds = new DefaultXYDataset();
580 addSeries(ds, dataset.getSeriesKey(0), xs, ys);
581 datasets.add(ds);
582 }
583
584 return datasets;
585 }
586
587 private static final boolean add(TDoubleArrayList xs, double x) {
588 int N = xs.size();
589 if (N == 0 || xs.getQuick(N-1) < x) {
590 xs.add(x);
591 return true;
592 }
593 log.debug("pushed smaller");
594 return false;
595 }
596
597 protected static List<XYDataset> splitByNaNsTwoSeries(
598 XYDataset dataset
599 ) {
600 boolean debug = log.isDebugEnabled();
601
602 List<XYDataset> datasets = new ArrayList<XYDataset>();
603
604 int N = dataset.getItemCount(0);
605 int M = dataset.getItemCount(1);
606
607 int i = 0, j = 0;
608 // ignore leading NaNs
609 for (; i < N; ++i) {
610 double x = dataset.getXValue(0, i);
611 double y = dataset.getYValue(0, i);
612 if (!Double.isNaN(x) && !Double.isNaN(y)) {
613 break;
614 }
615 }
616
617 for (; j < M; ++j) {
618 double x = dataset.getXValue(1, j);
619 double y = dataset.getYValue(1, j);
620 if (!Double.isNaN(x) && !Double.isNaN(y)) {
621 break;
622 }
623 }
624
625 TDoubleArrayList six = new TDoubleArrayList();
626 TDoubleArrayList siy = new TDoubleArrayList();
627 TDoubleArrayList sjx = new TDoubleArrayList();
628 TDoubleArrayList sjy = new TDoubleArrayList();
629
630 while (i < N && j < M) {
631 int ni = i+1;
632 for (; ni < N && !Double.isNaN(dataset.getXValue(0, ni)); ++ni);
633 for (; ni < N && Double.isNaN(dataset.getXValue(0, ni)); ++ni);
634
635 int nj = j+1;
636 for (; nj < M && !Double.isNaN(dataset.getXValue(1, nj)); ++nj);
637 for (; nj < M && Double.isNaN(dataset.getXValue(1, nj)); ++nj);
638
639 if (ni == N && nj == M) { // no more splits
640 log.debug("no more splits ....");
641 for (; i < ni; ++i) {
642 double x = dataset.getXValue(0, i);
643 double y = dataset.getYValue(0, i);
644 if (!Double.isNaN(x)
645 && !Double.isNaN(y)
646 && add(six, x)) {
647 siy.add(y);
648 }
649 }
650 for (; j < nj; ++j) {
651 double x = dataset.getXValue(1, j);
652 double y = dataset.getYValue(1, j);
653 if (!Double.isNaN(x)
654 && !Double.isNaN(y)
655 && add(sjx, x)) {
656 sjy.add(y);
657 }
658 }
659 if (!six.isEmpty() && !sjx.isEmpty()) {
660 DefaultXYDataset ds = new DefaultXYDataset();
661 addSeries(ds, dataset.getSeriesKey(0), six, siy);
662 addSeries(ds, dataset.getSeriesKey(1), sjx, sjy);
663 datasets.add(ds);
664 }
665 break;
666 }
667
668 if (debug) {
669 log.debug("ni: " + ni + " " + N);
670 log.debug("nj: " + nj + " " + M);
671 }
672
673 double xni = ni < N
674 ? dataset.getXValue(0, ni)
675 : Double.MAX_VALUE;
676
677 double xnj = nj < M
678 ? dataset.getXValue(1, nj)
679 : Double.MAX_VALUE;
680
681 double xns = Math.min(xni, xnj);
682
683 double pushxi = Double.NaN;
684 double pushyi = Double.NaN;
685 double pushxj = Double.NaN;
686 double pushyj = Double.NaN;
687
688 for (; i < ni; ++i) {
689 double x = dataset.getXValue(0, i);
690 double y = dataset.getYValue(0, i);
691 if (Double.isNaN(x) || Double.isNaN(y)) {
692 continue;
693 }
694 if (x < xns) {
695 if (add(six, x)) {
696 siy.add(y);
697 }
698 continue;
699 }
700 if (x == xns) { // exact match
701 if (add(six, x)) {
702 siy.add(y);
703 }
704 pushxi = x; pushyi = y;
705 }
706 else { // x > xns: intersection
707 if (debug) {
708 log.debug("xns: " + xns);
709 log.debug("x/y: " + x + " / " + y);
710 }
711 int SIX = six.size();
712 if (SIX > 0) { // should always be true
713 double yns = Linear.linear(
714 xns,
715 six.getQuick(SIX-1), x,
716 siy.getQuick(SIX-1), y);
717 if (debug) {
718 log.debug("intersection at: " + yns);
719 }
720 if (add(six, xns)) {
721 siy.add(yns);
722 }
723 pushxi = xns;
724 pushyi = yns;
725 }
726 }
727 break; // Split point reached.
728 }
729
730 for (; j < nj; ++j) {
731 double x = dataset.getXValue(1, j);
732 double y = dataset.getYValue(1, j);
733 if (Double.isNaN(x) || Double.isNaN(y)) {
734 continue;
735 }
736 if (x < xns) {
737 if (add(sjx, x)) {
738 sjy.add(y);
739 }
740 continue;
741 }
742 if (x == xns) { // exact match
743 if (add(sjx, x)) {
744 sjy.add(y);
745 }
746 pushxj = x; pushyj = y;
747 }
748 else { // x > xns: intersection
749 int SJX = sjx.size();
750 if (SJX > 0) { // should always be true
751 double yns = Linear.linear(
752 xns,
753 sjx.getQuick(SJX-1), x,
754 sjy.getQuick(SJX-1), y);
755 if (debug) {
756 log.debug("intersection at: " + yns);
757 }
758 if (add(sjx, xns)) {
759 sjy.add(yns);
760 }
761 pushxj = xns; pushyj = yns;
762 }
763 }
764 break; // Split point reached.
765 }
766
767 if (!six.isEmpty() && !sjx.isEmpty()) {
768 DefaultXYDataset ds = new DefaultXYDataset();
769 addSeries(ds, dataset.getSeriesKey(0), six, siy);
770 addSeries(ds, dataset.getSeriesKey(1), sjx, sjy);
771 datasets.add(ds);
772 }
773
774 six.resetQuick(); siy.resetQuick();
775 sjx.resetQuick(); sjy.resetQuick();
776
777 // Push split points.
778 if (!Double.isNaN(pushxi)) {
779 six.add(pushxi);
780 siy.add(pushyi);
781 }
782
783 if (!Double.isNaN(pushxj)) {
784 sjx.add(pushxj);
785 sjy.add(pushyj);
786 }
787 }
788
789 // Copy the rest.
790 for (; i < N; ++i) {
791 double x = dataset.getXValue(0, i);
792 double y = dataset.getXValue(0, i);
793 if (!Double.isNaN(x)
794 && !Double.isNaN(y)
795 && add(six, x)) {
796 siy.add(y);
797 }
798 }
799
800 for (; j < M; ++j) {
801 double x = dataset.getXValue(1, j);
802 double y = dataset.getXValue(1, j);
803 if (!Double.isNaN(x)
804 && !Double.isNaN(y)
805 && add(sjx, x)) {
806 sjy.add(y);
807 }
808 }
809
810 // Build final dataset.
811 if (!six.isEmpty() && !sjx.isEmpty()) {
812 DefaultXYDataset ds = new DefaultXYDataset();
813 addSeries(ds, dataset.getSeriesKey(0), six, siy);
814 addSeries(ds, dataset.getSeriesKey(1), sjx, sjy);
815 datasets.add(ds);
816 }
817
818 if (debug) {
819 log.debug("datasets after split: " + datasets.size());
820 }
821
822 return datasets;
823 }
824
825 public static List<XYDataset> splitByNaNs(XYDataset dataset) {
826
827 switch (dataset.getSeriesCount()) {
828 case 0:
829 return Collections.emptyList();
830 case 1:
831 return splitByNaNsOneSeries(dataset);
832 default: // two or more
833 return splitByNaNsTwoSeries(dataset);
834 }
835 }
836
837
838 /**
839 * Draws the visual representation of a single data item.
840 *
841 * @param g2 the graphics device.
842 * @param state the renderer state.
843 * @param dataArea the area within which the data is being drawn.
844 * @param info collects information about the drawing.
845 * @param plot the plot (can be used to obtain standard color
846 * information etc).
847 * @param domainAxis the domain (horizontal) axis.
848 * @param rangeAxis the range (vertical) axis.
849 * @param dataset the dataset.
850 * @param series the series index (zero-based).
851 * @param item the item index (zero-based).
852 * @param crosshairState crosshair information for the plot
853 * (<code>null</code> permitted).
854 * @param pass the pass index.
855 */
856 public void drawItem(Graphics2D g2,
857 XYItemRendererState state,
858 Rectangle2D dataArea,
859 PlotRenderingInfo info,
860 XYPlot plot,
861 ValueAxis domainAxis,
862 ValueAxis rangeAxis,
863 XYDataset dataset,
864 int series,
865 int item,
866 CrosshairState crosshairState,
867 int pass) {
868 switch (pass) {
869 case 0:
870 for (XYDataset ds: splitByNaNs(dataset)) {
871 drawItemPass0(g2, dataArea, info,
872 plot, domainAxis, rangeAxis,
873 ds, series, item, crosshairState);
874 }
875 break;
876 case 1:
877 drawItemPass1(g2, dataArea, info,
878 plot, domainAxis, rangeAxis,
879 dataset, series, item, crosshairState);
880 }
881
882 // Find geometric middle, calculate area and paint a string with it here.
883 // TODO also i18n
884 if (pass == 1 && this.labelArea) {
885 double center_x = centroid.getX();
886 double center_y = centroid.getY();
887 center_x = domainAxis.valueToJava2D(center_x, dataArea,
888 plot.getDomainAxisEdge());
889 center_y = rangeAxis.valueToJava2D(center_y, dataArea,
890 plot.getRangeAxisEdge());
891
892 // Respect text-extend if text should appear really centered.
893
894 float area = 0f;
895 if (areaCalculationMode == CALCULATE_POSITIVE_AREA
896 || areaCalculationMode == CALCULATE_ALL_AREA) {
897 area += Math.abs(positiveArea);
898 }
899 if (areaCalculationMode == CALCULATE_NEGATIVE_AREA
900 || areaCalculationMode == CALCULATE_ALL_AREA) {
901 area += Math.abs(negativeArea);
902 }
903 if (area != 0f) {
904 Color oldColor = g2.getColor();
905 Font oldFont = g2.getFont();
906 g2.setFont(labelFont);
907 String labelText = "Area= " + area + "m2";
908 if (labelBGColor != null) {
909 EnhancedLineAndShapeRenderer.drawTextBox(g2, labelText,
910 (float)center_x, (float)center_y, labelBGColor);
911 }
912 g2.setColor(labelColor);
913 g2.drawString(labelText, (float)center_x, (float)center_y);
914 g2.setFont(oldFont);
915 g2.setColor(oldColor);
916 }
917 }
918 }
919
920 /**
921 * Draws the visual representation of a single data item, first pass.
922 *
923 * @param x_graphics the graphics device.
924 * @param x_dataArea the area within which the data is being drawn.
925 * @param x_info collects information about the drawing.
926 * @param x_plot the plot (can be used to obtain standard color
927 * information etc).
928 * @param x_domainAxis the domain (horizontal) axis.
929 * @param x_rangeAxis the range (vertical) axis.
930 * @param x_dataset the dataset.
931 * @param x_series the series index (zero-based).
932 * @param x_item the item index (zero-based).
933 * @param x_crosshairState crosshair information for the plot
934 * (<code>null</code> permitted).
935 */
936 protected void drawItemPass0(Graphics2D x_graphics,
937 Rectangle2D x_dataArea,
938 PlotRenderingInfo x_info,
939 XYPlot x_plot,
940 ValueAxis x_domainAxis,
941 ValueAxis x_rangeAxis,
942 XYDataset x_dataset,
943 int x_series,
944 int x_item,
945 CrosshairState x_crosshairState) {
946
947 if (!((0 == x_series) && (0 == x_item))) {
948 return;
949 }
950
951 boolean b_impliedZeroSubtrahend = (1 == x_dataset.getSeriesCount());
952
953 // check if either series is a degenerate case (i.e. less than 2 points)
954 if (isEitherSeriesDegenerate(x_dataset, b_impliedZeroSubtrahend)) {
955 return;
956 }
957
958 // check if series are disjoint (i.e. domain-spans do not overlap)
959 if (!b_impliedZeroSubtrahend && areSeriesDisjoint(x_dataset)) {
960 return;
961 }
962
963 // polygon definitions
964 LinkedList l_minuendXs = new LinkedList();
965 LinkedList l_minuendYs = new LinkedList();
966 LinkedList l_subtrahendXs = new LinkedList();
967 LinkedList l_subtrahendYs = new LinkedList();
968 LinkedList l_polygonXs = new LinkedList();
969 LinkedList l_polygonYs = new LinkedList();
970
971 // state
972 int l_minuendItem = 0;
973 int l_minuendItemCount = x_dataset.getItemCount(0);
974 Double l_minuendCurX = null;
975 Double l_minuendNextX = null;
976 Double l_minuendCurY = null;
977 Double l_minuendNextY = null;
978 double l_minuendMaxY = Double.NEGATIVE_INFINITY;
979 double l_minuendMinY = Double.POSITIVE_INFINITY;
980
981 int l_subtrahendItem = 0;
982 int l_subtrahendItemCount = 0; // actual value set below
983 Double l_subtrahendCurX = null;
984 Double l_subtrahendNextX = null;
985 Double l_subtrahendCurY = null;
986 Double l_subtrahendNextY = null;
987 double l_subtrahendMaxY = Double.NEGATIVE_INFINITY;
988 double l_subtrahendMinY = Double.POSITIVE_INFINITY;
989
990 // if a subtrahend is not specified, assume it is zero
991 if (b_impliedZeroSubtrahend) {
992 l_subtrahendItem = 0;
993 l_subtrahendItemCount = 2;
994 l_subtrahendCurX = new Double(x_dataset.getXValue(0, 0));
995 l_subtrahendNextX = new Double(x_dataset.getXValue(0,
996 (l_minuendItemCount - 1)));
997 l_subtrahendCurY = new Double(0.0);
998 l_subtrahendNextY = new Double(0.0);
999 l_subtrahendMaxY = 0.0;
1000 l_subtrahendMinY = 0.0;
1001
1002 l_subtrahendXs.add(l_subtrahendCurX);
1003 l_subtrahendYs.add(l_subtrahendCurY);
1004 }
1005 else {
1006 l_subtrahendItemCount = x_dataset.getItemCount(1);
1007 }
1008
1009 boolean b_minuendDone = false;
1010 boolean b_minuendAdvanced = true;
1011 boolean b_minuendAtIntersect = false;
1012 boolean b_minuendFastForward = false;
1013 boolean b_subtrahendDone = false;
1014 boolean b_subtrahendAdvanced = true;
1015 boolean b_subtrahendAtIntersect = false;
1016 boolean b_subtrahendFastForward = false;
1017 boolean b_colinear = false;
1018
1019 boolean b_positive;
1020
1021 // coordinate pairs
1022 double l_x1 = 0.0, l_y1 = 0.0; // current minuend point
1023 double l_x2 = 0.0, l_y2 = 0.0; // next minuend point
1024 double l_x3 = 0.0, l_y3 = 0.0; // current subtrahend point
1025 double l_x4 = 0.0, l_y4 = 0.0; // next subtrahend point
1026
1027 // fast-forward through leading tails
1028 boolean b_fastForwardDone = false;
1029 while (!b_fastForwardDone) {
1030 // get the x and y coordinates
1031 l_x1 = x_dataset.getXValue(0, l_minuendItem);
1032 l_y1 = x_dataset.getYValue(0, l_minuendItem);
1033 l_x2 = x_dataset.getXValue(0, l_minuendItem + 1);
1034 l_y2 = x_dataset.getYValue(0, l_minuendItem + 1);
1035
1036 l_minuendCurX = new Double(l_x1);
1037 l_minuendCurY = new Double(l_y1);
1038 l_minuendNextX = new Double(l_x2);
1039 l_minuendNextY = new Double(l_y2);
1040
1041 if (b_impliedZeroSubtrahend) {
1042 l_x3 = l_subtrahendCurX.doubleValue();
1043 l_y3 = l_subtrahendCurY.doubleValue();
1044 l_x4 = l_subtrahendNextX.doubleValue();
1045 l_y4 = l_subtrahendNextY.doubleValue();
1046 }
1047 else {
1048 l_x3 = x_dataset.getXValue(1, l_subtrahendItem);
1049 l_y3 = x_dataset.getYValue(1, l_subtrahendItem);
1050 l_x4 = x_dataset.getXValue(1, l_subtrahendItem + 1);
1051 l_y4 = x_dataset.getYValue(1, l_subtrahendItem + 1);
1052
1053 l_subtrahendCurX = new Double(l_x3);
1054 l_subtrahendCurY = new Double(l_y3);
1055 l_subtrahendNextX = new Double(l_x4);
1056 l_subtrahendNextY = new Double(l_y4);
1057 }
1058
1059 if (l_x2 <= l_x3) {
1060 // minuend needs to be fast forwarded
1061 l_minuendItem++;
1062 b_minuendFastForward = true;
1063 continue;
1064 }
1065
1066 if (l_x4 <= l_x1) {
1067 // subtrahend needs to be fast forwarded
1068 l_subtrahendItem++;
1069 b_subtrahendFastForward = true;
1070 continue;
1071 }
1072
1073 // check if initial polygon needs to be clipped
1074 if ((l_x3 < l_x1) && (l_x1 < l_x4)) {
1075 // project onto subtrahend
1076 double l_slope = (l_y4 - l_y3) / (l_x4 - l_x3);
1077 l_subtrahendCurX = l_minuendCurX;
1078 l_subtrahendCurY = new Double((l_slope * l_x1)
1079 + (l_y3 - (l_slope * l_x3)));
1080
1081 l_subtrahendXs.add(l_subtrahendCurX);
1082 l_subtrahendYs.add(l_subtrahendCurY);
1083 }
1084
1085 if ((l_x1 < l_x3) && (l_x3 < l_x2)) {
1086 // project onto minuend
1087 double l_slope = (l_y2 - l_y1) / (l_x2 - l_x1);
1088 l_minuendCurX = l_subtrahendCurX;
1089 l_minuendCurY = new Double((l_slope * l_x3)
1090 + (l_y1 - (l_slope * l_x1)));
1091
1092 l_minuendXs.add(l_minuendCurX);
1093 l_minuendYs.add(l_minuendCurY);
1094 }
1095
1096 l_minuendMaxY = l_minuendCurY.doubleValue();
1097 l_minuendMinY = l_minuendCurY.doubleValue();
1098 l_subtrahendMaxY = l_subtrahendCurY.doubleValue();
1099 l_subtrahendMinY = l_subtrahendCurY.doubleValue();
1100
1101 b_fastForwardDone = true;
1102 }
1103
1104 // start of algorithm
1105 while (!b_minuendDone && !b_subtrahendDone) {
1106 if (!b_minuendDone && !b_minuendFastForward && b_minuendAdvanced) {
1107 l_x1 = x_dataset.getXValue(0, l_minuendItem);
1108 l_y1 = x_dataset.getYValue(0, l_minuendItem);
1109 l_minuendCurX = new Double(l_x1);
1110 l_minuendCurY = new Double(l_y1);
1111
1112 if (!b_minuendAtIntersect) {
1113 l_minuendXs.add(l_minuendCurX);
1114 l_minuendYs.add(l_minuendCurY);
1115 }
1116
1117 l_minuendMaxY = Math.max(l_minuendMaxY, l_y1);
1118 l_minuendMinY = Math.min(l_minuendMinY, l_y1);
1119
1120 l_x2 = x_dataset.getXValue(0, l_minuendItem + 1);
1121 l_y2 = x_dataset.getYValue(0, l_minuendItem + 1);
1122 l_minuendNextX = new Double(l_x2);
1123 l_minuendNextY = new Double(l_y2);
1124 }
1125
1126 // never updated the subtrahend if it is implied to be zero
1127 if (!b_impliedZeroSubtrahend && !b_subtrahendDone
1128 && !b_subtrahendFastForward && b_subtrahendAdvanced) {
1129 l_x3 = x_dataset.getXValue(1, l_subtrahendItem);
1130 l_y3 = x_dataset.getYValue(1, l_subtrahendItem);
1131 l_subtrahendCurX = new Double(l_x3);
1132 l_subtrahendCurY = new Double(l_y3);
1133
1134 if (!b_subtrahendAtIntersect) {
1135 l_subtrahendXs.add(l_subtrahendCurX);
1136 l_subtrahendYs.add(l_subtrahendCurY);
1137 }
1138
1139 l_subtrahendMaxY = Math.max(l_subtrahendMaxY, l_y3);
1140 l_subtrahendMinY = Math.min(l_subtrahendMinY, l_y3);
1141
1142 l_x4 = x_dataset.getXValue(1, l_subtrahendItem + 1);
1143 l_y4 = x_dataset.getYValue(1, l_subtrahendItem + 1);
1144 l_subtrahendNextX = new Double(l_x4);
1145 l_subtrahendNextY = new Double(l_y4);
1146 }
1147
1148 // deassert b_*FastForward (only matters for 1st time through loop)
1149 b_minuendFastForward = false;
1150 b_subtrahendFastForward = false;
1151
1152 Double l_intersectX = null;
1153 Double l_intersectY = null;
1154 boolean b_intersect = false;
1155
1156 b_minuendAtIntersect = false;
1157 b_subtrahendAtIntersect = false;
1158
1159 // check for intersect
1160 if ((l_x2 == l_x4) && (l_y2 == l_y4)) {
1161 // check if line segments are colinear
1162 if ((l_x1 == l_x3) && (l_y1 == l_y3)) {
1163 b_colinear = true;
1164 }
1165 else {
1166 // the intersect is at the next point for both the minuend
1167 // and subtrahend
1168 l_intersectX = new Double(l_x2);
1169 l_intersectY = new Double(l_y2);
1170
1171 b_intersect = true;
1172 b_minuendAtIntersect = true;
1173 b_subtrahendAtIntersect = true;
1174 }
1175 }
1176 else {
1177 // compute common denominator
1178 double l_denominator = ((l_y4 - l_y3) * (l_x2 - l_x1))
1179 - ((l_x4 - l_x3) * (l_y2 - l_y1));
1180
1181 // compute common deltas
1182 double l_deltaY = l_y1 - l_y3;
1183 double l_deltaX = l_x1 - l_x3;
1184
1185 // compute numerators
1186 double l_numeratorA = ((l_x4 - l_x3) * l_deltaY)
1187 - ((l_y4 - l_y3) * l_deltaX);
1188 double l_numeratorB = ((l_x2 - l_x1) * l_deltaY)
1189 - ((l_y2 - l_y1) * l_deltaX);
1190
1191 // check if line segments are colinear
1192 if ((0 == l_numeratorA) && (0 == l_numeratorB)
1193 && (0 == l_denominator)) {
1194 b_colinear = true;
1195 }
1196 else {
1197 // check if previously colinear
1198 if (b_colinear) {
1199 // clear colinear points and flag
1200 l_minuendXs.clear();
1201 l_minuendYs.clear();
1202 l_subtrahendXs.clear();
1203 l_subtrahendYs.clear();
1204 l_polygonXs.clear();
1205 l_polygonYs.clear();
1206
1207 b_colinear = false;
1208
1209 // set new starting point for the polygon
1210 boolean b_useMinuend = ((l_x3 <= l_x1)
1211 && (l_x1 <= l_x4));
1212 l_polygonXs.add(b_useMinuend ? l_minuendCurX
1213 : l_subtrahendCurX);
1214 l_polygonYs.add(b_useMinuend ? l_minuendCurY
1215 : l_subtrahendCurY);
1216 }
1217
1218 // compute slope components
1219 double l_slopeA = l_numeratorA / l_denominator;
1220 double l_slopeB = l_numeratorB / l_denominator;
1221
1222 // check if the line segments intersect
1223 if ((0 < l_slopeA) && (l_slopeA <= 1) && (0 < l_slopeB)
1224 && (l_slopeB <= 1)) {
1225 // compute the point of intersection
1226 double l_xi = l_x1 + (l_slopeA * (l_x2 - l_x1));
1227 double l_yi = l_y1 + (l_slopeA * (l_y2 - l_y1));
1228
1229 l_intersectX = new Double(l_xi);
1230 l_intersectY = new Double(l_yi);
1231 b_intersect = true;
1232 b_minuendAtIntersect = ((l_xi == l_x2)
1233 && (l_yi == l_y2));
1234 b_subtrahendAtIntersect = ((l_xi == l_x4)
1235 && (l_yi == l_y4));
1236
1237 // advance minuend and subtrahend to intesect
1238 l_minuendCurX = l_intersectX;
1239 l_minuendCurY = l_intersectY;
1240 l_subtrahendCurX = l_intersectX;
1241 l_subtrahendCurY = l_intersectY;
1242 }
1243 }
1244 }
1245
1246 if (b_intersect) {
1247 // create the polygon
1248 // add the minuend's points to polygon
1249 l_polygonXs.addAll(l_minuendXs);
1250 l_polygonYs.addAll(l_minuendYs);
1251
1252 // add intersection point to the polygon
1253 l_polygonXs.add(l_intersectX);
1254 l_polygonYs.add(l_intersectY);
1255
1256 // add the subtrahend's points to the polygon in reverse
1257 Collections.reverse(l_subtrahendXs);
1258 Collections.reverse(l_subtrahendYs);
1259 l_polygonXs.addAll(l_subtrahendXs);
1260 l_polygonYs.addAll(l_subtrahendYs);
1261
1262 // create an actual polygon
1263 b_positive = (l_subtrahendMaxY <= l_minuendMaxY)
1264 && (l_subtrahendMinY <= l_minuendMinY);
1265 createPolygon(x_graphics, x_dataArea, x_plot, x_domainAxis,
1266 x_rangeAxis, b_positive, l_polygonXs, l_polygonYs);
1267
1268 // clear the point vectors
1269 l_minuendXs.clear();
1270 l_minuendYs.clear();
1271 l_subtrahendXs.clear();
1272 l_subtrahendYs.clear();
1273 l_polygonXs.clear();
1274 l_polygonYs.clear();
1275
1276 // set the maxY and minY values to intersect y-value
1277 double l_y = l_intersectY.doubleValue();
1278 l_minuendMaxY = l_y;
1279 l_subtrahendMaxY = l_y;
1280 l_minuendMinY = l_y;
1281 l_subtrahendMinY = l_y;
1282
1283 // add interection point to new polygon
1284 l_polygonXs.add(l_intersectX);
1285 l_polygonYs.add(l_intersectY);
1286 }
1287
1288 // advance the minuend if needed
1289 if (l_x2 <= l_x4) {
1290 l_minuendItem++;
1291 b_minuendAdvanced = true;
1292 }
1293 else {
1294 b_minuendAdvanced = false;
1295 }
1296
1297 // advance the subtrahend if needed
1298 if (l_x4 <= l_x2) {
1299 l_subtrahendItem++;
1300 b_subtrahendAdvanced = true;
1301 }
1302 else {
1303 b_subtrahendAdvanced = false;
1304 }
1305
1306 b_minuendDone = (l_minuendItem == (l_minuendItemCount - 1));
1307 b_subtrahendDone = (l_subtrahendItem == (l_subtrahendItemCount
1308 - 1));
1309 }
1310
1311 // check if the final polygon needs to be clipped
1312 if (b_minuendDone && (l_x3 < l_x2) && (l_x2 < l_x4)) {
1313 // project onto subtrahend
1314 double l_slope = (l_y4 - l_y3) / (l_x4 - l_x3);
1315 l_subtrahendNextX = l_minuendNextX;
1316 l_subtrahendNextY = new Double((l_slope * l_x2)
1317 + (l_y3 - (l_slope * l_x3)));
1318 }
1319
1320 if (b_subtrahendDone && (l_x1 < l_x4) && (l_x4 < l_x2)) {
1321 // project onto minuend
1322 double l_slope = (l_y2 - l_y1) / (l_x2 - l_x1);
1323 l_minuendNextX = l_subtrahendNextX;
1324 l_minuendNextY = new Double((l_slope * l_x4)
1325 + (l_y1 - (l_slope * l_x1)));
1326 }
1327
1328 // consider last point of minuend and subtrahend for determining
1329 // positivity
1330 l_minuendMaxY = Math.max(l_minuendMaxY,
1331 l_minuendNextY.doubleValue());
1332 l_subtrahendMaxY = Math.max(l_subtrahendMaxY,
1333 l_subtrahendNextY.doubleValue());
1334 l_minuendMinY = Math.min(l_minuendMinY,
1335 l_minuendNextY.doubleValue());
1336 l_subtrahendMinY = Math.min(l_subtrahendMinY,
1337 l_subtrahendNextY.doubleValue());
1338
1339 // add the last point of the minuned and subtrahend
1340 l_minuendXs.add(l_minuendNextX);
1341 l_minuendYs.add(l_minuendNextY);
1342 l_subtrahendXs.add(l_subtrahendNextX);
1343 l_subtrahendYs.add(l_subtrahendNextY);
1344
1345 // create the polygon
1346 // add the minuend's points to polygon
1347 l_polygonXs.addAll(l_minuendXs);
1348 l_polygonYs.addAll(l_minuendYs);
1349
1350 // add the subtrahend's points to the polygon in reverse
1351 Collections.reverse(l_subtrahendXs);
1352 Collections.reverse(l_subtrahendYs);
1353 l_polygonXs.addAll(l_subtrahendXs);
1354 l_polygonYs.addAll(l_subtrahendYs);
1355
1356 // create an actual polygon
1357 b_positive = (l_subtrahendMaxY <= l_minuendMaxY)
1358 && (l_subtrahendMinY <= l_minuendMinY);
1359 createPolygon(x_graphics, x_dataArea, x_plot, x_domainAxis,
1360 x_rangeAxis, b_positive, l_polygonXs, l_polygonYs);
1361 }
1362
1363 /**
1364 * Draws the visual representation of a single data item, second pass. In
1365 * the second pass, the renderer draws the lines and shapes for the
1366 * individual points in the two series.
1367 *
1368 * @param x_graphics the graphics device.
1369 * @param x_dataArea the area within which the data is being drawn.
1370 * @param x_info collects information about the drawing.
1371 * @param x_plot the plot (can be used to obtain standard color
1372 * information etc).
1373 * @param x_domainAxis the domain (horizontal) axis.
1374 * @param x_rangeAxis the range (vertical) axis.
1375 * @param x_dataset the dataset.
1376 * @param x_series the series index (zero-based).
1377 * @param x_item the item index (zero-based).
1378 * @param x_crosshairState crosshair information for the plot
1379 * (<code>null</code> permitted).
1380 */
1381 protected void drawItemPass1(Graphics2D x_graphics,
1382 Rectangle2D x_dataArea,
1383 PlotRenderingInfo x_info,
1384 XYPlot x_plot,
1385 ValueAxis x_domainAxis,
1386 ValueAxis x_rangeAxis,
1387 XYDataset x_dataset,
1388 int x_series,
1389 int x_item,
1390 CrosshairState x_crosshairState) {
1391
1392 Shape l_entityArea = null;
1393 EntityCollection l_entities = null;
1394 if (null != x_info) {
1395 l_entities = x_info.getOwner().getEntityCollection();
1396 }
1397
1398 Paint l_seriesPaint = getItemPaint(x_series, x_item);
1399 Stroke l_seriesStroke = getItemStroke(x_series, x_item);
1400 x_graphics.setPaint(l_seriesPaint);
1401 x_graphics.setStroke(l_seriesStroke);
1402
1403 PlotOrientation l_orientation = x_plot.getOrientation();
1404 RectangleEdge l_domainAxisLocation = x_plot.getDomainAxisEdge();
1405 RectangleEdge l_rangeAxisLocation = x_plot.getRangeAxisEdge();
1406
1407 double l_x0 = x_dataset.getXValue(x_series, x_item);
1408 double l_y0 = x_dataset.getYValue(x_series, x_item);
1409 double l_x1 = x_domainAxis.valueToJava2D(l_x0, x_dataArea,
1410 l_domainAxisLocation);
1411 double l_y1 = x_rangeAxis.valueToJava2D(l_y0, x_dataArea,
1412 l_rangeAxisLocation);
1413
1414 // These are the shapes of the series items.
1415 if (getShapesVisible()) {
1416 Shape l_shape = getItemShape(x_series, x_item);
1417 if (l_orientation == PlotOrientation.HORIZONTAL) {
1418 l_shape = ShapeUtilities.createTranslatedShape(l_shape,
1419 l_y1, l_x1);
1420 }
1421 else {
1422 l_shape = ShapeUtilities.createTranslatedShape(l_shape,
1423 l_x1, l_y1);
1424 }
1425 if (l_shape.intersects(x_dataArea)) {
1426 x_graphics.setPaint(getItemPaint(x_series, x_item));
1427 x_graphics.fill(l_shape);
1428 /* TODO We could draw the shapes of single items here.
1429 if (drawOutline) {
1430 x_graphics.setPaint(this.outlinePaint);
1431 x_graphics.setStroke(this.outlineStroke);
1432 x_graphics.draw(l_shape);
1433 }
1434 */
1435 }
1436 l_entityArea = l_shape;
1437 } // if (getShapesVisible())
1438
1439 // add an entity for the item...
1440 if (null != l_entities) {
1441 if (null == l_entityArea) {
1442 l_entityArea = new Rectangle2D.Double((l_x1 - 2), (l_y1 - 2),
1443 4, 4);
1444 }
1445 String l_tip = null;
1446 XYToolTipGenerator l_tipGenerator = getToolTipGenerator(x_series,
1447 x_item);
1448 if (null != l_tipGenerator) {
1449 l_tip = l_tipGenerator.generateToolTip(x_dataset, x_series,
1450 x_item);
1451 }
1452 String l_url = null;
1453 XYURLGenerator l_urlGenerator = getURLGenerator();
1454 if (null != l_urlGenerator) {
1455 l_url = l_urlGenerator.generateURL(x_dataset, x_series,
1456 x_item);
1457 }
1458 XYItemEntity l_entity = new XYItemEntity(l_entityArea, x_dataset,
1459 x_series, x_item, l_tip, l_url);
1460 l_entities.add(l_entity);
1461 }
1462
1463 // draw the item label if there is one...
1464 if (isItemLabelVisible(x_series, x_item)) {
1465 drawItemLabel(x_graphics, l_orientation, x_dataset, x_series,
1466 x_item, l_x1, l_y1, (l_y1 < 0.0));
1467 }
1468
1469 int l_domainAxisIndex = x_plot.getDomainAxisIndex(x_domainAxis);
1470 int l_rangeAxisIndex = x_plot.getRangeAxisIndex(x_rangeAxis);
1471 updateCrosshairValues(x_crosshairState, l_x0, l_y0, l_domainAxisIndex,
1472 l_rangeAxisIndex, l_x1, l_y1, l_orientation);
1473
1474 if (0 == x_item) {
1475 return;
1476 }
1477
1478 double l_x2 = x_domainAxis.valueToJava2D(x_dataset.getXValue(x_series,
1479 (x_item - 1)), x_dataArea, l_domainAxisLocation);
1480 double l_y2 = x_rangeAxis.valueToJava2D(x_dataset.getYValue(x_series,
1481 (x_item - 1)), x_dataArea, l_rangeAxisLocation);
1482
1483 Line2D l_line = null;
1484 if (PlotOrientation.HORIZONTAL == l_orientation) {
1485 l_line = new Line2D.Double(l_y1, l_x1, l_y2, l_x2);
1486 }
1487 else if (PlotOrientation.VERTICAL == l_orientation) {
1488 l_line = new Line2D.Double(l_x1, l_y1, l_x2, l_y2);
1489 }
1490
1491 if ((null != l_line) && l_line.intersects(x_dataArea)) {
1492 x_graphics.setPaint(getItemPaint(x_series, x_item));
1493 x_graphics.setStroke(getItemStroke(x_series, x_item));
1494 if (drawOriginalSeries) {
1495 x_graphics.setPaint(this.outlinePaint);
1496 x_graphics.setStroke(this.outlineStroke);
1497 x_graphics.draw(l_line);
1498 }
1499 }
1500 }
1501
1502 /**
1503 * Determines if a dataset is degenerate. A degenerate dataset is a
1504 * dataset where either series has less than two (2) points.
1505 *
1506 * @param x_dataset the dataset.
1507 * @param x_impliedZeroSubtrahend if false, do not check the subtrahend
1508 *
1509 * @return true if the dataset is degenerate.
1510 */
1511 private boolean isEitherSeriesDegenerate(XYDataset x_dataset,
1512 boolean x_impliedZeroSubtrahend) {
1513
1514 if (x_impliedZeroSubtrahend) {
1515 return (x_dataset.getItemCount(0) < 2);
1516 }
1517
1518 return ((x_dataset.getItemCount(0) < 2)
1519 || (x_dataset.getItemCount(1) < 2));
1520 }
1521
1522 /**
1523 * Determines if the two (2) series are disjoint.
1524 * Disjoint series do not overlap in the domain space.
1525 *
1526 * @param x_dataset the dataset.
1527 *
1528 * @return true if the dataset is degenerate.
1529 */
1530 private boolean areSeriesDisjoint(XYDataset x_dataset) {
1531
1532 int l_minuendItemCount = x_dataset.getItemCount(0);
1533 double l_minuendFirst = x_dataset.getXValue(0, 0);
1534 double l_minuendLast = x_dataset.getXValue(0, l_minuendItemCount - 1);
1535
1536 int l_subtrahendItemCount = x_dataset.getItemCount(1);
1537 double l_subtrahendFirst = x_dataset.getXValue(1, 0);
1538 double l_subtrahendLast = x_dataset.getXValue(1,
1539 l_subtrahendItemCount - 1);
1540
1541 return ((l_minuendLast < l_subtrahendFirst)
1542 || (l_subtrahendLast < l_minuendFirst));
1543 }
1544
1545
1546 public void updateCentroid(Object [] xValues, Object [] yValues) {
1547 double x = 0d, y = 0d;
1548
1549 for (int i = 0, N = xValues.length; i < N; ++i) {
1550 x += ((Double)xValues[i]).doubleValue();
1551 y += ((Double)yValues[i]).doubleValue();
1552 }
1553
1554 x /= xValues.length;
1555 y /= yValues.length;
1556
1557 centroidNPoints++;
1558 double factorNew = 1d / centroidNPoints;
1559 double factorOld = 1d - factorNew;
1560
1561 centroid = new Point2D.Double((factorNew * x + factorOld * centroid.x),
1562 (factorNew * y + factorOld * centroid.y));
1563 }
1564
1565
1566 public static double calculateArea(Object [] xValues, Object [] yValues) {
1567 double area = 0d;
1568
1569 for (int i = 0, N = xValues.length; i < N; ++i) {
1570 int j = (i + 1) % N;
1571 double xi = ((Double)xValues[i]).doubleValue();
1572 double yi = ((Double)yValues[i]).doubleValue();
1573 double xj = ((Double)xValues[j]).doubleValue();
1574 double yj = ((Double)yValues[j]).doubleValue();
1575
1576 area += xi*yj;
1577 area -= xj*yi;
1578 // TODO centroid calculation here?
1579 }
1580
1581 return 0.5d*area;
1582 }
1583
1584 /**
1585 * Draws the visual representation of a polygon
1586 *
1587 * @param x_graphics the graphics device.
1588 * @param x_dataArea the area within which the data is being drawn.
1589 * @param x_plot the plot (can be used to obtain standard color
1590 * information etc).
1591 * @param x_domainAxis the domain (horizontal) axis.
1592 * @param x_rangeAxis the range (vertical) axis.
1593 * @param x_positive indicates if the polygon is positive (true) or
1594 * negative (false).
1595 * @param x_xValues a linked list of the x values (expects values to be
1596 * of type Double).
1597 * @param x_yValues a linked list of the y values (expects values to be
1598 * of type Double).
1599 */
1600 private void createPolygon (Graphics2D x_graphics,
1601 Rectangle2D x_dataArea,
1602 XYPlot x_plot,
1603 ValueAxis x_domainAxis,
1604 ValueAxis x_rangeAxis,
1605 boolean x_positive,
1606 LinkedList x_xValues,
1607 LinkedList x_yValues) {
1608
1609 PlotOrientation l_orientation = x_plot.getOrientation();
1610 RectangleEdge l_domainAxisLocation = x_plot.getDomainAxisEdge();
1611 RectangleEdge l_rangeAxisLocation = x_plot.getRangeAxisEdge();
1612
1613 Object[] l_xValues = x_xValues.toArray();
1614 Object[] l_yValues = x_yValues.toArray();
1615
1616 double area = calculateArea(l_xValues, l_yValues)/2d;
1617 if (x_positive) positiveArea += area;
1618 else negativeArea += area;
1619 updateCentroid(l_xValues, l_yValues);
1620
1621 GeneralPath l_path = new GeneralPath();
1622
1623 if (PlotOrientation.VERTICAL == l_orientation) {
1624 double l_x = x_domainAxis.valueToJava2D((
1625 (Double) l_xValues[0]).doubleValue(), x_dataArea,
1626 l_domainAxisLocation);
1627 if (this.roundXCoordinates) {
1628 l_x = Math.rint(l_x);
1629 }
1630
1631 double l_y = x_rangeAxis.valueToJava2D((
1632 (Double) l_yValues[0]).doubleValue(), x_dataArea,
1633 l_rangeAxisLocation);
1634
1635 l_path.moveTo((float) l_x, (float) l_y);
1636 for (int i = 1; i < l_xValues.length; i++) {
1637 l_x = x_domainAxis.valueToJava2D((
1638 (Double) l_xValues[i]).doubleValue(), x_dataArea,
1639 l_domainAxisLocation);
1640 if (this.roundXCoordinates) {
1641 l_x = Math.rint(l_x);
1642 }
1643
1644 l_y = x_rangeAxis.valueToJava2D((
1645 (Double) l_yValues[i]).doubleValue(), x_dataArea,
1646 l_rangeAxisLocation);
1647 l_path.lineTo((float) l_x, (float) l_y);
1648 }
1649 l_path.closePath();
1650 }
1651 else {
1652 double l_x = x_domainAxis.valueToJava2D((
1653 (Double) l_xValues[0]).doubleValue(), x_dataArea,
1654 l_domainAxisLocation);
1655 if (this.roundXCoordinates) {
1656 l_x = Math.rint(l_x);
1657 }
1658
1659 double l_y = x_rangeAxis.valueToJava2D((
1660 (Double) l_yValues[0]).doubleValue(), x_dataArea,
1661 l_rangeAxisLocation);
1662
1663 l_path.moveTo((float) l_y, (float) l_x);
1664 for (int i = 1; i < l_xValues.length; i++) {
1665 l_x = x_domainAxis.valueToJava2D((
1666 (Double) l_xValues[i]).doubleValue(), x_dataArea,
1667 l_domainAxisLocation);
1668 if (this.roundXCoordinates) {
1669 l_x = Math.rint(l_x);
1670 }
1671
1672 l_y = x_rangeAxis.valueToJava2D((
1673 (Double) l_yValues[i]).doubleValue(), x_dataArea,
1674 l_rangeAxisLocation);
1675 l_path.lineTo((float) l_y, (float) l_x);
1676 }
1677 l_path.closePath();
1678 }
1679
1680 if (l_path.intersects(x_dataArea)) {
1681 x_graphics.setPaint(x_positive ? getPositivePaint()
1682 : getNegativePaint());
1683 x_graphics.fill(l_path);
1684 if (drawOutline) {
1685 x_graphics.setStroke(this.outlineStroke);
1686 x_graphics.setPaint(this.outlinePaint);
1687 x_graphics.draw(l_path);
1688 }
1689 }
1690 }
1691
1692 /**
1693 * Returns a default legend item for the specified series. Subclasses
1694 * should override this method to generate customised items.
1695 *
1696 * @param datasetIndex the dataset index (zero-based).
1697 * @param series the series index (zero-based).
1698 *
1699 * @return A legend item for the series.
1700 */
1701 public LegendItem getLegendItem(int datasetIndex, int series) {
1702 LegendItem result = null;
1703 XYPlot p = getPlot();
1704 if (p != null) {
1705 XYDataset dataset = p.getDataset(datasetIndex);
1706 if (dataset != null) {
1707 if (getItemVisible(series, 0)) {
1708 String label = getLegendItemLabelGenerator().generateLabel(
1709 dataset, series);
1710 String description = label;
1711 String toolTipText = null;
1712 if (getLegendItemToolTipGenerator() != null) {
1713 toolTipText
1714 = getLegendItemToolTipGenerator().generateLabel(
1715 dataset, series);
1716 }
1717 String urlText = null;
1718 if (getLegendItemURLGenerator() != null) {
1719 urlText = getLegendItemURLGenerator().generateLabel(
1720 dataset, series);
1721 }
1722 // Individualized Paints:
1723 //Paint paint = lookupSeriesPaint(series);
1724
1725 // "Area-Style"- Paint.
1726 Paint paint = getPositivePaint();
1727 Stroke stroke = lookupSeriesStroke(series);
1728 Shape line = getLegendLine();
1729 // Not-filled Shape:
1730 //result = new LegendItem(label, description,
1731 // toolTipText, urlText, line, stroke, paint);
1732
1733 if (drawOutline) {
1734 // TODO Include outline style in legenditem (there is a constructor for that)
1735 }
1736
1737 // Filled Shape ("Area-Style").
1738 result = new LegendItem(label, description,
1739 toolTipText, urlText, line, paint);
1740 result.setLabelFont(lookupLegendTextFont(series));
1741 Paint labelPaint = lookupLegendTextPaint(series);
1742 if (labelPaint != null) {
1743 result.setLabelPaint(labelPaint);
1744 }
1745 result.setDataset(dataset);
1746 result.setDatasetIndex(datasetIndex);
1747 result.setSeriesKey(dataset.getSeriesKey(series));
1748 result.setSeriesIndex(series);
1749 }
1750 }
1751
1752 }
1753
1754 return result;
1755 }
1756
1757 /**
1758 * Tests this renderer for equality with an arbitrary object.
1759 *
1760 * @param obj the object (<code>null</code> permitted).
1761 *
1762 * @return A boolean.
1763 */
1764 public boolean equals(Object obj) {
1765 if (obj == this) {
1766 return true;
1767 }
1768 if (!(obj instanceof StableXYDifferenceRenderer)) {
1769 return false;
1770 }
1771 if (!super.equals(obj)) {
1772 return false;
1773 }
1774 StableXYDifferenceRenderer that = (StableXYDifferenceRenderer) obj;
1775 if (!PaintUtilities.equal(this.positivePaint, that.positivePaint)) {
1776 return false;
1777 }
1778 if (!PaintUtilities.equal(this.negativePaint, that.negativePaint)) {
1779 return false;
1780 }
1781 if (this.shapesVisible != that.shapesVisible) {
1782 return false;
1783 }
1784 if (!ShapeUtilities.equal(this.legendShape, that.legendShape)) {
1785 return false;
1786 }
1787 if (this.roundXCoordinates != that.roundXCoordinates) {
1788 return false;
1789 }
1790 return true;
1791 }
1792
1793 /**
1794 * Returns a clone of the renderer.
1795 *
1796 * @return A clone.
1797 *
1798 * @throws CloneNotSupportedException if the renderer cannot be cloned.
1799 */
1800 public Object clone() throws CloneNotSupportedException {
1801 StableXYDifferenceRenderer clone = (StableXYDifferenceRenderer) super.clone();
1802 clone.legendShape = ShapeUtilities.clone(this.legendShape);
1803 return clone;
1804 }
1805
1806 /**
1807 * Provides serialization support.
1808 *
1809 * @param stream the output stream.
1810 *
1811 * @throws IOException if there is an I/O error.
1812 */
1813 private void writeObject(ObjectOutputStream stream) throws IOException {
1814 stream.defaultWriteObject();
1815 SerialUtilities.writePaint(this.positivePaint, stream);
1816 SerialUtilities.writePaint(this.negativePaint, stream);
1817 SerialUtilities.writeShape(this.legendShape, stream);
1818 }
1819
1820 /**
1821 * Provides serialization support.
1822 *
1823 * @param stream the input stream.
1824 *
1825 * @throws IOException if there is an I/O error.
1826 * @throws ClassNotFoundException if there is a classpath problem.
1827 */
1828 private void readObject(ObjectInputStream stream)
1829 throws IOException, ClassNotFoundException {
1830 stream.defaultReadObject();
1831 this.positivePaint = SerialUtilities.readPaint(stream);
1832 this.negativePaint = SerialUtilities.readPaint(stream);
1833 this.legendShape = SerialUtilities.readShape(stream);
1834 }
1835 }
1836 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org