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
Open
@Marko19907

Description

@Marko19907

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0