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}