    001/* ===========================================================
    002 * Orson Charts : a 3D chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C)opyright 2013-2016, by Object Refinery Limited.  All rights reserved.
    006 *
    007 * http://www.object-refinery.com/orsoncharts/index.html
    008 *
    009 * This program is free software: you can redistribute it and/or modify
    010 * it under the terms of the GNU General Public License as published by
    011 * the Free Software Foundation, either version 3 of the License, or
    012 * (at your option) any later version.
    013 *
    014 * This program is distributed in the hope that it will be useful,
    015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
    016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    017 * GNU General Public License for more details.
    018 *
    019 * You should have received a copy of the GNU General Public License
    020 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    021 *
    022 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    023 * Other names may be trademarks of their respective owners.]
    024 *
    025 * If you do not wish to be bound by the terms of the GPL, an alternative
    026 * commercial license can be purchased.  For details, please see visit the
    027 * Orson Charts home page:
    028 *
    029 * http://www.object-refinery.com/orsoncharts/index.html
    030 *
    031 */
    032
    033package com.orsoncharts.plot;
    034
    035import java.awt.BasicStroke;
    036import java.awt.Color;
    037import java.awt.Paint;
    038import java.awt.Stroke;
    039import java.io.Serializable;
    040import java.io.IOException;
    041import java.io.ObjectInputStream;
    042import java.io.ObjectOutputStream;
    043import java.util.ArrayList;
    044import java.util.List;
    045
    046import com.orsoncharts.ChartElementVisitor;
    047import com.orsoncharts.axis.Axis3DChangeEvent;
    048import com.orsoncharts.axis.Axis3DChangeListener;
    049import com.orsoncharts.axis.ValueAxis3D;
    050import com.orsoncharts.data.Dataset3DChangeEvent;
    051import com.orsoncharts.data.Dataset3DChangeListener;
    052import com.orsoncharts.data.ItemKey;
    053import com.orsoncharts.data.xyz.XYZDataset;
    054import com.orsoncharts.data.xyz.XYZItemKey;
    055import com.orsoncharts.renderer.xyz.XYZRenderer;
    056import com.orsoncharts.util.ArgChecks;
    057import com.orsoncharts.graphics3d.Dimension3D;
    058import com.orsoncharts.graphics3d.World;
    059import com.orsoncharts.label.StandardXYZLabelGenerator;
    060import com.orsoncharts.label.StandardXYZItemLabelGenerator;
    061import com.orsoncharts.label.XYZLabelGenerator;
    062import com.orsoncharts.label.XYZItemLabelGenerator;
    063import com.orsoncharts.legend.LegendItemInfo;
    064import com.orsoncharts.legend.StandardLegendItemInfo;
    065import com.orsoncharts.renderer.ComposeType;
    066import com.orsoncharts.renderer.Renderer3DChangeEvent;
    067import com.orsoncharts.renderer.Renderer3DChangeListener;
    068import com.orsoncharts.util.ObjectUtils;
    069import com.orsoncharts.util.SerialUtils;
    070
    071/**
    072 * A 3D plot with three numerical axes that displays data from an
    073 * {@link XYZDataset}.
    074 * <br><br>
    075 * NOTE: This class is serializable, but the serialization format is subject
    076 * to change in future releases and should not be relied upon for persisting
    077 * instances of this class.
    078 */
    079@SuppressWarnings("serial")
    080public class XYZPlot extends AbstractPlot3D implements Dataset3DChangeListener,
    081        Axis3DChangeListener, Renderer3DChangeListener, Serializable {
    082
    083    private static Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f,
    084            BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1f,
    085            new float[] { 3f, 3f }, 0f);
    086
    087    /** The dataset. */
    088    private XYZDataset dataset;
    089
    090    /** The renderer. */
    091    private XYZRenderer renderer;
    092
    093    /** The x-axis. */
    094    private ValueAxis3D xAxis;
    095
    096    /** The y-axis. */
    097    private ValueAxis3D yAxis;
    098
    099    /** The z-axis. */
    100    private ValueAxis3D zAxis;
    101
    102    /** Are gridlines visible for the x-axis? */
    103    private boolean gridlinesVisibleX;
    104
    105    /** The paint for the x-axis gridlines. */
    106    private transient Paint gridlinePaintX;
    107
    108    /** The stroke for the x-axis gridlines. */
    109    private transient Stroke gridlineStrokeX;
    110
    111    /** Are gridlines visible for the y-axis? */
    112    private boolean gridlinesVisibleY;
    113
    114    /** The paint for the y-axis gridlines. */
    115    private transient Paint gridlinePaintY;
    116
    117    /** The stroke for the y-axis gridlines. */
    118    private transient Stroke gridlineStrokeY;
    119
    120    /** Are gridlines visible for the z-axis? */
    121    private boolean gridlinesVisibleZ;
    122
    123    /** The paint for the z-axis gridlines. */
    124    private transient Paint gridlinePaintZ;
    125
    126    /** The stroke for the z-axis gridlines. */
    127    private transient Stroke gridlineStrokeZ;
    128
    129    /** The legend label generator. */
    130    private XYZLabelGenerator legendLabelGenerator;
    131
    132    /** The tool tip generator (if null there will be no tooltips). */
    133    private XYZItemLabelGenerator toolTipGenerator;
    134
    135    /**
    136     * Creates a new plot with the specified axes.
    137     *
    138     * @param dataset  the dataset ({@code null} not permitted).
    139     * @param renderer  the renderer ({@code null} not permitted).
    140     * @param xAxis  the x-axis ({@code null} not permitted).
    141     * @param yAxis  the y-axis ({@code null} not permitted).
    142     * @param zAxis  the z-axis ({@code null} not permitted).
    143     */
    144    public XYZPlot(XYZDataset dataset, XYZRenderer renderer, ValueAxis3D xAxis,
    145            ValueAxis3D yAxis, ValueAxis3D zAxis) {
    146        ArgChecks.nullNotPermitted(dataset, "dataset");
    147        ArgChecks.nullNotPermitted(renderer, "renderer");
    148        ArgChecks.nullNotPermitted(xAxis, "xAxis");
    149        ArgChecks.nullNotPermitted(yAxis, "yAxis");
    150        ArgChecks.nullNotPermitted(zAxis, "zAxis");
    151        this.dimensions = new Dimension3D(10, 10, 10);
    152        this.dataset = dataset;
    153        this.dataset.addChangeListener(this);
    154        this.renderer = renderer;
    155        this.renderer.setPlot(this);
    156        this.renderer.addChangeListener(this);
    157        this.xAxis = xAxis;
    158        this.xAxis.addChangeListener(this);
    159        this.xAxis.configureAsXAxis(this);
    160        this.zAxis = zAxis;
    161        this.zAxis.addChangeListener(this);
    162        this.zAxis.configureAsZAxis(this);
    163        this.yAxis = yAxis;
    164        this.yAxis.addChangeListener(this);
    165        this.yAxis.configureAsYAxis(this);
    166        this.gridlinesVisibleX = true;
    167        this.gridlinePaintX = Color.WHITE;
    168        this.gridlineStrokeX = DEFAULT_GRIDLINE_STROKE;
    169        this.gridlinesVisibleY = true;
    170        this.gridlinePaintY = Color.WHITE;
    171        this.gridlineStrokeY = DEFAULT_GRIDLINE_STROKE;
    172        this.gridlinesVisibleZ = true;
    173        this.gridlinePaintZ = Color.WHITE;
    174        this.gridlineStrokeZ = DEFAULT_GRIDLINE_STROKE;
    175        this.legendLabelGenerator = new StandardXYZLabelGenerator();
    176        this.toolTipGenerator = new StandardXYZItemLabelGenerator();
    177    }
    178
    179    /**
    180     * Sets the dimensions for the plot and notifies registered listeners that
    181     * the plot dimensions have been changed.
    182     *
    183     * @param dim  the new dimensions ({@code null} not permitted).
    184     */
    185    public void setDimensions(Dimension3D dim) {
    186        ArgChecks.nullNotPermitted(dim, "dim");
    187        this.dimensions = dim;
    188        fireChangeEvent(true);
    189    }
    190
    191    /**
    192     * Returns the dataset for the plot.
    193     *
    194     * @return The dataset (never {@code null}).
    195     */
    196    public XYZDataset getDataset() {
    197        return this.dataset;
    198    }
    199
    200    /**
    201     * Sets the dataset and sends a change event notification to all registered
    202     * listeners.
    203     *
    204     * @param dataset  the new dataset ({@code null} not permitted).
    205     */
    206    public void setDataset(XYZDataset dataset) {
    207        ArgChecks.nullNotPermitted(dataset, "dataset");
    208        this.dataset.removeChangeListener(this);
    209        this.dataset = dataset;
    210        this.dataset.addChangeListener(this);
    211        fireChangeEvent(true);
    212    }
    213
    214    /**
    215     * Returns the x-axis.
    216     *
    217     * @return The x-axis (never {@code null}).
    218     */
    219    public ValueAxis3D getXAxis() {
    220        return this.xAxis;
    221    }
    222
    223    /**
    224     * Sets the x-axis and sends a {@link Plot3DChangeEvent} to all registered
    225     * listeners.
    226     *
    227     * @param xAxis  the x-axis ({@code null} not permitted).
    228     */
    229    public void setXAxis(ValueAxis3D xAxis) {
    230        ArgChecks.nullNotPermitted(xAxis, "xAxis");
    231        this.xAxis.removeChangeListener(this);
    232        xAxis.configureAsXAxis(this);
    233        xAxis.addChangeListener(this);
    234        this.xAxis = xAxis;
    235        fireChangeEvent(true);
    236    }
    237
    238    /**
    239     * Returns the y-axis.
    240     *
    241     * @return The y-axis (never {@code null}).
    242     */
    243    public ValueAxis3D getYAxis() {
    244        return this.yAxis;
    245    }
    246
    247    /**
    248     * Sets the y-axis and sends a {@link Plot3DChangeEvent} to all registered
    249     * listeners.
    250     *
    251     * @param yAxis  the y-axis ({@code null} not permitted).
    252     */
    253    public void setYAxis(ValueAxis3D yAxis) {
    254        ArgChecks.nullNotPermitted(yAxis, "yAxis");
    255        this.yAxis.removeChangeListener(this);
    256        yAxis.configureAsYAxis(this);
    257        yAxis.addChangeListener(this);
    258        this.yAxis = yAxis;
    259        fireChangeEvent(true);
    260    }
    261
    262    /**
    263     * Returns the z-axis.
    264     *
    265     * @return The z-axis (never {@code null}).
    266     */
    267    public ValueAxis3D getZAxis() {
    268        return this.zAxis;
    269    }
    270
    271    /**
    272     * Sets the z-axis and sends a {@link Plot3DChangeEvent} to all registered
    273     * listeners.
    274     *
    275     * @param zAxis  the z-axis ({@code null} not permitted).
    276     */
    277    public void setZAxis(ValueAxis3D zAxis) {
    278        ArgChecks.nullNotPermitted(zAxis, "zAxis");
    279        this.zAxis.removeChangeListener(this);
    280        zAxis.configureAsZAxis(this);
    281        zAxis.addChangeListener(this);
    282        this.zAxis = zAxis;
    283        fireChangeEvent(true);
    284    }
    285
    286    /**
    287     * Returns the renderer for the plot.
    288     *
    289     * @return The renderer (possibly {@code null}).
    290     */
    291    public XYZRenderer getRenderer() {
    292        return this.renderer;
    293    }
    294
    295    /**
    296     * Sets the renderer for the plot and sends a {@link Plot3DChangeEvent}
    297     * to all registered listeners.
    298     *
    299     * @param renderer  the renderer ({@code null} not permitted).
    300     */
    301    public void setRenderer(XYZRenderer renderer) {
    302        this.renderer.setPlot(null);
    303        this.renderer.removeChangeListener(this);
    304        this.renderer = renderer;
    305        this.renderer.setPlot(this);
    306        this.renderer.addChangeListener(this);
    307        fireChangeEvent(true);
    308    }
    309
    310    /**
    311     * Returns the flag that controls whether or not gridlines are shown for
    312     * the x-axis.
    313     *
    314     * @return A boolean.
    315     */
    316    public boolean isGridlinesVisibleX() {
    317        return this.gridlinesVisibleX;
    318    }
    319
    320    /**
    321     * Sets the flag that controls whether or not gridlines are shown for the
    322     * x-axis and sends a {@link Plot3DChangeEvent} to all registered
    323     * listeners.
    324     *
    325     * @param visible  the new flag value.
    326     */
    327    public void setGridlinesVisibleX(boolean visible) {
    328        this.gridlinesVisibleX = visible;
    329        fireChangeEvent(false);
    330    }
    331
    332    /**
    333     * Returns the paint used to draw the gridlines for the x-axis.
    334     *
    335     * @return The paint ({@code null} not permitted).
    336     */
    337    public Paint getGridlinePaintX() {
    338        return this.gridlinePaintX;
    339    }
    340
    341    /**
    342     * Sets the paint used to draw the gridlines for the x-axis, and sends
    343     * a {@link Plot3DChangeEvent} to all registered listeners.
    344     *
    345     * @param paint  the paint ({@code null} not permitted).
    346     */
    347    public void setGridlinePaintX(Paint paint) {
    348        ArgChecks.nullNotPermitted(paint, "paint");
    349        this.gridlinePaintX = paint;
    350        fireChangeEvent(false);
    351    }
    352
    353    /**
    354     * Returns the stroke used to draw the gridlines for the x-axis.
    355     *
    356     * @return The stroke ({@code null} not permitted).
    357     */
    358    public Stroke getGridlineStrokeX() {
    359        return this.gridlineStrokeX;
    360    }
    361
    362    /**
    363     * Sets the stroke used to draw the gridlines for the x-axis, and sends
    364     * a {@link Plot3DChangeEvent} to all registered listeners.
    365     *
    366     * @param stroke  the stroke ({@code null} not permitted).
    367     */
    368    public void setGridlineStrokeX(Stroke stroke) {
    369        ArgChecks.nullNotPermitted(stroke, "stroke");
    370        this.gridlineStrokeX = stroke;
    371        fireChangeEvent(false);
    372    }
    373
    374    /**
    375     * Returns the flag that controls whether or not gridlines are shown for
    376     * the y-axis.
    377     *
    378     * @return A boolean.
    379     */
    380    public boolean isGridlinesVisibleY() {
    381        return this.gridlinesVisibleY;
    382    }
    383
    384    /**
    385     * Sets the flag that controls whether or not gridlines are shown for the
    386     * y-axis and sends a {@link Plot3DChangeEvent} to all registered
    387     * listeners.
    388     *
    389     * @param visible  the new flag value.
    390     */
    391    public void setGridlinesVisibleY(boolean visible) {
    392        this.gridlinesVisibleY = visible;
    393        fireChangeEvent(false);
    394    }
    395
    396    /**
    397     * Returns the paint used to draw the gridlines for the y-axis.
    398     *
    399     * @return The paint ({@code null} not permitted).
    400     */
    401    public Paint getGridlinePaintY() {
    402        return this.gridlinePaintY;
    403    }
    404
    405    /**
    406     * Sets the paint used to draw the gridlines for the y-axis, and sends
    407     * a {@link Plot3DChangeEvent} to all registered listeners.
    408     *
    409     * @param paint  the paint ({@code null} not permitted).
    410     */
    411    public void setGridlinePaintY(Paint paint) {
    412        ArgChecks.nullNotPermitted(paint, "paint");
    413        this.gridlinePaintY = paint;
    414        fireChangeEvent(false);
    415    }
    416
    417    /**
    418     * Returns the stroke used to draw the gridlines for the y-axis.
    419     *
    420     * @return The stroke ({@code null} not permitted).
    421     */
    422    public Stroke getGridlineStrokeY() {
    423        return this.gridlineStrokeY;
    424    }
    425
    426    /**
    427     * Sets the stroke used to draw the gridlines for the y-axis, and sends
    428     * a {@link Plot3DChangeEvent} to all registered listeners.
    429     *
    430     * @param stroke  the stroke ({@code null} not permitted).
    431     */
    432    public void setGridlineStrokeY(Stroke stroke) {
    433        ArgChecks.nullNotPermitted(stroke, "stroke");
    434        this.gridlineStrokeY = stroke;
    435        fireChangeEvent(false);
    436    }
    437
    438    /**
    439     * Returns the flag that controls whether or not gridlines are shown for
    440     * the z-axis.
    441     *
    442     * @return A boolean.
    443     */
    444    public boolean isGridlinesVisibleZ() {
    445        return this.gridlinesVisibleZ;
    446    }
    447
    448    /**
    449     * Sets the flag that controls whether or not gridlines are shown for the
    450     * z-axis and sends a {@link Plot3DChangeEvent} to all registered
    451     * listeners.
    452     *
    453     * @param visible  the new flag value.
    454     */
    455    public void setGridlinesVisibleZ(boolean visible) {
    456        this.gridlinesVisibleZ = visible;
    457        fireChangeEvent(false);
    458    }
    459
    460    /**
    461     * Returns the paint used to draw the gridlines for the z-axis.
    462     *
    463     * @return The paint ({@code null} not permitted).
    464     */
    465    public Paint getGridlinePaintZ() {
    466        return this.gridlinePaintZ;
    467    }
    468
    469    /**
    470     * Sets the paint used to draw the gridlines for the z-axis, and sends
    471     * a {@link Plot3DChangeEvent} to all registered listeners.
    472     *
    473     * @param paint  the paint ({@code null} not permitted).
    474     */
    475    public void setGridlinePaintZ(Paint paint) {
    476        ArgChecks.nullNotPermitted(paint, "paint");
    477        this.gridlinePaintZ = paint;
    478        fireChangeEvent(false);
    479    }
    480
    481    /**
    482     * Returns the stroke used to draw the gridlines for the z-axis.
    483     *
    484     * @return The stroke ({@code null} not permitted).
    485     */
    486    public Stroke getGridlineStrokeZ() {
    487        return this.gridlineStrokeZ;
    488    }
    489
    490    /**
    491     * Sets the stroke used to draw the gridlines for the z-axis, and sends
    492     * a {@link Plot3DChangeEvent} to all registered listeners.
    493     *
    494     * @param stroke  the stroke ({@code null} not permitted).
    495     */
    496    public void setGridlineStrokeZ(Stroke stroke) {
    497        ArgChecks.nullNotPermitted(stroke, "stroke");
    498        this.gridlineStrokeZ = stroke;
    499        fireChangeEvent(false);
    500    }
    501
    502    /**
    503     * Returns the legend label generator.  The default value is a default
    504     * instance of {@link StandardXYZLabelGenerator}.
    505     *
    506     * @return The legend label generator (never {@code null}).
    507     *
    508     * @since 1.2
    509     */
    510    public XYZLabelGenerator getLegendLabelGenerator() {
    511        return this.legendLabelGenerator;
    512    }
    513
    514    /**
    515     * Sets the legend label generator and sends a {@link Plot3DChangeEvent}
    516     * to all registered listeners.
    517     *
    518     * @param generator  the generator ({@code null} not permitted).
    519     *
    520     * @since 1.2
    521     */
    522    public void setLegendLabelGenerator(XYZLabelGenerator generator) {
    523        ArgChecks.nullNotPermitted(generator, "generator");
    524        this.legendLabelGenerator = generator;
    525        fireChangeEvent(false);
    526    }
    527
    528    /**
    529     * Returns a list containing legend item info, typically one item for
    530     * each series in the chart.  This is intended for use in the construction
    531     * of a chart legend.
    532     *
    533     * @return A list containing legend item info.
    534     */
    535    @Override
    536    @SuppressWarnings("unchecked") // we don't know the generic types of the dataset
    537    public List<LegendItemInfo> getLegendInfo() {
    538        List<LegendItemInfo> result = new ArrayList<LegendItemInfo>();
    539        List<Comparable<?>> keys = this.dataset.getSeriesKeys();
    540        for (Comparable key : keys) {
    541            String label = this.legendLabelGenerator.generateSeriesLabel(
    542                    this.dataset, (Comparable) key);
    543            int series = this.dataset.getSeriesIndex(key);
    544            Color color = this.renderer.getColorSource().getLegendColor(series);
    545            LegendItemInfo info = new StandardLegendItemInfo(key, label, color);
    546            result.add(info);
    547        }
    548        return result;
    549    }
    550
    551    /**
    552     * Adds 3D objects representing the current data for the plot to the
    553     * specified world.  After the world has been populated (or constructed) in
    554     * this way, it is ready for rendering.
    555     *
    556     * @param world  the world ({@code null} not permitted).
    557     * @param xOffset  the x-offset.
    558     * @param yOffset  the y-offset.
    559     * @param zOffset  the z-offset.
    560     */
    561    @Override
    562    public void compose(World world, double xOffset, double yOffset,
    563            double zOffset) {
    564        if (this.renderer.getComposeType() == ComposeType.ALL) {
    565            this.renderer.composeAll(this, world, this.dimensions, xOffset,
    566                    yOffset, zOffset);
    567        } else if (this.renderer.getComposeType() == ComposeType.PER_ITEM) {
    568            // for each data point in the dataset figure out if the composed
    569            // shape intersects with the visible
    570            // subset of the world, and if so add the object
    571            int seriesCount = this.dataset.getSeriesCount();
    572            for (int series = 0; series < seriesCount; series++) {
    573                int itemCount = this.dataset.getItemCount(series);
    574                for (int item = 0; item < itemCount; item++) {
    575                    this.renderer.composeItem(this.dataset, series, item, world,
    576                            this.dimensions, xOffset, yOffset, zOffset);
    577                }
    578            }
    579        } else {
    580            // if we get here, someone changed the ComposeType enum
    581            throw new IllegalStateException("ComposeType not expected: "
    582                    + this.renderer.getComposeType());
    583        }
    584    }
    585
    586    @Override
    587    public String generateToolTipText(ItemKey itemKey) {
    588        if (!(itemKey instanceof XYZItemKey)) {
    589            throw new IllegalArgumentException(
    590                    "The itemKey must be a XYZItemKey instance.");
    591        }
    592        if (this.toolTipGenerator == null) {
    593            return null;
    594        }
    595        XYZItemKey k = (XYZItemKey) itemKey;
    596        return this.toolTipGenerator.generateItemLabel(dataset,
    597                k.getSeriesKey(), k.getItemIndex());
    598    }
    599
    600    /**
    601     * Receives a visitor.  This is a general purpose mechanism, but the main
    602     * use is to apply chart style changes across all the elements of a
    603     * chart.
    604     *
    605     * @param visitor  the visitor ({@code null} not permitted).
    606     *
    607     * @since 1.2
    608     */
    609    @Override
    610    public void receive(ChartElementVisitor visitor) {
    611        this.xAxis.receive(visitor);
    612        this.yAxis.receive(visitor);
    613        this.zAxis.receive(visitor);
    614        this.renderer.receive(visitor);
    615        visitor.visit(this);
    616    }
    617
    618    /**
    619     * Tests this plot instance for equality with an arbitrary object.
    620     *
    621     * @param obj  the object ({@code null} permitted).
    622     *
    623     * @return A boolean.
    624     */
    625    @Override
    626    public boolean equals(Object obj) {
    627        if (obj == this) {
    628            return true;
    629        }
    630        if (!(obj instanceof XYZPlot)) {
    631            return false;
    632        }
    633        XYZPlot that = (XYZPlot) obj;
    634        if (!this.dimensions.equals(that.dimensions)) {
    635            return false;
    636        }
    637        if (this.gridlinesVisibleX != that.gridlinesVisibleX) {
    638            return false;
    639        }
    640        if (this.gridlinesVisibleY != that.gridlinesVisibleY) {
    641            return false;
    642        }
    643        if (this.gridlinesVisibleZ != that.gridlinesVisibleZ) {
    644            return false;
    645        }
    646        if (!ObjectUtils.equalsPaint(this.gridlinePaintX,
    647                that.gridlinePaintX)) {
    648            return false;
    649        }
    650        if (!ObjectUtils.equalsPaint(this.gridlinePaintY,
    651                that.gridlinePaintY)) {
    652            return false;
    653        }
    654        if (!ObjectUtils.equalsPaint(this.gridlinePaintZ,
    655                that.gridlinePaintZ)) {
    656            return false;
    657        }
    658        if (!this.gridlineStrokeX.equals(that.gridlineStrokeX)) {
    659            return false;
    660        }
    661        if (!this.gridlineStrokeY.equals(that.gridlineStrokeY)) {
    662            return false;
    663        }
    664        if (!this.gridlineStrokeZ.equals(that.gridlineStrokeZ)) {
    665            return false;
    666        }
    667        if (!this.legendLabelGenerator.equals(that.legendLabelGenerator)) {
    668            return false;
    669        }
    670        return super.equals(obj);
    671    }
    672
    673    /**
    674     * Receives notification that one of the plot's axes has changed, and
    675     * responds by passing on a {@link Plot3DChangeEvent} to the plot's
    676     * registered listeners (with the default set-up, this notifies the
    677     * chart).
    678     *
    679     * @param event  the event.
    680     */
    681    @Override
    682    public void axisChanged(Axis3DChangeEvent event) {
    683        this.yAxis.configureAsYAxis(this);
    684        fireChangeEvent(event.requiresWorldUpdate());
    685    }
    686
    687    /**
    688     * Receives notification that the plot's renderer has changed, and
    689     * responds by passing on a {@link Plot3DChangeEvent} to the plot's
    690     * registered listeners (with the default set-up, this notifies the
    691     * chart).
    692     *
    693     * @param event  the event.
    694     */
    695    @Override
    696    public void rendererChanged(Renderer3DChangeEvent event) {
    697        fireChangeEvent(event.requiresWorldUpdate());
    698    }
    699
    700    /**
    701     * Receives notification that the plot's dataset has changed, and
    702     * responds by passing on a {@link Plot3DChangeEvent} to the plot's
    703     * registered listeners (with the default set-up, this notifies the
    704     * chart).
    705     *
    706     * @param event  the event.
    707     */
    708    @Override
    709    public void datasetChanged(Dataset3DChangeEvent event) {
    710        this.xAxis.configureAsXAxis(this);
    711        this.yAxis.configureAsYAxis(this);
    712        this.zAxis.configureAsZAxis(this);
    713        super.datasetChanged(event);
    714    }
    715
    716    /**
    717     * Provides serialization support.
    718     *
    719     * @param stream  the output stream.
    720     *
    721     * @throws IOException  if there is an I/O error.
    722     */
    723    private void writeObject(ObjectOutputStream stream) throws IOException {
    724        stream.defaultWriteObject();
    725        SerialUtils.writePaint(this.gridlinePaintX, stream);
    726        SerialUtils.writePaint(this.gridlinePaintY, stream);
    727        SerialUtils.writePaint(this.gridlinePaintZ, stream);
    728        SerialUtils.writeStroke(this.gridlineStrokeX, stream);
    729        SerialUtils.writeStroke(this.gridlineStrokeY, stream);
    730        SerialUtils.writeStroke(this.gridlineStrokeZ, stream);
    731
    732    }
    733
    734    /**
    735     * Provides serialization support.
    736     *
    737     * @param stream  the input stream.
    738     *
    739     * @throws IOException  if there is an I/O error.
    740     * @throws ClassNotFoundException  if there is a classpath problem.
    741     */
    742    private void readObject(ObjectInputStream stream)
    743        throws IOException, ClassNotFoundException {
    744        stream.defaultReadObject();
    745        this.gridlinePaintX = SerialUtils.readPaint(stream);
    746        this.gridlinePaintY = SerialUtils.readPaint(stream);
    747        this.gridlinePaintZ = SerialUtils.readPaint(stream);
    748        this.gridlineStrokeX = SerialUtils.readStroke(stream);
    749        this.gridlineStrokeY = SerialUtils.readStroke(stream);
    750        this.gridlineStrokeZ = SerialUtils.readStroke(stream);
    751    }
    752
    753}