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