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