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