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     * StatisticalLineAndShapeRenderer.java
029     * ------------------------------------
030     * (C) Copyright 2005, by Object Refinery Limited and Contributors.
031     *
032     * Original Author:  Mofeed Shahin;
033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
034     *
035     * $Id: StatisticalLineAndShapeRenderer.java,v 1.4.2.5 2005/12/02 10:40:17 mungady Exp $
036     *
037     * Changes
038     * -------
039     * 01-Feb-2005 : Version 1, contributed by Mofeed Shahin (DG);
040     * 16-Jun-2005 : Added errorIndicatorPaint to be consistent with 
041     *               StatisticalBarRenderer (DG);
042     *  
043     */
044    
045    package org.jfree.chart.renderer.category;
046    
047    import java.awt.Graphics2D;
048    import java.awt.Paint;
049    import java.awt.Shape;
050    import java.awt.geom.Line2D;
051    import java.awt.geom.Rectangle2D;
052    import java.io.IOException;
053    import java.io.ObjectInputStream;
054    import java.io.ObjectOutputStream;
055    import java.io.Serializable;
056    
057    import org.jfree.chart.axis.CategoryAxis;
058    import org.jfree.chart.axis.ValueAxis;
059    import org.jfree.chart.entity.CategoryItemEntity;
060    import org.jfree.chart.entity.EntityCollection;
061    import org.jfree.chart.event.RendererChangeEvent;
062    import org.jfree.chart.labels.CategoryToolTipGenerator;
063    import org.jfree.chart.plot.CategoryPlot;
064    import org.jfree.chart.plot.PlotOrientation;
065    import org.jfree.data.category.CategoryDataset;
066    import org.jfree.data.statistics.StatisticalCategoryDataset;
067    import org.jfree.io.SerialUtilities;
068    import org.jfree.ui.RectangleEdge;
069    import org.jfree.util.PaintUtilities;
070    import org.jfree.util.PublicCloneable;
071    import org.jfree.util.ShapeUtilities;
072    
073    /**
074     * A renderer that draws shapes for each data item, and lines between data 
075     * items.  Each point has a mean value and a standard deviation line. For use 
076     * with the {@link CategoryPlot} class.
077     */
078    public class StatisticalLineAndShapeRenderer extends LineAndShapeRenderer 
079        implements Cloneable, PublicCloneable, Serializable {
080    
081        /** For serialization. */
082        private static final long serialVersionUID = -3557517173697777579L;
083        
084        /** The paint used to show the error indicator. */
085        private transient Paint errorIndicatorPaint;
086    
087        /**
088         * Constructs a default renderer (draws shapes and lines).
089         */
090        public StatisticalLineAndShapeRenderer() {
091            this(true, true);
092        }
093    
094        /**
095         * Constructs a new renderer.
096         * 
097         * @param linesVisible  draw lines?
098         * @param shapesVisible  draw shapes?
099         */
100        public StatisticalLineAndShapeRenderer(boolean linesVisible, 
101                                               boolean shapesVisible) {
102            super(true, true);
103            this.errorIndicatorPaint = null;
104        }
105    
106        /**
107         * Returns the paint used for the error indicators.
108         * 
109         * @return The paint used for the error indicators (possibly 
110         *         <code>null</code>).
111         */
112        public Paint getErrorIndicatorPaint() {
113            return this.errorIndicatorPaint;   
114        }
115    
116        /**
117         * Sets the paint used for the error indicators (if <code>null</code>, 
118         * the item outline paint is used instead)
119         * 
120         * @param paint  the paint (<code>null</code> permitted).
121         */
122        public void setErrorIndicatorPaint(Paint paint) {
123            this.errorIndicatorPaint = paint;
124            notifyListeners(new RendererChangeEvent(this));
125        }
126        
127        /**
128         * Draw a single data item.
129         *
130         * @param g2  the graphics device.
131         * @param state  the renderer state.
132         * @param dataArea  the area in which the data is drawn.
133         * @param plot  the plot.
134         * @param domainAxis  the domain axis.
135         * @param rangeAxis  the range axis.
136         * @param dataset  the dataset.
137         * @param row  the row index (zero-based).
138         * @param column  the column index (zero-based).
139         * @param pass  the pass.
140         */
141        public void drawItem(Graphics2D g2,
142                             CategoryItemRendererState state,
143                             Rectangle2D dataArea,
144                             CategoryPlot plot,
145                             CategoryAxis domainAxis,
146                             ValueAxis rangeAxis,
147                             CategoryDataset dataset,
148                             int row,
149                             int column,
150                             int pass) {
151    
152            // nothing is drawn for null...
153            Number v = dataset.getValue(row, column);
154            if (v == null) {
155              return;
156            }
157    
158            StatisticalCategoryDataset statData 
159                = (StatisticalCategoryDataset) dataset;
160    
161            Number meanValue = statData.getMeanValue(row, column);
162    
163            PlotOrientation orientation = plot.getOrientation();
164    
165            // current data point...
166            double x1 = domainAxis.getCategoryMiddle(column, getColumnCount(), 
167                    dataArea, plot.getDomainAxisEdge());
168    
169            double y1 = rangeAxis.valueToJava2D(meanValue.doubleValue(), dataArea, 
170                    plot.getRangeAxisEdge());
171    
172            Shape shape = getItemShape(row, column);
173            if (orientation == PlotOrientation.HORIZONTAL) {
174                shape = ShapeUtilities.createTranslatedShape(shape, y1, x1);
175            }
176            else if (orientation == PlotOrientation.VERTICAL) {
177                shape = ShapeUtilities.createTranslatedShape(shape, x1, y1);
178            }
179            if (getItemShapeVisible(row, column)) {
180                
181                if (getItemShapeFilled(row, column)) {
182                    g2.setPaint(getItemPaint(row, column));
183                    g2.fill(shape);
184                }
185                else {
186                    if (getUseOutlinePaint()) {
187                        g2.setPaint(getItemOutlinePaint(row, column));   
188                    }
189                    else {
190                        g2.setPaint(getItemPaint(row, column));
191                    }
192                    g2.setStroke(getItemOutlineStroke(row, column));
193                    g2.draw(shape);
194                }
195            }
196    
197            if (getItemLineVisible(row, column)) {
198                if (column != 0) {
199    
200                    Number previousValue = statData.getValue(row, column - 1);
201                    if (previousValue != null) {
202    
203                        // previous data point...
204                        double previous = previousValue.doubleValue();
205                        double x0 = domainAxis.getCategoryMiddle(column - 1, 
206                                getColumnCount(), dataArea, 
207                                plot.getDomainAxisEdge());
208                        double y0 = rangeAxis.valueToJava2D(previous, dataArea, 
209                                plot.getRangeAxisEdge());
210    
211                        Line2D line = null;
212                        if (orientation == PlotOrientation.HORIZONTAL) {
213                            line = new Line2D.Double(y0, x0, y1, x1);
214                        }
215                        else if (orientation == PlotOrientation.VERTICAL) {
216                            line = new Line2D.Double(x0, y0, x1, y1);
217                        }
218                        g2.setPaint(getItemPaint(row, column));
219                        g2.setStroke(getItemStroke(row, column));
220                        g2.draw(line);
221                    }
222                }
223            }
224    
225            RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
226            RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
227            double rectX = domainAxis.getCategoryStart(column, getColumnCount(), 
228                    dataArea, xAxisLocation);
229            
230            rectX = rectX + row * state.getBarWidth();
231            
232            g2.setPaint(getItemPaint(row, column));
233            //standard deviation lines
234            double valueDelta = statData.getStdDevValue(row, column).doubleValue(); 
235    
236            double highVal, lowVal;
237            if ((meanValue.doubleValue() + valueDelta) 
238                    > rangeAxis.getRange().getUpperBound()) {
239                highVal = rangeAxis.valueToJava2D(
240                        rangeAxis.getRange().getUpperBound(), dataArea, 
241                        yAxisLocation);
242            }
243            else {
244                highVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 
245                        + valueDelta, dataArea, yAxisLocation);
246            }
247            
248            if ((meanValue.doubleValue() + valueDelta) 
249                    < rangeAxis.getRange().getLowerBound()) {
250                lowVal = rangeAxis.valueToJava2D(
251                        rangeAxis.getRange().getLowerBound(), dataArea, 
252                        yAxisLocation);
253            }
254            else {
255                lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 
256                        - valueDelta, dataArea, yAxisLocation);
257            }
258            
259            if (this.errorIndicatorPaint != null) {
260                g2.setPaint(this.errorIndicatorPaint);  
261            }
262            else {
263                g2.setPaint(getItemPaint(row, column));   
264            }
265            Line2D line = null;
266            line = new Line2D.Double(x1, lowVal, x1, highVal);
267            g2.draw(line);
268            line = new Line2D.Double(x1 - 5.0d, highVal, x1 + 5.0d, highVal);
269            g2.draw(line);
270            line = new Line2D.Double(x1 - 5.0d, lowVal, x1 + 5.0d, lowVal);
271            g2.draw(line);
272            
273            // draw the item label if there is one...
274            if (isItemLabelVisible(row, column)) {
275                if (orientation == PlotOrientation.HORIZONTAL) {
276                  drawItemLabel(g2, orientation, dataset, row, column, 
277                      y1, x1, (meanValue.doubleValue() < 0.0));
278                }
279                else if (orientation == PlotOrientation.VERTICAL) {
280                  drawItemLabel(g2, orientation, dataset, row, column, 
281                      x1, y1, (meanValue.doubleValue() < 0.0));                
282                }
283            }
284    
285            // collect entity and tool tip information...
286            if (state.getInfo() != null) {
287                EntityCollection entities = state.getEntityCollection();
288                if (entities != null && shape != null) {
289                    String tip = null;
290                    CategoryToolTipGenerator tipster = getToolTipGenerator(row, 
291                            column);
292                    if (tipster != null) {
293                        tip = tipster.generateToolTip(dataset, row, column);
294                    }
295                    String url = null;
296                    if (getItemURLGenerator(row, column) != null) {
297                        url = getItemURLGenerator(row, column).generateURL(
298                                dataset, row, column);
299                    }
300                    CategoryItemEntity entity = new CategoryItemEntity(shape, tip, 
301                            url, dataset, row, dataset.getColumnKey(column), 
302                            column);
303                    entities.add(entity);
304    
305                }
306    
307            }
308    
309        }
310    
311        /**
312         * Tests this renderer for equality with an arbitrary object.
313         * 
314         * @param obj  the object (<code>null</code> permitted).
315         * 
316         * @return A boolean.
317         */
318        public boolean equals(Object obj) {
319            if (obj == this) {
320                return true;   
321            }
322            if (!(obj instanceof StatisticalLineAndShapeRenderer)) {
323                return false;   
324            }
325            if (!super.equals(obj)) {
326                return false;   
327            }
328            StatisticalLineAndShapeRenderer that 
329                = (StatisticalLineAndShapeRenderer) obj;
330            if (!PaintUtilities.equal(this.errorIndicatorPaint, 
331                    that.errorIndicatorPaint)) {
332                return false;
333            }
334            return true;
335        }
336        
337        /**
338         * Provides serialization support.
339         *
340         * @param stream  the output stream.
341         *
342         * @throws IOException  if there is an I/O error.
343         */
344        private void writeObject(ObjectOutputStream stream) throws IOException {
345            stream.defaultWriteObject();
346            SerialUtilities.writePaint(this.errorIndicatorPaint, stream);
347        }
348    
349        /**
350         * Provides serialization support.
351         *
352         * @param stream  the input stream.
353         *
354         * @throws IOException  if there is an I/O error.
355         * @throws ClassNotFoundException  if there is a classpath problem.
356         */
357        private void readObject(ObjectInputStream stream) 
358            throws IOException, ClassNotFoundException {
359            stream.defaultReadObject();
360            this.errorIndicatorPaint = SerialUtilities.readPaint(stream);
361        }
362    
363    }