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.IOException;
040import java.io.ObjectInputStream;
041import java.io.ObjectOutputStream;
042import java.io.Serializable;
043import java.util.ArrayList;
044import java.util.List;
045
046import com.orsoncharts.Chart3D;
047import com.orsoncharts.ChartElementVisitor;
048import com.orsoncharts.axis.Axis3DChangeEvent;
049import com.orsoncharts.axis.Axis3DChangeListener;
050import com.orsoncharts.axis.CategoryAxis3D;
051import com.orsoncharts.axis.ValueAxis3D;
052import com.orsoncharts.data.Dataset3DChangeEvent;
053import com.orsoncharts.data.ItemKey;
054import com.orsoncharts.data.KeyedValues3DItemKey;
055import com.orsoncharts.data.category.CategoryDataset3D;
056import com.orsoncharts.graphics3d.Dimension3D;
057import com.orsoncharts.graphics3d.World;
058import com.orsoncharts.label.CategoryLabelGenerator;
059import com.orsoncharts.label.CategoryItemLabelGenerator;
060import com.orsoncharts.label.StandardCategoryLabelGenerator;
061import com.orsoncharts.label.StandardCategoryItemLabelGenerator;
062import com.orsoncharts.legend.LegendItemInfo;
063import com.orsoncharts.legend.StandardLegendItemInfo;
064import com.orsoncharts.renderer.Renderer3DChangeEvent;
065import com.orsoncharts.renderer.Renderer3DChangeListener;
066import com.orsoncharts.renderer.category.CategoryRenderer3D;
067import com.orsoncharts.util.ArgChecks;
068import com.orsoncharts.util.ObjectUtils;
069import com.orsoncharts.util.SerialUtils;
070
071/**
072 * A 3D plot with two category axes (x and z) and a numerical y-axis that can
073 * display data from a {@link CategoryDataset3D}.
074 * <br><br>
075 * The plot implements several listener interfaces so that it can receive 
076 * notification of changes to its dataset, axes and renderer.  When change
077 * events are received, the plot passes on a {@link Plot3DChangeEvent} to the
078 * {@link Chart3D} instance that owns the plot.  This event chain is the 
079 * mechanism that ensures that charts are repainted whenever the dataset 
080 * changes, or when changes are made to the configuration of any chart 
081 * component.
082 * <br><br>
083 * NOTE: This class is serializable, but the serialization format is subject 
084 * to change in future releases and should not be relied upon for persisting 
085 * instances of this class. 
086 */
087@SuppressWarnings("serial")
088public class CategoryPlot3D extends AbstractPlot3D 
089        implements Axis3DChangeListener, Renderer3DChangeListener, 
090        Serializable {
091
092    private static Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f, 
093            BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1f, 
094            new float[] { 3f, 3f }, 0f);
095
096    /** The dataset. */
097    private CategoryDataset3D dataset;
098    
099    /** The renderer (never {@code null}). */
100    private CategoryRenderer3D renderer;
101
102    /** The row axis. */
103    private CategoryAxis3D rowAxis;
104    
105    /** The column axis. */
106    private CategoryAxis3D columnAxis;
107    
108    /** The value axis. */
109    private ValueAxis3D valueAxis;
110
111    /** Are gridlines shown for the row (z) axis? */
112    private boolean gridlinesVisibleForRows;
113    
114    /** The paint for the row axis gridlines (never {@code null}). */
115    private transient Paint gridlinePaintForRows;
116    
117    /** The stroke for the row axis gridlines (never {@code null}). */
118    private transient Stroke gridlineStrokeForRows;
119
120    /** Are gridlines shown for the column (x) axis? */
121    private boolean gridlinesVisibleForColumns;
122    
123    /** The paint for the column axis gridlines (never {@code null}). */
124    private transient Paint gridlinePaintForColumns;
125    
126    /** The stroke for the column axis gridlines (never {@code null}). */
127    private transient Stroke gridlineStrokeForColumns;
128
129    /** Are gridlines shown for the value axis? */
130    private boolean gridlinesVisibleForValues;
131    
132    /** The paint for the value axis gridlines (never {@code null}). */
133    private transient Paint gridlinePaintForValues;
134
135    /** The stroke for the value axis gridlines (never {@code null}). */
136    private transient Stroke gridlineStrokeForValues;
137    
138    /** The legend label generator. */
139    private CategoryLabelGenerator legendLabelGenerator;
140
141    /** 
142     * A special attribute to provide control over the y-dimension for the
143     * plot when the plot dimensions are auto-calculated.  The default value
144     * is {@code null}.
145     * 
146     * @since 1.2
147     */
148    private Double yDimensionOverride;
149    
150    /** 
151     * The tool tip generator (if null there will be no tool tips).
152     * 
153     * @since 1.3
154     */
155    private CategoryItemLabelGenerator toolTipGenerator;
156    
157    /**
158     * Creates a new plot with the supplied dataset, renderer and axes.
159     * 
160     * @param dataset  the dataset ({@code null} not permitted). 
161     * @param renderer  the renderer ({@code null} not permitted).
162     * @param rowAxis  the row axis ({@code null} not permitted).
163     * @param columnAxis  the column axis ({@code null} not permitted).
164     * @param valueAxis  the value axis ({@code null} not permitted).
165     */
166    public CategoryPlot3D(CategoryDataset3D dataset, 
167            CategoryRenderer3D renderer, CategoryAxis3D rowAxis, 
168            CategoryAxis3D columnAxis, ValueAxis3D valueAxis) {
169        ArgChecks.nullNotPermitted(dataset, "dataset");
170        ArgChecks.nullNotPermitted(renderer, "renderer");
171        ArgChecks.nullNotPermitted(rowAxis, "rowAxis");
172        ArgChecks.nullNotPermitted(columnAxis, "columnAxis");
173        ArgChecks.nullNotPermitted(valueAxis, "valueAxis");
174        this.dataset = dataset;
175        this.dataset.addChangeListener(this);
176        this.dimensions = calculateDimensions();
177        this.renderer = renderer;
178        this.renderer.setPlot(this);
179        this.renderer.addChangeListener(this);
180        this.rowAxis = rowAxis;
181        this.rowAxis.addChangeListener(this);
182        this.columnAxis = columnAxis;
183        this.columnAxis.addChangeListener(this);
184        this.valueAxis = valueAxis;
185        this.valueAxis.addChangeListener(this);
186        this.rowAxis.configureAsRowAxis(this);
187        this.columnAxis.configureAsColumnAxis(this);
188        this.valueAxis.configureAsValueAxis(this);
189        this.gridlinesVisibleForValues = true;
190        this.gridlinesVisibleForColumns = false;
191        this.gridlinesVisibleForRows = false;
192        this.gridlinePaintForRows = Color.WHITE;
193        this.gridlinePaintForColumns = Color.WHITE;
194        this.gridlinePaintForValues = Color.WHITE;
195        this.gridlineStrokeForRows = DEFAULT_GRIDLINE_STROKE;
196        this.gridlineStrokeForColumns = DEFAULT_GRIDLINE_STROKE;
197        this.gridlineStrokeForValues = DEFAULT_GRIDLINE_STROKE;
198        this.legendLabelGenerator = new StandardCategoryLabelGenerator();
199        this.yDimensionOverride = null;
200        this.toolTipGenerator = new StandardCategoryItemLabelGenerator();
201    }
202    
203    /**
204     * Sets the flag that controls whether the plot's dimensions are 
205     * automatically calculated and, if {@code true}, sends a change
206     * event to all registered listeners.
207     * 
208     * @param auto  the new flag value.
209     * 
210     * @since 1.2
211     */
212    public void setAutoAdjustDimensions(boolean auto) {
213        this.autoAdjustDimensions = auto;
214        if (auto) {
215            this.dimensions = calculateDimensions();
216            fireChangeEvent(true);
217        }
218    }
219    
220    /**
221     * Sets the dimensions (in 3D space) for the plot, resets the 
222     * {@code autoAdjustDimensions} flag to {@code false}, and sends
223     * a {@link Plot3DChangeEvent} to all registered listeners.
224     * 
225     * @param dimensions  the dimensions ({@code null} not permitted).
226     * 
227     * @see Plot3D#getDimensions() 
228     */
229    public void setDimensions(Dimension3D dimensions) {
230        ArgChecks.nullNotPermitted(dimensions, "dimensions");
231        this.dimensions = dimensions;
232        this.autoAdjustDimensions = false;
233        fireChangeEvent(true);
234    }
235
236    /**
237     * Returns the dataset for the chart.
238     * 
239     * @return The dataset (never {@code null}). 
240     */
241    public CategoryDataset3D getDataset() {
242        return this.dataset;
243    }
244    
245    /**
246     * Sets the dataset and sends a {@link Plot3DChangeEvent} to all registered
247     * listeners.  When you call this method, the axes will be reconfigured for 
248     * the new data.
249     * 
250     * @param dataset  the dataset ({@code null} not permitted). 
251     */
252    public void setDataset(CategoryDataset3D dataset) {
253        ArgChecks.nullNotPermitted(dataset, "dataset");
254        this.dataset.removeChangeListener(this);
255        this.dataset = dataset;
256        this.dataset.addChangeListener(this);
257        // we send ourselves a dataset change event since this will 
258        // reconfigure the axes then trigger the required plot change event
259        datasetChanged(new Dataset3DChangeEvent(this, this.dataset));
260    }
261    
262    /**
263     * Returns the renderer (very often you will need to cast this to a 
264     * specific class to make customisations).
265     * 
266     * @return The renderer (never {@code null}). 
267     */
268    public CategoryRenderer3D getRenderer() {
269        return this.renderer;
270    }
271    
272    /**
273     * Sets the renderer and sends a change event to all registered listeners.
274     * 
275     * @param renderer  the renderer ({@code null} not permitted).
276     */
277    public void setRenderer(CategoryRenderer3D renderer) {
278        ArgChecks.nullNotPermitted(renderer, "renderer");
279        this.renderer.removeChangeListener(this);
280        this.renderer = renderer;
281        this.renderer.addChangeListener(this);
282        // a new renderer might mean the axis range needs changing...
283        this.valueAxis.configureAsValueAxis(this);
284        fireChangeEvent(true);
285    }
286    
287    /**
288     * Returns the row axis.
289     * 
290     * @return The row axis. 
291     */
292    public CategoryAxis3D getRowAxis() {
293        return this.rowAxis;
294    }
295    
296    /**
297     * Sets the row axis and sends a {@link Plot3DChangeEvent} to all 
298     * registered listeners.  The row axis is equivalent to the z-axis.
299     * 
300     * @param axis  the row axis ({@code null} not permitted).
301     */
302    public void setRowAxis(CategoryAxis3D axis) {
303        ArgChecks.nullNotPermitted(axis, "axis");
304        this.rowAxis.removeChangeListener(this);
305        this.rowAxis = axis;
306        this.rowAxis.addChangeListener(this);
307        fireChangeEvent(true);
308    }
309    
310    /**
311     * Returns the column axis.
312     * 
313     * @return The column axis (never {@code null}).
314     */
315    public CategoryAxis3D getColumnAxis() {
316        return this.columnAxis;
317    }
318    
319    /**
320     * Sets the column axis and sends a {@link Plot3DChangeEvent} to all 
321     * registered listeners.
322     * 
323     * @param axis  the new axis ({@code null} not permitted).
324     * 
325     * @see #setRowAxis(com.orsoncharts.axis.CategoryAxis3D) 
326     * @see #setValueAxis(com.orsoncharts.axis.ValueAxis3D) 
327     * 
328     */
329    public void setColumnAxis(CategoryAxis3D axis) {
330        ArgChecks.nullNotPermitted(axis, "axis");
331        this.columnAxis.removeChangeListener(this);
332        this.columnAxis = axis;
333        this.columnAxis.addChangeListener(this);
334        fireChangeEvent(true);
335    }
336    
337    /**
338     * Returns the value axis (the vertical axis in the plot).
339     * 
340     * @return The value axis (never {@code null}). 
341     */
342    public ValueAxis3D getValueAxis() {
343        return this.valueAxis;
344    }
345    
346    /**
347     * Sets the value axis and sends a {@link Plot3DChangeEvent} to all 
348     * registered listeners.
349     * 
350     * @param axis  the axis ({@code null} not permitted). 
351     */
352    public void setValueAxis(ValueAxis3D axis) {
353        ArgChecks.nullNotPermitted(axis, "axis");
354        this.valueAxis.removeChangeListener(this);
355        this.valueAxis = axis;
356        this.valueAxis.configureAsValueAxis(this);
357        this.valueAxis.addChangeListener(this);
358        fireChangeEvent(true);
359    }
360    
361    /**
362     * Returns {@code true} if gridlines are shown for the column axis
363     * and {@code false} otherwise.  The default value is {@code false}.
364     * 
365     * @return A boolean. 
366     */
367    public boolean getGridlinesVisibleForRows() {
368        return this.gridlinesVisibleForRows;
369    }
370
371    /**
372     * Sets the flag that controls whether or not gridlines are shown for the
373     * row axis and sends a {@link Plot3DChangeEvent} to all registered 
374     * listeners.
375     * 
376     * @param visible  the new flag value.
377     */
378    public void setGridlinesVisibleForRows(boolean visible) {
379        this.gridlinesVisibleForRows = visible;
380        fireChangeEvent(false);
381    }
382
383    /**
384     * Returns the paint used to draw the gridlines for the row axis, if they
385     * are visible.
386     * 
387     * @return The paint (never {@code null}). 
388     */
389    public Paint getGridlinePaintForRows() {
390        return this.gridlinePaintForRows;
391    }
392
393    /**
394     * Sets the paint used for the row axis gridlines and sends a 
395     * {@link Plot3DChangeEvent} to all registered listeners.
396     * 
397     * @param paint  the paint ({@code null} not permitted). 
398     */
399    public void setGridlinePaintForRows(Paint paint) {
400        ArgChecks.nullNotPermitted(paint, "paint");
401        this.gridlinePaintForRows = paint;
402        fireChangeEvent(false);
403    }
404
405    /**
406     * Returns the stroke for the gridlines associated with the row axis.
407     * The default value is {@code BasicStroke(0.5f, BasicStroke.CAP_ROUND, 
408     * BasicStroke.JOIN_ROUND, 1f, new float[] { 3f, 3f }, 0f)}.
409     * 
410     * @return The stroke (never {@code null}).
411     */
412    public Stroke getGridlineStrokeForRows() {
413        return this.gridlineStrokeForRows;
414    }
415
416    /**
417     * Sets the stroke used to draw the gridlines for the row axis, if they
418     * are visible, and sends a {@link Plot3DChangeEvent} to all 
419     * registered listeners.
420     * 
421     * @param stroke  the stroke ({@code null} not permitted). 
422     */
423    public void setGridlineStrokeForRows(Stroke stroke) {
424        ArgChecks.nullNotPermitted(stroke, "stroke");
425        this.gridlineStrokeForRows = stroke;
426        fireChangeEvent(false);
427    }
428
429    /**
430     * Returns {@code true} if gridlines are shown for the column axis
431     * and {@code false} otherwise.  The default value is {@code false}.
432     * 
433     * @return A boolean. 
434     */
435    public boolean getGridlinesVisibleForColumns() {
436        return this.gridlinesVisibleForColumns;
437    }
438
439    /**
440     * Sets the flag that controls whether or not gridlines are shown for the
441     * column axis and sends a {@link Plot3DChangeEvent} to all registered 
442     * listeners.
443     * 
444     * @param visible  the new flag value.
445     */
446    public void setGridlinesVisibleForColumns(boolean visible) {
447        this.gridlinesVisibleForColumns = visible;
448        fireChangeEvent(false);
449    }
450    
451    /**
452     * Returns {@code true} if gridlines are shown for the column axis
453     * and {@code false} otherwise.  The default value is {@code true}.
454     * 
455     * @return A boolean. 
456     */
457    public boolean getGridlinesVisibleForValues() {
458        return this.gridlinesVisibleForValues;
459    }
460    
461    /**
462     * Sets the flag that controls whether or not gridlines are shown for the
463     * value axis and sends a {@link Plot3DChangeEvent} to all registered 
464     * listeners.
465     * 
466     * @param visible  the new flag value.
467     */
468    public void setGridlinesVisibleForValues(boolean visible) {
469        this.gridlinesVisibleForValues = visible;
470        fireChangeEvent(false);
471    }
472
473    /**
474     * Returns the paint for the gridlines associated with the value axis. 
475     * The default value is {@code Color.WHITE}.
476     * 
477     * @return The paint for value axis gridlines (never {@code null}). 
478     */
479    public Paint getGridlinePaintForValues() {
480        return this.gridlinePaintForValues;
481    }
482    
483    /**
484     * Sets the paint used for the value axis gridlines and sends a 
485     * {@link Plot3DChangeEvent} to all registered listeners.
486     * 
487     * @param paint  the paint ({@code null} not permitted). 
488     */
489    public void setGridlinePaintForValues(Paint paint) {
490        ArgChecks.nullNotPermitted(paint, "paint");
491        this.gridlinePaintForValues = paint;
492        fireChangeEvent(false);
493    }
494
495    /**
496     * Returns the stroke for the gridlines associated with the value axis.
497     * The default value is {@code BasicStroke(0.5f, BasicStroke.CAP_ROUND, 
498     * BasicStroke.JOIN_ROUND, 1f, new float[] { 3f, 3f }, 0f)}.
499     * 
500     * @return The stroke (never {@code null}).
501     */
502    public Stroke getGridlineStrokeForValues() {
503        return this.gridlineStrokeForValues;
504    }
505    
506    /**
507     * Sets the stroke used to draw the grid lines for the value axis, if
508     * they are visible, and sends a {@link Plot3DChangeEvent} to all
509     * registered listeners.
510     * 
511     * @param stroke  the stroke ({@code null} not permitted).
512     */
513    public void setGridlineStrokeForValues(Stroke stroke) {
514        ArgChecks.nullNotPermitted(stroke, "stroke");
515        this.gridlineStrokeForValues = stroke;
516        fireChangeEvent(false);
517    }
518    
519    /**
520     * Returns the paint used to draw the grid lines for the column axis, if
521     * they are visible.  The default value is {@code Color.WHITE}.
522     * 
523     * @return The paint (never {@code null}). 
524     */
525    public Paint getGridlinePaintForColumns() {
526        return this.gridlinePaintForColumns;
527    }
528    
529    /**
530     * Sets the paint used to draw the grid lines for the column axis, if 
531     * they are visible, and sends a {@link Plot3DChangeEvent} to all 
532     * registered listeners.
533     * 
534     * @param paint  the paint ({@code null} not permitted). 
535     */
536    public void setGridlinePaintForColumns(Paint paint) {
537        ArgChecks.nullNotPermitted(paint, "paint");
538        this.gridlinePaintForColumns = paint;
539        fireChangeEvent(false);
540    }
541
542    /**
543     * Returns the stroke for the gridlines associated with the column axis.
544     * The default value is {@code BasicStroke(0.5f, BasicStroke.CAP_ROUND, 
545     * BasicStroke.JOIN_ROUND, 1f, new float[] { 3f, 3f }, 0f)}.
546     * 
547     * @return The stroke (never {@code null}).
548     */
549    public Stroke getGridlineStrokeForColumns() {
550        return this.gridlineStrokeForColumns;
551    }
552    
553    /**
554     * Sets the stroke used to draw the grid lines for the column axis, if
555     * they are visible, and sends a {@link Plot3DChangeEvent} to all
556     * registered listeners.
557     * 
558     * @param stroke  the stroke ({@code null} not permitted).
559     */
560    public void setGridlineStrokeForColumns(Stroke stroke) {
561        ArgChecks.nullNotPermitted(stroke, "stroke");
562        this.gridlineStrokeForColumns = stroke;
563        fireChangeEvent(false);
564    }
565
566    /**
567     * Returns the legend label generator, an object that converts key values
568     * in the dataset into corresponding strings for presentation in the chart.
569     * 
570     * @return The legend label generator (never {@code null}).
571     * 
572     * @since 1.2
573     */
574    public CategoryLabelGenerator getLegendLabelGenerator() {
575        return this.legendLabelGenerator;    
576    }
577    
578    /**
579     * Sets the legend label generator and sends a {@link Plot3DChangeEvent}
580     * to all registered listeners.
581     * 
582     * @param generator  the generator ({@code null} not permitted).
583     * 
584     * @since 1.2
585     */
586    public void setLegendLabelGenerator(CategoryLabelGenerator generator) {
587        ArgChecks.nullNotPermitted(generator, "generator");
588        this.legendLabelGenerator = generator;
589        fireChangeEvent(false);
590    }
591    
592    /**
593     * Returns the y-dimension override.  The default value is {@code null},
594     * which means that when the plot dimensions are automatically calculated,
595     * the height of the plot will be set to the greater of the width and
596     * the depth.
597     * 
598     * @return The y-dimension override (possibly {@code null}). 
599     * 
600     * @since 1.2
601     */
602    public Double getYDimensionOverride() {
603        return this.yDimensionOverride;
604    }
605
606    /**
607     * Sets the y-dimension override and, if the {@code autoAdjustDimensions} 
608     * flag is set, recalculates the dimensions and sends a 
609     * {@link Plot3DChangeEvent} to all registered listeners.
610     * 
611     * @param dim  the new y-dimension override ({@code null} permitted).
612     * 
613     * @since 1.2
614     */
615    public void setYDimensionOverride(Double dim) {
616        this.yDimensionOverride = dim;
617        if (this.autoAdjustDimensions) {
618            this.dimensions = calculateDimensions();
619            fireChangeEvent(true);
620        }
621    }
622    
623    /**
624     * Returns the tool tip generator.  This is an object that calculates and
625     * returns a string (that will be used as the tool tip) for any given
626     * data value in the dataset.
627     * 
628     * @return The tool tip generator (possibly {@code null}).
629     * 
630     * @since 1.3
631     */
632    public CategoryItemLabelGenerator getToolTipGenerator() {
633        return this.toolTipGenerator;
634    }
635    
636    /**
637     * Sets the tool tip generator and sends a {@link Plot3DChangeEvent} to all 
638     * registered listeners.
639     * 
640     * @param generator  the new generator ({@code null} permitted).
641     * 
642     * @since 1.3
643     */
644    public void setToolTipGenerator(CategoryItemLabelGenerator generator) {
645        this.toolTipGenerator = generator;
646        fireChangeEvent(false);
647    }
648
649    /**
650     * Returns a list containing legend item info, typically one item for
651     * each series in the chart.  This is intended for use in the construction
652     * of a chart legend.
653     * 
654     * @return A list containing legend item info (possibly empty but never 
655     *     {@code null}).
656     */
657    @Override
658    public List<LegendItemInfo> getLegendInfo() {
659        List<LegendItemInfo> result = new ArrayList<LegendItemInfo>();
660        List<Comparable<?>> keys = this.dataset.getSeriesKeys();
661        for (Comparable<?> key : keys) {
662            int series = this.dataset.getSeriesIndex(key);
663            Color color = this.renderer.getColorSource().getLegendColor(series);
664            String seriesLabel = this.legendLabelGenerator.generateSeriesLabel(
665                    this.dataset, key);
666            LegendItemInfo info = new StandardLegendItemInfo(key, 
667                    seriesLabel, color);
668            result.add(info);
669        }
670        return result;
671    }
672
673    @Override
674    public void compose(World world, double xOffset, double yOffset, 
675            double zOffset) {
676        for (int series = 0; series < this.dataset.getSeriesCount(); series++) {
677            for (int row = 0; row < this.dataset.getRowCount(); row++) {
678                for (int column = 0; column < this.dataset.getColumnCount(); 
679                        column++) {
680                    this.renderer.composeItem(this.dataset, series, row, column,
681                            world, getDimensions(), xOffset, yOffset, zOffset);
682                }
683            }
684        }
685    }
686    
687    @Override
688    public String generateToolTipText(ItemKey itemKey) {
689        if (!(itemKey instanceof KeyedValues3DItemKey)) {
690            throw new IllegalArgumentException(
691                    "The itemKey must be a Values3DItemKey instance.");
692        }
693        KeyedValues3DItemKey vik = (KeyedValues3DItemKey) itemKey;
694        return this.toolTipGenerator.generateItemLabel(dataset, 
695                vik.getSeriesKey(), vik.getRowKey(), vik.getColumnKey());
696    }
697
698    /**
699     * Accepts a visitor for the plot.  This method first calls the 
700     * {@code receive()} method for each of the plot's axes and the renderer, 
701     * then performs the visitor's function on the plot.  This is a general 
702     * purpose mechanism, but the main use is to apply chart style changes 
703     * across all the elements of a chart.
704     * 
705     * @param visitor  the visitor ({@code null} not permitted).
706     * 
707     * @since 1.2
708     */
709    @Override 
710    public void receive(ChartElementVisitor visitor) {
711        this.columnAxis.receive(visitor);
712        this.rowAxis.receive(visitor);
713        this.valueAxis.receive(visitor);
714        this.renderer.receive(visitor);
715        visitor.visit(this);
716    }
717
718    /**
719     * Tests this plot for equality with an arbitrary object.
720     * 
721     * @param obj  the object ({@code null} permitted).
722     * 
723     * @return A boolean. 
724     */
725    @Override
726    public boolean equals(Object obj) {
727        if (obj == this) {
728            return true;
729        }    
730        if (!(obj instanceof CategoryPlot3D)) {
731            return false;
732        }
733        CategoryPlot3D that = (CategoryPlot3D) obj;
734        if (this.gridlinesVisibleForRows != that.gridlinesVisibleForRows) {
735            return false;
736        }
737        if (!this.gridlineStrokeForRows.equals(that.gridlineStrokeForRows)) {
738            return false;
739        }
740        if (!ObjectUtils.equalsPaint(this.gridlinePaintForRows, 
741                that.gridlinePaintForRows)) {
742            return false;
743        }
744        if (this.gridlinesVisibleForColumns 
745                != that.gridlinesVisibleForColumns) {
746            return false;
747        }
748        if (!this.gridlineStrokeForColumns.equals(
749                that.gridlineStrokeForColumns)) {
750            return false;
751        }
752        if (!ObjectUtils.equalsPaint(this.gridlinePaintForColumns, 
753                that.gridlinePaintForColumns)) {
754            return false;
755        }
756        if (this.gridlinesVisibleForValues != that.gridlinesVisibleForValues) {
757            return false;
758        }
759        if (!this.gridlineStrokeForValues.equals(that.gridlineStrokeForValues)) {
760            return false;
761        }
762        if (!ObjectUtils.equalsPaint(this.gridlinePaintForValues, 
763                 that.gridlinePaintForValues)) {
764            return false;
765        }
766        if (!this.legendLabelGenerator.equals(that.legendLabelGenerator)) {
767            return false;
768        }
769        if (!ObjectUtils.equals(this.yDimensionOverride, 
770                that.yDimensionOverride)) {
771            return false;
772        }
773        if (!ObjectUtils.equals(this.toolTipGenerator, that.toolTipGenerator)) {
774            return false;
775        }
776       return super.equals(obj);
777    }
778    
779    /**
780     * Receives notification of a change to the dataset and handles this by
781     * adjusting the plot dimensions (according to the setting of the
782     * {@code autoAdjustDimensions} flag), reconfiguring the axes, and
783     * propagating a {@code Plot3DChangeEvent}. 
784     * 
785     * @param event  the change event. 
786     */
787    @Override
788    public void datasetChanged(Dataset3DChangeEvent event) {
789        // update the category axis labels 
790        // and the value axis range
791        if (this.autoAdjustDimensions) {
792            this.dimensions = calculateDimensions();
793        }
794        this.columnAxis.configureAsColumnAxis(this);
795        this.rowAxis.configureAsRowAxis(this);
796        this.valueAxis.configureAsValueAxis(this);
797        super.datasetChanged(event);  // propagates a plot change event
798    }
799    
800    /**
801     * Returns the dimensions for the plot that best suit the current data 
802     * values.  The x-dimension is set to the number of columns in the 
803     * dataset and the z-dimension is set to the number of rows in the dataset.
804     * For the y-dimension, the code first checks the 
805     * {@code yDimensionOverride} attribute to see if a specific value is 
806     * requested...and if not, the minimum of the x and z dimensions will be
807     * used.
808     * 
809     * @return The dimensions (never {@code null}). 
810     */
811    private Dimension3D calculateDimensions() {
812        double depth = Math.max(1.0, this.dataset.getRowCount() + 1);
813        double width = Math.max(1.0, this.dataset.getColumnCount() + 1);
814        double height = Math.max(1.0, Math.min(width, depth));
815        if (this.yDimensionOverride != null) {
816            height = this.yDimensionOverride.doubleValue();
817        }
818        return new Dimension3D(width, height, depth);
819    }
820   
821    /**
822     * Receives notification that one of the axes has been changed.  This will
823     * trigger a {@link Plot3DChangeEvent} that will usually cause the chart
824     * to be repainted.
825     * 
826     * @param event  the change event. 
827     */
828    @Override
829    public void axisChanged(Axis3DChangeEvent event) {
830        // for now we just fire a plot change event which will flow up the
831        // chain and eventually trigger a chart repaint
832        fireChangeEvent(event.requiresWorldUpdate());
833    }
834
835    /**
836     * Receives notification that the renderer has been modified in some way.  
837     * This will trigger a {@link Plot3DChangeEvent} that will usually cause 
838     * the chart to be repainted.     
839     * 
840     * @param event  information about the event. 
841     */
842    @Override
843    public void rendererChanged(Renderer3DChangeEvent event) {
844        // for now we just fire a plot change event which will flow up the
845        // chain and eventually trigger a chart repaint
846        fireChangeEvent(event.requiresWorldUpdate());
847    }
848    
849    /**
850     * Provides serialization support.
851     *
852     * @param stream  the output stream.
853     *
854     * @throws IOException  if there is an I/O error.
855     */
856    private void writeObject(ObjectOutputStream stream) throws IOException {
857        stream.defaultWriteObject();
858        SerialUtils.writePaint(this.gridlinePaintForRows, stream);
859        SerialUtils.writePaint(this.gridlinePaintForColumns, stream);
860        SerialUtils.writePaint(this.gridlinePaintForValues, stream);
861        SerialUtils.writeStroke(this.gridlineStrokeForRows, stream);
862        SerialUtils.writeStroke(this.gridlineStrokeForColumns, stream);
863        SerialUtils.writeStroke(this.gridlineStrokeForValues, stream);
864    }
865
866    /**
867     * Provides serialization support.
868     *
869     * @param stream  the input stream.
870     *
871     * @throws IOException  if there is an I/O error.
872     * @throws ClassNotFoundException  if there is a classpath problem.
873     */
874    private void readObject(ObjectInputStream stream)
875        throws IOException, ClassNotFoundException {
876        stream.defaultReadObject();
877        this.gridlinePaintForRows = SerialUtils.readPaint(stream);
878        this.gridlinePaintForColumns = SerialUtils.readPaint(stream);
879        this.gridlinePaintForValues = SerialUtils.readPaint(stream);
880        this.gridlineStrokeForRows = SerialUtils.readStroke(stream);
881        this.gridlineStrokeForColumns = SerialUtils.readStroke(stream);
882        this.gridlineStrokeForValues = SerialUtils.readStroke(stream);
883    }
884
885}