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