Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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);
}
}
@jewelsea

This comment has been minimized.

Copy link
Owner Author

jewelsea commented Mar 20, 2012

@jewelsea

This comment has been minimized.

Copy link
Owner Author

jewelsea commented Mar 20, 2012

Modified to improve layout and add a delayed timeline based hack to the dynamic css styling to allow all elements on the graph to be correctly styled dynamically.

@jewelsea

This comment has been minimized.

Copy link
Owner Author

jewelsea commented Mar 13, 2014

Updated the solution to use Java 8 features and functions (which removes some of the hacks required to get dynamically looked up nodes).

If you need the older Java 7 version, see the code in a prior revision.

In general, Java 8 is preferred for this kind of work.

@sirolf2009

This comment has been minimized.

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 ;");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.