comparison artifacts/src/main/java/org/dive4elements/river/jfree/StableXYDifferenceRenderer.java @ 5838:5aa05a7a34b7

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

http://dive4elements.wald.intevation.org