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