Source for org.jfree.chart.plot.dial.DialPlot

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * -------------
  28:  * DialPlot.java
  29:  * -------------
  30:  * (C) Copyright 2006, 2007, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * Changes
  36:  * -------
  37:  * 03-Nov-2006 : Version 1 (DG);
  38:  * 08-Mar-2007 : Fix in hashCode() (DG);
  39:  * 17-Oct-2007 : Fixed listener registration/deregistration bugs (DG);
  40:  * 24-Oct-2007 : Maintain pointers in their own list, so they can be
  41:  *               drawn after other layers (DG);
  42:  * 
  43:  */
  44: 
  45: package org.jfree.chart.plot.dial;
  46: 
  47: import java.awt.Graphics2D;
  48: import java.awt.Shape;
  49: import java.awt.geom.Point2D;
  50: import java.awt.geom.Rectangle2D;
  51: import java.io.IOException;
  52: import java.io.ObjectInputStream;
  53: import java.io.ObjectOutputStream;
  54: import java.util.Iterator;
  55: import java.util.List;
  56: 
  57: import org.jfree.chart.JFreeChart;
  58: import org.jfree.chart.event.PlotChangeEvent;
  59: import org.jfree.chart.plot.Plot;
  60: import org.jfree.chart.plot.PlotRenderingInfo;
  61: import org.jfree.chart.plot.PlotState;
  62: import org.jfree.data.general.DatasetChangeEvent;
  63: import org.jfree.data.general.ValueDataset;
  64: import org.jfree.util.ObjectList;
  65: import org.jfree.util.ObjectUtilities;
  66: 
  67: /**
  68:  * A dial plot.
  69:  */
  70: public class DialPlot extends Plot implements DialLayerChangeListener {
  71: 
  72:     /**
  73:      * The background layer (optional).
  74:      */
  75:     private DialLayer background;
  76:     
  77:     /**
  78:      * The needle cap (optional).
  79:      */
  80:     private DialLayer cap;
  81:     
  82:     /**
  83:      * The dial frame.
  84:      */
  85:     private DialFrame dialFrame;
  86:     
  87:     /**
  88:      * The dataset(s) for the dial plot.
  89:      */
  90:     private ObjectList datasets;
  91:     
  92:     /**
  93:      * The scale(s) for the dial plot. 
  94:      */
  95:     private ObjectList scales;
  96:     
  97:     /** Storage for keys that map datasets to scales. */
  98:     private ObjectList datasetToScaleMap;
  99: 
 100:     /**
 101:      * The drawing layers for the dial plot.
 102:      */
 103:     private List layers;
 104:     
 105:     /** 
 106:      * The pointer(s) for the dial.
 107:      */
 108:     private List pointers;
 109:     
 110:     /**
 111:      * The x-coordinate for the view window.
 112:      */
 113:     private double viewX;
 114:     
 115:     /**
 116:      * The y-coordinate for the view window.
 117:      */
 118:     private double viewY;
 119:     
 120:     /**
 121:      * The width of the view window, expressed as a percentage.
 122:      */
 123:     private double viewW;
 124:     
 125:     /**
 126:      * The height of the view window, expressed as a percentage.
 127:      */
 128:     private double viewH;
 129:     
 130:     /** 
 131:      * Creates a new instance of <code>DialPlot</code>.
 132:      */
 133:     public DialPlot() {
 134:         this(null);    
 135:     }
 136:     
 137:     /** 
 138:      * Creates a new instance of <code>DialPlot</code>.
 139:      * 
 140:      * @param dataset  the dataset (<code>null</code> permitted).
 141:      */
 142:     public DialPlot(ValueDataset dataset) {
 143:         this.background = null;
 144:         this.cap = null;
 145:         this.dialFrame = new ArcDialFrame();
 146:         this.datasets = new ObjectList();
 147:         if (dataset != null) {
 148:             this.setDataset(dataset);  
 149:         }
 150:         this.scales = new ObjectList();
 151:         this.datasetToScaleMap = new ObjectList();
 152:         this.layers = new java.util.ArrayList();
 153:         this.pointers = new java.util.ArrayList();
 154:         this.viewX = 0.0;
 155:         this.viewY = 0.0;
 156:         this.viewW = 1.0;
 157:         this.viewH = 1.0;
 158:     }
 159: 
 160:     /**
 161:      * Returns the background.
 162:      *
 163:      * @return The background (possibly <code>null</code>).
 164:      *
 165:      * @see #setBackground(DialLayer)
 166:      */
 167:     public DialLayer getBackground() {
 168:         return this.background;
 169:     }
 170:     
 171:     /**
 172:      * Sets the background layer and sends a {@link PlotChangeEvent} to all
 173:      * registered listeners.
 174:      *
 175:      * @param background  the background layer (<code>null</code> permitted).
 176:      *
 177:      * @see #getBackground()
 178:      */
 179:     public void setBackground(DialLayer background) {
 180:         if (this.background != null) {
 181:             this.background.removeChangeListener(this);
 182:         }
 183:         this.background = background;
 184:         if (background != null) {
 185:             background.addChangeListener(this);
 186:         }
 187:         notifyListeners(new PlotChangeEvent(this));
 188:     }
 189:     
 190:     /**
 191:      * Returns the cap.
 192:      *
 193:      * @return The cap (possibly <code>null</code>).
 194:      *
 195:      * @see #setCap(DialLayer)
 196:      */
 197:     public DialLayer getCap() {
 198:         return this.cap;
 199:     }
 200:     
 201:     /**
 202:      * Sets the cap and sends a {@link PlotChangeEvent} to all registered 
 203:      * listeners.
 204:      *
 205:      * @param cap  the cap (<code>null</code> permitted).
 206:      *
 207:      * @see #getCap()
 208:      */
 209:     public void setCap(DialLayer cap) {
 210:         if (this.cap != null) {
 211:             this.cap.removeChangeListener(this);
 212:         }
 213:         this.cap = cap;
 214:         if (cap != null) {
 215:             cap.addChangeListener(this);
 216:         }
 217:         notifyListeners(new PlotChangeEvent(this));
 218:     }
 219: 
 220:     /**
 221:      * Returns the dial's frame.
 222:      *
 223:      * @return The dial's frame (never <code>null</code>).
 224:      *
 225:      * @see #setDialFrame(DialFrame)
 226:      */
 227:     public DialFrame getDialFrame() {
 228:         return this.dialFrame;
 229:     }
 230:     
 231:     /**
 232:      * Sets the dial's frame and sends a {@link PlotChangeEvent} to all 
 233:      * registered listeners.
 234:      *
 235:      * @param frame  the frame (<code>null</code> not permitted).
 236:      *
 237:      * @see #getDialFrame()
 238:      */
 239:     public void setDialFrame(DialFrame frame) {
 240:         if (frame == null) {
 241:             throw new IllegalArgumentException("Null 'frame' argument.");
 242:         }
 243:         this.dialFrame.removeChangeListener(this);
 244:         this.dialFrame = frame;
 245:         frame.addChangeListener(this);
 246:         notifyListeners(new PlotChangeEvent(this));
 247:     }
 248: 
 249:     /**
 250:      * Returns the x-coordinate 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 x-coordinate of the viewing rectangle.
 254:      * 
 255:      * @see #setView(double, double, double, double)
 256:      */
 257:     public double getViewX() {
 258:         return this.viewX;
 259:     }
 260:     
 261:     /**
 262:      * Returns the y-coordinate of the viewing rectangle.  This is specified
 263:      * in the range 0.0 to 1.0, relative to the dial's framing rectangle.
 264:      * 
 265:      * @return The y-coordinate of the viewing rectangle.
 266:      * 
 267:      * @see #setView(double, double, double, double)
 268:      */
 269:     public double getViewY() {
 270:         return this.viewY;
 271:     }
 272:     
 273:     /**
 274:      * Returns the width of the viewing rectangle.  This is specified
 275:      * in the range 0.0 to 1.0, relative to the dial's framing rectangle.
 276:      * 
 277:      * @return The width of the viewing rectangle.
 278:      * 
 279:      * @see #setView(double, double, double, double)
 280:      */
 281:     public double getViewWidth() {
 282:         return this.viewW;
 283:     }
 284:     
 285:     /**
 286:      * Returns the height of the viewing rectangle.  This is specified
 287:      * in the range 0.0 to 1.0, relative to the dial's framing rectangle.
 288:      * 
 289:      * @return The height of the viewing rectangle.
 290:      * 
 291:      * @see #setView(double, double, double, double)
 292:      */
 293:     public double getViewHeight() {
 294:         return this.viewH;
 295:     }
 296:     
 297:     /**
 298:      * Sets the viewing rectangle, relative to the dial's framing rectangle,
 299:      * and sends a {@link PlotChangeEvent} to all registered listeners.
 300:      * 
 301:      * @param x  the x-coordinate (in the range 0.0 to 1.0).
 302:      * @param y  the y-coordinate (in the range 0.0 to 1.0).
 303:      * @param w  the width (in the range 0.0 to 1.0).
 304:      * @param h  the height (in the range 0.0 to 1.0).
 305:      * 
 306:      * @see #getViewX()
 307:      * @see #getViewY()
 308:      * @see #getViewWidth()
 309:      * @see #getViewHeight()
 310:      */
 311:     public void setView(double x, double y, double w, double h) {
 312:         this.viewX = x;
 313:         this.viewY = y;
 314:         this.viewW = w;
 315:         this.viewH = h;
 316:         notifyListeners(new PlotChangeEvent(this));
 317:     }
 318: 
 319:     /**
 320:      * Adds a layer to the plot and sends a {@link PlotChangeEvent} to all 
 321:      * registered listeners.
 322:      * 
 323:      * @param layer  the layer (<code>null</code> not permitted).
 324:      */
 325:     public void addLayer(DialLayer layer) {
 326:         if (layer == null) {
 327:             throw new IllegalArgumentException("Null 'layer' argument.");
 328:         }
 329:         this.layers.add(layer);
 330:         layer.addChangeListener(this);
 331:         notifyListeners(new PlotChangeEvent(this));
 332:     }
 333:     
 334:     /**
 335:      * Returns the index for the specified layer.
 336:      * 
 337:      * @param layer  the layer (<code>null</code> not permitted).
 338:      * 
 339:      * @return The layer index.
 340:      */
 341:     public int getLayerIndex(DialLayer layer) {
 342:         if (layer == null) {
 343:             throw new IllegalArgumentException("Null 'layer' argument.");
 344:         }
 345:         return this.layers.indexOf(layer);
 346:     }
 347:     
 348:     /**
 349:      * Removes the layer at the specified index and sends a 
 350:      * {@link PlotChangeEvent} to all registered listeners.
 351:      * 
 352:      * @param index  the index.
 353:      */
 354:     public void removeLayer(int index) {
 355:         DialLayer layer = (DialLayer) this.layers.get(index);
 356:         if (layer != null) {
 357:             layer.removeChangeListener(this);
 358:         }
 359:         this.layers.remove(index);
 360:         notifyListeners(new PlotChangeEvent(this));
 361:     }
 362:     
 363:     /**
 364:      * Removes the specified layer and sends a {@link PlotChangeEvent} to all
 365:      * registered listeners.
 366:      * 
 367:      * @param layer  the layer (<code>null</code> not permitted).
 368:      */
 369:     public void removeLayer(DialLayer layer) {
 370:         // defer argument checking
 371:         removeLayer(getLayerIndex(layer));
 372:     }
 373:     
 374:     /**
 375:      * Adds a pointer to the plot and sends a {@link PlotChangeEvent} to all 
 376:      * registered listeners.
 377:      * 
 378:      * @param pointer  the pointer (<code>null</code> not permitted).
 379:      */
 380:     public void addPointer(DialPointer pointer) {
 381:         if (pointer == null) {
 382:             throw new IllegalArgumentException("Null 'pointer' argument.");
 383:         }
 384:         this.pointers.add(pointer);
 385:         pointer.addChangeListener(this);
 386:         notifyListeners(new PlotChangeEvent(this));
 387:     }
 388:     
 389:     /**
 390:      * Returns the index for the specified pointer.
 391:      * 
 392:      * @param pointer  the pointer (<code>null</code> not permitted).
 393:      * 
 394:      * @return The pointer index.
 395:      */
 396:     public int getPointerIndex(DialPointer pointer) {
 397:         if (pointer == null) {
 398:             throw new IllegalArgumentException("Null 'pointer' argument.");
 399:         }
 400:         return this.pointers.indexOf(pointer);
 401:     }
 402:     
 403:     /**
 404:      * Removes the pointer at the specified index and sends a 
 405:      * {@link PlotChangeEvent} to all registered listeners.
 406:      * 
 407:      * @param index  the index.
 408:      */
 409:     public void removePointer(int index) {
 410:         DialPointer pointer = (DialPointer) this.pointers.get(index);
 411:         if (pointer != null) {
 412:             pointer.removeChangeListener(this);
 413:         }
 414:         this.pointers.remove(index);
 415:         notifyListeners(new PlotChangeEvent(this));
 416:     }
 417:     
 418:     /**
 419:      * Removes the specified pointer and sends a {@link PlotChangeEvent} to all
 420:      * registered listeners.
 421:      * 
 422:      * @param pointer  the pointer (<code>null</code> not permitted).
 423:      */
 424:     public void removePointer(DialPointer pointer) {
 425:         // defer argument checking
 426:         removeLayer(getPointerIndex(pointer));
 427:     }
 428: 
 429:     /**
 430:      * Returns the dial pointer that is associated with the specified
 431:      * dataset, or <code>null</code>.
 432:      * 
 433:      * @param datasetIndex  the dataset index.
 434:      * 
 435:      * @return The pointer.
 436:      */
 437:     public DialPointer getPointerForDataset(int datasetIndex) {
 438:         DialPointer result = null;
 439:         Iterator iterator = this.pointers.iterator();
 440:         while (iterator.hasNext()) {
 441:             DialPointer p = (DialPointer) iterator.next();
 442:             if (p.getDatasetIndex() == datasetIndex) {
 443:                 return p;
 444:             }
 445:         }
 446:         return result;
 447:     }
 448:     
 449:     /**
 450:      * Returns the primary dataset for the plot.
 451:      *
 452:      * @return The primary dataset (possibly <code>null</code>).
 453:      */
 454:     public ValueDataset getDataset() {
 455:         return getDataset(0);
 456:     }
 457: 
 458:     /**
 459:      * Returns the dataset at the given index.
 460:      *
 461:      * @param index  the dataset index.
 462:      *
 463:      * @return The dataset (possibly <code>null</code>).
 464:      */
 465:     public ValueDataset getDataset(int index) {
 466:         ValueDataset result = null;
 467:         if (this.datasets.size() > index) {
 468:             result = (ValueDataset) this.datasets.get(index);
 469:         }
 470:         return result;
 471:     }
 472: 
 473:     /**
 474:      * Sets the dataset for the plot, replacing the existing dataset, if there 
 475:      * is one, and sends a {@link PlotChangeEvent} to all registered 
 476:      * listeners.
 477:      *
 478:      * @param dataset  the dataset (<code>null</code> permitted).
 479:      */
 480:     public void setDataset(ValueDataset dataset) {
 481:         setDataset(0, dataset);
 482:     }
 483: 
 484:     /**
 485:      * Sets a dataset for the plot.
 486:      *
 487:      * @param index  the dataset index.
 488:      * @param dataset  the dataset (<code>null</code> permitted).
 489:      */
 490:     public void setDataset(int index, ValueDataset dataset) {
 491:         
 492:         ValueDataset existing = (ValueDataset) this.datasets.get(index);
 493:         if (existing != null) {
 494:             existing.removeChangeListener(this);
 495:         }
 496:         this.datasets.set(index, dataset);
 497:         if (dataset != null) {
 498:             dataset.addChangeListener(this);
 499:         }
 500:         
 501:         // send a dataset change event to self...
 502:         DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
 503:         datasetChanged(event);
 504:         
 505:     }
 506: 
 507:     /**
 508:      * Returns the number of datasets.
 509:      *
 510:      * @return The number of datasets.
 511:      */
 512:     public int getDatasetCount() {
 513:         return this.datasets.size();
 514:     }    
 515:     
 516:     /**
 517:      * Draws the plot.  This method is usually called by the {@link JFreeChart}
 518:      * instance that manages the plot.
 519:      * 
 520:      * @param g2  the graphics target.
 521:      * @param area  the area in which the plot should be drawn.
 522:      * @param anchor  the anchor point (typically the last point that the 
 523:      *     mouse clicked on, <code>null</code> is permitted).
 524:      * @param parentState  the state for the parent plot (if any).
 525:      * @param info  used to collect plot rendering info (<code>null</code> 
 526:      *     permitted).
 527:      */
 528:     public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, 
 529:             PlotState parentState, PlotRenderingInfo info) {
 530:         
 531:         // first, expand the viewing area into a drawing frame
 532:         Rectangle2D frame = viewToFrame(area);
 533:         
 534:         // draw the background if there is one...
 535:         if (this.background != null && this.background.isVisible()) {
 536:             if (this.background.isClippedToWindow()) {
 537:                 Shape savedClip = g2.getClip();
 538:                 g2.setClip(this.dialFrame.getWindow(frame));
 539:                 this.background.draw(g2, this, frame, area);
 540:                 g2.setClip(savedClip);
 541:             }
 542:             else {
 543:                 this.background.draw(g2, this, frame, area);
 544:             }
 545:         }
 546:         
 547:         Iterator iterator = this.layers.iterator();
 548:         while (iterator.hasNext()) {
 549:             DialLayer current = (DialLayer) iterator.next();
 550:             if (current.isVisible()) {
 551:                 if (current.isClippedToWindow()) {
 552:                     Shape savedClip = g2.getClip();
 553:                     g2.setClip(this.dialFrame.getWindow(frame));
 554:                     current.draw(g2, this, frame, area);
 555:                     g2.setClip(savedClip);
 556:                 }
 557:                 else {
 558:                     current.draw(g2, this, frame, area);
 559:                 }
 560:             }
 561:         }
 562:         
 563:         // draw the pointers
 564:         iterator = this.pointers.iterator();
 565:         while (iterator.hasNext()) {
 566:             DialPointer current = (DialPointer) iterator.next();
 567:             if (current.isVisible()) {
 568:                 if (current.isClippedToWindow()) {
 569:                     Shape savedClip = g2.getClip();
 570:                     g2.setClip(this.dialFrame.getWindow(frame));
 571:                     current.draw(g2, this, frame, area);
 572:                     g2.setClip(savedClip);
 573:                 }
 574:                 else {
 575:                     current.draw(g2, this, frame, area);
 576:                 }
 577:             }
 578:         }
 579: 
 580:         // draw the cap if there is one...
 581:         if (this.cap != null && this.cap.isVisible()) {
 582:             if (this.cap.isClippedToWindow()) {
 583:                 Shape savedClip = g2.getClip();
 584:                 g2.setClip(this.dialFrame.getWindow(frame));
 585:                 this.cap.draw(g2, this, frame, area);
 586:                 g2.setClip(savedClip);
 587:             }
 588:             else {
 589:                 this.cap.draw(g2, this, frame, area);
 590:             }
 591:         }
 592:         
 593:         if (this.dialFrame.isVisible()) {
 594:             this.dialFrame.draw(g2, this, frame, area);
 595:         }
 596:         
 597:     }
 598:     
 599:     /**
 600:      * Returns the frame surrounding the specified view rectangle.
 601:      * 
 602:      * @param view  the view rectangle (<code>null</code> not permitted).
 603:      * 
 604:      * @return The frame rectangle.
 605:      */
 606:     private Rectangle2D viewToFrame(Rectangle2D view) {
 607:         double width = view.getWidth() / this.viewW;
 608:         double height = view.getHeight() / this.viewH;
 609:         double x = view.getX() - (width * this.viewX);
 610:         double y = view.getY() - (height * this.viewY);
 611:         return new Rectangle2D.Double(x, y, width, height);
 612:     }
 613:     
 614:     /**
 615:      * Returns the value from the specified dataset.
 616:      * 
 617:      * @param datasetIndex  the dataset index.
 618:      * 
 619:      * @return The data value.
 620:      */
 621:     public double getValue(int datasetIndex) {
 622:         double result = Double.NaN;
 623:         ValueDataset dataset = getDataset(datasetIndex);
 624:         if (dataset != null) {
 625:             Number n = dataset.getValue();
 626:             if (n != null) {
 627:                 result = n.doubleValue();
 628:             }
 629:         }
 630:         return result;
 631:     }
 632:     
 633:     /**
 634:      * Adds a dial scale to the plot and sends a {@link PlotChangeEvent} to 
 635:      * all registered listeners.
 636:      * 
 637:      * @param index  the scale index.
 638:      * @param scale  the scale (<code>null</code> not permitted).
 639:      */
 640:     public void addScale(int index, DialScale scale) {
 641:         if (scale == null) {
 642:             throw new IllegalArgumentException("Null 'scale' argument.");
 643:         }
 644:         DialScale existing = (DialScale) this.scales.get(index);
 645:         if (existing != null) {
 646:             removeLayer(existing);
 647:         }
 648:         this.layers.add(scale);
 649:         this.scales.set(index, scale);
 650:         scale.addChangeListener(this);
 651:         notifyListeners(new PlotChangeEvent(this));         
 652:     }
 653:     
 654:     /**
 655:      * Returns the scale at the given index.
 656:      *
 657:      * @param index  the scale index.
 658:      *
 659:      * @return The scale (possibly <code>null</code>).
 660:      */
 661:     public DialScale getScale(int index) {
 662:         DialScale result = null;
 663:         if (this.scales.size() > index) {
 664:             result = (DialScale) this.scales.get(index);
 665:         }
 666:         return result;
 667:     }
 668: 
 669:     /**
 670:      * Maps a dataset to a particular scale.
 671:      * 
 672:      * @param index  the dataset index (zero-based).
 673:      * @param scaleIndex  the scale index (zero-based).
 674:      */
 675:     public void mapDatasetToScale(int index, int scaleIndex) {
 676:         this.datasetToScaleMap.set(index, new Integer(scaleIndex));  
 677:         notifyListeners(new PlotChangeEvent(this)); 
 678:     }
 679:     
 680:     /**
 681:      * Returns the dial scale for a specific dataset.
 682:      * 
 683:      * @param datasetIndex  the dataset index.
 684:      * 
 685:      * @return The dial scale.
 686:      */
 687:     public DialScale getScaleForDataset(int datasetIndex) {
 688:         DialScale result = (DialScale) this.scales.get(0);    
 689:         Integer scaleIndex = (Integer) this.datasetToScaleMap.get(datasetIndex);
 690:         if (scaleIndex != null) {
 691:             result = getScale(scaleIndex.intValue());
 692:         }
 693:         return result;    
 694:     }
 695:     
 696:     /**
 697:      * A utility method that computes a rectangle using relative radius values.
 698:      * 
 699:      * @param rect  the reference rectangle (<code>null</code> not permitted).
 700:      * @param radiusW  the width radius (must be > 0.0)
 701:      * @param radiusH  the height radius.
 702:      * 
 703:      * @return A new rectangle.
 704:      */
 705:     public static Rectangle2D rectangleByRadius(Rectangle2D rect, 
 706:             double radiusW, double radiusH) {
 707:         if (rect == null) {
 708:             throw new IllegalArgumentException("Null 'rect' argument.");
 709:         }
 710:         double x = rect.getCenterX();
 711:         double y = rect.getCenterY();
 712:         double w = rect.getWidth() * radiusW;
 713:         double h = rect.getHeight() * radiusH;
 714:         return new Rectangle2D.Double(x - w / 2.0, y - h / 2.0, w, h);
 715:     }
 716:     
 717:     /**
 718:      * Receives notification when a layer has changed, and responds by 
 719:      * forwarding a {@link PlotChangeEvent} to all registered listeners.
 720:      * 
 721:      * @param event  the event.
 722:      */
 723:     public void dialLayerChanged(DialLayerChangeEvent event) {
 724:         this.notifyListeners(new PlotChangeEvent(this));
 725:     }
 726: 
 727:     /**
 728:      * Tests this <code>DialPlot</code> instance for equality with an 
 729:      * arbitrary object.  The plot's dataset(s) is (are) not included in 
 730:      * the test.
 731:      *
 732:      * @param obj  the object (<code>null</code> permitted).
 733:      *
 734:      * @return A boolean.
 735:      */
 736:     public boolean equals(Object obj) {
 737:         if (obj == this) {
 738:             return true;
 739:         }
 740:         if (!(obj instanceof DialPlot)) {
 741:             return false;
 742:         }
 743:         DialPlot that = (DialPlot) obj;
 744:         if (!ObjectUtilities.equal(this.background, that.background)) {
 745:             return false;
 746:         }
 747:         if (!ObjectUtilities.equal(this.cap, that.cap)) {
 748:             return false;
 749:         }
 750:         if (!this.dialFrame.equals(that.dialFrame)) {
 751:             return false;
 752:         }
 753:         if (this.viewX != that.viewX) {
 754:             return false;
 755:         }
 756:         if (this.viewY != that.viewY) {
 757:             return false;
 758:         }
 759:         if (this.viewW != that.viewW) {
 760:             return false;
 761:         }
 762:         if (this.viewH != that.viewH) {
 763:             return false;
 764:         }
 765:         if (!this.layers.equals(that.layers)) {
 766:             return false;
 767:         }
 768:         if (!this.pointers.equals(that.pointers)) {
 769:             return false;
 770:         }
 771:         return super.equals(obj);
 772:     }
 773: 
 774:     /**
 775:      * Returns a hash code for this instance.
 776:      * 
 777:      * @return The hash code.
 778:      */
 779:     public int hashCode() {
 780:         int result = 193;
 781:         result = 37 * result + ObjectUtilities.hashCode(this.background);
 782:         result = 37 * result + ObjectUtilities.hashCode(this.cap);
 783:         result = 37 * result + this.dialFrame.hashCode();
 784:         long temp = Double.doubleToLongBits(this.viewX);
 785:         result = 37 * result + (int) (temp ^ (temp >>> 32));
 786:         temp = Double.doubleToLongBits(this.viewY);
 787:         result = 37 * result + (int) (temp ^ (temp >>> 32));
 788:         temp = Double.doubleToLongBits(this.viewW);
 789:         result = 37 * result + (int) (temp ^ (temp >>> 32));
 790:         temp = Double.doubleToLongBits(this.viewH);
 791:         result = 37 * result + (int) (temp ^ (temp >>> 32));
 792:         return result;
 793:     }
 794:     
 795:     /**
 796:      * Returns the plot type.
 797:      * 
 798:      * @return <code>"DialPlot"</code>
 799:      */
 800:     public String getPlotType() {
 801:         return "DialPlot";
 802:     }
 803:     
 804:     /**
 805:      * Provides serialization support.
 806:      *
 807:      * @param stream  the output stream.
 808:      *
 809:      * @throws IOException  if there is an I/O error.
 810:      */
 811:     private void writeObject(ObjectOutputStream stream) throws IOException {
 812:         stream.defaultWriteObject();
 813:     }
 814: 
 815:     /**
 816:      * Provides serialization support.
 817:      *
 818:      * @param stream  the input stream.
 819:      *
 820:      * @throws IOException  if there is an I/O error.
 821:      * @throws ClassNotFoundException  if there is a classpath problem.
 822:      */
 823:     private void readObject(ObjectInputStream stream) 
 824:             throws IOException, ClassNotFoundException {
 825:         stream.defaultReadObject();
 826:     }
 827: 
 828:     
 829: }