001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2007, 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 * StandardXYItemRenderer.java
029 * ---------------------------
030 * (C) Copyright 2001-2007, by Object Refinery Limited and Contributors.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): Mark Watson (www.markwatson.com);
034 * Jonathan Nash;
035 * Andreas Schneider;
036 * Norbert Kiesel (for TBD Networks);
037 * Christian W. Zuckschwerdt;
038 * Bill Kelemen;
039 * Nicolas Brodu (for Astrium and EADS Corporate Research
040 * Center);
041 *
042 * $Id: StandardXYItemRenderer.java,v 1.18.2.14 2007/06/08 13:29:38 mungady Exp $
043 *
044 * Changes:
045 * --------
046 * 19-Oct-2001 : Version 1, based on code by Mark Watson (DG);
047 * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
048 * 21-Dec-2001 : Added working line instance to improve performance (DG);
049 * 22-Jan-2002 : Added code to lock crosshairs to data points. Based on code
050 * by Jonathan Nash (DG);
051 * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG);
052 * 28-Mar-2002 : Added a property change listener mechanism so that the
053 * renderer no longer needs to be immutable (DG);
054 * 02-Apr-2002 : Modified to handle null values (DG);
055 * 09-Apr-2002 : Modified draw method to return void. Removed the translated
056 * zero from the drawItem method. Override the initialise()
057 * method to calculate it (DG);
058 * 13-May-2002 : Added code from Andreas Schneider to allow changing
059 * shapes/colors per item (DG);
060 * 24-May-2002 : Incorporated tooltips into chart entities (DG);
061 * 25-Jun-2002 : Removed redundant code (DG);
062 * 05-Aug-2002 : Incorporated URLs for HTML image maps into chart entities (RA);
063 * 08-Aug-2002 : Added discontinuous lines option contributed by
064 * Norbert Kiesel (DG);
065 * 20-Aug-2002 : Added user definable default values to be returned by
066 * protected methods unless overridden by a subclass (DG);
067 * 23-Sep-2002 : Updated for changes in the XYItemRenderer interface (DG);
068 * 02-Oct-2002 : Fixed errors reported by Checkstyle (DG);
069 * 25-Mar-2003 : Implemented Serializable (DG);
070 * 01-May-2003 : Modified drawItem() method signature (DG);
071 * 15-May-2003 : Modified to take into account the plot orientation (DG);
072 * 29-Jul-2003 : Amended code that doesn't compile with JDK 1.2.2 (DG);
073 * 30-Jul-2003 : Modified entity constructor (CZ);
074 * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
075 * 24-Aug-2003 : Added null/NaN checks in drawItem (BK);
076 * 08-Sep-2003 : Fixed serialization (NB);
077 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
078 * 21-Jan-2004 : Override for getLegendItem() method (DG);
079 * 27-Jan-2004 : Moved working line into state object (DG);
080 * 10-Feb-2004 : Changed drawItem() method to make cut-and-paste overriding
081 * easier (DG);
082 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState. Renamed
083 * XYToolTipGenerator --> XYItemLabelGenerator (DG);
084 * 08-Jun-2004 : Modified to use getX() and getY() methods (DG);
085 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
086 * getYValue() (DG);
087 * 25-Aug-2004 : Created addEntity() method in superclass (DG);
088 * 08-Oct-2004 : Added 'gapThresholdType' as suggested by Mike Watts (DG);
089 * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
090 * 23-Feb-2005 : Fixed getLegendItem() method to show lines. Fixed bug
091 * 1077108 (shape not visible for first item in series) (DG);
092 * 10-Apr-2005 : Fixed item label positioning with horizontal orientation (DG);
093 * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
094 * 27-Apr-2005 : Use generator for series label in legend (DG);
095 * ------------- JFREECHART 1.0.x ---------------------------------------------
096 * 15-Jun-2006 : Fixed bug (1380480) for rendering series as path (DG);
097 * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
098 * 14-Mar-2007 : Fixed problems with the equals() and clone() methods (DG);
099 * 23-Mar-2007 : Clean-up of shapesFilled attributes (DG);
100 * 20-Apr-2007 : Updated getLegendItem() and drawItem() for renderer
101 * change (DG);
102 * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem()
103 * method (DG);
104 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
105 * 08-Jun-2007 : Fixed bug in entity creation (DG);
106 *
107 */
108
109 package org.jfree.chart.renderer.xy;
110
111
112 import java.awt.Graphics2D;
113 import java.awt.Image;
114 import java.awt.Paint;
115 import java.awt.Point;
116 import java.awt.Shape;
117 import java.awt.Stroke;
118 import java.awt.geom.GeneralPath;
119 import java.awt.geom.Line2D;
120 import java.awt.geom.Rectangle2D;
121 import java.io.IOException;
122 import java.io.ObjectInputStream;
123 import java.io.ObjectOutputStream;
124 import java.io.Serializable;
125
126 import org.jfree.chart.LegendItem;
127 import org.jfree.chart.axis.ValueAxis;
128 import org.jfree.chart.entity.EntityCollection;
129 import org.jfree.chart.event.RendererChangeEvent;
130 import org.jfree.chart.labels.XYToolTipGenerator;
131 import org.jfree.chart.plot.CrosshairState;
132 import org.jfree.chart.plot.Plot;
133 import org.jfree.chart.plot.PlotOrientation;
134 import org.jfree.chart.plot.PlotRenderingInfo;
135 import org.jfree.chart.plot.XYPlot;
136 import org.jfree.chart.urls.XYURLGenerator;
137 import org.jfree.data.xy.XYDataset;
138 import org.jfree.io.SerialUtilities;
139 import org.jfree.ui.RectangleEdge;
140 import org.jfree.util.BooleanList;
141 import org.jfree.util.BooleanUtilities;
142 import org.jfree.util.ObjectUtilities;
143 import org.jfree.util.PublicCloneable;
144 import org.jfree.util.ShapeUtilities;
145 import org.jfree.util.UnitType;
146
147 /**
148 * Standard item renderer for an {@link XYPlot}. This class can draw (a)
149 * shapes at each point, or (b) lines between points, or (c) both shapes and
150 * lines.
151 * <P>
152 * This renderer has been retained for historical reasons and, in general, you
153 * should use the {@link XYLineAndShapeRenderer} class instead.
154 */
155 public class StandardXYItemRenderer extends AbstractXYItemRenderer
156 implements XYItemRenderer, Cloneable, PublicCloneable, Serializable {
157
158 /** For serialization. */
159 private static final long serialVersionUID = -3271351259436865995L;
160
161 /** Constant for the type of rendering (shapes only). */
162 public static final int SHAPES = 1;
163
164 /** Constant for the type of rendering (lines only). */
165 public static final int LINES = 2;
166
167 /** Constant for the type of rendering (shapes and lines). */
168 public static final int SHAPES_AND_LINES = SHAPES | LINES;
169
170 /** Constant for the type of rendering (images only). */
171 public static final int IMAGES = 4;
172
173 /** Constant for the type of rendering (discontinuous lines). */
174 public static final int DISCONTINUOUS = 8;
175
176 /** Constant for the type of rendering (discontinuous lines). */
177 public static final int DISCONTINUOUS_LINES = LINES | DISCONTINUOUS;
178
179 /** A flag indicating whether or not shapes are drawn at each XY point. */
180 private boolean baseShapesVisible;
181
182 /** A flag indicating whether or not lines are drawn between XY points. */
183 private boolean plotLines;
184
185 /** A flag indicating whether or not images are drawn between XY points. */
186 private boolean plotImages;
187
188 /** A flag controlling whether or not discontinuous lines are used. */
189 private boolean plotDiscontinuous;
190
191 /** Specifies how the gap threshold value is interpreted. */
192 private UnitType gapThresholdType = UnitType.RELATIVE;
193
194 /** Threshold for deciding when to discontinue a line. */
195 private double gapThreshold = 1.0;
196
197 /** A flag that controls whether or not shapes are filled for ALL series. */
198 private Boolean shapesFilled;
199
200 /**
201 * A table of flags that control (per series) whether or not shapes are
202 * filled.
203 */
204 private BooleanList seriesShapesFilled;
205
206 /** The default value returned by the getShapeFilled() method. */
207 private boolean baseShapesFilled;
208
209 /**
210 * A flag that controls whether or not each series is drawn as a single
211 * path.
212 */
213 private boolean drawSeriesLineAsPath;
214
215 /**
216 * The shape that is used to represent a line in the legend.
217 * This should never be set to <code>null</code>.
218 */
219 private transient Shape legendLine;
220
221 /**
222 * Constructs a new renderer.
223 */
224 public StandardXYItemRenderer() {
225 this(LINES, null);
226 }
227
228 /**
229 * Constructs a new renderer. To specify the type of renderer, use one of
230 * the constants: {@link #SHAPES}, {@link #LINES} or
231 * {@link #SHAPES_AND_LINES}.
232 *
233 * @param type the type.
234 */
235 public StandardXYItemRenderer(int type) {
236 this(type, null);
237 }
238
239 /**
240 * Constructs a new renderer. To specify the type of renderer, use one of
241 * the constants: {@link #SHAPES}, {@link #LINES} or
242 * {@link #SHAPES_AND_LINES}.
243 *
244 * @param type the type of renderer.
245 * @param toolTipGenerator the item label generator (<code>null</code>
246 * permitted).
247 */
248 public StandardXYItemRenderer(int type,
249 XYToolTipGenerator toolTipGenerator) {
250 this(type, toolTipGenerator, null);
251 }
252
253 /**
254 * Constructs a new renderer. To specify the type of renderer, use one of
255 * the constants: {@link #SHAPES}, {@link #LINES} or
256 * {@link #SHAPES_AND_LINES}.
257 *
258 * @param type the type of renderer.
259 * @param toolTipGenerator the item label generator (<code>null</code>
260 * permitted).
261 * @param urlGenerator the URL generator.
262 */
263 public StandardXYItemRenderer(int type,
264 XYToolTipGenerator toolTipGenerator,
265 XYURLGenerator urlGenerator) {
266
267 super();
268 setBaseToolTipGenerator(toolTipGenerator);
269 setURLGenerator(urlGenerator);
270 if ((type & SHAPES) != 0) {
271 this.baseShapesVisible = true;
272 }
273 if ((type & LINES) != 0) {
274 this.plotLines = true;
275 }
276 if ((type & IMAGES) != 0) {
277 this.plotImages = true;
278 }
279 if ((type & DISCONTINUOUS) != 0) {
280 this.plotDiscontinuous = true;
281 }
282
283 this.shapesFilled = null;
284 this.seriesShapesFilled = new BooleanList();
285 this.baseShapesFilled = true;
286 this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
287 this.drawSeriesLineAsPath = false;
288 }
289
290 /**
291 * Returns true if shapes are being plotted by the renderer.
292 *
293 * @return <code>true</code> if shapes are being plotted by the renderer.
294 *
295 * @see #setBaseShapesVisible
296 */
297 public boolean getBaseShapesVisible() {
298 return this.baseShapesVisible;
299 }
300
301 /**
302 * Sets the flag that controls whether or not a shape is plotted at each
303 * data point.
304 *
305 * @param flag the flag.
306 *
307 * @see #getBaseShapesVisible
308 */
309 public void setBaseShapesVisible(boolean flag) {
310 if (this.baseShapesVisible != flag) {
311 this.baseShapesVisible = flag;
312 notifyListeners(new RendererChangeEvent(this));
313 }
314 }
315
316 // SHAPES FILLED
317
318 /**
319 * Returns the flag used to control whether or not the shape for an item is
320 * filled.
321 * <p>
322 * The default implementation passes control to the
323 * <code>getSeriesShapesFilled</code> method. You can override this method
324 * if you require different behaviour.
325 *
326 * @param series the series index (zero-based).
327 * @param item the item index (zero-based).
328 *
329 * @return A boolean.
330 *
331 * @see #getSeriesShapesFilled(int)
332 */
333 public boolean getItemShapeFilled(int series, int item) {
334 // return the overall setting, if there is one...
335 if (this.shapesFilled != null) {
336 return this.shapesFilled.booleanValue();
337 }
338
339 // otherwise look up the paint table
340 Boolean flag = this.seriesShapesFilled.getBoolean(series);
341 if (flag != null) {
342 return flag.booleanValue();
343 }
344 else {
345 return this.baseShapesFilled;
346 }
347 }
348
349 /**
350 * Returns the override flag that controls whether or not shapes are filled
351 * for ALL series.
352 *
353 * @return The flag (possibly <code>null</code>).
354 *
355 * @since 1.0.5
356 */
357 public Boolean getShapesFilled() {
358 return this.shapesFilled;
359 }
360
361 /**
362 * Sets the 'shapes filled' for ALL series.
363 *
364 * @param filled the flag.
365 *
366 * @see #setShapesFilled(Boolean)
367 */
368 public void setShapesFilled(boolean filled) {
369 // here we use BooleanUtilities to remain compatible with JDKs < 1.4
370 setShapesFilled(BooleanUtilities.valueOf(filled));
371 }
372
373 /**
374 * Sets the override flag that controls whether or not shapes are filled
375 * for ALL series and sends a {@link RendererChangeEvent} to all registered
376 * listeners.
377 *
378 * @param filled the flag (<code>null</code> permitted).
379 *
380 * @see #setShapesFilled(boolean)
381 */
382 public void setShapesFilled(Boolean filled) {
383 this.shapesFilled = filled;
384 fireChangeEvent();
385 }
386
387 /**
388 * Returns the flag used to control whether or not the shapes for a series
389 * are filled.
390 *
391 * @param series the series index (zero-based).
392 *
393 * @return A boolean.
394 */
395 public Boolean getSeriesShapesFilled(int series) {
396 return this.seriesShapesFilled.getBoolean(series);
397 }
398
399 /**
400 * Sets the 'shapes filled' flag for a series.
401 *
402 * @param series the series index (zero-based).
403 * @param flag the flag.
404 *
405 * @see #getSeriesShapesFilled(int)
406 */
407 public void setSeriesShapesFilled(int series, Boolean flag) {
408 this.seriesShapesFilled.setBoolean(series, flag);
409 fireChangeEvent();
410 }
411
412 /**
413 * Returns the base 'shape filled' attribute.
414 *
415 * @return The base flag.
416 *
417 * @see #setBaseShapesFilled(boolean)
418 */
419 public boolean getBaseShapesFilled() {
420 return this.baseShapesFilled;
421 }
422
423 /**
424 * Sets the base 'shapes filled' flag.
425 *
426 * @param flag the flag.
427 *
428 * @see #getBaseShapesFilled()
429 */
430 public void setBaseShapesFilled(boolean flag) {
431 this.baseShapesFilled = flag;
432 }
433
434 /**
435 * Returns true if lines are being plotted by the renderer.
436 *
437 * @return <code>true</code> if lines are being plotted by the renderer.
438 *
439 * @see #setPlotLines(boolean)
440 */
441 public boolean getPlotLines() {
442 return this.plotLines;
443 }
444
445 /**
446 * Sets the flag that controls whether or not a line is plotted between
447 * each data point.
448 *
449 * @param flag the flag.
450 *
451 * @see #getPlotLines()
452 */
453 public void setPlotLines(boolean flag) {
454 if (this.plotLines != flag) {
455 this.plotLines = flag;
456 notifyListeners(new RendererChangeEvent(this));
457 }
458 }
459
460 /**
461 * Returns the gap threshold type (relative or absolute).
462 *
463 * @return The type.
464 *
465 * @see #setGapThresholdType(UnitType)
466 */
467 public UnitType getGapThresholdType() {
468 return this.gapThresholdType;
469 }
470
471 /**
472 * Sets the gap threshold type.
473 *
474 * @param thresholdType the type (<code>null</code> not permitted).
475 *
476 * @see #getGapThresholdType()
477 */
478 public void setGapThresholdType(UnitType thresholdType) {
479 if (thresholdType == null) {
480 throw new IllegalArgumentException(
481 "Null 'thresholdType' argument.");
482 }
483 this.gapThresholdType = thresholdType;
484 notifyListeners(new RendererChangeEvent(this));
485 }
486
487 /**
488 * Returns the gap threshold for discontinuous lines.
489 *
490 * @return The gap threshold.
491 *
492 * @see #setGapThreshold(double)
493 */
494 public double getGapThreshold() {
495 return this.gapThreshold;
496 }
497
498 /**
499 * Sets the gap threshold for discontinuous lines.
500 *
501 * @param t the threshold.
502 *
503 * @see #getGapThreshold()
504 */
505 public void setGapThreshold(double t) {
506 this.gapThreshold = t;
507 notifyListeners(new RendererChangeEvent(this));
508 }
509
510 /**
511 * Returns true if images are being plotted by the renderer.
512 *
513 * @return <code>true</code> if images are being plotted by the renderer.
514 *
515 * @see #setPlotImages(boolean)
516 */
517 public boolean getPlotImages() {
518 return this.plotImages;
519 }
520
521 /**
522 * Sets the flag that controls whether or not an image is drawn at each
523 * data point.
524 *
525 * @param flag the flag.
526 *
527 * @see #getPlotImages()
528 */
529 public void setPlotImages(boolean flag) {
530 if (this.plotImages != flag) {
531 this.plotImages = flag;
532 notifyListeners(new RendererChangeEvent(this));
533 }
534 }
535
536 /**
537 * Returns a flag that controls whether or not the renderer shows
538 * discontinuous lines.
539 *
540 * @return <code>true</code> if lines should be discontinuous.
541 */
542 public boolean getPlotDiscontinuous() {
543 return this.plotDiscontinuous;
544 }
545
546 /**
547 * Sets the flag that controls whether or not the renderer shows
548 * discontinuous lines, and sends a {@link RendererChangeEvent} to all
549 * registered listeners.
550 *
551 * @param flag the new flag value.
552 *
553 * @since 1.0.5
554 */
555 public void setPlotDiscontinuous(boolean flag) {
556 if (this.plotDiscontinuous != flag) {
557 this.plotDiscontinuous = flag;
558 fireChangeEvent();
559 }
560 }
561
562 /**
563 * Returns a flag that controls whether or not each series is drawn as a
564 * single path.
565 *
566 * @return A boolean.
567 *
568 * @see #setDrawSeriesLineAsPath(boolean)
569 */
570 public boolean getDrawSeriesLineAsPath() {
571 return this.drawSeriesLineAsPath;
572 }
573
574 /**
575 * Sets the flag that controls whether or not each series is drawn as a
576 * single path.
577 *
578 * @param flag the flag.
579 *
580 * @see #getDrawSeriesLineAsPath()
581 */
582 public void setDrawSeriesLineAsPath(boolean flag) {
583 this.drawSeriesLineAsPath = flag;
584 }
585
586 /**
587 * Returns the shape used to represent a line in the legend.
588 *
589 * @return The legend line (never <code>null</code>).
590 *
591 * @see #setLegendLine(Shape)
592 */
593 public Shape getLegendLine() {
594 return this.legendLine;
595 }
596
597 /**
598 * Sets the shape used as a line in each legend item and sends a
599 * {@link RendererChangeEvent} to all registered listeners.
600 *
601 * @param line the line (<code>null</code> not permitted).
602 *
603 * @see #getLegendLine()
604 */
605 public void setLegendLine(Shape line) {
606 if (line == null) {
607 throw new IllegalArgumentException("Null 'line' argument.");
608 }
609 this.legendLine = line;
610 notifyListeners(new RendererChangeEvent(this));
611 }
612
613 /**
614 * Returns a legend item for a series.
615 *
616 * @param datasetIndex the dataset index (zero-based).
617 * @param series the series index (zero-based).
618 *
619 * @return A legend item for the series.
620 */
621 public LegendItem getLegendItem(int datasetIndex, int series) {
622 XYPlot plot = getPlot();
623 if (plot == null) {
624 return null;
625 }
626 LegendItem result = null;
627 XYDataset dataset = plot.getDataset(datasetIndex);
628 if (dataset != null) {
629 if (getItemVisible(series, 0)) {
630 String label = getLegendItemLabelGenerator().generateLabel(
631 dataset, series);
632 String description = label;
633 String toolTipText = null;
634 if (getLegendItemToolTipGenerator() != null) {
635 toolTipText = getLegendItemToolTipGenerator().generateLabel(
636 dataset, series);
637 }
638 String urlText = null;
639 if (getLegendItemURLGenerator() != null) {
640 urlText = getLegendItemURLGenerator().generateLabel(
641 dataset, series);
642 }
643 Shape shape = lookupSeriesShape(series);
644 boolean shapeFilled = getItemShapeFilled(series, 0);
645 Paint paint = lookupSeriesPaint(series);
646 Paint linePaint = paint;
647 Stroke lineStroke = lookupSeriesStroke(series);
648 result = new LegendItem(label, description, toolTipText,
649 urlText, this.baseShapesVisible, shape, shapeFilled,
650 paint, !shapeFilled, paint, lineStroke,
651 this.plotLines, this.legendLine, lineStroke, linePaint);
652 result.setDataset(dataset);
653 result.setDatasetIndex(datasetIndex);
654 result.setSeriesKey(dataset.getSeriesKey(series));
655 result.setSeriesIndex(series);
656 }
657 }
658 return result;
659 }
660
661 /**
662 * Records the state for the renderer. This is used to preserve state
663 * information between calls to the drawItem() method for a single chart
664 * drawing.
665 */
666 public static class State extends XYItemRendererState {
667
668 /** The path for the current series. */
669 public GeneralPath seriesPath;
670
671 /** The series index. */
672 private int seriesIndex;
673
674 /**
675 * A flag that indicates if the last (x, y) point was 'good'
676 * (non-null).
677 */
678 private boolean lastPointGood;
679
680 /**
681 * Creates a new state instance.
682 *
683 * @param info the plot rendering info.
684 */
685 public State(PlotRenderingInfo info) {
686 super(info);
687 }
688
689 /**
690 * Returns a flag that indicates if the last point drawn (in the
691 * current series) was 'good' (non-null).
692 *
693 * @return A boolean.
694 */
695 public boolean isLastPointGood() {
696 return this.lastPointGood;
697 }
698
699 /**
700 * Sets a flag that indicates if the last point drawn (in the current
701 * series) was 'good' (non-null).
702 *
703 * @param good the flag.
704 */
705 public void setLastPointGood(boolean good) {
706 this.lastPointGood = good;
707 }
708
709 /**
710 * Returns the series index for the current path.
711 *
712 * @return The series index for the current path.
713 */
714 public int getSeriesIndex() {
715 return this.seriesIndex;
716 }
717
718 /**
719 * Sets the series index for the current path.
720 *
721 * @param index the index.
722 */
723 public void setSeriesIndex(int index) {
724 this.seriesIndex = index;
725 }
726 }
727
728 /**
729 * Initialises the renderer.
730 * <P>
731 * This method will be called before the first item is rendered, giving the
732 * renderer an opportunity to initialise any state information it wants to
733 * maintain. The renderer can do nothing if it chooses.
734 *
735 * @param g2 the graphics device.
736 * @param dataArea the area inside the axes.
737 * @param plot the plot.
738 * @param data the data.
739 * @param info an optional info collection object to return data back to
740 * the caller.
741 *
742 * @return The renderer state.
743 */
744 public XYItemRendererState initialise(Graphics2D g2,
745 Rectangle2D dataArea,
746 XYPlot plot,
747 XYDataset data,
748 PlotRenderingInfo info) {
749
750 State state = new State(info);
751 state.seriesPath = new GeneralPath();
752 state.seriesIndex = -1;
753 return state;
754
755 }
756
757 /**
758 * Draws the visual representation of a single data item.
759 *
760 * @param g2 the graphics device.
761 * @param state the renderer state.
762 * @param dataArea the area within which the data is being drawn.
763 * @param info collects information about the drawing.
764 * @param plot the plot (can be used to obtain standard color information
765 * etc).
766 * @param domainAxis the domain axis.
767 * @param rangeAxis the range axis.
768 * @param dataset the dataset.
769 * @param series the series index (zero-based).
770 * @param item the item index (zero-based).
771 * @param crosshairState crosshair information for the plot
772 * (<code>null</code> permitted).
773 * @param pass the pass index.
774 */
775 public void drawItem(Graphics2D g2,
776 XYItemRendererState state,
777 Rectangle2D dataArea,
778 PlotRenderingInfo info,
779 XYPlot plot,
780 ValueAxis domainAxis,
781 ValueAxis rangeAxis,
782 XYDataset dataset,
783 int series,
784 int item,
785 CrosshairState crosshairState,
786 int pass) {
787
788 boolean itemVisible = getItemVisible(series, item);
789
790 // setup for collecting optional entity info...
791 Shape entityArea = null;
792 EntityCollection entities = null;
793 if (info != null) {
794 entities = info.getOwner().getEntityCollection();
795 }
796
797 PlotOrientation orientation = plot.getOrientation();
798 Paint paint = getItemPaint(series, item);
799 Stroke seriesStroke = getItemStroke(series, item);
800 g2.setPaint(paint);
801 g2.setStroke(seriesStroke);
802
803 // get the data point...
804 double x1 = dataset.getXValue(series, item);
805 double y1 = dataset.getYValue(series, item);
806 if (Double.isNaN(x1) || Double.isNaN(y1)) {
807 itemVisible = false;
808 }
809
810 RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
811 RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
812 double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
813 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
814
815 if (getPlotLines()) {
816 if (this.drawSeriesLineAsPath) {
817 State s = (State) state;
818 if (s.getSeriesIndex() != series) {
819 // we are starting a new series path
820 s.seriesPath.reset();
821 s.lastPointGood = false;
822 s.setSeriesIndex(series);
823 }
824
825 // update path to reflect latest point
826 if (itemVisible && !Double.isNaN(transX1)
827 && !Double.isNaN(transY1)) {
828 float x = (float) transX1;
829 float y = (float) transY1;
830 if (orientation == PlotOrientation.HORIZONTAL) {
831 x = (float) transY1;
832 y = (float) transX1;
833 }
834 if (s.isLastPointGood()) {
835 // TODO: check threshold
836 s.seriesPath.lineTo(x, y);
837 }
838 else {
839 s.seriesPath.moveTo(x, y);
840 }
841 s.setLastPointGood(true);
842 }
843 else {
844 s.setLastPointGood(false);
845 }
846 if (item == dataset.getItemCount(series) - 1) {
847 if (s.seriesIndex == series) {
848 // draw path
849 g2.setStroke(lookupSeriesStroke(series));
850 g2.setPaint(lookupSeriesPaint(series));
851 g2.draw(s.seriesPath);
852 }
853 }
854 }
855
856 else if (item != 0 && itemVisible) {
857 // get the previous data point...
858 double x0 = dataset.getXValue(series, item - 1);
859 double y0 = dataset.getYValue(series, item - 1);
860 if (!Double.isNaN(x0) && !Double.isNaN(y0)) {
861 boolean drawLine = true;
862 if (getPlotDiscontinuous()) {
863 // only draw a line if the gap between the current and
864 // previous data point is within the threshold
865 int numX = dataset.getItemCount(series);
866 double minX = dataset.getXValue(series, 0);
867 double maxX = dataset.getXValue(series, numX - 1);
868 if (this.gapThresholdType == UnitType.ABSOLUTE) {
869 drawLine = Math.abs(x1 - x0) <= this.gapThreshold;
870 }
871 else {
872 drawLine = Math.abs(x1 - x0) <= ((maxX - minX)
873 / numX * getGapThreshold());
874 }
875 }
876 if (drawLine) {
877 double transX0 = domainAxis.valueToJava2D(x0, dataArea,
878 xAxisLocation);
879 double transY0 = rangeAxis.valueToJava2D(y0, dataArea,
880 yAxisLocation);
881
882 // only draw if we have good values
883 if (Double.isNaN(transX0) || Double.isNaN(transY0)
884 || Double.isNaN(transX1) || Double.isNaN(transY1)) {
885 return;
886 }
887
888 if (orientation == PlotOrientation.HORIZONTAL) {
889 state.workingLine.setLine(transY0, transX0,
890 transY1, transX1);
891 }
892 else if (orientation == PlotOrientation.VERTICAL) {
893 state.workingLine.setLine(transX0, transY0,
894 transX1, transY1);
895 }
896
897 if (state.workingLine.intersects(dataArea)) {
898 g2.draw(state.workingLine);
899 }
900 }
901 }
902 }
903 }
904
905 // we needed to get this far even for invisible items, to ensure that
906 // seriesPath updates happened, but now there is nothing more we need
907 // to do for non-visible items...
908 if (!itemVisible) {
909 return;
910 }
911
912 if (getBaseShapesVisible()) {
913
914 Shape shape = getItemShape(series, item);
915 if (orientation == PlotOrientation.HORIZONTAL) {
916 shape = ShapeUtilities.createTranslatedShape(shape, transY1,
917 transX1);
918 }
919 else if (orientation == PlotOrientation.VERTICAL) {
920 shape = ShapeUtilities.createTranslatedShape(shape, transX1,
921 transY1);
922 }
923 if (shape.intersects(dataArea)) {
924 if (getItemShapeFilled(series, item)) {
925 g2.fill(shape);
926 }
927 else {
928 g2.draw(shape);
929 }
930 }
931 entityArea = shape;
932
933 }
934
935 if (getPlotImages()) {
936 Image image = getImage(plot, series, item, transX1, transY1);
937 if (image != null) {
938 Point hotspot = getImageHotspot(plot, series, item, transX1,
939 transY1, image);
940 g2.drawImage(image, (int) (transX1 - hotspot.getX()),
941 (int) (transY1 - hotspot.getY()), null);
942 entityArea = new Rectangle2D.Double(transX1 - hotspot.getX(),
943 transY1 - hotspot.getY(), image.getWidth(null),
944 image.getHeight(null));
945 }
946
947 }
948
949 double xx = transX1;
950 double yy = transY1;
951 if (orientation == PlotOrientation.HORIZONTAL) {
952 xx = transY1;
953 yy = transX1;
954 }
955
956 // draw the item label if there is one...
957 if (isItemLabelVisible(series, item)) {
958 drawItemLabel(g2, orientation, dataset, series, item, xx, yy,
959 (y1 < 0.0));
960 }
961
962 int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
963 int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
964 updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
965 rangeAxisIndex, transX1, transY1, orientation);
966
967 // add an entity for the item...
968 if (entities != null && dataArea.contains(xx, yy)) {
969 addEntity(entities, entityArea, dataset, series, item, xx, yy);
970 }
971
972 }
973
974 /**
975 * Tests this renderer for equality with another object.
976 *
977 * @param obj the object (<code>null</code> permitted).
978 *
979 * @return A boolean.
980 */
981 public boolean equals(Object obj) {
982
983 if (obj == this) {
984 return true;
985 }
986 if (!(obj instanceof StandardXYItemRenderer)) {
987 return false;
988 }
989 StandardXYItemRenderer that = (StandardXYItemRenderer) obj;
990 if (this.baseShapesVisible != that.baseShapesVisible) {
991 return false;
992 }
993 if (this.plotLines != that.plotLines) {
994 return false;
995 }
996 if (this.plotImages != that.plotImages) {
997 return false;
998 }
999 if (this.plotDiscontinuous != that.plotDiscontinuous) {
1000 return false;
1001 }
1002 if (this.gapThresholdType != that.gapThresholdType) {
1003 return false;
1004 }
1005 if (this.gapThreshold != that.gapThreshold) {
1006 return false;
1007 }
1008 if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) {
1009 return false;
1010 }
1011 if (!this.seriesShapesFilled.equals(that.seriesShapesFilled)) {
1012 return false;
1013 }
1014 if (this.baseShapesFilled != that.baseShapesFilled) {
1015 return false;
1016 }
1017 if (this.drawSeriesLineAsPath != that.drawSeriesLineAsPath) {
1018 return false;
1019 }
1020 if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) {
1021 return false;
1022 }
1023 return super.equals(obj);
1024
1025 }
1026
1027 /**
1028 * Returns a clone of the renderer.
1029 *
1030 * @return A clone.
1031 *
1032 * @throws CloneNotSupportedException if the renderer cannot be cloned.
1033 */
1034 public Object clone() throws CloneNotSupportedException {
1035 StandardXYItemRenderer clone = (StandardXYItemRenderer) super.clone();
1036 clone.seriesShapesFilled
1037 = (BooleanList) this.seriesShapesFilled.clone();
1038 clone.legendLine = ShapeUtilities.clone(this.legendLine);
1039 return clone;
1040 }
1041
1042 ////////////////////////////////////////////////////////////////////////////
1043 // PROTECTED METHODS
1044 // These provide the opportunity to subclass the standard renderer and
1045 // create custom effects.
1046 ////////////////////////////////////////////////////////////////////////////
1047
1048 /**
1049 * Returns the image used to draw a single data item.
1050 *
1051 * @param plot the plot (can be used to obtain standard color information
1052 * etc).
1053 * @param series the series index.
1054 * @param item the item index.
1055 * @param x the x value of the item.
1056 * @param y the y value of the item.
1057 *
1058 * @return The image.
1059 *
1060 * @see #getPlotImages()
1061 */
1062 protected Image getImage(Plot plot, int series, int item,
1063 double x, double y) {
1064 // this method must be overridden if you want to display images
1065 return null;
1066 }
1067
1068 /**
1069 * Returns the hotspot of the image used to draw a single data item.
1070 * The hotspot is the point relative to the top left of the image
1071 * that should indicate the data item. The default is the center of the
1072 * image.
1073 *
1074 * @param plot the plot (can be used to obtain standard color information
1075 * etc).
1076 * @param image the image (can be used to get size information about the
1077 * image)
1078 * @param series the series index
1079 * @param item the item index
1080 * @param x the x value of the item
1081 * @param y the y value of the item
1082 *
1083 * @return The hotspot used to draw the data item.
1084 */
1085 protected Point getImageHotspot(Plot plot, int series, int item,
1086 double x, double y, Image image) {
1087
1088 int height = image.getHeight(null);
1089 int width = image.getWidth(null);
1090 return new Point(width / 2, height / 2);
1091
1092 }
1093
1094 /**
1095 * Provides serialization support.
1096 *
1097 * @param stream the input stream.
1098 *
1099 * @throws IOException if there is an I/O error.
1100 * @throws ClassNotFoundException if there is a classpath problem.
1101 */
1102 private void readObject(ObjectInputStream stream)
1103 throws IOException, ClassNotFoundException {
1104 stream.defaultReadObject();
1105 this.legendLine = SerialUtilities.readShape(stream);
1106 }
1107
1108 /**
1109 * Provides serialization support.
1110 *
1111 * @param stream the output stream.
1112 *
1113 * @throws IOException if there is an I/O error.
1114 */
1115 private void writeObject(ObjectOutputStream stream) throws IOException {
1116 stream.defaultWriteObject();
1117 SerialUtilities.writeShape(this.legendLine, stream);
1118 }
1119
1120 }