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