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 */
032
033package org.jfree.chart3d.plot;
034
035import java.io.IOException;
036import java.io.ObjectInputStream;
037import java.io.Serializable;
038
039import javax.swing.event.EventListenerList;
040
041import org.jfree.chart3d.Chart3D;
042import org.jfree.chart3d.ChartElementVisitor;
043import org.jfree.chart3d.data.Dataset3DChangeEvent;
044import org.jfree.chart3d.data.Dataset3DChangeListener;
045import org.jfree.chart3d.data.ItemKey;
046import org.jfree.chart3d.graphics3d.Dimension3D;
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}