    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}