001: /* ===========================================================
002: * JFreeChart : a free chart library for the Java(tm) platform
003: * ===========================================================
004: *
005: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
006: *
007: * Project Info: http://www.jfree.org/jfreechart/index.html
008: *
009: * This library is free software; you can redistribute it and/or modify it
010: * under the terms of the GNU Lesser General Public License as published by
011: * the Free Software Foundation; either version 2.1 of the License, or
012: * (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful, but
015: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017: * License for more details.
018: *
019: * You should have received a copy of the GNU Lesser General Public
020: * License along with this library; if not, write to the Free Software
021: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022: * USA.
023: *
024: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025: * in the United States and other countries.]
026: *
027: * -------------
028: * DialPlot.java
029: * -------------
030: * (C) Copyright 2006, 2007, by Object Refinery Limited.
031: *
032: * Original Author: David Gilbert (for Object Refinery Limited);
033: * Contributor(s): -;
034: *
035: * $Id: DialPlot.java,v 1.1.2.5 2007/03/08 16:51:07 mungady Exp $
036: *
037: * Changes
038: * -------
039: * 03-Nov-2006 : Version 1 (DG);
040: * 08-Mar-2007 : Fix in hashCode() (DG);
041: *
042: */
043:
044: package org.jfree.experimental.chart.plot.dial;
045:
046: import java.awt.Graphics2D;
047: import java.awt.Shape;
048: import java.awt.geom.Point2D;
049: import java.awt.geom.Rectangle2D;
050: import java.io.IOException;
051: import java.io.ObjectInputStream;
052: import java.io.ObjectOutputStream;
053: import java.util.Iterator;
054: import java.util.List;
055:
056: import org.jfree.chart.JFreeChart;
057: import org.jfree.chart.event.PlotChangeEvent;
058: import org.jfree.chart.plot.Plot;
059: import org.jfree.chart.plot.PlotRenderingInfo;
060: import org.jfree.chart.plot.PlotState;
061: import org.jfree.data.general.DatasetChangeEvent;
062: import org.jfree.data.general.ValueDataset;
063: import org.jfree.util.ObjectList;
064: import org.jfree.util.ObjectUtilities;
065:
066: /**
067: * A dial plot.
068: */
069: public class DialPlot extends Plot implements DialLayerChangeListener {
070:
071: /**
072: * The background layer (optional).
073: */
074: private DialLayer background;
075:
076: /**
077: * The needle cap (optional).
078: */
079: private DialLayer cap;
080:
081: /**
082: * The dial frame.
083: */
084: private DialFrame dialFrame;
085:
086: /**
087: * The dataset(s) for the dial plot.
088: */
089: private ObjectList datasets;
090:
091: /**
092: * The scale(s) for the dial plot.
093: */
094: private ObjectList scales;
095:
096: /** Storage for keys that map datasets to scales. */
097: private ObjectList datasetToScaleMap;
098:
099: /**
100: * The drawing layers for the dial plot.
101: */
102: private List layers;
103:
104: /**
105: * The x-coordinate for the view window.
106: */
107: private double viewX;
108:
109: /**
110: * The y-coordinate for the view window.
111: */
112: private double viewY;
113:
114: /**
115: * The width of the view window, expressed as a percentage.
116: */
117: private double viewW;
118:
119: /**
120: * The height of the view window, expressed as a percentage.
121: */
122: private double viewH;
123:
124: /**
125: * Creates a new instance of <code>DialPlot</code>.
126: */
127: public DialPlot() {
128: this .background = null;
129: this .cap = null;
130: this .dialFrame = new StandardDialFrame();
131: this .datasets = new ObjectList();
132: this .scales = new ObjectList();
133: this .datasetToScaleMap = new ObjectList();
134: this .layers = new java.util.ArrayList();
135: this .viewX = 0.0;
136: this .viewY = 0.0;
137: this .viewW = 1.0;
138: this .viewH = 1.0;
139: }
140:
141: /**
142: * Returns the background.
143: *
144: * @return The background (possibly <code>null</code>).
145: *
146: * @see #setBackground(DialLayer)
147: */
148: public DialLayer getBackground() {
149: return this .background;
150: }
151:
152: /**
153: * Sets the background layer.
154: *
155: * @param background the background layer (<code>null</code> permitted).
156: *
157: * @see #getBackground()
158: */
159: public void setBackground(DialLayer background) {
160: this .background = background;
161: notifyListeners(new PlotChangeEvent(this ));
162: }
163:
164: /**
165: * Returns the cap.
166: *
167: * @return The cap (possibly <code>null</code>).
168: *
169: * @see #setCap(DialLayer)
170: */
171: public DialLayer getCap() {
172: return this .cap;
173: }
174:
175: /**
176: * Sets the cap.
177: *
178: * @param cap the cap (<code>null</code> permitted).
179: *
180: * @see #getCap()
181: */
182: public void setCap(DialLayer cap) {
183: this .cap = cap;
184: notifyListeners(new PlotChangeEvent(this ));
185: }
186:
187: /**
188: * Returns the dial's frame.
189: *
190: * @return The dial's frame (never <code>null</code>).
191: *
192: * @see #setDialFrame(DialFrame)
193: */
194: public DialFrame getDialFrame() {
195: return this .dialFrame;
196: }
197:
198: /**
199: * Sets the dial's frame.
200: *
201: * @param frame the frame (<code>null</code> not permitted).
202: *
203: * @see #getDialFrame()
204: */
205: public void setDialFrame(DialFrame frame) {
206: if (frame == null) {
207: throw new IllegalArgumentException("Null 'frame' argument.");
208: }
209: this .dialFrame = frame;
210: notifyListeners(new PlotChangeEvent(this ));
211: }
212:
213: /**
214: * Returns the x-coordinate of the viewing rectangle. This is specified
215: * in the range 0.0 to 1.0, relative to the dial's framing rectangle.
216: *
217: * @return The x-coordinate of the viewing rectangle.
218: *
219: * @see #setView(double, double, double, double)
220: */
221: public double getViewX() {
222: return this .viewX;
223: }
224:
225: /**
226: * Returns the y-coordinate of the viewing rectangle. This is specified
227: * in the range 0.0 to 1.0, relative to the dial's framing rectangle.
228: *
229: * @return The y-coordinate of the viewing rectangle.
230: *
231: * @see #setView(double, double, double, double)
232: */
233: public double getViewY() {
234: return this .viewY;
235: }
236:
237: /**
238: * Returns the width of the viewing rectangle. This is specified
239: * in the range 0.0 to 1.0, relative to the dial's framing rectangle.
240: *
241: * @return The width of the viewing rectangle.
242: *
243: * @see #setView(double, double, double, double)
244: */
245: public double getViewWidth() {
246: return this .viewW;
247: }
248:
249: /**
250: * Returns the height of the viewing rectangle. This is specified
251: * in the range 0.0 to 1.0, relative to the dial's framing rectangle.
252: *
253: * @return The height of the viewing rectangle.
254: *
255: * @see #setView(double, double, double, double)
256: */
257: public double getViewHeight() {
258: return this .viewH;
259: }
260:
261: /**
262: * Sets the viewing rectangle, relative to the dial's framing rectangle.
263: *
264: * @param x the x-coordinate (in the range 0.0 to 1.0).
265: * @param y the y-coordinate (in the range 0.0 to 1.0).
266: * @param w the width (in the range 0.0 to 1.0).
267: * @param h the height (in the range 0.0 to 1.0).
268: *
269: * @see #getViewX()
270: * @see #getViewY()
271: * @see #getViewWidth()
272: * @see #getViewHeight()
273: */
274: public void setView(double x, double y, double w, double h) {
275: this .viewX = x;
276: this .viewY = y;
277: this .viewW = w;
278: this .viewH = h;
279: notifyListeners(new PlotChangeEvent(this ));
280: }
281:
282: /**
283: * Adds a layer to the plot.
284: *
285: * @param layer the layer (<code>null</code> not permitted).
286: */
287: public void addLayer(DialLayer layer) {
288: if (layer == null) {
289: throw new IllegalArgumentException("Null 'layer' argument.");
290: }
291: this .layers.add(layer);
292: notifyListeners(new PlotChangeEvent(this ));
293: }
294:
295: /**
296: * Returns the primary dataset for the plot.
297: *
298: * @return The primary dataset (possibly <code>null</code>).
299: */
300: public ValueDataset getDataset() {
301: return getDataset(0);
302: }
303:
304: /**
305: * Returns the dataset at the given index.
306: *
307: * @param index the dataset index.
308: *
309: * @return The dataset (possibly <code>null</code>).
310: */
311: public ValueDataset getDataset(int index) {
312: ValueDataset result = null;
313: if (this .datasets.size() > index) {
314: result = (ValueDataset) this .datasets.get(index);
315: }
316: return result;
317: }
318:
319: /**
320: * Sets the dataset for the plot, replacing the existing dataset, if there
321: * is one, and sends a {@link PlotChangeEvent} to all registered
322: * listeners.
323: *
324: * @param dataset the dataset (<code>null</code> permitted).
325: */
326: public void setDataset(ValueDataset dataset) {
327: setDataset(0, dataset);
328: }
329:
330: /**
331: * Sets a dataset for the plot.
332: *
333: * @param index the dataset index.
334: * @param dataset the dataset (<code>null</code> permitted).
335: */
336: public void setDataset(int index, ValueDataset dataset) {
337:
338: ValueDataset existing = (ValueDataset) this .datasets.get(index);
339: if (existing != null) {
340: existing.removeChangeListener(this );
341: }
342: this .datasets.set(index, dataset);
343: if (dataset != null) {
344: dataset.addChangeListener(this );
345: }
346:
347: // send a dataset change event to self...
348: DatasetChangeEvent event = new DatasetChangeEvent(this , dataset);
349: datasetChanged(event);
350:
351: }
352:
353: /**
354: * Returns the number of datasets.
355: *
356: * @return The number of datasets.
357: */
358: public int getDatasetCount() {
359: return this .datasets.size();
360: }
361:
362: /**
363: * Draws the plot. This method is usually called by the {@link JFreeChart}
364: * instance that manages the plot.
365: *
366: * @param g2 the graphics target.
367: * @param area the area in which the plot should be drawn.
368: * @param anchor the anchor point (typically the last point that the
369: * mouse clicked on, <code>null</code> is permitted).
370: * @param parentState the state for the parent plot (if any).
371: * @param info used to collect plot rendering info (<code>null</code>
372: * permitted).
373: */
374: public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
375: PlotState parentState, PlotRenderingInfo info) {
376:
377: // first, expand the viewing area into a drawing frame
378: Rectangle2D frame = viewToFrame(area);
379:
380: // draw the background if there is one...
381: if (this .background != null && this .background.isVisible()) {
382: if (this .background.isClippedToWindow()) {
383: Shape savedClip = g2.getClip();
384: g2.setClip(this .dialFrame.getWindow(frame));
385: this .background.draw(g2, this , frame, area);
386: g2.setClip(savedClip);
387: } else {
388: this .background.draw(g2, this , frame, area);
389: }
390: }
391:
392: Iterator iterator = this .layers.iterator();
393: while (iterator.hasNext()) {
394: DialLayer current = (DialLayer) iterator.next();
395: if (current.isVisible()) {
396: if (current.isClippedToWindow()) {
397: Shape savedClip = g2.getClip();
398: g2.setClip(this .dialFrame.getWindow(frame));
399: current.draw(g2, this , frame, area);
400: g2.setClip(savedClip);
401: } else {
402: current.draw(g2, this , frame, area);
403: }
404: }
405: }
406:
407: // draw the cap if there is one...
408: if (this .cap != null && this .cap.isVisible()) {
409: if (this .cap.isClippedToWindow()) {
410: Shape savedClip = g2.getClip();
411: g2.setClip(this .dialFrame.getWindow(frame));
412: this .cap.draw(g2, this , frame, area);
413: g2.setClip(savedClip);
414: } else {
415: this .cap.draw(g2, this , frame, area);
416: }
417: }
418:
419: if (this .dialFrame.isVisible()) {
420: this .dialFrame.draw(g2, this , frame, area);
421: }
422:
423: }
424:
425: /**
426: * Returns the frame surrounding the specified view rectangle.
427: *
428: * @param view the view rectangle (<code>null</code> not permitted).
429: */
430: private Rectangle2D viewToFrame(Rectangle2D view) {
431: double width = view.getWidth() / this .viewW;
432: double height = view.getHeight() / this .viewH;
433: double x = view.getX() - (width * this .viewX);
434: double y = view.getY() - (height * this .viewY);
435: return new Rectangle2D.Double(x, y, width, height);
436: }
437:
438: /**
439: * Returns the value from the specified dataset.
440: *
441: * @param datasetIndex the dataset index.
442: *
443: * @return The data value.
444: */
445: public double getValue(int datasetIndex) {
446: double result = Double.NaN;
447: ValueDataset dataset = getDataset(datasetIndex);
448: if (dataset != null) {
449: Number n = dataset.getValue();
450: if (n != null) {
451: result = n.doubleValue();
452: }
453: }
454: return result;
455: }
456:
457: /**
458: * Adds a dial scale to the plot.
459: *
460: * @param index the scale index.
461: * @param scale the scale.
462: */
463: public void addScale(int index, DialScale scale) {
464: this .layers.add(scale);
465: this .scales.set(index, scale);
466: }
467:
468: /**
469: * Returns the scale at the given index.
470: *
471: * @param index the scale index.
472: *
473: * @return The scale (possibly <code>null</code>).
474: */
475: public DialScale getScale(int index) {
476: DialScale result = null;
477: if (this .scales.size() > index) {
478: result = (DialScale) this .scales.get(index);
479: }
480: return result;
481: }
482:
483: /**
484: * Maps a dataset to a particular scale.
485: *
486: * @param index the dataset index (zero-based).
487: * @param scaleIndex the scale index (zero-based).
488: */
489: public void mapDatasetToScale(int index, int scaleIndex) {
490: this .datasetToScaleMap.set(index, new Integer(scaleIndex));
491: notifyListeners(new PlotChangeEvent(this ));
492: }
493:
494: /**
495: * Returns the dial scale for a specific dataset.
496: *
497: * @param datasetIndex the dataset index.
498: *
499: * @return The dial scale.
500: */
501: public DialScale getScaleForDataset(int datasetIndex) {
502: DialScale result = (DialScale) this .scales.get(0);
503: Integer scaleIndex = (Integer) this .datasetToScaleMap
504: .get(datasetIndex);
505: if (scaleIndex != null) {
506: result = getScale(scaleIndex.intValue());
507: }
508: return result;
509: }
510:
511: /**
512: * A utility method that computes a rectangle using relative radius values.
513: *
514: * @param rect the reference rectangle.
515: * @param radiusW the width radius (must be > 0.0)
516: * @param radiusH the height radius.
517: *
518: * @return A new rectangle.
519: */
520: public static Rectangle2D rectangleByRadius(Rectangle2D rect,
521: double radiusW, double radiusH) {
522: double x = rect.getCenterX();
523: double y = rect.getCenterY();
524: double w = rect.getWidth() * radiusW;
525: double h = rect.getHeight() * radiusH;
526: return new Rectangle2D.Double(x - w / 2.0, y - h / 2.0, w, h);
527: }
528:
529: /**
530: * Receives notification when a layer has changed.
531: *
532: * @param event the event.
533: */
534: public void dialLayerChanged(DialLayerChangeEvent event) {
535: this .notifyListeners(new PlotChangeEvent(this ));
536: }
537:
538: /**
539: * Tests this <code>DialPlot</code> instance for equality with an
540: * arbitrary object. The plot's dataset(s) is (are) not included in
541: * the test.
542: *
543: * @param obj the object (<code>null</code> permitted).
544: *
545: * @return A boolean.
546: */
547: public boolean equals(Object obj) {
548: if (obj == this ) {
549: return true;
550: }
551: if (!(obj instanceof DialPlot)) {
552: return false;
553: }
554: DialPlot that = (DialPlot) obj;
555: if (!ObjectUtilities.equal(this .background, that.background)) {
556: return false;
557: }
558: if (!ObjectUtilities.equal(this .cap, that.cap)) {
559: return false;
560: }
561: if (!this .dialFrame.equals(that.dialFrame)) {
562: return false;
563: }
564: if (this .viewX != that.viewX) {
565: return false;
566: }
567: if (this .viewY != that.viewY) {
568: return false;
569: }
570: if (this .viewW != that.viewW) {
571: return false;
572: }
573: if (this .viewH != that.viewH) {
574: return false;
575: }
576: if (!this .layers.equals(that.layers)) {
577: return false;
578: }
579: return super .equals(obj);
580: }
581:
582: /**
583: * Returns a hash code for this instance.
584: *
585: * @return The hash code.
586: */
587: public int hashCode() {
588: int result = 193;
589: result = 37 * result
590: + ObjectUtilities.hashCode(this .background);
591: result = 37 * result + ObjectUtilities.hashCode(this .cap);
592: result = 37 * result + this .dialFrame.hashCode();
593: long temp = Double.doubleToLongBits(this .viewX);
594: result = 37 * result + (int) (temp ^ (temp >>> 32));
595: temp = Double.doubleToLongBits(this .viewY);
596: result = 37 * result + (int) (temp ^ (temp >>> 32));
597: temp = Double.doubleToLongBits(this .viewW);
598: result = 37 * result + (int) (temp ^ (temp >>> 32));
599: temp = Double.doubleToLongBits(this .viewH);
600: result = 37 * result + (int) (temp ^ (temp >>> 32));
601: return result;
602: }
603:
604: /**
605: * Returns the plot type.
606: *
607: * @return <code>"DialPlot"</code>
608: */
609: public String getPlotType() {
610: return "DialPlot";
611: }
612:
613: /**
614: * Provides serialization support.
615: *
616: * @param stream the output stream.
617: *
618: * @throws IOException if there is an I/O error.
619: */
620: private void writeObject(ObjectOutputStream stream)
621: throws IOException {
622: stream.defaultWriteObject();
623: }
624:
625: /**
626: * Provides serialization support.
627: *
628: * @param stream the input stream.
629: *
630: * @throws IOException if there is an I/O error.
631: * @throws ClassNotFoundException if there is a classpath problem.
632: */
633: private void readObject(ObjectInputStream stream)
634: throws IOException, ClassNotFoundException {
635: stream.defaultReadObject();
636: }
637:
638: }
|