Charts: How to correlate colors between charts?

2019-04-11 12:52发布

Below is an example that

  • has two charts visualizing the same set of data
  • while the data are the same, their sequence differs (in rw code that would happen dynamically)

Goal:

  • show the same data with the same colors in both charts

By default, colors are applied by sequence of addition of the data. Can't find any api to change those colors - most probably I'm missing something obvious, what is it?

import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collector;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.PieChart;
import javafx.scene.chart.PieChart.Data;
import javafx.scene.chart.StackedBarChart;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class CorrelatedChartColors extends Application {

    @Override
    public void start(Stage primaryStage) {
        PieChart pieChart = new PieChart();
        pieChart.setData(getPieData());

        final CategoryAxis xAxis = new CategoryAxis();
        final NumberAxis yAxis = new NumberAxis();
        final StackedBarChart<String, Number> sbc =
                new StackedBarChart<>(xAxis, yAxis);
        ObservableList<Series<String, Number>> barData = createBarData(getPieData());
        // simulate client code that re-orders/filters the data
        FXCollections.shuffle(barData);
        sbc.setData(barData);

        primaryStage.setTitle("Correlated Charts");
        Scene scene = new Scene(new HBox(pieChart, sbc));
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * Creates and returns data for StackedBarChart from the given pieData.
     */
    @SuppressWarnings("unchecked")
    private ObservableList<Series<String, Number>> createBarData(
            ObservableList<Data> pieChartData) {
        ObservableList<Series<String, Number>> data = pieChartData.stream()
            .map(p -> new XYChart.Data<>("none", (Number) p.getPieValue(), p.getName())) 
            .map(xy -> new Series<>((String)xy.getExtraValue(), 
                        FXCollections.observableArrayList(xy)))
            .collect(toObservableList())
        ;
        return data;
    }

    /**
     * Creates and returns data for PieChart.
     */
    private ObservableList<Data> getPieData() {
        ObservableList<Data> pieData = FXCollections.observableArrayList();
        pieData.addAll(new PieChart.Data("java", 17.56), 
                new PieChart.Data("C", 17.06), 
                new PieChart.Data("PHP", 6.0),
                new PieChart.Data("(Visual)Basic", 4.76),
                new PieChart.Data("Other", 31.37));
        return pieData;
    }

    public static <T>  Collector<T, ?, ObservableList<T>> toObservableList() {
        return Collector.of((Supplier<ObservableList<T>>) FXCollections::observableArrayList,
                List::add,
                (left, right) -> {
                    left.addAll(right);
                    return left;
                });
    }

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

}

Screenshot:

enter image description here

2条回答
一纸荒年 Trace。
2楼-- · 2019-04-11 13:19

Got it working, here's the code:

public class CorrelatedChartColors extends Application {

@Override
public void start(Stage primaryStage) {
    PieChart pieChart = new PieChart();
    pieChart.setData(getPieData());

    final CategoryAxis xAxis = new CategoryAxis();
    final NumberAxis yAxis = new NumberAxis();
    final StackedBarChart<String, Number> sbc =
            new StackedBarChart<>(xAxis, yAxis);
    xAxis.setCategories(FXCollections.<String> observableArrayList(Arrays.asList("Programming Languages Usage in %")));

    XYChart.Series<String, Number> series1 = new XYChart.Series();
    XYChart.Series<String, Number> series2 = new XYChart.Series();
    XYChart.Series<String, Number> series3 = new XYChart.Series();
    XYChart.Series<String, Number> series4 = new XYChart.Series();
    XYChart.Series<String, Number> series5 = new XYChart.Series();

    series1.setName("Java");
    series2.setName("C");
    series3.setName("PHP");
    series4.setName("(Visual)Basic");
    series5.setName("Other");

    series1.getData().add(new XYChart.Data("Programming Languages Usage in %", 17.56));
    series2.getData().add(new XYChart.Data<>("Programming Languages Usage in %", 17.06));
    series3.getData().add(new XYChart.Data<>("Programming Languages Usage in %", 6.0));
    series4.getData().add(new XYChart.Data<>("Programming Languages Usage in %", 4.76));
    series5.getData().add(new XYChart.Data<>("Programming Languages Usage in %", 31.37));

    sbc.getData().addAll(series1, series2, series3, series4, series5);
    primaryStage.setTitle("Correlated Charts");
    Scene scene = new Scene(new HBox(pieChart, sbc));
    scene.getStylesheets().add(CorrelatedChartColors.class.getResource("chartStyles.css").toExternalForm());
    primaryStage.setScene(scene);
    primaryStage.show();
}

/**
 * Creates and returns data for StackedBarChart from the given pieData.
 */
@SuppressWarnings("unchecked")
private ObservableList<Series<String, Number>> createBarData(
        ObservableList<Data> pieChartData) {
    ObservableList<Series<String, Number>> data = pieChartData.stream()
        .map(p -> new XYChart.Data<>("none", (Number) p.getPieValue(), p.getName())) 
        .map(xy -> new Series<>((String)xy.getExtraValue(), 
                    FXCollections.observableArrayList(xy)))
        .collect(toObservableList())
    ;
    return data;
}

/**
 * Creates and returns data for PieChart.
 */
private ObservableList<Data> getPieData() {
    ObservableList<Data> pieData = FXCollections.observableArrayList();
    pieData.addAll(new PieChart.Data("java", 17.56), 
            new PieChart.Data("C", 17.06), 
            new PieChart.Data("PHP", 6.0),
            new PieChart.Data("(Visual)Basic", 4.76),
            new PieChart.Data("Other", 31.37));
    return pieData;
}

public static <T>  Collector<T, ?, ObservableList<T>> toObservableList() {
    return Collector.of((Supplier<ObservableList<T>>) FXCollections::observableArrayList,
            ObservableList::add,
            (left, right) -> {
                left.addAll(right);
                return left;
            });
}

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

and then the code for the CSS file:

.default-color3.chart-bar {
-fx-bar-fill: black;
}

.default-color0.chart-pie {
-fx-pie-color: black;
}

Now you can change the color of every bar in either one of the 2 charts. If you want to change the color of the top bar, simply add a new css entry

.default-color0.chart-bar {
-fx-bar-fill: green;
}
查看更多
Emotional °昔
3楼-- · 2019-04-11 13:31

I just changed some stuff in the start method (plus some imports I'm sure). I don't think this is the best way. I would make a stylesheet in user.dir and write my colors there with names like java-color etc., then use that for the default chart colors.

@Override
public void start(Stage primaryStage) {
    PieChart pieChart = new PieChart();
    pieChart.setData(getPieData());
    final HashMap<String, Integer> colors = new HashMap<>();
    pieChart.getData().stream().forEach((pd)->{
        colors.put(pd.getName(), pieChart.getData().indexOf(pd));
    });

    final CategoryAxis xAxis = new CategoryAxis(FXCollections.observableArrayList("none"));
    final NumberAxis yAxis = new NumberAxis();
    final StackedBarChart<String, Number> sbc =
            new StackedBarChart<>(xAxis, yAxis);
    ObservableList<Series<String, Number>> barData = createBarData(getPieData());
    // simulate client code that re-orders/filters the data
    FXCollections.shuffle(barData);
    sbc.setData(barData);

    primaryStage.setTitle("Correlated Charts");
    Scene scene = new Scene(new HBox(pieChart, sbc));
    primaryStage.setScene(scene);
    primaryStage.show();

    //can only get nodes after charts are drawn
    barData.stream().forEach((bd)->{
        int num = colors.get(bd.getName());
        //eg. chart-bar series1 data0 default-color1
        bd.getData().get(0).getNode().getStyleClass().setAll("chart-bar","series"+num,"data0","default-color"+num);
    });

    Legend legend = (Legend)sbc.lookup(".chart-legend");
    legend.getChildrenUnmodifiable().stream().forEach((l)->{
        Label label = (Label)l;
        Node n = label.getGraphic();
        int num = colors.get(label.getText());
        //eg. chart-legend-item-symbol chart-bar series1 bar-legend-symbol default-color1
        n.getStyleClass().setAll("chart-legend-item-symbol","chart-bar","series"+num,"bar-legend-symbol","default-color"+num);
    });
}

here's proof

查看更多
登录 后发表回答