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
091package org.jfree.chart.renderer.category;
092
093import java.awt.Graphics2D;
094import java.awt.Paint;
095import java.awt.Shape;
096import java.awt.Stroke;
097import java.awt.geom.Line2D;
098import java.awt.geom.Rectangle2D;
099import java.io.Serializable;
100
101import org.jfree.chart.LegendItem;
102import org.jfree.chart.axis.CategoryAxis;
103import org.jfree.chart.axis.ValueAxis;
104import org.jfree.chart.entity.EntityCollection;
105import org.jfree.chart.event.RendererChangeEvent;
106import org.jfree.chart.plot.CategoryPlot;
107import org.jfree.chart.plot.PlotOrientation;
108import org.jfree.data.category.CategoryDataset;
109import org.jfree.util.BooleanList;
110import org.jfree.util.BooleanUtilities;
111import org.jfree.util.ObjectUtilities;
112import org.jfree.util.PublicCloneable;
113import 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 */
119public 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        notifyListeners(new RendererChangeEvent(this));
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        notifyListeners(new RendererChangeEvent(this));
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        notifyListeners(new RendererChangeEvent(this));
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        notifyListeners(new RendererChangeEvent(this));
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        notifyListeners(new RendererChangeEvent(this));
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        notifyListeners(new RendererChangeEvent(this));
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        notifyListeners(new RendererChangeEvent(this));
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        notifyListeners(new RendererChangeEvent(this));
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        notifyListeners(new RendererChangeEvent(this));
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        notifyListeners(new RendererChangeEvent(this));
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        notifyListeners(new RendererChangeEvent(this));
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        notifyListeners(new RendererChangeEvent(this));
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        notifyListeners(new RendererChangeEvent(this));
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        notifyListeners(new RendererChangeEvent(this));
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}