JFXtras / jfxtras-styles

Java, JavaFX themes or look and feels. Currently contains JMetro theme.
https://pixelduke.com/java-javafx-theme-jmetro/
639 stars 144 forks source link

Charts lose tick label color after applying jmetro #225

Closed runiter closed 1 year ago

runiter commented 1 year ago

In moderna I can change tick label color of charts via xAxis.lookup(".axis").setStyle("-fx-tick-label-fill: red"); but this method has no effect in jmetro.

In moderna I get this (correct): image

In jmetro the axis labels appear as black (incorrect): image

Here's the code:

public class Test extends Application {

    public void start(Stage stage) {
        NumberAxis xAxis = new NumberAxis();
        NumberAxis yAxis = new NumberAxis();
        LineChart<Number,Number> chart = new LineChart<>(xAxis, yAxis);
        Scene scene  = new Scene(chart,500,200);
        stage.setScene(scene);
        JMetro jMetro = new JMetro(Style.LIGHT);
        jMetro.setScene(scene);
        stage.show();
        xAxis.lookup(".axis").setStyle("-fx-tick-label-fill: red");
    }

    public static void main(String[] args) {
        launch(args);
    }
}
dukke commented 1 year ago

Hi @runiter, that's by design.

If you check out the JMetro documentation you'll see that unfortunately due to limitations in Javafx user agent stylesheets, JMetro will trump any rules set in code.

Set those rules in a stylesheet, that way JMetro rules will be overridden.

Thanks!

runiter commented 1 year ago

Hi @dukke The problem is that I don't know ahead of time what the color of various css items will be. The user will select the color from UI. So I cannot use stylesheet for these settings. Do you have any other suggestions for changing css color at runtime?

dukke commented 1 year ago

Use setStyle(...) method of Node, that will trump any rules set in stylesheets.

runiter commented 1 year ago

I do already use setStyle() as a workaround but the problem is that as soon as the chart is resized the color is reverted back to default. The reason for that is that resizing causes the LineChart to recreate its labels so they lose the style. I compensate for it by resetting the style as soon as I detect a resize but that causes flickering as label colors keeps switching back and forth between default color and style color. It only stops when I stop resizing. It's not a pleasant UI experience. Is there any other workaround?

dukke commented 1 year ago

That's weird, it seems like a javafx bug. If you set an inline style, via setStyle, that should trump any rules either set through code or in a stylesheet. Also, that inline style should be respected even when the layout is updated via a window resize or whatever... Unless there's something that resets that style in your code?

Another option I can think of would be to have various styleclasses in your Scene stylsheet, each setting a different color that the user can choose from. Then you add/remove a styleclass depending on which color the user chose. Wouldn't be ideal as you would have to have a styleclass per color and the user wouldn't be able to select an arbitrary color.

runiter commented 1 year ago

I don't think it's a bug because javafx charts were not designed to have their tickLabels modified directly. That's because when chart is resized, there may not be enough room for display the initial tickLabels so they have to be recalculated and recreated.

Even without jmetro I get flickering effect. Here's the code in case you like to try it:

public class Test extends Application {

    public void start(Stage stage) {
        NumberAxis xAxis = new NumberAxis();
        NumberAxis yAxis = new NumberAxis();
        LineChart<Number,Number> chart = new LineChart<>(xAxis, yAxis);
        Scene scene  = new Scene(chart,500,200);
        stage.setScene(scene);
        stage.show();
//      xAxis.lookup(".axis").setStyle("-fx-tick-label-fill: red");

        scene.widthProperty().addListener((s, o, v) -> {
            Set<Node> nodes = xAxis.lookupAll(".axis Text");
            System.out.println(nodes.iterator().next().hashCode()); // print hashcode of first axis number
            nodes.forEach(n -> n.setStyle("-fx-fill: red;"));
        });
    }

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

Your solution may work but as you said it's not ideal. I found another workaround that is also not ideal but a little better. I need to create a subclass of javafx LineChart and override the methods so that I apply the style immediately after axis labels are created.

It works for me fine, but these issues could discourage other people from using jmetro. I'm going to file a ticket with JDK to ask them to provide the necessary API for user-agent solution. Hope they will listen.