    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.io.IOException;
    036import java.io.ObjectInputStream;
    037import java.io.Serializable;
    038
    039import javax.swing.event.EventListenerList;
    040
    041import com.orsoncharts.data.Dataset3DChangeEvent;
    042import com.orsoncharts.data.Dataset3DChangeListener;
    043import com.orsoncharts.graphics3d.Dimension3D;
    044import com.orsoncharts.Chart3D;
    045import com.orsoncharts.ChartElementVisitor;
    046import com.orsoncharts.data.ItemKey;
    047
    048/**
    049 * A base class that can be used to create classes that implement
    050 * {@link Plot3D}.
    051 * <br><br>
    052 * A mechanism is provided for registering change listeners
    053 * on the plot.  Whenever some attribute of the plot changes, all the
    054 * registered listeners are notified.  The {@link Chart3D} instance that owns
    055 * the plot will be automatically registered as a listener so that it receives
    056 * notification whenever the plot (or some other object managed by the plot)
    057 * changes.
    058 * <br><br>
    059 * Typically a plot registers itself as a change listener on its dataset
    060 * and whenever a dataset change notification is received, the plot will
    061 * pass on a {@link Plot3DChangeEvent} to all *its* listeners.  If the plot
    062 * has axes, then the same approach is used to listen for changes to the axes.
    063 * <br><br>
    064 * NOTE: This class is serializable, but the serialization format is subject
    065 * to change in future releases and should not be relied upon for persisting
    066 * instances of this class.
    067 */
    068@SuppressWarnings("serial")
    069public abstract class AbstractPlot3D implements Plot3D,
    070        Dataset3DChangeListener, Serializable {
    071
    072    /** The chart that this plot is assigned to, if any. */
    073    private Chart3D chart;
    074
    075    /**
    076     * The plot dimensions in 3D space.  By default, this is auto-adjusted
    077     * according to the dataset, but the user can override this.
    078     */
    079    protected Dimension3D dimensions;
    080
    081    /**
    082     * A flag that controls whether or not the plot dimensions (in the 3D
    083     * model) are adjusted automatically.
    084     */
    085    protected boolean autoAdjustDimensions;
    086
    087    /** Storage for registered change listeners. */
    088    private transient EventListenerList listenerList;
    089
    090    /**
    091     * A flag that controls whether or not the plot will notify listeners
    092     * of changes (defaults to {@code true}, but sometimes it is useful
    093     * to disable this).
    094     */
    095    private boolean notify;
    096
    097    /**
    098     * Default constructor.
    099     */
    100    protected AbstractPlot3D() {
    101        this.chart = null;
    102        this.dimensions = new Dimension3D(1.0, 1.0, 1.0);
    103        this.autoAdjustDimensions = true;
    104        this.listenerList = new EventListenerList();
    105        this.notify = true;
    106    }
    107
    108    /**
    109     * Returns the chart that the plot is assigned to, if any.
    110     *
    111     * @return The chart (possibly {@code null}).
    112     *
    113     * @since 1.2
    114     */
    115    @Override
    116    public Chart3D getChart() {
    117        return this.chart;
    118    }
    119
    120    /**
    121     * Sets the chart that the plot is assigned to.
    122     *
    123     * @param chart  the chart ({@code null} permitted).
    124     *
    125     * @since 1.2
    126     */
    127    @Override
    128    public void setChart(Chart3D chart) {
    129        this.chart = chart;
    130    }
    131
    132    /**
    133     * Returns the dimensions of the box in 3D space into which the plot will
    134     * be composed.  The dimension can change according to the shape of the
    135     * data.
    136     *
    137     * @return The dimensions of the plot (never {@code null}).
    138     *
    139     * @see #isAutoAdjustDimensions()
    140     */
    141    @Override
    142    public Dimension3D getDimensions() {
    143        return this.dimensions;
    144    }
    145
    146    /**
    147     * Returns the flag that controls whether or not the plot dimensions are
    148     * auto-adjusted when the dataset changes.  Certain subclasses will allow
    149     * this flag to be changed ({@link CategoryPlot3D} and {@link XYZPlot})
    150     * while others will always auto-adjust the dimensions ({@link PiePlot3D}).
    151     *
    152     * @return A boolean.
    153     */
    154    public boolean isAutoAdjustDimensions() {
    155        return this.autoAdjustDimensions;
    156    }
    157
    158    /**
    159     * Returns the tool tip text for the specified data item, or
    160     * {@code null} if no tool tip is required.
    161     *
    162     * @param itemKey  the item key ({@code null} not permitted).
    163     *
    164     * @return The tool tip text (possibly {@code null}).
    165     *
    166     * @since 1.3
    167     */
    168    @Override
    169    public abstract String generateToolTipText(ItemKey itemKey);
    170
    171    /**
    172     * Accepts a {@link ChartElementVisitor}.  This is part of
    173     * a general purpose mechanism for traversing the chart
    174     * structure, you won't normally call this method directly.
    175     *
    176     * @param visitor  the visitor (never {@code null}).
    177     */
    178    @Override
    179    public abstract void receive(ChartElementVisitor visitor);
    180
    181    /**
    182     * Tests this plot for equality with an arbitrary object.
    183     *
    184     * @param obj  the object ({@code null} permitted).
    185     *
    186     * @return A boolean.
    187     */
    188    @Override
    189    public boolean equals(Object obj) {
    190        if (obj == this) {
    191            return true;
    192        }
    193        if (!(obj instanceof AbstractPlot3D)) {
    194            return false;
    195        }
    196        AbstractPlot3D that = (AbstractPlot3D) obj;
    197        if (!this.dimensions.equals(that.dimensions)) {
    198            return false;
    199        }
    200        return true;
    201    }
    202
    203    /**
    204     * Returns a flag that controls whether or not change events are sent to
    205     * registered listeners.
    206     *
    207     * @return A boolean.
    208     *
    209     * @see #setNotify(boolean)
    210     */
    211    public boolean isNotify() {
    212        return this.notify;
    213    }
    214
    215    /**
    216     * Sets a flag that controls whether or not listeners receive
    217     * {@link Plot3DChangeEvent} notifications.
    218     *
    219     * @param notify  a boolean.
    220     *
    221     * @see #isNotify()
    222     */
    223    public void setNotify(boolean notify) {
    224        this.notify = notify;
    225        // if the flag is being set to true, there may be queued up changes...
    226        if (notify) {
    227            fireChangeEvent(true);
    228        }
    229    }
    230
    231    /**
    232     * Registers an object for notification of changes to the plot.
    233     *
    234     * @param listener  the object to be registered.
    235     *
    236     * @see #removeChangeListener(Plot3DChangeListener)
    237     */
    238    @Override
    239    public void addChangeListener(Plot3DChangeListener listener) {
    240        this.listenerList.add(Plot3DChangeListener.class, listener);
    241    }
    242
    243    /**
    244     * Unregisters an object for notification of changes to the plot.
    245     *
    246     * @param listener  the object to be unregistered.
    247     *
    248     * @see #addChangeListener(Plot3DChangeListener)
    249     */
    250    @Override
    251    public void removeChangeListener(Plot3DChangeListener listener) {
    252        this.listenerList.remove(Plot3DChangeListener.class, listener);
    253    }
    254
    255    /**
    256     * Notifies all registered listeners that the plot has been modified.
    257     *
    258     * @param event  information about the change event.
    259     */
    260    public void notifyListeners(Plot3DChangeEvent event) {
    261        // if the 'notify' flag has been switched to false, we don't notify
    262        // the listeners
    263        if (!this.notify) {
    264            return;
    265        }
    266        Object[] listeners = this.listenerList.getListenerList();
    267        for (int i = listeners.length - 2; i >= 0; i -= 2) {
    268            if (listeners[i] == Plot3DChangeListener.class) {
    269                ((Plot3DChangeListener) listeners[i + 1]).plotChanged(event);
    270            }
    271        }
    272    }
    273
    274    /**
    275     * Sends a {@link Plot3DChangeEvent} to all registered listeners.
    276     *
    277     * @param requiresWorldUpdate  requires the world to be updated?
    278     */
    279    protected void fireChangeEvent(boolean requiresWorldUpdate) {
    280        notifyListeners(new Plot3DChangeEvent(this, this, requiresWorldUpdate));
    281    }
    282
    283    /**
    284     * Receives notification of a dataset change, and passes this event on
    285     * wrapped in a {@link Plot3DChangeEvent}.
    286     *
    287     * @param event  the dataset change event.
    288     */
    289    @Override
    290    public void datasetChanged(Dataset3DChangeEvent event) {
    291        notifyListeners(new Plot3DChangeEvent(event, this, true));
    292    }
    293
    294    /**
    295     * Provides serialization support.
    296     *
    297     * @param stream  the input stream.
    298     *
    299     * @throws IOException  if there is an I/O error.
    300     * @throws ClassNotFoundException  if there is a classpath problem.
    301     */
    302    private void readObject(ObjectInputStream stream)
    303            throws IOException, ClassNotFoundException {
    304        stream.defaultReadObject();
    305        // recreate an empty listener list
    306        this.listenerList = new EventListenerList();
    307    }
    308
    309}