comparison flys-artifacts/src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.java @ 3818:dc18457b1cef

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

http://dive4elements.wald.intevation.org