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