8000 UI Freeze When Chart Window is Too Narrow (willLabelsFitInTickSpaceHint()) · Issue #886 · knowm/XChart · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

UI Freeze When Chart Window is Too Narrow (willLabelsFitInTickSpaceHint()) #886

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Marko19907 opened this issue Feb 4, 2025 · 1 comment

Comments

@Marko19907
Copy link

Describe the bug

Hi!
We've encountered an issue where XChart can completely freeze the UI thread when a plot window is too narrow. It looks like willLabelsFitInTickSpaceHint() in AxisTickCalculator_ gets stuck, causing the AWT Event Dispatch Thread (EDT) to never return.

We're not entirely sure what's happening, but when the plot window starts too small, the UI becomes unresponsive and never recovers. This is a problem for us since we use two threads, one fetching data while the other spawns the chart that's too narrow at the start.

The issue is present on both Java 8 and 17, with the latest version of XChart 3.8.8

We've taken a thread dump to see where the UI thread is stuck:

"AWT-EventQueue-0" #34 prio=6 os_prio=0 cpu=15281.25ms elapsed=265.88s tid=0x0000016bcc920e20 nid=0x79c4 runnable  [0x000000371f7fc000]
   java.lang.Thread.State: RUNNABLE
        at java.awt.geom.Path2D$Float.append(java.desktop@17.0.10/Path2D.java:786)
        at java.awt.geom.Path2D.append(java.desktop@17.0.10/Path2D.java:1975)
        at java.awt.font.TextLine.getOutline(java.desktop@17.0.10/TextLine.java:874)
        at java.awt.font.TextLayout.getOutline(java.desktop@17.0.10/TextLayout.java:2663)
        at org.knowm.xchart.internal.chartpart.AxisTickCalculator_.willLabelsFitInTickSpaceHint(AxisTickCalculator_.java:140)
        at org.knowm.xchart.internal.chartpart.AxisTickCalculator_.calculateForEquallySpacedAxisValues(AxisTickCalculator_.java:408)
        at org.knowm.xchart.internal.chartpart.AxisTickCalculator_.calculate(AxisTickCalculator_.java:205)
        at org.knowm.xchart.internal.chartpart.AxisTickCalculator_Number.<init>(AxisTickCalculator_Number.java:47)
        at org.knowm.xchart.internal.chartpart.Axis.getAxisTickCalculatorForX(Axis.java:547)
        at org.knowm.xchart.internal.chartpart.Axis.getAxisTickCalculator(Axis.java:390)
        at org.knowm.xchart.internal.chartpart.Axis.getXAxisHeightHint(Axis.java:293)
        at org.knowm.xchart.internal.chartpart.Axis.preparePaint(Axis.java:161)
        at org.knowm.xchart.internal.chartpart.AxisPair.paint(AxisPair.java:120)
        at org.knowm.xchart.XYChart.paint(XYChart.java:416)
        at org.knowm.xchart.XChartPanel.paintComponent(XChartPanel.java:166)
        at javax.swing.JComponent.paint(java.desktop@17.0.10/JComponent.java:1119)
        at javax.swing.JComponent.paintChildren(java.desktop@17.0.10/JComponent.java:952)
        - locked <0x000000040207c240> (a java.awt.Component$AWTTreeLock)
        at javax.swing.JComponent.paint(java.desktop@17.0.10/JComponent.java:1128)
        at javax.swing.JComponent.paintChildren(java.desktop@17.0.10/JComponent.java:952)
        - locked <0x000000040207c240> (a java.awt.Component$AWTTreeLock)
        at javax.swing.JComponent.paint(java.desktop@17.0.10/JComponent.java:1128)
        at javax.swing.JLayeredPane.paint(java.desktop@17.0.10/JLayeredPane.java:586)
        at javax.swing.JComponent.paintChildren(java.desktop@17.0.10/JComponent.java:952)
        - locked <0x000000040207c240> (a java.awt.Component$AWTTreeLock)
        at javax.swing.JComponent.paintToOffscreen(java.desktop@17.0.10/JComponent.java:5318)
        at javax.swing.RepaintManager$PaintManager.paintDoubleBufferedFPScales(java.desktop@17.0.10/RepaintManager.java:1721)
        at javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(java.desktop@17.0.10/RepaintManager.java:1630)
        at javax.swing.RepaintManager$PaintManager.paint(java.desktop@17.0.10/RepaintManager.java:1570)
        at javax.swing.RepaintManager.paint(java.desktop@17.0.10/RepaintManager.java:1337)
        at javax.swing.JComponent.paint(java.desktop@17.0.10/JComponent.java:1105)
        at java.awt.GraphicsCallback$PaintCallback.run(java.desktop@17.0.10/GraphicsCallback.java:39)
        at sun.awt.SunGraphicsCallback.runOneComponent(java.desktop@17.0.10/SunGraphicsCallback.java:75)
        at sun.awt.SunGraphicsCallback.runComponents(java.desktop@17.0.10/SunGraphicsCallback.java:112)
        at java.awt.Container.paint(java.desktop@17.0.10/Container.java:2005)
        at java.awt.Window.paint(java.desktop@17.0.10/Window.java:3959)
        at javax.swing.RepaintManager$4.run(java.desktop@17.0.10/RepaintManager.java:890)
        at javax.swing.RepaintManager$4.run(java.desktop@17.0.10/RepaintManager.java:862)
        at java.security.AccessController.executePrivileged(java.base@17.0.10/AccessController.java:776)
        at java.security.AccessController.doPrivileged(java.base@17.0.10/AccessController.java:399)
        at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(java.base@17.0.10/ProtectionDomain.java:86)
        at javax.swing.RepaintManager.paintDirtyRegions(java.desktop@17.0.10/RepaintManager.java:862)
        at javax.swing.RepaintManager.paintDirtyRegions(java.desktop@17.0.10/RepaintManager.java:835)
        at javax.swing.RepaintManager.prePaintDirtyRegions(java.desktop@17.0.10/RepaintManager.java:784)
        at javax.swing.RepaintManager$ProcessingRunnable.run(java.desktop@17.0.10/RepaintManager.java:1898)
        at java.awt.event.InvocationEvent.dispatch(java.desktop@17.0.10/InvocationEvent.java:318)
        at java.awt.EventQueue.dispatchEventImpl(java.desktop@17.0.10/EventQueue.java:773)
        at java.awt.EventQueue$4.run(java.desktop@17.0.10/EventQueue.java:720)
        at java.awt.EventQueue$4.run(java.desktop@17.0.10/EventQueue.java:714)
        at java.security.AccessController.executePrivileged(java.base@17.0.10/AccessController.java:776)
        at java.security.AccessController.doPrivileged(java.base@17.0.10/AccessController.java:399)
        at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(java.base@17.0.10/ProtectionDomain.java:86)
        at java.awt.EventQueue.dispatchEvent(java.desktop@17.0.10/EventQueue.java:742)
        at java.awt.EventDispatchThread.pumpOneEventForFilters(java.desktop@17.0.10/EventDispatchThread.java:203)
        at java.awt.EventDispatchThread.pumpEventsForFilter(java.desktop@17.0.10/EventDispatchThread.java:124)
        at java.awt.EventDispatchThread.pumpEventsForHierarchy(java.desktop@17.0.10/EventDispatchThread.java:113)
        at java.awt.EventDispatchThread.pumpEvents(java.desktop@17.0.10/EventDispatchThread.java:109)
        at java.awt.EventDispatchThread.pumpEvents(java.desktop@17.0.10/EventDispatchThread.java:101)
        at java.awt.EventDispatchThread.run(java.desktop@17.0.10/EventDispatchThread.java:90)

   Locked ownable synchronizers:
        - None

To Reproduce

This happens even in the standard XChart example when the window is too small.

public class TestForIssue834 {

    public static void main(String[] args) throws Exception {
        double phase = 0;
        double[][] initdata = getSineData(phase);

        // Create Chart
        final XYChart chart = QuickChart.getChart("XChart Real-time Demo", 
                                                  "Radians", "Sine",
                                                  "sine", initdata[0], initdata[1]);

        // Show it in a small window
        final SwingWrapper<XYChart> sw = new SwingWrapper<>(chart);
        JFrame frame = sw.displayChart();
        frame.setSize(200, 150); // deliberately small

        while (true) {
            phase += 2 * Math.PI * 2 / 20.0;
            Thread.sleep(10);
            final double[][] data = getSineData(phase);

            javax.swing.SwingUtilities.invokeLater(() -> {
                chart.updateXYSeries("sine", data[0], data[1], null);
                sw.repaintChart();
            });
        }
    }

    private static double[][] getSineData(double phase) {
        double[] xData = new double[100];
        double[] yData = new double[100];
        for (int i = 0; i < xData.length; i++) {
            double radians = phase + (2 * Math.PI / xData.length * i);
            xData[i] = radians;
            yData[i] = Math.sin(radians);
        }
        return new double[][] { xData, yData };
    }
}

Screenshots

Here's a very rough video of the issue, note that it doesn't always take this long to occur.

Recording.2025-02-04.103113.mp4

This leads to high memory usage over time. Here’s Java 8 vs. 17:

Image

Image

Expected behavior

The UI thread should not block indefinitely. If the tick labels can't fit either:

  • Have a bailout condition.
  • Use an approximation instead of re-running expensive text layout calculations.
@hypgfrch
Copy link
hypgfrch commented Feb 8, 2025

Comment

Reporting the same. Making the window enough small causes the app to freeze.
This is the provided real time demo. No changes made.

Screenshot

Image

Code

import org.knowm.xchart.QuickChart;
import org.knowm.xchart.SwingWrapper;
import org.knowm.xchart.XYChart;

public class Demo {

    public static void main(String[] args) throws Exception {

        double phase = 0;
        double[][] initdata = getSineData(phase);

        // Create Chart
        final XYChart chart = QuickChart.getChart("Simple XChart Real-time Demo", "Radians", "Sine", "sine", initdata[0], initdata[1]);

        // Show it
        final SwingWrapper<XYChart> sw = new SwingWrapper<XYChart>(chart);
        sw.displayChart();

        while (true) {

            phase += 2 * Math.PI * 2 / 20.0;

            Thread.sleep(100);

            final double[][] data = getSineData(phase);

            javax.swing.SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {

                    chart.updateXYSeries("sine", data[0], data[1], null);
                    sw.repaintChart();
                }
            });
        }

    }

    private static double[][] getSineData(double phase) {

        double[] xData = new double[100];
        double[] yData = new double[100];
        for (int i = 0; i < xData.length; i++) {
            double radians = phase + (2 * Math.PI / xData.length * i);
            xData[i] = radians;
            yData[i] = Math.sin(radians);
        }
        return new double[][]{xData, yData};
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants
0