001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2021, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [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-2021, by Bryan Scott and Contributors. 032 * 033 * Original Author: Bryan Scott (based on MeterPlot by Hari). 034 * Contributor(s): David Gilbert (for Object Refinery Limited). 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 1145 g2.setPaint(this.thermometerPaint); 1146 g2.setFont(this.valueFont); 1147 1148 // draw units indicator 1149 metrics = g2.getFontMetrics(); 1150 int tickX1 = midX - getColumnRadius() - getGap() * 2 1151 - metrics.stringWidth(UNITS[this.units]); 1152 if (tickX1 > area.getMinX()) { 1153 g2.drawString(UNITS[this.units], tickX1, 1154 (int) (area.getMinY() + 20)); 1155 } 1156 1157 // draw thermometer outline 1158 g2.setStroke(this.thermometerStroke); 1159 g2.draw(outerThermometer); 1160 g2.draw(innerThermometer); 1161 1162 drawOutline(g2, area); 1163 } 1164 1165 /** 1166 * A zoom method that does nothing. Plots are required to support the 1167 * zoom operation. In the case of a thermometer chart, it doesn't make 1168 * sense to zoom in or out, so the method is empty. 1169 * 1170 * @param percent the zoom percentage. 1171 */ 1172 @Override 1173 public void zoom(double percent) { 1174 // intentionally blank 1175 } 1176 1177 /** 1178 * Returns a short string describing the type of plot. 1179 * 1180 * @return A short string describing the type of plot. 1181 */ 1182 @Override 1183 public String getPlotType() { 1184 return localizationResources.getString("Thermometer_Plot"); 1185 } 1186 1187 /** 1188 * Checks to see if a new value means the axis range needs adjusting. 1189 * 1190 * @param event the dataset change event. 1191 */ 1192 @Override 1193 public void datasetChanged(DatasetChangeEvent event) { 1194 if (this.dataset != null) { 1195 Number vn = this.dataset.getValue(); 1196 if (vn != null) { 1197 double value = vn.doubleValue(); 1198 if (inSubrange(NORMAL, value)) { 1199 this.subrange = NORMAL; 1200 } 1201 else if (inSubrange(WARNING, value)) { 1202 this.subrange = WARNING; 1203 } 1204 else if (inSubrange(CRITICAL, value)) { 1205 this.subrange = CRITICAL; 1206 } 1207 else { 1208 this.subrange = -1; 1209 } 1210 setAxisRange(); 1211 } 1212 } 1213 super.datasetChanged(event); 1214 } 1215 1216 /** 1217 * Returns the data range. 1218 * 1219 * @param axis the axis. 1220 * 1221 * @return The range of data displayed. 1222 */ 1223 @Override 1224 public Range getDataRange(ValueAxis axis) { 1225 return new Range(this.lowerBound, this.upperBound); 1226 } 1227 1228 /** 1229 * Sets the axis range to the current values in the rangeInfo array. 1230 */ 1231 protected void setAxisRange() { 1232 if ((this.subrange >= 0) && (this.followDataInSubranges)) { 1233 this.rangeAxis.setRange( 1234 new Range(this.subrangeInfo[this.subrange][DISPLAY_LOW], 1235 this.subrangeInfo[this.subrange][DISPLAY_HIGH])); 1236 } 1237 else { 1238 this.rangeAxis.setRange(this.lowerBound, this.upperBound); 1239 } 1240 } 1241 1242 /** 1243 * Returns the legend items for the plot. 1244 * 1245 * @return {@code null}. 1246 */ 1247 @Override 1248 public LegendItemCollection getLegendItems() { 1249 return null; 1250 } 1251 1252 /** 1253 * Returns the orientation of the plot. 1254 * 1255 * @return The orientation (always {@link PlotOrientation#VERTICAL}). 1256 */ 1257 @Override 1258 public PlotOrientation getOrientation() { 1259 return PlotOrientation.VERTICAL; 1260 } 1261 1262 /** 1263 * Determine whether a number is valid and finite. 1264 * 1265 * @param d the number to be tested. 1266 * 1267 * @return {@code true} if the number is valid and finite, and 1268 * {@code false} otherwise. 1269 */ 1270 protected static boolean isValidNumber(double d) { 1271 return (!(Double.isNaN(d) || Double.isInfinite(d))); 1272 } 1273 1274 /** 1275 * Returns true if the value is in the specified range, and false otherwise. 1276 * 1277 * @param subrange the subrange. 1278 * @param value the value to check. 1279 * 1280 * @return A boolean. 1281 */ 1282 private boolean inSubrange(int subrange, double value) { 1283 return (value > this.subrangeInfo[subrange][RANGE_LOW] 1284 && value <= this.subrangeInfo[subrange][RANGE_HIGH]); 1285 } 1286 1287 /** 1288 * Returns the mercury paint corresponding to the current data value. 1289 * Called from the {@link #draw(Graphics2D, Rectangle2D, Point2D, 1290 * PlotState, PlotRenderingInfo)} method. 1291 * 1292 * @return The paint (never {@code null}). 1293 */ 1294 private Paint getCurrentPaint() { 1295 Paint result = this.mercuryPaint; 1296 if (this.useSubrangePaint) { 1297 double value = this.dataset.getValue().doubleValue(); 1298 if (inSubrange(NORMAL, value)) { 1299 result = this.subrangePaint[NORMAL]; 1300 } 1301 else if (inSubrange(WARNING, value)) { 1302 result = this.subrangePaint[WARNING]; 1303 } 1304 else if (inSubrange(CRITICAL, value)) { 1305 result = this.subrangePaint[CRITICAL]; 1306 } 1307 } 1308 return result; 1309 } 1310 1311 /** 1312 * Tests this plot for equality with another object. The plot's dataset 1313 * is not considered in the test. 1314 * 1315 * @param obj the object ({@code null} permitted). 1316 * 1317 * @return {@code true} or {@code false}. 1318 */ 1319 @Override 1320 public boolean equals(Object obj) { 1321 if (obj == this) { 1322 return true; 1323 } 1324 if (!(obj instanceof ThermometerPlot)) { 1325 return false; 1326 } 1327 ThermometerPlot that = (ThermometerPlot) obj; 1328 if (!super.equals(obj)) { 1329 return false; 1330 } 1331 if (!Objects.equals(this.rangeAxis, that.rangeAxis)) { 1332 return false; 1333 } 1334 if (this.axisLocation != that.axisLocation) { 1335 return false; 1336 } 1337 if (this.lowerBound != that.lowerBound) { 1338 return false; 1339 } 1340 if (this.upperBound != that.upperBound) { 1341 return false; 1342 } 1343 if (!Objects.equals(this.padding, that.padding)) { 1344 return false; 1345 } 1346 if (!Objects.equals(this.thermometerStroke, 1347 that.thermometerStroke)) { 1348 return false; 1349 } 1350 if (!PaintUtils.equal(this.thermometerPaint, 1351 that.thermometerPaint)) { 1352 return false; 1353 } 1354 if (this.units != that.units) { 1355 return false; 1356 } 1357 if (this.valueLocation != that.valueLocation) { 1358 return false; 1359 } 1360 if (!Objects.equals(this.valueFont, that.valueFont)) { 1361 return false; 1362 } 1363 if (!PaintUtils.equal(this.valuePaint, that.valuePaint)) { 1364 return false; 1365 } 1366 if (!Objects.equals(this.valueFormat, that.valueFormat)) { 1367 return false; 1368 } 1369 if (!PaintUtils.equal(this.mercuryPaint, that.mercuryPaint)) { 1370 return false; 1371 } 1372 if (this.showValueLines != that.showValueLines) { 1373 return false; 1374 } 1375 if (this.subrange != that.subrange) { 1376 return false; 1377 } 1378 if (this.followDataInSubranges != that.followDataInSubranges) { 1379 return false; 1380 } 1381 if (!equal(this.subrangeInfo, that.subrangeInfo)) { 1382 return false; 1383 } 1384 if (this.useSubrangePaint != that.useSubrangePaint) { 1385 return false; 1386 } 1387 if (this.bulbRadius != that.bulbRadius) { 1388 return false; 1389 } 1390 if (this.columnRadius != that.columnRadius) { 1391 return false; 1392 } 1393 if (this.gap != that.gap) { 1394 return false; 1395 } 1396 for (int i = 0; i < this.subrangePaint.length; i++) { 1397 if (!PaintUtils.equal(this.subrangePaint[i], 1398 that.subrangePaint[i])) { 1399 return false; 1400 } 1401 } 1402 return true; 1403 } 1404 1405 /** 1406 * Tests two double[][] arrays for equality. 1407 * 1408 * @param array1 the first array ({@code null} permitted). 1409 * @param array2 the second arrray ({@code null} permitted). 1410 * 1411 * @return A boolean. 1412 */ 1413 private static boolean equal(double[][] array1, double[][] array2) { 1414 if (array1 == null) { 1415 return (array2 == null); 1416 } 1417 if (array2 == null) { 1418 return false; 1419 } 1420 if (array1.length != array2.length) { 1421 return false; 1422 } 1423 for (int i = 0; i < array1.length; i++) { 1424 if (!Arrays.equals(array1[i], array2[i])) { 1425 return false; 1426 } 1427 } 1428 return true; 1429 } 1430 1431 /** 1432 * Returns a clone of the plot. 1433 * 1434 * @return A clone. 1435 * 1436 * @throws CloneNotSupportedException if the plot cannot be cloned. 1437 */ 1438 @Override 1439 public Object clone() throws CloneNotSupportedException { 1440 1441 ThermometerPlot clone = (ThermometerPlot) super.clone(); 1442 1443 if (clone.dataset != null) { 1444 clone.dataset.addChangeListener(clone); 1445 } 1446 clone.rangeAxis = (ValueAxis) ObjectUtils.clone(this.rangeAxis); 1447 if (clone.rangeAxis != null) { 1448 clone.rangeAxis.setPlot(clone); 1449 clone.rangeAxis.addChangeListener(clone); 1450 } 1451 clone.valueFormat = (NumberFormat) this.valueFormat.clone(); 1452 clone.subrangePaint = (Paint[]) this.subrangePaint.clone(); 1453 1454 return clone; 1455 1456 } 1457 1458 /** 1459 * Provides serialization support. 1460 * 1461 * @param stream the output stream. 1462 * 1463 * @throws IOException if there is an I/O error. 1464 */ 1465 private void writeObject(ObjectOutputStream stream) throws IOException { 1466 stream.defaultWriteObject(); 1467 SerialUtils.writeStroke(this.thermometerStroke, stream); 1468 SerialUtils.writePaint(this.thermometerPaint, stream); 1469 SerialUtils.writePaint(this.valuePaint, stream); 1470 SerialUtils.writePaint(this.mercuryPaint, stream); 1471 SerialUtils.writeStroke(this.subrangeIndicatorStroke, stream); 1472 SerialUtils.writeStroke(this.rangeIndicatorStroke, stream); 1473 for (int i = 0; i < 3; i++) { 1474 SerialUtils.writePaint(this.subrangePaint[i], stream); 1475 } 1476 } 1477 1478 /** 1479 * Provides serialization support. 1480 * 1481 * @param stream the input stream. 1482 * 1483 * @throws IOException if there is an I/O error. 1484 * @throws ClassNotFoundException if there is a classpath problem. 1485 */ 1486 private void readObject(ObjectInputStream stream) throws IOException, 1487 ClassNotFoundException { 1488 stream.defaultReadObject(); 1489 this.thermometerStroke = SerialUtils.readStroke(stream); 1490 this.thermometerPaint = SerialUtils.readPaint(stream); 1491 this.valuePaint = SerialUtils.readPaint(stream); 1492 this.mercuryPaint = SerialUtils.readPaint(stream); 1493 this.subrangeIndicatorStroke = SerialUtils.readStroke(stream); 1494 this.rangeIndicatorStroke = SerialUtils.readStroke(stream); 1495 this.subrangePaint = new Paint[3]; 1496 for (int i = 0; i < 3; i++) { 1497 this.subrangePaint[i] = SerialUtils.readPaint(stream); 1498 } 1499 if (this.rangeAxis != null) { 1500 this.rangeAxis.addChangeListener(this); 1501 } 1502 } 1503 1504 /** 1505 * Multiplies the range on the domain axis/axes by the specified factor. 1506 * 1507 * @param factor the zoom factor. 1508 * @param state the plot state. 1509 * @param source the source point. 1510 */ 1511 @Override 1512 public void zoomDomainAxes(double factor, PlotRenderingInfo state, 1513 Point2D source) { 1514 // no domain axis to zoom 1515 } 1516 1517 /** 1518 * Multiplies the range on the domain axis/axes by the specified factor. 1519 * 1520 * @param factor the zoom factor. 1521 * @param state the plot state. 1522 * @param source the source point. 1523 * @param useAnchor a flag that controls whether or not the source point 1524 * is used for the zoom anchor. 1525 */ 1526 @Override 1527 public void zoomDomainAxes(double factor, PlotRenderingInfo state, 1528 Point2D source, boolean useAnchor) { 1529 // no domain axis to zoom 1530 } 1531 1532 /** 1533 * Multiplies the range on the range axis/axes by the specified factor. 1534 * 1535 * @param factor the zoom factor. 1536 * @param state the plot state. 1537 * @param source the source point. 1538 */ 1539 @Override 1540 public void zoomRangeAxes(double factor, PlotRenderingInfo state, 1541 Point2D source) { 1542 this.rangeAxis.resizeRange(factor); 1543 } 1544 1545 /** 1546 * Multiplies the range on the range axis/axes by the specified factor. 1547 * 1548 * @param factor the zoom factor. 1549 * @param state the plot state. 1550 * @param source the source point. 1551 * @param useAnchor a flag that controls whether or not the source point 1552 * is used for the zoom anchor. 1553 */ 1554 @Override 1555 public void zoomRangeAxes(double factor, PlotRenderingInfo state, 1556 Point2D source, boolean useAnchor) { 1557 double anchorY = this.getRangeAxis().java2DToValue(source.getY(), 1558 state.getDataArea(), RectangleEdge.LEFT); 1559 this.rangeAxis.resizeRange(factor, anchorY); 1560 } 1561 1562 /** 1563 * This method does nothing. 1564 * 1565 * @param lowerPercent the lower percent. 1566 * @param upperPercent the upper percent. 1567 * @param state the plot state. 1568 * @param source the source point. 1569 */ 1570 @Override 1571 public void zoomDomainAxes(double lowerPercent, double upperPercent, 1572 PlotRenderingInfo state, Point2D source) { 1573 // no domain axis to zoom 1574 } 1575 1576 /** 1577 * Zooms the range axes. 1578 * 1579 * @param lowerPercent the lower percent. 1580 * @param upperPercent the upper percent. 1581 * @param state the plot state. 1582 * @param source the source point. 1583 */ 1584 @Override 1585 public void zoomRangeAxes(double lowerPercent, double upperPercent, 1586 PlotRenderingInfo state, Point2D source) { 1587 this.rangeAxis.zoomRange(lowerPercent, upperPercent); 1588 } 1589 1590 /** 1591 * Returns {@code false}. 1592 * 1593 * @return A boolean. 1594 */ 1595 @Override 1596 public boolean isDomainZoomable() { 1597 return false; 1598 } 1599 1600 /** 1601 * Returns {@code true}. 1602 * 1603 * @return A boolean. 1604 */ 1605 @Override 1606 public boolean isRangeZoomable() { 1607 return true; 1608 } 1609 1610}