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