comparison flys-artifacts/src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.java @ 3814:8083f6384023

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

http://dive4elements.wald.intevation.org