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