javafxpie-chart

JavaFX PieChart Legend Color change


I need to change color of circles in PieChart Legend. I don't know how to get to this property of PieChart. For example I'm able to change color of text in label Legend and I think this is close to the solution.

It shows what I want to change:

enter image description here

@FXML
public PieChart chart;
public ObservableList<PieChart.Data> pieChartData = FXCollections.observableArrayList();

public void chartLoad() { 

    pieChartData.clear();
    List<String> colorList = new ArrayList<>();
    for(int i = 0; i < categoryList.getSize(); i++) {
        if(categoryList.getByIndex(i).getValue() > 0) {
            PieChart.Data data = new PieChart.Data(categoryList.getByIndex(i).getName(), 
                categoryList.getByIndex(i).getValue());
            pieChartData.add(data);
            data.getNode().setStyle("-fx-pie-color: " + 
                categoryList.getByIndex(i).getColor().getName());
            colorList.add(categoryList.getByIndex(i).getColor().getName());
        }
    }

    Set<Node> items = chart.lookupAll("Label.chart-legend-item");
    int i = 0;
    for(Node item : items) {
        Label label = (Label) item;
        label.setText("sampleText");
        label.setStyle("-fx-text-fill: " + colorList.get(i)); 
        System.out.println(label.getChildrenUnmodifiable().toString());
        i++;
    }
    chart.setData(pieChartData);
}

Thank you for your future comments and answers.


Solution

  • Dynamically allocating colors to charts is a bit of a pain. If you have a fixed set of colors, without a predefined mapping from your data to the colors, you can just use an external style sheet, but doing anything else needs (as far as I know) a bit of a hack.

    The default modena.css style sheet defines eight constant colors, CHART_COLOR_1 to CHART_COLOR_8. Nodes in a pie chart, including both the "pie slices" and the color swatches in the legend, are assigned a style class from the eight classes default-color0 to default-color7. Each of these style classes by default has -fx-pie-color set to one of the constants. Unfortunately, if the data in the pie chart are changed, these mappings from default-colorx to CHART_COLOR_y change in a way that isn't documented.

    So the best approach for your scenario that I can find is:

    The last trap here is that you need to make sure the legend is added to the chart, and that CSS is applied to the chart, so that the lookup works.

    public void chartLoad() { 
    
        pieChartData.clear();
        List<String> colors = new ArrayList<>();
        for(int i = 0; i < categoryList.getSize(); i++) {
            if(categoryList.getByIndex(i).getValue() > 0) {
                PieChart.Data data = new PieChart.Data(categoryList.getByIndex(i).getName(), 
                    categoryList.getByIndex(i).getValue());
                pieChartData.add(data);
                colors.add(categoryList.getByIndex(i).getColor().getName());
            }
        }
    
        chart.setData(pieChartData);
        chart.requestLayout();
        chart.applyCSS();
    
        for (int i = 0 ; i < pieChartData.size() ; i++) {
            PieChart.Data d = pieChartData.get(i);
            String colorClass = "" ;
            for (String cls : d.getNode().getStyleClass()) {
                if (cls.startsWith("default-color")) {
                    colorClass = cls ;
                    break ;
                }
            }
            for (Node n : chart.lookupAll("."+colorClass)) {
                n.setStyle("-fx-pie-color: "+colors.get(i));
            }
        }
    
    }
    

    Here's a quick, complete demo of this approach:

    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
    
    import javafx.application.Application;
    import javafx.geometry.Insets;
    import javafx.geometry.Pos;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.chart.PieChart;
    import javafx.scene.control.Button;
    import javafx.scene.layout.BorderPane;
    import javafx.scene.layout.HBox;
    import javafx.scene.paint.Color;
    import javafx.stage.Stage;
    
    public class PieChartTest extends Application {
    
        private final Random rng = new Random();
    
        @Override
        public void start(Stage primaryStage) throws Exception {
            PieChart chart = new PieChart();
            Button button = new Button("Generate Data");
            button.setOnAction(e -> updateChart(chart));
            BorderPane root = new BorderPane(chart);
            HBox controls = new HBox(button);
            controls.setAlignment(Pos.CENTER);
            controls.setPadding(new Insets(5));
            root.setTop(controls);
    
            Scene scene = new Scene(root, 600, 600);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        private void updateChart(PieChart chart) {
            chart.getData().clear();
            int numValues = 4 + rng.nextInt(10);
    
            List<String> colors = new ArrayList<>();
            List<PieChart.Data> data = new ArrayList<>();
    
    
    
            for (int i = 0 ; i < numValues ; i++) {
                colors.add(getRandomColor());
                PieChart.Data d = new PieChart.Data("Item "+i, rng.nextDouble() * 100);
                data.add( d );
                chart.getData().add(d) ;
            }
    
            chart.requestLayout();
            chart.applyCss();
    
            for (int i = 0 ; i < data.size() ; i++) {
                String colorClass = "" ;
                for (String cls : data.get(i).getNode().getStyleClass()) {
                    if (cls.startsWith("default-color")) {
                        colorClass = cls ;
                        break ;
                    }
                }
                for (Node n : chart.lookupAll("."+colorClass)) {
                    n.setStyle("-fx-pie-color: "+colors.get(i));
                }
            }
        }
    
        private String getRandomColor() {
            Color color = Color.hsb(rng.nextDouble() * 360, 1, 1);
            int r = (int) (255 * color.getRed()) ;
            int g = (int) (255 * color.getGreen());
            int b = (int) (255 * color.getBlue()) ;
    
            return String.format("#%02x%02x%02x", r, g, b) ;
        }
    
    
        public static void main(String[] args) {
            Application.launch(args);
        }
    }
    

    This really is a bit of a hack, so better solutions are obviously welcome.