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