001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2005, 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-2005, 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     * $Id: LineAndShapeRenderer.java,v 1.18.2.5 2005/11/28 12:06:35 mungady Exp $
039     *
040     * Changes
041     * -------
042     * 23-Oct-2001 : Version 1 (DG);
043     * 15-Nov-2001 : Modified to allow for null data values (DG);
044     * 16-Jan-2002 : Renamed HorizontalCategoryItemRenderer.java 
045     *               --> CategoryItemRenderer.java (DG);
046     * 05-Feb-2002 : Changed return type of the drawCategoryItem method from void 
047     *               to Shape, as part of the tooltips implementation (DG);
048     * 11-May-2002 : Support for value label drawing (JB);
049     * 29-May-2002 : Now extends AbstractCategoryItemRenderer (DG);
050     * 25-Jun-2002 : Removed redundant import (DG);
051     * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs 
052     *               for HTML image maps (RA);
053     * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG);
054     * 11-Oct-2002 : Added new constructor to incorporate tool tip and URL 
055     *               generators (DG);
056     * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and 
057     *               CategoryToolTipGenerator interface (DG);
058     * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
059     * 06-Nov-2002 : Renamed drawCategoryItem() --> drawItem() and now using axis 
060     *               for category spacing (DG);
061     * 17-Jan-2003 : Moved plot classes to a separate package (DG);
062     * 10-Apr-2003 : Changed CategoryDataset to KeyedValues2DDataset in drawItem()
063     *               method (DG);
064     * 12-May-2003 : Modified to take into account the plot orientation (DG);
065     * 29-Jul-2003 : Amended code that doesn't compile with JDK 1.2.2 (DG);
066     * 30-Jul-2003 : Modified entity constructor (CZ);
067     * 22-Sep-2003 : Fixed cloning (DG);
068     * 10-Feb-2004 : Small change to drawItem() method to make cut-and-paste 
069     *               override easier (DG);
070     * 16-Jun-2004 : Fixed bug (id=972454) with label positioning on horizontal 
071     *               charts (DG);
072     * 15-Oct-2004 : Updated equals() method (DG);
073     * 05-Nov-2004 : Modified drawItem() signature (DG);
074     * 11-Nov-2004 : Now uses ShapeUtilities class to translate shapes (DG);
075     * 27-Jan-2005 : Changed attribute names, modified constructor and removed 
076     *               constants (DG);
077     * 01-Feb-2005 : Removed unnecessary constants (DG);
078     * 15-Mar-2005 : Fixed bug 1163897, concerning outlines for shapes (DG);
079     * 13-Apr-2005 : Check flags that control series visibility (DG);
080     * 20-Apr-2005 : Use generators for legend labels, tooltips and URLs (DG);
081     * 09-Jun-2005 : Use addItemEntity() method (DG);
082     * 
083     */
084    
085    package org.jfree.chart.renderer.category;
086    
087    import java.awt.Graphics2D;
088    import java.awt.Paint;
089    import java.awt.Shape;
090    import java.awt.Stroke;
091    import java.awt.geom.Line2D;
092    import java.awt.geom.Rectangle2D;
093    import java.io.Serializable;
094    
095    import org.jfree.chart.LegendItem;
096    import org.jfree.chart.axis.CategoryAxis;
097    import org.jfree.chart.axis.ValueAxis;
098    import org.jfree.chart.entity.EntityCollection;
099    import org.jfree.chart.event.RendererChangeEvent;
100    import org.jfree.chart.plot.CategoryPlot;
101    import org.jfree.chart.plot.PlotOrientation;
102    import org.jfree.data.category.CategoryDataset;
103    import org.jfree.util.BooleanList;
104    import org.jfree.util.BooleanUtilities;
105    import org.jfree.util.ObjectUtilities;
106    import org.jfree.util.PublicCloneable;
107    import org.jfree.util.ShapeUtilities;
108    
109    /**
110     * A renderer that draws shapes for each data item, and lines between data 
111     * items (for use with the {@link CategoryPlot} class).
112     */
113    public class LineAndShapeRenderer extends AbstractCategoryItemRenderer 
114                                      implements Cloneable, PublicCloneable, 
115                                                 Serializable {
116    
117        /** For serialization. */
118        private static final long serialVersionUID = -197749519869226398L;
119        
120        /** A flag that controls whether or not lines are visible for ALL series. */
121        private Boolean linesVisible;
122    
123        /** 
124         * A table of flags that control (per series) whether or not lines are 
125         * visible. 
126         */
127        private BooleanList seriesLinesVisible;
128    
129        /** 
130         * A flag indicating whether or not lines are drawn between non-null 
131         * points. 
132         */
133        private boolean baseLinesVisible;
134    
135        /** 
136         * A flag that controls whether or not shapes are visible for ALL series. 
137         */
138        private Boolean shapesVisible;
139    
140        /** 
141         * A table of flags that control (per series) whether or not shapes are 
142         * visible. 
143         */
144        private BooleanList seriesShapesVisible;
145    
146        /** The default value returned by the getShapeVisible() method. */
147        private boolean baseShapesVisible;
148    
149        /** A flag that controls whether or not shapes are filled for ALL series. */
150        private Boolean shapesFilled;
151        
152        /** 
153         * A table of flags that control (per series) whether or not shapes are 
154         * filled. 
155         */
156        private BooleanList seriesShapesFilled;
157        
158        /** The default value returned by the getShapeFilled() method. */
159        private boolean baseShapesFilled;
160        
161        /** 
162         * A flag that controls whether the fill paint is used for filling 
163         * shapes. 
164         */
165        private boolean useFillPaint;
166    
167        /** A flag that controls whether outlines are drawn for shapes. */
168        private boolean drawOutlines;
169            
170        /** 
171         * A flag that controls whether the outline paint is used for drawing shape 
172         * outlines - if not, the regular series paint is used. 
173         */
174        private boolean useOutlinePaint;
175    
176        /**
177         * Creates a renderer with both lines and shapes visible by default.
178         */
179        public LineAndShapeRenderer() {
180            this(true, true);
181        }
182    
183        /**
184         * Creates a new renderer with lines and/or shapes visible.
185         * 
186         * @param lines  draw lines?
187         * @param shapes  draw shapes?
188         */
189        public LineAndShapeRenderer(boolean lines, boolean shapes) {
190            super();
191            this.linesVisible = null;
192            this.seriesLinesVisible = new BooleanList();
193            this.baseLinesVisible = lines;
194            this.shapesVisible = null;
195            this.seriesShapesVisible = new BooleanList();
196            this.baseShapesVisible = shapes;
197            this.shapesFilled = null;
198            this.seriesShapesFilled = new BooleanList();
199            this.baseShapesFilled = true;
200            this.useFillPaint = false;
201            this.drawOutlines = true;
202            this.useOutlinePaint = false;
203        }
204        
205        // LINES VISIBLE
206    
207        /**
208         * Returns the flag used to control whether or not the line for an item is 
209         * visible.
210         *
211         * @param series  the series index (zero-based).
212         * @param item  the item index (zero-based).
213         *
214         * @return A boolean.
215         */
216        public boolean getItemLineVisible(int series, int item) {
217            Boolean flag = this.linesVisible;
218            if (flag == null) {
219                flag = getSeriesLinesVisible(series);
220            }
221            if (flag != null) {
222                return flag.booleanValue();
223            }
224            else {
225                return this.baseLinesVisible;   
226            }
227        }
228    
229        /**
230         * Returns a flag that controls whether or not lines are drawn for ALL 
231         * series.  If this flag is <code>null</code>, then the "per series" 
232         * settings will apply.
233         * 
234         * @return A flag (possibly <code>null</code>).
235         */
236        public Boolean getLinesVisible() {
237            return this.linesVisible;   
238        }
239        
240        /**
241         * Sets a flag that controls whether or not lines are drawn between the 
242         * items in ALL series, and sends a {@link RendererChangeEvent} to all 
243         * registered listeners.  You need to set this to <code>null</code> if you 
244         * want the "per series" settings to apply.
245         *
246         * @param visible  the flag (<code>null</code> permitted).
247         */
248        public void setLinesVisible(Boolean visible) {
249            this.linesVisible = visible;
250            notifyListeners(new RendererChangeEvent(this));
251        }
252    
253        /**
254         * Sets a flag that controls whether or not lines are drawn between the 
255         * items in ALL series, and sends a {@link RendererChangeEvent} to all 
256         * registered listeners.
257         *
258         * @param visible  the flag.
259         */
260        public void setLinesVisible(boolean visible) {
261            setLinesVisible(BooleanUtilities.valueOf(visible));
262        }
263    
264        /**
265         * Returns the flag used to control whether or not the lines for a series 
266         * are visible.
267         *
268         * @param series  the series index (zero-based).
269         *
270         * @return The flag (possibly <code>null</code>).
271         */
272        public Boolean getSeriesLinesVisible(int series) {
273            return this.seriesLinesVisible.getBoolean(series);
274        }
275    
276        /**
277         * Sets the 'lines visible' flag for a series.
278         *
279         * @param series  the series index (zero-based).
280         * @param flag  the flag (<code>null</code> permitted).
281         */
282        public void setSeriesLinesVisible(int series, Boolean flag) {
283            this.seriesLinesVisible.setBoolean(series, flag);
284            notifyListeners(new RendererChangeEvent(this));
285        }
286    
287        /**
288         * Sets the 'lines visible' flag for a series.
289         * 
290         * @param series  the series index (zero-based).
291         * @param visible  the flag.
292         */
293        public void setSeriesLinesVisible(int series, boolean visible) {
294            setSeriesLinesVisible(series, BooleanUtilities.valueOf(visible));
295        }
296        
297        /**
298         * Returns the base 'lines visible' attribute.
299         *
300         * @return The base flag.
301         */
302        public boolean getBaseLinesVisible() {
303            return this.baseLinesVisible;
304        }
305    
306        /**
307         * Sets the base 'lines visible' flag.
308         *
309         * @param flag  the flag.
310         */
311        public void setBaseLinesVisible(boolean flag) {
312            this.baseLinesVisible = flag;
313            notifyListeners(new RendererChangeEvent(this));
314        }
315    
316        // SHAPES VISIBLE
317    
318        /**
319         * Returns the flag used to control whether or not the shape for an item is 
320         * visible.
321         *
322         * @param series  the series index (zero-based).
323         * @param item  the item index (zero-based).
324         *
325         * @return A boolean.
326         */
327        public boolean getItemShapeVisible(int series, int item) {
328            Boolean flag = this.shapesVisible;
329            if (flag == null) {
330                flag = getSeriesShapesVisible(series);
331            }
332            if (flag != null) {
333                return flag.booleanValue();
334            }
335            else {
336                return this.baseShapesVisible;   
337            }
338        }
339    
340        /**
341         * Returns the flag that controls whether the shapes are visible for the 
342         * items in ALL series.
343         * 
344         * @return The flag (possibly <code>null</code>).
345         */
346        public Boolean getShapesVisible() {
347            return this.shapesVisible;    
348        }
349        
350        /**
351         * Sets the 'shapes visible' for ALL series and sends a 
352         * {@link RendererChangeEvent} to all registered listeners.
353         *
354         * @param visible  the flag (<code>null</code> permitted).
355         */
356        public void setShapesVisible(Boolean visible) {
357            this.shapesVisible = visible;
358            notifyListeners(new RendererChangeEvent(this));
359        }
360    
361        /**
362         * Sets the 'shapes visible' for ALL series and sends a 
363         * {@link RendererChangeEvent} to all registered listeners.
364         * 
365         * @param visible  the flag.
366         */
367        public void setShapesVisible(boolean visible) {
368            setShapesVisible(BooleanUtilities.valueOf(visible));
369        }
370    
371        /**
372         * Returns the flag used to control whether or not the shapes for a series
373         * are visible.
374         *
375         * @param series  the series index (zero-based).
376         *
377         * @return A boolean.
378         */
379        public Boolean getSeriesShapesVisible(int series) {
380            return this.seriesShapesVisible.getBoolean(series);
381        }
382    
383        /**
384         * Sets the 'shapes visible' flag for a series and sends a 
385         * {@link RendererChangeEvent} to all registered listeners.
386         * 
387         * @param series  the series index (zero-based).
388         * @param visible  the flag.
389         */
390        public void setSeriesShapesVisible(int series, boolean visible) {
391            setSeriesShapesVisible(series, BooleanUtilities.valueOf(visible));
392        }
393        
394        /**
395         * Sets the 'shapes visible' flag for a series and sends a 
396         * {@link RendererChangeEvent} to all registered listeners.
397         *
398         * @param series  the series index (zero-based).
399         * @param flag  the flag.
400         */
401        public void setSeriesShapesVisible(int series, Boolean flag) {
402            this.seriesShapesVisible.setBoolean(series, flag);
403            notifyListeners(new RendererChangeEvent(this));
404        }
405    
406        /**
407         * Returns the base 'shape visible' attribute.
408         *
409         * @return The base flag.
410         */
411        public boolean getBaseShapesVisible() {
412            return this.baseShapesVisible;
413        }
414    
415        /**
416         * Sets the base 'shapes visible' flag.
417         *
418         * @param flag  the flag.
419         */
420        public void setBaseShapesVisible(boolean flag) {
421            this.baseShapesVisible = flag;
422            notifyListeners(new RendererChangeEvent(this));
423        }
424    
425        /**
426         * Returns <code>true</code> if outlines should be drawn for shapes, and 
427         * <code>false</code> otherwise.
428         * 
429         * @return A boolean.
430         */
431        public boolean getDrawOutlines() {
432            return this.drawOutlines;
433        }
434        
435        /**
436         * Sets the flag that controls whether outlines are drawn for 
437         * shapes, and sends a {@link RendererChangeEvent} to all registered 
438         * listeners. 
439         * <P>
440         * In some cases, shapes look better if they do NOT have an outline, but 
441         * this flag allows you to set your own preference.
442         * 
443         * @param flag  the flag.
444         */
445        public void setDrawOutlines(boolean flag) {
446            this.drawOutlines = flag;
447            notifyListeners(new RendererChangeEvent(this));
448        }
449        
450        /**
451         * Returns the flag that controls whether the outline paint is used for 
452         * shape outlines.  If not, the regular series paint is used.
453         * 
454         * @return A boolean.
455         */
456        public boolean getUseOutlinePaint() {
457            return this.useOutlinePaint;   
458        }
459        
460        /**
461         * Sets the flag that controls whether the outline paint is used for shape 
462         * outlines.
463         * 
464         * @param use  the flag.
465         */
466        public void setUseOutlinePaint(boolean use) {
467            this.useOutlinePaint = use;   
468        }
469    
470        // SHAPES FILLED
471        
472        /**
473         * Returns the flag used to control whether or not the shape for an item 
474         * is filled. The default implementation passes control to the 
475         * <code>getSeriesShapesFilled</code> method. You can override this method
476         * if you require different behaviour.
477         *
478         * @param series  the series index (zero-based).
479         * @param item  the item index (zero-based).
480         *
481         * @return A boolean.
482         */
483        public boolean getItemShapeFilled(int series, int item) {
484            return getSeriesShapesFilled(series);
485        }
486    
487        /**
488         * Returns the flag used to control whether or not the shapes for a series 
489         * are filled. 
490         *
491         * @param series  the series index (zero-based).
492         *
493         * @return A boolean.
494         */
495        public boolean getSeriesShapesFilled(int series) {
496    
497            // return the overall setting, if there is one...
498            if (this.shapesFilled != null) {
499                return this.shapesFilled.booleanValue();
500            }
501    
502            // otherwise look up the paint table
503            Boolean flag = this.seriesShapesFilled.getBoolean(series);
504            if (flag != null) {
505                return flag.booleanValue();
506            }
507            else {
508                return this.baseShapesFilled;
509            } 
510    
511        }
512        
513        /**
514         * Returns the flag that controls whether or not shapes are filled for 
515         * ALL series.
516         * 
517         * @return A Boolean.
518         */
519        public Boolean getShapesFilled() {
520            return this.shapesFilled;
521        }
522    
523        /**
524         * Sets the 'shapes filled' for ALL series.
525         * 
526         * @param filled  the flag.
527         */
528        public void setShapesFilled(boolean filled) {
529            if (filled) {
530                setShapesFilled(Boolean.TRUE);
531            }
532            else {
533                setShapesFilled(Boolean.FALSE);
534            }
535        }
536        
537        /**
538         * Sets the 'shapes filled' for ALL series.
539         * 
540         * @param filled  the flag (<code>null</code> permitted).
541         */
542        public void setShapesFilled(Boolean filled) {
543            this.shapesFilled = filled;
544        }
545        
546        /**
547         * Sets the 'shapes filled' flag for a series.
548         *
549         * @param series  the series index (zero-based).
550         * @param filled  the flag.
551         */
552        public void setSeriesShapesFilled(int series, Boolean filled) {
553            this.seriesShapesFilled.setBoolean(series, filled);
554        }
555    
556        /**
557         * Sets the 'shapes filled' flag for a series.
558         *
559         * @param series  the series index (zero-based).
560         * @param filled  the flag.
561         */
562        public void setSeriesShapesFilled(int series, boolean filled) {
563            this.seriesShapesFilled.setBoolean(
564                series, BooleanUtilities.valueOf(filled)
565            );
566        }
567    
568        /**
569         * Returns the base 'shape filled' attribute.
570         *
571         * @return The base flag.
572         */
573        public boolean getBaseShapesFilled() {
574            return this.baseShapesFilled;
575        }
576    
577        /**
578         * Sets the base 'shapes filled' flag.
579         *
580         * @param flag  the flag.
581         */
582        public void setBaseShapesFilled(boolean flag) {
583            this.baseShapesFilled = flag;
584        }
585    
586        /**
587         * Returns <code>true</code> if the renderer should use the fill paint 
588         * setting to fill shapes, and <code>false</code> if it should just
589         * use the regular paint.
590         * 
591         * @return A boolean.
592         */
593        public boolean getUseFillPaint() {
594            return this.useFillPaint;
595        }
596        
597        /**
598         * Sets the flag that controls whether the fill paint is used to fill 
599         * shapes, and sends a {@link RendererChangeEvent} to all 
600         * registered listeners.
601         * 
602         * @param flag  the flag.
603         */
604        public void setUseFillPaint(boolean flag) {
605            this.useFillPaint = flag;
606            notifyListeners(new RendererChangeEvent(this));
607        }
608        
609        /**
610         * Returns a legend item for a series.
611         *
612         * @param datasetIndex  the dataset index (zero-based).
613         * @param series  the series index (zero-based).
614         *
615         * @return The legend item.
616         */
617        public LegendItem getLegendItem(int datasetIndex, int series) {
618    
619            CategoryPlot cp = getPlot();
620            if (cp == null) {
621                return null;
622            }
623    
624            if (isSeriesVisible(series) && isSeriesVisibleInLegend(series)) {
625                CategoryDataset dataset;
626                dataset = cp.getDataset(datasetIndex);
627                String label = getLegendItemLabelGenerator().generateLabel(
628                    dataset, series
629                );
630                String description = label;
631                String toolTipText = null; 
632                if (getLegendItemToolTipGenerator() != null) {
633                    toolTipText = getLegendItemToolTipGenerator().generateLabel(
634                        dataset, series
635                    );   
636                }
637                String urlText = null;
638                if (getLegendItemURLGenerator() != null) {
639                    urlText = getLegendItemURLGenerator().generateLabel(
640                        dataset, series
641                    );   
642                }
643                Shape shape = getSeriesShape(series);
644                Paint paint = getSeriesPaint(series);
645                Paint fillPaint = (this.useFillPaint 
646                    ? getItemFillPaint(series, 0) : paint);
647                boolean shapeOutlineVisible = this.drawOutlines;
648                Paint outlinePaint = (this.useOutlinePaint 
649                    ? getItemOutlinePaint(series, 0) : paint);
650                Stroke outlineStroke = getSeriesOutlineStroke(series);
651                boolean lineVisible = getItemLineVisible(series, 0);
652                boolean shapeVisible = getItemShapeVisible(series, 0);
653                return new LegendItem(label, description, toolTipText, 
654                        urlText, shapeVisible, shape, getItemShapeFilled(series, 0),
655                        fillPaint, shapeOutlineVisible, outlinePaint, outlineStroke,
656                        lineVisible, new Line2D.Double(-7.0, 0.0, 7.0, 0.0),
657                        getItemStroke(series, 0), getItemPaint(series, 0));
658            }
659            return null;
660    
661        }
662    
663        /**
664         * This renderer uses two passes to draw the data.
665         * 
666         * @return The pass count (<code>2</code> for this renderer).
667         */
668        public int getPassCount() {
669            return 2;   
670        }
671        
672        /**
673         * Draw a single data item.
674         *
675         * @param g2  the graphics device.
676         * @param state  the renderer state.
677         * @param dataArea  the area in which the data is drawn.
678         * @param plot  the plot.
679         * @param domainAxis  the domain axis.
680         * @param rangeAxis  the range axis.
681         * @param dataset  the dataset.
682         * @param row  the row index (zero-based).
683         * @param column  the column index (zero-based).
684         * @param pass  the pass index.
685         */
686        public void drawItem(Graphics2D g2,
687                             CategoryItemRendererState state,
688                             Rectangle2D dataArea,
689                             CategoryPlot plot,
690                             CategoryAxis domainAxis,
691                             ValueAxis rangeAxis,
692                             CategoryDataset dataset,
693                             int row,
694                             int column,
695                             int pass) {
696    
697            // do nothing if item is not visible
698            if (!getItemVisible(row, column)) {
699                return;   
700            }
701    
702            // nothing is drawn for null...
703            Number v = dataset.getValue(row, column);
704            if (v == null) {
705                return;
706            }
707    
708            PlotOrientation orientation = plot.getOrientation();
709    
710            // current data point...
711            double x1 = domainAxis.getCategoryMiddle(
712                column, getColumnCount(), dataArea, plot.getDomainAxisEdge()
713            );
714            double value = v.doubleValue();
715            double y1 = rangeAxis.valueToJava2D(
716                value, dataArea, plot.getRangeAxisEdge()
717            );
718    
719            if (pass == 0 && getItemLineVisible(row, column)) {
720                if (column != 0) {
721                    Number previousValue = dataset.getValue(row, column - 1);
722                    if (previousValue != null) {
723                        // previous data point...
724                        double previous = previousValue.doubleValue();
725                        double x0 = domainAxis.getCategoryMiddle(
726                            column - 1, getColumnCount(), dataArea, 
727                            plot.getDomainAxisEdge()
728                        );
729                        double y0 = rangeAxis.valueToJava2D(
730                            previous, dataArea, plot.getRangeAxisEdge()
731                        );
732    
733                        Line2D line = null;
734                        if (orientation == PlotOrientation.HORIZONTAL) {
735                            line = new Line2D.Double(y0, x0, y1, x1);
736                        }
737                        else if (orientation == PlotOrientation.VERTICAL) {
738                            line = new Line2D.Double(x0, y0, x1, y1);
739                        }
740                        g2.setPaint(getItemPaint(row, column));
741                        g2.setStroke(getItemStroke(row, column));
742                        g2.draw(line);
743                    }
744                }
745            }
746    
747            if (pass == 1) {
748                Shape shape = getItemShape(row, column);
749                if (orientation == PlotOrientation.HORIZONTAL) {
750                    shape = ShapeUtilities.createTranslatedShape(shape, y1, x1);
751                }
752                else if (orientation == PlotOrientation.VERTICAL) {
753                    shape = ShapeUtilities.createTranslatedShape(shape, x1, y1);
754                }
755    
756                if (getItemShapeVisible(row, column)) {
757                    if (getItemShapeFilled(row, column)) {
758                        if (this.useFillPaint) {
759                            g2.setPaint(getItemFillPaint(row, column));
760                        }
761                        else {
762                            g2.setPaint(getItemPaint(row, column));   
763                        }
764                        g2.fill(shape);
765                    }
766                    if (this.drawOutlines) {
767                        if (this.useOutlinePaint) {
768                            g2.setPaint(getItemOutlinePaint(row, column));   
769                        }
770                        else {
771                            g2.setPaint(getItemPaint(row, column));
772                        }
773                        g2.setStroke(getItemOutlineStroke(row, column));
774                        g2.draw(shape);
775                    }
776                }
777    
778                // draw the item label if there is one...
779                if (isItemLabelVisible(row, column)) {
780                    if (orientation == PlotOrientation.HORIZONTAL) {
781                        drawItemLabel(
782                            g2, orientation, dataset, row, column, y1, x1, 
783                            (value < 0.0)
784                        );
785                    }
786                    else if (orientation == PlotOrientation.VERTICAL) {
787                        drawItemLabel(
788                            g2, orientation, dataset, row, column, x1, y1, 
789                            (value < 0.0)
790                        );
791                    }
792                }
793    
794                // add an item entity, if this information is being collected
795                EntityCollection entities = state.getEntityCollection();
796                if (entities != null) {
797                    addItemEntity(entities, dataset, row, column, shape);
798                }
799    
800            }
801    
802        }
803        
804        /**
805         * Tests this renderer for equality with an arbitrary object.
806         *
807         * @param obj  the object (<code>null</code> permitted).
808         *
809         * @return A boolean.
810         */
811        public boolean equals(Object obj) {
812    
813            if (obj == this) {
814                return true;
815            }
816            if (!(obj instanceof LineAndShapeRenderer)) {
817                return false;
818            }
819            
820            LineAndShapeRenderer that = (LineAndShapeRenderer) obj;
821            if (this.baseLinesVisible != that.baseLinesVisible) {
822                return false;
823            }
824            if (!ObjectUtilities.equal(this.seriesLinesVisible, 
825                    that.seriesLinesVisible)) {
826                return false;
827            }
828            if (!ObjectUtilities.equal(this.linesVisible, that.linesVisible)) {
829                return false;
830            }
831            if (this.baseShapesVisible != that.baseShapesVisible) {
832                return false;
833            }
834            if (!ObjectUtilities.equal(this.seriesShapesVisible, 
835                    that.seriesShapesVisible)) {
836                return false;
837            }
838            if (!ObjectUtilities.equal(this.shapesVisible, that.shapesVisible)) {
839                return false;
840            }
841            if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) {
842                return false;
843            }
844            if (!ObjectUtilities.equal(
845                this.seriesShapesFilled, that.seriesShapesFilled)
846            ) {
847                return false;
848            }
849            if (this.baseShapesFilled != that.baseShapesFilled) {
850                return false;
851            }
852            if (this.useOutlinePaint != that.useOutlinePaint) {
853                return false;
854            }
855            if (!super.equals(obj)) {
856                return false;
857            }
858            return true;
859        }
860    
861        /**
862         * Returns an independent copy of the renderer.
863         * 
864         * @return A clone.
865         * 
866         * @throws CloneNotSupportedException  should not happen.
867         */
868        public Object clone() throws CloneNotSupportedException {
869            LineAndShapeRenderer clone = (LineAndShapeRenderer) super.clone();
870            clone.seriesLinesVisible 
871                = (BooleanList) this.seriesLinesVisible.clone();
872            clone.seriesShapesVisible 
873                = (BooleanList) this.seriesLinesVisible.clone();
874            clone.seriesShapesFilled 
875                = (BooleanList) this.seriesShapesFilled.clone();
876            return clone;
877        }
878        
879    }