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