001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2008, 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     * 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    
048    package org.jfree.chart.renderer.xy;
049    
050    import java.awt.Graphics2D;
051    import java.awt.geom.Rectangle2D;
052    import java.io.Serializable;
053    
054    import org.jfree.chart.axis.CyclicNumberAxis;
055    import org.jfree.chart.axis.ValueAxis;
056    import org.jfree.chart.labels.XYToolTipGenerator;
057    import org.jfree.chart.plot.CrosshairState;
058    import org.jfree.chart.plot.PlotRenderingInfo;
059    import org.jfree.chart.plot.XYPlot;
060    import org.jfree.chart.urls.XYURLGenerator;
061    import org.jfree.data.DomainOrder;
062    import org.jfree.data.general.DatasetChangeListener;
063    import org.jfree.data.general.DatasetGroup;
064    import 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     */
076    public 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        public void drawItem(Graphics2D g2,
144                             XYItemRendererState state,
145                             Rectangle2D dataArea,
146                             PlotRenderingInfo info,
147                             XYPlot plot,
148                             ValueAxis domainAxis,
149                             ValueAxis rangeAxis,
150                             XYDataset dataset,
151                             int series,
152                             int item,
153                             CrosshairState crosshairState,
154                             int pass) {
155    
156            if ((!getPlotLines()) || ((!(domainAxis instanceof CyclicNumberAxis))
157                    && (!(rangeAxis instanceof CyclicNumberAxis))) || (item <= 0)) {
158                super.drawItem(g2, state, dataArea, info, plot, domainAxis,
159                        rangeAxis, dataset, series, item, crosshairState, pass);
160                return;
161            }
162    
163            // get the previous data point...
164            double xn = dataset.getXValue(series, item - 1);
165            double yn = dataset.getYValue(series, item - 1);
166            // If null, don't draw line => then delegate to parent
167            if (Double.isNaN(yn)) {
168                super.drawItem(g2, state, dataArea, info, plot, domainAxis,
169                        rangeAxis, dataset, series, item, crosshairState, pass);
170                return;
171            }
172            double[] x = new double[2];
173            double[] y = new double[2];
174            x[0] = xn;
175            y[0] = yn;
176    
177            // get the data point...
178            xn = dataset.getXValue(series, item);
179            yn = dataset.getYValue(series, item);
180            // If null, don't draw line at all
181            if (Double.isNaN(yn)) {
182                return;
183            }
184            x[1] = xn;
185            y[1] = yn;
186    
187            // Now split the segment as needed
188            double xcycleBound = Double.NaN;
189            double ycycleBound = Double.NaN;
190            boolean xBoundMapping = false, yBoundMapping = false;
191            CyclicNumberAxis cnax = null, cnay = null;
192    
193            if (domainAxis instanceof CyclicNumberAxis) {
194                cnax = (CyclicNumberAxis) domainAxis;
195                xcycleBound = cnax.getCycleBound();
196                xBoundMapping = cnax.isBoundMappedToLastCycle();
197                // If the segment must be splitted, insert a new point
198                // Strict test forces to have real segments (not 2 equal points)
199                // and avoids division by 0
200                if ((x[0] != x[1])
201                        && ((xcycleBound >= x[0])
202                        && (xcycleBound <= x[1])
203                        || (xcycleBound >= x[1])
204                        && (xcycleBound <= x[0]))) {
205                    double[] nx = new double[3];
206                    double[] ny = new double[3];
207                    nx[0] = x[0]; nx[2] = x[1]; ny[0] = y[0]; ny[2] = y[1];
208                    nx[1] = xcycleBound;
209                    ny[1] = (y[1] - y[0]) * (xcycleBound - x[0])
210                            / (x[1] - x[0]) + y[0];
211                    x = nx; y = ny;
212                }
213            }
214    
215            if (rangeAxis instanceof CyclicNumberAxis) {
216                cnay = (CyclicNumberAxis) rangeAxis;
217                ycycleBound = cnay.getCycleBound();
218                yBoundMapping = cnay.isBoundMappedToLastCycle();
219                // The split may occur in either x splitted segments, if any, but
220                // not in both
221                if ((y[0] != y[1]) && ((ycycleBound >= y[0])
222                        && (ycycleBound <= y[1])
223                        || (ycycleBound >= y[1]) && (ycycleBound <= y[0]))) {
224                    double[] nx = new double[x.length + 1];
225                    double[] ny = new double[y.length + 1];
226                    nx[0] = x[0]; nx[2] = x[1]; ny[0] = y[0]; ny[2] = y[1];
227                    ny[1] = ycycleBound;
228                    nx[1] = (x[1] - x[0]) * (ycycleBound - y[0])
229                            / (y[1] - y[0]) + x[0];
230                    if (x.length == 3) {
231                        nx[3] = x[2]; ny[3] = y[2];
232                    }
233                    x = nx; y = ny;
234                }
235                else if ((x.length == 3) && (y[1] != y[2]) && ((ycycleBound >= y[1])
236                        && (ycycleBound <= y[2])
237                        || (ycycleBound >= y[2]) && (ycycleBound <= y[1]))) {
238                    double[] nx = new double[4];
239                    double[] ny = new double[4];
240                    nx[0] = x[0]; nx[1] = x[1]; nx[3] = x[2];
241                    ny[0] = y[0]; ny[1] = y[1]; ny[3] = y[2];
242                    ny[2] = ycycleBound;
243                    nx[2] = (x[2] - x[1]) * (ycycleBound - y[1])
244                            / (y[2] - y[1]) + x[1];
245                    x = nx; y = ny;
246                }
247            }
248    
249            // If the line is not wrapping, then parent is OK
250            if (x.length == 2) {
251                super.drawItem(g2, state, dataArea, info, plot, domainAxis,
252                        rangeAxis, dataset, series, item, crosshairState, pass);
253                return;
254            }
255    
256            OverwriteDataSet newset = new OverwriteDataSet(x, y, dataset);
257    
258            if (cnax != null) {
259                if (xcycleBound == x[0]) {
260                    cnax.setBoundMappedToLastCycle(x[1] <= xcycleBound);
261                }
262                if (xcycleBound == x[1]) {
263                    cnax.setBoundMappedToLastCycle(x[0] <= xcycleBound);
264                }
265            }
266            if (cnay != null) {
267                if (ycycleBound == y[0]) {
268                    cnay.setBoundMappedToLastCycle(y[1] <= ycycleBound);
269                }
270                if (ycycleBound == y[1]) {
271                    cnay.setBoundMappedToLastCycle(y[0] <= ycycleBound);
272                }
273            }
274            super.drawItem(
275                g2, state, dataArea, info, plot, domainAxis, rangeAxis,
276                newset, series, 1, crosshairState, pass
277            );
278    
279            if (cnax != null) {
280                if (xcycleBound == x[1]) {
281                    cnax.setBoundMappedToLastCycle(x[2] <= xcycleBound);
282                }
283                if (xcycleBound == x[2]) {
284                    cnax.setBoundMappedToLastCycle(x[1] <= xcycleBound);
285                }
286            }
287            if (cnay != null) {
288                if (ycycleBound == y[1]) {
289                    cnay.setBoundMappedToLastCycle(y[2] <= ycycleBound);
290                }
291                if (ycycleBound == y[2]) {
292                    cnay.setBoundMappedToLastCycle(y[1] <= ycycleBound);
293                }
294            }
295            super.drawItem(g2, state, dataArea, info, plot, domainAxis, rangeAxis,
296                    newset, series, 2, crosshairState, pass);
297    
298            if (x.length == 4) {
299                if (cnax != null) {
300                    if (xcycleBound == x[2]) {
301                        cnax.setBoundMappedToLastCycle(x[3] <= xcycleBound);
302                    }
303                    if (xcycleBound == x[3]) {
304                        cnax.setBoundMappedToLastCycle(x[2] <= xcycleBound);
305                    }
306                }
307                if (cnay != null) {
308                    if (ycycleBound == y[2]) {
309                        cnay.setBoundMappedToLastCycle(y[3] <= ycycleBound);
310                    }
311                    if (ycycleBound == y[3]) {
312                        cnay.setBoundMappedToLastCycle(y[2] <= ycycleBound);
313                    }
314                }
315                super.drawItem(g2, state, dataArea, info, plot, domainAxis,
316                        rangeAxis, newset, series, 3, crosshairState, pass);
317            }
318    
319            if (cnax != null) {
320                cnax.setBoundMappedToLastCycle(xBoundMapping);
321            }
322            if (cnay != null) {
323                cnay.setBoundMappedToLastCycle(yBoundMapping);
324            }
325        }
326    
327        /**
328         * A dataset to hold the interpolated points when drawing new lines.
329         */
330        protected static class OverwriteDataSet implements XYDataset {
331    
332            /** The delegate dataset. */
333            protected XYDataset delegateSet;
334    
335            /** Storage for the x and y values. */
336            Double[] x, y;
337    
338            /**
339             * Creates a new dataset.
340             *
341             * @param x  the x values.
342             * @param y  the y values.
343             * @param delegateSet  the dataset.
344             */
345            public OverwriteDataSet(double [] x, double[] y,
346                                    XYDataset delegateSet) {
347                this.delegateSet = delegateSet;
348                this.x = new Double[x.length]; this.y = new Double[y.length];
349                for (int i = 0; i < x.length; ++i) {
350                    this.x[i] = new Double(x[i]);
351                    this.y[i] = new Double(y[i]);
352                }
353            }
354    
355            /**
356             * Returns the order of the domain (X) values.
357             *
358             * @return The domain order.
359             */
360            public DomainOrder getDomainOrder() {
361                return DomainOrder.NONE;
362            }
363    
364            /**
365             * Returns the number of items for the given series.
366             *
367             * @param series  the series index (zero-based).
368             *
369             * @return The item count.
370             */
371            public int getItemCount(int series) {
372                return this.x.length;
373            }
374    
375            /**
376             * Returns the x-value.
377             *
378             * @param series  the series index (zero-based).
379             * @param item  the item index (zero-based).
380             *
381             * @return The x-value.
382             */
383            public Number getX(int series, int item) {
384                return this.x[item];
385            }
386    
387            /**
388             * Returns the x-value (as a double primitive) for an item within a
389             * series.
390             *
391             * @param series  the series (zero-based index).
392             * @param item  the item (zero-based index).
393             *
394             * @return The x-value.
395             */
396            public double getXValue(int series, int item) {
397                double result = Double.NaN;
398                Number x = getX(series, item);
399                if (x != null) {
400                    result = x.doubleValue();
401                }
402                return result;
403            }
404    
405            /**
406             * Returns the y-value.
407             *
408             * @param series  the series index (zero-based).
409             * @param item  the item index (zero-based).
410             *
411             * @return The y-value.
412             */
413            public Number getY(int series, int item) {
414                return this.y[item];
415            }
416    
417            /**
418             * Returns the y-value (as a double primitive) for an item within a
419             * series.
420             *
421             * @param series  the series (zero-based index).
422             * @param item  the item (zero-based index).
423             *
424             * @return The y-value.
425             */
426            public double getYValue(int series, int item) {
427                double result = Double.NaN;
428                Number y = getY(series, item);
429                if (y != null) {
430                    result = y.doubleValue();
431                }
432                return result;
433            }
434    
435            /**
436             * Returns the number of series in the dataset.
437             *
438             * @return The series count.
439             */
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            public Comparable getSeriesKey(int series) {
452                return this.delegateSet.getSeriesKey(series);
453            }
454    
455            /**
456             * Returns the index of the named series, or -1.
457             *
458             * @param seriesName  the series name.
459             *
460             * @return The index.
461             */
462            public int indexOf(Comparable seriesName) {
463                return this.delegateSet.indexOf(seriesName);
464            }
465    
466            /**
467             * Does nothing.
468             *
469             * @param listener  ignored.
470             */
471            public void addChangeListener(DatasetChangeListener listener) {
472                // unused in parent
473            }
474    
475            /**
476             * Does nothing.
477             *
478             * @param listener  ignored.
479             */
480            public void removeChangeListener(DatasetChangeListener listener) {
481                // unused in parent
482            }
483    
484            /**
485             * Returns the dataset group.
486             *
487             * @return The dataset group.
488             */
489            public DatasetGroup getGroup() {
490                // unused but must return something, so while we are at it...
491                return this.delegateSet.getGroup();
492            }
493    
494            /**
495             * Does nothing.
496             *
497             * @param group  ignored.
498             */
499            public void setGroup(DatasetGroup group) {
500                // unused in parent
501            }
502    
503        }
504    
505    }
506    
507