JavaFX NumberAxis AutoRange 无限循环

JavaFX NumberAxis AutoRange Infinite Loop

我有一个 LineChart,其中 Y 轴设置为自动量程。 JavaFx 线程有时会因 NumberAxis.autoRange() 陷入无限循环而挂起。由工作线程生成的新数据,然后每隔几秒添加到图表(在 JFX 线程上)。无限循环发生在这段代码中(取自NumberAxis.autoRange()):

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
      last = size/2;
   } else {
      maxReqTickGap = Math.max(maxReqTickGap, last + 6 + (size/2) );
   }
}

通过调试,我发现 if (major == minRoundeed) 条件每次都是 true。因此,major 变量不能更新。

我没有带有局部变量调试信息的 NumberAxis class 的编译版本,所以我看不到局部变量是什么。构建 JavaFX 运行时 classes 似乎需要大量工作,但可能是下一步。

我无法可靠地重现此问题,因此无法提供 Minimal, Complete, and Verifiable example。我没有在 Oracle 或 OpenJDK 错误数据库中看到任何问题记录。

JDK版本:8u60

编辑:

我向 Oracle 报告了这个错误,目前正在等待他们接受。

问题

意味着循环将依赖于双精度值。因此,如果您尝试为 minValue 和 maxValue 取如此小的双精度值,它将失败。

有没有错误?

对我来说这不像是一个错误。你可以问问自己,如果你真的想在你的轴上显示这么大的分数,或者你能更好地放大它们吗?与 0.00000000000000000000000000000000000000015 或 1.5E-33?

而且整个 Java API 中还有更多的事情也会发生这种情况,因为这是一个简单的数字溢出。

一个简单的例子

这将证明,如果值太小,它将无限循环。

import javafx.geometry.Side;


public class AutoRangeTester {

  /**
   * @param args the command line arguments
   */
  public static void main(String[] args) {
    AutoRangeTester art = new AutoRangeTester();
    art.autoRange(Double.MIN_VALUE, Double.MIN_VALUE + 0.000000000000000000000000000000001, 100, 50);
  }

  /**
   * Called to set the upper and lower bound and anything else that needs to be
   * auto-ranged
   *
   * @param minValue  The min data value that needs to be plotted on this axis
   * @param maxValue  The max data value that needs to be plotted on this axis
   * @param length    The length of the axis in display coordinates
   * @param labelSize The approximate average size a label takes along the axis
   *
   * @return The calculated range
   */
  public Object autoRange(double minValue, double maxValue, double length,
                          double labelSize) {
    final Side side = Side.LEFT;
    // check if we need to force zero into range
    if (true) {
      if (maxValue < 0) {
        maxValue = 0;
      } else if (minValue > 0) {
        minValue = 0;
      }
    }
    final double range = maxValue - minValue;
    // 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 padding = (paddedRange - range) / 2;
    // if min and max are not zero then add padding to them
    double paddedMin = minValue - padding;
    double paddedMax = maxValue + padding;
    // check padding has not pushed min or max over zero line
    if ((paddedMin < 0 && minValue >= 0) || (paddedMin > 0 && minValue <= 0)) {
      // padding pushed min above or below zero so clamp to 0
      paddedMin = 0;
    }
    if ((paddedMax < 0 && maxValue >= 0) || (paddedMax > 0 && maxValue <= 0)) {
      // 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
    double tickUnitRounded = 0;
    double minRounded = 0;
    double maxRounded = 0;
    int count = 0;
    double reqLength = Double.MAX_VALUE;

    // loop till we find a set of ticks that fit length and result in a total of less than 20 tick marks
    while (reqLength > length || count > 20) {
      int exp = (int) Math.floor(Math.log10(tickUnit));
      final double mant = tickUnit / Math.pow(10, exp);
      double ratio = mant;
      if (mant > 5d) {
        exp++;
        ratio = 1;
      } else if (mant > 1d) {
        ratio = mant > 2.5 ? 5 : 2.5;
      }

      tickUnitRounded = ratio * Math.pow(10, exp);

      minRounded = Math.floor(paddedMin / tickUnitRounded) * tickUnitRounded;
      maxRounded = Math.ceil(paddedMax / tickUnitRounded) * tickUnitRounded;

      count = 0;
      for (double major = minRounded; major <= maxRounded; major
              += tickUnitRounded, count++) {
        System.out.println("minRounded: " + minRounded);
        System.out.println("maxRounded: " + maxRounded);
        System.out.println("major: " + major);
        System.out.println("tickUnitRounded: " + tickUnitRounded);
        System.out.println("-------------------------------------");
      }

    }
    return null;
  }

}

更新

错误报告:https://bugs.openjdk.java.net/browse/JDK-8136535 计划对版本 9 进行修复。