changeset 9397:b45ddd59073b 9-b94

8136535: JavaFX NumberAxis AutoRange Infinite Loop Reviewed-by: jgiles
author vadim
date Mon, 23 Nov 2015 13:48:43 +0300
parents 172c329b1a0c
children 66c8988e78d7 c710a363647f
files modules/controls/src/main/java/javafx/scene/chart/NumberAxis.java modules/controls/src/test/java/javafx/scene/chart/NumberAxisShim.java modules/controls/src/test/java/test/javafx/scene/chart/NumberAxisTest.java
diffstat 3 files changed, 99 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- a/modules/controls/src/main/java/javafx/scene/chart/NumberAxis.java	Fri Nov 20 15:13:05 2015 -0800
+++ b/modules/controls/src/main/java/javafx/scene/chart/NumberAxis.java	Mon Nov 23 13:48:43 2015 +0300
@@ -247,8 +247,9 @@
             } else {
                 if (lowerBound + tickUnit < upperBound) {
                     // If tickUnit is integer, start with the nearest integer
-                    double first = Math.rint(tickUnit) == tickUnit ? Math.ceil(lowerBound) : lowerBound + tickUnit;
-                    for (double major = first; major < upperBound; major += tickUnit) {
+                    double major = Math.rint(tickUnit) == tickUnit ? Math.ceil(lowerBound) : lowerBound + tickUnit;
+                    int count = (int)Math.ceil((upperBound - major)/tickUnit);
+                    for (int i = 0; major < upperBound && i < count; major += tickUnit, i++) {
                         if (!tickValues.contains(major)) {
                             tickValues.add(major);
                         }
@@ -280,16 +281,21 @@
             }
             final boolean tickUnitIsInteger = Math.rint(tickUnit) == tickUnit;
             if (tickUnitIsInteger) {
-                for (double minor = Math.floor(lowerBound) + minorUnit; minor < Math.ceil(lowerBound); minor += minorUnit) {
+                double minor = Math.floor(lowerBound) + minorUnit;
+                int count = (int)Math.ceil((Math.ceil(lowerBound) - minor)/minorUnit);
+                for (int i = 0; minor < Math.ceil(lowerBound) && i < count; minor += minorUnit, i++) {
                     if (minor > lowerBound) {
                         minorTickMarks.add(minor);
                     }
                 }
             }
             double major = tickUnitIsInteger ? Math.ceil(lowerBound) : lowerBound;
-            for (; major < upperBound; major += tickUnit)  {
+            int count = (int)Math.ceil((upperBound - major)/tickUnit);
+            for (int i = 0; major < upperBound && i < count; major += tickUnit, i++)  {
                 final double next = Math.min(major + tickUnit, upperBound);
-                for (double minor = major + minorUnit; minor < next; minor += minorUnit) {
+                double minor = major + minorUnit;
+                int minorCount = (int)Math.ceil((next - minor)/minorUnit);
+                for (int j = 0; minor < next && j < minorCount; minor += minorUnit, j++) {
                     minorTickMarks.add(minor);
                 }
             }
@@ -349,9 +355,21 @@
                 minValue = 0;
             }
         }
-        final double range = maxValue-minValue;
+        // calculate the number of tick-marks we can fit in the given length
+        int numOfTickMarks = (int)Math.floor(length/labelSize);
+        // can never have less than 2 tick marks one for each end
+        numOfTickMarks = Math.max(numOfTickMarks, 2);
+        int minorTickCount = Math.max(getMinorTickCount(), 1);
+
+        double range = maxValue-minValue;
+
+        if (range != 0 && range/(numOfTickMarks*minorTickCount) <= Math.ulp(minValue)) {
+            range = 0;
+        }
         // pad min and max by 2%, checking if the range is zero
-        final double paddedRange = (range==0) ? 2 : Math.abs(range)*1.02;
+        final double paddedRange = (range == 0)
+                ? minValue == 0 ? 2 : Math.abs(minValue)*0.02
+                : Math.abs(range)*1.02;
         final double padding = (paddedRange - range) / 2;
         // if min and max are not zero then add padding to them
         double paddedMin = minValue - padding;
@@ -365,10 +383,6 @@
             // padding pushed min above or below zero so clamp to 0
             paddedMax = 0;
         }
-        // calculate the number of tick-marks we can fit in the given length
-        int numOfTickMarks = (int)Math.floor(length/labelSize);
-        // can never have less than 2 tick marks one for each end
-        numOfTickMarks = Math.max(numOfTickMarks, 2);
         // calculate tick unit for the number of ticks can have in the given data range
         double tickUnit = paddedRange/(double)numOfTickMarks;
         // search for the best tick unit that fits
@@ -412,11 +426,12 @@
             // huge numbers involved etc or special formatting of the tick mark label text
             double maxReqTickGap = 0;
             double last = 0;
-            count = 0;
-            for (double major = minRounded; major <= maxRounded; major += tickUnitRounded, count ++)  {
-                double size = side.isVertical() ? measureTickMarkSize(major, getTickLabelRotation(), formatter).getHeight() :
-                                            measureTickMarkSize(major, getTickLabelRotation(), formatter).getWidth();
-                if (major == minRounded) { // first
+            count = (int)Math.ceil((maxRounded - minRounded)/tickUnitRounded);
+            double major = minRounded;
+            for (int i = 0; major <= maxRounded && i < count; major += tickUnitRounded, i++)  {
+                Dimension2D markSize = measureTickMarkSize(major, getTickLabelRotation(), formatter);
+                double size = side.isVertical() ? markSize.getHeight() : markSize.getWidth();
+                if (i == 0) { // first
                     last = size/2;
                 } else {
                     maxReqTickGap = Math.max(maxReqTickGap, last + 6 + (size/2) );
--- a/modules/controls/src/test/java/javafx/scene/chart/NumberAxisShim.java	Fri Nov 20 15:13:05 2015 -0800
+++ b/modules/controls/src/test/java/javafx/scene/chart/NumberAxisShim.java	Mon Nov 23 13:48:43 2015 +0300
@@ -39,5 +39,12 @@
     public static Object getRange(NumberAxis na) {
         return na.getRange();
     }
-    
+
+    public static void setRange(NumberAxis na, Object range, boolean animate) {
+        na.setRange(range, animate);
+    }
+
+    public static Object autoRange(NumberAxis na, double minValue, double maxValue, double length, double labelSize) {
+        return na.autoRange(minValue, maxValue, length, labelSize);
+    }
 }
--- a/modules/controls/src/test/java/test/javafx/scene/chart/NumberAxisTest.java	Fri Nov 20 15:13:05 2015 -0800
+++ b/modules/controls/src/test/java/test/javafx/scene/chart/NumberAxisTest.java	Mon Nov 23 13:48:43 2015 +0300
@@ -40,6 +40,7 @@
 
 import java.util.Arrays;
 import java.util.List;
+import javafx.geometry.Side;
 import javafx.scene.chart.NumberAxis;
 import javafx.scene.chart.NumberAxisShim;
 
@@ -252,4 +253,63 @@
         assertArrayEquals(new double[] {8.45, 8.55, 8.65}, asDoubleArray, 1e-10);
     }
 
+    @Test(timeout = 1000)
+    public void testCloseValues() {
+        axis.setForceZeroInRange(false);
+        axis.setSide(Side.LEFT);
+        double minValue = 1.0;
+        double maxValue = minValue + Math.ulp(minValue);
+        NumberAxisShim.autoRange(axis, minValue, maxValue, 500, 50);
+    }
+
+    @Test(timeout = 1000)
+    public void testCloseValuesMinorTicks() {
+        axis.setForceZeroInRange(false);
+        axis.setSide(Side.LEFT);
+        double minValue = 1.0;
+        double maxValue = minValue + 11*Math.ulp(minValue);
+        Object range = NumberAxisShim.autoRange(axis, minValue, maxValue, 500, 50);
+        NumberAxisShim.setRange(axis, range, false);
+        NumberAxisShim.calculateMinorTickMarks(axis);
+    }
+
+    @Test(timeout = 1000)
+    public void testEqualLargeValues() {
+        axis.setForceZeroInRange(false);
+        axis.setSide(Side.LEFT);
+        double minValue = Math.pow(2, 52); // ulp == 1.0
+        double maxValue = minValue;
+        NumberAxisShim.autoRange(axis, minValue, maxValue, 500, 50);
+    }
+
+    @Test(timeout = 1000)
+    public void testCloseValuesNoAutorange() {
+        axis.setForceZeroInRange(false);
+        axis.setSide(Side.LEFT);
+        axis.setAutoRanging(false);
+        double minValue = 1.0;
+        double maxValue = minValue + Math.ulp(minValue);
+        axis.setLowerBound(minValue);
+        axis.setUpperBound(maxValue);
+        // minValue + tickUnit == minValue
+        axis.setTickUnit(0.5*Math.ulp(minValue));
+        Object range = NumberAxisShim.getRange(axis);        
+        NumberAxisShim.calculateTickValues(axis, 500, range);
+        NumberAxisShim.calculateMinorTickMarks(axis);
+    }
+
+    @Test(timeout = 1000)
+    public void testCloseValuesMinorTicksNoAutoRange() {
+        axis.setForceZeroInRange(false);
+        axis.setSide(Side.LEFT);
+        axis.setAutoRanging(false);
+        double minValue = 1.0;
+        double maxValue = minValue + Math.ulp(minValue);
+        axis.setLowerBound(minValue);
+        axis.setUpperBound(maxValue);
+        axis.setTickUnit(Math.ulp(minValue));
+        Object range = NumberAxisShim.getRange(axis);
+        NumberAxisShim.calculateTickValues(axis, 500, range);
+        NumberAxisShim.calculateMinorTickMarks(axis);
+    }
 }