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 * XYLineAndShapeRenderer.java
029 * ---------------------------
030 * (C) Copyright 2004-2007, by Object Refinery Limited.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): -;
034 *
035 * $Id: XYLineAndShapeRenderer.java,v 1.20.2.13 2007/06/08 13:29:38 mungady Exp $
036 *
037 * Changes:
038 * --------
039 * 27-Jan-2004 : Version 1 (DG);
040 * 10-Feb-2004 : Minor change to drawItem() method to make cut-and-paste
041 * overriding easier (DG);
042 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
043 * 25-Aug-2004 : Added support for chart entities (required for tooltips) (DG);
044 * 24-Sep-2004 : Added flag to allow whole series to be drawn as a path
045 * (necessary when using a dashed stroke with many data
046 * items) (DG);
047 * 04-Oct-2004 : Renamed BooleanUtils --> BooleanUtilities (DG);
048 * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
049 * 27-Jan-2005 : The getLegendItem() method now omits hidden series (DG);
050 * 28-Jan-2005 : Added new constructor (DG);
051 * 09-Mar-2005 : Added fillPaint settings (DG);
052 * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
053 * 22-Jul-2005 : Renamed defaultLinesVisible --> baseLinesVisible,
054 * defaultShapesVisible --> baseShapesVisible and
055 * defaultShapesFilled --> baseShapesFilled (DG);
056 * 29-Jul-2005 : Added code to draw item labels (DG);
057 * ------------- JFREECHART 1.0.x ---------------------------------------------
058 * 20-Jul-2006 : Set dataset and series indices in LegendItem (DG);
059 * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
060 * 21-Feb-2007 : Fixed bugs in clone() and equals() (DG);
061 * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
062 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
063 * 08-Jun-2007 : Fix for bug 1731912 where entities are created even for data
064 * items that are not displayed (DG);
065 *
066 */
067
068 package org.jfree.chart.renderer.xy;
069
070 import java.awt.Graphics2D;
071 import java.awt.Paint;
072 import java.awt.Shape;
073 import java.awt.Stroke;
074 import java.awt.geom.GeneralPath;
075 import java.awt.geom.Line2D;
076 import java.awt.geom.Rectangle2D;
077 import java.io.IOException;
078 import java.io.ObjectInputStream;
079 import java.io.ObjectOutputStream;
080 import java.io.Serializable;
081
082 import org.jfree.chart.LegendItem;
083 import org.jfree.chart.axis.ValueAxis;
084 import org.jfree.chart.entity.EntityCollection;
085 import org.jfree.chart.event.RendererChangeEvent;
086 import org.jfree.chart.plot.CrosshairState;
087 import org.jfree.chart.plot.PlotOrientation;
088 import org.jfree.chart.plot.PlotRenderingInfo;
089 import org.jfree.chart.plot.XYPlot;
090 import org.jfree.data.xy.XYDataset;
091 import org.jfree.io.SerialUtilities;
092 import org.jfree.ui.RectangleEdge;
093 import org.jfree.util.BooleanList;
094 import org.jfree.util.BooleanUtilities;
095 import org.jfree.util.ObjectUtilities;
096 import org.jfree.util.PublicCloneable;
097 import org.jfree.util.ShapeUtilities;
098
099 /**
100 * A renderer that connects data points with lines and/or draws shapes at each
101 * data point. This renderer is designed for use with the {@link XYPlot}
102 * class.
103 */
104 public class XYLineAndShapeRenderer extends AbstractXYItemRenderer
105 implements XYItemRenderer,
106 Cloneable,
107 PublicCloneable,
108 Serializable {
109
110 /** For serialization. */
111 private static final long serialVersionUID = -7435246895986425885L;
112
113 /** A flag that controls whether or not lines are visible for ALL series. */
114 private Boolean linesVisible;
115
116 /**
117 * A table of flags that control (per series) whether or not lines are
118 * visible.
119 */
120 private BooleanList seriesLinesVisible;
121
122 /** The default value returned by the getLinesVisible() method. */
123 private boolean baseLinesVisible;
124
125 /** The shape that is used to represent a line in the legend. */
126 private transient Shape legendLine;
127
128 /**
129 * A flag that controls whether or not shapes are visible for ALL series.
130 */
131 private Boolean shapesVisible;
132
133 /**
134 * A table of flags that control (per series) whether or not shapes are
135 * visible.
136 */
137 private BooleanList seriesShapesVisible;
138
139 /** The default value returned by the getShapeVisible() method. */
140 private boolean baseShapesVisible;
141
142 /** A flag that controls whether or not shapes are filled for ALL series. */
143 private Boolean shapesFilled;
144
145 /**
146 * A table of flags that control (per series) whether or not shapes are
147 * filled.
148 */
149 private BooleanList seriesShapesFilled;
150
151 /** The default value returned by the getShapeFilled() method. */
152 private boolean baseShapesFilled;
153
154 /** A flag that controls whether outlines are drawn for shapes. */
155 private boolean drawOutlines;
156
157 /**
158 * A flag that controls whether the fill paint is used for filling
159 * shapes.
160 */
161 private boolean useFillPaint;
162
163 /**
164 * A flag that controls whether the outline paint is used for drawing shape
165 * outlines.
166 */
167 private boolean useOutlinePaint;
168
169 /**
170 * A flag that controls whether or not each series is drawn as a single
171 * path.
172 */
173 private boolean drawSeriesLineAsPath;
174
175 /**
176 * Creates a new renderer with both lines and shapes visible.
177 */
178 public XYLineAndShapeRenderer() {
179 this(true, true);
180 }
181
182 /**
183 * Creates a new renderer.
184 *
185 * @param lines lines visible?
186 * @param shapes shapes visible?
187 */
188 public XYLineAndShapeRenderer(boolean lines, boolean shapes) {
189 this.linesVisible = null;
190 this.seriesLinesVisible = new BooleanList();
191 this.baseLinesVisible = lines;
192 this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
193
194 this.shapesVisible = null;
195 this.seriesShapesVisible = new BooleanList();
196 this.baseShapesVisible = shapes;
197
198 this.shapesFilled = null;
199 this.useFillPaint = false; // use item paint for fills by default
200 this.seriesShapesFilled = new BooleanList();
201 this.baseShapesFilled = true;
202
203 this.drawOutlines = true;
204 this.useOutlinePaint = false; // use item paint for outlines by
205 // default, not outline paint
206
207 this.drawSeriesLineAsPath = false;
208 }
209
210 /**
211 * Returns a flag that controls whether or not each series is drawn as a
212 * single path.
213 *
214 * @return A boolean.
215 *
216 * @see #setDrawSeriesLineAsPath(boolean)
217 */
218 public boolean getDrawSeriesLineAsPath() {
219 return this.drawSeriesLineAsPath;
220 }
221
222 /**
223 * Sets the flag that controls whether or not each series is drawn as a
224 * single path.
225 *
226 * @param flag the flag.
227 *
228 * @see #getDrawSeriesLineAsPath()
229 */
230 public void setDrawSeriesLineAsPath(boolean flag) {
231 if (this.drawSeriesLineAsPath != flag) {
232 this.drawSeriesLineAsPath = flag;
233 notifyListeners(new RendererChangeEvent(this));
234 }
235 }
236
237 /**
238 * Returns the number of passes through the data that the renderer requires
239 * in order to draw the chart. Most charts will require a single pass, but
240 * some require two passes.
241 *
242 * @return The pass count.
243 */
244 public int getPassCount() {
245 return 2;
246 }
247
248 // LINES VISIBLE
249
250 /**
251 * Returns the flag used to control whether or not the shape for an item is
252 * visible.
253 *
254 * @param series the series index (zero-based).
255 * @param item the item index (zero-based).
256 *
257 * @return A boolean.
258 */
259 public boolean getItemLineVisible(int series, int item) {
260 Boolean flag = this.linesVisible;
261 if (flag == null) {
262 flag = getSeriesLinesVisible(series);
263 }
264 if (flag != null) {
265 return flag.booleanValue();
266 }
267 else {
268 return this.baseLinesVisible;
269 }
270 }
271
272 /**
273 * Returns a flag that controls whether or not lines are drawn for ALL
274 * series. If this flag is <code>null</code>, then the "per series"
275 * settings will apply.
276 *
277 * @return A flag (possibly <code>null</code>).
278 *
279 * @see #setLinesVisible(Boolean)
280 */
281 public Boolean getLinesVisible() {
282 return this.linesVisible;
283 }
284
285 /**
286 * Sets a flag that controls whether or not lines are drawn between the
287 * items in ALL series, and sends a {@link RendererChangeEvent} to all
288 * registered listeners. You need to set this to <code>null</code> if you
289 * want the "per series" settings to apply.
290 *
291 * @param visible the flag (<code>null</code> permitted).
292 *
293 * @see #getLinesVisible()
294 */
295 public void setLinesVisible(Boolean visible) {
296 this.linesVisible = visible;
297 notifyListeners(new RendererChangeEvent(this));
298 }
299
300 /**
301 * Sets a flag that controls whether or not lines are drawn between the
302 * items in ALL series, and sends a {@link RendererChangeEvent} to all
303 * registered listeners.
304 *
305 * @param visible the flag.
306 *
307 * @see #getLinesVisible()
308 */
309 public void setLinesVisible(boolean visible) {
310 // we use BooleanUtilities here to preserve JRE 1.3.1 compatibility
311 setLinesVisible(BooleanUtilities.valueOf(visible));
312 }
313
314 /**
315 * Returns the flag used to control whether or not the lines for a series
316 * are visible.
317 *
318 * @param series the series index (zero-based).
319 *
320 * @return The flag (possibly <code>null</code>).
321 *
322 * @see #setSeriesLinesVisible(int, Boolean)
323 */
324 public Boolean getSeriesLinesVisible(int series) {
325 return this.seriesLinesVisible.getBoolean(series);
326 }
327
328 /**
329 * Sets the 'lines visible' flag for a series and sends a
330 * {@link RendererChangeEvent} to all registered listeners.
331 *
332 * @param series the series index (zero-based).
333 * @param flag the flag (<code>null</code> permitted).
334 *
335 * @see #getSeriesLinesVisible(int)
336 */
337 public void setSeriesLinesVisible(int series, Boolean flag) {
338 this.seriesLinesVisible.setBoolean(series, flag);
339 notifyListeners(new RendererChangeEvent(this));
340 }
341
342 /**
343 * Sets the 'lines visible' flag for a series and sends a
344 * {@link RendererChangeEvent} to all registered listeners.
345 *
346 * @param series the series index (zero-based).
347 * @param visible the flag.
348 *
349 * @see #getSeriesLinesVisible(int)
350 */
351 public void setSeriesLinesVisible(int series, boolean visible) {
352 setSeriesLinesVisible(series, BooleanUtilities.valueOf(visible));
353 }
354
355 /**
356 * Returns the base 'lines visible' attribute.
357 *
358 * @return The base flag.
359 *
360 * @see #setBaseLinesVisible(boolean)
361 */
362 public boolean getBaseLinesVisible() {
363 return this.baseLinesVisible;
364 }
365
366 /**
367 * Sets the base 'lines visible' flag and sends a
368 * {@link RendererChangeEvent} to all registered listeners.
369 *
370 * @param flag the flag.
371 *
372 * @see #getBaseLinesVisible()
373 */
374 public void setBaseLinesVisible(boolean flag) {
375 this.baseLinesVisible = flag;
376 notifyListeners(new RendererChangeEvent(this));
377 }
378
379 /**
380 * Returns the shape used to represent a line in the legend.
381 *
382 * @return The legend line (never <code>null</code>).
383 *
384 * @see #setLegendLine(Shape)
385 */
386 public Shape getLegendLine() {
387 return this.legendLine;
388 }
389
390 /**
391 * Sets the shape used as a line in each legend item and sends a
392 * {@link RendererChangeEvent} to all registered listeners.
393 *
394 * @param line the line (<code>null</code> not permitted).
395 *
396 * @see #getLegendLine()
397 */
398 public void setLegendLine(Shape line) {
399 if (line == null) {
400 throw new IllegalArgumentException("Null 'line' argument.");
401 }
402 this.legendLine = line;
403 notifyListeners(new RendererChangeEvent(this));
404 }
405
406 // SHAPES VISIBLE
407
408 /**
409 * Returns the flag used to control whether or not the shape for an item is
410 * visible.
411 * <p>
412 * The default implementation passes control to the
413 * <code>getSeriesShapesVisible</code> method. You can override this method
414 * if you require different behaviour.
415 *
416 * @param series the series index (zero-based).
417 * @param item the item index (zero-based).
418 *
419 * @return A boolean.
420 */
421 public boolean getItemShapeVisible(int series, int item) {
422 Boolean flag = this.shapesVisible;
423 if (flag == null) {
424 flag = getSeriesShapesVisible(series);
425 }
426 if (flag != null) {
427 return flag.booleanValue();
428 }
429 else {
430 return this.baseShapesVisible;
431 }
432 }
433
434 /**
435 * Returns the flag that controls whether the shapes are visible for the
436 * items in ALL series.
437 *
438 * @return The flag (possibly <code>null</code>).
439 *
440 * @see #setShapesVisible(Boolean)
441 */
442 public Boolean getShapesVisible() {
443 return this.shapesVisible;
444 }
445
446 /**
447 * Sets the 'shapes visible' for ALL series and sends a
448 * {@link RendererChangeEvent} to all registered listeners.
449 *
450 * @param visible the flag (<code>null</code> permitted).
451 *
452 * @see #getShapesVisible()
453 */
454 public void setShapesVisible(Boolean visible) {
455 this.shapesVisible = visible;
456 notifyListeners(new RendererChangeEvent(this));
457 }
458
459 /**
460 * Sets the 'shapes visible' for ALL series and sends a
461 * {@link RendererChangeEvent} to all registered listeners.
462 *
463 * @param visible the flag.
464 *
465 * @see #getShapesVisible()
466 */
467 public void setShapesVisible(boolean visible) {
468 setShapesVisible(BooleanUtilities.valueOf(visible));
469 }
470
471 /**
472 * Returns the flag used to control whether or not the shapes for a series
473 * are visible.
474 *
475 * @param series the series index (zero-based).
476 *
477 * @return A boolean.
478 *
479 * @see #setSeriesShapesVisible(int, Boolean)
480 */
481 public Boolean getSeriesShapesVisible(int series) {
482 return this.seriesShapesVisible.getBoolean(series);
483 }
484
485 /**
486 * Sets the 'shapes visible' flag for a series and sends a
487 * {@link RendererChangeEvent} to all registered listeners.
488 *
489 * @param series the series index (zero-based).
490 * @param visible the flag.
491 *
492 * @see #getSeriesShapesVisible(int)
493 */
494 public void setSeriesShapesVisible(int series, boolean visible) {
495 setSeriesShapesVisible(series, BooleanUtilities.valueOf(visible));
496 }
497
498 /**
499 * Sets the 'shapes visible' flag for a series and sends a
500 * {@link RendererChangeEvent} to all registered listeners.
501 *
502 * @param series the series index (zero-based).
503 * @param flag the flag.
504 *
505 * @see #getSeriesShapesVisible(int)
506 */
507 public void setSeriesShapesVisible(int series, Boolean flag) {
508 this.seriesShapesVisible.setBoolean(series, flag);
509 notifyListeners(new RendererChangeEvent(this));
510 }
511
512 /**
513 * Returns the base 'shape visible' attribute.
514 *
515 * @return The base flag.
516 *
517 * @see #setBaseShapesVisible(boolean)
518 */
519 public boolean getBaseShapesVisible() {
520 return this.baseShapesVisible;
521 }
522
523 /**
524 * Sets the base 'shapes visible' flag and sends a
525 * {@link RendererChangeEvent} to all registered listeners.
526 *
527 * @param flag the flag.
528 *
529 * @see #getBaseShapesVisible()
530 */
531 public void setBaseShapesVisible(boolean flag) {
532 this.baseShapesVisible = flag;
533 notifyListeners(new RendererChangeEvent(this));
534 }
535
536 // SHAPES FILLED
537
538 /**
539 * Returns the flag used to control whether or not the shape for an item
540 * is filled.
541 * <p>
542 * The default implementation passes control to the
543 * <code>getSeriesShapesFilled</code> method. You can override this method
544 * if you require different behaviour.
545 *
546 * @param series the series index (zero-based).
547 * @param item the item index (zero-based).
548 *
549 * @return A boolean.
550 */
551 public boolean getItemShapeFilled(int series, int item) {
552 Boolean flag = this.shapesFilled;
553 if (flag == null) {
554 flag = getSeriesShapesFilled(series);
555 }
556 if (flag != null) {
557 return flag.booleanValue();
558 }
559 else {
560 return this.baseShapesFilled;
561 }
562 }
563
564 // FIXME: Why no getShapesFilled()? An oversight probably
565
566 /**
567 * Sets the 'shapes filled' for ALL series and sends a
568 * {@link RendererChangeEvent} to all registered listeners.
569 *
570 * @param filled the flag.
571 */
572 public void setShapesFilled(boolean filled) {
573 setShapesFilled(BooleanUtilities.valueOf(filled));
574 }
575
576 /**
577 * Sets the 'shapes filled' for ALL series and sends a
578 * {@link RendererChangeEvent} to all registered listeners.
579 *
580 * @param filled the flag (<code>null</code> permitted).
581 */
582 public void setShapesFilled(Boolean filled) {
583 this.shapesFilled = filled;
584 notifyListeners(new RendererChangeEvent(this));
585 }
586
587 /**
588 * Returns the flag used to control whether or not the shapes for a series
589 * are filled.
590 *
591 * @param series the series index (zero-based).
592 *
593 * @return A boolean.
594 *
595 * @see #setSeriesShapesFilled(int, Boolean)
596 */
597 public Boolean getSeriesShapesFilled(int series) {
598 return this.seriesShapesFilled.getBoolean(series);
599 }
600
601 /**
602 * Sets the 'shapes filled' flag for a series and sends a
603 * {@link RendererChangeEvent} to all registered listeners.
604 *
605 * @param series the series index (zero-based).
606 * @param flag the flag.
607 *
608 * @see #getSeriesShapesFilled(int)
609 */
610 public void setSeriesShapesFilled(int series, boolean flag) {
611 setSeriesShapesFilled(series, BooleanUtilities.valueOf(flag));
612 }
613
614 /**
615 * Sets the 'shapes filled' flag for a series and sends a
616 * {@link RendererChangeEvent} to all registered listeners.
617 *
618 * @param series the series index (zero-based).
619 * @param flag the flag.
620 *
621 * @see #getSeriesShapesFilled(int)
622 */
623 public void setSeriesShapesFilled(int series, Boolean flag) {
624 this.seriesShapesFilled.setBoolean(series, flag);
625 notifyListeners(new RendererChangeEvent(this));
626 }
627
628 /**
629 * Returns the base 'shape filled' attribute.
630 *
631 * @return The base flag.
632 *
633 * @see #setBaseShapesFilled(boolean)
634 */
635 public boolean getBaseShapesFilled() {
636 return this.baseShapesFilled;
637 }
638
639 /**
640 * Sets the base 'shapes filled' flag and sends a
641 * {@link RendererChangeEvent} to all registered listeners.
642 *
643 * @param flag the flag.
644 *
645 * @see #getBaseShapesFilled()
646 */
647 public void setBaseShapesFilled(boolean flag) {
648 this.baseShapesFilled = flag;
649 notifyListeners(new RendererChangeEvent(this));
650 }
651
652 /**
653 * Returns <code>true</code> if outlines should be drawn for shapes, and
654 * <code>false</code> otherwise.
655 *
656 * @return A boolean.
657 *
658 * @see #setDrawOutlines(boolean)
659 */
660 public boolean getDrawOutlines() {
661 return this.drawOutlines;
662 }
663
664 /**
665 * Sets the flag that controls whether outlines are drawn for
666 * shapes, and sends a {@link RendererChangeEvent} to all registered
667 * listeners.
668 * <P>
669 * In some cases, shapes look better if they do NOT have an outline, but
670 * this flag allows you to set your own preference.
671 *
672 * @param flag the flag.
673 *
674 * @see #getDrawOutlines()
675 */
676 public void setDrawOutlines(boolean flag) {
677 this.drawOutlines = flag;
678 notifyListeners(new RendererChangeEvent(this));
679 }
680
681 /**
682 * Returns <code>true</code> if the renderer should use the fill paint
683 * setting to fill shapes, and <code>false</code> if it should just
684 * use the regular paint.
685 *
686 * @return A boolean.
687 *
688 * @see #setUseFillPaint(boolean)
689 * @see #getUseOutlinePaint()
690 */
691 public boolean getUseFillPaint() {
692 return this.useFillPaint;
693 }
694
695 /**
696 * Sets the flag that controls whether the fill paint is used to fill
697 * shapes, and sends a {@link RendererChangeEvent} to all
698 * registered listeners.
699 *
700 * @param flag the flag.
701 *
702 * @see #getUseFillPaint()
703 */
704 public void setUseFillPaint(boolean flag) {
705 this.useFillPaint = flag;
706 notifyListeners(new RendererChangeEvent(this));
707 }
708
709 /**
710 * Returns <code>true</code> if the renderer should use the outline paint
711 * setting to draw shape outlines, and <code>false</code> if it should just
712 * use the regular paint.
713 *
714 * @return A boolean.
715 *
716 * @see #setUseOutlinePaint(boolean)
717 * @see #getUseFillPaint()
718 */
719 public boolean getUseOutlinePaint() {
720 return this.useOutlinePaint;
721 }
722
723 /**
724 * Sets the flag that controls whether the outline paint is used to draw
725 * shape outlines, and sends a {@link RendererChangeEvent} to all
726 * registered listeners.
727 *
728 * @param flag the flag.
729 *
730 * @see #getUseOutlinePaint()
731 */
732 public void setUseOutlinePaint(boolean flag) {
733 this.useOutlinePaint = flag;
734 notifyListeners(new RendererChangeEvent(this));
735 }
736
737 /**
738 * Records the state for the renderer. This is used to preserve state
739 * information between calls to the drawItem() method for a single chart
740 * drawing.
741 */
742 public static class State extends XYItemRendererState {
743
744 /** The path for the current series. */
745 public GeneralPath seriesPath;
746
747 /**
748 * A flag that indicates if the last (x, y) point was 'good'
749 * (non-null).
750 */
751 private boolean lastPointGood;
752
753 /**
754 * Creates a new state instance.
755 *
756 * @param info the plot rendering info.
757 */
758 public State(PlotRenderingInfo info) {
759 super(info);
760 }
761
762 /**
763 * Returns a flag that indicates if the last point drawn (in the
764 * current series) was 'good' (non-null).
765 *
766 * @return A boolean.
767 */
768 public boolean isLastPointGood() {
769 return this.lastPointGood;
770 }
771
772 /**
773 * Sets a flag that indicates if the last point drawn (in the current
774 * series) was 'good' (non-null).
775 *
776 * @param good the flag.
777 */
778 public void setLastPointGood(boolean good) {
779 this.lastPointGood = good;
780 }
781 }
782
783 /**
784 * Initialises the renderer.
785 * <P>
786 * This method will be called before the first item is rendered, giving the
787 * renderer an opportunity to initialise any state information it wants to
788 * maintain. The renderer can do nothing if it chooses.
789 *
790 * @param g2 the graphics device.
791 * @param dataArea the area inside the axes.
792 * @param plot the plot.
793 * @param data the data.
794 * @param info an optional info collection object to return data back to
795 * the caller.
796 *
797 * @return The renderer state.
798 */
799 public XYItemRendererState initialise(Graphics2D g2,
800 Rectangle2D dataArea,
801 XYPlot plot,
802 XYDataset data,
803 PlotRenderingInfo info) {
804
805 State state = new State(info);
806 state.seriesPath = new GeneralPath();
807 return state;
808
809 }
810
811 /**
812 * Draws the visual representation of a single data item.
813 *
814 * @param g2 the graphics device.
815 * @param state the renderer state.
816 * @param dataArea the area within which the data is being drawn.
817 * @param info collects information about the drawing.
818 * @param plot the plot (can be used to obtain standard color
819 * information etc).
820 * @param domainAxis the domain axis.
821 * @param rangeAxis the range axis.
822 * @param dataset the dataset.
823 * @param series the series index (zero-based).
824 * @param item the item index (zero-based).
825 * @param crosshairState crosshair information for the plot
826 * (<code>null</code> permitted).
827 * @param pass the pass index.
828 */
829 public void drawItem(Graphics2D g2,
830 XYItemRendererState state,
831 Rectangle2D dataArea,
832 PlotRenderingInfo info,
833 XYPlot plot,
834 ValueAxis domainAxis,
835 ValueAxis rangeAxis,
836 XYDataset dataset,
837 int series,
838 int item,
839 CrosshairState crosshairState,
840 int pass) {
841
842 // do nothing if item is not visible
843 if (!getItemVisible(series, item)) {
844 return;
845 }
846
847 // first pass draws the background (lines, for instance)
848 if (isLinePass(pass)) {
849 if (item == 0) {
850 if (this.drawSeriesLineAsPath) {
851 State s = (State) state;
852 s.seriesPath.reset();
853 s.lastPointGood = false;
854 }
855 }
856
857 if (getItemLineVisible(series, item)) {
858 if (this.drawSeriesLineAsPath) {
859 drawPrimaryLineAsPath(state, g2, plot, dataset, pass,
860 series, item, domainAxis, rangeAxis, dataArea);
861 }
862 else {
863 drawPrimaryLine(state, g2, plot, dataset, pass, series,
864 item, domainAxis, rangeAxis, dataArea);
865 }
866 }
867 }
868 // second pass adds shapes where the items are ..
869 else if (isItemPass(pass)) {
870
871 // setup for collecting optional entity info...
872 EntityCollection entities = null;
873 if (info != null) {
874 entities = info.getOwner().getEntityCollection();
875 }
876
877 drawSecondaryPass(g2, plot, dataset, pass, series, item,
878 domainAxis, dataArea, rangeAxis, crosshairState, entities);
879 }
880 }
881
882 /**
883 * Returns <code>true</code> if the specified pass is the one for drawing
884 * lines.
885 *
886 * @param pass the pass.
887 *
888 * @return A boolean.
889 */
890 protected boolean isLinePass(int pass) {
891 return pass == 0;
892 }
893
894 /**
895 * Returns <code>true</code> if the specified pass is the one for drawing
896 * items.
897 *
898 * @param pass the pass.
899 *
900 * @return A boolean.
901 */
902 protected boolean isItemPass(int pass) {
903 return pass == 1;
904 }
905
906 /**
907 * Draws the item (first pass). This method draws the lines
908 * connecting the items.
909 *
910 * @param g2 the graphics device.
911 * @param state the renderer state.
912 * @param dataArea the area within which the data is being drawn.
913 * @param plot the plot (can be used to obtain standard color
914 * information etc).
915 * @param domainAxis the domain axis.
916 * @param rangeAxis the range axis.
917 * @param dataset the dataset.
918 * @param pass the pass.
919 * @param series the series index (zero-based).
920 * @param item the item index (zero-based).
921 */
922 protected void drawPrimaryLine(XYItemRendererState state,
923 Graphics2D g2,
924 XYPlot plot,
925 XYDataset dataset,
926 int pass,
927 int series,
928 int item,
929 ValueAxis domainAxis,
930 ValueAxis rangeAxis,
931 Rectangle2D dataArea) {
932 if (item == 0) {
933 return;
934 }
935
936 // get the data point...
937 double x1 = dataset.getXValue(series, item);
938 double y1 = dataset.getYValue(series, item);
939 if (Double.isNaN(y1) || Double.isNaN(x1)) {
940 return;
941 }
942
943 double x0 = dataset.getXValue(series, item - 1);
944 double y0 = dataset.getYValue(series, item - 1);
945 if (Double.isNaN(y0) || Double.isNaN(x0)) {
946 return;
947 }
948
949 RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
950 RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
951
952 double transX0 = domainAxis.valueToJava2D(x0, dataArea, xAxisLocation);
953 double transY0 = rangeAxis.valueToJava2D(y0, dataArea, yAxisLocation);
954
955 double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
956 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
957
958 // only draw if we have good values
959 if (Double.isNaN(transX0) || Double.isNaN(transY0)
960 || Double.isNaN(transX1) || Double.isNaN(transY1)) {
961 return;
962 }
963
964 PlotOrientation orientation = plot.getOrientation();
965 if (orientation == PlotOrientation.HORIZONTAL) {
966 state.workingLine.setLine(transY0, transX0, transY1, transX1);
967 }
968 else if (orientation == PlotOrientation.VERTICAL) {
969 state.workingLine.setLine(transX0, transY0, transX1, transY1);
970 }
971
972 if (state.workingLine.intersects(dataArea)) {
973 drawFirstPassShape(g2, pass, series, item, state.workingLine);
974 }
975 }
976
977 /**
978 * Draws the first pass shape.
979 *
980 * @param g2 the graphics device.
981 * @param pass the pass.
982 * @param series the series index.
983 * @param item the item index.
984 * @param shape the shape.
985 */
986 protected void drawFirstPassShape(Graphics2D g2, int pass, int series,
987 int item, Shape shape) {
988 g2.setStroke(getItemStroke(series, item));
989 g2.setPaint(getItemPaint(series, item));
990 g2.draw(shape);
991 }
992
993
994 /**
995 * Draws the item (first pass). This method draws the lines
996 * connecting the items. Instead of drawing separate lines,
997 * a GeneralPath is constructed and drawn at the end of
998 * the series painting.
999 *
1000 * @param g2 the graphics device.
1001 * @param state the renderer state.
1002 * @param plot the plot (can be used to obtain standard color information
1003 * etc).
1004 * @param dataset the dataset.
1005 * @param pass the pass.
1006 * @param series the series index (zero-based).
1007 * @param item the item index (zero-based).
1008 * @param domainAxis the domain axis.
1009 * @param rangeAxis the range axis.
1010 * @param dataArea the area within which the data is being drawn.
1011 */
1012 protected void drawPrimaryLineAsPath(XYItemRendererState state,
1013 Graphics2D g2, XYPlot plot,
1014 XYDataset dataset,
1015 int pass,
1016 int series,
1017 int item,
1018 ValueAxis domainAxis,
1019 ValueAxis rangeAxis,
1020 Rectangle2D dataArea) {
1021
1022
1023 RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
1024 RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
1025
1026 // get the data point...
1027 double x1 = dataset.getXValue(series, item);
1028 double y1 = dataset.getYValue(series, item);
1029 double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
1030 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
1031
1032 State s = (State) state;
1033 // update path to reflect latest point
1034 if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) {
1035 float x = (float) transX1;
1036 float y = (float) transY1;
1037 PlotOrientation orientation = plot.getOrientation();
1038 if (orientation == PlotOrientation.HORIZONTAL) {
1039 x = (float) transY1;
1040 y = (float) transX1;
1041 }
1042 if (s.isLastPointGood()) {
1043 s.seriesPath.lineTo(x, y);
1044 }
1045 else {
1046 s.seriesPath.moveTo(x, y);
1047 }
1048 s.setLastPointGood(true);
1049 }
1050 else {
1051 s.setLastPointGood(false);
1052 }
1053 // if this is the last item, draw the path ...
1054 if (item == dataset.getItemCount(series) - 1) {
1055 // draw path
1056 drawFirstPassShape(g2, pass, series, item, s.seriesPath);
1057 }
1058 }
1059
1060 /**
1061 * Draws the item shapes and adds chart entities (second pass). This method
1062 * draws the shapes which mark the item positions. If <code>entities</code>
1063 * is not <code>null</code> it will be populated with entity information
1064 * for points that fall within the data area.
1065 *
1066 * @param g2 the graphics device.
1067 * @param plot the plot (can be used to obtain standard color
1068 * information etc).
1069 * @param domainAxis the domain axis.
1070 * @param dataArea the area within which the data is being drawn.
1071 * @param rangeAxis the range axis.
1072 * @param dataset the dataset.
1073 * @param pass the pass.
1074 * @param series the series index (zero-based).
1075 * @param item the item index (zero-based).
1076 * @param crosshairState the crosshair state.
1077 * @param entities the entity collection.
1078 */
1079 protected void drawSecondaryPass(Graphics2D g2, XYPlot plot,
1080 XYDataset dataset,
1081 int pass, int series, int item,
1082 ValueAxis domainAxis,
1083 Rectangle2D dataArea,
1084 ValueAxis rangeAxis,
1085 CrosshairState crosshairState,
1086 EntityCollection entities) {
1087
1088 Shape entityArea = null;
1089
1090 // get the data point...
1091 double x1 = dataset.getXValue(series, item);
1092 double y1 = dataset.getYValue(series, item);
1093 if (Double.isNaN(y1) || Double.isNaN(x1)) {
1094 return;
1095 }
1096
1097 PlotOrientation orientation = plot.getOrientation();
1098 RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
1099 RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
1100 double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
1101 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
1102
1103 if (getItemShapeVisible(series, item)) {
1104 Shape shape = getItemShape(series, item);
1105 if (orientation == PlotOrientation.HORIZONTAL) {
1106 shape = ShapeUtilities.createTranslatedShape(shape, transY1,
1107 transX1);
1108 }
1109 else if (orientation == PlotOrientation.VERTICAL) {
1110 shape = ShapeUtilities.createTranslatedShape(shape, transX1,
1111 transY1);
1112 }
1113 entityArea = shape;
1114 if (shape.intersects(dataArea)) {
1115 if (getItemShapeFilled(series, item)) {
1116 if (this.useFillPaint) {
1117 g2.setPaint(getItemFillPaint(series, item));
1118 }
1119 else {
1120 g2.setPaint(getItemPaint(series, item));
1121 }
1122 g2.fill(shape);
1123 }
1124 if (this.drawOutlines) {
1125 if (getUseOutlinePaint()) {
1126 g2.setPaint(getItemOutlinePaint(series, item));
1127 }
1128 else {
1129 g2.setPaint(getItemPaint(series, item));
1130 }
1131 g2.setStroke(getItemOutlineStroke(series, item));
1132 g2.draw(shape);
1133 }
1134 }
1135 }
1136
1137 double xx = transX1;
1138 double yy = transY1;
1139 if (orientation == PlotOrientation.HORIZONTAL) {
1140 xx = transY1;
1141 yy = transX1;
1142 }
1143
1144 // draw the item label if there is one...
1145 if (isItemLabelVisible(series, item)) {
1146 drawItemLabel(g2, orientation, dataset, series, item, xx, yy,
1147 (y1 < 0.0));
1148 }
1149
1150 int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
1151 int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
1152 updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
1153 rangeAxisIndex, transX1, transY1, plot.getOrientation());
1154
1155 // add an entity for the item, but only if it falls within the data
1156 // area...
1157 if (entities != null && dataArea.contains(xx, yy)) {
1158 addEntity(entities, entityArea, dataset, series, item, xx, yy);
1159 }
1160 }
1161
1162
1163 /**
1164 * Returns a legend item for the specified series.
1165 *
1166 * @param datasetIndex the dataset index (zero-based).
1167 * @param series the series index (zero-based).
1168 *
1169 * @return A legend item for the series.
1170 */
1171 public LegendItem getLegendItem(int datasetIndex, int series) {
1172
1173 XYPlot plot = getPlot();
1174 if (plot == null) {
1175 return null;
1176 }
1177
1178 LegendItem result = null;
1179 XYDataset dataset = plot.getDataset(datasetIndex);
1180 if (dataset != null) {
1181 if (getItemVisible(series, 0)) {
1182 String label = getLegendItemLabelGenerator().generateLabel(
1183 dataset, series);
1184 String description = label;
1185 String toolTipText = null;
1186 if (getLegendItemToolTipGenerator() != null) {
1187 toolTipText = getLegendItemToolTipGenerator().generateLabel(
1188 dataset, series);
1189 }
1190 String urlText = null;
1191 if (getLegendItemURLGenerator() != null) {
1192 urlText = getLegendItemURLGenerator().generateLabel(
1193 dataset, series);
1194 }
1195 boolean shapeIsVisible = getItemShapeVisible(series, 0);
1196 Shape shape = lookupSeriesShape(series);
1197 boolean shapeIsFilled = getItemShapeFilled(series, 0);
1198 Paint fillPaint = (this.useFillPaint
1199 ? lookupSeriesFillPaint(series) : lookupSeriesPaint(series));
1200 boolean shapeOutlineVisible = this.drawOutlines;
1201 Paint outlinePaint = (this.useOutlinePaint
1202 ? lookupSeriesOutlinePaint(series)
1203 : lookupSeriesPaint(series));
1204 Stroke outlineStroke = lookupSeriesOutlineStroke(series);
1205 boolean lineVisible = getItemLineVisible(series, 0);
1206 Stroke lineStroke = lookupSeriesStroke(series);
1207 Paint linePaint = lookupSeriesPaint(series);
1208 result = new LegendItem(label, description, toolTipText,
1209 urlText, shapeIsVisible, shape, shapeIsFilled,
1210 fillPaint, shapeOutlineVisible, outlinePaint,
1211 outlineStroke, lineVisible, this.legendLine,
1212 lineStroke, linePaint);
1213 result.setSeriesKey(dataset.getSeriesKey(series));
1214 result.setSeriesIndex(series);
1215 result.setDataset(dataset);
1216 result.setDatasetIndex(datasetIndex);
1217 }
1218 }
1219
1220 return result;
1221
1222 }
1223
1224 /**
1225 * Returns a clone of the renderer.
1226 *
1227 * @return A clone.
1228 *
1229 * @throws CloneNotSupportedException if the clone cannot be created.
1230 */
1231 public Object clone() throws CloneNotSupportedException {
1232 XYLineAndShapeRenderer clone = (XYLineAndShapeRenderer) super.clone();
1233 clone.seriesLinesVisible
1234 = (BooleanList) this.seriesLinesVisible.clone();
1235 if (this.legendLine != null) {
1236 clone.legendLine = ShapeUtilities.clone(this.legendLine);
1237 }
1238 clone.seriesShapesVisible
1239 = (BooleanList) this.seriesShapesVisible.clone();
1240 clone.seriesShapesFilled
1241 = (BooleanList) this.seriesShapesFilled.clone();
1242 return clone;
1243 }
1244
1245 /**
1246 * Tests this renderer for equality with an arbitrary object.
1247 *
1248 * @param obj the object (<code>null</code> permitted).
1249 *
1250 * @return <code>true</code> or <code>false</code>.
1251 */
1252 public boolean equals(Object obj) {
1253
1254 if (obj == this) {
1255 return true;
1256 }
1257 if (!(obj instanceof XYLineAndShapeRenderer)) {
1258 return false;
1259 }
1260 if (!super.equals(obj)) {
1261 return false;
1262 }
1263 XYLineAndShapeRenderer that = (XYLineAndShapeRenderer) obj;
1264 if (!ObjectUtilities.equal(this.linesVisible, that.linesVisible)) {
1265 return false;
1266 }
1267 if (!ObjectUtilities.equal(
1268 this.seriesLinesVisible, that.seriesLinesVisible)
1269 ) {
1270 return false;
1271 }
1272 if (this.baseLinesVisible != that.baseLinesVisible) {
1273 return false;
1274 }
1275 if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) {
1276 return false;
1277 }
1278 if (!ObjectUtilities.equal(this.shapesVisible, that.shapesVisible)) {
1279 return false;
1280 }
1281 if (!ObjectUtilities.equal(
1282 this.seriesShapesVisible, that.seriesShapesVisible)
1283 ) {
1284 return false;
1285 }
1286 if (this.baseShapesVisible != that.baseShapesVisible) {
1287 return false;
1288 }
1289 if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) {
1290 return false;
1291 }
1292 if (!ObjectUtilities.equal(
1293 this.seriesShapesFilled, that.seriesShapesFilled)
1294 ) {
1295 return false;
1296 }
1297 if (this.baseShapesFilled != that.baseShapesFilled) {
1298 return false;
1299 }
1300 if (this.drawOutlines != that.drawOutlines) {
1301 return false;
1302 }
1303 if (this.useOutlinePaint != that.useOutlinePaint) {
1304 return false;
1305 }
1306 if (this.useFillPaint != that.useFillPaint) {
1307 return false;
1308 }
1309 if (this.drawSeriesLineAsPath != that.drawSeriesLineAsPath) {
1310 return false;
1311 }
1312 return true;
1313
1314 }
1315
1316 /**
1317 * Provides serialization support.
1318 *
1319 * @param stream the input stream.
1320 *
1321 * @throws IOException if there is an I/O error.
1322 * @throws ClassNotFoundException if there is a classpath problem.
1323 */
1324 private void readObject(ObjectInputStream stream)
1325 throws IOException, ClassNotFoundException {
1326 stream.defaultReadObject();
1327 this.legendLine = SerialUtilities.readShape(stream);
1328 }
1329
1330 /**
1331 * Provides serialization support.
1332 *
1333 * @param stream the output stream.
1334 *
1335 * @throws IOException if there is an I/O error.
1336 */
1337 private void writeObject(ObjectOutputStream stream) throws IOException {
1338 stream.defaultWriteObject();
1339 SerialUtilities.writeShape(this.legendLine, stream);
1340 }
1341
1342 }