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    public List<LegendItemInfo> getLegendInfo() {
537        List<LegendItemInfo> result = new ArrayList<LegendItemInfo>();
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        this.yAxis.configureAsYAxis(this);
683        fireChangeEvent(event.requiresWorldUpdate());
684    }
685
686    /**
687     * Receives notification that the plot's renderer has changed, and 
688     * responds by passing on a {@link Plot3DChangeEvent} to the plot's 
689     * registered listeners (with the default set-up, this notifies the 
690     * chart).
691     * 
692     * @param event  the event. 
693     */
694    @Override
695    public void rendererChanged(Renderer3DChangeEvent event) {
696        fireChangeEvent(event.requiresWorldUpdate());
697    }
698
699    /**
700     * Receives notification that the plot's dataset has changed, and 
701     * responds by passing on a {@link Plot3DChangeEvent} to the plot's 
702     * registered listeners (with the default set-up, this notifies the 
703     * chart).
704     * 
705     * @param event  the event. 
706     */
707    @Override
708    public void datasetChanged(Dataset3DChangeEvent event) {
709        this.xAxis.configureAsXAxis(this);
710        this.yAxis.configureAsYAxis(this);
711        this.zAxis.configureAsZAxis(this);
712        super.datasetChanged(event);
713    }
714    
715    /**
716     * Provides serialization support.
717     *
718     * @param stream  the output stream.
719     *
720     * @throws IOException  if there is an I/O error.
721     */
722    private void writeObject(ObjectOutputStream stream) throws IOException {
723        stream.defaultWriteObject();
724        SerialUtils.writePaint(this.gridlinePaintX, stream);
725        SerialUtils.writePaint(this.gridlinePaintY, stream);
726        SerialUtils.writePaint(this.gridlinePaintZ, stream);
727        SerialUtils.writeStroke(this.gridlineStrokeX, stream);
728        SerialUtils.writeStroke(this.gridlineStrokeY, stream);
729        SerialUtils.writeStroke(this.gridlineStrokeZ, stream);
730        
731    }
732
733    /**
734     * Provides serialization support.
735     *
736     * @param stream  the input stream.
737     *
738     * @throws IOException  if there is an I/O error.
739     * @throws ClassNotFoundException  if there is a classpath problem.
740     */
741    private void readObject(ObjectInputStream stream)
742        throws IOException, ClassNotFoundException {
743        stream.defaultReadObject();
744        this.gridlinePaintX = SerialUtils.readPaint(stream);
745        this.gridlinePaintY = SerialUtils.readPaint(stream);
746        this.gridlinePaintZ = SerialUtils.readPaint(stream);
747        this.gridlineStrokeX = SerialUtils.readStroke(stream);
748        this.gridlineStrokeY = SerialUtils.readStroke(stream);
749        this.gridlineStrokeZ = SerialUtils.readStroke(stream);
750    }
751
752}