    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
    048package org.jfree.chart.renderer.xy;
    049
    050import java.awt.Graphics2D;
    051import java.awt.geom.Rectangle2D;
    052import java.io.Serializable;
    053
    054import org.jfree.chart.axis.CyclicNumberAxis;
    055import org.jfree.chart.axis.ValueAxis;
    056import org.jfree.chart.labels.XYToolTipGenerator;
    057import org.jfree.chart.plot.CrosshairState;
    058import org.jfree.chart.plot.PlotRenderingInfo;
    059import org.jfree.chart.plot.XYPlot;
    060import org.jfree.chart.urls.XYURLGenerator;
    061import org.jfree.data.DomainOrder;
    062import org.jfree.data.general.DatasetChangeListener;
    063import org.jfree.data.general.DatasetGroup;
    064import 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 */
    076public 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