comparison flys-artifacts/src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.java @ 1823:7a11b37d8594

Added a copy of JFreeChart's XYDifferenceRenderer flys-artifacts/trunk@3152 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Thu, 03 Nov 2011 10:32:17 +0000
parents
children 982956bde69e
comparison
equal deleted inserted replaced
1822:6ed439ff61bf 1823:7a11b37d8594
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
77 package org.jfree.chart.renderer.xy;
78
79 import java.awt.Color;
80 import java.awt.Graphics2D;
81 import java.awt.Paint;
82 import java.awt.Shape;
83 import java.awt.Stroke;
84 import java.awt.geom.GeneralPath;
85 import java.awt.geom.Line2D;
86 import java.awt.geom.Rectangle2D;
87 import java.io.IOException;
88 import java.io.ObjectInputStream;
89 import java.io.ObjectOutputStream;
90 import java.util.Collections;
91 import java.util.LinkedList;
92
93 import org.jfree.chart.LegendItem;
94 import org.jfree.chart.axis.ValueAxis;
95 import org.jfree.chart.entity.EntityCollection;
96 import org.jfree.chart.entity.XYItemEntity;
97 import org.jfree.chart.event.RendererChangeEvent;
98 import org.jfree.chart.labels.XYToolTipGenerator;
99 import org.jfree.chart.plot.CrosshairState;
100 import org.jfree.chart.plot.PlotOrientation;
101 import org.jfree.chart.plot.PlotRenderingInfo;
102 import org.jfree.chart.plot.XYPlot;
103 import org.jfree.chart.urls.XYURLGenerator;
104 import org.jfree.data.xy.XYDataset;
105 import org.jfree.io.SerialUtilities;
106 import org.jfree.ui.RectangleEdge;
107 import org.jfree.util.PaintUtilities;
108 import org.jfree.util.PublicCloneable;
109 import org.jfree.util.ShapeUtilities;
110
111 /**
112 * A renderer for an {@link XYPlot} that highlights the differences between two
113 * series. The example shown here is generated by the
114 * <code>DifferenceChartDemo1.java</code> program included in the JFreeChart
115 * demo collection:
116 * <br><br>
117 * <img src="../../../../../images/StableXYDifferenceRendererSample.png"
118 * alt="StableXYDifferenceRendererSample.png" />
119 */
120 public class StableXYDifferenceRenderer extends AbstractXYItemRenderer
121 implements XYItemRenderer, PublicCloneable {
122
123 /** For serialization. */
124 private static final long serialVersionUID = -8447915602375584857L;
125
126 /** The paint used to highlight positive differences (y(0) > y(1)). */
127 private transient Paint positivePaint;
128
129 /** The paint used to highlight negative differences (y(0) < y(1)). */
130 private transient Paint negativePaint;
131
132 /** Display shapes at each point? */
133 private boolean shapesVisible;
134
135 /** The shape to display in the legend item. */
136 private transient Shape legendLine;
137
138 /**
139 * This flag controls whether or not the x-coordinates (in Java2D space)
140 * are rounded to integers. When set to true, this can avoid the vertical
141 * striping that anti-aliasing can generate. However, the rounding may not
142 * be appropriate for output in high resolution formats (for example,
143 * vector graphics formats such as SVG and PDF).
144 *
145 * @since 1.0.4
146 */
147 private boolean roundXCoordinates;
148
149 /**
150 * Creates a new renderer with default attributes.
151 */
152 public StableXYDifferenceRenderer() {
153 this(Color.green, Color.red, false);
154 }
155
156 /**
157 * Creates a new renderer.
158 *
159 * @param positivePaint the highlight color for positive differences
160 * (<code>null</code> not permitted).
161 * @param negativePaint the highlight color for negative differences
162 * (<code>null</code> not permitted).
163 * @param shapes draw shapes?
164 */
165 public StableXYDifferenceRenderer(Paint positivePaint, Paint negativePaint,
166 boolean shapes) {
167 if (positivePaint == null) {
168 throw new IllegalArgumentException(
169 "Null 'positivePaint' argument.");
170 }
171 if (negativePaint == null) {
172 throw new IllegalArgumentException(
173 "Null 'negativePaint' argument.");
174 }
175 this.positivePaint = positivePaint;
176 this.negativePaint = negativePaint;
177 this.shapesVisible = shapes;
178 this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
179 this.roundXCoordinates = false;
180 }
181
182 /**
183 * Returns the paint used to highlight positive differences.
184 *
185 * @return The paint (never <code>null</code>).
186 *
187 * @see #setPositivePaint(Paint)
188 */
189 public Paint getPositivePaint() {
190 return this.positivePaint;
191 }
192
193 /**
194 * Sets the paint used to highlight positive differences and sends a
195 * {@link RendererChangeEvent} to all registered listeners.
196 *
197 * @param paint the paint (<code>null</code> not permitted).
198 *
199 * @see #getPositivePaint()
200 */
201 public void setPositivePaint(Paint paint) {
202 if (paint == null) {
203 throw new IllegalArgumentException("Null 'paint' argument.");
204 }
205 this.positivePaint = paint;
206 fireChangeEvent();
207 }
208
209 /**
210 * Returns the paint used to highlight negative differences.
211 *
212 * @return The paint (never <code>null</code>).
213 *
214 * @see #setNegativePaint(Paint)
215 */
216 public Paint getNegativePaint() {
217 return this.negativePaint;
218 }
219
220 /**
221 * Sets the paint used to highlight negative differences.
222 *
223 * @param paint the paint (<code>null</code> not permitted).
224 *
225 * @see #getNegativePaint()
226 */
227 public void setNegativePaint(Paint paint) {
228 if (paint == null) {
229 throw new IllegalArgumentException("Null 'paint' argument.");
230 }
231 this.negativePaint = paint;
232 notifyListeners(new RendererChangeEvent(this));
233 }
234
235 /**
236 * Returns a flag that controls whether or not shapes are drawn for each
237 * data value.
238 *
239 * @return A boolean.
240 *
241 * @see #setShapesVisible(boolean)
242 */
243 public boolean getShapesVisible() {
244 return this.shapesVisible;
245 }
246
247 /**
248 * Sets a flag that controls whether or not shapes are drawn for each
249 * data value, and sends a {@link RendererChangeEvent} to all registered
250 * listeners.
251 *
252 * @param flag the flag.
253 *
254 * @see #getShapesVisible()
255 */
256 public void setShapesVisible(boolean flag) {
257 this.shapesVisible = flag;
258 fireChangeEvent();
259 }
260
261 /**
262 * Returns the shape used to represent a line in the legend.
263 *
264 * @return The legend line (never <code>null</code>).
265 *
266 * @see #setLegendLine(Shape)
267 */
268 public Shape getLegendLine() {
269 return this.legendLine;
270 }
271
272 /**
273 * Sets the shape used as a line in each legend item and sends a
274 * {@link RendererChangeEvent} to all registered listeners.
275 *
276 * @param line the line (<code>null</code> not permitted).
277 *
278 * @see #getLegendLine()
279 */
280 public void setLegendLine(Shape line) {
281 if (line == null) {
282 throw new IllegalArgumentException("Null 'line' argument.");
283 }
284 this.legendLine = line;
285 fireChangeEvent();
286 }
287
288 /**
289 * Returns the flag that controls whether or not the x-coordinates (in
290 * Java2D space) are rounded to integer values.
291 *
292 * @return The flag.
293 *
294 * @since 1.0.4
295 *
296 * @see #setRoundXCoordinates(boolean)
297 */
298 public boolean getRoundXCoordinates() {
299 return this.roundXCoordinates;
300 }
301
302 /**
303 * Sets the flag that controls whether or not the x-coordinates (in
304 * Java2D space) are rounded to integer values, and sends a
305 * {@link RendererChangeEvent} to all registered listeners.
306 *
307 * @param round the new flag value.
308 *
309 * @since 1.0.4
310 *
311 * @see #getRoundXCoordinates()
312 */
313 public void setRoundXCoordinates(boolean round) {
314 this.roundXCoordinates = round;
315 fireChangeEvent();
316 }
317
318 /**
319 * Initialises the renderer and returns a state object that should be
320 * passed to subsequent calls to the drawItem() method. This method will
321 * be called before the first item is rendered, giving the renderer an
322 * opportunity to initialise any state information it wants to maintain.
323 * The renderer can do nothing if it chooses.
324 *
325 * @param g2 the graphics device.
326 * @param dataArea the area inside the axes.
327 * @param plot the plot.
328 * @param data the data.
329 * @param info an optional info collection object to return data back to
330 * the caller.
331 *
332 * @return A state object.
333 */
334 public XYItemRendererState initialise(Graphics2D g2,
335 Rectangle2D dataArea,
336 XYPlot plot,
337 XYDataset data,
338 PlotRenderingInfo info) {
339
340 XYItemRendererState state = super.initialise(g2, dataArea, plot, data,
341 info);
342 state.setProcessVisibleItemsOnly(false);
343 return state;
344
345 }
346
347 /**
348 * Returns <code>2</code>, the number of passes required by the renderer.
349 * The {@link XYPlot} will run through the dataset this number of times.
350 *
351 * @return The number of passes required by the renderer.
352 */
353 public int getPassCount() {
354 return 2;
355 }
356
357 /**
358 * Draws the visual representation of a single data item.
359 *
360 * @param g2 the graphics device.
361 * @param state the renderer state.
362 * @param dataArea the area within which the data is being drawn.
363 * @param info collects information about the drawing.
364 * @param plot the plot (can be used to obtain standard color
365 * information etc).
366 * @param domainAxis the domain (horizontal) axis.
367 * @param rangeAxis the range (vertical) axis.
368 * @param dataset the dataset.
369 * @param series the series index (zero-based).
370 * @param item the item index (zero-based).
371 * @param crosshairState crosshair information for the plot
372 * (<code>null</code> permitted).
373 * @param pass the pass index.
374 */
375 public void drawItem(Graphics2D g2,
376 XYItemRendererState state,
377 Rectangle2D dataArea,
378 PlotRenderingInfo info,
379 XYPlot plot,
380 ValueAxis domainAxis,
381 ValueAxis rangeAxis,
382 XYDataset dataset,
383 int series,
384 int item,
385 CrosshairState crosshairState,
386 int pass) {
387
388 if (pass == 0) {
389 drawItemPass0(g2, dataArea, info, plot, domainAxis, rangeAxis,
390 dataset, series, item, crosshairState);
391 }
392 else if (pass == 1) {
393 drawItemPass1(g2, dataArea, info, plot, domainAxis, rangeAxis,
394 dataset, series, item, crosshairState);
395 }
396
397 }
398
399 /**
400 * Draws the visual representation of a single data item, first pass.
401 *
402 * @param x_graphics the graphics device.
403 * @param x_dataArea the area within which the data is being drawn.
404 * @param x_info collects information about the drawing.
405 * @param x_plot the plot (can be used to obtain standard color
406 * information etc).
407 * @param x_domainAxis the domain (horizontal) axis.
408 * @param x_rangeAxis the range (vertical) axis.
409 * @param x_dataset the dataset.
410 * @param x_series the series index (zero-based).
411 * @param x_item the item index (zero-based).
412 * @param x_crosshairState crosshair information for the plot
413 * (<code>null</code> permitted).
414 */
415 protected void drawItemPass0(Graphics2D x_graphics,
416 Rectangle2D x_dataArea,
417 PlotRenderingInfo x_info,
418 XYPlot x_plot,
419 ValueAxis x_domainAxis,
420 ValueAxis x_rangeAxis,
421 XYDataset x_dataset,
422 int x_series,
423 int x_item,
424 CrosshairState x_crosshairState) {
425
426 if (!((0 == x_series) && (0 == x_item))) {
427 return;
428 }
429
430 boolean b_impliedZeroSubtrahend = (1 == x_dataset.getSeriesCount());
431
432 // check if either series is a degenerate case (i.e. less than 2 points)
433 if (isEitherSeriesDegenerate(x_dataset, b_impliedZeroSubtrahend)) {
434 return;
435 }
436
437 // check if series are disjoint (i.e. domain-spans do not overlap)
438 if (!b_impliedZeroSubtrahend && areSeriesDisjoint(x_dataset)) {
439 return;
440 }
441
442 // polygon definitions
443 LinkedList l_minuendXs = new LinkedList();
444 LinkedList l_minuendYs = new LinkedList();
445 LinkedList l_subtrahendXs = new LinkedList();
446 LinkedList l_subtrahendYs = new LinkedList();
447 LinkedList l_polygonXs = new LinkedList();
448 LinkedList l_polygonYs = new LinkedList();
449
450 // state
451 int l_minuendItem = 0;
452 int l_minuendItemCount = x_dataset.getItemCount(0);
453 Double l_minuendCurX = null;
454 Double l_minuendNextX = null;
455 Double l_minuendCurY = null;
456 Double l_minuendNextY = null;
457 double l_minuendMaxY = Double.NEGATIVE_INFINITY;
458 double l_minuendMinY = Double.POSITIVE_INFINITY;
459
460 int l_subtrahendItem = 0;
461 int l_subtrahendItemCount = 0; // actual value set below
462 Double l_subtrahendCurX = null;
463 Double l_subtrahendNextX = null;
464 Double l_subtrahendCurY = null;
465 Double l_subtrahendNextY = null;
466 double l_subtrahendMaxY = Double.NEGATIVE_INFINITY;
467 double l_subtrahendMinY = Double.POSITIVE_INFINITY;
468
469 // if a subtrahend is not specified, assume it is zero
470 if (b_impliedZeroSubtrahend) {
471 l_subtrahendItem = 0;
472 l_subtrahendItemCount = 2;
473 l_subtrahendCurX = new Double(x_dataset.getXValue(0, 0));
474 l_subtrahendNextX = new Double(x_dataset.getXValue(0,
475 (l_minuendItemCount - 1)));
476 l_subtrahendCurY = new Double(0.0);
477 l_subtrahendNextY = new Double(0.0);
478 l_subtrahendMaxY = 0.0;
479 l_subtrahendMinY = 0.0;
480
481 l_subtrahendXs.add(l_subtrahendCurX);
482 l_subtrahendYs.add(l_subtrahendCurY);
483 }
484 else {
485 l_subtrahendItemCount = x_dataset.getItemCount(1);
486 }
487
488 boolean b_minuendDone = false;
489 boolean b_minuendAdvanced = true;
490 boolean b_minuendAtIntersect = false;
491 boolean b_minuendFastForward = false;
492 boolean b_subtrahendDone = false;
493 boolean b_subtrahendAdvanced = true;
494 boolean b_subtrahendAtIntersect = false;
495 boolean b_subtrahendFastForward = false;
496 boolean b_colinear = false;
497
498 boolean b_positive;
499
500 // coordinate pairs
501 double l_x1 = 0.0, l_y1 = 0.0; // current minuend point
502 double l_x2 = 0.0, l_y2 = 0.0; // next minuend point
503 double l_x3 = 0.0, l_y3 = 0.0; // current subtrahend point
504 double l_x4 = 0.0, l_y4 = 0.0; // next subtrahend point
505
506 // fast-forward through leading tails
507 boolean b_fastForwardDone = false;
508 while (!b_fastForwardDone) {
509 // get the x and y coordinates
510 l_x1 = x_dataset.getXValue(0, l_minuendItem);
511 l_y1 = x_dataset.getYValue(0, l_minuendItem);
512 l_x2 = x_dataset.getXValue(0, l_minuendItem + 1);
513 l_y2 = x_dataset.getYValue(0, l_minuendItem + 1);
514
515 l_minuendCurX = new Double(l_x1);
516 l_minuendCurY = new Double(l_y1);
517 l_minuendNextX = new Double(l_x2);
518 l_minuendNextY = new Double(l_y2);
519
520 if (b_impliedZeroSubtrahend) {
521 l_x3 = l_subtrahendCurX.doubleValue();
522 l_y3 = l_subtrahendCurY.doubleValue();
523 l_x4 = l_subtrahendNextX.doubleValue();
524 l_y4 = l_subtrahendNextY.doubleValue();
525 }
526 else {
527 l_x3 = x_dataset.getXValue(1, l_subtrahendItem);
528 l_y3 = x_dataset.getYValue(1, l_subtrahendItem);
529 l_x4 = x_dataset.getXValue(1, l_subtrahendItem + 1);
530 l_y4 = x_dataset.getYValue(1, l_subtrahendItem + 1);
531
532 l_subtrahendCurX = new Double(l_x3);
533 l_subtrahendCurY = new Double(l_y3);
534 l_subtrahendNextX = new Double(l_x4);
535 l_subtrahendNextY = new Double(l_y4);
536 }
537
538 if (l_x2 <= l_x3) {
539 // minuend needs to be fast forwarded
540 l_minuendItem++;
541 b_minuendFastForward = true;
542 continue;
543 }
544
545 if (l_x4 <= l_x1) {
546 // subtrahend needs to be fast forwarded
547 l_subtrahendItem++;
548 b_subtrahendFastForward = true;
549 continue;
550 }
551
552 // check if initial polygon needs to be clipped
553 if ((l_x3 < l_x1) && (l_x1 < l_x4)) {
554 // project onto subtrahend
555 double l_slope = (l_y4 - l_y3) / (l_x4 - l_x3);
556 l_subtrahendCurX = l_minuendCurX;
557 l_subtrahendCurY = new Double((l_slope * l_x1)
558 + (l_y3 - (l_slope * l_x3)));
559
560 l_subtrahendXs.add(l_subtrahendCurX);
561 l_subtrahendYs.add(l_subtrahendCurY);
562 }
563
564 if ((l_x1 < l_x3) && (l_x3 < l_x2)) {
565 // project onto minuend
566 double l_slope = (l_y2 - l_y1) / (l_x2 - l_x1);
567 l_minuendCurX = l_subtrahendCurX;
568 l_minuendCurY = new Double((l_slope * l_x3)
569 + (l_y1 - (l_slope * l_x1)));
570
571 l_minuendXs.add(l_minuendCurX);
572 l_minuendYs.add(l_minuendCurY);
573 }
574
575 l_minuendMaxY = l_minuendCurY.doubleValue();
576 l_minuendMinY = l_minuendCurY.doubleValue();
577 l_subtrahendMaxY = l_subtrahendCurY.doubleValue();
578 l_subtrahendMinY = l_subtrahendCurY.doubleValue();
579
580 b_fastForwardDone = true;
581 }
582
583 // start of algorithm
584 while (!b_minuendDone && !b_subtrahendDone) {
585 if (!b_minuendDone && !b_minuendFastForward && b_minuendAdvanced) {
586 l_x1 = x_dataset.getXValue(0, l_minuendItem);
587 l_y1 = x_dataset.getYValue(0, l_minuendItem);
588 l_minuendCurX = new Double(l_x1);
589 l_minuendCurY = new Double(l_y1);
590
591 if (!b_minuendAtIntersect) {
592 l_minuendXs.add(l_minuendCurX);
593 l_minuendYs.add(l_minuendCurY);
594 }
595
596 l_minuendMaxY = Math.max(l_minuendMaxY, l_y1);
597 l_minuendMinY = Math.min(l_minuendMinY, l_y1);
598
599 l_x2 = x_dataset.getXValue(0, l_minuendItem + 1);
600 l_y2 = x_dataset.getYValue(0, l_minuendItem + 1);
601 l_minuendNextX = new Double(l_x2);
602 l_minuendNextY = new Double(l_y2);
603 }
604
605 // never updated the subtrahend if it is implied to be zero
606 if (!b_impliedZeroSubtrahend && !b_subtrahendDone
607 && !b_subtrahendFastForward && b_subtrahendAdvanced) {
608 l_x3 = x_dataset.getXValue(1, l_subtrahendItem);
609 l_y3 = x_dataset.getYValue(1, l_subtrahendItem);
610 l_subtrahendCurX = new Double(l_x3);
611 l_subtrahendCurY = new Double(l_y3);
612
613 if (!b_subtrahendAtIntersect) {
614 l_subtrahendXs.add(l_subtrahendCurX);
615 l_subtrahendYs.add(l_subtrahendCurY);
616 }
617
618 l_subtrahendMaxY = Math.max(l_subtrahendMaxY, l_y3);
619 l_subtrahendMinY = Math.min(l_subtrahendMinY, l_y3);
620
621 l_x4 = x_dataset.getXValue(1, l_subtrahendItem + 1);
622 l_y4 = x_dataset.getYValue(1, l_subtrahendItem + 1);
623 l_subtrahendNextX = new Double(l_x4);
624 l_subtrahendNextY = new Double(l_y4);
625 }
626
627 // deassert b_*FastForward (only matters for 1st time through loop)
628 b_minuendFastForward = false;
629 b_subtrahendFastForward = false;
630
631 Double l_intersectX = null;
632 Double l_intersectY = null;
633 boolean b_intersect = false;
634
635 b_minuendAtIntersect = false;
636 b_subtrahendAtIntersect = false;
637
638 // check for intersect
639 if ((l_x2 == l_x4) && (l_y2 == l_y4)) {
640 // check if line segments are colinear
641 if ((l_x1 == l_x3) && (l_y1 == l_y3)) {
642 b_colinear = true;
643 }
644 else {
645 // the intersect is at the next point for both the minuend
646 // and subtrahend
647 l_intersectX = new Double(l_x2);
648 l_intersectY = new Double(l_y2);
649
650 b_intersect = true;
651 b_minuendAtIntersect = true;
652 b_subtrahendAtIntersect = true;
653 }
654 }
655 else {
656 // compute common denominator
657 double l_denominator = ((l_y4 - l_y3) * (l_x2 - l_x1))
658 - ((l_x4 - l_x3) * (l_y2 - l_y1));
659
660 // compute common deltas
661 double l_deltaY = l_y1 - l_y3;
662 double l_deltaX = l_x1 - l_x3;
663
664 // compute numerators
665 double l_numeratorA = ((l_x4 - l_x3) * l_deltaY)
666 - ((l_y4 - l_y3) * l_deltaX);
667 double l_numeratorB = ((l_x2 - l_x1) * l_deltaY)
668 - ((l_y2 - l_y1) * l_deltaX);
669
670 // check if line segments are colinear
671 if ((0 == l_numeratorA) && (0 == l_numeratorB)
672 && (0 == l_denominator)) {
673 b_colinear = true;
674 }
675 else {
676 // check if previously colinear
677 if (b_colinear) {
678 // clear colinear points and flag
679 l_minuendXs.clear();
680 l_minuendYs.clear();
681 l_subtrahendXs.clear();
682 l_subtrahendYs.clear();
683 l_polygonXs.clear();
684 l_polygonYs.clear();
685
686 b_colinear = false;
687
688 // set new starting point for the polygon
689 boolean b_useMinuend = ((l_x3 <= l_x1)
690 && (l_x1 <= l_x4));
691 l_polygonXs.add(b_useMinuend ? l_minuendCurX
692 : l_subtrahendCurX);
693 l_polygonYs.add(b_useMinuend ? l_minuendCurY
694 : l_subtrahendCurY);
695 }
696
697 // compute slope components
698 double l_slopeA = l_numeratorA / l_denominator;
699 double l_slopeB = l_numeratorB / l_denominator;
700
701 // check if the line segments intersect
702 if ((0 < l_slopeA) && (l_slopeA <= 1) && (0 < l_slopeB)
703 && (l_slopeB <= 1)) {
704 // compute the point of intersection
705 double l_xi = l_x1 + (l_slopeA * (l_x2 - l_x1));
706 double l_yi = l_y1 + (l_slopeA * (l_y2 - l_y1));
707
708 l_intersectX = new Double(l_xi);
709 l_intersectY = new Double(l_yi);
710 b_intersect = true;
711 b_minuendAtIntersect = ((l_xi == l_x2)
712 && (l_yi == l_y2));
713 b_subtrahendAtIntersect = ((l_xi == l_x4)
714 && (l_yi == l_y4));
715
716 // advance minuend and subtrahend to intesect
717 l_minuendCurX = l_intersectX;
718 l_minuendCurY = l_intersectY;
719 l_subtrahendCurX = l_intersectX;
720 l_subtrahendCurY = l_intersectY;
721 }
722 }
723 }
724
725 if (b_intersect) {
726 // create the polygon
727 // add the minuend's points to polygon
728 l_polygonXs.addAll(l_minuendXs);
729 l_polygonYs.addAll(l_minuendYs);
730
731 // add intersection point to the polygon
732 l_polygonXs.add(l_intersectX);
733 l_polygonYs.add(l_intersectY);
734
735 // add the subtrahend's points to the polygon in reverse
736 Collections.reverse(l_subtrahendXs);
737 Collections.reverse(l_subtrahendYs);
738 l_polygonXs.addAll(l_subtrahendXs);
739 l_polygonYs.addAll(l_subtrahendYs);
740
741 // create an actual polygon
742 b_positive = (l_subtrahendMaxY <= l_minuendMaxY)
743 && (l_subtrahendMinY <= l_minuendMinY);
744 createPolygon(x_graphics, x_dataArea, x_plot, x_domainAxis,
745 x_rangeAxis, b_positive, l_polygonXs, l_polygonYs);
746
747 // clear the point vectors
748 l_minuendXs.clear();
749 l_minuendYs.clear();
750 l_subtrahendXs.clear();
751 l_subtrahendYs.clear();
752 l_polygonXs.clear();
753 l_polygonYs.clear();
754
755 // set the maxY and minY values to intersect y-value
756 double l_y = l_intersectY.doubleValue();
757 l_minuendMaxY = l_y;
758 l_subtrahendMaxY = l_y;
759 l_minuendMinY = l_y;
760 l_subtrahendMinY = l_y;
761
762 // add interection point to new polygon
763 l_polygonXs.add(l_intersectX);
764 l_polygonYs.add(l_intersectY);
765 }
766
767 // advance the minuend if needed
768 if (l_x2 <= l_x4) {
769 l_minuendItem++;
770 b_minuendAdvanced = true;
771 }
772 else {
773 b_minuendAdvanced = false;
774 }
775
776 // advance the subtrahend if needed
777 if (l_x4 <= l_x2) {
778 l_subtrahendItem++;
779 b_subtrahendAdvanced = true;
780 }
781 else {
782 b_subtrahendAdvanced = false;
783 }
784
785 b_minuendDone = (l_minuendItem == (l_minuendItemCount - 1));
786 b_subtrahendDone = (l_subtrahendItem == (l_subtrahendItemCount
787 - 1));
788 }
789
790 // check if the final polygon needs to be clipped
791 if (b_minuendDone && (l_x3 < l_x2) && (l_x2 < l_x4)) {
792 // project onto subtrahend
793 double l_slope = (l_y4 - l_y3) / (l_x4 - l_x3);
794 l_subtrahendNextX = l_minuendNextX;
795 l_subtrahendNextY = new Double((l_slope * l_x2)
796 + (l_y3 - (l_slope * l_x3)));
797 }
798
799 if (b_subtrahendDone && (l_x1 < l_x4) && (l_x4 < l_x2)) {
800 // project onto minuend
801 double l_slope = (l_y2 - l_y1) / (l_x2 - l_x1);
802 l_minuendNextX = l_subtrahendNextX;
803 l_minuendNextY = new Double((l_slope * l_x4)
804 + (l_y1 - (l_slope * l_x1)));
805 }
806
807 // consider last point of minuend and subtrahend for determining
808 // positivity
809 l_minuendMaxY = Math.max(l_minuendMaxY,
810 l_minuendNextY.doubleValue());
811 l_subtrahendMaxY = Math.max(l_subtrahendMaxY,
812 l_subtrahendNextY.doubleValue());
813 l_minuendMinY = Math.min(l_minuendMinY,
814 l_minuendNextY.doubleValue());
815 l_subtrahendMinY = Math.min(l_subtrahendMinY,
816 l_subtrahendNextY.doubleValue());
817
818 // add the last point of the minuned and subtrahend
819 l_minuendXs.add(l_minuendNextX);
820 l_minuendYs.add(l_minuendNextY);
821 l_subtrahendXs.add(l_subtrahendNextX);
822 l_subtrahendYs.add(l_subtrahendNextY);
823
824 // create the polygon
825 // add the minuend's points to polygon
826 l_polygonXs.addAll(l_minuendXs);
827 l_polygonYs.addAll(l_minuendYs);
828
829 // add the subtrahend's points to the polygon in reverse
830 Collections.reverse(l_subtrahendXs);
831 Collections.reverse(l_subtrahendYs);
832 l_polygonXs.addAll(l_subtrahendXs);
833 l_polygonYs.addAll(l_subtrahendYs);
834
835 // create an actual polygon
836 b_positive = (l_subtrahendMaxY <= l_minuendMaxY)
837 && (l_subtrahendMinY <= l_minuendMinY);
838 createPolygon(x_graphics, x_dataArea, x_plot, x_domainAxis,
839 x_rangeAxis, b_positive, l_polygonXs, l_polygonYs);
840 }
841
842 /**
843 * Draws the visual representation of a single data item, second pass. In
844 * the second pass, the renderer draws the lines and shapes for the
845 * individual points in the two series.
846 *
847 * @param x_graphics the graphics device.
848 * @param x_dataArea the area within which the data is being drawn.
849 * @param x_info collects information about the drawing.
850 * @param x_plot the plot (can be used to obtain standard color
851 * information etc).
852 * @param x_domainAxis the domain (horizontal) axis.
853 * @param x_rangeAxis the range (vertical) axis.
854 * @param x_dataset the dataset.
855 * @param x_series the series index (zero-based).
856 * @param x_item the item index (zero-based).
857 * @param x_crosshairState crosshair information for the plot
858 * (<code>null</code> permitted).
859 */
860 protected void drawItemPass1(Graphics2D x_graphics,
861 Rectangle2D x_dataArea,
862 PlotRenderingInfo x_info,
863 XYPlot x_plot,
864 ValueAxis x_domainAxis,
865 ValueAxis x_rangeAxis,
866 XYDataset x_dataset,
867 int x_series,
868 int x_item,
869 CrosshairState x_crosshairState) {
870
871 Shape l_entityArea = null;
872 EntityCollection l_entities = null;
873 if (null != x_info) {
874 l_entities = x_info.getOwner().getEntityCollection();
875 }
876
877 Paint l_seriesPaint = getItemPaint(x_series, x_item);
878 Stroke l_seriesStroke = getItemStroke(x_series, x_item);
879 x_graphics.setPaint(l_seriesPaint);
880 x_graphics.setStroke(l_seriesStroke);
881
882 PlotOrientation l_orientation = x_plot.getOrientation();
883 RectangleEdge l_domainAxisLocation = x_plot.getDomainAxisEdge();
884 RectangleEdge l_rangeAxisLocation = x_plot.getRangeAxisEdge();
885
886 double l_x0 = x_dataset.getXValue(x_series, x_item);
887 double l_y0 = x_dataset.getYValue(x_series, x_item);
888 double l_x1 = x_domainAxis.valueToJava2D(l_x0, x_dataArea,
889 l_domainAxisLocation);
890 double l_y1 = x_rangeAxis.valueToJava2D(l_y0, x_dataArea,
891 l_rangeAxisLocation);
892
893 if (getShapesVisible()) {
894 Shape l_shape = getItemShape(x_series, x_item);
895 if (l_orientation == PlotOrientation.HORIZONTAL) {
896 l_shape = ShapeUtilities.createTranslatedShape(l_shape,
897 l_y1, l_x1);
898 }
899 else {
900 l_shape = ShapeUtilities.createTranslatedShape(l_shape,
901 l_x1, l_y1);
902 }
903 if (l_shape.intersects(x_dataArea)) {
904 x_graphics.setPaint(getItemPaint(x_series, x_item));
905 x_graphics.fill(l_shape);
906 }
907 l_entityArea = l_shape;
908 }
909
910 // add an entity for the item...
911 if (null != l_entities) {
912 if (null == l_entityArea) {
913 l_entityArea = new Rectangle2D.Double((l_x1 - 2), (l_y1 - 2),
914 4, 4);
915 }
916 String l_tip = null;
917 XYToolTipGenerator l_tipGenerator = getToolTipGenerator(x_series,
918 x_item);
919 if (null != l_tipGenerator) {
920 l_tip = l_tipGenerator.generateToolTip(x_dataset, x_series,
921 x_item);
922 }
923 String l_url = null;
924 XYURLGenerator l_urlGenerator = getURLGenerator();
925 if (null != l_urlGenerator) {
926 l_url = l_urlGenerator.generateURL(x_dataset, x_series,
927 x_item);
928 }
929 XYItemEntity l_entity = new XYItemEntity(l_entityArea, x_dataset,
930 x_series, x_item, l_tip, l_url);
931 l_entities.add(l_entity);
932 }
933
934 // draw the item label if there is one...
935 if (isItemLabelVisible(x_series, x_item)) {
936 drawItemLabel(x_graphics, l_orientation, x_dataset, x_series,
937 x_item, l_x1, l_y1, (l_y1 < 0.0));
938 }
939
940 int l_domainAxisIndex = x_plot.getDomainAxisIndex(x_domainAxis);
941 int l_rangeAxisIndex = x_plot.getRangeAxisIndex(x_rangeAxis);
942 updateCrosshairValues(x_crosshairState, l_x0, l_y0, l_domainAxisIndex,
943 l_rangeAxisIndex, l_x1, l_y1, l_orientation);
944
945 if (0 == x_item) {
946 return;
947 }
948
949 double l_x2 = x_domainAxis.valueToJava2D(x_dataset.getXValue(x_series,
950 (x_item - 1)), x_dataArea, l_domainAxisLocation);
951 double l_y2 = x_rangeAxis.valueToJava2D(x_dataset.getYValue(x_series,
952 (x_item - 1)), x_dataArea, l_rangeAxisLocation);
953
954 Line2D l_line = null;
955 if (PlotOrientation.HORIZONTAL == l_orientation) {
956 l_line = new Line2D.Double(l_y1, l_x1, l_y2, l_x2);
957 }
958 else if (PlotOrientation.VERTICAL == l_orientation) {
959 l_line = new Line2D.Double(l_x1, l_y1, l_x2, l_y2);
960 }
961
962 if ((null != l_line) && l_line.intersects(x_dataArea)) {
963 x_graphics.setPaint(getItemPaint(x_series, x_item));
964 x_graphics.setStroke(getItemStroke(x_series, x_item));
965 x_graphics.draw(l_line);
966 }
967 }
968
969 /**
970 * Determines if a dataset is degenerate. A degenerate dataset is a
971 * dataset where either series has less than two (2) points.
972 *
973 * @param x_dataset the dataset.
974 * @param x_impliedZeroSubtrahend if false, do not check the subtrahend
975 *
976 * @return true if the dataset is degenerate.
977 */
978 private boolean isEitherSeriesDegenerate(XYDataset x_dataset,
979 boolean x_impliedZeroSubtrahend) {
980
981 if (x_impliedZeroSubtrahend) {
982 return (x_dataset.getItemCount(0) < 2);
983 }
984
985 return ((x_dataset.getItemCount(0) < 2)
986 || (x_dataset.getItemCount(1) < 2));
987 }
988
989 /**
990 * Determines if the two (2) series are disjoint.
991 * Disjoint series do not overlap in the domain space.
992 *
993 * @param x_dataset the dataset.
994 *
995 * @return true if the dataset is degenerate.
996 */
997 private boolean areSeriesDisjoint(XYDataset x_dataset) {
998
999 int l_minuendItemCount = x_dataset.getItemCount(0);
1000 double l_minuendFirst = x_dataset.getXValue(0, 0);
1001 double l_minuendLast = x_dataset.getXValue(0, l_minuendItemCount - 1);
1002
1003 int l_subtrahendItemCount = x_dataset.getItemCount(1);
1004 double l_subtrahendFirst = x_dataset.getXValue(1, 0);
1005 double l_subtrahendLast = x_dataset.getXValue(1,
1006 l_subtrahendItemCount - 1);
1007
1008 return ((l_minuendLast < l_subtrahendFirst)
1009 || (l_subtrahendLast < l_minuendFirst));
1010 }
1011
1012 /**
1013 * Draws the visual representation of a polygon
1014 *
1015 * @param x_graphics the graphics device.
1016 * @param x_dataArea the area within which the data is being drawn.
1017 * @param x_plot the plot (can be used to obtain standard color
1018 * information etc).
1019 * @param x_domainAxis the domain (horizontal) axis.
1020 * @param x_rangeAxis the range (vertical) axis.
1021 * @param x_positive indicates if the polygon is positive (true) or
1022 * negative (false).
1023 * @param x_xValues a linked list of the x values (expects values to be
1024 * of type Double).
1025 * @param x_yValues a linked list of the y values (expects values to be
1026 * of type Double).
1027 */
1028 private void createPolygon (Graphics2D x_graphics,
1029 Rectangle2D x_dataArea,
1030 XYPlot x_plot,
1031 ValueAxis x_domainAxis,
1032 ValueAxis x_rangeAxis,
1033 boolean x_positive,
1034 LinkedList x_xValues,
1035 LinkedList x_yValues) {
1036
1037 PlotOrientation l_orientation = x_plot.getOrientation();
1038 RectangleEdge l_domainAxisLocation = x_plot.getDomainAxisEdge();
1039 RectangleEdge l_rangeAxisLocation = x_plot.getRangeAxisEdge();
1040
1041 Object[] l_xValues = x_xValues.toArray();
1042 Object[] l_yValues = x_yValues.toArray();
1043
1044 GeneralPath l_path = new GeneralPath();
1045
1046 if (PlotOrientation.VERTICAL == l_orientation) {
1047 double l_x = x_domainAxis.valueToJava2D((
1048 (Double) l_xValues[0]).doubleValue(), x_dataArea,
1049 l_domainAxisLocation);
1050 if (this.roundXCoordinates) {
1051 l_x = Math.rint(l_x);
1052 }
1053
1054 double l_y = x_rangeAxis.valueToJava2D((
1055 (Double) l_yValues[0]).doubleValue(), x_dataArea,
1056 l_rangeAxisLocation);
1057
1058 l_path.moveTo((float) l_x, (float) l_y);
1059 for (int i = 1; i < l_xValues.length; i++) {
1060 l_x = x_domainAxis.valueToJava2D((
1061 (Double) l_xValues[i]).doubleValue(), x_dataArea,
1062 l_domainAxisLocation);
1063 if (this.roundXCoordinates) {
1064 l_x = Math.rint(l_x);
1065 }
1066
1067 l_y = x_rangeAxis.valueToJava2D((
1068 (Double) l_yValues[i]).doubleValue(), x_dataArea,
1069 l_rangeAxisLocation);
1070 l_path.lineTo((float) l_x, (float) l_y);
1071 }
1072 l_path.closePath();
1073 }
1074 else {
1075 double l_x = x_domainAxis.valueToJava2D((
1076 (Double) l_xValues[0]).doubleValue(), x_dataArea,
1077 l_domainAxisLocation);
1078 if (this.roundXCoordinates) {
1079 l_x = Math.rint(l_x);
1080 }
1081
1082 double l_y = x_rangeAxis.valueToJava2D((
1083 (Double) l_yValues[0]).doubleValue(), x_dataArea,
1084 l_rangeAxisLocation);
1085
1086 l_path.moveTo((float) l_y, (float) l_x);
1087 for (int i = 1; i < l_xValues.length; i++) {
1088 l_x = x_domainAxis.valueToJava2D((
1089 (Double) l_xValues[i]).doubleValue(), x_dataArea,
1090 l_domainAxisLocation);
1091 if (this.roundXCoordinates) {
1092 l_x = Math.rint(l_x);
1093 }
1094
1095 l_y = x_rangeAxis.valueToJava2D((
1096 (Double) l_yValues[i]).doubleValue(), x_dataArea,
1097 l_rangeAxisLocation);
1098 l_path.lineTo((float) l_y, (float) l_x);
1099 }
1100 l_path.closePath();
1101 }
1102
1103 if (l_path.intersects(x_dataArea)) {
1104 x_graphics.setPaint(x_positive ? getPositivePaint()
1105 : getNegativePaint());
1106 x_graphics.fill(l_path);
1107 }
1108 }
1109
1110 /**
1111 * Returns a default legend item for the specified series. Subclasses
1112 * should override this method to generate customised items.
1113 *
1114 * @param datasetIndex the dataset index (zero-based).
1115 * @param series the series index (zero-based).
1116 *
1117 * @return A legend item for the series.
1118 */
1119 public LegendItem getLegendItem(int datasetIndex, int series) {
1120 LegendItem result = null;
1121 XYPlot p = getPlot();
1122 if (p != null) {
1123 XYDataset dataset = p.getDataset(datasetIndex);
1124 if (dataset != null) {
1125 if (getItemVisible(series, 0)) {
1126 String label = getLegendItemLabelGenerator().generateLabel(
1127 dataset, series);
1128 String description = label;
1129 String toolTipText = null;
1130 if (getLegendItemToolTipGenerator() != null) {
1131 toolTipText
1132 = getLegendItemToolTipGenerator().generateLabel(
1133 dataset, series);
1134 }
1135 String urlText = null;
1136 if (getLegendItemURLGenerator() != null) {
1137 urlText = getLegendItemURLGenerator().generateLabel(
1138 dataset, series);
1139 }
1140 Paint paint = lookupSeriesPaint(series);
1141 Stroke stroke = lookupSeriesStroke(series);
1142 Shape line = getLegendLine();
1143 result = new LegendItem(label, description,
1144 toolTipText, urlText, line, stroke, paint);
1145 result.setLabelFont(lookupLegendTextFont(series));
1146 Paint labelPaint = lookupLegendTextPaint(series);
1147 if (labelPaint != null) {
1148 result.setLabelPaint(labelPaint);
1149 }
1150 result.setDataset(dataset);
1151 result.setDatasetIndex(datasetIndex);
1152 result.setSeriesKey(dataset.getSeriesKey(series));
1153 result.setSeriesIndex(series);
1154 }
1155 }
1156
1157 }
1158
1159 return result;
1160
1161 }
1162
1163 /**
1164 * Tests this renderer for equality with an arbitrary object.
1165 *
1166 * @param obj the object (<code>null</code> permitted).
1167 *
1168 * @return A boolean.
1169 */
1170 public boolean equals(Object obj) {
1171 if (obj == this) {
1172 return true;
1173 }
1174 if (!(obj instanceof StableXYDifferenceRenderer)) {
1175 return false;
1176 }
1177 if (!super.equals(obj)) {
1178 return false;
1179 }
1180 StableXYDifferenceRenderer that = (StableXYDifferenceRenderer) obj;
1181 if (!PaintUtilities.equal(this.positivePaint, that.positivePaint)) {
1182 return false;
1183 }
1184 if (!PaintUtilities.equal(this.negativePaint, that.negativePaint)) {
1185 return false;
1186 }
1187 if (this.shapesVisible != that.shapesVisible) {
1188 return false;
1189 }
1190 if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) {
1191 return false;
1192 }
1193 if (this.roundXCoordinates != that.roundXCoordinates) {
1194 return false;
1195 }
1196 return true;
1197 }
1198
1199 /**
1200 * Returns a clone of the renderer.
1201 *
1202 * @return A clone.
1203 *
1204 * @throws CloneNotSupportedException if the renderer cannot be cloned.
1205 */
1206 public Object clone() throws CloneNotSupportedException {
1207 StableXYDifferenceRenderer clone = (StableXYDifferenceRenderer) super.clone();
1208 clone.legendLine = ShapeUtilities.clone(this.legendLine);
1209 return clone;
1210 }
1211
1212 /**
1213 * Provides serialization support.
1214 *
1215 * @param stream the output stream.
1216 *
1217 * @throws IOException if there is an I/O error.
1218 */
1219 private void writeObject(ObjectOutputStream stream) throws IOException {
1220 stream.defaultWriteObject();
1221 SerialUtilities.writePaint(this.positivePaint, stream);
1222 SerialUtilities.writePaint(this.negativePaint, stream);
1223 SerialUtilities.writeShape(this.legendLine, stream);
1224 }
1225
1226 /**
1227 * Provides serialization support.
1228 *
1229 * @param stream the input stream.
1230 *
1231 * @throws IOException if there is an I/O error.
1232 * @throws ClassNotFoundException if there is a classpath problem.
1233 */
1234 private void readObject(ObjectInputStream stream)
1235 throws IOException, ClassNotFoundException {
1236 stream.defaultReadObject();
1237 this.positivePaint = SerialUtilities.readPaint(stream);
1238 this.negativePaint = SerialUtilities.readPaint(stream);
1239 this.legendLine = SerialUtilities.readShape(stream);
1240 }
1241 }
1242

http://dive4elements.wald.intevation.org