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