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