Mercurial > dive4elements > river
comparison flys-artifacts/src/main/java/de/intevation/flys/jfree/StableXYDifferenceRenderer.java @ 3938:c0cab28ba1ea
merged flys-artifacts
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Fri, 28 Sep 2012 12:15:03 +0200 |
parents | c0a15547ad76 |
children | 22cd60315e08 |
comparison
equal
deleted
inserted
replaced
3865:436eec3be6ff | 3938:c0cab28ba1ea |
---|---|
1 /* =========================================================== | |
2 * JFreeChart : a free chart library for the Java(tm) platform | |
3 * =========================================================== | |
4 * | |
5 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors. | |
6 * | |
7 * Project Info: http://www.jfree.org/jfreechart/index.html | |
8 * | |
9 * This library is free software; you can redistribute it and/or modify it | |
10 * under the terms of the GNU Lesser General Public License as published by | |
11 * the Free Software Foundation; either version 2.1 of the License, or | |
12 * (at your option) any later version. | |
13 * | |
14 * This library is distributed in the hope that it will be useful, but | |
15 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | |
16 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public | |
17 * License for more details. | |
18 * | |
19 * You should have received a copy of the GNU Lesser General Public | |
20 * License along with this library; if not, write to the Free Software | |
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, | |
22 * USA. | |
23 * | |
24 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. | |
25 * in the United States and other countries.] | |
26 * | |
27 * ------------------------- | |
28 * StableXYDifferenceRenderer.java | |
29 * ------------------------- | |
30 * (C) Copyright 2003-2008, by Object Refinery Limited and Contributors. | |
31 * | |
32 * Original Author: David Gilbert (for Object Refinery Limited); | |
33 * Contributor(s): Richard West, Advanced Micro Devices, Inc. (major rewrite | |
34 * of difference drawing algorithm); | |
35 * | |
36 * Changes: | |
37 * -------- | |
38 * 30-Apr-2003 : Version 1 (DG); | |
39 * 30-Jul-2003 : Modified entity constructor (CZ); | |
40 * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG); | |
41 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); | |
42 * 09-Feb-2004 : Updated to support horizontal plot orientation (DG); | |
43 * 10-Feb-2004 : Added default constructor, setter methods and updated | |
44 * Javadocs (DG); | |
45 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG); | |
46 * 30-Mar-2004 : Fixed bug in getNegativePaint() method (DG); | |
47 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with | |
48 * getYValue() (DG); | |
49 * 25-Aug-2004 : Fixed a bug preventing the use of crosshairs (DG); | |
50 * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG); | |
51 * 19-Jan-2005 : Now accesses only primitive values from dataset (DG); | |
52 * 22-Feb-2005 : Override getLegendItem(int, int) to return "line" items (DG); | |
53 * 13-Apr-2005 : Fixed shape positioning bug (id = 1182062) (DG); | |
54 * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG); | |
55 * 04-May-2005 : Override equals() method, renamed get/setPlotShapes() --> | |
56 * get/setShapesVisible (DG); | |
57 * 09-Jun-2005 : Updated equals() to handle GradientPaint (DG); | |
58 * 16-Jun-2005 : Fix bug (1221021) affecting stroke used for each series (DG); | |
59 * ------------- JFREECHART 1.0.x --------------------------------------------- | |
60 * 24-Jan-2007 : Added flag to allow rounding of x-coordinates, and fixed | |
61 * bug in clone() (DG); | |
62 * 05-Feb-2007 : Added an extra call to updateCrosshairValues() in | |
63 * drawItemPass1(), to fix bug 1564967 (DG); | |
64 * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG); | |
65 * 08-Mar-2007 : Fixed entity generation (DG); | |
66 * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG); | |
67 * 23-Apr-2007 : Rewrite of difference drawing algorithm to allow use of | |
68 * series with disjoint x-values (RW); | |
69 * 04-May-2007 : Set processVisibleItemsOnly flag to false (DG); | |
70 * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG); | |
71 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG); | |
72 * 05-Nov-2007 : Draw item labels if visible (RW); | |
73 * 17-Jun-2008 : Apply legend shape, font and paint attributes (DG); | |
74 */ | |
75 /* | |
76 * For further changes within the FLYS project, refer to the ChangeLog. | |
77 */ | |
78 package de.intevation.flys.jfree; | |
79 | |
80 import java.awt.BasicStroke; | |
81 import java.awt.Color; | |
82 import java.awt.Graphics2D; | |
83 import java.awt.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 de.intevation.flys.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 j = (i + 1) % N; | |
1591 double xi = ((Double)xValues[i]).doubleValue(); | |
1592 double yi = ((Double)yValues[i]).doubleValue(); | |
1593 double xj = ((Double)xValues[j]).doubleValue(); | |
1594 double yj = ((Double)yValues[j]).doubleValue(); | |
1595 | |
1596 area += xi*yj; | |
1597 area -= xj*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 : |