HanSolo / medusa

A JavaFX library for Gauges
Apache License 2.0
687 stars 129 forks source link

Adding Gauge/Gauges to UI performance improvement and Marker valueProperty update #223

Open chrisrush1 opened 8 months ago

chrisrush1 commented 8 months ago

Hello,

Firstly thanks for such a great library, I've been using it as a compass gauge for a while now and it works really well. I currently want to add a new gauge to my project and it will appear in a view that is basically a ListCell in a ListView, there can be up to 9 Gauges on the Tab that displays them. I've customised the Gauge to look the way I want and I'm actually using the Marker to show progress around the Gauge, as I don't want a needle and I couldn't see another way to do this without creating my own skin. Maybe I need to do this anyway, or I may end up creating my own simple Gauge depending on what advice you're able to offer to my main issue.. :)

The Gauges are added to the UI at the point the ListCell views are added to the ListView, there is another tab view that has a single instance of this Gauge on and that is created dynamically when the Tab is opened. I find that the Gauge makes the UI a bit unresponsive, which surprises me. I've written a simple REPREX to show this. I'm just adding 9 of the Gauges that I've created with your library and you can see it's fairly slow to add the Gauges when you press the Add button. Is there anything I'm doing or could change to increase the performance? When I perform a Thread dump to see what the Application Thread is doing it's redrawing the ticks like this:

"JavaFX Application Thread" #24 prio=5 os_prio=0 cpu=296.88ms elapsed=9.33s tid=0x000001cf67de8000 nid=0x4cf0 runnable [0x00000086752f8000] java.lang.Thread.State: RUNNABLE at java.math.BigDecimal.divideAndRemainder(java.base@11.0.2/BigDecimal.java:1981) at java.math.BigDecimal.remainder(java.base@11.0.2/BigDecimal.java:1922) at eu.hansolo.medusa.tools.Helper.drawRadialTickMarks(Helper.java:977) at eu.hansolo.medusa.skins.GaugeSkin.redraw(GaugeSkin.java:1095) at eu.hansolo.medusa.skins.GaugeSkinBase.handleEvents(GaugeSkinBase.java:60) at eu.hansolo.medusa.skins.GaugeSkin.handleEvents(GaugeSkin.java:305) at eu.hansolo.medusa.skins.GaugeSkinBase.lambda$registerListeners$2(GaugeSkinBase.java:52) at eu.hansolo.medusa.skins.GaugeSkinBase$$Lambda$156/0x000000080021cc40.onUpdateEvent(Unknown Source) at eu.hansolo.medusa.Gauge.lambda$setupBinding$6(Gauge.java:5515) at eu.hansolo.medusa.Gauge$$Lambda$398/0x000000080039ec40.call(Unknown Source) at javafx.beans.binding.Bindings$1.computeValue(javafx.base/Bindings.java:157) at javafx.beans.binding.BooleanBinding.get(javafx.base/BooleanBinding.java:155) at javafx.beans.binding.BooleanExpression.getValue(javafx.base/BooleanExpression.java:55) at javafx.beans.binding.BooleanBinding.getValue(javafx.base/BooleanBinding.java:59) at com.sun.javafx.binding.ExpressionHelper$SingleChange.(javafx.base/ExpressionHelper.java:151) at com.sun.javafx.binding.ExpressionHelper.addListener(javafx.base/ExpressionHelper.java:68) at javafx.beans.binding.BooleanBinding.addListener(javafx.base/BooleanBinding.java:85) at eu.hansolo.medusa.Gauge.setupBinding(Gauge.java:5523) at eu.hansolo.medusa.Gauge.lambda$registerListeners$4(Gauge.java:875) at eu.hansolo.medusa.Gauge$$Lambda$128/0x00000008001ed840.changed(Unknown Source)

Here's my REPREX:

import eu.hansolo.medusa.*;

import javafx.application.Application; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.geometry.Insets; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.layout.*; import javafx.scene.paint.Color; import javafx.stage.Stage;

import java.util.ArrayList; import java.util.List;

public class App extends Application { private static final double SIZE = 400; private Marker marker; private Gauge gauge;

public static void main(String[] args) {
    launch(args);
}

@Override
public void start(Stage primaryStage) {
    primaryStage.setTitle("Gauges");

    StackPane stackPane = new StackPane();
    stackPane.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));

    stackPane.setPrefWidth(SIZE);
    stackPane.setMinWidth(SIZE);
    stackPane.setPrefHeight(SIZE);
    stackPane.setMinHeight(SIZE);

    List<Gauge> gauges = new ArrayList<>();
    for (int i = 0; i < 9; i++) {
        gauges.add(createGauge());
    }

    TilePane tilePane = new TilePane();

    Button btn = new Button("ADD");
    gauge = createGauge();

    btn.setOnAction(e -> {
        tilePane.getChildren().setAll(gauges);
    });

    stackPane.getChildren().addAll(tilePane, btn);

    Scene scene = new Scene(
            stackPane,
            SIZE, SIZE);

    primaryStage.setScene(scene);
    primaryStage.show();
    primaryStage.setAlwaysOnTop(true);
    primaryStage.setAlwaysOnTop(false);
    scene.getWindow().setOnCloseRequest(e -> {
        System.exit(0);
        e.consume();
    });
}

private Gauge createGauge() {
    Gauge gauge = GaugeBuilder.create()
            .prefSize(100, 100)
            .animated(false)
            .scaleDirection(Gauge.ScaleDirection.CLOCKWISE)
            .tickLabelsVisible(false)
            .startAngle(180)// Start angle of Scale (bottom -> 0, direction -> CCW)
            .angleRange(360)
            .minValue(0)
            .maxValue(9)
            .tickMarkColor(Color.WHITE)
            .markersVisible(true)
            .decimals(0)
            .minorTickMarksVisible(false)
            .majorTickMarkWidthFactor(3)
            .needleColor(Color.TRANSPARENT)
            .knobVisible(false)
            .valueVisible(false)
            .tickMarkSectionsVisible(false)
            .build();
    marker = new Marker(0.0, "", Color.RED, Marker.MarkerType.STANDARD);

    gauge.getMarkers().add(0, marker);

    return gauge;
}

}

I found not including the mediumTicks helped and animated(false), I don't need either of these.

The other thing I found was that calling marker.valueProperty().bind(SOME_OBSERVABLE_PROPERTY); didn't update the markers value. I found I had to do the following to see the marker change position

gauge.getMarkers().clear(); marker.setValue(dec.get()); gauge.getMarkers().add(0, marker);

I'm sure the marker isn't intended to be used to show progress around the gauge so if there's another way to customise the needle to look like a coloured tick please let me know?

Thanks for any help and thanks again for the library.

Chris