001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * -------------------- 028 * ThermometerPlot.java 029 * -------------------- 030 * 031 * (C) Copyright 2000-present, by Bryan Scott and Contributors. 032 * 033 * Original Author: Bryan Scott (based on MeterPlot by Hari). 034 * Contributor(s): David Gilbert. 035 * Arnaud Lelievre; 036 * Julien Henry (see patch 1769088) (DG); 037 */ 038 039package org.jfree.chart.plot; 040 041import java.awt.BasicStroke; 042import java.awt.Color; 043import java.awt.Font; 044import java.awt.FontMetrics; 045import java.awt.Graphics2D; 046import java.awt.Paint; 047import java.awt.Stroke; 048import java.awt.geom.Area; 049import java.awt.geom.Ellipse2D; 050import java.awt.geom.Line2D; 051import java.awt.geom.Point2D; 052import java.awt.geom.Rectangle2D; 053import java.awt.geom.RoundRectangle2D; 054import java.io.IOException; 055import java.io.ObjectInputStream; 056import java.io.ObjectOutputStream; 057import java.io.Serializable; 058import java.text.DecimalFormat; 059import java.text.NumberFormat; 060import java.util.Arrays; 061import java.util.Objects; 062import java.util.ResourceBundle; 063 064import org.jfree.chart.LegendItemCollection; 065import org.jfree.chart.axis.NumberAxis; 066import org.jfree.chart.axis.ValueAxis; 067import org.jfree.chart.event.PlotChangeEvent; 068import org.jfree.chart.ui.RectangleEdge; 069import org.jfree.chart.ui.RectangleInsets; 070import org.jfree.chart.util.ObjectUtils; 071import org.jfree.chart.util.PaintUtils; 072import org.jfree.chart.util.Args; 073import org.jfree.chart.util.ResourceBundleWrapper; 074import org.jfree.chart.util.SerialUtils; 075import org.jfree.chart.util.UnitType; 076import org.jfree.data.Range; 077import org.jfree.data.general.DatasetChangeEvent; 078import org.jfree.data.general.DefaultValueDataset; 079import org.jfree.data.general.ValueDataset; 080 081/** 082 * A plot that displays a single value (from a {@link ValueDataset}) in a 083 * thermometer type display. 084 * <p> 085 * This plot supports a number of options: 086 * <ol> 087 * <li>three sub-ranges which could be viewed as 'Normal', 'Warning' 088 * and 'Critical' ranges.</li> 089 * <li>the thermometer can be run in two modes: 090 * <ul> 091 * <li>fixed range, or</li> 092 * <li>range adjusts to current sub-range.</li> 093 * </ul> 094 * </li> 095 * <li>settable units to be displayed.</li> 096 * <li>settable display location for the value text.</li> 097 * </ol> 098 */ 099public class ThermometerPlot extends Plot implements ValueAxisPlot, 100 Zoomable, Cloneable, Serializable { 101 102 /** For serialization. */ 103 private static final long serialVersionUID = 4087093313147984390L; 104 105 /** A constant for unit type 'None'. */ 106 public static final int UNITS_NONE = 0; 107 108 /** A constant for unit type 'Fahrenheit'. */ 109 public static final int UNITS_FAHRENHEIT = 1; 110 111 /** A constant for unit type 'Celcius'. */ 112 public static final int UNITS_CELCIUS = 2; 113 114 /** A constant for unit type 'Kelvin'. */ 115 public static final int UNITS_KELVIN = 3; 116 117 /** A constant for the value label position (no label). */ 118 public static final int NONE = 0; 119 120 /** A constant for the value label position (right of the thermometer). */ 121 public static final int RIGHT = 1; 122 123 /** A constant for the value label position (left of the thermometer). */ 124 public static final int LEFT = 2; 125 126 /** A constant for the value label position (in the thermometer bulb). */ 127 public static final int BULB = 3; 128 129 /** A constant for the 'normal' range. */ 130 public static final int NORMAL = 0; 131 132 /** A constant for the 'warning' range. */ 133 public static final int WARNING = 1; 134 135 /** A constant for the 'critical' range. */ 136 public static final int CRITICAL = 2; 137 138 /** The axis gap. */ 139 protected static final int AXIS_GAP = 10; 140 141 /** The unit strings. */ 142 protected static final String[] UNITS = {"", "\u00B0F", "\u00B0C", 143 "\u00B0K"}; 144 145 /** Index for low value in subrangeInfo matrix. */ 146 protected static final int RANGE_LOW = 0; 147 148 /** Index for high value in subrangeInfo matrix. */ 149 protected static final int RANGE_HIGH = 1; 150 151 /** Index for display low value in subrangeInfo matrix. */ 152 protected static final int DISPLAY_LOW = 2; 153 154 /** Index for display high value in subrangeInfo matrix. */ 155 protected static final int DISPLAY_HIGH = 3; 156 157 /** The default lower bound. */ 158 protected static final double DEFAULT_LOWER_BOUND = 0.0; 159 160 /** The default upper bound. */ 161 protected static final double DEFAULT_UPPER_BOUND = 100.0; 162 163 /** 164 * The default bulb radius. 165 */ 166 protected static final int DEFAULT_BULB_RADIUS = 40; 167 168 /** 169 * The default column radius. 170 */ 171 protected static final int DEFAULT_COLUMN_RADIUS = 20; 172 173 /** 174 * The default gap between the outlines representing the thermometer. 175 */ 176 protected static final int DEFAULT_GAP = 5; 177 178 /** The dataset for the plot. */ 179 private ValueDataset dataset; 180 181 /** The range axis. */ 182 private ValueAxis rangeAxis; 183 184 /** The lower bound for the thermometer. */ 185 private double lowerBound = DEFAULT_LOWER_BOUND; 186 187 /** The upper bound for the thermometer. */ 188 private double upperBound = DEFAULT_UPPER_BOUND; 189 190 /** 191 * The value label position. 192 */ 193 private int bulbRadius = DEFAULT_BULB_RADIUS; 194 195 /** 196 * The column radius. 197 */ 198 private int columnRadius = DEFAULT_COLUMN_RADIUS; 199 200 /** 201 * The gap between the two outlines the represent the thermometer. 202 */ 203 private int gap = DEFAULT_GAP; 204 205 /** 206 * Blank space inside the plot area around the outside of the thermometer. 207 */ 208 private RectangleInsets padding; 209 210 /** Stroke for drawing the thermometer */ 211 private transient Stroke thermometerStroke = new BasicStroke(1.0f); 212 213 /** Paint for drawing the thermometer */ 214 private transient Paint thermometerPaint = Color.BLACK; 215 216 /** The display units */ 217 private int units = UNITS_CELCIUS; 218 219 /** The value label position. */ 220 private int valueLocation = BULB; 221 222 /** The position of the axis **/ 223 private int axisLocation = LEFT; 224 225 /** The font to write the value in */ 226 private Font valueFont = new Font("SansSerif", Font.BOLD, 16); 227 228 /** Colour that the value is written in */ 229 private transient Paint valuePaint = Color.WHITE; 230 231 /** Number format for the value */ 232 private NumberFormat valueFormat = new DecimalFormat(); 233 234 /** The default paint for the mercury in the thermometer. */ 235 private transient Paint mercuryPaint = Color.LIGHT_GRAY; 236 237 /** A flag that controls whether value lines are drawn. */ 238 private boolean showValueLines = false; 239 240 /** The display sub-range. */ 241 private int subrange = -1; 242 243 /** The start and end values for the subranges. */ 244 private double[][] subrangeInfo = { 245 {0.0, 50.0, 0.0, 50.0}, 246 {50.0, 75.0, 50.0, 75.0}, 247 {75.0, 100.0, 75.0, 100.0} 248 }; 249 250 /** 251 * A flag that controls whether or not the axis range adjusts to the 252 * sub-ranges. 253 */ 254 private boolean followDataInSubranges = false; 255 256 /** 257 * A flag that controls whether or not the mercury paint changes with 258 * the subranges. 259 */ 260 private boolean useSubrangePaint = true; 261 262 /** Paint for each range */ 263 private transient Paint[] subrangePaint = {Color.GREEN, Color.ORANGE, 264 Color.RED}; 265 266 /** A flag that controls whether the sub-range indicators are visible. */ 267 private boolean subrangeIndicatorsVisible = true; 268 269 /** The stroke for the sub-range indicators. */ 270 private transient Stroke subrangeIndicatorStroke = new BasicStroke(2.0f); 271 272 /** The range indicator stroke. */ 273 private transient Stroke rangeIndicatorStroke = new BasicStroke(3.0f); 274 275 /** The resourceBundle for the localization. */ 276 protected static ResourceBundle localizationResources 277 = ResourceBundleWrapper.getBundle( 278 "org.jfree.chart.plot.LocalizationBundle"); 279 280 /** 281 * Creates a new thermometer plot. 282 */ 283 public ThermometerPlot() { 284 this(new DefaultValueDataset()); 285 } 286 287 /** 288 * Creates a new thermometer plot, using default attributes where necessary. 289 * 290 * @param dataset the data set. 291 */ 292 public ThermometerPlot(ValueDataset dataset) { 293 294 super(); 295 296 this.padding = new RectangleInsets(UnitType.RELATIVE, 0.05, 0.05, 0.05, 297 0.05); 298 this.dataset = dataset; 299 if (dataset != null) { 300 dataset.addChangeListener(this); 301 } 302 NumberAxis axis = new NumberAxis(null); 303 axis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); 304 axis.setAxisLineVisible(false); 305 axis.setPlot(this); 306 axis.addChangeListener(this); 307 this.rangeAxis = axis; 308 setAxisRange(); 309 } 310 311 /** 312 * Returns the dataset for the plot. 313 * 314 * @return The dataset (possibly {@code null}). 315 * 316 * @see #setDataset(ValueDataset) 317 */ 318 public ValueDataset getDataset() { 319 return this.dataset; 320 } 321 322 /** 323 * Sets the dataset for the plot, replacing the existing dataset if there 324 * is one, and sends a {@link PlotChangeEvent} to all registered listeners. 325 * 326 * @param dataset the dataset ({@code null} permitted). 327 * 328 * @see #getDataset() 329 */ 330 public void setDataset(ValueDataset dataset) { 331 332 // if there is an existing dataset, remove the plot from the list 333 // of change listeners... 334 ValueDataset existing = this.dataset; 335 if (existing != null) { 336 existing.removeChangeListener(this); 337 } 338 339 // set the new dataset, and register the chart as a change listener... 340 this.dataset = dataset; 341 if (dataset != null) { 342 setDatasetGroup(dataset.getGroup()); 343 dataset.addChangeListener(this); 344 } 345 346 // send a dataset change event to self... 347 DatasetChangeEvent event = new DatasetChangeEvent(this, dataset); 348 datasetChanged(event); 349 350 } 351 352 /** 353 * Returns the range axis. 354 * 355 * @return The range axis (never {@code null}). 356 * 357 * @see #setRangeAxis(ValueAxis) 358 */ 359 public ValueAxis getRangeAxis() { 360 return this.rangeAxis; 361 } 362 363 /** 364 * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to 365 * all registered listeners. 366 * 367 * @param axis the new axis ({@code null} not permitted). 368 * 369 * @see #getRangeAxis() 370 */ 371 public void setRangeAxis(ValueAxis axis) { 372 Args.nullNotPermitted(axis, "axis"); 373 // plot is registered as a listener with the existing axis... 374 this.rangeAxis.removeChangeListener(this); 375 376 axis.setPlot(this); 377 axis.addChangeListener(this); 378 this.rangeAxis = axis; 379 fireChangeEvent(); 380 } 381 382 /** 383 * Returns the lower bound for the thermometer. The data value can be set 384 * lower than this, but it will not be shown in the thermometer. 385 * 386 * @return The lower bound. 387 * 388 * @see #setLowerBound(double) 389 */ 390 public double getLowerBound() { 391 return this.lowerBound; 392 } 393 394 /** 395 * Sets the lower bound for the thermometer. 396 * 397 * @param lower the lower bound. 398 * 399 * @see #getLowerBound() 400 */ 401 public void setLowerBound(double lower) { 402 this.lowerBound = lower; 403 setAxisRange(); 404 } 405 406 /** 407 * Returns the upper bound for the thermometer. The data value can be set 408 * higher than this, but it will not be shown in the thermometer. 409 * 410 * @return The upper bound. 411 * 412 * @see #setUpperBound(double) 413 */ 414 public double getUpperBound() { 415 return this.upperBound; 416 } 417 418 /** 419 * Sets the upper bound for the thermometer. 420 * 421 * @param upper the upper bound. 422 * 423 * @see #getUpperBound() 424 */ 425 public void setUpperBound(double upper) { 426 this.upperBound = upper; 427 setAxisRange(); 428 } 429 430 /** 431 * Sets the lower and upper bounds for the thermometer. 432 * 433 * @param lower the lower bound. 434 * @param upper the upper bound. 435 */ 436 public void setRange(double lower, double upper) { 437 this.lowerBound = lower; 438 this.upperBound = upper; 439 setAxisRange(); 440 } 441 442 /** 443 * Returns the padding for the thermometer. This is the space inside the 444 * plot area. 445 * 446 * @return The padding (never {@code null}). 447 * 448 * @see #setPadding(RectangleInsets) 449 */ 450 public RectangleInsets getPadding() { 451 return this.padding; 452 } 453 454 /** 455 * Sets the padding for the thermometer and sends a {@link PlotChangeEvent} 456 * to all registered listeners. 457 * 458 * @param padding the padding ({@code null} not permitted). 459 * 460 * @see #getPadding() 461 */ 462 public void setPadding(RectangleInsets padding) { 463 Args.nullNotPermitted(padding, "padding"); 464 this.padding = padding; 465 fireChangeEvent(); 466 } 467 468 /** 469 * Returns the stroke used to draw the thermometer outline. 470 * 471 * @return The stroke (never {@code null}). 472 * 473 * @see #setThermometerStroke(Stroke) 474 * @see #getThermometerPaint() 475 */ 476 public Stroke getThermometerStroke() { 477 return this.thermometerStroke; 478 } 479 480 /** 481 * Sets the stroke used to draw the thermometer outline and sends a 482 * {@link PlotChangeEvent} to all registered listeners. 483 * 484 * @param s the new stroke ({@code null} ignored). 485 * 486 * @see #getThermometerStroke() 487 */ 488 public void setThermometerStroke(Stroke s) { 489 if (s != null) { 490 this.thermometerStroke = s; 491 fireChangeEvent(); 492 } 493 } 494 495 /** 496 * Returns the paint used to draw the thermometer outline. 497 * 498 * @return The paint (never {@code null}). 499 * 500 * @see #setThermometerPaint(Paint) 501 * @see #getThermometerStroke() 502 */ 503 public Paint getThermometerPaint() { 504 return this.thermometerPaint; 505 } 506 507 /** 508 * Sets the paint used to draw the thermometer outline and sends a 509 * {@link PlotChangeEvent} to all registered listeners. 510 * 511 * @param paint the new paint ({@code null} ignored). 512 * 513 * @see #getThermometerPaint() 514 */ 515 public void setThermometerPaint(Paint paint) { 516 if (paint != null) { 517 this.thermometerPaint = paint; 518 fireChangeEvent(); 519 } 520 } 521 522 /** 523 * Returns a code indicating the unit display type. This is one of 524 * {@link #UNITS_NONE}, {@link #UNITS_FAHRENHEIT}, {@link #UNITS_CELCIUS} 525 * and {@link #UNITS_KELVIN}. 526 * 527 * @return The units type. 528 * 529 * @see #setUnits(int) 530 */ 531 public int getUnits() { 532 return this.units; 533 } 534 535 /** 536 * Sets the units to be displayed in the thermometer. Use one of the 537 * following constants: 538 * 539 * <ul> 540 * <li>UNITS_NONE : no units displayed.</li> 541 * <li>UNITS_FAHRENHEIT : units displayed in Fahrenheit.</li> 542 * <li>UNITS_CELCIUS : units displayed in Celcius.</li> 543 * <li>UNITS_KELVIN : units displayed in Kelvin.</li> 544 * </ul> 545 * 546 * @param u the new unit type. 547 * 548 * @see #getUnits() 549 */ 550 public void setUnits(int u) { 551 if ((u >= 0) && (u < UNITS.length)) { 552 if (this.units != u) { 553 this.units = u; 554 fireChangeEvent(); 555 } 556 } 557 } 558 559 /** 560 * Returns a code indicating the location at which the value label is 561 * displayed. 562 * 563 * @return The location (one of {@link #NONE}, {@link #RIGHT}, 564 * {@link #LEFT} and {@link #BULB}.). 565 */ 566 public int getValueLocation() { 567 return this.valueLocation; 568 } 569 570 /** 571 * Sets the location at which the current value is displayed and sends a 572 * {@link PlotChangeEvent} to all registered listeners. 573 * <P> 574 * The location can be one of the constants: {@code NONE}, {@code RIGHT}, 575 * {@code LEFT} and {@code BULB}. 576 * 577 * @param location the location. 578 */ 579 public void setValueLocation(int location) { 580 if ((location >= 0) && (location < 4)) { 581 this.valueLocation = location; 582 fireChangeEvent(); 583 } 584 else { 585 throw new IllegalArgumentException("Location not recognised."); 586 } 587 } 588 589 /** 590 * Returns the axis location. 591 * 592 * @return The location (one of {@link #NONE}, {@link #LEFT} and 593 * {@link #RIGHT}). 594 * 595 * @see #setAxisLocation(int) 596 */ 597 public int getAxisLocation() { 598 return this.axisLocation; 599 } 600 601 /** 602 * Sets the location at which the axis is displayed relative to the 603 * thermometer, and sends a {@link PlotChangeEvent} to all registered 604 * listeners. 605 * 606 * @param location the location (one of {@link #NONE}, {@link #LEFT} and 607 * {@link #RIGHT}). 608 * 609 * @see #getAxisLocation() 610 */ 611 public void setAxisLocation(int location) { 612 if ((location >= 0) && (location < 3)) { 613 this.axisLocation = location; 614 fireChangeEvent(); 615 } 616 else { 617 throw new IllegalArgumentException("Location not recognised."); 618 } 619 } 620 621 /** 622 * Gets the font used to display the current value. 623 * 624 * @return The font. 625 * 626 * @see #setValueFont(Font) 627 */ 628 public Font getValueFont() { 629 return this.valueFont; 630 } 631 632 /** 633 * Sets the font used to display the current value. 634 * 635 * @param f the new font ({@code null} not permitted). 636 * 637 * @see #getValueFont() 638 */ 639 public void setValueFont(Font f) { 640 Args.nullNotPermitted(f, "f"); 641 if (!this.valueFont.equals(f)) { 642 this.valueFont = f; 643 fireChangeEvent(); 644 } 645 } 646 647 /** 648 * Gets the paint used to display the current value. 649 * 650 * @return The paint. 651 * 652 * @see #setValuePaint(Paint) 653 */ 654 public Paint getValuePaint() { 655 return this.valuePaint; 656 } 657 658 /** 659 * Sets the paint used to display the current value and sends a 660 * {@link PlotChangeEvent} to all registered listeners. 661 * 662 * @param paint the new paint ({@code null} not permitted). 663 * 664 * @see #getValuePaint() 665 */ 666 public void setValuePaint(Paint paint) { 667 Args.nullNotPermitted(paint, "paint"); 668 if (!this.valuePaint.equals(paint)) { 669 this.valuePaint = paint; 670 fireChangeEvent(); 671 } 672 } 673 674 // FIXME: No getValueFormat() method? 675 676 /** 677 * Sets the formatter for the value label and sends a 678 * {@link PlotChangeEvent} to all registered listeners. 679 * 680 * @param formatter the new formatter ({@code null} not permitted). 681 */ 682 public void setValueFormat(NumberFormat formatter) { 683 Args.nullNotPermitted(formatter, "formatter"); 684 this.valueFormat = formatter; 685 fireChangeEvent(); 686 } 687 688 /** 689 * Returns the default mercury paint. 690 * 691 * @return The paint (never {@code null}). 692 * 693 * @see #setMercuryPaint(Paint) 694 */ 695 public Paint getMercuryPaint() { 696 return this.mercuryPaint; 697 } 698 699 /** 700 * Sets the default mercury paint and sends a {@link PlotChangeEvent} to 701 * all registered listeners. 702 * 703 * @param paint the new paint ({@code null} not permitted). 704 * 705 * @see #getMercuryPaint() 706 */ 707 public void setMercuryPaint(Paint paint) { 708 Args.nullNotPermitted(paint, "paint"); 709 this.mercuryPaint = paint; 710 fireChangeEvent(); 711 } 712 713 /** 714 * Sets information for a particular range. 715 * 716 * @param range the range to specify information about. 717 * @param low the low value for the range 718 * @param hi the high value for the range 719 */ 720 public void setSubrangeInfo(int range, double low, double hi) { 721 setSubrangeInfo(range, low, hi, low, hi); 722 } 723 724 /** 725 * Sets the subrangeInfo attribute of the ThermometerPlot object 726 * 727 * @param range the new rangeInfo value. 728 * @param rangeLow the new rangeInfo value 729 * @param rangeHigh the new rangeInfo value 730 * @param displayLow the new rangeInfo value 731 * @param displayHigh the new rangeInfo value 732 */ 733 public void setSubrangeInfo(int range, 734 double rangeLow, double rangeHigh, 735 double displayLow, double displayHigh) { 736 737 if ((range >= 0) && (range < 3)) { 738 setSubrange(range, rangeLow, rangeHigh); 739 setDisplayRange(range, displayLow, displayHigh); 740 setAxisRange(); 741 fireChangeEvent(); 742 } 743 744 } 745 746 /** 747 * Sets the bounds for a subrange. 748 * 749 * @param range the range type. 750 * @param low the low value. 751 * @param high the high value. 752 */ 753 public void setSubrange(int range, double low, double high) { 754 if ((range >= 0) && (range < 3)) { 755 this.subrangeInfo[range][RANGE_HIGH] = high; 756 this.subrangeInfo[range][RANGE_LOW] = low; 757 } 758 } 759 760 /** 761 * Sets the displayed bounds for a sub range. 762 * 763 * @param range the range type. 764 * @param low the low value. 765 * @param high the high value. 766 */ 767 public void setDisplayRange(int range, double low, double high) { 768 769 if ((range >= 0) && (range < this.subrangeInfo.length) 770 && isValidNumber(high) && isValidNumber(low)) { 771 772 if (high > low) { 773 this.subrangeInfo[range][DISPLAY_HIGH] = high; 774 this.subrangeInfo[range][DISPLAY_LOW] = low; 775 } 776 else { 777 this.subrangeInfo[range][DISPLAY_HIGH] = low; 778 this.subrangeInfo[range][DISPLAY_LOW] = high; 779 } 780 781 } 782 783 } 784 785 /** 786 * Gets the paint used for a particular subrange. 787 * 788 * @param range the range (. 789 * 790 * @return The paint. 791 * 792 * @see #setSubrangePaint(int, Paint) 793 */ 794 public Paint getSubrangePaint(int range) { 795 if ((range >= 0) && (range < this.subrangePaint.length)) { 796 return this.subrangePaint[range]; 797 } 798 else { 799 return this.mercuryPaint; 800 } 801 } 802 803 /** 804 * Sets the paint to be used for a subrange and sends a 805 * {@link PlotChangeEvent} to all registered listeners. 806 * 807 * @param range the range (0, 1 or 2). 808 * @param paint the paint to be applied ({@code null} not permitted). 809 * 810 * @see #getSubrangePaint(int) 811 */ 812 public void setSubrangePaint(int range, Paint paint) { 813 if ((range >= 0) 814 && (range < this.subrangePaint.length) && (paint != null)) { 815 this.subrangePaint[range] = paint; 816 fireChangeEvent(); 817 } 818 } 819 820 /** 821 * Returns a flag that controls whether or not the thermometer axis zooms 822 * to display the subrange within which the data value falls. 823 * 824 * @return The flag. 825 */ 826 public boolean getFollowDataInSubranges() { 827 return this.followDataInSubranges; 828 } 829 830 /** 831 * Sets the flag that controls whether or not the thermometer axis zooms 832 * to display the subrange within which the data value falls. 833 * 834 * @param flag the flag. 835 */ 836 public void setFollowDataInSubranges(boolean flag) { 837 this.followDataInSubranges = flag; 838 fireChangeEvent(); 839 } 840 841 /** 842 * Returns a flag that controls whether or not the mercury color changes 843 * for each subrange. 844 * 845 * @return The flag. 846 * 847 * @see #setUseSubrangePaint(boolean) 848 */ 849 public boolean getUseSubrangePaint() { 850 return this.useSubrangePaint; 851 } 852 853 /** 854 * Sets the range colour change option. 855 * 856 * @param flag the new range colour change option 857 * 858 * @see #getUseSubrangePaint() 859 */ 860 public void setUseSubrangePaint(boolean flag) { 861 this.useSubrangePaint = flag; 862 fireChangeEvent(); 863 } 864 865 /** 866 * Returns the bulb radius, in Java2D units. 867 868 * @return The bulb radius. 869 */ 870 public int getBulbRadius() { 871 return this.bulbRadius; 872 } 873 874 /** 875 * Sets the bulb radius (in Java2D units) and sends a 876 * {@link PlotChangeEvent} to all registered listeners. 877 * 878 * @param r the new radius (in Java2D units). 879 * 880 * @see #getBulbRadius() 881 */ 882 public void setBulbRadius(int r) { 883 this.bulbRadius = r; 884 fireChangeEvent(); 885 } 886 887 /** 888 * Returns the bulb diameter, which is always twice the value returned 889 * by {@link #getBulbRadius()}. 890 * 891 * @return The bulb diameter. 892 */ 893 public int getBulbDiameter() { 894 return getBulbRadius() * 2; 895 } 896 897 /** 898 * Returns the column radius, in Java2D units. 899 * 900 * @return The column radius. 901 * 902 * @see #setColumnRadius(int) 903 */ 904 public int getColumnRadius() { 905 return this.columnRadius; 906 } 907 908 /** 909 * Sets the column radius (in Java2D units) and sends a 910 * {@link PlotChangeEvent} to all registered listeners. 911 * 912 * @param r the new radius. 913 * 914 * @see #getColumnRadius() 915 */ 916 public void setColumnRadius(int r) { 917 this.columnRadius = r; 918 fireChangeEvent(); 919 } 920 921 /** 922 * Returns the column diameter, which is always twice the value returned 923 * by {@link #getColumnRadius()}. 924 * 925 * @return The column diameter. 926 */ 927 public int getColumnDiameter() { 928 return getColumnRadius() * 2; 929 } 930 931 /** 932 * Returns the gap, in Java2D units, between the two outlines that 933 * represent the thermometer. 934 * 935 * @return The gap. 936 * 937 * @see #setGap(int) 938 */ 939 public int getGap() { 940 return this.gap; 941 } 942 943 /** 944 * Sets the gap (in Java2D units) between the two outlines that represent 945 * the thermometer, and sends a {@link PlotChangeEvent} to all registered 946 * listeners. 947 * 948 * @param gap the new gap. 949 * 950 * @see #getGap() 951 */ 952 public void setGap(int gap) { 953 this.gap = gap; 954 fireChangeEvent(); 955 } 956 957 /** 958 * Draws the plot on a Java 2D graphics device (such as the screen or a 959 * printer). 960 * 961 * @param g2 the graphics device. 962 * @param area the area within which the plot should be drawn. 963 * @param anchor the anchor point ({@code null} permitted). 964 * @param parentState the state from the parent plot, if there is one. 965 * @param info collects info about the drawing. 966 */ 967 @Override 968 public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, 969 PlotState parentState, 970 PlotRenderingInfo info) { 971 972 RoundRectangle2D outerStem = new RoundRectangle2D.Double(); 973 RoundRectangle2D innerStem = new RoundRectangle2D.Double(); 974 RoundRectangle2D mercuryStem = new RoundRectangle2D.Double(); 975 Ellipse2D outerBulb = new Ellipse2D.Double(); 976 Ellipse2D innerBulb = new Ellipse2D.Double(); 977 String temp; 978 FontMetrics metrics; 979 if (info != null) { 980 info.setPlotArea(area); 981 } 982 983 // adjust for insets... 984 RectangleInsets insets = getInsets(); 985 insets.trim(area); 986 drawBackground(g2, area); 987 988 // adjust for padding... 989 Rectangle2D interior = (Rectangle2D) area.clone(); 990 this.padding.trim(interior); 991 int midX = (int) (interior.getX() + (interior.getWidth() / 2)); 992 int midY = (int) (interior.getY() + (interior.getHeight() / 2)); 993 int stemTop = (int) (interior.getMinY() + getBulbRadius()); 994 int stemBottom = (int) (interior.getMaxY() - getBulbDiameter()); 995 Rectangle2D dataArea = new Rectangle2D.Double(midX - getColumnRadius(), 996 stemTop, getColumnRadius(), stemBottom - stemTop); 997 998 outerBulb.setFrame(midX - getBulbRadius(), stemBottom, 999 getBulbDiameter(), getBulbDiameter()); 1000 1001 outerStem.setRoundRect(midX - getColumnRadius(), interior.getMinY(), 1002 getColumnDiameter(), stemBottom + getBulbDiameter() - stemTop, 1003 getColumnDiameter(), getColumnDiameter()); 1004 1005 Area outerThermometer = new Area(outerBulb); 1006 Area tempArea = new Area(outerStem); 1007 outerThermometer.add(tempArea); 1008 1009 innerBulb.setFrame(midX - getBulbRadius() + getGap(), stemBottom 1010 + getGap(), getBulbDiameter() - getGap() * 2, getBulbDiameter() 1011 - getGap() * 2); 1012 1013 innerStem.setRoundRect(midX - getColumnRadius() + getGap(), 1014 interior.getMinY() + getGap(), getColumnDiameter() 1015 - getGap() * 2, stemBottom + getBulbDiameter() - getGap() * 2 1016 - stemTop, getColumnDiameter() - getGap() * 2, 1017 getColumnDiameter() - getGap() * 2); 1018 1019 Area innerThermometer = new Area(innerBulb); 1020 tempArea = new Area(innerStem); 1021 innerThermometer.add(tempArea); 1022 1023 if ((this.dataset != null) && (this.dataset.getValue() != null)) { 1024 double current = this.dataset.getValue().doubleValue(); 1025 double ds = this.rangeAxis.valueToJava2D(current, dataArea, 1026 RectangleEdge.LEFT); 1027 1028 int i = getColumnDiameter() - getGap() * 2; // already calculated 1029 int j = getColumnRadius() - getGap(); // already calculated 1030 int l = (i / 2); 1031 int k = (int) Math.round(ds); 1032 if (k < (getGap() + interior.getMinY())) { 1033 k = (int) (getGap() + interior.getMinY()); 1034 l = getBulbRadius(); 1035 } 1036 1037 Area mercury = new Area(innerBulb); 1038 1039 if (k < (stemBottom + getBulbRadius())) { 1040 mercuryStem.setRoundRect(midX - j, k, i, 1041 (stemBottom + getBulbRadius()) - k, l, l); 1042 tempArea = new Area(mercuryStem); 1043 mercury.add(tempArea); 1044 } 1045 1046 g2.setPaint(getCurrentPaint()); 1047 g2.fill(mercury); 1048 1049 // draw range indicators... 1050 if (this.subrangeIndicatorsVisible) { 1051 g2.setStroke(this.subrangeIndicatorStroke); 1052 Range range = this.rangeAxis.getRange(); 1053 1054 // draw start of normal range 1055 double value = this.subrangeInfo[NORMAL][RANGE_LOW]; 1056 if (range.contains(value)) { 1057 double x = midX + getColumnRadius() + 2; 1058 double y = this.rangeAxis.valueToJava2D(value, dataArea, 1059 RectangleEdge.LEFT); 1060 Line2D line = new Line2D.Double(x, y, x + 10, y); 1061 g2.setPaint(this.subrangePaint[NORMAL]); 1062 g2.draw(line); 1063 } 1064 1065 // draw start of warning range 1066 value = this.subrangeInfo[WARNING][RANGE_LOW]; 1067 if (range.contains(value)) { 1068 double x = midX + getColumnRadius() + 2; 1069 double y = this.rangeAxis.valueToJava2D(value, dataArea, 1070 RectangleEdge.LEFT); 1071 Line2D line = new Line2D.Double(x, y, x + 10, y); 1072 g2.setPaint(this.subrangePaint[WARNING]); 1073 g2.draw(line); 1074 } 1075 1076 // draw start of critical range 1077 value = this.subrangeInfo[CRITICAL][RANGE_LOW]; 1078 if (range.contains(value)) { 1079 double x = midX + getColumnRadius() + 2; 1080 double y = this.rangeAxis.valueToJava2D(value, dataArea, 1081 RectangleEdge.LEFT); 1082 Line2D line = new Line2D.Double(x, y, x + 10, y); 1083 g2.setPaint(this.subrangePaint[CRITICAL]); 1084 g2.draw(line); 1085 } 1086 } 1087 1088 // draw the axis... 1089 if ((this.rangeAxis != null) && (this.axisLocation != NONE)) { 1090 int drawWidth = AXIS_GAP; 1091 if (this.showValueLines) { 1092 drawWidth += getColumnDiameter(); 1093 } 1094 Rectangle2D drawArea; 1095 double cursor; 1096 1097 switch (this.axisLocation) { 1098 case RIGHT: 1099 cursor = midX + getColumnRadius(); 1100 drawArea = new Rectangle2D.Double(cursor, 1101 stemTop, drawWidth, (stemBottom - stemTop + 1)); 1102 this.rangeAxis.draw(g2, cursor, area, drawArea, 1103 RectangleEdge.RIGHT, null); 1104 break; 1105 1106 case LEFT: 1107 default: 1108 //cursor = midX - COLUMN_RADIUS - AXIS_GAP; 1109 cursor = midX - getColumnRadius(); 1110 drawArea = new Rectangle2D.Double(cursor, stemTop, 1111 drawWidth, (stemBottom - stemTop + 1)); 1112 this.rangeAxis.draw(g2, cursor, area, drawArea, 1113 RectangleEdge.LEFT, null); 1114 break; 1115 } 1116 1117 } 1118 1119 // draw text value on screen 1120 g2.setFont(this.valueFont); 1121 g2.setPaint(this.valuePaint); 1122 metrics = g2.getFontMetrics(); 1123 switch (this.valueLocation) { 1124 case RIGHT: 1125 g2.drawString(this.valueFormat.format(current), 1126 midX + getColumnRadius() + getGap(), midY); 1127 break; 1128 case LEFT: 1129 String valueString = this.valueFormat.format(current); 1130 int stringWidth = metrics.stringWidth(valueString); 1131 g2.drawString(valueString, midX - getColumnRadius() 1132 - getGap() - stringWidth, midY); 1133 break; 1134 case BULB: 1135 temp = this.valueFormat.format(current); 1136 i = metrics.stringWidth(temp) / 2; 1137 g2.drawString(temp, midX - i, 1138 stemBottom + getBulbRadius() + getGap()); 1139 break; 1140 default: 1141 } 1142 } 1143 1144 g2.setPaint(this.thermometerPaint); 1145 g2.setFont(this.valueFont); 1146 1147 // draw units indicator 1148 metrics = g2.getFontMetrics(); 1149 int tickX1 = midX - getColumnRadius() - getGap() * 2 1150 - metrics.stringWidth(UNITS[this.units]); 1151 if (tickX1 > area.getMinX()) { 1152 g2.drawString(UNITS[this.units], tickX1, 1153 (int) (area.getMinY() + 20)); 1154 } 1155 1156 // draw thermometer outline 1157 g2.setStroke(this.thermometerStroke); 1158 g2.draw(outerThermometer); 1159 g2.draw(innerThermometer); 1160 1161 drawOutline(g2, area); 1162 } 1163 1164 /** 1165 * A zoom method that does nothing. Plots are required to support the 1166 * zoom operation. In the case of a thermometer chart, it doesn't make 1167 * sense to zoom in or out, so the method is empty. 1168 * 1169 * @param percent the zoom percentage. 1170 */ 1171 @Override 1172 public void zoom(double percent) { 1173 // intentionally blank 1174 } 1175 1176 /** 1177 * Returns a short string describing the type of plot. 1178 * 1179 * @return A short string describing the type of plot. 1180 */ 1181 @Override 1182 public String getPlotType() { 1183 return localizationResources.getString("Thermometer_Plot"); 1184 } 1185 1186 /** 1187 * Checks to see if a new value means the axis range needs adjusting. 1188 * 1189 * @param event the dataset change event. 1190 */ 1191 @Override 1192 public void datasetChanged(DatasetChangeEvent event) { 1193 if (this.dataset != null) { 1194 Number vn = this.dataset.getValue(); 1195 if (vn != null) { 1196 double value = vn.doubleValue(); 1197 if (inSubrange(NORMAL, value)) { 1198 this.subrange = NORMAL; 1199 } 1200 else if (inSubrange(WARNING, value)) { 1201 this.subrange = WARNING; 1202 } 1203 else if (inSubrange(CRITICAL, value)) { 1204 this.subrange = CRITICAL; 1205 } 1206 else { 1207 this.subrange = -1; 1208 } 1209 setAxisRange(); 1210 } 1211 } 1212 super.datasetChanged(event); 1213 } 1214 1215 /** 1216 * Returns the data range. 1217 * 1218 * @param axis the axis. 1219 * 1220 * @return The range of data displayed. 1221 */ 1222 @Override 1223 public Range getDataRange(ValueAxis axis) { 1224 return new Range(this.lowerBound, this.upperBound); 1225 } 1226 1227 /** 1228 * Sets the axis range to the current values in the rangeInfo array. 1229 */ 1230 protected void setAxisRange() { 1231 if ((this.subrange >= 0) && (this.followDataInSubranges)) { 1232 this.rangeAxis.setRange( 1233 new Range(this.subrangeInfo[this.subrange][DISPLAY_LOW], 1234 this.subrangeInfo[this.subrange][DISPLAY_HIGH])); 1235 } 1236 else { 1237 this.rangeAxis.setRange(this.lowerBound, this.upperBound); 1238 } 1239 } 1240 1241 /** 1242 * Returns the legend items for the plot. 1243 * 1244 * @return {@code null}. 1245 */ 1246 @Override 1247 public LegendItemCollection getLegendItems() { 1248 return null; 1249 } 1250 1251 /** 1252 * Returns the orientation of the plot. 1253 * 1254 * @return The orientation (always {@link PlotOrientation#VERTICAL}). 1255 */ 1256 @Override 1257 public PlotOrientation getOrientation() { 1258 return PlotOrientation.VERTICAL; 1259 } 1260 1261 /** 1262 * Determine whether a number is valid and finite. 1263 * 1264 * @param d the number to be tested. 1265 * 1266 * @return {@code true} if the number is valid and finite, and 1267 * {@code false} otherwise. 1268 */ 1269 protected static boolean isValidNumber(double d) { 1270 return (!(Double.isNaN(d) || Double.isInfinite(d))); 1271 } 1272 1273 /** 1274 * Returns true if the value is in the specified range, and false otherwise. 1275 * 1276 * @param subrange the subrange. 1277 * @param value the value to check. 1278 * 1279 * @return A boolean. 1280 */ 1281 private boolean inSubrange(int subrange, double value) { 1282 return (value > this.subrangeInfo[subrange][RANGE_LOW] 1283 && value <= this.subrangeInfo[subrange][RANGE_HIGH]); 1284 } 1285 1286 /** 1287 * Returns the mercury paint corresponding to the current data value. 1288 * Called from the {@link #draw(Graphics2D, Rectangle2D, Point2D, 1289 * PlotState, PlotRenderingInfo)} method. 1290 * 1291 * @return The paint (never {@code null}). 1292 */ 1293 private Paint getCurrentPaint() { 1294 Paint result = this.mercuryPaint; 1295 if (this.useSubrangePaint) { 1296 double value = this.dataset.getValue().doubleValue(); 1297 if (inSubrange(NORMAL, value)) { 1298 result = this.subrangePaint[NORMAL]; 1299 } 1300 else if (inSubrange(WARNING, value)) { 1301 result = this.subrangePaint[WARNING]; 1302 } 1303 else if (inSubrange(CRITICAL, value)) { 1304 result = this.subrangePaint[CRITICAL]; 1305 } 1306 } 1307 return result; 1308 } 1309 1310 /** 1311 * Tests this plot for equality with another object. The plot's dataset 1312 * is not considered in the test. 1313 * 1314 * @param obj the object ({@code null} permitted). 1315 * 1316 * @return {@code true} or {@code false}. 1317 */ 1318 @Override 1319 public boolean equals(Object obj) { 1320 if (obj == this) { 1321 return true; 1322 } 1323 if (!(obj instanceof ThermometerPlot)) { 1324 return false; 1325 } 1326 ThermometerPlot that = (ThermometerPlot) obj; 1327 if (!super.equals(obj)) { 1328 return false; 1329 } 1330 if (!Objects.equals(this.rangeAxis, that.rangeAxis)) { 1331 return false; 1332 } 1333 if (this.axisLocation != that.axisLocation) { 1334 return false; 1335 } 1336 if (this.lowerBound != that.lowerBound) { 1337 return false; 1338 } 1339 if (this.upperBound != that.upperBound) { 1340 return false; 1341 } 1342 if (!Objects.equals(this.padding, that.padding)) { 1343 return false; 1344 } 1345 if (!Objects.equals(this.thermometerStroke, 1346 that.thermometerStroke)) { 1347 return false; 1348 } 1349 if (!PaintUtils.equal(this.thermometerPaint, 1350 that.thermometerPaint)) { 1351 return false; 1352 } 1353 if (this.units != that.units) { 1354 return false; 1355 } 1356 if (this.valueLocation != that.valueLocation) { 1357 return false; 1358 } 1359 if (!Objects.equals(this.valueFont, that.valueFont)) { 1360 return false; 1361 } 1362 if (!PaintUtils.equal(this.valuePaint, that.valuePaint)) { 1363 return false; 1364 } 1365 if (!Objects.equals(this.valueFormat, that.valueFormat)) { 1366 return false; 1367 } 1368 if (!PaintUtils.equal(this.mercuryPaint, that.mercuryPaint)) { 1369 return false; 1370 } 1371 if (this.showValueLines != that.showValueLines) { 1372 return false; 1373 } 1374 if (this.subrange != that.subrange) { 1375 return false; 1376 } 1377 if (this.followDataInSubranges != that.followDataInSubranges) { 1378 return false; 1379 } 1380 if (!equal(this.subrangeInfo, that.subrangeInfo)) { 1381 return false; 1382 } 1383 if (this.useSubrangePaint != that.useSubrangePaint) { 1384 return false; 1385 } 1386 if (this.bulbRadius != that.bulbRadius) { 1387 return false; 1388 } 1389 if (this.columnRadius != that.columnRadius) { 1390 return false; 1391 } 1392 if (this.gap != that.gap) { 1393 return false; 1394 } 1395 for (int i = 0; i < this.subrangePaint.length; i++) { 1396 if (!PaintUtils.equal(this.subrangePaint[i], 1397 that.subrangePaint[i])) { 1398 return false; 1399 } 1400 } 1401 return true; 1402 } 1403 1404 /** 1405 * Tests two double[][] arrays for equality. 1406 * 1407 * @param array1 the first array ({@code null} permitted). 1408 * @param array2 the second arrray ({@code null} permitted). 1409 * 1410 * @return A boolean. 1411 */ 1412 private static boolean equal(double[][] array1, double[][] array2) { 1413 if (array1 == null) { 1414 return (array2 == null); 1415 } 1416 if (array2 == null) { 1417 return false; 1418 } 1419 if (array1.length != array2.length) { 1420 return false; 1421 } 1422 for (int i = 0; i < array1.length; i++) { 1423 if (!Arrays.equals(array1[i], array2[i])) { 1424 return false; 1425 } 1426 } 1427 return true; 1428 } 1429 1430 /** 1431 * Returns a clone of the plot. 1432 * 1433 * @return A clone. 1434 * 1435 * @throws CloneNotSupportedException if the plot cannot be cloned. 1436 */ 1437 @Override 1438 public Object clone() throws CloneNotSupportedException { 1439 1440 ThermometerPlot clone = (ThermometerPlot) super.clone(); 1441 1442 if (clone.dataset != null) { 1443 clone.dataset.addChangeListener(clone); 1444 } 1445 clone.rangeAxis = (ValueAxis) ObjectUtils.clone(this.rangeAxis); 1446 if (clone.rangeAxis != null) { 1447 clone.rangeAxis.setPlot(clone); 1448 clone.rangeAxis.addChangeListener(clone); 1449 } 1450 clone.valueFormat = (NumberFormat) this.valueFormat.clone(); 1451 clone.subrangePaint = (Paint[]) this.subrangePaint.clone(); 1452 1453 return clone; 1454 1455 } 1456 1457 /** 1458 * Provides serialization support. 1459 * 1460 * @param stream the output stream. 1461 * 1462 * @throws IOException if there is an I/O error. 1463 */ 1464 private void writeObject(ObjectOutputStream stream) throws IOException { 1465 stream.defaultWriteObject(); 1466 SerialUtils.writeStroke(this.thermometerStroke, stream); 1467 SerialUtils.writePaint(this.thermometerPaint, stream); 1468 SerialUtils.writePaint(this.valuePaint, stream); 1469 SerialUtils.writePaint(this.mercuryPaint, stream); 1470 SerialUtils.writeStroke(this.subrangeIndicatorStroke, stream); 1471 SerialUtils.writeStroke(this.rangeIndicatorStroke, stream); 1472 for (int i = 0; i < 3; i++) { 1473 SerialUtils.writePaint(this.subrangePaint[i], stream); 1474 } 1475 } 1476 1477 /** 1478 * Provides serialization support. 1479 * 1480 * @param stream the input stream. 1481 * 1482 * @throws IOException if there is an I/O error. 1483 * @throws ClassNotFoundException if there is a classpath problem. 1484 */ 1485 private void readObject(ObjectInputStream stream) throws IOException, 1486 ClassNotFoundException { 1487 stream.defaultReadObject(); 1488 this.thermometerStroke = SerialUtils.readStroke(stream); 1489 this.thermometerPaint = SerialUtils.readPaint(stream); 1490 this.valuePaint = SerialUtils.readPaint(stream); 1491 this.mercuryPaint = SerialUtils.readPaint(stream); 1492 this.subrangeIndicatorStroke = SerialUtils.readStroke(stream); 1493 this.rangeIndicatorStroke = SerialUtils.readStroke(stream); 1494 this.subrangePaint = new Paint[3]; 1495 for (int i = 0; i < 3; i++) { 1496 this.subrangePaint[i] = SerialUtils.readPaint(stream); 1497 } 1498 if (this.rangeAxis != null) { 1499 this.rangeAxis.addChangeListener(this); 1500 } 1501 } 1502 1503 /** 1504 * Multiplies the range on the domain axis/axes by the specified factor. 1505 * 1506 * @param factor the zoom factor. 1507 * @param state the plot state. 1508 * @param source the source point. 1509 */ 1510 @Override 1511 public void zoomDomainAxes(double factor, PlotRenderingInfo state, 1512 Point2D source) { 1513 // no domain axis to zoom 1514 } 1515 1516 /** 1517 * Multiplies the range on the domain axis/axes by the specified factor. 1518 * 1519 * @param factor the zoom factor. 1520 * @param state the plot state. 1521 * @param source the source point. 1522 * @param useAnchor a flag that controls whether or not the source point 1523 * is used for the zoom anchor. 1524 */ 1525 @Override 1526 public void zoomDomainAxes(double factor, PlotRenderingInfo state, 1527 Point2D source, boolean useAnchor) { 1528 // no domain axis to zoom 1529 } 1530 1531 /** 1532 * Multiplies the range on the range axis/axes by the specified factor. 1533 * 1534 * @param factor the zoom factor. 1535 * @param state the plot state. 1536 * @param source the source point. 1537 */ 1538 @Override 1539 public void zoomRangeAxes(double factor, PlotRenderingInfo state, 1540 Point2D source) { 1541 this.rangeAxis.resizeRange(factor); 1542 } 1543 1544 /** 1545 * Multiplies the range on the range axis/axes by the specified factor. 1546 * 1547 * @param factor the zoom factor. 1548 * @param state the plot state. 1549 * @param source the source point. 1550 * @param useAnchor a flag that controls whether or not the source point 1551 * is used for the zoom anchor. 1552 */ 1553 @Override 1554 public void zoomRangeAxes(double factor, PlotRenderingInfo state, 1555 Point2D source, boolean useAnchor) { 1556 double anchorY = this.getRangeAxis().java2DToValue(source.getY(), 1557 state.getDataArea(), RectangleEdge.LEFT); 1558 this.rangeAxis.resizeRange(factor, anchorY); 1559 } 1560 1561 /** 1562 * This method does nothing. 1563 * 1564 * @param lowerPercent the lower percent. 1565 * @param upperPercent the upper percent. 1566 * @param state the plot state. 1567 * @param source the source point. 1568 */ 1569 @Override 1570 public void zoomDomainAxes(double lowerPercent, double upperPercent, 1571 PlotRenderingInfo state, Point2D source) { 1572 // no domain axis to zoom 1573 } 1574 1575 /** 1576 * Zooms the range axes. 1577 * 1578 * @param lowerPercent the lower percent. 1579 * @param upperPercent the upper percent. 1580 * @param state the plot state. 1581 * @param source the source point. 1582 */ 1583 @Override 1584 public void zoomRangeAxes(double lowerPercent, double upperPercent, 1585 PlotRenderingInfo state, Point2D source) { 1586 this.rangeAxis.zoomRange(lowerPercent, upperPercent); 1587 } 1588 1589 /** 1590 * Returns {@code false}. 1591 * 1592 * @return A boolean. 1593 */ 1594 @Override 1595 public boolean isDomainZoomable() { 1596 return false; 1597 } 1598 1599 /** 1600 * Returns {@code true}. 1601 * 1602 * @return A boolean. 1603 */ 1604 @Override 1605 public boolean isRangeZoomable() { 1606 return true; 1607 } 1608 1609}