Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.scene.chart; 027 028import javafx.css.CssMetaData; 029import javafx.css.StyleableBooleanProperty; 030import javafx.css.StyleableDoubleProperty; 031import javafx.css.StyleableIntegerProperty; 032import com.sun.javafx.css.converters.BooleanConverter; 033import com.sun.javafx.css.converters.SizeConverter; 034import java.util.ArrayList; 035import java.util.Collections; 036import java.util.List; 037 038import javafx.beans.property.*; 039import javafx.css.Styleable; 040import javafx.css.StyleableProperty; 041import javafx.geometry.Side; 042import javafx.scene.shape.LineTo; 043import javafx.scene.shape.MoveTo; 044import javafx.scene.shape.Path; 045import javafx.util.StringConverter; 046 047 048/** 049 * A axis who's data is defined as Numbers. It can also draw minor 050 * tick-marks between the major ones. 051 */ 052public abstract class ValueAxis<T extends Number> extends Axis<T> { 053 054 // -------------- PRIVATE FIELDS ----------------------------------------------------------------------------------- 055 056 private final Path minorTickPath = new Path(); 057 058 private boolean saveMinorTickVisible = false; 059 private boolean restoreMinorTickVisiblity = false; 060 061 private double offset; 062 /** This is the minimum current data value and it is used while auto ranging. */ 063 private double dataMinValue; 064 /** This is the maximum current data value and it is used while auto ranging. */ 065 private double dataMaxValue; 066 /** List of the values at which there are minor ticks */ 067 private List<T> minorTickMarkValues = null; 068 // -------------- PRIVATE PROPERTIES ------------------------------------------------------------------------------- 069 070 /** 071 * The current value for the lowerBound of this axis, ie min value. 072 * This may be the same as lowerBound or different. It is used by NumberAxis to animate the 073 * lowerBound from the old value to the new value. 074 */ 075 protected final DoubleProperty currentLowerBound = new SimpleDoubleProperty(this, "currentLowerBound"); 076 077 // -------------- PUBLIC PROPERTIES -------------------------------------------------------------------------------- 078 079 /** true if minor tick marks should be displayed */ 080 private BooleanProperty minorTickVisible = new StyleableBooleanProperty(true) { 081 @Override protected void invalidated() { 082 minorTickPath.setVisible(get()); 083 requestAxisLayout(); 084 } 085 086 @Override 087 public Object getBean() { 088 return ValueAxis.this; 089 } 090 091 @Override 092 public String getName() { 093 return "minorTickVisible"; 094 } 095 096 @Override 097 public CssMetaData<ValueAxis<? extends Number>,Boolean> getCssMetaData() { 098 return StyleableProperties.MINOR_TICK_VISIBLE; 099 } 100 }; 101 public final boolean isMinorTickVisible() { return minorTickVisible.get(); } 102 public final void setMinorTickVisible(boolean value) { minorTickVisible.set(value); } 103 public final BooleanProperty minorTickVisibleProperty() { return minorTickVisible; } 104 105 106 /** The scale factor from data units to visual units */ 107 private ReadOnlyDoubleWrapper scale = new ReadOnlyDoubleWrapper(this, "scale", 0); 108 public final double getScale() { return scale.get(); } 109 protected final void setScale(double scale) { this.scale.set(scale); } 110 public final ReadOnlyDoubleProperty scaleProperty() { return scale.getReadOnlyProperty(); } 111 ReadOnlyDoubleWrapper scalePropertyImpl() { return scale; } 112 113 /** The value for the upper bound of this axis, ie max value. This is automatically set if auto ranging is on. */ 114 private DoubleProperty upperBound = new DoublePropertyBase(100) { 115 @Override protected void invalidated() { 116 if(!isAutoRanging()) { 117 invalidateRange(); 118 requestAxisLayout(); 119 } 120 } 121 122 @Override 123 public Object getBean() { 124 return ValueAxis.this; 125 } 126 127 @Override 128 public String getName() { 129 return "upperBound"; 130 } 131 }; 132 public final double getUpperBound() { return upperBound.get(); } 133 public final void setUpperBound(double value) { upperBound.set(value); } 134 public final DoubleProperty upperBoundProperty() { return upperBound; } 135 136 /** The value for the lower bound of this axis, ie min value. This is automatically set if auto ranging is on. */ 137 private DoubleProperty lowerBound = new DoublePropertyBase(0) { 138 @Override protected void invalidated() { 139 if(!isAutoRanging()) { 140 invalidateRange(); 141 requestAxisLayout(); 142 } 143 } 144 145 @Override 146 public Object getBean() { 147 return ValueAxis.this; 148 } 149 150 @Override 151 public String getName() { 152 return "lowerBound"; 153 } 154 }; 155 public final double getLowerBound() { return lowerBound.get(); } 156 public final void setLowerBound(double value) { lowerBound.set(value); } 157 public final DoubleProperty lowerBoundProperty() { return lowerBound; } 158 159 /** StringConverter used to format tick mark labels. If null a default will be used */ 160 private final ObjectProperty<StringConverter<T>> tickLabelFormatter = new ObjectPropertyBase<StringConverter<T>>(null){ 161 @Override protected void invalidated() { 162 invalidateRange(); 163 formatterValid = true; 164 requestAxisLayout(); 165 } 166 167 @Override 168 public Object getBean() { 169 return ValueAxis.this; 170 } 171 172 @Override 173 public String getName() { 174 return "tickLabelFormatter"; 175 } 176 }; 177 public final StringConverter<T> getTickLabelFormatter() { return tickLabelFormatter.getValue(); } 178 public final void setTickLabelFormatter(StringConverter<T> value) { tickLabelFormatter.setValue(value); } 179 public final ObjectProperty<StringConverter<T>> tickLabelFormatterProperty() { return tickLabelFormatter; } 180 181 /** The length of minor tick mark lines. Set to 0 to not display minor tick marks. */ 182 private DoubleProperty minorTickLength = new StyleableDoubleProperty(5) { 183 @Override protected void invalidated() { 184 // RT-16747 if tick length is negative - set it to 0 185 if (minorTickLength.get() < 0 && !minorTickLength.isBound()) { 186 minorTickLength.set(0); 187 } 188 requestAxisLayout(); 189 } 190 191 @Override 192 public Object getBean() { 193 return ValueAxis.this; 194 } 195 196 @Override 197 public String getName() { 198 return "minorTickLength"; 199 } 200 201 @Override 202 public CssMetaData<ValueAxis<? extends Number>,Number> getCssMetaData() { 203 return StyleableProperties.MINOR_TICK_LENGTH; 204 } 205 }; 206 public final double getMinorTickLength() { return minorTickLength.get(); } 207 public final void setMinorTickLength(double value) { minorTickLength.set(value); } 208 public final DoubleProperty minorTickLengthProperty() { return minorTickLength; } 209 210 /** 211 * The number of minor tick divisions to be displayed between each major tick mark. 212 * The number of actual minor tick marks will be one less than this. 213 */ 214 private IntegerProperty minorTickCount = new StyleableIntegerProperty(5) { 215 @Override protected void invalidated() { 216 // RT-16747 if the tick count is negative, set it to 0 217 if ((minorTickCount.get() - 1) < 0 && !minorTickCount.isBound()) { 218 minorTickCount.set(0); 219 } 220 invalidateRange(); 221 requestAxisLayout(); 222 } 223 224 @Override 225 public Object getBean() { 226 return ValueAxis.this; 227 } 228 229 @Override 230 public String getName() { 231 return "minorTickCount"; 232 } 233 234 @Override 235 public CssMetaData<ValueAxis<? extends Number>,Number> getCssMetaData() { 236 return StyleableProperties.MINOR_TICK_COUNT; 237 } 238 }; 239 public final int getMinorTickCount() { return minorTickCount.get(); } 240 public final void setMinorTickCount(int value) { minorTickCount.set(value); } 241 public final IntegerProperty minorTickCountProperty() { return minorTickCount; } 242 243 // -------------- CONSTRUCTORS ------------------------------------------------------------------------------------- 244 245 /** 246 * Create a auto-ranging ValueAxis 247 */ 248 public ValueAxis() { 249 minorTickPath.getStyleClass().add("axis-minor-tick-mark"); 250 getChildren().add(minorTickPath); 251 } 252 253 /** 254 * Create a non-auto-ranging ValueAxis with the given upper & lower bound 255 * 256 * @param lowerBound The lower bound for this axis, ie min plottable value 257 * @param upperBound The upper bound for this axis, ie max plottable value 258 */ 259 public ValueAxis(double lowerBound, double upperBound) { 260 this(); 261 setAutoRanging(false); 262 setLowerBound(lowerBound); 263 setUpperBound(upperBound); 264 } 265 266 // -------------- PROTECTED METHODS -------------------------------------------------------------------------------- 267 268 269 /** 270 * This calculates the upper and lower bound based on the data provided to invalidateRange() method. This must not 271 * effect the state of the axis, changing any properties of the axis. Any results of the auto-ranging should be 272 * returned in the range object. This will we passed to setRange() if it has been decided to adopt this range for 273 * this axis. 274 * 275 * @param length The length of the axis in screen coordinates 276 * @return Range information, this is implementation dependent 277 */ 278 @Override protected final Object autoRange(double length) { 279 // guess a sensible starting size for label size, that is approx 2 lines vertically or 2 charts horizontally 280 if (isAutoRanging()) { 281 // guess a sensible starting size for label size, that is approx 2 lines vertically or 2 charts horizontally 282 double labelSize = getTickLabelFont().getSize() * 2; 283 return autoRange(dataMinValue,dataMaxValue,length,labelSize); 284 } else { 285 return getRange(); 286 } 287 } 288 289 /** 290 * Calculate a new scale for this axis. This should not effect any state(properties) of this axis. 291 * 292 * @param length The display length of the axis 293 * @param lowerBound The lower bound value 294 * @param upperBound The upper bound value 295 * @return new scale to fit the range from lower bound to upper bound in the given display length 296 */ 297 protected final double calculateNewScale(double length, double lowerBound, double upperBound) { 298 double newScale = 1; 299 final Side side = getSide(); 300 if(side != null) { 301 if (side.equals(Side.TOP) || side.equals(Side.BOTTOM)) { // HORIZONTAL 302 offset = 0; 303 newScale = ((upperBound-lowerBound) == 0) ? length : length / (upperBound - lowerBound); 304 } else { // VERTICAL 305 offset = length; 306 newScale = ((upperBound-lowerBound) == 0) ? -length : -(length / (upperBound - lowerBound)); 307 } 308 } 309 return newScale; 310 } 311 312 /** 313 * Called to set the upper and lower bound and anything else that needs to be auto-ranged. This must not effect 314 * the state of the axis, changing any properties of the axis. Any results of the auto-ranging should be returned 315 * in the range object. This will we passed to setRange() if it has been decided to adopt this range for this axis. 316 * 317 * @param minValue The min data value that needs to be plotted on this axis 318 * @param maxValue The max data value that needs to be plotted on this axis 319 * @param length The length of the axis in display coordinates 320 * @param labelSize The approximate average size a label takes along the axis 321 * @return The calculated range 322 */ 323 protected Object autoRange(double minValue, double maxValue, double length, double labelSize) { 324 return null; // this method should have been abstract as there is no way for it to 325 // return anything correct. so just return null. 326 327 } 328 329 /** 330 * Calculate a list of the data values for every minor tick mark 331 * 332 * @return List of data values where to draw minor tick marks 333 */ 334 protected abstract List<T> calculateMinorTickMarks(); 335 336 /** 337 * Called during layout if the tickmarks have been updated, allowing subclasses to do anything they need to 338 * in reaction. 339 */ 340 @Override protected void tickMarksUpdated() { 341 super.tickMarksUpdated(); 342 // recalculate minor tick marks 343 minorTickMarkValues = calculateMinorTickMarks(); 344 } 345 346 /** 347 * Invoked during the layout pass to layout this axis and all its content. 348 */ 349 @Override protected void layoutChildren() { 350 final Side side = getSide(); 351 final double length = (Side.TOP.equals(side) || Side.BOTTOM.equals(side)) ? getWidth() : getHeight(); 352 // if we are not auto ranging we need to calculate the new scale 353 if(!isAutoRanging()) { 354 // calculate new scale 355 setScale(calculateNewScale(length, getLowerBound(), getUpperBound())); 356 // update current lower bound 357 currentLowerBound.set(getLowerBound()); 358 } 359 // we have done all auto calcs, let Axis position major tickmarks 360 super.layoutChildren(); 361 int numMinorTicks = (getTickMarks().size() - 1)*(getMinorTickCount() - 1); 362 double neededLength = (getTickMarks().size()+numMinorTicks)*2; 363 if (length < neededLength) { 364 if (!restoreMinorTickVisiblity) { 365 restoreMinorTickVisiblity = true; 366 saveMinorTickVisible = isMinorTickVisible(); 367 setMinorTickVisible(false); 368 } 369 } else { 370 if (restoreMinorTickVisiblity) { 371 setMinorTickVisible(saveMinorTickVisible); 372 restoreMinorTickVisiblity = false; 373 } 374 } 375 // Update minor tickmarks 376 minorTickPath.getElements().clear(); 377 if (getMinorTickLength() > 0) { 378 if (side.equals(Side.LEFT)) { 379 // snap minorTickPath to pixels 380 minorTickPath.setLayoutX(-0.5); 381 minorTickPath.setLayoutY(0.5); 382 for (T value : minorTickMarkValues) { 383 double y = getDisplayPosition(value); 384 if(y >= 0 && y <= length) { 385 minorTickPath.getElements().addAll( 386 new MoveTo(getWidth() - getMinorTickLength(), y), 387 new LineTo(getWidth()-1, y)); 388 } 389 } 390 } else if (side.equals(Side.RIGHT)) { 391 // snap minorTickPath to pixels 392 minorTickPath.setLayoutX(0.5); 393 minorTickPath.setLayoutY(0.5); 394 for (T value : minorTickMarkValues) { 395 double y = getDisplayPosition(value); 396 if(y >= 0 && y <= length) { 397 minorTickPath.getElements().addAll( 398 new MoveTo(1, y), 399 new LineTo(getMinorTickLength(), y)); 400 } 401 } 402 } else if (side.equals(Side.TOP)) { 403 // snap minorTickPath to pixels 404 minorTickPath.setLayoutX(0.5); 405 minorTickPath.setLayoutY(-0.5); 406 for (T value : minorTickMarkValues) { 407 double x = getDisplayPosition(value); 408 if(x >= 0 && x <= length) { 409 minorTickPath.getElements().addAll( 410 new MoveTo(x, getHeight()-1), 411 new LineTo(x, getHeight() - getMinorTickLength())); 412 } 413 } 414 } else if (side.equals(Side.BOTTOM)) { 415 // snap minorTickPath to pixels 416 minorTickPath.setLayoutX(0.5); 417 minorTickPath.setLayoutY(0.5); 418 for (T value : minorTickMarkValues) { 419 double x = getDisplayPosition(value); 420 if(x >= 0 && x <= length) { 421 minorTickPath.getElements().addAll( 422 new MoveTo(x, 1.0F), 423 new LineTo(x, getMinorTickLength())); 424 } 425 } 426 } 427 } 428 } 429 430 // -------------- METHODS ------------------------------------------------------------------------------------------ 431 432 /** 433 * Called when data has changed and the range may not be valid any more. This is only called by the chart if 434 * isAutoRanging() returns true. If we are auto ranging it will cause layout to be requested and auto ranging to 435 * happen on next layout pass. 436 * 437 * @param data The current set of all data that needs to be plotted on this axis 438 */ 439 @Override public void invalidateRange(List<T> data) { 440 if (data.isEmpty()) { 441 dataMaxValue = getUpperBound(); 442 dataMinValue = getLowerBound(); 443 } else { 444 dataMinValue = Double.MAX_VALUE; 445 dataMaxValue = Double.MIN_VALUE; 446 } 447 for(T dataValue: data) { 448 dataMinValue = Math.min(dataMinValue, dataValue.doubleValue()); 449 dataMaxValue = Math.max(dataMaxValue, dataValue.doubleValue()); 450 } 451 super.invalidateRange(data); 452 } 453 454 /** 455 * Get the display position along this axis for a given value 456 * 457 * @param value The data value to work out display position for 458 * @return display position or Double.NaN if zero is not in current range; 459 */ 460 @Override public double getDisplayPosition(T value) { 461 return Math.ceil(offset + ((value.doubleValue() - currentLowerBound.get()) * getScale())); 462 } 463 464 /** 465 * Get the data value for the given display position on this axis. If the axis 466 * is a CategoryAxis this will be the nearest value. 467 * 468 * @param displayPosition A pixel position on this axis 469 * @return the nearest data value to the given pixel position or 470 * null if not on axis; 471 */ 472 @Override public T getValueForDisplay(double displayPosition) { 473 return toRealValue(((displayPosition-offset) / getScale()) + currentLowerBound.get()); 474 } 475 476 /** 477 * Get the display position of the zero line along this axis. 478 * 479 * @return display position or Double.NaN if zero is not in current range; 480 */ 481 @Override public double getZeroPosition() { 482 if (0 < getLowerBound() || 0 > getUpperBound()) return Double.NaN; 483 //noinspection unchecked 484 return getDisplayPosition((T)Double.valueOf(0)); 485 } 486 487 /** 488 * Checks if the given value is plottable on this axis 489 * 490 * @param value The value to check if its on axis 491 * @return true if the given value is plottable on this axis 492 */ 493 @Override public boolean isValueOnAxis(T value) { 494 final double num = value.doubleValue(); 495 return num >= getLowerBound() && num <= getUpperBound(); 496 } 497 498 /** 499 * All axis values must be representable by some numeric value. This gets the numeric value for a given data value. 500 * 501 * @param value The data value to convert 502 * @return Numeric value for the given data value 503 */ 504 @Override public double toNumericValue(T value) { 505 return (value == null) ? Double.NaN : value.doubleValue(); 506 } 507 508 /** 509 * All axis values must be representable by some numeric value. This gets the data value for a given numeric value. 510 * 511 * @param value The numeric value to convert 512 * @return Data value for given numeric value 513 */ 514 @Override public T toRealValue(double value) { 515 //noinspection unchecked 516 return (T)new Double(value); 517 } 518 519 // -------------- STYLESHEET HANDLING ------------------------------------------------------------------------------ 520 521 /** @treatAsPrivate implementation detail */ 522 private static class StyleableProperties { 523 private static final CssMetaData<ValueAxis<? extends Number>,Number> MINOR_TICK_LENGTH = 524 new CssMetaData<ValueAxis<? extends Number>,Number>("-fx-minor-tick-length", 525 SizeConverter.getInstance(), 5.0) { 526 527 @Override 528 public boolean isSettable(ValueAxis<? extends Number> n) { 529 return n.minorTickLength == null || !n.minorTickLength.isBound(); 530 } 531 532 @Override 533 public StyleableProperty<Number> getStyleableProperty(ValueAxis<? extends Number> n) { 534 return (StyleableProperty<Number>)n.minorTickLengthProperty(); 535 } 536 }; 537 538 private static final CssMetaData<ValueAxis<? extends Number>,Number> MINOR_TICK_COUNT = 539 new CssMetaData<ValueAxis<? extends Number>,Number>("-fx-minor-tick-count", 540 SizeConverter.getInstance(), 5) { 541 542 @Override 543 public boolean isSettable(ValueAxis<? extends Number> n) { 544 return n.minorTickCount == null || !n.minorTickCount.isBound(); 545 } 546 547 @Override 548 public StyleableProperty<Number> getStyleableProperty(ValueAxis<? extends Number> n) { 549 return (StyleableProperty<Number>)n.minorTickCountProperty(); 550 } 551 }; 552 553 private static final CssMetaData<ValueAxis<? extends Number>,Boolean> MINOR_TICK_VISIBLE = 554 new CssMetaData<ValueAxis<? extends Number>,Boolean>("-fx-minor-tick-visible", 555 BooleanConverter.getInstance(), Boolean.TRUE) { 556 557 @Override 558 public boolean isSettable(ValueAxis<? extends Number> n) { 559 return n.minorTickVisible == null || !n.minorTickVisible.isBound(); 560 } 561 562 @Override 563 public StyleableProperty<Boolean> getStyleableProperty(ValueAxis<? extends Number> n) { 564 return (StyleableProperty<Boolean>)n.minorTickVisibleProperty(); 565 } 566 }; 567 568 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 569 static { 570 final List<CssMetaData<? extends Styleable, ?>> styleables = 571 new ArrayList<CssMetaData<? extends Styleable, ?>>(Axis.getClassCssMetaData()); 572 styleables.add(MINOR_TICK_COUNT); 573 styleables.add(MINOR_TICK_LENGTH); 574 styleables.add(MINOR_TICK_COUNT); 575 styleables.add(MINOR_TICK_VISIBLE); 576 STYLEABLES = Collections.unmodifiableList(styleables); 577 } 578 } 579 580 /** 581 * @return The CssMetaData associated with this class, which may include the 582 * CssMetaData of its super classes. 583 */ 584 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 585 return StyleableProperties.STYLEABLES; 586 } 587 588 /** 589 * {@inheritDoc} 590 */ 591 @Override 592 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 593 return getClassCssMetaData(); 594 } 595 596}