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}