001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2007, 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     * XYSplineRenderer.java
029     * ---------------------
030     * (C) Copyright 2007, by Klaus Rheinwald and Contributors.
031     *
032     * Original Author:  Klaus Rheinwald;
033     * Contributor(s):   Tobias von Petersdorff (tvp@math.umd.edu, 
034     *                       http://www.wam.umd.edu/~petersd/);
035     *                   David Gilbert (for Object Refinery Limited);
036     *
037     * Changes:
038     * --------
039     * 25-Jul-2007 : Version 1, contributed by Klaus Rheinwald (DG);
040     * 03-Aug-2007 : Added new constructor (KR);
041     * 25-Oct-2007 : Prevent duplicate control points (KR);
042     *
043     */
044    
045    
046    package org.jfree.chart.renderer.xy;
047    
048    import java.awt.Graphics2D;
049    import java.awt.geom.Rectangle2D;
050    import java.util.Vector;
051    
052    import org.jfree.chart.axis.ValueAxis;
053    import org.jfree.chart.event.RendererChangeEvent;
054    import org.jfree.chart.plot.PlotOrientation;
055    import org.jfree.chart.plot.PlotRenderingInfo;
056    import org.jfree.chart.plot.XYPlot;
057    import org.jfree.data.xy.XYDataset;
058    import org.jfree.ui.RectangleEdge;
059    
060    
061    /**
062     * A renderer that connects data points with natural cubic splines and/or 
063     * draws shapes at each data point.  This renderer is designed for use with 
064     * the {@link XYPlot} class.
065     * 
066     * @since 1.0.7
067     */
068    public class XYSplineRenderer extends XYLineAndShapeRenderer {
069    
070        /**
071         * To collect data points for later splining.
072         */
073        private Vector points;
074    
075        /**
076         * Resolution of splines (number of line segments between points)
077         */
078        private int precision;
079    
080        /**
081         * Creates a new instance with the 'precision' attribute defaulting to 
082         * 5.
083         */
084        public XYSplineRenderer() {
085            this(5);
086        }
087        
088        /**
089         * Creates a new renderer with the specified precision.
090         * 
091         * @param precision  the number of points between data items.
092         */
093        public XYSplineRenderer(int precision) {
094            super();
095            if (precision <= 0) {
096                throw new IllegalArgumentException("Requires precision > 0.");
097            }
098            this.precision = precision;
099        }    
100        
101        /**
102         * Get the resolution of splines.
103         *
104         * @return Number of line segments between points.
105         * 
106         * @see #setPrecision(int)
107         */
108        public int getPrecision() {
109            return this.precision;
110        }
111    
112        /**
113         * Set the resolution of splines and sends a {@link RendererChangeEvent}
114         * to all registered listeners.
115         *
116         * @param p  number of line segments between points (must be > 0).
117         * 
118         * @see #getPrecision()
119         */
120        public void setPrecision(int p) {
121            if (p <= 0) {
122                throw new IllegalArgumentException("Requires p > 0.");
123            }
124            this.precision = p;
125            fireChangeEvent();
126        }
127    
128        /**
129         * Initialises the renderer.
130         * <P>
131         * This method will be called before the first item is rendered, giving the
132         * renderer an opportunity to initialise any state information it wants to
133         * maintain.  The renderer can do nothing if it chooses.
134         *
135         * @param g2  the graphics device.
136         * @param dataArea  the area inside the axes.
137         * @param plot  the plot.
138         * @param data  the data.
139         * @param info  an optional info collection object to return data back to
140         *              the caller.
141         *
142         * @return The renderer state.
143         */
144        public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, 
145                XYPlot plot, XYDataset data, PlotRenderingInfo info) {
146    
147            State state = (State) super.initialise(g2, dataArea, plot, data, info);
148            state.setProcessVisibleItemsOnly(false);
149            this.points = new Vector();
150            setDrawSeriesLineAsPath(true);
151            return state;
152        }
153    
154        /**
155         * Draws the item (first pass). This method draws the lines
156         * connecting the items. Instead of drawing separate lines,
157         * a GeneralPath is constructed and drawn at the end of
158         * the series painting.
159         *
160         * @param g2  the graphics device.
161         * @param state  the renderer state.
162         * @param plot  the plot (can be used to obtain standard color information
163         *              etc).
164         * @param dataset  the dataset.
165         * @param pass  the pass.
166         * @param series  the series index (zero-based).
167         * @param item  the item index (zero-based).
168         * @param domainAxis  the domain axis.
169         * @param rangeAxis  the range axis.
170         * @param dataArea  the area within which the data is being drawn.
171         */
172        protected void drawPrimaryLineAsPath(XYItemRendererState state, 
173                Graphics2D g2, XYPlot plot, XYDataset dataset, int pass, 
174                int series, int item, ValueAxis domainAxis, ValueAxis rangeAxis, 
175                Rectangle2D dataArea) {
176    
177            RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
178            RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
179    
180            // get the data points
181            double x1 = dataset.getXValue(series, item);
182            double y1 = dataset.getYValue(series, item);
183            double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
184            double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
185    
186            // collect points
187            if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) {
188                ControlPoint p = new ControlPoint(plot.getOrientation() 
189                                    == PlotOrientation.HORIZONTAL ? (float) transY1 
190                                    : (float) transX1, plot.getOrientation() 
191                                    == PlotOrientation.HORIZONTAL ? (float) transX1 
192                                            : (float) transY1);
193                if (!this.points.contains(p)) {
194                    this.points.add(p);
195                }
196            }
197            if (item == dataset.getItemCount(series) - 1) {
198                State s = (State) state;
199                // construct path
200                if (this.points.size() > 1) {
201                    // we need at least two points to draw something
202                    ControlPoint cp0 = (ControlPoint) this.points.get(0);
203                    s.seriesPath.moveTo(cp0.x, cp0.y);
204                    if (this.points.size() == 2) {
205                        // we need at least 3 points to spline. Draw simple line 
206                        // for two points
207                        ControlPoint cp1 = (ControlPoint) this.points.get(1);
208                        s.seriesPath.lineTo(cp1.x, cp1.y);
209                    } 
210                    else {
211                        // construct spline
212                        int np = this.points.size(); // number of points
213                        float[] d = new float[np]; // Newton form coefficients
214                        float[] x = new float[np]; // x-coordinates of nodes
215                        float y;
216                        float t;
217                        float oldy = 0;
218                        float oldt = 0;
219    
220                        float[] a = new float[np];
221                        float t1;
222                        float t2;
223                        float[] h = new float[np];
224    
225                        for (int i = 0; i < np; i++) {
226                            ControlPoint cpi = (ControlPoint) this.points.get(i);
227                            x[i] = cpi.x;
228                            d[i] = cpi.y;
229                        }
230    
231                        for (int i = 1; i <= np - 1; i++) {
232                            h[i] = x[i] - x[i - 1];
233                        }
234                        float[] sub = new float[np - 1];
235                        float[] diag = new float[np - 1];
236                        float[] sup = new float[np - 1];
237    
238                        for (int i = 1; i <= np - 2; i++) {
239                            diag[i] = (h[i] + h[i + 1]) / 3;
240                            sup[i] = h[i + 1] / 6;
241                            sub[i] = h[i] / 6;
242                            a[i] = (d[i + 1] - d[i]) / h[i + 1] 
243                                       - (d[i] - d[i - 1]) / h[i];
244                        }
245                        solveTridiag(sub, diag, sup, a, np - 2);
246    
247                        // note that a[0]=a[np-1]=0
248                        // draw
249                        oldt = x[0];
250                        oldy = d[0];
251                        s.seriesPath.moveTo(oldt, oldy);
252                        for (int i = 1; i <= np - 1; i++) {
253                            // loop over intervals between nodes
254                            for (int j = 1; j <= this.precision; j++) {
255                                t1 = (h[i] * j) / this.precision;
256                                t2 = h[i] - t1;
257                                y = ((-a[i - 1] / 6 * (t2 + h[i]) * t1 + d[i - 1]) 
258                                        * t2 + (-a[i] / 6 * (t1 + h[i]) * t2 
259                                        + d[i]) * t1) / h[i];
260                                t = x[i - 1] + t1;
261                                s.seriesPath.lineTo(t, y);
262                                oldt = t;
263                                oldy = y;
264                            }
265                        }
266                    }
267                    // draw path
268                    drawFirstPassShape(g2, pass, series, item, s.seriesPath);
269                }
270    
271                // reset points vector
272                this.points = new Vector();
273            }
274        }
275    
276        /**
277         * Document me!
278         * 
279         * @param sub
280         * @param diag
281         * @param sup
282         * @param b
283         * @param n
284         */
285        private void solveTridiag(float[] sub, float[] diag, float[] sup, 
286                float[] b, int n) {
287    /*      solve linear system with tridiagonal n by n matrix a
288            using Gaussian elimination *without* pivoting
289            where   a(i,i-1) = sub[i]  for 2<=i<=n
290            a(i,i)   = diag[i] for 1<=i<=n
291            a(i,i+1) = sup[i]  for 1<=i<=n-1
292            (the values sub[1], sup[n] are ignored)
293            right hand side vector b[1:n] is overwritten with solution
294            NOTE: 1...n is used in all arrays, 0 is unused */
295            int i;
296    /*                  factorization and forward substitution */
297            for (i = 2; i <= n; i++) {
298                sub[i] = sub[i] / diag[i - 1];
299                diag[i] = diag[i] - sub[i] * sup[i - 1];
300                b[i] = b[i] - sub[i] * b[i - 1];
301            }
302            b[n] = b[n] / diag[n];
303            for (i = n - 1; i >= 1; i--) {
304                b[i] = (b[i] - sup[i] * b[i + 1]) / diag[i];
305            }
306        }
307        
308        /**
309         * Tests this renderer for equality with an arbitrary object.
310         * 
311         * @param obj  the object (<code>null</code> permitted).
312         * 
313         * @return A boolean.
314         */
315        public boolean equals(Object obj) {
316            if (obj == this) {
317                return true;
318            }
319            if (!(obj instanceof XYSplineRenderer)) {
320                return false;
321            }
322            XYSplineRenderer that = (XYSplineRenderer) obj;
323            if (this.precision != that.precision) {
324                return false;
325            }
326            return super.equals(obj);
327        }
328    
329        /**
330         * Represents a control point.
331         */
332        class ControlPoint {
333            
334            /** The x-coordinate. */
335            public float x;
336            
337            /** The y-coordinate. */
338            public float y;
339    
340            /**
341             * Creates a new control point.
342             * 
343             * @param x  the x-coordinate.
344             * @param y  the y-coordinate.
345             */
346            public ControlPoint(float x, float y) {
347                this.x = x;
348                this.y = y;
349            }
350    
351            /**
352             * Tests this point for equality with an arbitrary object.
353             * 
354             * @param obj  the object (<code>null</code> permitted.
355             * 
356             * @return A boolean.
357             */
358            public boolean equals(Object obj) {
359                if (obj == this) {
360                    return true;
361                }
362                if (!(obj instanceof ControlPoint)) {
363                    return false;
364                }
365                ControlPoint that = (ControlPoint) obj;
366                if (this.x != that.x) {
367                    return false;
368                }
369                /*&& y == ((ControlPoint) obj).y*/;
370                return true;
371            }
372    
373        }
374    }