comparison flys-artifacts/src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.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 b75681c09ef8
children 5642a83420f2
comparison
equal deleted inserted replaced
2548:ada02bbd3b7f 2793:6310b1582f2d
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 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 private static final void addSeries(
536 DefaultXYDataset ds,
537 Comparable key,
538 TDoubleArrayList xs,
539 TDoubleArrayList ys
540 ) {
541 ds.addSeries(
542 key,
543 new double [][] {
544 xs.toNativeArray(),
545 ys.toNativeArray()
546 });
547 }
548
549 protected static List<XYDataset> splitByNaNsOneSeries(
550 XYDataset dataset
551 ) {
552 List<XYDataset> datasets = new ArrayList<XYDataset>();
553
554 int N = dataset.getItemCount(0);
555 TDoubleArrayList xs = new TDoubleArrayList(N);
556 TDoubleArrayList ys = new TDoubleArrayList(N);
557 for (int i = 0; i < N; ++i) {
558 double x = dataset.getXValue(0, i);
559 double y = dataset.getYValue(0, i);
560 if (Double.isNaN(x) || Double.isNaN(y)) {
561 if (!xs.isEmpty()) {
562 DefaultXYDataset ds = new DefaultXYDataset();
563 addSeries(ds, dataset.getSeriesKey(0), xs, ys);
564 datasets.add(ds);
565 xs.resetQuick();
566 ys.resetQuick();
567 }
568 }
569 else {
570 xs.add(x);
571 ys.add(y);
572 }
573 }
574 if (!xs.isEmpty()) {
575 DefaultXYDataset ds = new DefaultXYDataset();
576 addSeries(ds, dataset.getSeriesKey(0), xs, ys);
577 datasets.add(ds);
578 }
579
580 return datasets;
581 }
582
583 private static final boolean add(TDoubleArrayList xs, double x) {
584 int N = xs.size();
585 if (N == 0 || xs.getQuick(N-1) < x) {
586 xs.add(x);
587 return true;
588 }
589 log.debug("pushed smaller");
590 return false;
591 }
592
593 protected static List<XYDataset> splitByNaNsTwoSeries(
594 XYDataset dataset
595 ) {
596 boolean debug = log.isDebugEnabled();
597
598 List<XYDataset> datasets = new ArrayList<XYDataset>();
599
600 int N = dataset.getItemCount(0);
601 int M = dataset.getItemCount(1);
602
603 int i = 0, j = 0;
604 // ignore leading NaNs
605 for (; i < N; ++i) {
606 double x = dataset.getXValue(0, i);
607 double y = dataset.getYValue(0, i);
608 if (!Double.isNaN(x) && !Double.isNaN(y)) {
609 break;
610 }
611 }
612
613 for (; j < M; ++j) {
614 double x = dataset.getXValue(1, j);
615 double y = dataset.getYValue(1, j);
616 if (!Double.isNaN(x) && !Double.isNaN(y)) {
617 break;
618 }
619 }
620
621 TDoubleArrayList six = new TDoubleArrayList();
622 TDoubleArrayList siy = new TDoubleArrayList();
623 TDoubleArrayList sjx = new TDoubleArrayList();
624 TDoubleArrayList sjy = new TDoubleArrayList();
625
626 while (i < N && j < M) {
627 int ni = i+1;
628 for (; ni < N && !Double.isNaN(dataset.getXValue(0, ni)); ++ni);
629 for (; ni < N && Double.isNaN(dataset.getXValue(0, ni)); ++ni);
630
631 int nj = j+1;
632 for (; nj < M && !Double.isNaN(dataset.getXValue(1, nj)); ++nj);
633 for (; nj < M && Double.isNaN(dataset.getXValue(1, nj)); ++nj);
634
635 if (ni == N && nj == M) { // no more splits
636 log.debug("no more splits ....");
637 for (; i < ni; ++i) {
638 double x = dataset.getXValue(0, i);
639 double y = dataset.getYValue(0, i);
640 if (!Double.isNaN(x)
641 && !Double.isNaN(y)
642 && add(six, x)) {
643 siy.add(y);
644 }
645 }
646 for (; j < nj; ++j) {
647 double x = dataset.getXValue(1, j);
648 double y = dataset.getYValue(1, j);
649 if (!Double.isNaN(x)
650 && !Double.isNaN(y)
651 && add(sjx, x)) {
652 sjy.add(y);
653 }
654 }
655 if (!six.isEmpty() && !sjx.isEmpty()) {
656 DefaultXYDataset ds = new DefaultXYDataset();
657 addSeries(ds, dataset.getSeriesKey(0), six, siy);
658 addSeries(ds, dataset.getSeriesKey(1), sjx, sjy);
659 datasets.add(ds);
660 }
661 break;
662 }
663
664 if (debug) {
665 log.debug("ni: " + ni + " " + N);
666 log.debug("nj: " + nj + " " + M);
667 }
668
669 double xni = ni < N
670 ? dataset.getXValue(0, ni)
671 : Double.MAX_VALUE;
672
673 double xnj = nj < M
674 ? dataset.getXValue(1, nj)
675 : Double.MAX_VALUE;
676
677 double xns = Math.min(xni, xnj);
678
679 double pushxi = Double.NaN;
680 double pushyi = Double.NaN;
681 double pushxj = Double.NaN;
682 double pushyj = Double.NaN;
683
684 for (; i < ni; ++i) {
685 double x = dataset.getXValue(0, i);
686 double y = dataset.getYValue(0, i);
687 if (Double.isNaN(x) || Double.isNaN(y)) {
688 continue;
689 }
690 if (x < xns) {
691 if (add(six, x)) {
692 siy.add(y);
693 }
694 continue;
695 }
696 if (x == xns) { // exact match
697 if (add(six, x)) {
698 siy.add(y);
699 }
700 pushxi = x; pushyi = y;
701 }
702 else { // x > xns: intersection
703 if (debug) {
704 log.debug("xns: " + xns);
705 log.debug("x/y: " + x + " / " + y);
706 }
707 int SIX = six.size();
708 if (SIX > 0) { // should always be true
709 double yns = Linear.linear(
710 xns,
711 six.getQuick(SIX-1), x,
712 siy.getQuick(SIX-1), y);
713 if (debug) {
714 log.debug("intersection at: " + yns);
715 }
716 if (add(six, xns)) {
717 siy.add(yns);
718 }
719 pushxi = xns;
720 pushyi = yns;
721 }
722 }
723 break; // Split point reached.
724 }
725
726 for (; j < nj; ++j) {
727 double x = dataset.getXValue(1, j);
728 double y = dataset.getYValue(1, j);
729 if (Double.isNaN(x) || Double.isNaN(y)) {
730 continue;
731 }
732 if (x < xns) {
733 if (add(sjx, x)) {
734 sjy.add(y);
735 }
736 continue;
737 }
738 if (x == xns) { // exact match
739 if (add(sjx, x)) {
740 sjy.add(y);
741 }
742 pushxj = x; pushyj = y;
743 }
744 else { // x > xns: intersection
745 int SJX = sjx.size();
746 if (SJX > 0) { // should always be true
747 double yns = Linear.linear(
748 xns,
749 sjx.getQuick(SJX-1), x,
750 sjy.getQuick(SJX-1), y);
751 if (debug) {
752 log.debug("intersection at: " + yns);
753 }
754 if (add(sjx, xns)) {
755 sjy.add(yns);
756 }
757 pushxj = xns; pushyj = yns;
758 }
759 }
760 break; // Split point reached.
761 }
762
763 if (!six.isEmpty() && !sjx.isEmpty()) {
764 DefaultXYDataset ds = new DefaultXYDataset();
765 addSeries(ds, dataset.getSeriesKey(0), six, siy);
766 addSeries(ds, dataset.getSeriesKey(1), sjx, sjy);
767 datasets.add(ds);
768 }
769
770 six.resetQuick(); siy.resetQuick();
771 sjx.resetQuick(); sjy.resetQuick();
772
773 // Push split points.
774 if (!Double.isNaN(pushxi)) {
775 six.add(pushxi);
776 siy.add(pushyi);
777 }
778
779 if (!Double.isNaN(pushxj)) {
780 sjx.add(pushxj);
781 sjy.add(pushyj);
782 }
783 }
784
785 // Copy the rest.
786 for (; i < N; ++i) {
787 double x = dataset.getXValue(0, i);
788 double y = dataset.getXValue(0, i);
789 if (!Double.isNaN(x)
790 && !Double.isNaN(y)
791 && add(six, x)) {
792 siy.add(y);
793 }
794 }
795
796 for (; j < M; ++j) {
797 double x = dataset.getXValue(1, j);
798 double y = dataset.getXValue(1, j);
799 if (!Double.isNaN(x)
800 && !Double.isNaN(y)
801 && add(sjx, x)) {
802 sjy.add(y);
803 }
804 }
805
806 // Build final dataset.
807 if (!six.isEmpty() && !sjx.isEmpty()) {
808 DefaultXYDataset ds = new DefaultXYDataset();
809 addSeries(ds, dataset.getSeriesKey(0), six, siy);
810 addSeries(ds, dataset.getSeriesKey(1), sjx, sjy);
811 datasets.add(ds);
812 }
813
814 if (debug) {
815 log.debug("datasets after split: " + datasets.size());
816 }
817
818 return datasets;
819 }
820
821 public static List<XYDataset> splitByNaNs(XYDataset dataset) {
822
823 switch (dataset.getSeriesCount()) {
824 case 0:
825 return Collections.emptyList();
826 case 1:
827 return splitByNaNsOneSeries(dataset);
828 default: // two or more
829 return splitByNaNsTwoSeries(dataset);
830 }
831 }
832
833 /**
834 * Draws the visual representation of a single data item.
835 *
836 * @param g2 the graphics device.
837 * @param state the renderer state.
838 * @param dataArea the area within which the data is being drawn.
839 * @param info collects information about the drawing.
840 * @param plot the plot (can be used to obtain standard color
841 * information etc).
842 * @param domainAxis the domain (horizontal) axis.
843 * @param rangeAxis the range (vertical) axis.
844 * @param dataset the dataset.
845 * @param series the series index (zero-based).
846 * @param item the item index (zero-based).
847 * @param crosshairState crosshair information for the plot
848 * (<code>null</code> permitted).
849 * @param pass the pass index.
850 */
851 public void drawItem(Graphics2D g2,
852 XYItemRendererState state,
853 Rectangle2D dataArea,
854 PlotRenderingInfo info,
855 XYPlot plot,
856 ValueAxis domainAxis,
857 ValueAxis rangeAxis,
858 XYDataset dataset,
859 int series,
860 int item,
861 CrosshairState crosshairState,
862 int pass) {
863 switch (pass) {
864 case 0:
865 for (XYDataset ds: splitByNaNs(dataset)) {
866 drawItemPass0(g2, dataArea, info,
867 plot, domainAxis, rangeAxis,
868 ds, series, item, crosshairState);
869 }
870 break;
871 case 1:
872 drawItemPass1(g2, dataArea, info,
873 plot, domainAxis, rangeAxis,
874 dataset, series, item, crosshairState);
875 }
876
877 // Find geometric middle, calculate area and paint a string with it here.
878 // TODO also i18n
879 if (pass == 1 && this.labelArea) {
880 double center_x = centroid.getX();
881 double center_y = centroid.getY();
882 center_x = domainAxis.valueToJava2D(center_x, dataArea,
883 plot.getDomainAxisEdge());
884 center_y = rangeAxis.valueToJava2D(center_y, dataArea,
885 plot.getRangeAxisEdge());
886
887 // Respect text-extend if text should appear really centered.
888
889 float area = 0f;
890 if (areaCalculationMode == CALCULATE_POSITIVE_AREA
891 || areaCalculationMode == CALCULATE_ALL_AREA) {
892 area += Math.abs(positiveArea);
893 }
894 if (areaCalculationMode == CALCULATE_NEGATIVE_AREA
895 || areaCalculationMode == CALCULATE_ALL_AREA) {
896 area += Math.abs(negativeArea);
897 }
898 if (area != 0f) {
899 Color oldColor = g2.getColor();
900 Font oldFont = g2.getFont();
901 g2.setFont(labelFont);
902 String labelText = "Area= " + area + "m2";
903 if (labelBGColor != null) {
904 EnhancedLineAndShapeRenderer.drawTextBox(g2, labelText,
905 (float)center_x, (float)center_y, labelBGColor);
906 }
907 g2.setColor(labelColor);
908 g2.drawString(labelText, (float)center_x, (float)center_y);
909 g2.setFont(oldFont);
910 g2.setColor(oldColor);
911 }
912 }
913 }
914
915 /**
916 * Draws the visual representation of a single data item, first pass.
917 *
918 * @param x_graphics the graphics device.
919 * @param x_dataArea the area within which the data is being drawn.
920 * @param x_info collects information about the drawing.
921 * @param x_plot the plot (can be used to obtain standard color
922 * information etc).
923 * @param x_domainAxis the domain (horizontal) axis.
924 * @param x_rangeAxis the range (vertical) axis.
925 * @param x_dataset the dataset.
926 * @param x_series the series index (zero-based).
927 * @param x_item the item index (zero-based).
928 * @param x_crosshairState crosshair information for the plot
929 * (<code>null</code> permitted).
930 */
931 protected void drawItemPass0(Graphics2D x_graphics,
932 Rectangle2D x_dataArea,
933 PlotRenderingInfo x_info,
934 XYPlot x_plot,
935 ValueAxis x_domainAxis,
936 ValueAxis x_rangeAxis,
937 XYDataset x_dataset,
938 int x_series,
939 int x_item,
940 CrosshairState x_crosshairState) {
941
942 if (!((0 == x_series) && (0 == x_item))) {
943 return;
944 }
945
946 boolean b_impliedZeroSubtrahend = (1 == x_dataset.getSeriesCount());
947
948 // check if either series is a degenerate case (i.e. less than 2 points)
949 if (isEitherSeriesDegenerate(x_dataset, b_impliedZeroSubtrahend)) {
950 return;
951 }
952
953 // check if series are disjoint (i.e. domain-spans do not overlap)
954 if (!b_impliedZeroSubtrahend && areSeriesDisjoint(x_dataset)) {
955 return;
956 }
957
958 // polygon definitions
959 LinkedList l_minuendXs = new LinkedList();
960 LinkedList l_minuendYs = new LinkedList();
961 LinkedList l_subtrahendXs = new LinkedList();
962 LinkedList l_subtrahendYs = new LinkedList();
963 LinkedList l_polygonXs = new LinkedList();
964 LinkedList l_polygonYs = new LinkedList();
965
966 // state
967 int l_minuendItem = 0;
968 int l_minuendItemCount = x_dataset.getItemCount(0);
969 Double l_minuendCurX = null;
970 Double l_minuendNextX = null;
971 Double l_minuendCurY = null;
972 Double l_minuendNextY = null;
973 double l_minuendMaxY = Double.NEGATIVE_INFINITY;
974 double l_minuendMinY = Double.POSITIVE_INFINITY;
975
976 int l_subtrahendItem = 0;
977 int l_subtrahendItemCount = 0; // actual value set below
978 Double l_subtrahendCurX = null;
979 Double l_subtrahendNextX = null;
980 Double l_subtrahendCurY = null;
981 Double l_subtrahendNextY = null;
982 double l_subtrahendMaxY = Double.NEGATIVE_INFINITY;
983 double l_subtrahendMinY = Double.POSITIVE_INFINITY;
984
985 // if a subtrahend is not specified, assume it is zero
986 if (b_impliedZeroSubtrahend) {
987 l_subtrahendItem = 0;
988 l_subtrahendItemCount = 2;
989 l_subtrahendCurX = new Double(x_dataset.getXValue(0, 0));
990 l_subtrahendNextX = new Double(x_dataset.getXValue(0,
991 (l_minuendItemCount - 1)));
992 l_subtrahendCurY = new Double(0.0);
993 l_subtrahendNextY = new Double(0.0);
994 l_subtrahendMaxY = 0.0;
995 l_subtrahendMinY = 0.0;
996
997 l_subtrahendXs.add(l_subtrahendCurX);
998 l_subtrahendYs.add(l_subtrahendCurY);
999 }
1000 else {
1001 l_subtrahendItemCount = x_dataset.getItemCount(1);
1002 }
1003
1004 boolean b_minuendDone = false;
1005 boolean b_minuendAdvanced = true;
1006 boolean b_minuendAtIntersect = false;
1007 boolean b_minuendFastForward = false;
1008 boolean b_subtrahendDone = false;
1009 boolean b_subtrahendAdvanced = true;
1010 boolean b_subtrahendAtIntersect = false;
1011 boolean b_subtrahendFastForward = false;
1012 boolean b_colinear = false;
1013
1014 boolean b_positive;
1015
1016 // coordinate pairs
1017 double l_x1 = 0.0, l_y1 = 0.0; // current minuend point
1018 double l_x2 = 0.0, l_y2 = 0.0; // next minuend point
1019 double l_x3 = 0.0, l_y3 = 0.0; // current subtrahend point
1020 double l_x4 = 0.0, l_y4 = 0.0; // next subtrahend point
1021
1022 // fast-forward through leading tails
1023 boolean b_fastForwardDone = false;
1024 while (!b_fastForwardDone) {
1025 // get the x and y coordinates
1026 l_x1 = x_dataset.getXValue(0, l_minuendItem);
1027 l_y1 = x_dataset.getYValue(0, l_minuendItem);
1028 l_x2 = x_dataset.getXValue(0, l_minuendItem + 1);
1029 l_y2 = x_dataset.getYValue(0, l_minuendItem + 1);
1030
1031 l_minuendCurX = new Double(l_x1);
1032 l_minuendCurY = new Double(l_y1);
1033 l_minuendNextX = new Double(l_x2);
1034 l_minuendNextY = new Double(l_y2);
1035
1036 if (b_impliedZeroSubtrahend) {
1037 l_x3 = l_subtrahendCurX.doubleValue();
1038 l_y3 = l_subtrahendCurY.doubleValue();
1039 l_x4 = l_subtrahendNextX.doubleValue();
1040 l_y4 = l_subtrahendNextY.doubleValue();
1041 }
1042 else {
1043 l_x3 = x_dataset.getXValue(1, l_subtrahendItem);
1044 l_y3 = x_dataset.getYValue(1, l_subtrahendItem);
1045 l_x4 = x_dataset.getXValue(1, l_subtrahendItem + 1);
1046 l_y4 = x_dataset.getYValue(1, l_subtrahendItem + 1);
1047
1048 l_subtrahendCurX = new Double(l_x3);
1049 l_subtrahendCurY = new Double(l_y3);
1050 l_subtrahendNextX = new Double(l_x4);
1051 l_subtrahendNextY = new Double(l_y4);
1052 }
1053
1054 if (l_x2 <= l_x3) {
1055 // minuend needs to be fast forwarded
1056 l_minuendItem++;
1057 b_minuendFastForward = true;
1058 continue;
1059 }
1060
1061 if (l_x4 <= l_x1) {
1062 // subtrahend needs to be fast forwarded
1063 l_subtrahendItem++;
1064 b_subtrahendFastForward = true;
1065 continue;
1066 }
1067
1068 // check if initial polygon needs to be clipped
1069 if ((l_x3 < l_x1) && (l_x1 < l_x4)) {
1070 // project onto subtrahend
1071 double l_slope = (l_y4 - l_y3) / (l_x4 - l_x3);
1072 l_subtrahendCurX = l_minuendCurX;
1073 l_subtrahendCurY = new Double((l_slope * l_x1)
1074 + (l_y3 - (l_slope * l_x3)));
1075
1076 l_subtrahendXs.add(l_subtrahendCurX);
1077 l_subtrahendYs.add(l_subtrahendCurY);
1078 }
1079
1080 if ((l_x1 < l_x3) && (l_x3 < l_x2)) {
1081 // project onto minuend
1082 double l_slope = (l_y2 - l_y1) / (l_x2 - l_x1);
1083 l_minuendCurX = l_subtrahendCurX;
1084 l_minuendCurY = new Double((l_slope * l_x3)
1085 + (l_y1 - (l_slope * l_x1)));
1086
1087 l_minuendXs.add(l_minuendCurX);
1088 l_minuendYs.add(l_minuendCurY);
1089 }
1090
1091 l_minuendMaxY = l_minuendCurY.doubleValue();
1092 l_minuendMinY = l_minuendCurY.doubleValue();
1093 l_subtrahendMaxY = l_subtrahendCurY.doubleValue();
1094 l_subtrahendMinY = l_subtrahendCurY.doubleValue();
1095
1096 b_fastForwardDone = true;
1097 }
1098
1099 // start of algorithm
1100 while (!b_minuendDone && !b_subtrahendDone) {
1101 if (!b_minuendDone && !b_minuendFastForward && b_minuendAdvanced) {
1102 l_x1 = x_dataset.getXValue(0, l_minuendItem);
1103 l_y1 = x_dataset.getYValue(0, l_minuendItem);
1104 l_minuendCurX = new Double(l_x1);
1105 l_minuendCurY = new Double(l_y1);
1106
1107 if (!b_minuendAtIntersect) {
1108 l_minuendXs.add(l_minuendCurX);
1109 l_minuendYs.add(l_minuendCurY);
1110 }
1111
1112 l_minuendMaxY = Math.max(l_minuendMaxY, l_y1);
1113 l_minuendMinY = Math.min(l_minuendMinY, l_y1);
1114
1115 l_x2 = x_dataset.getXValue(0, l_minuendItem + 1);
1116 l_y2 = x_dataset.getYValue(0, l_minuendItem + 1);
1117 l_minuendNextX = new Double(l_x2);
1118 l_minuendNextY = new Double(l_y2);
1119 }
1120
1121 // never updated the subtrahend if it is implied to be zero
1122 if (!b_impliedZeroSubtrahend && !b_subtrahendDone
1123 && !b_subtrahendFastForward && b_subtrahendAdvanced) {
1124 l_x3 = x_dataset.getXValue(1, l_subtrahendItem);
1125 l_y3 = x_dataset.getYValue(1, l_subtrahendItem);
1126 l_subtrahendCurX = new Double(l_x3);
1127 l_subtrahendCurY = new Double(l_y3);
1128
1129 if (!b_subtrahendAtIntersect) {
1130 l_subtrahendXs.add(l_subtrahendCurX);
1131 l_subtrahendYs.add(l_subtrahendCurY);
1132 }
1133
1134 l_subtrahendMaxY = Math.max(l_subtrahendMaxY, l_y3);
1135 l_subtrahendMinY = Math.min(l_subtrahendMinY, l_y3);
1136
1137 l_x4 = x_dataset.getXValue(1, l_subtrahendItem + 1);
1138 l_y4 = x_dataset.getYValue(1, l_subtrahendItem + 1);
1139 l_subtrahendNextX = new Double(l_x4);
1140 l_subtrahendNextY = new Double(l_y4);
1141 }
1142
1143 // deassert b_*FastForward (only matters for 1st time through loop)
1144 b_minuendFastForward = false;
1145 b_subtrahendFastForward = false;
1146
1147 Double l_intersectX = null;
1148 Double l_intersectY = null;
1149 boolean b_intersect = false;
1150
1151 b_minuendAtIntersect = false;
1152 b_subtrahendAtIntersect = false;
1153
1154 // check for intersect
1155 if ((l_x2 == l_x4) && (l_y2 == l_y4)) {
1156 // check if line segments are colinear
1157 if ((l_x1 == l_x3) && (l_y1 == l_y3)) {
1158 b_colinear = true;
1159 }
1160 else {
1161 // the intersect is at the next point for both the minuend
1162 // and subtrahend
1163 l_intersectX = new Double(l_x2);
1164 l_intersectY = new Double(l_y2);
1165
1166 b_intersect = true;
1167 b_minuendAtIntersect = true;
1168 b_subtrahendAtIntersect = true;
1169 }
1170 }
1171 else {
1172 // compute common denominator
1173 double l_denominator = ((l_y4 - l_y3) * (l_x2 - l_x1))
1174 - ((l_x4 - l_x3) * (l_y2 - l_y1));
1175
1176 // compute common deltas
1177 double l_deltaY = l_y1 - l_y3;
1178 double l_deltaX = l_x1 - l_x3;
1179
1180 // compute numerators
1181 double l_numeratorA = ((l_x4 - l_x3) * l_deltaY)
1182 - ((l_y4 - l_y3) * l_deltaX);
1183 double l_numeratorB = ((l_x2 - l_x1) * l_deltaY)
1184 - ((l_y2 - l_y1) * l_deltaX);
1185
1186 // check if line segments are colinear
1187 if ((0 == l_numeratorA) && (0 == l_numeratorB)
1188 && (0 == l_denominator)) {
1189 b_colinear = true;
1190 }
1191 else {
1192 // check if previously colinear
1193 if (b_colinear) {
1194 // clear colinear points and flag
1195 l_minuendXs.clear();
1196 l_minuendYs.clear();
1197 l_subtrahendXs.clear();
1198 l_subtrahendYs.clear();
1199 l_polygonXs.clear();
1200 l_polygonYs.clear();
1201
1202 b_colinear = false;
1203
1204 // set new starting point for the polygon
1205 boolean b_useMinuend = ((l_x3 <= l_x1)
1206 && (l_x1 <= l_x4));
1207 l_polygonXs.add(b_useMinuend ? l_minuendCurX
1208 : l_subtrahendCurX);
1209 l_polygonYs.add(b_useMinuend ? l_minuendCurY
1210 : l_subtrahendCurY);
1211 }
1212
1213 // compute slope components
1214 double l_slopeA = l_numeratorA / l_denominator;
1215 double l_slopeB = l_numeratorB / l_denominator;
1216
1217 // check if the line segments intersect
1218 if ((0 < l_slopeA) && (l_slopeA <= 1) && (0 < l_slopeB)
1219 && (l_slopeB <= 1)) {
1220 // compute the point of intersection
1221 double l_xi = l_x1 + (l_slopeA * (l_x2 - l_x1));
1222 double l_yi = l_y1 + (l_slopeA * (l_y2 - l_y1));
1223
1224 l_intersectX = new Double(l_xi);
1225 l_intersectY = new Double(l_yi);
1226 b_intersect = true;
1227 b_minuendAtIntersect = ((l_xi == l_x2)
1228 && (l_yi == l_y2));
1229 b_subtrahendAtIntersect = ((l_xi == l_x4)
1230 && (l_yi == l_y4));
1231
1232 // advance minuend and subtrahend to intesect
1233 l_minuendCurX = l_intersectX;
1234 l_minuendCurY = l_intersectY;
1235 l_subtrahendCurX = l_intersectX;
1236 l_subtrahendCurY = l_intersectY;
1237 }
1238 }
1239 }
1240
1241 if (b_intersect) {
1242 // create the polygon
1243 // add the minuend's points to polygon
1244 l_polygonXs.addAll(l_minuendXs);
1245 l_polygonYs.addAll(l_minuendYs);
1246
1247 // add intersection point to the polygon
1248 l_polygonXs.add(l_intersectX);
1249 l_polygonYs.add(l_intersectY);
1250
1251 // add the subtrahend's points to the polygon in reverse
1252 Collections.reverse(l_subtrahendXs);
1253 Collections.reverse(l_subtrahendYs);
1254 l_polygonXs.addAll(l_subtrahendXs);
1255 l_polygonYs.addAll(l_subtrahendYs);
1256
1257 // create an actual polygon
1258 b_positive = (l_subtrahendMaxY <= l_minuendMaxY)
1259 && (l_subtrahendMinY <= l_minuendMinY);
1260 createPolygon(x_graphics, x_dataArea, x_plot, x_domainAxis,
1261 x_rangeAxis, b_positive, l_polygonXs, l_polygonYs);
1262
1263 // clear the point vectors
1264 l_minuendXs.clear();
1265 l_minuendYs.clear();
1266 l_subtrahendXs.clear();
1267 l_subtrahendYs.clear();
1268 l_polygonXs.clear();
1269 l_polygonYs.clear();
1270
1271 // set the maxY and minY values to intersect y-value
1272 double l_y = l_intersectY.doubleValue();
1273 l_minuendMaxY = l_y;
1274 l_subtrahendMaxY = l_y;
1275 l_minuendMinY = l_y;
1276 l_subtrahendMinY = l_y;
1277
1278 // add interection point to new polygon
1279 l_polygonXs.add(l_intersectX);
1280 l_polygonYs.add(l_intersectY);
1281 }
1282
1283 // advance the minuend if needed
1284 if (l_x2 <= l_x4) {
1285 l_minuendItem++;
1286 b_minuendAdvanced = true;
1287 }
1288 else {
1289 b_minuendAdvanced = false;
1290 }
1291
1292 // advance the subtrahend if needed
1293 if (l_x4 <= l_x2) {
1294 l_subtrahendItem++;
1295 b_subtrahendAdvanced = true;
1296 }
1297 else {
1298 b_subtrahendAdvanced = false;
1299 }
1300
1301 b_minuendDone = (l_minuendItem == (l_minuendItemCount - 1));
1302 b_subtrahendDone = (l_subtrahendItem == (l_subtrahendItemCount
1303 - 1));
1304 }
1305
1306 // check if the final polygon needs to be clipped
1307 if (b_minuendDone && (l_x3 < l_x2) && (l_x2 < l_x4)) {
1308 // project onto subtrahend
1309 double l_slope = (l_y4 - l_y3) / (l_x4 - l_x3);
1310 l_subtrahendNextX = l_minuendNextX;
1311 l_subtrahendNextY = new Double((l_slope * l_x2)
1312 + (l_y3 - (l_slope * l_x3)));
1313 }
1314
1315 if (b_subtrahendDone && (l_x1 < l_x4) && (l_x4 < l_x2)) {
1316 // project onto minuend
1317 double l_slope = (l_y2 - l_y1) / (l_x2 - l_x1);
1318 l_minuendNextX = l_subtrahendNextX;
1319 l_minuendNextY = new Double((l_slope * l_x4)
1320 + (l_y1 - (l_slope * l_x1)));
1321 }
1322
1323 // consider last point of minuend and subtrahend for determining
1324 // positivity
1325 l_minuendMaxY = Math.max(l_minuendMaxY,
1326 l_minuendNextY.doubleValue());
1327 l_subtrahendMaxY = Math.max(l_subtrahendMaxY,
1328 l_subtrahendNextY.doubleValue());
1329 l_minuendMinY = Math.min(l_minuendMinY,
1330 l_minuendNextY.doubleValue());
1331 l_subtrahendMinY = Math.min(l_subtrahendMinY,
1332 l_subtrahendNextY.doubleValue());
1333
1334 // add the last point of the minuned and subtrahend
1335 l_minuendXs.add(l_minuendNextX);
1336 l_minuendYs.add(l_minuendNextY);
1337 l_subtrahendXs.add(l_subtrahendNextX);
1338 l_subtrahendYs.add(l_subtrahendNextY);
1339
1340 // create the polygon
1341 // add the minuend's points to polygon
1342 l_polygonXs.addAll(l_minuendXs);
1343 l_polygonYs.addAll(l_minuendYs);
1344
1345 // add the subtrahend's points to the polygon in reverse
1346 Collections.reverse(l_subtrahendXs);
1347 Collections.reverse(l_subtrahendYs);
1348 l_polygonXs.addAll(l_subtrahendXs);
1349 l_polygonYs.addAll(l_subtrahendYs);
1350
1351 // create an actual polygon
1352 b_positive = (l_subtrahendMaxY <= l_minuendMaxY)
1353 && (l_subtrahendMinY <= l_minuendMinY);
1354 createPolygon(x_graphics, x_dataArea, x_plot, x_domainAxis,
1355 x_rangeAxis, b_positive, l_polygonXs, l_polygonYs);
1356 }
1357
1358 /**
1359 * Draws the visual representation of a single data item, second pass. In
1360 * the second pass, the renderer draws the lines and shapes for the
1361 * individual points in the two series.
1362 *
1363 * @param x_graphics the graphics device.
1364 * @param x_dataArea the area within which the data is being drawn.
1365 * @param x_info collects information about the drawing.
1366 * @param x_plot the plot (can be used to obtain standard color
1367 * information etc).
1368 * @param x_domainAxis the domain (horizontal) axis.
1369 * @param x_rangeAxis the range (vertical) axis.
1370 * @param x_dataset the dataset.
1371 * @param x_series the series index (zero-based).
1372 * @param x_item the item index (zero-based).
1373 * @param x_crosshairState crosshair information for the plot
1374 * (<code>null</code> permitted).
1375 */
1376 protected void drawItemPass1(Graphics2D x_graphics,
1377 Rectangle2D x_dataArea,
1378 PlotRenderingInfo x_info,
1379 XYPlot x_plot,
1380 ValueAxis x_domainAxis,
1381 ValueAxis x_rangeAxis,
1382 XYDataset x_dataset,
1383 int x_series,
1384 int x_item,
1385 CrosshairState x_crosshairState) {
1386
1387 Shape l_entityArea = null;
1388 EntityCollection l_entities = null;
1389 if (null != x_info) {
1390 l_entities = x_info.getOwner().getEntityCollection();
1391 }
1392
1393 Paint l_seriesPaint = getItemPaint(x_series, x_item);
1394 Stroke l_seriesStroke = getItemStroke(x_series, x_item);
1395 x_graphics.setPaint(l_seriesPaint);
1396 x_graphics.setStroke(l_seriesStroke);
1397
1398 PlotOrientation l_orientation = x_plot.getOrientation();
1399 RectangleEdge l_domainAxisLocation = x_plot.getDomainAxisEdge();
1400 RectangleEdge l_rangeAxisLocation = x_plot.getRangeAxisEdge();
1401
1402 double l_x0 = x_dataset.getXValue(x_series, x_item);
1403 double l_y0 = x_dataset.getYValue(x_series, x_item);
1404 double l_x1 = x_domainAxis.valueToJava2D(l_x0, x_dataArea,
1405 l_domainAxisLocation);
1406 double l_y1 = x_rangeAxis.valueToJava2D(l_y0, x_dataArea,
1407 l_rangeAxisLocation);
1408
1409 // These are the shapes of the series items.
1410 if (getShapesVisible()) {
1411 Shape l_shape = getItemShape(x_series, x_item);
1412 if (l_orientation == PlotOrientation.HORIZONTAL) {
1413 l_shape = ShapeUtilities.createTranslatedShape(l_shape,
1414 l_y1, l_x1);
1415 }
1416 else {
1417 l_shape = ShapeUtilities.createTranslatedShape(l_shape,
1418 l_x1, l_y1);
1419 }
1420 if (l_shape.intersects(x_dataArea)) {
1421 x_graphics.setPaint(getItemPaint(x_series, x_item));
1422 x_graphics.fill(l_shape);
1423 /* TODO We could draw the shapes of single items here.
1424 if (drawOutline) {
1425 x_graphics.setPaint(this.outlinePaint);
1426 x_graphics.setStroke(this.outlineStroke);
1427 x_graphics.draw(l_shape);
1428 }
1429 */
1430 }
1431 l_entityArea = l_shape;
1432 } // if (getShapesVisible())
1433
1434 // add an entity for the item...
1435 if (null != l_entities) {
1436 if (null == l_entityArea) {
1437 l_entityArea = new Rectangle2D.Double((l_x1 - 2), (l_y1 - 2),
1438 4, 4);
1439 }
1440 String l_tip = null;
1441 XYToolTipGenerator l_tipGenerator = getToolTipGenerator(x_series,
1442 x_item);
1443 if (null != l_tipGenerator) {
1444 l_tip = l_tipGenerator.generateToolTip(x_dataset, x_series,
1445 x_item);
1446 }
1447 String l_url = null;
1448 XYURLGenerator l_urlGenerator = getURLGenerator();
1449 if (null != l_urlGenerator) {
1450 l_url = l_urlGenerator.generateURL(x_dataset, x_series,
1451 x_item);
1452 }
1453 XYItemEntity l_entity = new XYItemEntity(l_entityArea, x_dataset,
1454 x_series, x_item, l_tip, l_url);
1455 l_entities.add(l_entity);
1456 }
1457
1458 // draw the item label if there is one...
1459 if (isItemLabelVisible(x_series, x_item)) {
1460 drawItemLabel(x_graphics, l_orientation, x_dataset, x_series,
1461 x_item, l_x1, l_y1, (l_y1 < 0.0));
1462 }
1463
1464 int l_domainAxisIndex = x_plot.getDomainAxisIndex(x_domainAxis);
1465 int l_rangeAxisIndex = x_plot.getRangeAxisIndex(x_rangeAxis);
1466 updateCrosshairValues(x_crosshairState, l_x0, l_y0, l_domainAxisIndex,
1467 l_rangeAxisIndex, l_x1, l_y1, l_orientation);
1468
1469 if (0 == x_item) {
1470 return;
1471 }
1472
1473 double l_x2 = x_domainAxis.valueToJava2D(x_dataset.getXValue(x_series,
1474 (x_item - 1)), x_dataArea, l_domainAxisLocation);
1475 double l_y2 = x_rangeAxis.valueToJava2D(x_dataset.getYValue(x_series,
1476 (x_item - 1)), x_dataArea, l_rangeAxisLocation);
1477
1478 Line2D l_line = null;
1479 if (PlotOrientation.HORIZONTAL == l_orientation) {
1480 l_line = new Line2D.Double(l_y1, l_x1, l_y2, l_x2);
1481 }
1482 else if (PlotOrientation.VERTICAL == l_orientation) {
1483 l_line = new Line2D.Double(l_x1, l_y1, l_x2, l_y2);
1484 }
1485
1486 if ((null != l_line) && l_line.intersects(x_dataArea)) {
1487 x_graphics.setPaint(getItemPaint(x_series, x_item));
1488 x_graphics.setStroke(getItemStroke(x_series, x_item));
1489 if (drawOriginalSeries) {
1490 x_graphics.setPaint(this.outlinePaint);
1491 x_graphics.setStroke(this.outlineStroke);
1492 x_graphics.draw(l_line);
1493 }
1494 }
1495 }
1496
1497 /**
1498 * Determines if a dataset is degenerate. A degenerate dataset is a
1499 * dataset where either series has less than two (2) points.
1500 *
1501 * @param x_dataset the dataset.
1502 * @param x_impliedZeroSubtrahend if false, do not check the subtrahend
1503 *
1504 * @return true if the dataset is degenerate.
1505 */
1506 private boolean isEitherSeriesDegenerate(XYDataset x_dataset,
1507 boolean x_impliedZeroSubtrahend) {
1508
1509 if (x_impliedZeroSubtrahend) {
1510 return (x_dataset.getItemCount(0) < 2);
1511 }
1512
1513 return ((x_dataset.getItemCount(0) < 2)
1514 || (x_dataset.getItemCount(1) < 2));
1515 }
1516
1517 /**
1518 * Determines if the two (2) series are disjoint.
1519 * Disjoint series do not overlap in the domain space.
1520 *
1521 * @param x_dataset the dataset.
1522 *
1523 * @return true if the dataset is degenerate.
1524 */
1525 private boolean areSeriesDisjoint(XYDataset x_dataset) {
1526
1527 int l_minuendItemCount = x_dataset.getItemCount(0);
1528 double l_minuendFirst = x_dataset.getXValue(0, 0);
1529 double l_minuendLast = x_dataset.getXValue(0, l_minuendItemCount - 1);
1530
1531 int l_subtrahendItemCount = x_dataset.getItemCount(1);
1532 double l_subtrahendFirst = x_dataset.getXValue(1, 0);
1533 double l_subtrahendLast = x_dataset.getXValue(1,
1534 l_subtrahendItemCount - 1);
1535
1536 return ((l_minuendLast < l_subtrahendFirst)
1537 || (l_subtrahendLast < l_minuendFirst));
1538 }
1539
1540
1541 public void updateCentroid(Object [] xValues, Object [] yValues) {
1542 double x = 0d, y = 0d;
1543
1544 for (int i = 0, N = xValues.length; i < N; ++i) {
1545 x += ((Double)xValues[i]).doubleValue();
1546 y += ((Double)yValues[i]).doubleValue();
1547 }
1548
1549 x /= xValues.length;
1550 y /= yValues.length;
1551
1552 centroidNPoints++;
1553 double factorNew = 1d / centroidNPoints;
1554 double factorOld = 1d - factorNew;
1555
1556 centroid = new Point2D.Double((factorNew * x + factorOld * centroid.x),
1557 (factorNew * y + factorOld * centroid.y));
1558 }
1559
1560
1561 public static double calculateArea(Object [] xValues, Object [] yValues) {
1562 double area = 0d;
1563
1564 for (int i = 0, N = xValues.length; i < N; ++i) {
1565 int j = (i + 1) % N;
1566 double xi = ((Double)xValues[i]).doubleValue();
1567 double yi = ((Double)yValues[i]).doubleValue();
1568 double xj = ((Double)xValues[j]).doubleValue();
1569 double yj = ((Double)yValues[j]).doubleValue();
1570
1571 area += xi*yj;
1572 area -= xj*yi;
1573 // TODO centroid calculation here?
1574 }
1575
1576 return 0.5d*area;
1577 }
1578
1579 /**
1580 * Draws the visual representation of a polygon
1581 *
1582 * @param x_graphics the graphics device.
1583 * @param x_dataArea the area within which the data is being drawn.
1584 * @param x_plot the plot (can be used to obtain standard color
1585 * information etc).
1586 * @param x_domainAxis the domain (horizontal) axis.
1587 * @param x_rangeAxis the range (vertical) axis.
1588 * @param x_positive indicates if the polygon is positive (true) or
1589 * negative (false).
1590 * @param x_xValues a linked list of the x values (expects values to be
1591 * of type Double).
1592 * @param x_yValues a linked list of the y values (expects values to be
1593 * of type Double).
1594 */
1595 private void createPolygon (Graphics2D x_graphics,
1596 Rectangle2D x_dataArea,
1597 XYPlot x_plot,
1598 ValueAxis x_domainAxis,
1599 ValueAxis x_rangeAxis,
1600 boolean x_positive,
1601 LinkedList x_xValues,
1602 LinkedList x_yValues) {
1603
1604 PlotOrientation l_orientation = x_plot.getOrientation();
1605 RectangleEdge l_domainAxisLocation = x_plot.getDomainAxisEdge();
1606 RectangleEdge l_rangeAxisLocation = x_plot.getRangeAxisEdge();
1607
1608 Object[] l_xValues = x_xValues.toArray();
1609 Object[] l_yValues = x_yValues.toArray();
1610
1611 double area = calculateArea(l_xValues, l_yValues);
1612 if (x_positive) positiveArea += area;
1613 else negativeArea += area;
1614 updateCentroid(l_xValues, l_yValues);
1615
1616 GeneralPath l_path = new GeneralPath();
1617
1618 if (PlotOrientation.VERTICAL == l_orientation) {
1619 double l_x = x_domainAxis.valueToJava2D((
1620 (Double) l_xValues[0]).doubleValue(), x_dataArea,
1621 l_domainAxisLocation);
1622 if (this.roundXCoordinates) {
1623 l_x = Math.rint(l_x);
1624 }
1625
1626 double l_y = x_rangeAxis.valueToJava2D((
1627 (Double) l_yValues[0]).doubleValue(), x_dataArea,
1628 l_rangeAxisLocation);
1629
1630 l_path.moveTo((float) l_x, (float) l_y);
1631 for (int i = 1; i < l_xValues.length; i++) {
1632 l_x = x_domainAxis.valueToJava2D((
1633 (Double) l_xValues[i]).doubleValue(), x_dataArea,
1634 l_domainAxisLocation);
1635 if (this.roundXCoordinates) {
1636 l_x = Math.rint(l_x);
1637 }
1638
1639 l_y = x_rangeAxis.valueToJava2D((
1640 (Double) l_yValues[i]).doubleValue(), x_dataArea,
1641 l_rangeAxisLocation);
1642 l_path.lineTo((float) l_x, (float) l_y);
1643 }
1644 l_path.closePath();
1645 }
1646 else {
1647 double l_x = x_domainAxis.valueToJava2D((
1648 (Double) l_xValues[0]).doubleValue(), x_dataArea,
1649 l_domainAxisLocation);
1650 if (this.roundXCoordinates) {
1651 l_x = Math.rint(l_x);
1652 }
1653
1654 double l_y = x_rangeAxis.valueToJava2D((
1655 (Double) l_yValues[0]).doubleValue(), x_dataArea,
1656 l_rangeAxisLocation);
1657
1658 l_path.moveTo((float) l_y, (float) l_x);
1659 for (int i = 1; i < l_xValues.length; i++) {
1660 l_x = x_domainAxis.valueToJava2D((
1661 (Double) l_xValues[i]).doubleValue(), x_dataArea,
1662 l_domainAxisLocation);
1663 if (this.roundXCoordinates) {
1664 l_x = Math.rint(l_x);
1665 }
1666
1667 l_y = x_rangeAxis.valueToJava2D((
1668 (Double) l_yValues[i]).doubleValue(), x_dataArea,
1669 l_rangeAxisLocation);
1670 l_path.lineTo((float) l_y, (float) l_x);
1671 }
1672 l_path.closePath();
1673 }
1674
1675 if (l_path.intersects(x_dataArea)) {
1676 x_graphics.setPaint(x_positive ? getPositivePaint()
1677 : getNegativePaint());
1678 x_graphics.fill(l_path);
1679 if (drawOutline) {
1680 x_graphics.setStroke(this.outlineStroke);
1681 x_graphics.setPaint(this.outlinePaint);
1682 x_graphics.draw(l_path);
1683 }
1684 }
1685 }
1686
1687 /**
1688 * Returns a default legend item for the specified series. Subclasses
1689 * should override this method to generate customised items.
1690 *
1691 * @param datasetIndex the dataset index (zero-based).
1692 * @param series the series index (zero-based).
1693 *
1694 * @return A legend item for the series.
1695 */
1696 public LegendItem getLegendItem(int datasetIndex, int series) {
1697 LegendItem result = null;
1698 XYPlot p = getPlot();
1699 if (p != null) {
1700 XYDataset dataset = p.getDataset(datasetIndex);
1701 if (dataset != null) {
1702 if (getItemVisible(series, 0)) {
1703 String label = getLegendItemLabelGenerator().generateLabel(
1704 dataset, series);
1705 String description = label;
1706 String toolTipText = null;
1707 if (getLegendItemToolTipGenerator() != null) {
1708 toolTipText
1709 = getLegendItemToolTipGenerator().generateLabel(
1710 dataset, series);
1711 }
1712 String urlText = null;
1713 if (getLegendItemURLGenerator() != null) {
1714 urlText = getLegendItemURLGenerator().generateLabel(
1715 dataset, series);
1716 }
1717 // Individualized Paints:
1718 //Paint paint = lookupSeriesPaint(series);
1719
1720 // "Area-Style"- Paint.
1721 Paint paint = getPositivePaint();
1722 Stroke stroke = lookupSeriesStroke(series);
1723 Shape line = getLegendLine();
1724 // Not-filled Shape:
1725 //result = new LegendItem(label, description,
1726 // toolTipText, urlText, line, stroke, paint);
1727
1728 if (drawOutline) {
1729 // TODO Include outline style in legenditem (there is a constructor for that)
1730 }
1731
1732 // Filled Shape ("Area-Style").
1733 result = new LegendItem(label, description,
1734 toolTipText, urlText, line, paint);
1735 result.setLabelFont(lookupLegendTextFont(series));
1736 Paint labelPaint = lookupLegendTextPaint(series);
1737 if (labelPaint != null) {
1738 result.setLabelPaint(labelPaint);
1739 }
1740 result.setDataset(dataset);
1741 result.setDatasetIndex(datasetIndex);
1742 result.setSeriesKey(dataset.getSeriesKey(series));
1743 result.setSeriesIndex(series);
1744 }
1745 }
1746
1747 }
1748
1749 return result;
1750 }
1751
1752 /**
1753 * Tests this renderer for equality with an arbitrary object.
1754 *
1755 * @param obj the object (<code>null</code> permitted).
1756 *
1757 * @return A boolean.
1758 */
1759 public boolean equals(Object obj) {
1760 if (obj == this) {
1761 return true;
1762 }
1763 if (!(obj instanceof StableXYDifferenceRenderer)) {
1764 return false;
1765 }
1766 if (!super.equals(obj)) {
1767 return false;
1768 }
1769 StableXYDifferenceRenderer that = (StableXYDifferenceRenderer) obj;
1770 if (!PaintUtilities.equal(this.positivePaint, that.positivePaint)) {
1771 return false;
1772 }
1773 if (!PaintUtilities.equal(this.negativePaint, that.negativePaint)) {
1774 return false;
1775 }
1776 if (this.shapesVisible != that.shapesVisible) {
1777 return false;
1778 }
1779 if (!ShapeUtilities.equal(this.legendShape, that.legendShape)) {
1780 return false;
1781 }
1782 if (this.roundXCoordinates != that.roundXCoordinates) {
1783 return false;
1784 }
1785 return true;
1786 }
1787
1788 /**
1789 * Returns a clone of the renderer.
1790 *
1791 * @return A clone.
1792 *
1793 * @throws CloneNotSupportedException if the renderer cannot be cloned.
1794 */
1795 public Object clone() throws CloneNotSupportedException {
1796 StableXYDifferenceRenderer clone = (StableXYDifferenceRenderer) super.clone();
1797 clone.legendShape = ShapeUtilities.clone(this.legendShape);
1798 return clone;
1799 }
1800
1801 /**
1802 * Provides serialization support.
1803 *
1804 * @param stream the output stream.
1805 *
1806 * @throws IOException if there is an I/O error.
1807 */
1808 private void writeObject(ObjectOutputStream stream) throws IOException {
1809 stream.defaultWriteObject();
1810 SerialUtilities.writePaint(this.positivePaint, stream);
1811 SerialUtilities.writePaint(this.negativePaint, stream);
1812 SerialUtilities.writeShape(this.legendShape, stream);
1813 }
1814
1815 /**
1816 * Provides serialization support.
1817 *
1818 * @param stream the input stream.
1819 *
1820 * @throws IOException if there is an I/O error.
1821 * @throws ClassNotFoundException if there is a classpath problem.
1822 */
1823 private void readObject(ObjectInputStream stream)
1824 throws IOException, ClassNotFoundException {
1825 stream.defaultReadObject();
1826 this.positivePaint = SerialUtilities.readPaint(stream);
1827 this.negativePaint = SerialUtilities.readPaint(stream);
1828 this.legendShape = SerialUtilities.readShape(stream);
1829 }
1830 }
1831 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org