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