From 23fc3674c9cd47626435b4584506b1dd5444d963 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Wed, 12 Mar 2025 18:35:56 +0100 Subject: [PATCH] LineChartPanel: - support "synchron" charts - reworked to make code easier to understand/maintain - added some JavaBean properties to make it configurable in JFormDesigner - fixes some bugs removed `FlatAnimatorTest.LineChartPanel` and replaced it with `LineChartPanel` --- .../testing/FlatAnimatedBorderTest.java | 19 +- .../testing/FlatAnimatedBorderTest.jfd | 10 +- .../flatlaf/testing/FlatAnimatedIconTest.java | 20 +- .../flatlaf/testing/FlatAnimatedIconTest.jfd | 10 +- .../flatlaf/testing/FlatAnimatorTest.java | 425 +----------------- .../flatlaf/testing/FlatAnimatorTest.jfd | 66 +-- .../flatlaf/testing/LineChartPanel.java | 364 +++++++++++---- .../flatlaf/testing/LineChartPanel.jfd | 10 +- 8 files changed, 308 insertions(+), 616 deletions(-) diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java index 14f94ea7..46e17f96 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java @@ -25,6 +25,7 @@ import java.awt.Insets; import java.awt.geom.Rectangle2D; import javax.swing.*; import javax.swing.border.AbstractBorder; +import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.ui.FlatUIUtils; import com.formdev.flatlaf.util.AnimatedBorder; import com.formdev.flatlaf.util.ColorFunctions; @@ -92,7 +93,7 @@ public class FlatAnimatedBorderTest private void initComponents() { // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents label3 = new JLabel(); - lineChartPanel = new FlatAnimatorTest.LineChartPanel(); + lineChartPanel = new LineChartPanel(); fade1TextField = new JTextField(); fade1ChartColor = new FlatAnimatorTest.JChartColor(); fade2TextField = new JTextField(); @@ -118,7 +119,7 @@ public class FlatAnimatedBorderTest // columns "[fill]" + "[fill]para" + - "[fill]", + "[grow,fill]", // rows "[]" + "[]" + @@ -136,7 +137,7 @@ public class FlatAnimatedBorderTest //---- label3 ---- label3.setText("Fade:"); add(label3, "cell 0 0"); - add(lineChartPanel, "cell 2 0 1 12"); + add(lineChartPanel, "cell 2 0 1 12,growy"); add(fade1TextField, "cell 0 1"); add(fade1ChartColor, "cell 1 1"); add(fade2TextField, "cell 0 2"); @@ -151,12 +152,12 @@ public class FlatAnimatedBorderTest add(material2ChartColor, "cell 1 5"); //---- material3TextField ---- - material3TextField.putClientProperty("FlatLaf.styleClass", "large"); + material3TextField.putClientProperty(FlatClientProperties.STYLE_CLASS, "large"); add(material3TextField, "cell 0 6"); add(material3ChartColor, "cell 1 6"); //---- material4TextField ---- - material4TextField.putClientProperty("FlatLaf.styleClass", "large"); + material4TextField.putClientProperty(FlatClientProperties.STYLE_CLASS, "large"); add(material4TextField, "cell 0 7"); add(material4ChartColor, "cell 1 7"); @@ -178,7 +179,7 @@ public class FlatAnimatedBorderTest // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables private JLabel label3; - private FlatAnimatorTest.LineChartPanel lineChartPanel; + private LineChartPanel lineChartPanel; private JTextField fade1TextField; private FlatAnimatorTest.JChartColor fade1ChartColor; private JTextField fade2TextField; @@ -231,7 +232,7 @@ public class FlatAnimatedBorderTest if( animatedValue != 0 && animatedValue != 1 ) { Color chartColor = (Color) ((JComponent)c).getClientProperty( CHART_COLOR_KEY ); - lineChartPanel.lineChart.addValue( animatedValue, chartColor ); + lineChartPanel.addValue( chartColor, animatedValue, Integer.MIN_VALUE, "fade" ); } } @@ -295,7 +296,7 @@ public class FlatAnimatedBorderTest if( animatedValue != 0 && animatedValue != 1 ) { Color chartColor = (Color) ((JComponent)c).getClientProperty( CHART_COLOR_KEY ); - lineChartPanel.lineChart.addValue( animatedValue, chartColor ); + lineChartPanel.addValue( chartColor, animatedValue, Integer.MIN_VALUE, "material" ); } } @@ -410,7 +411,7 @@ public class FlatAnimatedBorderTest if( animatedValue != 0 && animatedValue != 1 ) { Color chartColor = (Color) ((JComponent)c).getClientProperty( CHART_COLOR_KEY ); - lineChartPanel.lineChart.addValue( animatedValue, chartColor ); + lineChartPanel.addValue( chartColor, animatedValue, Integer.MIN_VALUE, "minimal" ); } } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.jfd index 4c32e5ed..8c023082 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.jfd @@ -1,11 +1,11 @@ -JFDML JFormDesigner: "7.0.5.0.382" Java: "16" encoding: "UTF-8" +JFDML JFormDesigner: "8.3" encoding: "UTF-8" new FormModel { contentType: "form/swing" root: new FormRoot { add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "insets dialog,hidemode 3" - "$columnConstraints": "[fill][fill]para[fill]" + "$columnConstraints": "[fill][fill]para[grow,fill]" "$rowConstraints": "[][][]para[][][][][]para[][][grow][]" } ) { name: "this" @@ -15,10 +15,10 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 0" } ) - add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$LineChartPanel" ) { + add( new FormComponent( "com.formdev.flatlaf.testing.LineChartPanel" ) { name: "lineChartPanel" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 2 0 1 12" + "value": "cell 2 0 1 12,growy" } ) add( new FormComponent( "javax.swing.JTextField" ) { name: "fade1TextField" @@ -122,7 +122,7 @@ new FormModel { } ) }, new FormLayoutConstraints( null ) { "location": new java.awt.Point( 0, 0 ) - "size": new java.awt.Dimension( 725, 325 ) + "size": new java.awt.Dimension( 725, 465 ) } ) } } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.java index 49e80740..12f8bc22 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.java @@ -39,7 +39,7 @@ public class FlatAnimatedIconTest private static final Color CHART_RADIO_BUTTON_3 = Color.green; private static final Color CHART_CHECK_BOX_1 = Color.magenta; private static final Color CHART_CHECK_BOX_2 = Color.orange; - private static final Color[] CHART_SWITCH_EX = new Color[] { Color.red, Color.green, Color.blue }; + private static final Color[] CHART_SWITCH_EX = { Color.red, Color.green, Color.blue }; private static final String CHART_COLOR_KEY = "chartColor"; @@ -79,7 +79,7 @@ public class FlatAnimatedIconTest // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents radioButton1 = new JRadioButton(); radioButton1ChartColor = new FlatAnimatorTest.JChartColor(); - lineChartPanel = new FlatAnimatorTest.LineChartPanel(); + lineChartPanel = new LineChartPanel(); radioButton2 = new JRadioButton(); radioButton2ChartColor = new FlatAnimatorTest.JChartColor(); radioButton3 = new JRadioButton(); @@ -114,7 +114,7 @@ public class FlatAnimatedIconTest radioButton1.setSelected(true); add(radioButton1, "cell 0 0"); add(radioButton1ChartColor, "cell 1 0"); - add(lineChartPanel, "cell 2 0 1 7"); + add(lineChartPanel, "cell 2 0 1 8,growy"); //---- radioButton2 ---- radioButton2.setText("radio 2"); @@ -142,11 +142,11 @@ public class FlatAnimatedIconTest //---- durationLabel ---- durationLabel.setText("Duration:"); - add(durationLabel, "cell 0 7 3 1"); + add(durationLabel, "cell 0 7 2 1"); //---- durationField ---- durationField.setModel(new SpinnerNumberModel(200, 0, null, 50)); - add(durationField, "cell 0 7 3 1"); + add(durationField, "cell 0 7 2 1"); //---- buttonGroup1 ---- ButtonGroup buttonGroup1 = new ButtonGroup(); @@ -159,7 +159,7 @@ public class FlatAnimatedIconTest // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables private JRadioButton radioButton1; private FlatAnimatorTest.JChartColor radioButton1ChartColor; - private FlatAnimatorTest.LineChartPanel lineChartPanel; + private LineChartPanel lineChartPanel; private JRadioButton radioButton2; private FlatAnimatorTest.JChartColor radioButton2ChartColor; private JRadioButton radioButton3; @@ -216,7 +216,7 @@ public class FlatAnimatedIconTest if( animatedValue != 0 && animatedValue != 1 ) { Color chartColor = (Color) ((JComponent)c).getClientProperty( CHART_COLOR_KEY ); - lineChartPanel.lineChart.addValue( animatedValue, chartColor ); + lineChartPanel.addValue( chartColor, animatedValue, Integer.MIN_VALUE, "radio" ); } } @@ -261,7 +261,7 @@ public class FlatAnimatedIconTest if( animatedValue != 0 && animatedValue != 1 ) { Color chartColor = (Color) ((JComponent)c).getClientProperty( CHART_COLOR_KEY ); - lineChartPanel.lineChart.addValue( animatedValue, chartColor ); + lineChartPanel.addValue( chartColor, animatedValue, Integer.MIN_VALUE, "switch" ); } } @@ -328,7 +328,7 @@ public class FlatAnimatedIconTest for( int i = 0; i < animatedValues.length; i++ ) { float animatedValue = animatedValues[i]; if( animatedValue != 0 && animatedValue != 1 ) - lineChartPanel.lineChart.addValue( animatedValue, CHART_SWITCH_EX[i] ); + lineChartPanel.addValue( CHART_SWITCH_EX[i], animatedValue, Integer.MIN_VALUE, "switch ex" ); } } @@ -387,7 +387,7 @@ public class FlatAnimatedIconTest if( animatedValue != 0 && animatedValue != 1 ) { Color chartColor = (Color) ((JComponent)c).getClientProperty( CHART_COLOR_KEY ); - lineChartPanel.lineChart.addValue( animatedValue, chartColor ); + lineChartPanel.addValue( chartColor, animatedValue, Integer.MIN_VALUE, "minimal" ); } } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.jfd index 62a2ae32..dd7867fc 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.jfd @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "7.0.5.0.382" Java: "16" encoding: "UTF-8" +JFDML JFormDesigner: "8.3" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -22,10 +22,10 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 1 0" } ) - add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$LineChartPanel" ) { + add( new FormComponent( "com.formdev.flatlaf.testing.LineChartPanel" ) { name: "lineChartPanel" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 2 0 1 7" + "value": "cell 2 0 1 8,growy" } ) add( new FormComponent( "javax.swing.JRadioButton" ) { name: "radioButton2" @@ -83,7 +83,7 @@ new FormModel { name: "durationLabel" "text": "Duration:" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 7 3 1" + "value": "cell 0 7 2 1" } ) add( new FormComponent( "javax.swing.JSpinner" ) { name: "durationField" @@ -93,7 +93,7 @@ new FormModel { value: 200 } }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 7 3 1" + "value": "cell 0 7 2 1" } ) }, new FormLayoutConstraints( null ) { "location": new java.awt.Point( 0, 0 ) diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.java index 11928d85..7cb6e651 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.java @@ -17,17 +17,9 @@ package com.formdev.flatlaf.testing; import java.awt.*; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import javax.swing.*; -import com.formdev.flatlaf.FlatLaf; -import com.formdev.flatlaf.ui.FlatUIUtils; import com.formdev.flatlaf.util.Animator; import com.formdev.flatlaf.util.CubicBezierEasing; -import com.formdev.flatlaf.util.HSLColor; -import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.Animator.Interpolator; import net.miginfocom.swing.*; @@ -74,7 +66,7 @@ public class FlatAnimatorTest } else { animator = new Animator( 1000, fraction -> { scrollBar.setValue( Math.round( fraction * scrollBar.getMaximum() ) ); - lineChartPanel.lineChart.addValue( fraction, chartColor ); + lineChartPanel.addValue( chartColor, fraction, Integer.MIN_VALUE, "animator" ); } ); animator.setInterpolator( interpolator ); animator.start(); @@ -94,7 +86,7 @@ public class FlatAnimatorTest standardEasingChartColor = new FlatAnimatorTest.JChartColor(); standardEasingScrollBar = new JScrollBar(); startButton = new JButton(); - lineChartPanel = new FlatAnimatorTest.LineChartPanel(); + lineChartPanel = new LineChartPanel(); //======== this ======== setLayout(new MigLayout( @@ -159,420 +151,9 @@ public class FlatAnimatorTest private FlatAnimatorTest.JChartColor standardEasingChartColor; private JScrollBar standardEasingScrollBar; private JButton startButton; - private FlatAnimatorTest.LineChartPanel lineChartPanel; + private LineChartPanel lineChartPanel; // JFormDesigner - End of variables declaration //GEN-END:variables - //---- class LineChartPanel ----------------------------------------------- - - static class LineChartPanel - extends JPanel - { - LineChartPanel() { - initComponents(); - - secondsWidthSlider.setValue( lineChart.getSecondsWidth() ); - updateChartDelayedChanged(); - } - - void setSecondsWidth( int secondsWidth ) { - lineChart.setSecondsWidth( secondsWidth ); - secondsWidthSlider.setValue( secondsWidth ); - } - - private void secondsWidthChanged() { - lineChart.setSecondsWidth( secondsWidthSlider.getValue() ); - } - - private void updateChartDelayedChanged() { - lineChart.setUpdateDelayed( updateChartDelayedCheckBox.isSelected() ); - } - - private void clearChart() { - lineChart.clear(); - } - - private void initComponents() { - // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents - JScrollPane lineChartScrollPane = new JScrollPane(); - lineChart = new FlatAnimatorTest.LineChart(); - JLabel lineChartInfoLabel = new JLabel(); - secondsWidthSlider = new JSlider(); - updateChartDelayedCheckBox = new JCheckBox(); - JButton clearChartButton = new JButton(); - - //======== this ======== - setLayout(new MigLayout( - "ltr,insets 0,hidemode 3", - // columns - "[fill]" + - "[grow,fill]", - // rows - "[400,grow,fill]" + - "[]")); - - //======== lineChartScrollPane ======== - { - lineChartScrollPane.putClientProperty("JScrollPane.smoothScrolling", false); - lineChartScrollPane.setViewportView(lineChart); - } - add(lineChartScrollPane, "cell 0 0 2 1"); - - //---- lineChartInfoLabel ---- - lineChartInfoLabel.setText("X: time (500ms per line) / Y: value (10% per line)"); - add(lineChartInfoLabel, "cell 0 1 2 1"); - - //---- secondsWidthSlider ---- - secondsWidthSlider.setMinimum(100); - secondsWidthSlider.setMaximum(2000); - secondsWidthSlider.addChangeListener(e -> secondsWidthChanged()); - add(secondsWidthSlider, "cell 0 1 2 1"); - - //---- updateChartDelayedCheckBox ---- - updateChartDelayedCheckBox.setText("Update chart delayed"); - updateChartDelayedCheckBox.setMnemonic('U'); - updateChartDelayedCheckBox.addActionListener(e -> updateChartDelayedChanged()); - add(updateChartDelayedCheckBox, "cell 0 1 2 1,alignx right,growx 0"); - - //---- clearChartButton ---- - clearChartButton.setText("Clear Chart"); - clearChartButton.setMnemonic('C'); - clearChartButton.addActionListener(e -> clearChart()); - add(clearChartButton, "cell 0 1 2 1,alignx right,growx 0"); - // JFormDesigner - End of component initialization //GEN-END:initComponents - } - - // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables - FlatAnimatorTest.LineChart lineChart; - private JSlider secondsWidthSlider; - private JCheckBox updateChartDelayedCheckBox; - // JFormDesigner - End of variables declaration //GEN-END:variables - } - - //---- class LineChart ---------------------------------------------------- - - static class LineChart - extends JComponent - implements Scrollable - { - private static final int NEW_SEQUENCE_TIME_LAG = 500; - private static final int NEW_SEQUENCE_GAP = 100; - - private boolean asynchron; - private int secondsWidth = 500; - - private static class Data { - final double value; - final boolean dot; - final Color chartColor; - final long time; // in milliseconds - - Data( double value, boolean dot, Color chartColor, long time ) { - this.value = value; - this.dot = dot; - this.chartColor = chartColor; - this.time = time; - } - - @Override - public String toString() { - // for debugging - return String.valueOf( value ); - } - } - - private final List syncChartData = new ArrayList<>(); - private final Map> asyncColor2dataMap = new HashMap<>(); - private final Timer repaintTime; - private Color lastUsedChartColor; - private boolean updateDelayed; - - LineChart() { - repaintTime = new Timer( 20, e -> repaintAndRevalidate() ); - repaintTime.setRepeats( false ); - } - - void enableAsynchron() { - if( !syncChartData.isEmpty() ) - throw new IllegalStateException(); - - asynchron = true; - } - - void addValue( double value, Color chartColor ) { - addValue( value, false, chartColor ); - } - - void addValue( double value, boolean dot, Color chartColor ) { - List chartData = asyncColor2dataMap.computeIfAbsent( chartColor, k -> new ArrayList<>() ); - Data data = new Data( value, dot, chartColor, System.nanoTime() / 1000000 ); - if( asynchron ) - chartData.add( data ); - else - syncChartData.add( data ); - - lastUsedChartColor = chartColor; - - if( updateDelayed ) { - repaintTime.stop(); - repaintTime.start(); - } else - repaintAndRevalidate(); - } - - void clear() { - syncChartData.clear(); - asyncColor2dataMap.clear(); - lastUsedChartColor = null; - - repaint(); - revalidate(); - } - - void setUpdateDelayed( boolean updateDelayed ) { - this.updateDelayed = updateDelayed; - } - - int getSecondsWidth() { - return secondsWidth; - } - - void setSecondsWidth( int secondsWidth ) { - this.secondsWidth = secondsWidth; - repaint(); - revalidate(); - } - - private void repaintAndRevalidate() { - repaint(); - revalidate(); - - // scroll horizontally - if( lastUsedChartColor != null ) { - // compute chart width of last used color and start of last sequence - int[] lastSeqX = new int[1]; - int cw = chartWidth( asynchron ? asyncColor2dataMap.get( lastUsedChartColor ) : syncChartData, lastSeqX ); - - // scroll to end of last sequence (of last used color) - int lastSeqWidth = cw - lastSeqX[0]; - int width = Math.min( lastSeqWidth, getParent().getWidth() ); - int x = cw - width; - scrollRectToVisible( new Rectangle( x, 0, width, getHeight() ) ); - } - } - - @Override - protected void paintComponent( Graphics g ) { - Graphics g2 = g.create(); - try { - HiDPIUtils.paintAtScale1x( (Graphics2D) g2, this, this::paintImpl ); - } finally { - g2.dispose(); - } - } - - private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) { - FlatUIUtils.setRenderingHints( g ); - - int secondsWidth = (int) (this.secondsWidth * scaleFactor); - - Color lineColor = FlatUIUtils.getUIColor( "Component.borderColor", Color.lightGray ); - Color lineColor2 = FlatLaf.isLafDark() - ? new HSLColor( lineColor ).adjustTone( 30 ) - : new HSLColor( lineColor ).adjustShade( 30 ); - - g.translate( x, y ); - - // fill background - g.setColor( UIManager.getColor( "Table.background" ) ); - g.fillRect( x, y, width, height ); - - // paint horizontal lines - for( int i = 1; i < 10; i++ ) { - int hy = (height * i) / 10; - g.setColor( (i != 5) ? lineColor : lineColor2 ); - g.drawLine( 0, hy, width, hy ); - } - - // paint vertical lines - int twoHundredMillisWidth = secondsWidth / 5; - for( int i = twoHundredMillisWidth; i < width; i += twoHundredMillisWidth ) { - g.setColor( (i % secondsWidth != 0) ? lineColor : lineColor2 ); - g.drawLine( i, 0, i, height ); - } - - // paint lines - for( Map.Entry> e : asyncColor2dataMap.entrySet() ) { - List chartData = asynchron ? e.getValue() : syncChartData; - Color chartColor = e.getKey(); - paintChartData( g, chartData, chartColor, height, scaleFactor ); - } - } - - private void paintChartData( Graphics2D g, List chartData, Color chartColor, int height, double scaleFactor ) { - if( FlatLaf.isLafDark() ) - chartColor = new HSLColor( chartColor ).adjustTone( 50 ); - Color temporaryValueColor = new Color( (chartColor.getRGB() & 0xffffff) | 0x40000000, true ); - - int seqGapWidth = (int) (NEW_SEQUENCE_GAP * scaleFactor); - long seqTime = 0; - int seqX = 0; - long ptime = 0; - int px = 0; - int py = height - 1; - int cx = px; - int cy = py; - int pcount = 0; - - g.setColor( chartColor ); - - boolean first = true; - int size = chartData.size(); - for( int i = 0; i < size; i++ ) { - Data data = chartData.get( i ); - boolean useData = (data.chartColor == chartColor); - int dy = height - 1 - (int) ((height - 1) * data.value); - - if( data.dot ) { - int dotx = px; - if( i > 0 && data.time > ptime + NEW_SEQUENCE_TIME_LAG ) - dotx += seqGapWidth; - int o = UIScale.scale( 1 ); - int s = UIScale.scale( 3 ); - g.fillRect( dotx - o, dy - o, s, s ); - continue; - } - - if( data.time > ptime + NEW_SEQUENCE_TIME_LAG ) { - if( !first && pcount == 0 ) - g.drawLine( px, py, px + (int) (4 * scaleFactor), py ); - - // start new sequence - seqTime = data.time; - seqX = !first ? px + seqGapWidth : 0; - px = seqX; - pcount = 0; - first = false; - } else { - boolean isTemporaryValue = isTemporaryValue( chartData, i ) || isTemporaryValue( chartData, i - 1 ); - if( isTemporaryValue ) - g.setColor( temporaryValueColor ); - - // line in sequence - int dx = (int) (seqX + (((data.time - seqTime) / 1000.) * secondsWidth)); - if( useData ) - g.drawLine( cx, cy, dx, dy ); - px = dx; - pcount++; - - if( isTemporaryValue ) - g.setColor( chartColor ); - } - - py = dy; - ptime = data.time; - - if( useData ) { - cx = px; - cy = py; - } - } - } - - /** - * One or two values between two equal values are considered "temporary", - * which means that they are the target value for the following scroll animation. - */ - private boolean isTemporaryValue( List chartData, int i ) { - if( i == 0 || i == chartData.size() - 1 ) - return false; - - Data dataBefore = chartData.get( i - 1 ); - Data dataAfter = chartData.get( i + 1 ); - - if( dataBefore.dot || dataAfter.dot ) - return false; - - double valueBefore = dataBefore.value; - double valueAfter = dataAfter.value; - - return valueBefore == valueAfter || - (i < chartData.size() - 2 && valueBefore == chartData.get( i + 2 ).value) || - (i > 1 && chartData.get( i - 2 ).value == valueAfter); - } - - private int chartWidth() { - int width = 0; - if( asynchron ) { - for( List chartData : asyncColor2dataMap.values() ) - width = Math.max( width, chartWidth( chartData, null ) ); - } else - width = Math.max( width, chartWidth( syncChartData, null ) ); - return width; - } - - private int chartWidth( List chartData, int[] lastSeqX ) { - long seqTime = 0; - int seqX = 0; - long ptime = 0; - int px = 0; - - int size = chartData.size(); - for( int i = 0; i < size; i++ ) { - Data data = chartData.get( i ); - - if( data.time > ptime + NEW_SEQUENCE_TIME_LAG ) { - // start new sequence - seqTime = data.time; - seqX = (i > 0) ? px + NEW_SEQUENCE_GAP : 0; - px = seqX; - } else { - // line in sequence - int dx = (int) (seqX + (((data.time - seqTime) / 1000.) * secondsWidth)); - px = dx; - } - - ptime = data.time; - } - - if( lastSeqX != null ) - lastSeqX[0] = seqX; - - return px; - } - - @Override - public Dimension getPreferredSize() { - return new Dimension( chartWidth(), 200 ); - } - - @Override - public Dimension getPreferredScrollableViewportSize() { - return new Dimension( chartWidth(), 200 ); - } - - @Override - public int getScrollableUnitIncrement( Rectangle visibleRect, int orientation, int direction ) { - return secondsWidth; - } - - @Override - public int getScrollableBlockIncrement( Rectangle visibleRect, int orientation, int direction ) { - JViewport viewport = (JViewport) SwingUtilities.getAncestorOfClass( JViewport.class, this ); - return (viewport != null) ? viewport.getWidth() : 200; - } - - @Override - public boolean getScrollableTracksViewportWidth() { - JViewport viewport = (JViewport) SwingUtilities.getAncestorOfClass( JViewport.class, this ); - return (viewport != null) ? viewport.getWidth() > chartWidth() : true; - } - - @Override - public boolean getScrollableTracksViewportHeight() { - return true; - } - } - //---- class JChartColor -------------------------------------------------- static class JChartColor diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.jfd index f8a7f3c1..c3bf86cf 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.jfd @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "7.0.5.0.382" Java: "16" encoding: "UTF-8" +JFDML JFormDesigner: "8.3" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -103,7 +103,7 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 3" } ) - add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$LineChartPanel" ) { + add( new FormComponent( "com.formdev.flatlaf.testing.LineChartPanel" ) { name: "lineChartPanel" auxiliary() { "JavaCodeGenerator.variableLocal": false @@ -115,67 +115,5 @@ new FormModel { "location": new java.awt.Point( 0, 0 ) "size": new java.awt.Dimension( 625, 625 ) } ) - add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { - "$columnConstraints": "[fill][grow,fill]" - "$rowConstraints": "[400,grow,fill][]" - "$layoutConstraints": "ltr,insets 0,hidemode 3" - } ) { - name: "lineChartPanelNested" - auxiliary() { - "JavaCodeGenerator.className": "LineChartPanel" - } - add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) { - name: "lineChartScrollPane" - "$client.JScrollPane.smoothScrolling": false - add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$LineChart" ) { - name: "lineChart" - auxiliary() { - "JavaCodeGenerator.variableLocal": false - "JavaCodeGenerator.variableModifiers": 0 - } - } ) - }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 0 2 1" - } ) - add( new FormComponent( "javax.swing.JLabel" ) { - name: "lineChartInfoLabel" - "text": "X: time (500ms per line) / Y: value (10% per line)" - }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 1 2 1" - } ) - add( new FormComponent( "javax.swing.JSlider" ) { - name: "secondsWidthSlider" - "minimum": 100 - "maximum": 2000 - auxiliary() { - "JavaCodeGenerator.variableLocal": false - } - addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "secondsWidthChanged", false ) ) - }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 1 2 1" - } ) - add( new FormComponent( "javax.swing.JCheckBox" ) { - name: "updateChartDelayedCheckBox" - "text": "Update chart delayed" - "mnemonic": 85 - auxiliary() { - "JavaCodeGenerator.variableLocal": false - } - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "updateChartDelayedChanged", false ) ) - }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 1 2 1,alignx right,growx 0" - } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "clearChartButton" - "text": "Clear Chart" - "mnemonic": 67 - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "clearChart", false ) ) - }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 1 2 1,alignx right,growx 0" - } ) - }, new FormLayoutConstraints( null ) { - "location": new java.awt.Point( 0, 650 ) - "size": new java.awt.Dimension( 603, 325 ) - } ) } } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/LineChartPanel.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/LineChartPanel.java index 3507c798..436ab871 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/LineChartPanel.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/LineChartPanel.java @@ -77,6 +77,33 @@ class LineChartPanel JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); } + public boolean isYZeroAtTop() { + return lineChart.yZeroAtTop; + } + + public void setYZeroAtTop( boolean yZeroAtTop ) { + lineChart.yZeroAtTop = yZeroAtTop; + lineChart.repaint(); + } + + public boolean isAsynchron() { + return lineChart.asynchron; + } + + public void setAsynchron( boolean asynchron ) { + lineChart.asynchron = asynchron; + lineChart.repaint(); + } + + public boolean isTemporaryValueDetection() { + return lineChart.temporaryValueDetection; + } + + public void setTemporaryValueDetection( boolean temporaryValueDetection ) { + lineChart.temporaryValueDetection = temporaryValueDetection; + lineChart.repaint(); + } + public String getLegendYValueText() { return yValueLabel.getText(); } @@ -101,12 +128,21 @@ class LineChartPanel legend2Label.setText( s ); } + public int getOneSecondWidth() { + return oneSecondWidthSlider.getValue(); + } + + public void setOneSecondWidth( int oneSecondWidth ) { + oneSecondWidthSlider.setValue( oneSecondWidth ); + } + public boolean isUpdateChartDelayed() { return updateChartDelayedCheckBox.isSelected(); } public void setUpdateChartDelayed( boolean updateChartDelayed ) { updateChartDelayedCheckBox.setSelected( updateChartDelayed ); + updateChartDelayedChanged(); } void addValue( Color chartColor, double value, int ivalue, String name ) { @@ -137,8 +173,8 @@ class LineChartPanel oneSecondWidth <= 8000 ? 25 : 10; - lineChart.setOneSecondWidth( oneSecondWidth ); - lineChart.setMsPerLineX( msPerLineX ); + lineChart.oneSecondWidth = oneSecondWidth; + lineChart.msPerLineX = msPerLineX; lineChart.revalidate(); lineChart.repaint(); @@ -149,7 +185,7 @@ class LineChartPanel private String xLabelText; private void updateChartDelayedChanged() { - lineChart.setUpdateDelayed( updateChartDelayedCheckBox.isSelected() ); + lineChart.updateDelayed = updateChartDelayedCheckBox.isSelected(); } private void clearChart() { @@ -227,15 +263,17 @@ class LineChartPanel add(oneSecondWidthLabel, "cell 0 1,alignx right,growx 0"); //---- oneSecondWidthSlider ---- - oneSecondWidthSlider.setMinimum(1000); + oneSecondWidthSlider.setMinimum(100); oneSecondWidthSlider.setMaximum(10000); + oneSecondWidthSlider.setSnapToTicks(true); + oneSecondWidthSlider.setMajorTickSpacing(100); + oneSecondWidthSlider.setValue(500); oneSecondWidthSlider.addChangeListener(e -> oneSecondWidthChanged()); - add(oneSecondWidthSlider, "cell 0 1,alignx right,growx 0,wmax 100"); + add(oneSecondWidthSlider, "cell 0 1,alignx right,growx 0"); //---- updateChartDelayedCheckBox ---- updateChartDelayedCheckBox.setText("Update chart delayed"); updateChartDelayedCheckBox.setMnemonic('P'); - updateChartDelayedCheckBox.setSelected(true); updateChartDelayedCheckBox.addActionListener(e -> updateChartDelayedChanged()); add(updateChartDelayedCheckBox, "cell 0 1,alignx right,growx 0"); @@ -267,27 +305,36 @@ class LineChartPanel implements Scrollable { private static final int UPDATE_DELAY_MS = 20; - - private static final int NEW_SEQUENCE_TIME_LAG = 500; - private static final int NEW_SEQUENCE_GAP = 100; + private static final int NEW_SEQUENCE_TIME_LAG_MS = 500; + private static final int NEW_SEQUENCE_GAP_MS = 100; private static final int HIT_OFFSET = 4; - private int oneSecondWidth = 1000; - private int msPerLineX = 200; + private static final boolean TEST = false; + + // asynchron means that chart for each color starts at x=0 + private boolean asynchron; + private boolean temporaryValueDetection; + private boolean yZeroAtTop; + private int oneSecondWidth = 500; + private int msPerLineX = 100; private final HashMap methodHighlightMap = new HashMap<>(); private static class Data { final double value; final int ivalue; + final Color chartColor; final Color dotColor; final boolean dotOnly; final long time; // in milliseconds final String name; final Exception stack; - Data( double value, int ivalue, Color dotColor, boolean dotOnly, long time, String name, Exception stack ) { + Data( double value, int ivalue, Color chartColor, Color dotColor, + boolean dotOnly, long time, String name, Exception stack ) + { this.value = value; this.ivalue = ivalue; + this.chartColor = chartColor; this.dotColor = dotColor; this.dotOnly = dotOnly; this.time = time; @@ -303,7 +350,8 @@ class LineChartPanel } } - private final Map> color2dataMap = new HashMap<>(); + private final List syncChartData = new ArrayList<>(); + private final Map> asyncColor2dataMap = new HashMap<>(); private final Timer repaintTime; private Color lastUsedChartColor; private boolean updateDelayed; @@ -318,11 +366,21 @@ class LineChartPanel repaintTime.setRepeats( false ); ToolTipManager.sharedInstance().registerComponent( this ); + + if( TEST ) + initTestData(); } void addValue( Color chartColor, double value, int ivalue, Color dotColor, boolean dotOnly, String name ) { - List chartData = color2dataMap.computeIfAbsent( chartColor, k -> new ArrayList<>() ); - chartData.add( new Data( value, ivalue, dotColor, dotOnly, System.nanoTime() / 1000000, name, new Exception() ) ); + if( TEST ) + return; + + List chartData = asyncColor2dataMap.computeIfAbsent( chartColor, k -> new ArrayList<>() ); + Data data = new Data( value, ivalue, chartColor, dotColor, dotOnly, System.nanoTime() / 1_000_000, name, new Exception() ); + if( asynchron ) + chartData.add( data ); + else + syncChartData.add( data ); lastUsedChartColor = chartColor; @@ -334,25 +392,19 @@ class LineChartPanel } void clear() { - color2dataMap.clear(); + if( TEST ) { + repaint(); + return; + } + + syncChartData.clear(); + asyncColor2dataMap.clear(); lastUsedChartColor = null; repaint(); revalidate(); } - void setUpdateDelayed( boolean updateDelayed ) { - this.updateDelayed = updateDelayed; - } - - void setOneSecondWidth( int oneSecondWidth ) { - this.oneSecondWidth = oneSecondWidth; - } - - void setMsPerLineX( int msPerLineX ) { - this.msPerLineX = msPerLineX; - } - private void repaintAndRevalidate() { repaint(); revalidate(); @@ -361,7 +413,7 @@ class LineChartPanel if( lastUsedChartColor != null ) { // compute chart width of last used color and start of last sequence int[] lastSeqX = new int[1]; - int cw = chartWidth( color2dataMap.get( lastUsedChartColor ), lastSeqX ); + int cw = chartWidth( asynchron ? asyncColor2dataMap.get( lastUsedChartColor ) : syncChartData, lastSeqX ); // scroll to end of last sequence (of last used color) int lastSeqWidth = cw - lastSeqX[0]; @@ -375,17 +427,17 @@ class LineChartPanel protected void paintComponent( Graphics g ) { Graphics g2 = g.create(); try { - HiDPIUtils.paintAtScale1x( (Graphics2D) g2, this, this::paintImpl ); + HiDPIUtils.paintAtScale1x( (Graphics2D) g2, this, this::paintAt1x ); } finally { g2.dispose(); } } - private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) { + private void paintAt1x( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) { FlatUIUtils.setRenderingHints( g ); int oneSecondWidth = (int) (UIScale.scale( this.oneSecondWidth ) * scaleFactor); - int seqGapWidth = (int) (NEW_SEQUENCE_GAP * scaleFactor); + int seqGapWidth = (oneSecondWidth * NEW_SEQUENCE_GAP_MS) / 1000; int hitOffset = (int) Math.round( UIScale.scale( HIT_OFFSET ) * scaleFactor ); Color lineColor = FlatUIUtils.getUIColor( "Component.borderColor", Color.lightGray ); @@ -418,8 +470,8 @@ class LineChartPanel lastSystemScaleFactor = scaleFactor; // paint lines - for( Map.Entry> e : color2dataMap.entrySet() ) { - List chartData = e.getValue(); + for( Map.Entry> e : asyncColor2dataMap.entrySet() ) { + List chartData = asynchron ? e.getValue() : syncChartData; Color chartColor = e.getKey(); if( FlatLaf.isLafDark() ) chartColor = new HSLColor( chartColor ).adjustTone( 50 ); @@ -427,107 +479,121 @@ class LineChartPanel Color dataPointColor = fade( chartColor, FlatLaf.isLafDark() ? 0.6f : 0.2f ); // sequence start time and x coordinate - long seqTime = 0; - int seqX = 0; + long seqStartTime = 0; + int seqStartX = 0; - // "previous" data point time, x/y coordinates and count - long ptime = 0; + // "previous" data point time and x coordinate (used for "new sequence" detection) + long ptime = Long.MIN_VALUE; int px = 0; - int py = 0; - int pcount = 0; - boolean first = true; + // "line" data point x/y coordinates + int lx = -1; + int ly = -1; + boolean isTemporaryValue = false; int lastTemporaryValueIndex = -1; int size = chartData.size(); for( int i = 0; i < size; i++ ) { Data data = chartData.get( i ); + boolean useData = (data.chartColor == chartColor); - boolean newSeq = (data.time > ptime + NEW_SEQUENCE_TIME_LAG); + // start new sequence if there is a larger time gap to previous data point + boolean newSeq = (data.time > ptime + NEW_SEQUENCE_TIME_LAG_MS); ptime = data.time; if( newSeq ) { - // paint short horizontal line for previous sequence that has only one data point - if( !first && pcount == 0 ) { - g.setColor( chartColor ); - g.drawLine( px, py, px + (int) Math.round( UIScale.scale( 8 ) * scaleFactor ), py ); - } - // start new sequence - seqTime = data.time; - seqX = !first ? px + seqGapWidth : 0; - px = seqX; - pcount = 0; - first = false; + seqStartTime = data.time; + seqStartX = (i > 0) ? px + seqGapWidth : 0; + px = seqStartX; + lx = -1; + ly = -1; isTemporaryValue = false; } // x/y coordinates of current data point + int dx = (int) (seqStartX + (((data.time - seqStartTime) / 1000.) * oneSecondWidth)); int dy = (int) ((height - 1) * data.value); - int dx = (int) (seqX + (((data.time - seqTime) / 1000.) * oneSecondWidth)); + if( !yZeroAtTop ) + dy = height - 1 - dy; - // paint rectangle to indicate data point - g.setColor( dataPointColor ); - g.drawRect( dx - hitOffset, dy - hitOffset, hitOffset * 2, hitOffset * 2 ); + // remember x coordinate for "new sequence" detection + px = dx; + + if( !useData ) + continue; // remember data point for tooltip lastPoints.add( new Point( dx, dy ) ); lastDatas.add( data ); + // paint rectangle to indicate data point + g.setColor( dataPointColor ); + g.drawRect( dx - hitOffset, dy - hitOffset, hitOffset * 2, hitOffset * 2 ); + + // paint dot if( data.dotColor != null ) { int s1 = (int) Math.round( UIScale.scale( 1 ) * scaleFactor ); int s3 = (int) Math.round( UIScale.scale( 3 ) * scaleFactor ); g.setColor( data.dotColor ); g.fillRect( dx - s1, dy - s1, s3, s3 ); + if( data.dotOnly ) continue; } - if( !newSeq ) { - if( isTemporaryValue && i > lastTemporaryValueIndex ) - isTemporaryValue = false; - - g.setColor( isTemporaryValue ? temporaryValueColor : chartColor ); - - // line in sequence - g.drawLine( px, py, dx, dy ); - - px = dx; - pcount++; - - // check next data points for "temporary" value(s) - if( !isTemporaryValue ) { - // one or two values between two equal values are considered "temporary", - // which means that they are the target value for the following scroll animation - int stage = 0; - for( int j = i + 1; j < size && stage <= 2 && !isTemporaryValue; j++ ) { - Data nextData = chartData.get( j ); - if( nextData.dotOnly ) - continue; // ignore dots - - // check whether next data point is within 10 milliseconds - if( nextData.time > data.time + 10 ) - break; - - if( stage >= 1 && stage <= 2 && nextData.value == data.value ) { - isTemporaryValue = true; - lastTemporaryValueIndex = j; - } - stage++; - } - } + // start of line? + if( lx < 0 ) { + // remember x/y coordinates for first line + lx = dx; + ly = dy; + continue; } - py = dy; + if( isTemporaryValue && i > lastTemporaryValueIndex ) + isTemporaryValue = false; + + // draw line in sequence + g.setColor( isTemporaryValue ? temporaryValueColor : chartColor ); + g.drawLine( lx, ly, dx, dy ); + + // remember x/y coordinates for next line + lx = dx; + ly = dy; + + // check next data points for "temporary" value(s) + if( temporaryValueDetection && !isTemporaryValue ) { + // one or two values between two equal values are considered "temporary", + // which means that they are the target value for the following scroll animation + int stage = 0; + for( int j = i + 1; j < size && stage <= 2 && !isTemporaryValue; j++ ) { + Data nextData = chartData.get( j ); + if( nextData.dotOnly ) + continue; // ignore dots + + // check whether next data point is within 10 milliseconds + if( nextData.time > data.time + 10 ) + break; + + if( stage >= 1 && stage <= 2 && nextData.value == data.value ) { + isTemporaryValue = true; + lastTemporaryValueIndex = j; + } + stage++; + } + } } } } private int chartWidth() { int width = 0; - for( List chartData : color2dataMap.values() ) - width = Math.max( width, chartWidth( chartData, null ) ); + if( asynchron ) { + for( List chartData : asyncColor2dataMap.values() ) + width = Math.max( width, chartWidth( chartData, null ) ); + } else + width = Math.max( width, chartWidth( syncChartData, null ) ); return width; } @@ -536,19 +602,21 @@ class LineChartPanel int seqX = 0; long ptime = 0; int px = 0; + int oneSecondWidth = UIScale.scale( this.oneSecondWidth ); + int seqGapWidth = (oneSecondWidth * NEW_SEQUENCE_GAP_MS) / 1000; int size = chartData.size(); for( int i = 0; i < size; i++ ) { Data data = chartData.get( i ); - if( data.time > ptime + NEW_SEQUENCE_TIME_LAG ) { + if( data.time > ptime + NEW_SEQUENCE_TIME_LAG_MS ) { // start new sequence seqTime = data.time; - seqX = (i > 0) ? px + NEW_SEQUENCE_GAP : 0; + seqX = (i > 0) ? px + seqGapWidth : 0; px = seqX; } else { // line in sequence - int dx = (int) (seqX + (((data.time - seqTime) / 1000.) * UIScale.scale( oneSecondWidth ))); + int dx = (int) (seqX + (((data.time - seqTime) / 1000.) * oneSecondWidth )); px = dx; } @@ -618,7 +686,11 @@ class LineChartPanel buf.append( "

" ); if( data.dotOnly ) buf.append( "DOT: " ); - buf.append( data.name ).append( ' ' ).append( data.ivalue ).append( "

" ); + buf.append( data.name ); + if( data.ivalue != Integer.MIN_VALUE ) + buf.append( ' ' ).append( data.ivalue ); + buf.append( " (" ).append( String.format( "%.3f", data.value ) ).append( ')' ); + buf.append( "" ); StackTraceElement[] stackTrace = data.stack.getStackTrace(); for( int j = 0; j < stackTrace.length; j++ ) { @@ -678,6 +750,7 @@ class LineChartPanel classAndMethod.equals( "java.awt.Component.processMouseWheelEvent" ) || classAndMethod.equals( "java.awt.Component.processMouseMotionEvent" ) || classAndMethod.equals( "javax.swing.JComponent.processKeyBinding" ) || + classAndMethod.equals( "javax.swing.JComponent.paintComponent" ) || classAndMethod.equals( "com.formdev.flatlaf.util.Animator.timingEvent" ) ) break; } @@ -704,6 +777,103 @@ class LineChartPanel return buf.toString(); } + private void initTestData() { +// asynchron = true; + + addTestSimpleLine( Color.red, 0.0, "red" ); + addTestSimpleLine( Color.green, 0.1, "green" ); + addTestSimpleLine( Color.blue, 0.2, "blue" ); + addTestSimpleLine( Color.magenta, 0.3, "magenta" ); + + addTestMiddleDotOnly( Color.red, 0.0, "red" ); + addTestMiddleDotOnly( Color.green, 0.1, "green" ); + addTestMiddleDotOnly( Color.blue, 0.2, "blue" ); + addTestMiddleDotOnly( Color.magenta, 0.3, "magenta" ); + + addTestLeadingDotOnly( Color.red, 0.0, "red" ); + addTestLeadingDotOnly( Color.green, 0.1, "green" ); + addTestLeadingDotOnly( Color.blue, 0.2, "blue" ); + addTestLeadingDotOnly( Color.magenta, 0.3, "magenta" ); + + addTestTrailingDotOnly( Color.red, 0.0, "red" ); + addTestTrailingDotOnly( Color.green, 0.1, "green" ); + addTestTrailingDotOnly( Color.blue, 0.2, "blue" ); + addTestTrailingDotOnly( Color.magenta, 0.3, "magenta" ); + + addTestSingleData( Color.red, 0.0, "red" ); + addTestSingleData( Color.green, 0.1, "green" ); + addTestSingleData( Color.blue, 0.2, "blue" ); + addTestSingleData( Color.magenta, 0.3, "magenta" ); + + temporaryValueDetection = true; + addTestWithTemporaryValues( Color.red, 0.0, "red" ); + addTestWithTemporaryValues( Color.green, 0.1, "green" ); + addTestWithTemporaryValues( Color.blue, 0.2, "blue" ); + addTestWithTemporaryValues( Color.magenta, 0.3, "magenta" ); + } + + private void addTestSimpleLine( Color chartColor, double baseValue, String name ) { + addTestValue( 0, chartColor, baseValue + 0.0, null, false, name ); + addTestValue( 50, chartColor, baseValue + 0.1, null, false, name ); + addTestValue( 50, chartColor, baseValue + 0.4, null, false, name ); + testTime += 1000; + } + + private void addTestMiddleDotOnly( Color chartColor, double baseValue, String name ) { + addTestValue( 0, chartColor, baseValue + 0.0, null, false, name ); + addTestValue( 20, chartColor, baseValue + 0.3, chartColor, true, name ); + addTestValue( 30, chartColor, baseValue + 0.1, null, false, name ); + addTestValue( 20, chartColor, baseValue + 0.05, chartColor, true, name ); + addTestValue( 30, chartColor, baseValue + 0.4, null, false, name ); + testTime += 1000; + } + + private void addTestLeadingDotOnly( Color chartColor, double baseValue, String name ) { + addTestValue( 0, chartColor, baseValue + 0.05, chartColor, true, name ); + addTestValue( 20, chartColor, baseValue + 0.0, null, false, name ); + addTestValue( 50, chartColor, baseValue + 0.1, null, false, name ); + addTestValue( 30, chartColor, baseValue + 0.4, null, false, name ); + testTime += 1000; + } + + private void addTestTrailingDotOnly( Color chartColor, double baseValue, String name ) { + addTestValue( 0, chartColor, baseValue + 0.0, null, false, name ); + addTestValue( 50, chartColor, baseValue + 0.1, null, false, name ); + addTestValue( 30, chartColor, baseValue + 0.4, null, false, name ); + addTestValue( 20, chartColor, baseValue + 0.05, chartColor, true, name ); + testTime += 1000; + } + + private void addTestSingleData( Color chartColor, double baseValue, String name ) { + addTestValue( 0, chartColor, baseValue + 0.15, chartColor, false, name ); + testTime += 1000; + } + + private void addTestWithTemporaryValues( Color chartColor, double baseValue, String name ) { + addTestValue( 0, chartColor, baseValue + 0.0, null, false, name ); + addTestValue( 50, chartColor, baseValue + 0.1, null, false, name ); + addTestValue( 5, chartColor, baseValue + 0.4, null, false, name ); + addTestValue( 5, chartColor, baseValue + 0.1, null, false, name ); + addTestValue( 40, chartColor, baseValue + 0.3, null, false, name ); + testTime += 1000; + } + + private void addTestValue( int timeDelta, Color chartColor, double value, Color dotColor, boolean dotOnly, String name ) { + testTime += timeDelta; + + List chartData = asyncColor2dataMap.computeIfAbsent( chartColor, k -> new ArrayList<>() ); + Data data = new Data( value, testIValue++, chartColor, dotColor, dotOnly, testTime, name, new Exception() ); + if( asynchron ) + chartData.add( data ); + else + syncChartData.add( data ); + + lastUsedChartColor = chartColor; + } + + private int testIValue; + private long testTime; + //TODO remove and use ColorFunctions.fade() when merging to main private static Color fade( Color color, float amount ) { int newAlpha = Math.round( 255 * amount ); diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/LineChartPanel.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/LineChartPanel.jfd index c81e0a65..2f66cb4a 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/LineChartPanel.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/LineChartPanel.jfd @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "8.1.0.0.283" Java: "19.0.2" encoding: "UTF-8" +JFDML JFormDesigner: "8.3" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -90,17 +90,19 @@ new FormModel { } ) add( new FormComponent( "javax.swing.JSlider" ) { name: "oneSecondWidthSlider" - "minimum": 1000 + "minimum": 100 "maximum": 10000 + "snapToTicks": true + "majorTickSpacing": 100 + "value": 500 addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "oneSecondWidthChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 1,alignx right,growx 0,wmax 100" + "value": "cell 0 1,alignx right,growx 0" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "updateChartDelayedCheckBox" "text": "Update chart delayed" "mnemonic": 80 - "selected": true addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "updateChartDelayedChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 1,alignx right,growx 0"