001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2013, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ---------------------------
028 * SamplingXYLineRenderer.java
029 * ---------------------------
030 * (C) Copyright 2008-2013, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 * Changes:
036 * --------
037 * 02-Oct-2008 : Version 1 (DG);
038 * 28-Apr-2009 : Fixed bug in legend shape display, and deprecated
039 *               getLegendLine() and setLegendLine() - these methods
040 *               are unnecessary because a mechanism already exists in the
041 *               superclass for specifying a custom legend shape (DG);
042 * 03-Jul-2013 : Use ParamChecks (DG);
043 *
044 */
045
046package org.jfree.chart.renderer.xy;
047
048import java.awt.Graphics2D;
049import java.awt.Shape;
050import java.awt.geom.GeneralPath;
051import java.awt.geom.Line2D;
052import java.awt.geom.PathIterator;
053import java.awt.geom.Rectangle2D;
054import java.io.IOException;
055import java.io.ObjectInputStream;
056import java.io.ObjectOutputStream;
057import java.io.Serializable;
058
059import org.jfree.chart.axis.ValueAxis;
060import org.jfree.chart.event.RendererChangeEvent;
061import org.jfree.chart.plot.CrosshairState;
062import org.jfree.chart.plot.PlotOrientation;
063import org.jfree.chart.plot.PlotRenderingInfo;
064import org.jfree.chart.plot.XYPlot;
065import org.jfree.chart.util.ParamChecks;
066import org.jfree.data.xy.XYDataset;
067import org.jfree.io.SerialUtilities;
068import org.jfree.ui.RectangleEdge;
069import org.jfree.util.PublicCloneable;
070import org.jfree.util.ShapeUtilities;
071
072/**
073 * A renderer that draws line charts.  The renderer doesn't necessarily plot
074 * every data item - instead, it tries to plot only those data items that
075 * make a difference to the visual output (the other data items are skipped).  
076 * This renderer is designed for use with the {@link XYPlot} class.
077 *
078 * @since 1.0.13
079 */
080public class SamplingXYLineRenderer extends AbstractXYItemRenderer
081        implements XYItemRenderer, Cloneable, PublicCloneable, Serializable {
082
083    /** The shape that is used to represent a line in the legend. */
084    private transient Shape legendLine;
085
086    /**
087     * Creates a new renderer.
088     */
089    public SamplingXYLineRenderer() {
090        this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
091        setBaseLegendShape(this.legendLine);
092        setTreatLegendShapeAsLine(true);
093    }
094
095    /**
096     * Returns the shape used to represent a line in the legend.
097     *
098     * @return The legend line (never <code>null</code>).
099     *
100     * @see #setLegendLine(Shape)
101     *
102     * @deprecated As of version 1.0.14, this method is deprecated.  You
103     * should use the {@link #getBaseLegendShape()} method instead.
104     */
105    public Shape getLegendLine() {
106        return this.legendLine;
107    }
108
109    /**
110     * Sets the shape used as a line in each legend item and sends a
111     * {@link RendererChangeEvent} to all registered listeners.
112     *
113     * @param line  the line (<code>null</code> not permitted).
114     *
115     * @see #getLegendLine()
116     *
117     * @deprecated As of version 1.0.14, this method is deprecated.  You should
118     * use the {@link #setBaseLegendShape(java.awt.Shape)} method instead.
119     */
120    public void setLegendLine(Shape line) {
121        ParamChecks.nullNotPermitted(line, "line");
122        this.legendLine = line;
123        fireChangeEvent();
124    }
125
126    /**
127     * Returns the number of passes through the data that the renderer requires
128     * in order to draw the chart.  Most charts will require a single pass, but
129     * some require two passes.
130     *
131     * @return The pass count.
132     */
133    @Override
134    public int getPassCount() {
135        return 1;
136    }
137
138    /**
139     * Records the state for the renderer.  This is used to preserve state
140     * information between calls to the drawItem() method for a single chart
141     * drawing.
142     */
143    public static class State extends XYItemRendererState {
144
145        /** The path for the current series. */
146        GeneralPath seriesPath;
147
148        /**
149         * A second path that draws vertical intervals to cover any extreme
150         * values.
151         */
152        GeneralPath intervalPath;
153
154        /**
155         * The minimum change in the x-value needed to trigger an update to
156         * the seriesPath.
157         */
158        double dX = 1.0;
159
160        /** The last x-coordinate visited by the seriesPath. */
161        double lastX;
162
163        /** The initial y-coordinate for the current x-coordinate. */
164        double openY = 0.0;
165
166        /** The highest y-coordinate for the current x-coordinate. */
167        double highY = 0.0;
168
169        /** The lowest y-coordinate for the current x-coordinate. */
170        double lowY = 0.0;
171
172        /** The final y-coordinate for the current x-coordinate. */
173        double closeY = 0.0;
174
175        /**
176         * A flag that indicates if the last (x, y) point was 'good'
177         * (non-null).
178         */
179        boolean lastPointGood;
180
181        /**
182         * Creates a new state instance.
183         *
184         * @param info  the plot rendering info.
185         */
186        public State(PlotRenderingInfo info) {
187            super(info);
188        }
189
190        /**
191         * This method is called by the {@link XYPlot} at the start of each
192         * series pass.  We reset the state for the current series.
193         *
194         * @param dataset  the dataset.
195         * @param series  the series index.
196         * @param firstItem  the first item index for this pass.
197         * @param lastItem  the last item index for this pass.
198         * @param pass  the current pass index.
199         * @param passCount  the number of passes.
200         */
201        @Override
202        public void startSeriesPass(XYDataset dataset, int series,
203                int firstItem, int lastItem, int pass, int passCount) {
204            this.seriesPath.reset();
205            this.intervalPath.reset();
206            this.lastPointGood = false;
207            super.startSeriesPass(dataset, series, firstItem, lastItem, pass,
208                    passCount);
209        }
210
211    }
212
213    /**
214     * Initialises the renderer.
215     * <P>
216     * This method will be called before the first item is rendered, giving the
217     * renderer an opportunity to initialise any state information it wants to
218     * maintain.  The renderer can do nothing if it chooses.
219     *
220     * @param g2  the graphics device.
221     * @param dataArea  the area inside the axes.
222     * @param plot  the plot.
223     * @param data  the data.
224     * @param info  an optional info collection object to return data back to
225     *              the caller.
226     *
227     * @return The renderer state.
228     */
229    @Override
230    public XYItemRendererState initialise(Graphics2D g2,
231            Rectangle2D dataArea, XYPlot plot, XYDataset data,
232            PlotRenderingInfo info) {
233
234        double dpi = 72;
235    //        Integer dpiVal = (Integer) g2.getRenderingHint(HintKey.DPI);
236    //        if (dpiVal != null) {
237    //            dpi = dpiVal.intValue();
238    //        }
239        State state = new State(info);
240        state.seriesPath = new GeneralPath();
241        state.intervalPath = new GeneralPath();
242        state.dX = 72.0 / dpi;
243        return state;
244    }
245
246    /**
247     * Draws the visual representation of a single data item.
248     *
249     * @param g2  the graphics device.
250     * @param state  the renderer state.
251     * @param dataArea  the area within which the data is being drawn.
252     * @param info  collects information about the drawing.
253     * @param plot  the plot (can be used to obtain standard color
254     *              information etc).
255     * @param domainAxis  the domain axis.
256     * @param rangeAxis  the range axis.
257     * @param dataset  the dataset.
258     * @param series  the series index (zero-based).
259     * @param item  the item index (zero-based).
260     * @param crosshairState  crosshair information for the plot
261     *                        (<code>null</code> permitted).
262     * @param pass  the pass index.
263     */
264    @Override
265    public void drawItem(Graphics2D g2, XYItemRendererState state, 
266            Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
267            ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
268            int series, int item, CrosshairState crosshairState, int pass) {
269
270        // do nothing if item is not visible
271        if (!getItemVisible(series, item)) {
272            return;
273        }
274        RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
275        RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
276
277        // get the data point...
278        double x1 = dataset.getXValue(series, item);
279        double y1 = dataset.getYValue(series, item);
280        double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
281        double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
282
283        State s = (State) state;
284        // update path to reflect latest point
285        if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) {
286            float x = (float) transX1;
287            float y = (float) transY1;
288            PlotOrientation orientation = plot.getOrientation();
289            if (orientation == PlotOrientation.HORIZONTAL) {
290                x = (float) transY1;
291                y = (float) transX1;
292            }
293            if (s.lastPointGood) {
294                if ((Math.abs(x - s.lastX) > s.dX)) {
295                    s.seriesPath.lineTo(x, y);
296                    if (s.lowY < s.highY) {
297                        s.intervalPath.moveTo((float) s.lastX, (float) s.lowY);
298                        s.intervalPath.lineTo((float) s.lastX, (float) s.highY);
299                    }
300                    s.lastX = x;
301                    s.openY = y;
302                    s.highY = y;
303                    s.lowY = y;
304                    s.closeY = y;
305                }
306                else {
307                    s.highY = Math.max(s.highY, y);
308                    s.lowY = Math.min(s.lowY, y);
309                    s.closeY = y;
310                }
311            }
312            else {
313                s.seriesPath.moveTo(x, y);
314                s.lastX = x;
315                s.openY = y;
316                s.highY = y;
317                s.lowY = y;
318                s.closeY = y;
319            }
320            s.lastPointGood = true;
321        }
322        else {
323            s.lastPointGood = false;
324        }
325        // if this is the last item, draw the path ...
326        if (item == s.getLastItemIndex()) {
327            // draw path
328            PathIterator pi = s.seriesPath.getPathIterator(null);
329            int count = 0;
330            while (!pi.isDone()) {
331                count++;
332                pi.next();
333            }
334            g2.setStroke(getItemStroke(series, item));
335            g2.setPaint(getItemPaint(series, item));
336            g2.draw(s.seriesPath);
337            g2.draw(s.intervalPath);
338        }
339    }
340
341    /**
342     * Returns a clone of the renderer.
343     *
344     * @return A clone.
345     *
346     * @throws CloneNotSupportedException if the clone cannot be created.
347     */
348    @Override
349    public Object clone() throws CloneNotSupportedException {
350        SamplingXYLineRenderer clone = (SamplingXYLineRenderer) super.clone();
351        if (this.legendLine != null) {
352            clone.legendLine = ShapeUtilities.clone(this.legendLine);
353        }
354        return clone;
355    }
356
357    /**
358     * Tests this renderer for equality with an arbitrary object.
359     *
360     * @param obj  the object (<code>null</code> permitted).
361     *
362     * @return <code>true</code> or <code>false</code>.
363     */
364    @Override
365    public boolean equals(Object obj) {
366        if (obj == this) {
367            return true;
368        }
369        if (!(obj instanceof SamplingXYLineRenderer)) {
370            return false;
371        }
372        if (!super.equals(obj)) {
373            return false;
374        }
375        SamplingXYLineRenderer that = (SamplingXYLineRenderer) obj;
376        if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) {
377            return false;
378        }
379        return true;
380    }
381
382    /**
383     * Provides serialization support.
384     *
385     * @param stream  the input stream.
386     *
387     * @throws IOException  if there is an I/O error.
388     * @throws ClassNotFoundException  if there is a classpath problem.
389     */
390    private void readObject(ObjectInputStream stream)
391            throws IOException, ClassNotFoundException {
392        stream.defaultReadObject();
393        this.legendLine = SerialUtilities.readShape(stream);
394    }
395
396    /**
397     * Provides serialization support.
398     *
399     * @param stream  the output stream.
400     *
401     * @throws IOException  if there is an I/O error.
402     */
403    private void writeObject(ObjectOutputStream stream) throws IOException {
404        stream.defaultWriteObject();
405        SerialUtilities.writeShape(this.legendLine, stream);
406    }
407
408}