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