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