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