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}