001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2009, 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 * LineAndShapeRenderer.java
029 * -------------------------
030 * (C) Copyright 2001-2009, by Object Refinery Limited and Contributors.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): Mark Watson (www.markwatson.com);
034 * Jeremy Bowman;
035 * Richard Atkinson;
036 * Christian W. Zuckschwerdt;
037 * Peter Kolb (patch 2497611);
038 *
039 * Changes
040 * -------
041 * 23-Oct-2001 : Version 1 (DG);
042 * 15-Nov-2001 : Modified to allow for null data values (DG);
043 * 16-Jan-2002 : Renamed HorizontalCategoryItemRenderer.java
044 * --> CategoryItemRenderer.java (DG);
045 * 05-Feb-2002 : Changed return type of the drawCategoryItem method from void
046 * to Shape, as part of the tooltips implementation (DG);
047 * 11-May-2002 : Support for value label drawing (JB);
048 * 29-May-2002 : Now extends AbstractCategoryItemRenderer (DG);
049 * 25-Jun-2002 : Removed redundant import (DG);
050 * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs
051 * for HTML image maps (RA);
052 * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG);
053 * 11-Oct-2002 : Added new constructor to incorporate tool tip and URL
054 * generators (DG);
055 * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and
056 * CategoryToolTipGenerator interface (DG);
057 * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
058 * 06-Nov-2002 : Renamed drawCategoryItem() --> drawItem() and now using axis
059 * for category spacing (DG);
060 * 17-Jan-2003 : Moved plot classes to a separate package (DG);
061 * 10-Apr-2003 : Changed CategoryDataset to KeyedValues2DDataset in drawItem()
062 * method (DG);
063 * 12-May-2003 : Modified to take into account the plot orientation (DG);
064 * 29-Jul-2003 : Amended code that doesn't compile with JDK 1.2.2 (DG);
065 * 30-Jul-2003 : Modified entity constructor (CZ);
066 * 22-Sep-2003 : Fixed cloning (DG);
067 * 10-Feb-2004 : Small change to drawItem() method to make cut-and-paste
068 * override easier (DG);
069 * 16-Jun-2004 : Fixed bug (id=972454) with label positioning on horizontal
070 * charts (DG);
071 * 15-Oct-2004 : Updated equals() method (DG);
072 * 05-Nov-2004 : Modified drawItem() signature (DG);
073 * 11-Nov-2004 : Now uses ShapeUtilities class to translate shapes (DG);
074 * 27-Jan-2005 : Changed attribute names, modified constructor and removed
075 * constants (DG);
076 * 01-Feb-2005 : Removed unnecessary constants (DG);
077 * 15-Mar-2005 : Fixed bug 1163897, concerning outlines for shapes (DG);
078 * 13-Apr-2005 : Check flags that control series visibility (DG);
079 * 20-Apr-2005 : Use generators for legend labels, tooltips and URLs (DG);
080 * 09-Jun-2005 : Use addItemEntity() method (DG);
081 * ------------- JFREECHART 1.0.x ---------------------------------------------
082 * 25-May-2006 : Added check to drawItem() to detect when both the line and
083 * the shape are not visible (DG);
084 * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
085 * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
086 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
087 * 24-Sep-2007 : Deprecated redundant fields/methods (DG);
088 * 27-Sep-2007 : Added option to offset series x-position within category (DG);
089 * 17-Jun-2008 : Apply legend shape, font and paint attributes (DG);
090 * 26-Jun-2008 : Added crosshair support (DG);
091 * 14-Jan-2009 : Added support for seriesVisible flags (PK);
092 *
093 */
094
095 package org.jfree.chart.renderer.category;
096
097 import java.awt.Graphics2D;
098 import java.awt.Paint;
099 import java.awt.Shape;
100 import java.awt.Stroke;
101 import java.awt.geom.Line2D;
102 import java.awt.geom.Rectangle2D;
103 import java.io.Serializable;
104
105 import org.jfree.chart.LegendItem;
106 import org.jfree.chart.axis.CategoryAxis;
107 import org.jfree.chart.axis.ValueAxis;
108 import org.jfree.chart.entity.EntityCollection;
109 import org.jfree.chart.event.RendererChangeEvent;
110 import org.jfree.chart.plot.CategoryPlot;
111 import org.jfree.chart.plot.PlotOrientation;
112 import org.jfree.data.category.CategoryDataset;
113 import org.jfree.util.BooleanList;
114 import org.jfree.util.BooleanUtilities;
115 import org.jfree.util.ObjectUtilities;
116 import org.jfree.util.PublicCloneable;
117 import org.jfree.util.ShapeUtilities;
118
119 /**
120 * A renderer that draws shapes for each data item, and lines between data
121 * items (for use with the {@link CategoryPlot} class).
122 * The example shown here is generated by the <code>LineChartDemo1.java</code>
123 * program included in the JFreeChart Demo Collection:
124 * <br><br>
125 * <img src="../../../../../images/LineAndShapeRendererSample.png"
126 * alt="LineAndShapeRendererSample.png" />
127 */
128 public class LineAndShapeRenderer extends AbstractCategoryItemRenderer
129 implements Cloneable, PublicCloneable, Serializable {
130
131 /** For serialization. */
132 private static final long serialVersionUID = -197749519869226398L;
133
134 /**
135 * A flag that controls whether or not lines are visible for ALL series.
136 *
137 * @deprecated As of 1.0.7 (this override flag is unnecessary).
138 */
139 private Boolean linesVisible;
140
141 /**
142 * A table of flags that control (per series) whether or not lines are
143 * visible.
144 */
145 private BooleanList seriesLinesVisible;
146
147 /**
148 * A flag indicating whether or not lines are drawn between non-null
149 * points.
150 */
151 private boolean baseLinesVisible;
152
153 /**
154 * A flag that controls whether or not shapes are visible for ALL series.
155 *
156 * @deprecated As of 1.0.7 (this override flag is unnecessary).
157 */
158 private Boolean shapesVisible;
159
160 /**
161 * A table of flags that control (per series) whether or not shapes are
162 * visible.
163 */
164 private BooleanList seriesShapesVisible;
165
166 /** The default value returned by the getShapeVisible() method. */
167 private boolean baseShapesVisible;
168
169 /**
170 * A flag that controls whether or not shapes are filled for ALL series.
171 *
172 * @deprecated As of 1.0.7 (this override flag is unnecessary).
173 */
174 private Boolean shapesFilled;
175
176 /**
177 * A table of flags that control (per series) whether or not shapes are
178 * filled.
179 */
180 private BooleanList seriesShapesFilled;
181
182 /** The default value returned by the getShapeFilled() method. */
183 private boolean baseShapesFilled;
184
185 /**
186 * A flag that controls whether the fill paint is used for filling
187 * shapes.
188 */
189 private boolean useFillPaint;
190
191 /** A flag that controls whether outlines are drawn for shapes. */
192 private boolean drawOutlines;
193
194 /**
195 * A flag that controls whether the outline paint is used for drawing shape
196 * outlines - if not, the regular series paint is used.
197 */
198 private boolean useOutlinePaint;
199
200 /**
201 * A flag that controls whether or not the x-position for each item is
202 * offset within the category according to the series.
203 *
204 * @since 1.0.7
205 */
206 private boolean useSeriesOffset;
207
208 /**
209 * The item margin used for series offsetting - this allows the positioning
210 * to match the bar positions of the {@link BarRenderer} class.
211 *
212 * @since 1.0.7
213 */
214 private double itemMargin;
215
216 /**
217 * Creates a renderer with both lines and shapes visible by default.
218 */
219 public LineAndShapeRenderer() {
220 this(true, true);
221 }
222
223 /**
224 * Creates a new renderer with lines and/or shapes visible.
225 *
226 * @param lines draw lines?
227 * @param shapes draw shapes?
228 */
229 public LineAndShapeRenderer(boolean lines, boolean shapes) {
230 super();
231 this.linesVisible = null;
232 this.seriesLinesVisible = new BooleanList();
233 this.baseLinesVisible = lines;
234 this.shapesVisible = null;
235 this.seriesShapesVisible = new BooleanList();
236 this.baseShapesVisible = shapes;
237 this.shapesFilled = null;
238 this.seriesShapesFilled = new BooleanList();
239 this.baseShapesFilled = true;
240 this.useFillPaint = false;
241 this.drawOutlines = true;
242 this.useOutlinePaint = false;
243 this.useSeriesOffset = false; // preserves old behaviour
244 this.itemMargin = 0.0;
245 }
246
247 // LINES VISIBLE
248
249 /**
250 * Returns the flag used to control whether or not the line for an item is
251 * visible.
252 *
253 * @param series the series index (zero-based).
254 * @param item the item index (zero-based).
255 *
256 * @return A boolean.
257 */
258 public boolean getItemLineVisible(int series, int item) {
259 Boolean flag = this.linesVisible;
260 if (flag == null) {
261 flag = getSeriesLinesVisible(series);
262 }
263 if (flag != null) {
264 return flag.booleanValue();
265 }
266 else {
267 return this.baseLinesVisible;
268 }
269 }
270
271 /**
272 * Returns a flag that controls whether or not lines are drawn for ALL
273 * series. If this flag is <code>null</code>, then the "per series"
274 * settings will apply.
275 *
276 * @return A flag (possibly <code>null</code>).
277 *
278 * @see #setLinesVisible(Boolean)
279 *
280 * @deprecated As of 1.0.7 (the override facility is unnecessary, just
281 * use the per-series and base (default) settings).
282 */
283 public Boolean getLinesVisible() {
284 return this.linesVisible;
285 }
286
287 /**
288 * Sets a flag that controls whether or not lines are drawn between the
289 * items in ALL series, and sends a {@link RendererChangeEvent} to all
290 * registered listeners. You need to set this to <code>null</code> if you
291 * want the "per series" settings to apply.
292 *
293 * @param visible the flag (<code>null</code> permitted).
294 *
295 * @see #getLinesVisible()
296 *
297 * @deprecated As of 1.0.7 (the override facility is unnecessary, just
298 * use the per-series and base (default) settings).
299 */
300 public void setLinesVisible(Boolean visible) {
301 this.linesVisible = visible;
302 fireChangeEvent();
303 }
304
305 /**
306 * Sets a flag that controls whether or not lines are drawn between the
307 * items in ALL series, and sends a {@link RendererChangeEvent} to all
308 * registered listeners.
309 *
310 * @param visible the flag.
311 *
312 * @see #getLinesVisible()
313 *
314 * @deprecated As of 1.0.7 (the override facility is unnecessary, just
315 * use the per-series and base (default) settings).
316 */
317 public void setLinesVisible(boolean visible) {
318 setLinesVisible(BooleanUtilities.valueOf(visible));
319 }
320
321 /**
322 * Returns the flag used to control whether or not the lines for a series
323 * are visible.
324 *
325 * @param series the series index (zero-based).
326 *
327 * @return The flag (possibly <code>null</code>).
328 *
329 * @see #setSeriesLinesVisible(int, Boolean)
330 */
331 public Boolean getSeriesLinesVisible(int series) {
332 return this.seriesLinesVisible.getBoolean(series);
333 }
334
335 /**
336 * Sets the 'lines visible' flag for a series and sends a
337 * {@link RendererChangeEvent} to all registered listeners.
338 *
339 * @param series the series index (zero-based).
340 * @param flag the flag (<code>null</code> permitted).
341 *
342 * @see #getSeriesLinesVisible(int)
343 */
344 public void setSeriesLinesVisible(int series, Boolean flag) {
345 this.seriesLinesVisible.setBoolean(series, flag);
346 fireChangeEvent();
347 }
348
349 /**
350 * Sets the 'lines visible' flag for a series and sends a
351 * {@link RendererChangeEvent} to all registered listeners.
352 *
353 * @param series the series index (zero-based).
354 * @param visible the flag.
355 *
356 * @see #getSeriesLinesVisible(int)
357 */
358 public void setSeriesLinesVisible(int series, boolean visible) {
359 setSeriesLinesVisible(series, BooleanUtilities.valueOf(visible));
360 }
361
362 /**
363 * Returns the base 'lines visible' attribute.
364 *
365 * @return The base flag.
366 *
367 * @see #getBaseLinesVisible()
368 */
369 public boolean getBaseLinesVisible() {
370 return this.baseLinesVisible;
371 }
372
373 /**
374 * Sets the base 'lines visible' flag and sends a
375 * {@link RendererChangeEvent} to all registered listeners.
376 *
377 * @param flag the flag.
378 *
379 * @see #getBaseLinesVisible()
380 */
381 public void setBaseLinesVisible(boolean flag) {
382 this.baseLinesVisible = flag;
383 fireChangeEvent();
384 }
385
386 // SHAPES VISIBLE
387
388 /**
389 * Returns the flag used to control whether or not the shape for an item is
390 * visible.
391 *
392 * @param series the series index (zero-based).
393 * @param item the item index (zero-based).
394 *
395 * @return A boolean.
396 */
397 public boolean getItemShapeVisible(int series, int item) {
398 Boolean flag = this.shapesVisible;
399 if (flag == null) {
400 flag = getSeriesShapesVisible(series);
401 }
402 if (flag != null) {
403 return flag.booleanValue();
404 }
405 else {
406 return this.baseShapesVisible;
407 }
408 }
409
410 /**
411 * Returns the flag that controls whether the shapes are visible for the
412 * items in ALL series.
413 *
414 * @return The flag (possibly <code>null</code>).
415 *
416 * @see #setShapesVisible(Boolean)
417 *
418 * @deprecated As of 1.0.7 (the override facility is unnecessary, just
419 * use the per-series and base (default) settings).
420 */
421 public Boolean getShapesVisible() {
422 return this.shapesVisible;
423 }
424
425 /**
426 * Sets the 'shapes visible' for ALL series and sends a
427 * {@link RendererChangeEvent} to all registered listeners.
428 *
429 * @param visible the flag (<code>null</code> permitted).
430 *
431 * @see #getShapesVisible()
432 *
433 * @deprecated As of 1.0.7 (the override facility is unnecessary, just
434 * use the per-series and base (default) settings).
435 */
436 public void setShapesVisible(Boolean visible) {
437 this.shapesVisible = visible;
438 fireChangeEvent();
439 }
440
441 /**
442 * Sets the 'shapes visible' for ALL series and sends a
443 * {@link RendererChangeEvent} to all registered listeners.
444 *
445 * @param visible the flag.
446 *
447 * @see #getShapesVisible()
448 *
449 * @deprecated As of 1.0.7 (the override facility is unnecessary, just
450 * use the per-series and base (default) settings).
451 */
452 public void setShapesVisible(boolean visible) {
453 setShapesVisible(BooleanUtilities.valueOf(visible));
454 }
455
456 /**
457 * Returns the flag used to control whether or not the shapes for a series
458 * are visible.
459 *
460 * @param series the series index (zero-based).
461 *
462 * @return A boolean.
463 *
464 * @see #setSeriesShapesVisible(int, Boolean)
465 */
466 public Boolean getSeriesShapesVisible(int series) {
467 return this.seriesShapesVisible.getBoolean(series);
468 }
469
470 /**
471 * Sets the 'shapes visible' flag for a series and sends a
472 * {@link RendererChangeEvent} to all registered listeners.
473 *
474 * @param series the series index (zero-based).
475 * @param visible the flag.
476 *
477 * @see #getSeriesShapesVisible(int)
478 */
479 public void setSeriesShapesVisible(int series, boolean visible) {
480 setSeriesShapesVisible(series, BooleanUtilities.valueOf(visible));
481 }
482
483 /**
484 * Sets the 'shapes visible' flag for a series and sends a
485 * {@link RendererChangeEvent} to all registered listeners.
486 *
487 * @param series the series index (zero-based).
488 * @param flag the flag.
489 *
490 * @see #getSeriesShapesVisible(int)
491 */
492 public void setSeriesShapesVisible(int series, Boolean flag) {
493 this.seriesShapesVisible.setBoolean(series, flag);
494 fireChangeEvent();
495 }
496
497 /**
498 * Returns the base 'shape visible' attribute.
499 *
500 * @return The base flag.
501 *
502 * @see #setBaseShapesVisible(boolean)
503 */
504 public boolean getBaseShapesVisible() {
505 return this.baseShapesVisible;
506 }
507
508 /**
509 * Sets the base 'shapes visible' flag and sends a
510 * {@link RendererChangeEvent} to all registered listeners.
511 *
512 * @param flag the flag.
513 *
514 * @see #getBaseShapesVisible()
515 */
516 public void setBaseShapesVisible(boolean flag) {
517 this.baseShapesVisible = flag;
518 fireChangeEvent();
519 }
520
521 /**
522 * Returns <code>true</code> if outlines should be drawn for shapes, and
523 * <code>false</code> otherwise.
524 *
525 * @return A boolean.
526 *
527 * @see #setDrawOutlines(boolean)
528 */
529 public boolean getDrawOutlines() {
530 return this.drawOutlines;
531 }
532
533 /**
534 * Sets the flag that controls whether outlines are drawn for
535 * shapes, and sends a {@link RendererChangeEvent} to all registered
536 * listeners.
537 * <P>
538 * In some cases, shapes look better if they do NOT have an outline, but
539 * this flag allows you to set your own preference.
540 *
541 * @param flag the flag.
542 *
543 * @see #getDrawOutlines()
544 */
545 public void setDrawOutlines(boolean flag) {
546 this.drawOutlines = flag;
547 fireChangeEvent();
548 }
549
550 /**
551 * Returns the flag that controls whether the outline paint is used for
552 * shape outlines. If not, the regular series paint is used.
553 *
554 * @return A boolean.
555 *
556 * @see #setUseOutlinePaint(boolean)
557 */
558 public boolean getUseOutlinePaint() {
559 return this.useOutlinePaint;
560 }
561
562 /**
563 * Sets the flag that controls whether the outline paint is used for shape
564 * outlines, and sends a {@link RendererChangeEvent} to all registered
565 * listeners.
566 *
567 * @param use the flag.
568 *
569 * @see #getUseOutlinePaint()
570 */
571 public void setUseOutlinePaint(boolean use) {
572 this.useOutlinePaint = use;
573 fireChangeEvent();
574 }
575
576 // SHAPES FILLED
577
578 /**
579 * Returns the flag used to control whether or not the shape for an item
580 * is filled. The default implementation passes control to the
581 * <code>getSeriesShapesFilled</code> method. You can override this method
582 * if you require different behaviour.
583 *
584 * @param series the series index (zero-based).
585 * @param item the item index (zero-based).
586 *
587 * @return A boolean.
588 */
589 public boolean getItemShapeFilled(int series, int item) {
590 return getSeriesShapesFilled(series);
591 }
592
593 /**
594 * Returns the flag used to control whether or not the shapes for a series
595 * are filled.
596 *
597 * @param series the series index (zero-based).
598 *
599 * @return A boolean.
600 */
601 public boolean getSeriesShapesFilled(int series) {
602
603 // return the overall setting, if there is one...
604 if (this.shapesFilled != null) {
605 return this.shapesFilled.booleanValue();
606 }
607
608 // otherwise look up the paint table
609 Boolean flag = this.seriesShapesFilled.getBoolean(series);
610 if (flag != null) {
611 return flag.booleanValue();
612 }
613 else {
614 return this.baseShapesFilled;
615 }
616
617 }
618
619 /**
620 * Returns the flag that controls whether or not shapes are filled for
621 * ALL series.
622 *
623 * @return A Boolean.
624 *
625 * @see #setShapesFilled(Boolean)
626 *
627 * @deprecated As of 1.0.7 (the override facility is unnecessary, just
628 * use the per-series and base (default) settings).
629 */
630 public Boolean getShapesFilled() {
631 return this.shapesFilled;
632 }
633
634 /**
635 * Sets the 'shapes filled' for ALL series and sends a
636 * {@link RendererChangeEvent} to all registered listeners.
637 *
638 * @param filled the flag.
639 *
640 * @see #getShapesFilled()
641 *
642 * @deprecated As of 1.0.7 (the override facility is unnecessary, just
643 * use the per-series and base (default) settings).
644 */
645 public void setShapesFilled(boolean filled) {
646 if (filled) {
647 setShapesFilled(Boolean.TRUE);
648 }
649 else {
650 setShapesFilled(Boolean.FALSE);
651 }
652 }
653
654 /**
655 * Sets the 'shapes filled' for ALL series and sends a
656 * {@link RendererChangeEvent} to all registered listeners.
657 *
658 * @param filled the flag (<code>null</code> permitted).
659 *
660 * @see #getShapesFilled()
661 *
662 * @deprecated As of 1.0.7 (the override facility is unnecessary, just
663 * use the per-series and base (default) settings).
664 */
665 public void setShapesFilled(Boolean filled) {
666 this.shapesFilled = filled;
667 fireChangeEvent();
668 }
669
670 /**
671 * Sets the 'shapes filled' flag for a series and sends a
672 * {@link RendererChangeEvent} to all registered listeners.
673 *
674 * @param series the series index (zero-based).
675 * @param filled the flag.
676 *
677 * @see #getSeriesShapesFilled(int)
678 */
679 public void setSeriesShapesFilled(int series, Boolean filled) {
680 this.seriesShapesFilled.setBoolean(series, filled);
681 fireChangeEvent();
682 }
683
684 /**
685 * Sets the 'shapes filled' flag for a series and sends a
686 * {@link RendererChangeEvent} to all registered listeners.
687 *
688 * @param series the series index (zero-based).
689 * @param filled the flag.
690 *
691 * @see #getSeriesShapesFilled(int)
692 */
693 public void setSeriesShapesFilled(int series, boolean filled) {
694 // delegate
695 setSeriesShapesFilled(series, BooleanUtilities.valueOf(filled));
696 }
697
698 /**
699 * Returns the base 'shape filled' attribute.
700 *
701 * @return The base flag.
702 *
703 * @see #setBaseShapesFilled(boolean)
704 */
705 public boolean getBaseShapesFilled() {
706 return this.baseShapesFilled;
707 }
708
709 /**
710 * Sets the base 'shapes filled' flag and sends a
711 * {@link RendererChangeEvent} to all registered listeners.
712 *
713 * @param flag the flag.
714 *
715 * @see #getBaseShapesFilled()
716 */
717 public void setBaseShapesFilled(boolean flag) {
718 this.baseShapesFilled = flag;
719 fireChangeEvent();
720 }
721
722 /**
723 * Returns <code>true</code> if the renderer should use the fill paint
724 * setting to fill shapes, and <code>false</code> if it should just
725 * use the regular paint.
726 *
727 * @return A boolean.
728 *
729 * @see #setUseFillPaint(boolean)
730 */
731 public boolean getUseFillPaint() {
732 return this.useFillPaint;
733 }
734
735 /**
736 * Sets the flag that controls whether the fill paint is used to fill
737 * shapes, and sends a {@link RendererChangeEvent} to all
738 * registered listeners.
739 *
740 * @param flag the flag.
741 *
742 * @see #getUseFillPaint()
743 */
744 public void setUseFillPaint(boolean flag) {
745 this.useFillPaint = flag;
746 fireChangeEvent();
747 }
748
749 /**
750 * Returns the flag that controls whether or not the x-position for each
751 * data item is offset within the category according to the series.
752 *
753 * @return A boolean.
754 *
755 * @see #setUseSeriesOffset(boolean)
756 *
757 * @since 1.0.7
758 */
759 public boolean getUseSeriesOffset() {
760 return this.useSeriesOffset;
761 }
762
763 /**
764 * Sets the flag that controls whether or not the x-position for each
765 * data item is offset within its category according to the series, and
766 * sends a {@link RendererChangeEvent} to all registered listeners.
767 *
768 * @param offset the offset.
769 *
770 * @see #getUseSeriesOffset()
771 *
772 * @since 1.0.7
773 */
774 public void setUseSeriesOffset(boolean offset) {
775 this.useSeriesOffset = offset;
776 fireChangeEvent();
777 }
778
779 /**
780 * Returns the item margin, which is the gap between items within a
781 * category (expressed as a percentage of the overall category width).
782 * This can be used to match the offset alignment with the bars drawn by
783 * a {@link BarRenderer}).
784 *
785 * @return The item margin.
786 *
787 * @see #setItemMargin(double)
788 * @see #getUseSeriesOffset()
789 *
790 * @since 1.0.7
791 */
792 public double getItemMargin() {
793 return this.itemMargin;
794 }
795
796 /**
797 * Sets the item margin, which is the gap between items within a category
798 * (expressed as a percentage of the overall category width), and sends
799 * a {@link RendererChangeEvent} to all registered listeners.
800 *
801 * @param margin the margin (0.0 <= margin < 1.0).
802 *
803 * @see #getItemMargin()
804 * @see #getUseSeriesOffset()
805 *
806 * @since 1.0.7
807 */
808 public void setItemMargin(double margin) {
809 if (margin < 0.0 || margin >= 1.0) {
810 throw new IllegalArgumentException("Requires 0.0 <= margin < 1.0.");
811 }
812 this.itemMargin = margin;
813 fireChangeEvent();
814 }
815
816 /**
817 * Returns a legend item for a series.
818 *
819 * @param datasetIndex the dataset index (zero-based).
820 * @param series the series index (zero-based).
821 *
822 * @return The legend item.
823 */
824 public LegendItem getLegendItem(int datasetIndex, int series) {
825
826 CategoryPlot cp = getPlot();
827 if (cp == null) {
828 return null;
829 }
830
831 if (isSeriesVisible(series) && isSeriesVisibleInLegend(series)) {
832 CategoryDataset dataset = cp.getDataset(datasetIndex);
833 String label = getLegendItemLabelGenerator().generateLabel(
834 dataset, series);
835 String description = label;
836 String toolTipText = null;
837 if (getLegendItemToolTipGenerator() != null) {
838 toolTipText = getLegendItemToolTipGenerator().generateLabel(
839 dataset, series);
840 }
841 String urlText = null;
842 if (getLegendItemURLGenerator() != null) {
843 urlText = getLegendItemURLGenerator().generateLabel(
844 dataset, series);
845 }
846 Shape shape = lookupLegendShape(series);
847 Paint paint = lookupSeriesPaint(series);
848 Paint fillPaint = (this.useFillPaint
849 ? getItemFillPaint(series, 0) : paint);
850 boolean shapeOutlineVisible = this.drawOutlines;
851 Paint outlinePaint = (this.useOutlinePaint
852 ? getItemOutlinePaint(series, 0) : paint);
853 Stroke outlineStroke = lookupSeriesOutlineStroke(series);
854 boolean lineVisible = getItemLineVisible(series, 0);
855 boolean shapeVisible = getItemShapeVisible(series, 0);
856 LegendItem result = new LegendItem(label, description, toolTipText,
857 urlText, shapeVisible, shape, getItemShapeFilled(series, 0),
858 fillPaint, shapeOutlineVisible, outlinePaint, outlineStroke,
859 lineVisible, new Line2D.Double(-7.0, 0.0, 7.0, 0.0),
860 getItemStroke(series, 0), getItemPaint(series, 0));
861 result.setLabelFont(lookupLegendTextFont(series));
862 Paint labelPaint = lookupLegendTextPaint(series);
863 if (labelPaint != null) {
864 result.setLabelPaint(labelPaint);
865 }
866 result.setDataset(dataset);
867 result.setDatasetIndex(datasetIndex);
868 result.setSeriesKey(dataset.getRowKey(series));
869 result.setSeriesIndex(series);
870 return result;
871 }
872 return null;
873
874 }
875
876 /**
877 * This renderer uses two passes to draw the data.
878 *
879 * @return The pass count (<code>2</code> for this renderer).
880 */
881 public int getPassCount() {
882 return 2;
883 }
884
885 /**
886 * Draw a single data item.
887 *
888 * @param g2 the graphics device.
889 * @param state the renderer state.
890 * @param dataArea the area in which the data is drawn.
891 * @param plot the plot.
892 * @param domainAxis the domain axis.
893 * @param rangeAxis the range axis.
894 * @param dataset the dataset.
895 * @param row the row index (zero-based).
896 * @param column the column index (zero-based).
897 * @param pass the pass index.
898 */
899 public void drawItem(Graphics2D g2, CategoryItemRendererState state,
900 Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
901 ValueAxis rangeAxis, CategoryDataset dataset, int row, int column,
902 int pass) {
903
904 // do nothing if item is not visible
905 if (!getItemVisible(row, column)) {
906 return;
907 }
908
909 // do nothing if both the line and shape are not visible
910 if (!getItemLineVisible(row, column)
911 && !getItemShapeVisible(row, column)) {
912 return;
913 }
914
915 // nothing is drawn for null...
916 Number v = dataset.getValue(row, column);
917 if (v == null) {
918 return;
919 }
920
921 int visibleRow = state.getVisibleSeriesIndex(row);
922 if (visibleRow < 0) {
923 return;
924 }
925 int visibleRowCount = state.getVisibleSeriesCount();
926
927 PlotOrientation orientation = plot.getOrientation();
928
929 // current data point...
930 double x1;
931 if (this.useSeriesOffset) {
932 x1 = domainAxis.getCategorySeriesMiddle(column,
933 dataset.getColumnCount(), visibleRow, visibleRowCount,
934 this.itemMargin, dataArea, plot.getDomainAxisEdge());
935 }
936 else {
937 x1 = domainAxis.getCategoryMiddle(column, getColumnCount(),
938 dataArea, plot.getDomainAxisEdge());
939 }
940 double value = v.doubleValue();
941 double y1 = rangeAxis.valueToJava2D(value, dataArea,
942 plot.getRangeAxisEdge());
943
944 if (pass == 0 && getItemLineVisible(row, column)) {
945 if (column != 0) {
946 Number previousValue = dataset.getValue(row, column - 1);
947 if (previousValue != null) {
948 // previous data point...
949 double previous = previousValue.doubleValue();
950 double x0;
951 if (this.useSeriesOffset) {
952 x0 = domainAxis.getCategorySeriesMiddle(
953 column - 1, dataset.getColumnCount(),
954 visibleRow, visibleRowCount,
955 this.itemMargin, dataArea,
956 plot.getDomainAxisEdge());
957 }
958 else {
959 x0 = domainAxis.getCategoryMiddle(column - 1,
960 getColumnCount(), dataArea,
961 plot.getDomainAxisEdge());
962 }
963 double y0 = rangeAxis.valueToJava2D(previous, dataArea,
964 plot.getRangeAxisEdge());
965
966 Line2D line = null;
967 if (orientation == PlotOrientation.HORIZONTAL) {
968 line = new Line2D.Double(y0, x0, y1, x1);
969 }
970 else if (orientation == PlotOrientation.VERTICAL) {
971 line = new Line2D.Double(x0, y0, x1, y1);
972 }
973 g2.setPaint(getItemPaint(row, column));
974 g2.setStroke(getItemStroke(row, column));
975 g2.draw(line);
976 }
977 }
978 }
979
980 if (pass == 1) {
981 Shape shape = getItemShape(row, column);
982 if (orientation == PlotOrientation.HORIZONTAL) {
983 shape = ShapeUtilities.createTranslatedShape(shape, y1, x1);
984 }
985 else if (orientation == PlotOrientation.VERTICAL) {
986 shape = ShapeUtilities.createTranslatedShape(shape, x1, y1);
987 }
988
989 if (getItemShapeVisible(row, column)) {
990 if (getItemShapeFilled(row, column)) {
991 if (this.useFillPaint) {
992 g2.setPaint(getItemFillPaint(row, column));
993 }
994 else {
995 g2.setPaint(getItemPaint(row, column));
996 }
997 g2.fill(shape);
998 }
999 if (this.drawOutlines) {
1000 if (this.useOutlinePaint) {
1001 g2.setPaint(getItemOutlinePaint(row, column));
1002 }
1003 else {
1004 g2.setPaint(getItemPaint(row, column));
1005 }
1006 g2.setStroke(getItemOutlineStroke(row, column));
1007 g2.draw(shape);
1008 }
1009 }
1010
1011 // draw the item label if there is one...
1012 if (isItemLabelVisible(row, column)) {
1013 if (orientation == PlotOrientation.HORIZONTAL) {
1014 drawItemLabel(g2, orientation, dataset, row, column, y1,
1015 x1, (value < 0.0));
1016 }
1017 else if (orientation == PlotOrientation.VERTICAL) {
1018 drawItemLabel(g2, orientation, dataset, row, column, x1,
1019 y1, (value < 0.0));
1020 }
1021 }
1022
1023 // submit the current data point as a crosshair candidate
1024 int datasetIndex = plot.indexOf(dataset);
1025 updateCrosshairValues(state.getCrosshairState(),
1026 dataset.getRowKey(row), dataset.getColumnKey(column),
1027 value, datasetIndex, x1, y1, orientation);
1028
1029 // add an item entity, if this information is being collected
1030 EntityCollection entities = state.getEntityCollection();
1031 if (entities != null) {
1032 addItemEntity(entities, dataset, row, column, shape);
1033 }
1034 }
1035
1036 }
1037
1038 /**
1039 * Tests this renderer for equality with an arbitrary object.
1040 *
1041 * @param obj the object (<code>null</code> permitted).
1042 *
1043 * @return A boolean.
1044 */
1045 public boolean equals(Object obj) {
1046
1047 if (obj == this) {
1048 return true;
1049 }
1050 if (!(obj instanceof LineAndShapeRenderer)) {
1051 return false;
1052 }
1053
1054 LineAndShapeRenderer that = (LineAndShapeRenderer) obj;
1055 if (this.baseLinesVisible != that.baseLinesVisible) {
1056 return false;
1057 }
1058 if (!ObjectUtilities.equal(this.seriesLinesVisible,
1059 that.seriesLinesVisible)) {
1060 return false;
1061 }
1062 if (!ObjectUtilities.equal(this.linesVisible, that.linesVisible)) {
1063 return false;
1064 }
1065 if (this.baseShapesVisible != that.baseShapesVisible) {
1066 return false;
1067 }
1068 if (!ObjectUtilities.equal(this.seriesShapesVisible,
1069 that.seriesShapesVisible)) {
1070 return false;
1071 }
1072 if (!ObjectUtilities.equal(this.shapesVisible, that.shapesVisible)) {
1073 return false;
1074 }
1075 if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) {
1076 return false;
1077 }
1078 if (!ObjectUtilities.equal(this.seriesShapesFilled,
1079 that.seriesShapesFilled)) {
1080 return false;
1081 }
1082 if (this.baseShapesFilled != that.baseShapesFilled) {
1083 return false;
1084 }
1085 if (this.useOutlinePaint != that.useOutlinePaint) {
1086 return false;
1087 }
1088 if (this.useSeriesOffset != that.useSeriesOffset) {
1089 return false;
1090 }
1091 if (this.itemMargin != that.itemMargin) {
1092 return false;
1093 }
1094 return super.equals(obj);
1095 }
1096
1097 /**
1098 * Returns an independent copy of the renderer.
1099 *
1100 * @return A clone.
1101 *
1102 * @throws CloneNotSupportedException should not happen.
1103 */
1104 public Object clone() throws CloneNotSupportedException {
1105 LineAndShapeRenderer clone = (LineAndShapeRenderer) super.clone();
1106 clone.seriesLinesVisible
1107 = (BooleanList) this.seriesLinesVisible.clone();
1108 clone.seriesShapesVisible
1109 = (BooleanList) this.seriesShapesVisible.clone();
1110 clone.seriesShapesFilled
1111 = (BooleanList) this.seriesShapesFilled.clone();
1112 return clone;
1113 }
1114
1115 }