001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2014, 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 * XYSplineRenderer.java
029 * ---------------------
030 * (C) Copyright 2007-2014, 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 * 19-May-2009 : Fixed FindBugs warnings, patch by Michal Wozniak (DG);
043 * 14-Sep-2013 : Replaced Vector with List, general cleanup (KR);
044 * 15-Sep-2013 : Added support to fill the area 'under' (between '0' and) the 
045 *               spline(KR);
046 * 15-Sep-2013 : Replaced ControlPoint with Point2D.Float (KR);
047 *
048 */
049
050package org.jfree.chart.renderer.xy;
051
052import java.awt.GradientPaint;
053import java.awt.Graphics2D;
054import java.awt.Paint;
055import java.awt.geom.GeneralPath;
056import java.awt.geom.Point2D;
057import java.awt.geom.Rectangle2D;
058import java.util.ArrayList;
059import java.util.List;
060
061import org.jfree.chart.axis.ValueAxis;
062import org.jfree.chart.event.RendererChangeEvent;
063import org.jfree.chart.plot.PlotOrientation;
064import org.jfree.chart.plot.PlotRenderingInfo;
065import org.jfree.chart.plot.XYPlot;
066import org.jfree.chart.util.ParamChecks;
067import org.jfree.data.xy.XYDataset;
068import org.jfree.ui.GradientPaintTransformer;
069import org.jfree.ui.RectangleEdge;
070import org.jfree.ui.StandardGradientPaintTransformer;
071import org.jfree.util.ObjectUtilities;
072
073/**
074 * A renderer that connects data points with natural cubic splines and/or
075 * draws shapes at each data point.  This renderer is designed for use with
076 * the {@link XYPlot} class. The example shown here is generated by the
077 * <code>XYSplineRendererDemo1.java</code> program included in the JFreeChart
078 * demo collection:
079 * <br><br>
080 * <img src="../../../../../images/XYSplineRendererSample.png"
081 * alt="XYSplineRendererSample.png">
082 *
083 * @since 1.0.7
084 */
085public class XYSplineRenderer extends XYLineAndShapeRenderer {
086
087    /**
088     * An enumeration of the fill types for the renderer.
089     * 
090     * @since 1.0.17
091     */
092    public static enum FillType {
093        NONE,
094        TO_ZERO,
095        TO_LOWER_BOUND,
096        TO_UPPER_BOUND
097    }
098    
099    /**
100     * Represents state information that applies to a single rendering of
101     * a chart.
102     */
103    public static class XYSplineState extends State {
104        
105        /** The area to fill under the curve. */
106        public GeneralPath fillArea;
107        
108        /** The points. */
109        public List<Point2D> points;
110        
111        /**
112         * Creates a new state instance.
113         * 
114         * @param info  the plot rendering info. 
115         */
116        public XYSplineState(PlotRenderingInfo info) {
117            super(info);
118            this.fillArea = new GeneralPath();
119            this.points = new ArrayList<Point2D>();
120        }
121    }
122    
123    /**
124     * Resolution of splines (number of line segments between points)
125     */
126    private int precision;
127
128    /**
129     * A flag that can be set to specify 
130     * to fill the area under the spline.
131     */
132    private FillType fillType;
133
134    private GradientPaintTransformer gradientPaintTransformer;
135    
136    /**
137     * Creates a new instance with the precision attribute defaulting to 5 
138     * and no fill of the area 'under' the spline.
139     */
140    public XYSplineRenderer() {
141        this(5, FillType.NONE);
142    }
143
144    /**
145     * Creates a new renderer with the specified precision 
146     * and no fill of the area 'under' (between '0' and) the spline.
147     *
148     * @param precision  the number of points between data items.
149     */
150    public XYSplineRenderer(int precision) {
151        this(precision, FillType.NONE);
152    }
153
154    /**
155     * Creates a new renderer with the specified precision
156     * and specified fill of the area 'under' (between '0' and) the spline.
157     *
158     * @param precision  the number of points between data items.
159     * @param fillType  the type of fill beneath the curve (<code>null</code> 
160     *     not permitted).
161     * 
162     * @since 1.0.17
163     */
164    public XYSplineRenderer(int precision, FillType fillType) {
165        super();
166        if (precision <= 0) {
167            throw new IllegalArgumentException("Requires precision > 0.");
168        }
169        ParamChecks.nullNotPermitted(fillType, "fillType");
170        this.precision = precision;
171        this.fillType = fillType;
172        this.gradientPaintTransformer = new StandardGradientPaintTransformer();
173    }
174
175    /**
176     * Returns the number of line segments used to approximate the spline
177     * curve between data points.
178     *
179     * @return The number of line segments.
180     *
181     * @see #setPrecision(int)
182     */
183    public int getPrecision() {
184        return this.precision;
185    }
186
187    /**
188     * Set the resolution of splines and sends a {@link RendererChangeEvent}
189     * to all registered listeners.
190     *
191     * @param p  number of line segments between points (must be &gt; 0).
192     *
193     * @see #getPrecision()
194     */
195    public void setPrecision(int p) {
196        if (p <= 0) {
197            throw new IllegalArgumentException("Requires p > 0.");
198        }
199        this.precision = p;
200        fireChangeEvent();
201    }
202
203    /**
204     * Returns the type of fill that the renderer draws beneath the curve.
205     *
206     * @return The type of fill (never <code>null</code>).
207     *
208     * @see #setFillType(FillType) 
209     * 
210     * @since 1.0.17
211     */
212    public FillType getFillType() {
213        return this.fillType;
214    }
215
216    /**
217     * Set the fill type and sends a {@link RendererChangeEvent}
218     * to all registered listeners.
219     *
220     * @param fillType   the fill type (<code>null</code> not permitted).
221     *
222     * @see #getFillType()
223     * 
224     * @since 1.0.17
225     */
226    public void setFillType(FillType fillType) {
227        this.fillType = fillType;
228        fireChangeEvent();
229    }
230
231    /**
232     * Returns the gradient paint transformer, or <code>null</code>.
233     * 
234     * @return The gradient paint transformer (possibly <code>null</code>).
235     * 
236     * @since 1.0.17
237     */
238    public GradientPaintTransformer getGradientPaintTransformer() {
239        return this.gradientPaintTransformer;
240    }
241    
242    /**
243     * Sets the gradient paint transformer and sends a 
244     * {@link RendererChangeEvent} to all registered listeners.
245     * 
246     * @param gpt  the transformer (<code>null</code> permitted).
247     * 
248     * @since 1.0.17
249     */
250    public void setGradientPaintTransformer(GradientPaintTransformer gpt) {
251        this.gradientPaintTransformer = gpt;
252        fireChangeEvent();
253    }
254    
255    /**
256     * Initialises the renderer.
257     * <P>
258     * This method will be called before the first item is rendered, giving the
259     * renderer an opportunity to initialise any state information it wants to
260     * maintain.  The renderer can do nothing if it chooses.
261     *
262     * @param g2  the graphics device.
263     * @param dataArea  the area inside the axes.
264     * @param plot  the plot.
265     * @param data  the data.
266     * @param info  an optional info collection object to return data back to
267     *              the caller.
268     *
269     * @return The renderer state.
270     */
271    @Override
272    public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea,
273            XYPlot plot, XYDataset data, PlotRenderingInfo info) {
274
275        setDrawSeriesLineAsPath(true);
276        XYSplineState state = new XYSplineState(info);
277        state.setProcessVisibleItemsOnly(false);
278        return state;
279    }
280
281    /**
282     * Draws the item (first pass). This method draws the lines
283     * connecting the items. Instead of drawing separate lines,
284     * a GeneralPath is constructed and drawn at the end of
285     * the series painting.
286     *
287     * @param g2  the graphics device.
288     * @param state  the renderer state.
289     * @param plot  the plot (can be used to obtain standard color information
290     *              etc).
291     * @param dataset  the dataset.
292     * @param pass  the pass.
293     * @param series  the series index (zero-based).
294     * @param item  the item index (zero-based).
295     * @param xAxis  the domain axis.
296     * @param yAxis  the range axis.
297     * @param dataArea  the area within which the data is being drawn.
298     */
299    @Override
300    protected void drawPrimaryLineAsPath(XYItemRendererState state,
301            Graphics2D g2, XYPlot plot, XYDataset dataset, int pass,
302            int series, int item, ValueAxis xAxis, ValueAxis yAxis,
303            Rectangle2D dataArea) {
304
305        XYSplineState s = (XYSplineState) state;
306        RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
307        RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
308
309        // get the data points
310        double x1 = dataset.getXValue(series, item);
311        double y1 = dataset.getYValue(series, item);
312        double transX1 = xAxis.valueToJava2D(x1, dataArea, xAxisLocation);
313        double transY1 = yAxis.valueToJava2D(y1, dataArea, yAxisLocation);
314
315        // Collect points
316        if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) {
317            Point2D p = plot.getOrientation() == PlotOrientation.HORIZONTAL 
318                ? new Point2D.Float((float) transY1, (float) transX1) 
319                : new Point2D.Float((float) transX1, (float) transY1);
320            if (!s.points.contains(p))
321                s.points.add(p);
322        }
323        
324        if (item == dataset.getItemCount(series) - 1) {     // construct path
325            if (s.points.size() > 1) {
326                Point2D origin;
327                if (this.fillType == FillType.TO_ZERO) {
328                    float xz = (float) xAxis.valueToJava2D(0, dataArea, 
329                            yAxisLocation);
330                    float yz = (float) yAxis.valueToJava2D(0, dataArea, 
331                            yAxisLocation);
332                    origin = plot.getOrientation() == PlotOrientation.HORIZONTAL
333                            ? new Point2D.Float(yz, xz) 
334                            : new Point2D.Float(xz, yz);
335                } else if (this.fillType == FillType.TO_LOWER_BOUND) {
336                    float xlb = (float) xAxis.valueToJava2D(
337                            xAxis.getLowerBound(), dataArea, xAxisLocation);
338                    float ylb = (float) yAxis.valueToJava2D(
339                            yAxis.getLowerBound(), dataArea, yAxisLocation);
340                    origin = plot.getOrientation() == PlotOrientation.HORIZONTAL
341                            ? new Point2D.Float(ylb, xlb) 
342                            : new Point2D.Float(xlb, ylb);
343                } else {// fillType == TO_UPPER_BOUND
344                    float xub = (float) xAxis.valueToJava2D(
345                            xAxis.getUpperBound(), dataArea, xAxisLocation);
346                    float yub = (float) yAxis.valueToJava2D(
347                            yAxis.getUpperBound(), dataArea, yAxisLocation);
348                    origin = plot.getOrientation() == PlotOrientation.HORIZONTAL
349                            ? new Point2D.Float(yub, xub)
350                            : new Point2D.Float(xub, yub);
351                }
352                
353                // we need at least two points to draw something
354                Point2D cp0 = s.points.get(0);
355                s.seriesPath.moveTo(cp0.getX(), cp0.getY());
356                if (this.fillType != FillType.NONE) {
357                    if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
358                        s.fillArea.moveTo(origin.getX(), cp0.getY());
359                    } else {
360                        s.fillArea.moveTo(cp0.getX(), origin.getY());
361                    }
362                    s.fillArea.lineTo(cp0.getX(), cp0.getY());
363                }
364                if (s.points.size() == 2) {
365                    // we need at least 3 points to spline. Draw simple line
366                    // for two points
367                    Point2D cp1 = s.points.get(1);
368                    if (this.fillType != FillType.NONE) {
369                        s.fillArea.lineTo(cp1.getX(), cp1.getY());
370                        s.fillArea.lineTo(cp1.getX(), origin.getY());
371                        s.fillArea.closePath();
372                    }
373                    s.seriesPath.lineTo(cp1.getX(), cp1.getY());
374                } else {
375                    // construct spline
376                    int np = s.points.size(); // number of points
377                    float[] d = new float[np]; // Newton form coefficients
378                    float[] x = new float[np]; // x-coordinates of nodes
379                    float y, oldy;
380                    float t, oldt;
381
382                    float[] a = new float[np];
383                    float t1;
384                    float t2;
385                    float[] h = new float[np];
386
387                    for (int i = 0; i < np; i++) {
388                        Point2D.Float cpi = (Point2D.Float) s.points.get(i);
389                        x[i] = cpi.x;
390                        d[i] = cpi.y;
391                    }
392
393                    for (int i = 1; i <= np - 1; i++)
394                        h[i] = x[i] - x[i - 1];
395
396                    float[] sub = new float[np - 1];
397                    float[] diag = new float[np - 1];
398                    float[] sup = new float[np - 1];
399
400                    for (int i = 1; i <= np - 2; i++) {
401                        diag[i] = (h[i] + h[i + 1]) / 3;
402                        sup[i] = h[i + 1] / 6;
403                        sub[i] = h[i] / 6;
404                        a[i] = (d[i + 1] - d[i]) / h[i + 1]
405                                   - (d[i] - d[i - 1]) / h[i];
406                    }
407                    solveTridiag(sub, diag, sup, a, np - 2);
408
409                    // note that a[0]=a[np-1]=0
410                    oldt = x[0];
411                    oldy = d[0];
412                    for (int i = 1; i <= np - 1; i++) {
413                        // loop over intervals between nodes
414                        for (int j = 1; j <= this.precision; j++) {
415                            t1 = (h[i] * j) / this.precision;
416                            t2 = h[i] - t1;
417                            y = ((-a[i - 1] / 6 * (t2 + h[i]) * t1 + d[i - 1])
418                                    * t2 + (-a[i] / 6 * (t1 + h[i]) * t2
419                                    + d[i]) * t1) / h[i];
420                            t = x[i - 1] + t1;
421                            s.seriesPath.lineTo(t, y);
422                            if (this.fillType != FillType.NONE) {
423                                s.fillArea.lineTo(t, y);
424                            }
425                        }
426                    }
427                }
428                // Add last point @ y=0 for fillPath and close path
429                if (this.fillType != FillType.NONE) {
430                    if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
431                        s.fillArea.lineTo(origin.getX(), s.points.get(
432                                s.points.size() - 1).getY());
433                    } else {
434                        s.fillArea.lineTo(s.points.get(
435                                s.points.size() - 1).getX(), origin.getY());
436                    }
437                    s.fillArea.closePath();
438                }
439
440                // fill under the curve...
441                if (this.fillType != FillType.NONE) {
442                    Paint fp = getSeriesFillPaint(series);
443                    if (this.gradientPaintTransformer != null 
444                            && fp instanceof GradientPaint) {
445                        GradientPaint gp = this.gradientPaintTransformer
446                                .transform((GradientPaint) fp, s.fillArea);
447                        g2.setPaint(gp);
448                    } else {
449                        g2.setPaint(fp);                        
450                    }
451                    g2.fill(s.fillArea);
452                    s.fillArea.reset();
453                }
454                // then draw the line...
455                drawFirstPassShape(g2, pass, series, item, s.seriesPath);
456            }
457            // reset points vector
458            s.points = new ArrayList<Point2D>();
459        }
460    }
461    
462    private void solveTridiag(float[] sub, float[] diag, float[] sup,
463            float[] b, int n) {
464/*      solve linear system with tridiagonal n by n matrix a
465        using Gaussian elimination *without* pivoting
466        where   a(i,i-1) = sub[i]  for 2<=i<=n
467        a(i,i)   = diag[i] for 1<=i<=n
468        a(i,i+1) = sup[i]  for 1<=i<=n-1
469        (the values sub[1], sup[n] are ignored)
470        right hand side vector b[1:n] is overwritten with solution
471        NOTE: 1...n is used in all arrays, 0 is unused */
472        int i;
473/*      factorization and forward substitution */
474        for (i = 2; i <= n; i++) {
475            sub[i] /= diag[i - 1];
476            diag[i] -= sub[i] * sup[i - 1];
477            b[i] -= sub[i] * b[i - 1];
478        }
479        b[n] /= diag[n];
480        for (i = n - 1; i >= 1; i--)
481            b[i] = (b[i] - sup[i] * b[i + 1]) / diag[i];
482    }
483
484    /**
485     * Tests this renderer for equality with an arbitrary object.
486     *
487     * @param obj  the object (<code>null</code> permitted).
488     *
489     * @return A boolean.
490     */
491    @Override
492    public boolean equals(Object obj) {
493        if (obj == this) {
494            return true;
495        }
496        if (!(obj instanceof XYSplineRenderer)) {
497            return false;
498        }
499        XYSplineRenderer that = (XYSplineRenderer) obj;
500        if (this.precision != that.precision) {
501            return false;
502        }
503        if (this.fillType != that.fillType) {
504            return false;
505        }
506        if (!ObjectUtilities.equal(this.gradientPaintTransformer, 
507                that.gradientPaintTransformer)) {
508            return false;
509        }
510        return super.equals(obj);
511    }
512}