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