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