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