Last active
January 26, 2024 14:54
-
-
Save jewelsea/5094893 to your computer and use it in GitHub Desktop.
Colors the the bars in a JavaFX BarChart depending upon the value of each bar's data.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* file: colored-chart.css | |
* Place in same directory as DynamicallyColoredBarChartWithLabel.java | |
* Ensure that the build system copies this file to the build output directory. | |
*/ | |
.root { | |
-fx-not-achieved: red; | |
-fx-achieved: green; | |
-fx-exceeded: blue; | |
} | |
.default-color0.chart-bar { -fx-bar-fill: -fx-not-achieved; } | |
.default-color1.chart-bar { -fx-bar-fill: -fx-achieved; } | |
.default-color2.chart-bar { -fx-bar-fill: -fx-exceeded; } | |
.level-legend { | |
-fx-padding: 10; | |
-fx-border-width: 2; | |
-fx-background-color: rgba(211, 211, 211, 0.5); | |
-fx-border-color: derive(rgba(211, 211, 211, 0.7), 10%); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javafx.application.Application; | |
import javafx.beans.value.*; | |
import javafx.geometry.Bounds; | |
import javafx.geometry.Pos; | |
import javafx.scene.*; | |
import javafx.scene.chart.*; | |
import javafx.scene.control.Label; | |
import javafx.scene.layout.*; | |
import javafx.scene.paint.Color; | |
import javafx.scene.shape.*; | |
import javafx.scene.text.Text; | |
import javafx.stage.Stage; | |
/** | |
* Displays a bar with a single series whose bars are different colors depending upon the bar value. | |
* A custom legend is created and displayed for the bar data. | |
* Bars in the chart are customized to include a text label of the bar's data value above the bar. | |
*/ | |
public class DynamicallyColoredBarChartWithLabel extends Application { | |
@Override public void start(Stage stage) { | |
final CategoryAxis xAxis = new CategoryAxis(); | |
xAxis.setLabel("Bars"); | |
final NumberAxis yAxis = new NumberAxis(); | |
yAxis.setLabel("Value"); | |
final BarChart<String, Number> bc = new BarChart<>(xAxis, yAxis); | |
bc.setLegendVisible(false); | |
XYChart.Series series1 = new XYChart.Series(); | |
for (int i = 0; i < 10; i++) { | |
final XYChart.Data<String, Number> data = new XYChart.Data("Value " + i, i); | |
data.nodeProperty().addListener(new ChangeListener<Node>() { | |
@Override public void changed(ObservableValue<? extends Node> ov, Node oldNode, final Node node) { | |
if (node != null) { | |
setNodeStyle(data); | |
displayLabelForData(data); | |
} | |
} | |
}); | |
series1.getData().add(data); | |
} | |
bc.getData().add(series1); | |
LevelLegend legend = new LevelLegend(); | |
legend.setAlignment(Pos.CENTER); | |
VBox chartWithLegend = new VBox(); | |
chartWithLegend.getChildren().setAll(bc, legend); | |
VBox.setVgrow(bc, Priority.ALWAYS); | |
chartWithLegend.getStylesheets().add(getClass().getResource("colored-chart.css").toExternalForm()); | |
stage.setScene(new Scene(chartWithLegend)); | |
stage.setMinHeight(400); | |
stage.setMinWidth(400); | |
stage.show(); | |
} | |
/** Change color of bar if value of i is <5 then red, if >5 then green if i>8 then blue */ | |
private void setNodeStyle(XYChart.Data<String, Number> data) { | |
Node node = data.getNode(); | |
if (data.getYValue().intValue() > 8) { | |
node.setStyle("-fx-bar-fill: -fx-exceeded;"); | |
} else if (data.getYValue().intValue() > 5) { | |
node.setStyle("-fx-bar-fill: -fx-achieved;"); | |
} else { | |
node.setStyle("-fx-bar-fill: -fx-not-achieved;"); | |
} | |
} | |
/** places a text label with a bar's value above a bar node for a given XYChart.Data */ | |
private void displayLabelForData(XYChart.Data<String, Number> data) { | |
final Node node = data.getNode(); | |
final Text dataText = new Text(data.getYValue() + ""); | |
node.parentProperty().addListener(new ChangeListener<Parent>() { | |
@Override public void changed(ObservableValue<? extends Parent> ov, Parent oldParent, Parent parent) { | |
Group parentGroup = (Group) parent; | |
parentGroup.getChildren().add(dataText); | |
} | |
}); | |
node.boundsInParentProperty().addListener(new ChangeListener<Bounds>() { | |
@Override public void changed(ObservableValue<? extends Bounds> ov, Bounds oldBounds, Bounds bounds) { | |
dataText.setLayoutX( | |
Math.round( | |
bounds.getMinX() + bounds.getWidth() / 2 - dataText.prefWidth(-1) / 2 | |
) | |
); | |
dataText.setLayoutY( | |
Math.round( | |
bounds.getMinY() - dataText.prefHeight(-1) * 0.5 | |
) | |
); | |
} | |
}); | |
} | |
/** A simple custom legend for a three valued chart. */ | |
class LevelLegend extends GridPane { | |
LevelLegend() { | |
setHgap(10); | |
setVgap(10); | |
addRow(0, createSymbol("-fx-exceeded"), new Label("Exceeded")); | |
addRow(1, createSymbol("-fx-achieved"), new Label("Achieved")); | |
addRow(2, createSymbol("-fx-not-achieved"), new Label("Not Achieved")); | |
getStyleClass().add("level-legend"); | |
} | |
/** Create a custom symbol for a custom chart legend with the given fillStyle style string. */ | |
private Node createSymbol(String fillStyle) { | |
Shape symbol = new Ellipse(10, 5, 10, 5); | |
symbol.setStyle("-fx-fill: " + fillStyle); | |
symbol.setStroke(Color.BLACK); | |
symbol.setStrokeWidth(2); | |
return symbol; | |
} | |
} | |
public static void main(String[] args) { launch(args); } | |
} |
@jewelsea Hi, thank you very much for this example!
I'm experiencing the same issues as @danieldacorso, @jeremyhalin and @yasineryigit. Would be grateful for any help on how to make this code snippet better.
Till now I've investigated that the content of the BarChart clipped with Region(as shown on the attached video). But I don't know how to change this clipping behavior. Adding padding to the top of the chart didn't help.
Video
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi, I'm having same issue. Did you find a solution for it?