    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    @SuppressWarnings("unchecked") // we don't know the dataset generic types
    659    public List<LegendItemInfo> getLegendInfo() {
    660        List<LegendItemInfo> result = new ArrayList<LegendItemInfo>();
    661        List<Comparable<?>> keys = this.dataset.getSeriesKeys();
    662        for (Comparable<?> key : keys) {
    663            int series = this.dataset.getSeriesIndex(key);
    664            Color color = this.renderer.getColorSource().getLegendColor(series);
    665            String seriesLabel = this.legendLabelGenerator.generateSeriesLabel(
    666                    this.dataset, key);
    667            LegendItemInfo info = new StandardLegendItemInfo(key,
    668                    seriesLabel, color);
    669            result.add(info);
    670        }
    671        return result;
    672    }
    673
    674    @Override
    675    public void compose(World world, double xOffset, double yOffset,
    676            double zOffset) {
    677        for (int series = 0; series < this.dataset.getSeriesCount(); series++) {
    678            for (int row = 0; row < this.dataset.getRowCount(); row++) {
    679                for (int column = 0; column < this.dataset.getColumnCount();
    680                        column++) {
    681                    this.renderer.composeItem(this.dataset, series, row, column,
    682                            world, getDimensions(), xOffset, yOffset, zOffset);
    683                }
    684            }
    685        }
    686    }
    687
    688    @Override
    689    public String generateToolTipText(ItemKey itemKey) {
    690        if (!(itemKey instanceof KeyedValues3DItemKey)) {
    691            throw new IllegalArgumentException(
    692                    "The itemKey must be a Values3DItemKey instance.");
    693        }
    694        KeyedValues3DItemKey vik = (KeyedValues3DItemKey) itemKey;
    695        return this.toolTipGenerator.generateItemLabel(dataset,
    696                vik.getSeriesKey(), vik.getRowKey(), vik.getColumnKey());
    697    }
    698
    699    /**
    700     * Accepts a visitor for the plot.  This method first calls the
    701     * {@code receive()} method for each of the plot's axes and the renderer,
    702     * then performs the visitor's function on the plot.  This is a general
    703     * purpose mechanism, but the main use is to apply chart style changes
    704     * across all the elements of a chart.
    705     *
    706     * @param visitor  the visitor ({@code null} not permitted).
    707     *
    708     * @since 1.2
    709     */
    710    @Override
    711    public void receive(ChartElementVisitor visitor) {
    712        this.columnAxis.receive(visitor);
    713        this.rowAxis.receive(visitor);
    714        this.valueAxis.receive(visitor);
    715        this.renderer.receive(visitor);
    716        visitor.visit(this);
    717    }
    718
    719    /**
    720     * Tests this plot for equality with an arbitrary object.
    721     *
    722     * @param obj  the object ({@code null} permitted).
    723     *
    724     * @return A boolean.
    725     */
    726    @Override
    727    public boolean equals(Object obj) {
    728        if (obj == this) {
    729            return true;
    730        }
    731        if (!(obj instanceof CategoryPlot3D)) {
    732            return false;
    733        }
    734        CategoryPlot3D that = (CategoryPlot3D) obj;
    735        if (this.gridlinesVisibleForRows != that.gridlinesVisibleForRows) {
    736            return false;
    737        }
    738        if (!this.gridlineStrokeForRows.equals(that.gridlineStrokeForRows)) {
    739            return false;
    740        }
    741        if (!ObjectUtils.equalsPaint(this.gridlinePaintForRows,
    742                that.gridlinePaintForRows)) {
    743            return false;
    744        }
    745        if (this.gridlinesVisibleForColumns
    746                != that.gridlinesVisibleForColumns) {
    747            return false;
    748        }
    749        if (!this.gridlineStrokeForColumns.equals(
    750                that.gridlineStrokeForColumns)) {
    751            return false;
    752        }
    753        if (!ObjectUtils.equalsPaint(this.gridlinePaintForColumns,
    754                that.gridlinePaintForColumns)) {
    755            return false;
    756        }
    757        if (this.gridlinesVisibleForValues != that.gridlinesVisibleForValues) {
    758            return false;
    759        }
    760        if (!this.gridlineStrokeForValues.equals(that.gridlineStrokeForValues)) {
    761            return false;
    762        }
    763        if (!ObjectUtils.equalsPaint(this.gridlinePaintForValues,
    764                 that.gridlinePaintForValues)) {
    765            return false;
    766        }
    767        if (!this.legendLabelGenerator.equals(that.legendLabelGenerator)) {
    768            return false;
    769        }
    770        if (!ObjectUtils.equals(this.yDimensionOverride,
    771                that.yDimensionOverride)) {
    772            return false;
    773        }
    774        if (!ObjectUtils.equals(this.toolTipGenerator, that.toolTipGenerator)) {
    775            return false;
    776        }
    777       return super.equals(obj);
    778    }
    779
    780    /**
    781     * Receives notification of a change to the dataset and handles this by
    782     * adjusting the plot dimensions (according to the setting of the
    783     * {@code autoAdjustDimensions} flag), reconfiguring the axes, and
    784     * propagating a {@code Plot3DChangeEvent}.
    785     *
    786     * @param event  the change event.
    787     */
    788    @Override
    789    public void datasetChanged(Dataset3DChangeEvent event) {
    790        // update the category axis labels
    791        // and the value axis range
    792        if (this.autoAdjustDimensions) {
    793            this.dimensions = calculateDimensions();
    794        }
    795        this.columnAxis.configureAsColumnAxis(this);
    796        this.rowAxis.configureAsRowAxis(this);
    797        this.valueAxis.configureAsValueAxis(this);
    798        super.datasetChanged(event);  // propagates a plot change event
    799    }
    800
    801    /**
    802     * Returns the dimensions for the plot that best suit the current data
    803     * values.  The x-dimension is set to the number of columns in the
    804     * dataset and the z-dimension is set to the number of rows in the dataset.
    805     * For the y-dimension, the code first checks the
    806     * {@code yDimensionOverride} attribute to see if a specific value is
    807     * requested...and if not, the minimum of the x and z dimensions will be
    808     * used.
    809     *
    810     * @return The dimensions (never {@code null}).
    811     */
    812    private Dimension3D calculateDimensions() {
    813        double depth = Math.max(1.0, this.dataset.getRowCount() + 1);
    814        double width = Math.max(1.0, this.dataset.getColumnCount() + 1);
    815        double height = Math.max(1.0, Math.min(width, depth));
    816        if (this.yDimensionOverride != null) {
    817            height = this.yDimensionOverride.doubleValue();
    818        }
    819        return new Dimension3D(width, height, depth);
    820    }
    821
    822    /**
    823     * Receives notification that one of the axes has been changed.  This will
    824     * trigger a {@link Plot3DChangeEvent} that will usually cause the chart
    825     * to be repainted.
    826     *
    827     * @param event  the change event.
    828     */
    829    @Override
    830    public void axisChanged(Axis3DChangeEvent event) {
    831        // for now we just fire a plot change event which will flow up the
    832        // chain and eventually trigger a chart repaint
    833        fireChangeEvent(event.requiresWorldUpdate());
    834    }
    835
    836    /**
    837     * Receives notification that the renderer has been modified in some way.
    838     * This will trigger a {@link Plot3DChangeEvent} that will usually cause
    839     * the chart to be repainted.
    840     *
    841     * @param event  information about the event.
    842     */
    843    @Override
    844    public void rendererChanged(Renderer3DChangeEvent event) {
    845        // for now we just fire a plot change event which will flow up the
    846        // chain and eventually trigger a chart repaint
    847        fireChangeEvent(event.requiresWorldUpdate());
    848    }
    849
    850    /**
    851     * Provides serialization support.
    852     *
    853     * @param stream  the output stream.
    854     *
    855     * @throws IOException  if there is an I/O error.
    856     */
    857    private void writeObject(ObjectOutputStream stream) throws IOException {
    858        stream.defaultWriteObject();
    859        SerialUtils.writePaint(this.gridlinePaintForRows, stream);
    860        SerialUtils.writePaint(this.gridlinePaintForColumns, stream);
    861        SerialUtils.writePaint(this.gridlinePaintForValues, stream);
    862        SerialUtils.writeStroke(this.gridlineStrokeForRows, stream);
    863        SerialUtils.writeStroke(this.gridlineStrokeForColumns, stream);
    864        SerialUtils.writeStroke(this.gridlineStrokeForValues, stream);
    865    }
    866
    867    /**
    868     * Provides serialization support.
    869     *
    870     * @param stream  the input stream.
    871     *
    872     * @throws IOException  if there is an I/O error.
    873     * @throws ClassNotFoundException  if there is a classpath problem.
    874     */
    875    private void readObject(ObjectInputStream stream)
    876        throws IOException, ClassNotFoundException {
    877        stream.defaultReadObject();
    878        this.gridlinePaintForRows = SerialUtils.readPaint(stream);
    879        this.gridlinePaintForColumns = SerialUtils.readPaint(stream);
    880        this.gridlinePaintForValues = SerialUtils.readPaint(stream);
    881        this.gridlineStrokeForRows = SerialUtils.readStroke(stream);
    882        this.gridlineStrokeForColumns = SerialUtils.readStroke(stream);
    883        this.gridlineStrokeForValues = SerialUtils.readStroke(stream);
    884    }
    885
    886}