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 * CyclicXYItemRenderer.java
029 * ---------------------------
030 * (C) Copyright 2003-2008, by Nicolas Brodu and Contributors.
031 *
032 * Original Author:  Nicolas Brodu;
033 * Contributor(s):   David Gilbert (for Object Refinery Limited);
034 *
035 * Changes
036 * -------
037 * 19-Nov-2003 : Initial import to JFreeChart from the JSynoptic project (NB);
038 * 23-Dec-2003 : Added missing Javadocs (DG);
039 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
040 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
041 *               getYValue() (DG);
042 * ------------- JFREECHART 1.0.0 ---------------------------------------------
043 * 06-Jul-2006 : Modified to call only dataset methods that return double
044 *               primitives (DG);
045 *
046 */
047
048package org.jfree.chart.renderer.xy;
049
050import java.awt.Graphics2D;
051import java.awt.geom.Rectangle2D;
052import java.io.Serializable;
053
054import org.jfree.chart.axis.CyclicNumberAxis;
055import org.jfree.chart.axis.ValueAxis;
056import org.jfree.chart.labels.XYToolTipGenerator;
057import org.jfree.chart.plot.CrosshairState;
058import org.jfree.chart.plot.PlotRenderingInfo;
059import org.jfree.chart.plot.XYPlot;
060import org.jfree.chart.urls.XYURLGenerator;
061import org.jfree.data.DomainOrder;
062import org.jfree.data.general.DatasetChangeListener;
063import org.jfree.data.general.DatasetGroup;
064import org.jfree.data.xy.XYDataset;
065
066/**
067 * The Cyclic XY item renderer is specially designed to handle cyclic axis.
068 * While the standard renderer would draw a line across the plot when a cycling
069 * occurs, the cyclic renderer splits the line at each cycle end instead. This
070 * is done by interpolating new points at cycle boundary. Thus, correct
071 * appearance is restored.
072 *
073 * The Cyclic XY item renderer works exactly like a standard XY item renderer
074 * with non-cyclic axis.
075 */
076public class CyclicXYItemRenderer extends StandardXYItemRenderer
077                                  implements Serializable {
078
079    /** For serialization. */
080    private static final long serialVersionUID = 4035912243303764892L;
081
082    /**
083     * Default constructor.
084     */
085    public CyclicXYItemRenderer() {
086        super();
087    }
088
089    /**
090     * Creates a new renderer.
091     *
092     * @param type  the renderer type.
093     */
094    public CyclicXYItemRenderer(int type) {
095        super(type);
096    }
097
098    /**
099     * Creates a new renderer.
100     *
101     * @param type  the renderer type.
102     * @param labelGenerator  the tooltip generator.
103     */
104    public CyclicXYItemRenderer(int type, XYToolTipGenerator labelGenerator) {
105        super(type, labelGenerator);
106    }
107
108    /**
109     * Creates a new renderer.
110     *
111     * @param type  the renderer type.
112     * @param labelGenerator  the tooltip generator.
113     * @param urlGenerator  the url generator.
114     */
115    public CyclicXYItemRenderer(int type,
116                                XYToolTipGenerator labelGenerator,
117                                XYURLGenerator urlGenerator) {
118        super(type, labelGenerator, urlGenerator);
119    }
120
121
122    /**
123     * Draws the visual representation of a single data item.
124     * When using cyclic axis, do not draw a line from right to left when
125     * cycling as would a standard XY item renderer, but instead draw a line
126     * from the previous point to the cycle bound in the last cycle, and a line
127     * from the cycle bound to current point in the current cycle.
128     *
129     * @param g2  the graphics device.
130     * @param state  the renderer state.
131     * @param dataArea  the data area.
132     * @param info  the plot rendering info.
133     * @param plot  the plot.
134     * @param domainAxis  the domain axis.
135     * @param rangeAxis  the range axis.
136     * @param dataset  the dataset.
137     * @param series  the series index.
138     * @param item  the item index.
139     * @param crosshairState  crosshair information for the plot
140     *                        (<code>null</code> permitted).
141     * @param pass  the current pass index.
142     */
143    @Override
144    public void drawItem(Graphics2D g2, XYItemRendererState state, 
145            Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
146            ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
147            int series, int item, CrosshairState crosshairState, int pass) {
148
149        if ((!getPlotLines()) || ((!(domainAxis instanceof CyclicNumberAxis))
150                && (!(rangeAxis instanceof CyclicNumberAxis))) || (item <= 0)) {
151            super.drawItem(g2, state, dataArea, info, plot, domainAxis,
152                    rangeAxis, dataset, series, item, crosshairState, pass);
153            return;
154        }
155
156        // get the previous data point...
157        double xn = dataset.getXValue(series, item - 1);
158        double yn = dataset.getYValue(series, item - 1);
159        // If null, don't draw line => then delegate to parent
160        if (Double.isNaN(yn)) {
161            super.drawItem(g2, state, dataArea, info, plot, domainAxis,
162                    rangeAxis, dataset, series, item, crosshairState, pass);
163            return;
164        }
165        double[] x = new double[2];
166        double[] y = new double[2];
167        x[0] = xn;
168        y[0] = yn;
169
170        // get the data point...
171        xn = dataset.getXValue(series, item);
172        yn = dataset.getYValue(series, item);
173        // If null, don't draw line at all
174        if (Double.isNaN(yn)) {
175            return;
176        }
177        x[1] = xn;
178        y[1] = yn;
179
180        // Now split the segment as needed
181        double xcycleBound = Double.NaN;
182        double ycycleBound = Double.NaN;
183        boolean xBoundMapping = false, yBoundMapping = false;
184        CyclicNumberAxis cnax = null, cnay = null;
185
186        if (domainAxis instanceof CyclicNumberAxis) {
187            cnax = (CyclicNumberAxis) domainAxis;
188            xcycleBound = cnax.getCycleBound();
189            xBoundMapping = cnax.isBoundMappedToLastCycle();
190            // If the segment must be splitted, insert a new point
191            // Strict test forces to have real segments (not 2 equal points)
192            // and avoids division by 0
193            if ((x[0] != x[1])
194                    && ((xcycleBound >= x[0])
195                    && (xcycleBound <= x[1])
196                    || (xcycleBound >= x[1])
197                    && (xcycleBound <= x[0]))) {
198                double[] nx = new double[3];
199                double[] ny = new double[3];
200                nx[0] = x[0]; nx[2] = x[1]; ny[0] = y[0]; ny[2] = y[1];
201                nx[1] = xcycleBound;
202                ny[1] = (y[1] - y[0]) * (xcycleBound - x[0])
203                        / (x[1] - x[0]) + y[0];
204                x = nx; y = ny;
205            }
206        }
207
208        if (rangeAxis instanceof CyclicNumberAxis) {
209            cnay = (CyclicNumberAxis) rangeAxis;
210            ycycleBound = cnay.getCycleBound();
211            yBoundMapping = cnay.isBoundMappedToLastCycle();
212            // The split may occur in either x splitted segments, if any, but
213            // not in both
214            if ((y[0] != y[1]) && ((ycycleBound >= y[0])
215                    && (ycycleBound <= y[1])
216                    || (ycycleBound >= y[1]) && (ycycleBound <= y[0]))) {
217                double[] nx = new double[x.length + 1];
218                double[] ny = new double[y.length + 1];
219                nx[0] = x[0]; nx[2] = x[1]; ny[0] = y[0]; ny[2] = y[1];
220                ny[1] = ycycleBound;
221                nx[1] = (x[1] - x[0]) * (ycycleBound - y[0])
222                        / (y[1] - y[0]) + x[0];
223                if (x.length == 3) {
224                    nx[3] = x[2]; ny[3] = y[2];
225                }
226                x = nx; y = ny;
227            }
228            else if ((x.length == 3) && (y[1] != y[2]) && ((ycycleBound >= y[1])
229                    && (ycycleBound <= y[2])
230                    || (ycycleBound >= y[2]) && (ycycleBound <= y[1]))) {
231                double[] nx = new double[4];
232                double[] ny = new double[4];
233                nx[0] = x[0]; nx[1] = x[1]; nx[3] = x[2];
234                ny[0] = y[0]; ny[1] = y[1]; ny[3] = y[2];
235                ny[2] = ycycleBound;
236                nx[2] = (x[2] - x[1]) * (ycycleBound - y[1])
237                        / (y[2] - y[1]) + x[1];
238                x = nx; y = ny;
239            }
240        }
241
242        // If the line is not wrapping, then parent is OK
243        if (x.length == 2) {
244            super.drawItem(g2, state, dataArea, info, plot, domainAxis,
245                    rangeAxis, dataset, series, item, crosshairState, pass);
246            return;
247        }
248
249        OverwriteDataSet newset = new OverwriteDataSet(x, y, dataset);
250
251        if (cnax != null) {
252            if (xcycleBound == x[0]) {
253                cnax.setBoundMappedToLastCycle(x[1] <= xcycleBound);
254            }
255            if (xcycleBound == x[1]) {
256                cnax.setBoundMappedToLastCycle(x[0] <= xcycleBound);
257            }
258        }
259        if (cnay != null) {
260            if (ycycleBound == y[0]) {
261                cnay.setBoundMappedToLastCycle(y[1] <= ycycleBound);
262            }
263            if (ycycleBound == y[1]) {
264                cnay.setBoundMappedToLastCycle(y[0] <= ycycleBound);
265            }
266        }
267        super.drawItem(
268            g2, state, dataArea, info, plot, domainAxis, rangeAxis,
269            newset, series, 1, crosshairState, pass
270        );
271
272        if (cnax != null) {
273            if (xcycleBound == x[1]) {
274                cnax.setBoundMappedToLastCycle(x[2] <= xcycleBound);
275            }
276            if (xcycleBound == x[2]) {
277                cnax.setBoundMappedToLastCycle(x[1] <= xcycleBound);
278            }
279        }
280        if (cnay != null) {
281            if (ycycleBound == y[1]) {
282                cnay.setBoundMappedToLastCycle(y[2] <= ycycleBound);
283            }
284            if (ycycleBound == y[2]) {
285                cnay.setBoundMappedToLastCycle(y[1] <= ycycleBound);
286            }
287        }
288        super.drawItem(g2, state, dataArea, info, plot, domainAxis, rangeAxis,
289                newset, series, 2, crosshairState, pass);
290
291        if (x.length == 4) {
292            if (cnax != null) {
293                if (xcycleBound == x[2]) {
294                    cnax.setBoundMappedToLastCycle(x[3] <= xcycleBound);
295                }
296                if (xcycleBound == x[3]) {
297                    cnax.setBoundMappedToLastCycle(x[2] <= xcycleBound);
298                }
299            }
300            if (cnay != null) {
301                if (ycycleBound == y[2]) {
302                    cnay.setBoundMappedToLastCycle(y[3] <= ycycleBound);
303                }
304                if (ycycleBound == y[3]) {
305                    cnay.setBoundMappedToLastCycle(y[2] <= ycycleBound);
306                }
307            }
308            super.drawItem(g2, state, dataArea, info, plot, domainAxis,
309                    rangeAxis, newset, series, 3, crosshairState, pass);
310        }
311
312        if (cnax != null) {
313            cnax.setBoundMappedToLastCycle(xBoundMapping);
314        }
315        if (cnay != null) {
316            cnay.setBoundMappedToLastCycle(yBoundMapping);
317        }
318    }
319
320    /**
321     * A dataset to hold the interpolated points when drawing new lines.
322     */
323    protected static class OverwriteDataSet implements XYDataset {
324
325        /** The delegate dataset. */
326        protected XYDataset delegateSet;
327
328        /** Storage for the x and y values. */
329        Double[] x, y;
330
331        /**
332         * Creates a new dataset.
333         *
334         * @param x  the x values.
335         * @param y  the y values.
336         * @param delegateSet  the dataset.
337         */
338        public OverwriteDataSet(double [] x, double[] y,
339                                XYDataset delegateSet) {
340            this.delegateSet = delegateSet;
341            this.x = new Double[x.length]; this.y = new Double[y.length];
342            for (int i = 0; i < x.length; ++i) {
343                this.x[i] = new Double(x[i]);
344                this.y[i] = new Double(y[i]);
345            }
346        }
347
348        /**
349         * Returns the order of the domain (X) values.
350         *
351         * @return The domain order.
352         */
353        @Override
354        public DomainOrder getDomainOrder() {
355            return DomainOrder.NONE;
356        }
357
358        /**
359         * Returns the number of items for the given series.
360         *
361         * @param series  the series index (zero-based).
362         *
363         * @return The item count.
364         */
365        @Override
366        public int getItemCount(int series) {
367            return this.x.length;
368        }
369
370        /**
371         * Returns the x-value.
372         *
373         * @param series  the series index (zero-based).
374         * @param item  the item index (zero-based).
375         *
376         * @return The x-value.
377         */
378        @Override
379        public Number getX(int series, int item) {
380            return this.x[item];
381        }
382
383        /**
384         * Returns the x-value (as a double primitive) for an item within a
385         * series.
386         *
387         * @param series  the series (zero-based index).
388         * @param item  the item (zero-based index).
389         *
390         * @return The x-value.
391         */
392        @Override
393        public double getXValue(int series, int item) {
394            double result = Double.NaN;
395            Number xx = getX(series, item);
396            if (xx != null) {
397                result = xx.doubleValue();
398            }
399            return result;
400        }
401
402        /**
403         * Returns the y-value.
404         *
405         * @param series  the series index (zero-based).
406         * @param item  the item index (zero-based).
407         *
408         * @return The y-value.
409         */
410        @Override
411        public Number getY(int series, int item) {
412            return this.y[item];
413        }
414
415        /**
416         * Returns the y-value (as a double primitive) for an item within a
417         * series.
418         *
419         * @param series  the series (zero-based index).
420         * @param item  the item (zero-based index).
421         *
422         * @return The y-value.
423         */
424        @Override
425        public double getYValue(int series, int item) {
426            double result = Double.NaN;
427            Number yy = getY(series, item);
428            if (yy != null) {
429                result = yy.doubleValue();
430            }
431            return result;
432        }
433
434        /**
435         * Returns the number of series in the dataset.
436         *
437         * @return The series count.
438         */
439        @Override
440        public int getSeriesCount() {
441            return this.delegateSet.getSeriesCount();
442        }
443
444        /**
445         * Returns the name of the given series.
446         *
447         * @param series  the series index (zero-based).
448         *
449         * @return The series name.
450         */
451        @Override
452        public Comparable getSeriesKey(int series) {
453            return this.delegateSet.getSeriesKey(series);
454        }
455
456        /**
457         * Returns the index of the named series, or -1.
458         *
459         * @param seriesName  the series name.
460         *
461         * @return The index.
462         */
463        @Override
464        public int indexOf(Comparable seriesName) {
465            return this.delegateSet.indexOf(seriesName);
466        }
467
468        /**
469         * Does nothing.
470         *
471         * @param listener  ignored.
472         */
473        @Override
474        public void addChangeListener(DatasetChangeListener listener) {
475            // unused in parent
476        }
477
478        /**
479         * Does nothing.
480         *
481         * @param listener  ignored.
482         */
483        @Override
484        public void removeChangeListener(DatasetChangeListener listener) {
485            // unused in parent
486        }
487
488        /**
489         * Returns the dataset group.
490         *
491         * @return The dataset group.
492         */
493        @Override
494        public DatasetGroup getGroup() {
495            // unused but must return something, so while we are at it...
496            return this.delegateSet.getGroup();
497        }
498
499        /**
500         * Does nothing.
501         *
502         * @param group  ignored.
503         */
504        @Override
505        public void setGroup(DatasetGroup group) {
506            // unused in parent
507        }
508
509    }
510
511}
512
513