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