    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