Skip to content

Instantly share code, notes, and snippets.

@jewelsea
Last active February 9, 2021 08:40
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jewelsea/2129306 to your computer and use it in GitHub Desktop.
Save jewelsea/2129306 to your computer and use it in GitHub Desktop.
DynamicLineChart.java
import javafx.application.Application;
import javafx.collections.*;
import javafx.geometry.Pos;
import javafx.scene.*;
import javafx.scene.chart.*;
import javafx.scene.chart.XYChart;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
import java.util.*;
// Demonstrates dynamically changing the data series assigned to a chart and applying css styles to the
// chart based on user selection and data series attributes.
public class DynamicLineChart extends Application {
private LineChart<Number, Number> lineChart;
private ObservableList<Event> events;
private Pane layout = new HBox();
@Override public void init() throws Exception {
//defining the axes
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("Attempt");
yAxis.setLabel("Distance (meters)");
xAxis.setMinorTickVisible(false);
xAxis.setAutoRanging(false);
xAxis.setLowerBound(1);
xAxis.setUpperBound(3);
xAxis.setTickUnit(1);
//creating the chart
lineChart = new LineChart<>(xAxis, yAxis);
lineChart.setAnimated(false);
lineChart.setTitle("Event Performance");
events = FXCollections.observableArrayList(
new Event("Javelin", "6 6", FXCollections.observableArrayList(
createSeries("Javelin - Tokyo", FXCollections.observableArrayList(18, 20, 22)),
createSeries("Javelin - Kyoto", FXCollections.observableArrayList(23, 14, 15))
)),
new Event("Hammer", "12 2 2 12", FXCollections.observableArrayList(
createSeries("Hammer - Tokyo", FXCollections.observableArrayList(12, 11, 5)),
createSeries("Hammer - Kyoto", FXCollections.observableArrayList(9, 8, 13))
)),
new Event("Shotput", "", FXCollections.observableArrayList(
createSeries("Shotput - Tokyo", FXCollections.observableArrayList(3, 2, 4)),
createSeries("Shotput - Kyoto", FXCollections.observableArrayList(4, 6, 5))
))
);
populateData(events, lineChart);
// create some controls which can toggle series display on and off.
final VBox eventChecks = new VBox(20);
eventChecks.setStyle("-fx-padding: 10;");
final TitledPane controlPane = new TitledPane("Event Selection", eventChecks);
controlPane.setCollapsible(false);
for (final Event event: events) {
final CheckBox box = new CheckBox(event.getName());
box.setSelected(true);
Line line = new Line(0, 10, 50, 10);
StringBuilder styleString = new StringBuilder("-fx-stroke-width: 3; -fx-stroke: gray;");
if (event.getStrokeDashArray() != null && !event.getStrokeDashArray().isEmpty()) {
styleString.append("-fx-stroke-dash-array: ").append(event.getStrokeDashArray()).append(";");
}
line.setStyle(styleString.toString());
box.setGraphic(line);
eventChecks.getChildren().add(box);
box.setOnAction(action -> {
event.setActive(box.isSelected());
populateData(events, lineChart);
styleSeries(events, lineChart);
}
);
}
Label caption = new Label(
"The chart displays performance an athelete in selected events at various sporting meets. "
+ "Events in which the athelete performed above average at a given meet are shown blue and "
+ "events at which the athelete performed below average are shown red. For a given meet, "
+ "the athelete may make three attempts per event. The red and blue highlighting is calculated "
+ "based on the average distance achieved for an event at a meet, not the longest distance achieved for the event at the meet. Select events to display from the controls on the left."
);
caption.setWrapText(true);
// layout the scene
HBox controlledChart = new HBox(10,
controlPane, lineChart
);
controlledChart.setAlignment(Pos.CENTER);
VBox captionedChart = new VBox(10,
controlledChart,
caption
);
captionedChart.setAlignment(Pos.CENTER);
HBox.setHgrow(lineChart, Priority.ALWAYS);
VBox.setVgrow(captionedChart.getChildren().get(0), Priority.ALWAYS);
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 10;");
layout.getChildren().addAll(captionedChart);
}
@Override public void start(Stage stage) {
stage.setTitle("Sports Day Results");
Scene scene = new Scene(layout, 800, 600);
stage.setScene(scene);
stage.show();
styleSeries(events, lineChart);
}
private void populateData(final ObservableList<Event> events, final LineChart<Number, Number> lineChart) {
lineChart.getData().clear();
for (Event event: events) {
if (event.isActive()) {
lineChart.getData().addAll(event.getSeries());
}
}
}
private void styleSeries(ObservableList<Event> events, final LineChart<Number, Number> lineChart) {
// force a css layout pass to ensure that subsequent lookup calls work.
lineChart.applyCss();
// mark different series with different depending on whether they are above or below average.
int nSeries = 0;
for (Event event : events) {
if (!event.isActive()) continue;
for (int j = 0; j < event.getSeries().size(); j++) {
XYChart.Series<Number, Number> series = event.getSeries().get(j);
Set<Node> nodes = lineChart.lookupAll(".series" + nSeries);
for (Node n : nodes) {
StringBuilder style = new StringBuilder();
if (event.isBelowAverage(series)) {
style.append("-fx-stroke: red; -fx-background-color: red, white; ");
} else {
style.append("-fx-stroke: blue; -fx-background-color: blue, white; ");
}
if (event.getStrokeDashArray() != null && !event.getStrokeDashArray().isEmpty()) {
style.append("-fx-stroke-dash-array: ").append(event.getStrokeDashArray()).append(";");
}
n.setStyle(style.toString());
}
nSeries++;
}
}
}
private XYChart.Series<Number, Number> createSeries(String name, List<Number> data) {
XYChart.Series<Number, Number> series = new XYChart.Series<>();
series.setName(name);
ObservableList<XYChart.Data<Number, Number>> seriesData = FXCollections.observableArrayList();
for (int i = 0; i < data.size(); i++) {
seriesData.add(new XYChart.Data<>(i+1, data.get(i)));
}
series.setData(seriesData);
return series;
}
private class Event {
private String name;
private ObservableList<XYChart.Series<Number, Number>> series;
private String strokeDashArray;
private boolean isActive = true;
public String getName() { return name; }
public String getStrokeDashArray() { return strokeDashArray; }
public Event(String name, String strokeDashArray, ObservableList<XYChart.Series<Number, Number>> series) {
this.name = name; this.strokeDashArray = strokeDashArray; this.series = series;
}
public boolean isBelowAverage(XYChart.Series<Number, Number> checkedSeries) {
double checkedSeriesAvg = calcSeriesAverage(checkedSeries);
double allSeriesAvgTot = 0;
double seriesCount = series.size();
for (XYChart.Series<Number, Number> curSeries: series) {
allSeriesAvgTot += calcSeriesAverage(curSeries);
}
double allSeriesAvg = seriesCount != 0 ? allSeriesAvgTot / seriesCount: 0;
return checkedSeriesAvg < allSeriesAvg;
}
public ObservableList<XYChart.Series<Number, Number>> getSeries() {
return series;
}
private double calcSeriesAverage(XYChart.Series<Number, Number> series) {
double sum = 0;
int count = series.getData().size();
for (XYChart.Data<Number, Number> data: series.getData()) {
sum += data.YValueProperty().get().doubleValue();
}
return count != 0 ? sum / count : 0;
}
private boolean isActive() {
return isActive;
}
private void setActive(boolean isActive) {
this.isActive = isActive;
}
}
public static void main(String[] args) {
launch(args);
}
}
@sirolf2009
Copy link

sirolf2009 commented Dec 21, 2018

I took this snippet out

lineChart.applyCss();
Set<Node> nodes = lineChart.lookupAll(".series" + 0);
for (Node n : nodes) {
	StringBuilder style = new StringBuilder();
	style.append("-fx-stroke: blue; -fx-background-color: blue, white; ");
	n.setStyle(style.toString());
}

and my line in my linechart did turn blue, my legend is still showing it as the default orange series though. In your example the legend did update with the chart. Can you point me to the relevant code to make that happen?

image

EDIT:
I found this thing, which does update my legend, but it seems to be missing from the example above, yet the example above works. So I'm still confused

lineChart.setStyle("CHART_COLOR_1: #0000FF ;");

@hamedgibago
Copy link

Is there any solution to change circles to triangle or cube like other charts? For example like in this component: https://support.softwarefx.com/Chart_FX_for_Java_65/article/8251000#!2501713

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment