1 /* 2 * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.scene.chart; 27 28 import java.text.DecimalFormat; 29 import java.text.ParseException; 30 import java.util.ArrayList; 31 import java.util.Collections; 32 import java.util.List; 33 34 import javafx.animation.KeyFrame; 35 import javafx.animation.KeyValue; 36 import javafx.beans.property.BooleanProperty; 37 import javafx.beans.property.BooleanPropertyBase; 38 import javafx.beans.property.DoubleProperty; 39 import javafx.beans.property.SimpleStringProperty; 40 import javafx.beans.property.StringProperty; 41 import javafx.beans.value.ChangeListener; 42 import javafx.beans.value.WritableValue; 43 import javafx.geometry.Dimension2D; 44 import javafx.geometry.Side; 45 import javafx.util.Duration; 46 import javafx.util.StringConverter; 47 48 import com.sun.javafx.charts.ChartLayoutAnimator; 49 50 import javafx.css.StyleableDoubleProperty; 51 import javafx.css.CssMetaData; 52 53 import javafx.css.converter.SizeConverter; 54 55 import javafx.css.Styleable; 56 import javafx.css.StyleableProperty; 57 58 /** 59 * A axis class that plots a range of numbers with major tick marks every "tickUnit". You can use any Number type with 60 * this axis, Long, Double, BigDecimal etc. 61 * @since JavaFX 2.0 62 */ 63 public final class NumberAxis extends ValueAxis<Number> { 64 65 private Object currentAnimationID; 66 private final ChartLayoutAnimator animator = new ChartLayoutAnimator(this); 67 private final StringProperty currentFormatterProperty = new SimpleStringProperty(this, "currentFormatter", ""); 68 private final DefaultFormatter defaultFormatter = new DefaultFormatter(this); 69 70 // -------------- PUBLIC PROPERTIES -------------------------------------------------------------------------------- 71 72 /** When true zero is always included in the visible range. This only has effect if auto-ranging is on. */ 73 private BooleanProperty forceZeroInRange = new BooleanPropertyBase(true) { 74 @Override protected void invalidated() { 75 // This will effect layout if we are auto ranging 76 if(isAutoRanging()) { 77 requestAxisLayout(); 78 invalidateRange(); 79 } 80 } 81 82 @Override 83 public Object getBean() { 84 return NumberAxis.this; 85 } 86 87 @Override 88 public String getName() { 89 return "forceZeroInRange"; 90 } 91 }; 92 public final boolean isForceZeroInRange() { return forceZeroInRange.getValue(); } 93 public final void setForceZeroInRange(boolean value) { forceZeroInRange.setValue(value); } 94 public final BooleanProperty forceZeroInRangeProperty() { return forceZeroInRange; } 95 96 /** The value between each major tick mark in data units. This is automatically set if we are auto-ranging. */ 97 private DoubleProperty tickUnit = new StyleableDoubleProperty(5) { 98 @Override protected void invalidated() { 99 if(!isAutoRanging()) { 100 invalidateRange(); 101 requestAxisLayout(); 102 } 103 } 104 105 @Override 106 public CssMetaData<NumberAxis,Number> getCssMetaData() { 107 return StyleableProperties.TICK_UNIT; 108 } 109 110 @Override 111 public Object getBean() { 112 return NumberAxis.this; 113 } 114 115 @Override 116 public String getName() { 117 return "tickUnit"; 118 } 119 }; 120 public final double getTickUnit() { return tickUnit.get(); } 121 public final void setTickUnit(double value) { tickUnit.set(value); } 122 public final DoubleProperty tickUnitProperty() { return tickUnit; } 123 124 // -------------- CONSTRUCTORS ------------------------------------------------------------------------------------- 125 126 /** 127 * Create a auto-ranging NumberAxis 128 */ 129 public NumberAxis() {} 130 131 /** 132 * Create a non-auto-ranging NumberAxis with the given upper bound, lower bound and tick unit 133 * 134 * @param lowerBound The lower bound for this axis, ie min plottable value 135 * @param upperBound The upper bound for this axis, ie max plottable value 136 * @param tickUnit The tick unit, ie space between tickmarks 137 */ 138 public NumberAxis(double lowerBound, double upperBound, double tickUnit) { 139 super(lowerBound, upperBound); 140 setTickUnit(tickUnit); 141 } 142 143 /** 144 * Create a non-auto-ranging NumberAxis with the given upper bound, lower bound and tick unit 145 * 146 * @param axisLabel The name to display for this axis 147 * @param lowerBound The lower bound for this axis, ie min plottable value 148 * @param upperBound The upper bound for this axis, ie max plottable value 149 * @param tickUnit The tick unit, ie space between tickmarks 150 */ 151 public NumberAxis(String axisLabel, double lowerBound, double upperBound, double tickUnit) { 152 super(lowerBound, upperBound); 153 setTickUnit(tickUnit); 154 setLabel(axisLabel); 155 } 156 157 // -------------- PROTECTED METHODS -------------------------------------------------------------------------------- 158 159 /** 160 * Get the string label name for a tick mark with the given value 161 * 162 * @param value The value to format into a tick label string 163 * @return A formatted string for the given value 164 */ 165 @Override protected String getTickMarkLabel(Number value) { 166 StringConverter<Number> formatter = getTickLabelFormatter(); 167 if (formatter == null) formatter = defaultFormatter; 168 return formatter.toString(value); 169 } 170 171 /** 172 * Called to get the current axis range. 173 * 174 * @return A range object that can be passed to setRange() and calculateTickValues() 175 */ 176 @Override protected Object getRange() { 177 return new Object[]{ 178 getLowerBound(), 179 getUpperBound(), 180 getTickUnit(), 181 getScale(), 182 currentFormatterProperty.get() 183 }; 184 } 185 186 /** 187 * Called to set the current axis range to the given range. If isAnimating() is true then this method should 188 * animate the range to the new range. 189 * 190 * @param range A range object returned from autoRange() 191 * @param animate If true animate the change in range 192 */ 193 @Override protected void setRange(Object range, boolean animate) { 194 final Object[] rangeProps = (Object[]) range; 195 final double lowerBound = (Double)rangeProps[0]; 196 final double upperBound = (Double)rangeProps[1]; 197 final double tickUnit = (Double)rangeProps[2]; 198 final double scale = (Double)rangeProps[3]; 199 final String formatter = (String)rangeProps[4]; 200 currentFormatterProperty.set(formatter); 201 final double oldLowerBound = getLowerBound(); 202 setLowerBound(lowerBound); 203 setUpperBound(upperBound); 204 setTickUnit(tickUnit); 205 if(animate) { 206 animator.stop(currentAnimationID); 207 currentAnimationID = animator.animate( 208 new KeyFrame(Duration.ZERO, 209 new KeyValue(currentLowerBound, oldLowerBound), 210 new KeyValue(scalePropertyImpl(), getScale()) 211 ), 212 new KeyFrame(Duration.millis(700), 213 new KeyValue(currentLowerBound, lowerBound), 214 new KeyValue(scalePropertyImpl(), scale) 215 ) 216 ); 217 } else { 218 currentLowerBound.set(lowerBound); 219 setScale(scale); 220 } 221 } 222 223 /** 224 * Calculate a list of all the data values for each tick mark in range 225 * 226 * @param length The length of the axis in display units 227 * @param range A range object returned from autoRange() 228 * @return A list of tick marks that fit along the axis if it was the given length 229 */ 230 @Override protected List<Number> calculateTickValues(double length, Object range) { 231 final Object[] rangeProps = (Object[]) range; 232 final double lowerBound = (Double)rangeProps[0]; 233 final double upperBound = (Double)rangeProps[1]; 234 final double tickUnit = (Double)rangeProps[2]; 235 List<Number> tickValues = new ArrayList<>(); 236 if (lowerBound == upperBound) { 237 tickValues.add(lowerBound); 238 } else if (tickUnit <= 0) { 239 tickValues.add(lowerBound); 240 tickValues.add(upperBound); 241 } else if (tickUnit > 0) { 242 tickValues.add(lowerBound); 243 if (((upperBound - lowerBound) / tickUnit) > 2000) { 244 // This is a ridiculous amount of major tick marks, something has probably gone wrong 245 System.err.println("Warning we tried to create more than 2000 major tick marks on a NumberAxis. " + 246 "Lower Bound=" + lowerBound + ", Upper Bound=" + upperBound + ", Tick Unit=" + tickUnit); 247 } else { 248 if (lowerBound + tickUnit < upperBound) { 249 // If tickUnit is integer, start with the nearest integer 250 double first = Math.rint(tickUnit) == tickUnit ? Math.ceil(lowerBound) : lowerBound + tickUnit; 251 for (double major = first; major < upperBound; major += tickUnit) { 252 if (!tickValues.contains(major)) { 253 tickValues.add(major); 254 } 255 } 256 } 257 } 258 tickValues.add(upperBound); 259 } 260 return tickValues; 261 } 262 263 /** 264 * Calculate a list of the data values for every minor tick mark 265 * 266 * @return List of data values where to draw minor tick marks 267 */ 268 protected List<Number> calculateMinorTickMarks() { 269 final List<Number> minorTickMarks = new ArrayList<>(); 270 final double lowerBound = getLowerBound(); 271 final double upperBound = getUpperBound(); 272 final double tickUnit = getTickUnit(); 273 final double minorUnit = tickUnit/Math.max(1, getMinorTickCount()); 274 if (tickUnit > 0) { 275 if(((upperBound - lowerBound) / minorUnit) > 10000) { 276 // This is a ridiculous amount of major tick marks, something has probably gone wrong 277 System.err.println("Warning we tried to create more than 10000 minor tick marks on a NumberAxis. " + 278 "Lower Bound=" + getLowerBound() + ", Upper Bound=" + getUpperBound() + ", Tick Unit=" + tickUnit); 279 return minorTickMarks; 280 } 281 final boolean tickUnitIsInteger = Math.rint(tickUnit) == tickUnit; 282 if (tickUnitIsInteger) { 283 for (double minor = Math.floor(lowerBound) + minorUnit; minor < Math.ceil(lowerBound); minor += minorUnit) { 284 if (minor > lowerBound) { 285 minorTickMarks.add(minor); 286 } 287 } 288 } 289 double major = tickUnitIsInteger ? Math.ceil(lowerBound) : lowerBound; 290 for (; major < upperBound; major += tickUnit) { 291 final double next = Math.min(major + tickUnit, upperBound); 292 for (double minor = major + minorUnit; minor < next; minor += minorUnit) { 293 minorTickMarks.add(minor); 294 } 295 } 296 } 297 return minorTickMarks; 298 } 299 300 /** 301 * Measure the size of the label for given tick mark value. This uses the font that is set for the tick marks 302 * 303 * @param value tick mark value 304 * @param range range to use during calculations 305 * @return size of tick mark label for given value 306 */ 307 @Override protected Dimension2D measureTickMarkSize(Number value, Object range) { 308 final Object[] rangeProps = (Object[]) range; 309 final String formatter = (String)rangeProps[4]; 310 return measureTickMarkSize(value, getTickLabelRotation(), formatter); 311 } 312 313 /** 314 * Measure the size of the label for given tick mark value. This uses the font that is set for the tick marks 315 * 316 * @param value tick mark value 317 * @param rotation The text rotation 318 * @param numFormatter The number formatter 319 * @return size of tick mark label for given value 320 */ 321 private Dimension2D measureTickMarkSize(Number value, double rotation, String numFormatter) { 322 String labelText; 323 StringConverter<Number> formatter = getTickLabelFormatter(); 324 if (formatter == null) formatter = defaultFormatter; 325 if(formatter instanceof DefaultFormatter) { 326 labelText = ((DefaultFormatter)formatter).toString(value, numFormatter); 327 } else { 328 labelText = formatter.toString(value); 329 } 330 return measureTickMarkLabelSize(labelText, rotation); 331 } 332 333 /** 334 * Called to set the upper and lower bound and anything else that needs to be auto-ranged 335 * 336 * @param minValue The min data value that needs to be plotted on this axis 337 * @param maxValue The max data value that needs to be plotted on this axis 338 * @param length The length of the axis in display coordinates 339 * @param labelSize The approximate average size a label takes along the axis 340 * @return The calculated range 341 */ 342 @Override protected Object autoRange(double minValue, double maxValue, double length, double labelSize) { 343 final Side side = getEffectiveSide(); 344 // check if we need to force zero into range 345 if (isForceZeroInRange()) { 346 if (maxValue < 0) { 347 maxValue = 0; 348 } else if (minValue > 0) { 349 minValue = 0; 350 } 351 } 352 final double range = maxValue-minValue; 353 // pad min and max by 2%, checking if the range is zero 354 final double paddedRange = (range==0) ? 2 : Math.abs(range)*1.02; 355 final double padding = (paddedRange - range) / 2; 356 // if min and max are not zero then add padding to them 357 double paddedMin = minValue - padding; 358 double paddedMax = maxValue + padding; 359 // check padding has not pushed min or max over zero line 360 if ((paddedMin < 0 && minValue >= 0) || (paddedMin > 0 && minValue <= 0)) { 361 // padding pushed min above or below zero so clamp to 0 362 paddedMin = 0; 363 } 364 if ((paddedMax < 0 && maxValue >= 0) || (paddedMax > 0 && maxValue <= 0)) { 365 // padding pushed min above or below zero so clamp to 0 366 paddedMax = 0; 367 } 368 // calculate the number of tick-marks we can fit in the given length 369 int numOfTickMarks = (int)Math.floor(length/labelSize); 370 // can never have less than 2 tick marks one for each end 371 numOfTickMarks = Math.max(numOfTickMarks, 2); 372 // calculate tick unit for the number of ticks can have in the given data range 373 double tickUnit = paddedRange/(double)numOfTickMarks; 374 // search for the best tick unit that fits 375 double tickUnitRounded = 0; 376 double minRounded = 0; 377 double maxRounded = 0; 378 int count = 0; 379 double reqLength = Double.MAX_VALUE; 380 String formatter = "0.00000000"; 381 // loop till we find a set of ticks that fit length and result in a total of less than 20 tick marks 382 while (reqLength > length || count > 20) { 383 int exp = (int)Math.floor(Math.log10(tickUnit)); 384 final double mant = tickUnit / Math.pow(10, exp); 385 double ratio = mant; 386 if (mant > 5d) { 387 exp++; 388 ratio = 1; 389 } else if (mant > 1d) { 390 ratio = mant > 2.5 ? 5 : 2.5; 391 } 392 if (exp > 1) { 393 formatter = "#,##0"; 394 } else if (exp == 1) { 395 formatter = "0"; 396 } else { 397 final boolean ratioHasFrac = Math.rint(ratio) != ratio; 398 final StringBuilder formatterB = new StringBuilder("0"); 399 int n = ratioHasFrac ? Math.abs(exp) + 1 : Math.abs(exp); 400 if (n > 0) formatterB.append("."); 401 for (int i = 0; i < n; ++i) { 402 formatterB.append("0"); 403 } 404 formatter = formatterB.toString(); 405 406 } 407 tickUnitRounded = ratio * Math.pow(10, exp); 408 // move min and max to nearest tick mark 409 minRounded = Math.floor(paddedMin / tickUnitRounded) * tickUnitRounded; 410 maxRounded = Math.ceil(paddedMax / tickUnitRounded) * tickUnitRounded; 411 // calculate the required length to display the chosen tick marks for real, this will handle if there are 412 // huge numbers involved etc or special formatting of the tick mark label text 413 double maxReqTickGap = 0; 414 double last = 0; 415 count = 0; 416 for (double major = minRounded; major <= maxRounded; major += tickUnitRounded, count ++) { 417 double size = side.isVertical() ? measureTickMarkSize(major, getTickLabelRotation(), formatter).getHeight() : 418 measureTickMarkSize(major, getTickLabelRotation(), formatter).getWidth(); 419 if (major == minRounded) { // first 420 last = size/2; 421 } else { 422 maxReqTickGap = Math.max(maxReqTickGap, last + 6 + (size/2) ); 423 } 424 } 425 reqLength = (count-1) * maxReqTickGap; 426 tickUnit = tickUnitRounded; 427 428 // fix for RT-35600 where a massive tick unit was being selected 429 // unnecessarily. There is probably a better solution, but this works 430 // well enough for now. 431 if (numOfTickMarks == 2 && reqLength > length) { 432 break; 433 } 434 if (reqLength > length || count > 20) tickUnit *= 2; // This is just for the while loop, if there are still too many ticks 435 } 436 // calculate new scale 437 final double newScale = calculateNewScale(length, minRounded, maxRounded); 438 // return new range 439 return new Object[]{minRounded, maxRounded, tickUnitRounded, newScale, formatter}; 440 } 441 442 // -------------- STYLESHEET HANDLING ------------------------------------------------------------------------------ 443 444 /** @treatAsPrivate implementation detail */ 445 private static class StyleableProperties { 446 private static final CssMetaData<NumberAxis,Number> TICK_UNIT = 447 new CssMetaData<NumberAxis,Number>("-fx-tick-unit", 448 SizeConverter.getInstance(), 5.0) { 449 450 @Override 451 public boolean isSettable(NumberAxis n) { 452 return n.tickUnit == null || !n.tickUnit.isBound(); 453 } 454 455 @Override 456 public StyleableProperty<Number> getStyleableProperty(NumberAxis n) { 457 return (StyleableProperty<Number>)(WritableValue<Number>)n.tickUnitProperty(); 458 } 459 }; 460 461 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 462 static { 463 final List<CssMetaData<? extends Styleable, ?>> styleables = 464 new ArrayList<CssMetaData<? extends Styleable, ?>>(ValueAxis.getClassCssMetaData()); 465 styleables.add(TICK_UNIT); 466 STYLEABLES = Collections.unmodifiableList(styleables); 467 } 468 } 469 470 /** 471 * @return The CssMetaData associated with this class, which may include the 472 * CssMetaData of its super classes. 473 * @since JavaFX 8.0 474 */ 475 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 476 return StyleableProperties.STYLEABLES; 477 } 478 479 /** 480 * {@inheritDoc} 481 * @since JavaFX 8.0 482 */ 483 @Override 484 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 485 return getClassCssMetaData(); 486 } 487 488 // -------------- INNER CLASSES ------------------------------------------------------------------------------------ 489 490 /** 491 * Default number formatter for NumberAxis, this stays in sync with auto-ranging and formats values appropriately. 492 * You can wrap this formatter to add prefixes or suffixes; 493 * @since JavaFX 2.0 494 */ 495 public static class DefaultFormatter extends StringConverter<Number> { 496 private DecimalFormat formatter; 497 private String prefix = null; 498 private String suffix = null; 499 500 /** 501 * Construct a DefaultFormatter for the given NumberAxis 502 * 503 * @param axis The axis to format tick marks for 504 */ 505 public DefaultFormatter(final NumberAxis axis) { 506 formatter = axis.isAutoRanging()? new DecimalFormat(axis.currentFormatterProperty.get()) : new DecimalFormat(); 507 final ChangeListener<Object> axisListener = (observable, oldValue, newValue) -> { 508 formatter = axis.isAutoRanging()? new DecimalFormat(axis.currentFormatterProperty.get()) : new DecimalFormat(); 509 }; 510 axis.currentFormatterProperty.addListener(axisListener); 511 axis.autoRangingProperty().addListener(axisListener); 512 } 513 514 /** 515 * Construct a DefaultFormatter for the given NumberAxis with a prefix and/or suffix. 516 * 517 * @param axis The axis to format tick marks for 518 * @param prefix The prefix to append to the start of formatted number, can be null if not needed 519 * @param suffix The suffix to append to the end of formatted number, can be null if not needed 520 */ 521 public DefaultFormatter(NumberAxis axis, String prefix, String suffix) { 522 this(axis); 523 this.prefix = prefix; 524 this.suffix = suffix; 525 } 526 527 /** 528 * Converts the object provided into its string form. 529 * Format of the returned string is defined by this converter. 530 * @return a string representation of the object passed in. 531 * @see StringConverter#toString 532 */ 533 @Override public String toString(Number object) { 534 return toString(object, formatter); 535 } 536 537 private String toString(Number object, String numFormatter) { 538 if (numFormatter == null || numFormatter.isEmpty()) { 539 return toString(object, formatter); 540 } else { 541 return toString(object, new DecimalFormat(numFormatter)); 542 } 543 } 544 545 private String toString(Number object, DecimalFormat formatter) { 546 if (prefix != null && suffix != null) { 547 return prefix + formatter.format(object) + suffix; 548 } else if (prefix != null) { 549 return prefix + formatter.format(object); 550 } else if (suffix != null) { 551 return formatter.format(object) + suffix; 552 } else { 553 return formatter.format(object); 554 } 555 } 556 557 /** 558 * Converts the string provided into a Number defined by the this converter. 559 * Format of the string and type of the resulting object is defined by this converter. 560 * @return a Number representation of the string passed in. 561 * @see StringConverter#toString 562 */ 563 @Override public Number fromString(String string) { 564 try { 565 int prefixLength = (prefix == null)? 0: prefix.length(); 566 int suffixLength = (suffix == null)? 0: suffix.length(); 567 return formatter.parse(string.substring(prefixLength, string.length() - suffixLength)); 568 } catch (ParseException e) { 569 return null; 570 } 571 } 572 } 573 574 } 575 576 /* 577 // Code to generate tick unit defaults 578 579 public static void main(String[] args) { 580 List<BigDecimal> values = new ArrayList<BigDecimal>(); 581 List<String> formats = new ArrayList<String>(); 582 for(int power=-10; power <= 12; power ++) { 583 BigDecimal val = new BigDecimal(10); 584 val = val.pow(power, MathContext.DECIMAL32); 585 BigDecimal val2 = val.multiply(new BigDecimal(2.5d)); 586 BigDecimal val5 = val.multiply(new BigDecimal(5d)); 587 values.add(val); 588 values.add(val2); 589 values.add(val5); 590 System.out.print("["+power+"] "); 591 System.out.print( 592 val.doubleValue() + "d, " + 593 val2.doubleValue() + "d, " + 594 val5.doubleValue() + "d, " 595 ); 596 DecimalFormat df = null; 597 DecimalFormat dfTwoHalf = null; 598 if (power < 0) { 599 String nf = "0."; 600 for (int i=0; i<Math.abs(power); i++) nf = nf+"0"; 601 System.out.print(" --- nf = " + nf); 602 String nf2 = "0."; 603 for (int i=0; i<=Math.abs(power); i++) nf2 = nf2+"0"; 604 System.out.print(" --- nf2 = " + nf2); 605 df = new DecimalFormat(nf); 606 dfTwoHalf = new DecimalFormat(nf2); 607 formats.add(nf); 608 formats.add(nf2); 609 formats.add(nf); 610 } else if (power == 0) { 611 df = new DecimalFormat("0"); 612 dfTwoHalf = new DecimalFormat("0.0"); 613 formats.add("0"); 614 formats.add("0.0"); 615 formats.add("0"); 616 } else { 617 String nf = "0"; 618 for (int i=0; i<Math.abs(power); i++) { 619 if((i % 3) == 2) { 620 nf = "#," + nf; 621 } else { 622 nf = "#" + nf; 623 } 624 } 625 System.out.print(" --- nf = " + nf); 626 formats.add(nf); 627 formats.add(nf); 628 formats.add(nf); 629 dfTwoHalf = df = new DecimalFormat(nf); 630 } 631 System.out.println(" --- "+ 632 df.format(val.doubleValue())+", "+ 633 dfTwoHalf.format(val2.doubleValue())+", "+ 634 df.format(val5.doubleValue())+", " 635 ); 636 } 637 System.out.print(" private static final double[] TICK_UNIT_DEFAULTS = { "); 638 for(BigDecimal val: values) System.out.print(val.doubleValue()+", "); 639 System.out.println(" };"); 640 System.out.print(" private static final String[] TICK_UNIT_FORMATTER_DEFAULTS = { "); 641 for(String format: formats) System.out.print("\""+format+"\", "); 642 System.out.println(" };"); 643 } 644 */