    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    }