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 java.text.DecimalFormat;
029import java.text.ParseException;
030import java.util.ArrayList;
031import java.util.Collections;
032import java.util.List;
033
034import javafx.animation.KeyFrame;
035import javafx.animation.KeyValue;
036import javafx.beans.property.BooleanProperty;
037import javafx.beans.property.BooleanPropertyBase;
038import javafx.beans.property.DoubleProperty;
039import javafx.beans.property.IntegerProperty;
040import javafx.beans.property.SimpleIntegerProperty;
041import javafx.beans.value.ChangeListener;
042import javafx.beans.value.ObservableValue;
043import javafx.geometry.Dimension2D;
044import javafx.geometry.Side;
045import javafx.util.Duration;
046import javafx.util.StringConverter;
047
048import com.sun.javafx.charts.ChartLayoutAnimator;
049import javafx.css.StyleableDoubleProperty;
050import javafx.css.CssMetaData;
051import com.sun.javafx.css.converters.SizeConverter;
052import javafx.css.Styleable;
053import javafx.css.StyleableProperty;
054
055/**
056 * A axis class that plots a range of numbers with major tick marks every "tickUnit". You can use any Number type with
057 * this axis, Long, Double, BigDecimal etc.
058 */
059public final class NumberAxis extends ValueAxis<Number> {
060
061    /** We use these for auto ranging to pick a user friendly tick unit. We handle tick units in the range of 1e-10 to 1e+12 */
062    private static final double[] TICK_UNIT_DEFAULTS = {
063        1.0E-10d, 2.5E-10d, 5.0E-10d, 1.0E-9d, 2.5E-9d, 5.0E-9d, 1.0E-8d, 2.5E-8d, 5.0E-8d, 1.0E-7d, 2.5E-7d, 5.0E-7d,
064        1.0E-6d, 2.5E-6d, 5.0E-6d, 1.0E-5d, 2.5E-5d, 5.0E-5d, 1.0E-4d, 2.5E-4d, 5.0E-4d, 0.0010d, 0.0025d, 0.0050d,
065        0.01d, 0.025d, 0.05d, 0.1d, 0.25d, 0.5d, 1.0d, 2.5d, 5.0d, 10.0d, 25.0d, 50.0d, 100.0d, 250.0d, 500.0d,
066        1000.0d, 2500.0d, 5000.0d, 10000.0d, 25000.0d, 50000.0d, 100000.0d, 250000.0d, 500000.0d, 1000000.0d,
067        2500000.0d, 5000000.0d, 1.0E7d, 2.5E7d, 5.0E7d, 1.0E8d, 2.5E8d, 5.0E8d, 1.0E9d, 2.5E9d, 5.0E9d, 1.0E10d,
068        2.5E10d, 5.0E10d, 1.0E11d, 2.5E11d, 5.0E11d, 1.0E12d, 2.5E12d, 5.0E12d
069    };
070    /** These are matching decimal formatter strings */
071    private static final String[] TICK_UNIT_FORMATTER_DEFAULTS = {"0.0000000000", "0.00000000000", "0.0000000000",
072                                                                  "0.000000000", "0.0000000000", "0.000000000",
073                                                                  "0.00000000", "0.000000000", "0.00000000",
074                                                                  "0.0000000", "0.00000000", "0.0000000", "0.000000",
075                                                                  "0.0000000", "0.000000", "0.00000", "0.000000",
076                                                                  "0.00000", "0.0000", "0.00000", "0.0000", "0.000",
077                                                                  "0.0000", "0.000", "0.00", "0.000", "0.00", "0.0",
078                                                                  "0.00", "0.0", "0", "0.0", "0", "#,##0"};
079
080    private Object currentAnimationID;
081    private final ChartLayoutAnimator animator = new ChartLayoutAnimator(this);
082    private IntegerProperty currentRangeIndexProperty = new SimpleIntegerProperty(this, "currentRangeIndex", -1);
083    private DefaultFormatter defaultFormatter = new DefaultFormatter(this);
084
085    // -------------- PUBLIC PROPERTIES --------------------------------------------------------------------------------
086
087    /** When true zero is always included in the visible range. This only has effect if auto-ranging is on. */
088    private BooleanProperty forceZeroInRange = new BooleanPropertyBase(true) {
089        @Override protected void invalidated() {
090            // This will effect layout if we are auto ranging
091            if(isAutoRanging()) requestAxisLayout();
092        }
093
094        @Override
095        public Object getBean() {
096            return NumberAxis.this;
097        }
098
099        @Override
100        public String getName() {
101            return "forceZeroInRange";
102        }
103    };
104    public final boolean isForceZeroInRange() { return forceZeroInRange.getValue(); }
105    public final void setForceZeroInRange(boolean value) { forceZeroInRange.setValue(value); }
106    public final BooleanProperty forceZeroInRangeProperty() { return forceZeroInRange; }
107
108    /**  The value between each major tick mark in data units. This is automatically set if we are auto-ranging. */
109    private DoubleProperty tickUnit = new StyleableDoubleProperty(5) {
110        @Override protected void invalidated() {
111            if(!isAutoRanging()) {
112                invalidateRange();
113                requestAxisLayout();
114            }
115        }
116        
117        @Override
118        public CssMetaData<NumberAxis,Number> getCssMetaData() {
119            return StyleableProperties.TICK_UNIT;
120        }
121
122        @Override
123        public Object getBean() {
124            return NumberAxis.this;
125        }
126
127        @Override
128        public String getName() {
129            return "tickUnit";
130        }
131    };
132    public final double getTickUnit() { return tickUnit.get(); }
133    public final void setTickUnit(double value) { tickUnit.set(value); }
134    public final DoubleProperty tickUnitProperty() { return tickUnit; }
135
136    // -------------- CONSTRUCTORS -------------------------------------------------------------------------------------
137
138    /**
139     * Create a auto-ranging NumberAxis
140     */
141    public NumberAxis() {}
142
143    /**
144     * Create a non-auto-ranging NumberAxis with the given upper bound, lower bound and tick unit
145     *
146     * @param lowerBound The lower bound for this axis, ie min plottable value
147     * @param upperBound The upper bound for this axis, ie max plottable value
148     * @param tickUnit The tick unit, ie space between tickmarks
149     */
150    public NumberAxis(double lowerBound, double upperBound, double tickUnit) {
151        super(lowerBound, upperBound);
152        setTickUnit(tickUnit);
153    }
154
155    /**
156     * Create a non-auto-ranging NumberAxis with the given upper bound, lower bound and tick unit
157     *
158     * @param axisLabel The name to display for this axis
159     * @param lowerBound The lower bound for this axis, ie min plottable value
160     * @param upperBound The upper bound for this axis, ie max plottable value
161     * @param tickUnit The tick unit, ie space between tickmarks
162     */
163    public NumberAxis(String axisLabel, double lowerBound, double upperBound, double tickUnit) {
164        super(lowerBound, upperBound);
165        setTickUnit(tickUnit);
166        setLabel(axisLabel);
167    }
168
169    // -------------- PROTECTED METHODS --------------------------------------------------------------------------------
170
171    /**
172     * Get the string label name for a tick mark with the given value
173     *
174     * @param value The value to format into a tick label string
175     * @return A formatted string for the given value
176     */
177    @Override protected String getTickMarkLabel(Number value) {
178        StringConverter<Number> formatter = getTickLabelFormatter();
179        if (formatter == null) formatter = defaultFormatter;
180        return formatter.toString(value);
181    }
182
183    /**
184     * Called to get the current axis range.
185     *
186     * @return A range object that can be passed to setRange() and calculateTickValues()
187     */
188    @Override protected Object getRange() {
189        return new double[]{
190            getLowerBound(),
191            getUpperBound(),
192            getTickUnit(),
193            getScale(),
194            currentRangeIndexProperty.get()
195        };
196    }
197
198    /**
199     * Called to set the current axis range to the given range. If isAnimating() is true then this method should
200     * animate the range to the new range.
201     *
202     * @param range A range object returned from autoRange()
203     * @param animate If true animate the change in range
204     */
205    @Override protected void setRange(Object range, boolean animate) {
206        final double[] rangeProps = (double[]) range;
207        final double lowerBound = rangeProps[0];
208        final double upperBound = rangeProps[1];
209        final double tickUnit = rangeProps[2];
210        final double scale = rangeProps[3];
211        final double rangeIndex = rangeProps[4];
212        currentRangeIndexProperty.set((int)rangeIndex);
213        final double oldLowerBound = getLowerBound();
214        setLowerBound(lowerBound);
215        setUpperBound(upperBound);
216        setTickUnit(tickUnit);
217        if(animate) {
218            animator.stop(currentAnimationID);
219            currentAnimationID = animator.animate(
220                new KeyFrame(Duration.ZERO,
221                        new KeyValue(currentLowerBound, oldLowerBound),
222                        new KeyValue(scalePropertyImpl(), getScale())
223                ),
224                new KeyFrame(Duration.millis(700),
225                        new KeyValue(currentLowerBound, lowerBound),
226                        new KeyValue(scalePropertyImpl(), scale)
227                )
228            );
229        } else {
230            currentLowerBound.set(lowerBound);
231            setScale(scale);
232        }
233    }
234
235    /**
236     * Calculate a list of all the data values for each tick mark in range
237     *
238     * @param length The length of the axis in display units
239     * @param range A range object returned from autoRange()
240     * @return A list of tick marks that fit along the axis if it was the given length
241     */
242    @Override protected List<Number> calculateTickValues(double length, Object range) {
243        final double[] rangeProps = (double[]) range;
244        final double lowerBound = rangeProps[0];
245        final double upperBound = rangeProps[1];
246        final double tickUnit = rangeProps[2];
247        List<Number> tickValues =  new ArrayList<Number>();
248        if (tickUnit <= 0 || lowerBound == upperBound) {
249            tickValues.add(lowerBound);
250        } else if (getTickUnit() > 0) {
251            for (double major = lowerBound; major <= upperBound; major += tickUnit)  {
252                tickValues.add(major);
253                if(tickValues.size()>2000) {
254                    // This is a ridiculous amount of major tick marks, something has probably gone wrong
255                    System.err.println("Warning we tried to create more than 2000 major tick marks on a NumberAxis. " +
256                            "Lower Bound=" + lowerBound + ", Upper Bound=" + upperBound + ", Tick Unit=" + tickUnit);
257                    break;
258                }
259            }
260        } 
261        return tickValues;
262    }
263
264    /**
265     * Calculate a list of the data values for every minor tick mark
266     *
267     * @return List of data values where to draw minor tick marks
268     */
269    protected List<Number> calculateMinorTickMarks() {
270        final List<Number> minorTickMarks = new ArrayList<Number>();
271        final double lowerBound = getLowerBound();
272        final double upperBound = getUpperBound();
273        final double tickUnit = getTickUnit();
274        final double minorUnit = tickUnit/getMinorTickCount();
275        if (getTickUnit() > 0) {
276            for (double major = lowerBound; major < upperBound; major += tickUnit)  {
277                for (double minor=major+minorUnit; minor < (major+tickUnit); minor += minorUnit) {
278                    minorTickMarks.add(minor);
279                    if(minorTickMarks.size()>10000) {
280                        // This is a ridiculous amount of major tick marks, something has probably gone wrong
281                        System.err.println("Warning we tried to create more than 10000 minor tick marks on a NumberAxis. " +
282                                "Lower Bound=" + getLowerBound() + ", Upper Bound=" + getUpperBound() + ", Tick Unit=" + tickUnit);
283                        break;
284                    }
285                }
286            }
287        }
288        return minorTickMarks;
289    }
290
291    /**
292     * Measure the size of the label for given tick mark value. This uses the font that is set for the tick marks
293     *
294     * @param value tick mark value
295     * @param range range to use during calculations
296     * @return size of tick mark label for given value
297     */
298    @Override protected Dimension2D measureTickMarkSize(Number value, Object range) {
299        final double[] rangeProps = (double[]) range;
300        final double rangeIndex = rangeProps[4];
301        return measureTickMarkSize(value, getTickLabelRotation(), (int)rangeIndex);
302    }
303
304    /**
305     * Measure the size of the label for given tick mark value. This uses the font that is set for the tick marks
306     *
307     * @param value     tick mark value
308     * @param rotation  The text rotation
309     * @param rangeIndex The index of the tick unit range
310     * @return size of tick mark label for given value
311     */
312    private Dimension2D measureTickMarkSize(Number value, double rotation, int rangeIndex) {
313        String labelText;
314        StringConverter<Number> formatter = getTickLabelFormatter();
315        if (formatter == null) formatter = defaultFormatter;
316        if(formatter instanceof DefaultFormatter) {
317            labelText = ((DefaultFormatter)formatter).toString(value, rangeIndex);
318        } else {
319            labelText = formatter.toString(value);
320        }
321        return measureTickMarkLabelSize(labelText, rotation);
322    }
323
324    /**
325     * Called to set the upper and lower bound and anything else that needs to be auto-ranged
326     *
327     * @param minValue The min data value that needs to be plotted on this axis
328     * @param maxValue The max data value that needs to be plotted on this axis
329     * @param length The length of the axis in display coordinates
330     * @param labelSize The approximate average size a label takes along the axis
331     * @return The calculated range
332     */
333    @Override protected Object autoRange(double minValue, double maxValue, double length, double labelSize) {
334        final Side side = getSide();
335        final boolean vertical = Side.LEFT.equals(side) || Side.RIGHT.equals(side);
336        // check if we need to force zero into range
337        if (isForceZeroInRange()) {
338            if (maxValue < 0) {
339                maxValue = 0;
340            } else if (minValue > 0) {
341                minValue = 0;
342            }
343        }
344        final double range = maxValue-minValue;
345        // pad min and max by 2%, checking if the range is zero
346        final double paddedRange = (range==0) ? 2 : Math.abs(range)*1.02;
347        final double padding = (paddedRange - range) / 2;
348        // if min and max are not zero then add padding to them
349        double paddedMin = minValue - padding;
350        double paddedMax = maxValue + padding;
351        // check padding has not pushed min or max over zero line
352        if ((paddedMin < 0 && minValue >= 0) || (paddedMin > 0 && minValue <= 0)) {
353            // padding pushed min above or below zero so clamp to 0
354            paddedMin = 0;
355        }
356        if ((paddedMax < 0 && maxValue >= 0) || (paddedMax > 0 && maxValue <= 0)) {
357            // padding pushed min above or below zero so clamp to 0
358            paddedMax = 0;
359        }
360        // calculate the number of tick-marks we can fit in the given length
361        int numOfTickMarks = (int)Math.floor(Math.abs(length)/labelSize);
362        // can never have less than 2 tick marks one for each end
363        numOfTickMarks = Math.max(numOfTickMarks, 2);
364        // calculate tick unit for the number of ticks can have in the given data range
365        double tickUnit = paddedRange/(double)numOfTickMarks;
366        // search for the best tick unit that fits
367        double tickUnitRounded = 0;
368        double minRounded = 0;
369        double maxRounded = 0;
370        int count = 0;
371        double reqLength = Double.MAX_VALUE;
372        int rangeIndex = 10;
373        // loop till we find a set of ticks that fit length and result in a total of less than 20 tick marks
374        while (reqLength > length || count > 20) {
375            // find a user friendly match from our default tick units to match calculated tick unit
376            for (int i=0; i<TICK_UNIT_DEFAULTS.length; i++) {
377                double tickUnitDefault = TICK_UNIT_DEFAULTS[i];
378                if (tickUnitDefault > tickUnit) {
379                    tickUnitRounded = tickUnitDefault;
380                    rangeIndex = i;
381                    break;
382                }
383            }
384            // move min and max to nearest tick mark
385            minRounded = Math.floor(paddedMin / tickUnitRounded) * tickUnitRounded;
386            maxRounded = Math.ceil(paddedMax / tickUnitRounded) * tickUnitRounded;
387            // calculate the required length to display the chosen tick marks for real, this will handle if there are
388            // huge numbers involved etc or special formatting of the tick mark label text
389            double maxReqTickGap = 0;
390            double last = 0;
391            count = 0;
392            for (double major = minRounded; major <= maxRounded; major += tickUnitRounded, count ++)  {
393                double size = (vertical) ? measureTickMarkSize((Double)major, getTickLabelRotation(), rangeIndex).getHeight() :
394                                            measureTickMarkSize((Double)major, getTickLabelRotation(), rangeIndex).getWidth();
395                if (major == minRounded) { // first
396                    last = size/2;
397                } else {
398                    maxReqTickGap = Math.max(maxReqTickGap, last + 6 + (size/2) );
399                }
400            }
401            reqLength = (count-1) * maxReqTickGap;
402            tickUnit = tickUnitRounded;
403            // check if we already found max tick unit
404            if (tickUnitRounded == TICK_UNIT_DEFAULTS[TICK_UNIT_DEFAULTS.length-1]) {
405                // nothing we can do so just have to use this
406                break;
407            }
408        }
409        // calculate new scale
410        final double newScale = calculateNewScale(length, minRounded, maxRounded);
411        // return new range
412        return new double[]{minRounded, maxRounded, tickUnitRounded, newScale, rangeIndex};
413    }
414
415    // -------------- STYLESHEET HANDLING ------------------------------------------------------------------------------
416
417     /** @treatAsPrivate implementation detail */
418    private static class StyleableProperties {
419        private static final CssMetaData<NumberAxis,Number> TICK_UNIT =
420            new CssMetaData<NumberAxis,Number>("-fx-tick-unit",
421                SizeConverter.getInstance(), 5.0) {
422
423            @Override
424            public boolean isSettable(NumberAxis n) {
425                return n.tickUnit == null || !n.tickUnit.isBound();
426            }
427
428            @Override
429            public StyleableProperty<Number> getStyleableProperty(NumberAxis n) {
430                return (StyleableProperty<Number>)n.tickUnitProperty();
431            }
432        };
433
434        private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
435        static {
436           final List<CssMetaData<? extends Styleable, ?>> styleables = 
437               new ArrayList<CssMetaData<? extends Styleable, ?>>(ValueAxis.getClassCssMetaData());
438           styleables.add(TICK_UNIT);
439           STYLEABLES = Collections.unmodifiableList(styleables);
440        }
441    }
442
443    /**
444     * @return The CssMetaData associated with this class, which may include the
445     * CssMetaData of its super classes.
446     */
447    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
448        return StyleableProperties.STYLEABLES;
449    }
450
451    /**
452     * {@inheritDoc}
453     */
454    @Override
455    public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
456        return getClassCssMetaData();
457    }
458
459    // -------------- INNER CLASSES ------------------------------------------------------------------------------------
460
461    /**
462     * Default number formatter for NumberAxis, this stays in sync with auto-ranging and formats values appropriately.
463     * You can wrap this formatter to add prefixes or suffixes;
464     */
465    public static class DefaultFormatter extends StringConverter<Number> {
466        private DecimalFormat formatter;
467        private String prefix = null;
468        private String suffix = null;
469
470        /** used internally */
471        private DefaultFormatter() {}
472
473        /**
474         * Construct a DefaultFormatter for the given NumberAxis
475         *
476         * @param axis The axis to format tick marks for
477         */
478        public DefaultFormatter(final NumberAxis axis) {
479            formatter = getFormatter(axis.isAutoRanging()? axis.currentRangeIndexProperty.get() : -1);
480            final ChangeListener axisListener = new ChangeListener() {
481                @Override public void changed(ObservableValue observable, Object oldValue, Object newValue) {
482                    formatter = getFormatter(axis.isAutoRanging()? axis.currentRangeIndexProperty.get() : -1);
483                }
484            };
485            axis.currentRangeIndexProperty.addListener(axisListener);
486            axis.autoRangingProperty().addListener(axisListener);
487        }
488
489        /**
490         * Construct a DefaultFormatter for the given NumberAxis with a prefix and/or suffix.
491         *
492         * @param axis The axis to format tick marks for
493         * @param prefix The prefix to append to the start of formatted number, can be null if not needed
494         * @param suffix The suffix to append to the end of formatted number, can be null if not needed
495         */
496        public DefaultFormatter(NumberAxis axis, String prefix, String suffix) {
497            this(axis);
498            this.prefix = prefix;
499            this.suffix = suffix;
500        }
501
502        private static DecimalFormat getFormatter(int rangeIndex) {
503            if (rangeIndex < 0) {
504                return new DecimalFormat();
505            } else if(rangeIndex >= TICK_UNIT_FORMATTER_DEFAULTS.length) {
506                return new DecimalFormat(TICK_UNIT_FORMATTER_DEFAULTS[TICK_UNIT_FORMATTER_DEFAULTS.length-1]);
507            } else {
508                return new DecimalFormat(TICK_UNIT_FORMATTER_DEFAULTS[rangeIndex]);
509            }
510        }
511
512        /**
513        * Converts the object provided into its string form.
514        * Format of the returned string is defined by this converter.
515        * @return a string representation of the object passed in.
516        * @see StringConverter#toString
517        */
518        @Override public String toString(Number object) {
519            return toString(object, formatter);
520        }
521
522        private String toString(Number object, int rangeIndex) {
523            return toString(object, getFormatter(rangeIndex));
524        }
525
526        private String toString(Number object, DecimalFormat formatter) {
527            if (prefix != null && suffix != null) {
528                return prefix + formatter.format(object) + suffix;
529            } else if (prefix != null) {
530                return prefix + formatter.format(object);
531            } else if (suffix != null) {
532                return formatter.format(object) + suffix;
533            } else {
534                return formatter.format(object);
535            }
536        }
537
538        /**
539        * Converts the string provided into a Number defined by the this converter.
540        * Format of the string and type of the resulting object is defined by this converter.
541        * @return a Number representation of the string passed in.
542        * @see StringConverter#toString
543        */
544        @Override public Number fromString(String string) {
545            try {
546                int prefixLength = (prefix == null)? 0: prefix.length();
547                int suffixLength = (suffix == null)? 0: suffix.length();
548                return formatter.parse(string.substring(prefixLength, string.length() - suffixLength));
549            } catch (ParseException e) {
550                return null;
551            }
552        }
553    }
554
555}
556
557/*
558  // Code to generate tick unit defaults
559
560  public static void main(String[] args) {
561        List<BigDecimal> values = new ArrayList<BigDecimal>();
562        List<String> formats = new ArrayList<String>();
563        for(int power=-10; power <= 12; power ++) {
564            BigDecimal val = new BigDecimal(10);
565            val = val.pow(power, MathContext.DECIMAL32);
566            BigDecimal val2 = val.multiply(new BigDecimal(2.5d));
567            BigDecimal val5 = val.multiply(new BigDecimal(5d));
568            values.add(val);
569            values.add(val2);
570            values.add(val5);
571            System.out.print("["+power+"]  ");
572            System.out.print(
573                    val.doubleValue() + "d, " +
574                            val2.doubleValue() + "d, " +
575                            val5.doubleValue() + "d, "
576            );
577            DecimalFormat df = null;
578            DecimalFormat dfTwoHalf = null;
579            if (power < 0) {
580                String nf = "0.";
581                for (int i=0; i<Math.abs(power); i++) nf = nf+"0";
582                System.out.print("    ---   nf = " + nf);
583                String nf2 = "0.";
584                for (int i=0; i<=Math.abs(power); i++) nf2 = nf2+"0";
585                System.out.print("    ---   nf2 = " + nf2);
586                df = new DecimalFormat(nf);
587                dfTwoHalf = new DecimalFormat(nf2);
588                formats.add(nf);
589                formats.add(nf2);
590                formats.add(nf);
591            } else if (power == 0) {
592                df = new DecimalFormat("0");
593                dfTwoHalf = new DecimalFormat("0.0");
594                formats.add("0");
595                formats.add("0.0");
596                formats.add("0");
597            } else {
598                String nf = "0";
599                for (int i=0; i<Math.abs(power); i++) {
600                    if((i % 3) == 2) {
601                        nf = "#," + nf;
602                    } else {
603                        nf = "#" + nf;
604                    }
605                }
606                System.out.print("    ---   nf = " + nf);
607                formats.add(nf);
608                formats.add(nf);
609                formats.add(nf);
610                dfTwoHalf = df = new DecimalFormat(nf);
611            }
612            System.out.println("        ---      "+
613                    df.format(val.doubleValue())+", "+
614                    dfTwoHalf.format(val2.doubleValue())+", "+
615                    df.format(val5.doubleValue())+", "
616            );
617        }
618        System.out.print("    private static final double[] TICK_UNIT_DEFAULTS = { ");
619        for(BigDecimal val: values) System.out.print(val.doubleValue()+", ");
620        System.out.println(" };");
621        System.out.print("    private static final String[] TICK_UNIT_FORMATTER_DEFAULTS = { ");
622        for(String format: formats) System.out.print("\""+format+"\", ");
623        System.out.println(" };");
624    }
625*/