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