JavaFX Charts Customization in Java

Introduction

JavaFX provides powerful charting capabilities out of the box, but real-world applications often require extensive customization for branding, improved UX, and specific business requirements. This guide covers comprehensive customization techniques for all JavaFX chart types.

Basic Chart Setup and Styling

Common Chart Base Class

public abstract class CustomChartBase {
protected static final String DEFAULT_CSS = """
.chart {
-fx-background-color: transparent;
-fx-padding: 20px;
}
.chart-plot-background {
-fx-background-color: transparent;
}
.chart-title {
-fx-font-size: 18px;
-fx-font-weight: bold;
-fx-text-fill: #2c3e50;
}
""";
protected void applyCommonStyles(Node node) {
if (node instanceof Parent parent) {
parent.getStylesheets().add(
getClass().getResource("/styles/charts.css").toExternalForm()
);
}
}
protected String createInlineStyle(String... styles) {
return String.join("; ", styles) + ";";
}
}

Line Chart Customization

Advanced Line Chart with Custom Features

public class CustomLineChart extends CustomChartBase {
private LineChart<String, Number> lineChart;
private XYChart.Series<String, Number> mainSeries;
private XYChart.Series<String, Number> trendSeries;
public CustomLineChart(String title, String xAxisLabel, String yAxisLabel) {
initializeChart(title, xAxisLabel, yAxisLabel);
setupInteractions();
applyCustomStyling();
}
private void initializeChart(String title, String xAxisLabel, String yAxisLabel) {
CategoryAxis xAxis = new CategoryAxis();
NumberAxis yAxis = new NumberAxis();
xAxis.setLabel(xAxisLabel);
yAxis.setLabel(yAxisLabel);
lineChart = new LineChart<>(xAxis, yAxis);
lineChart.setTitle(title);
lineChart.setAnimated(true);
lineChart.setLegendVisible(true);
lineChart.setCreateSymbols(true);
// Create main data series
mainSeries = new XYChart.Series<>();
mainSeries.setName("Actual Values");
// Create trend line series
trendSeries = new XYChart.Series<>();
trendSeries.setName("Trend Line");
lineChart.getData().addAll(mainSeries, trendSeries);
}
public void addDataPoint(String category, Number value) {
XYChart.Data<String, Number> dataPoint = new XYChart.Data<>(category, value);
mainSeries.getData().add(dataPoint);
// Add custom node to data point
dataPoint.setNode(createCustomDataPoint());
// Update trend line
updateTrendLine();
}
private Node createCustomDataPoint() {
Circle point = new Circle(4);
point.setFill(Color.web("#3498db"));
point.setStroke(Color.web("#2980b9"));
point.setStrokeWidth(1.5);
// Add hover effect
point.setOnMouseEntered(e -> {
point.setRadius(6);
point.setFill(Color.web("#e74c3c"));
showTooltip(point, e.getSceneX(), e.getSceneY());
});
point.setOnMouseExited(e -> {
point.setRadius(4);
point.setFill(Color.web("#3498db"));
hideTooltip();
});
return point;
}
private void updateTrendLine() {
List<XYChart.Data<String, Number>> data = mainSeries.getData();
if (data.size() < 2) return;
trendSeries.getData().clear();
// Simple linear regression for trend line
double[] xValues = new double[data.size()];
double[] yValues = new double[data.size()];
for (int i = 0; i < data.size(); i++) {
xValues[i] = i;
yValues[i] = data.get(i).getYValue().doubleValue();
}
LinearRegression regression = new LinearRegression(xValues, yValues);
for (int i = 0; i < data.size(); i++) {
double trendValue = regression.predict(i);
trendSeries.getData().add(new XYChart.Data<>(
data.get(i).getXValue(), trendValue
));
}
}
public void applyGradientStroke() {
// Apply gradient to line
mainSeries.getNode().lookup(".chart-series-line")
.setStyle("-fx-stroke: linear-gradient(from 0% 0% to 100% 0%, #3498db 0%, #e74c3c 100%);");
}
public void setLineStyle(LineStyle style) {
String cssStyle = switch (style) {
case DASHED -> "-fx-stroke-dash-array: 10 5;";
case DOTTED -> "-fx-stroke-dash-array: 2 5;";
case SOLID -> "-fx-stroke-dash-array: none;";
};
mainSeries.getNode().lookup(".chart-series-line")
.setStyle(cssStyle + " -fx-stroke-width: 2.5;");
}
public void addVerticalMarker(String category, String label) {
// Find the x position for the category
CategoryAxis xAxis = (CategoryAxis) lineChart.getXAxis();
double markerX = calculateCategoryPosition(category, xAxis);
Line marker = new Line(markerX, 0, markerX, lineChart.getHeight());
marker.setStroke(Color.RED);
marker.setStrokeWidth(1);
marker.getStrokeDashArray().addAll(5d, 5d);
marker.setOpacity(0.7);
// Add label
Text markerLabel = new Text(label);
markerLabel.setFill(Color.RED);
markerLabel.setFont(Font.font("Arial", FontWeight.BOLD, 12));
StackPane markerPane = new StackPane();
markerPane.getChildren().addAll(marker, markerLabel);
markerPane.setLayoutX(markerX - 20);
markerPane.setLayoutY(10);
((Pane) lineChart.lookup(".chart-plot-background")).getChildren().add(markerPane);
}
private double calculateCategoryPosition(String category, CategoryAxis axis) {
// Simplified calculation - in real implementation, 
// you'd need to access internal axis calculations
return 100 + (mainSeries.getData().indexOf(
mainSeries.getData().stream()
.filter(d -> d.getXValue().equals(category))
.findFirst()
.orElse(null)) * 50);
}
private void setupInteractions() {
// Zoom functionality
lineChart.setOnScroll(e -> {
if (e.isControlDown()) {
double zoomFactor = e.getDeltaY() > 0 ? 1.1 : 0.9;
NumberAxis yAxis = (NumberAxis) lineChart.getYAxis();
double lowerBound = yAxis.getLowerBound();
double upperBound = yAxis.getUpperBound();
double newLower = lowerBound * zoomFactor;
double newUpper = upperBound * zoomFactor;
yAxis.setLowerBound(newLower);
yAxis.setUpperBound(newUpper);
e.consume();
}
});
// Pan functionality
final double[] sceneX = new double[1];
final double[] sceneY = new double[1];
lineChart.setOnMousePressed(e -> {
sceneX[0] = e.getSceneX();
sceneY[0] = e.getSceneY();
});
lineChart.setOnMouseDragged(e -> {
if (e.isSecondaryButtonDown()) {
NumberAxis yAxis = (NumberAxis) lineChart.getYAxis();
double deltaY = (e.getSceneY() - sceneY[0]) / 50;
yAxis.setLowerBound(yAxis.getLowerBound() + deltaY);
yAxis.setUpperBound(yAxis.getUpperBound() + deltaY);
sceneY[0] = e.getSceneY();
}
});
}
private void applyCustomStyling() {
// Axis styling
lineChart.getXAxis().setStyle(
createInlineStyle(
"-fx-tick-label-fill: #7f8c8d",
"-fx-font-size: 11px",
"-fx-border-color: #bdc3c7 transparent transparent transparent"
)
);
lineChart.getYAxis().setStyle(
createInlineStyle(
"-fx-tick-label-fill: #7f8c8d",
"-fx-font-size: 11px",
"-fx-border-color: transparent #bdc3c7 transparent transparent"
)
);
// Chart styling
lineChart.setStyle(
createInlineStyle(
"-fx-background-color: #ecf0f1",
"-fx-border-color: #bdc3c7",
"-fx-border-radius: 5",
"-fx-background-radius: 5"
)
);
}
// Tooltip management
private Tooltip currentTooltip;
private void showTooltip(Node node, double x, double y) {
if (currentTooltip != null) {
currentTooltip.hide();
}
currentTooltip = new Tooltip("Value: " + 
((XYChart.Data<?, ?>) ((Circle) node).getUserData()).getYValue());
currentTooltip.show(node, x, y);
}
private void hideTooltip() {
if (currentTooltip != null) {
currentTooltip.hide();
currentTooltip = null;
}
}
public LineChart<String, Number> getChart() {
return lineChart;
}
public enum LineStyle {
SOLID, DASHED, DOTTED
}
// Linear regression helper class
private static class LinearRegression {
private final double intercept, slope;
public LinearRegression(double[] x, double[] y) {
double meanX = Arrays.stream(x).average().orElse(0);
double meanY = Arrays.stream(y).average().orElse(0);
double numerator = 0;
double denominator = 0;
for (int i = 0; i < x.length; i++) {
numerator += (x[i] - meanX) * (y[i] - meanY);
denominator += (x[i] - meanX) * (x[i] - meanX);
}
slope = numerator / denominator;
intercept = meanY - slope * meanX;
}
public double predict(double x) {
return slope * x + intercept;
}
}
}

Bar Chart Customization

Advanced Bar Chart with Animation and Effects

public class CustomBarChart extends CustomChartBase {
private BarChart<String, Number> barChart;
private Map<String, Color> categoryColors;
private boolean animated = true;
public CustomBarChart(String title, String xAxisLabel, String yAxisLabel) {
initializeChart(title, xAxisLabel, yAxisLabel);
setupColorScheme();
applyCustomStyling();
setupInteractions();
}
private void initializeChart(String title, String xAxisLabel, String yAxisLabel) {
CategoryAxis xAxis = new CategoryAxis();
NumberAxis yAxis = new NumberAxis();
xAxis.setLabel(xAxisLabel);
yAxis.setLabel(yAxisLabel);
barChart = new BarChart<>(xAxis, yAxis);
barChart.setTitle(title);
barChart.setLegendVisible(false);
barChart.setAnimated(animated);
barChart.setCategoryGap(20);
barChart.setBarGap(5);
}
public void addSeries(String seriesName, Map<String, Number> data) {
XYChart.Series<String, Number> series = new XYChart.Series<>();
series.setName(seriesName);
data.forEach((category, value) -> {
XYChart.Data<String, Number> dataPoint = new XYChart.Data<>(category, value);
series.getData().add(dataPoint);
// Apply custom styling when data is added
if (animated) {
applyDataPointAnimation(dataPoint);
}
});
barChart.getData().add(series);
applySeriesStyling(series);
}
private void applyDataPointAnimation(XYChart.Data<String, Number> dataPoint) {
dataPoint.nodeProperty().addListener((obs, oldNode, newNode) -> {
if (newNode != null) {
// Initial state (invisible)
newNode.setScaleY(0);
newNode.setTranslateY(barChart.getHeight());
// Animate to final state
ScaleTransition st = new ScaleTransition(Duration.millis(500), newNode);
st.setToY(1);
TranslateTransition tt = new TranslateTransition(Duration.millis(500), newNode);
tt.setToY(0);
ParallelTransition pt = new ParallelTransition(st, tt);
pt.play();
// Add hover effects
setupBarHoverEffects(newNode, dataPoint);
}
});
}
private void setupBarHoverEffects(Node bar, XYChart.Data<String, Number> dataPoint) {
String category = dataPoint.getXValue();
Color baseColor = categoryColors.getOrDefault(category, Color.STEELBLUE);
bar.setOnMouseEntered(e -> {
bar.setStyle(
createInlineStyle(
"-fx-background-color: derive(" + toHex(baseColor) + ", -20%);",
"-fx-background-radius: 3 3 0 0;",
"-fx-effect: dropshadow(gaussian, " + toHex(baseColor) + ", 10, 0.5, 0, 0);"
)
);
showValueLabel(bar, dataPoint.getYValue().doubleValue());
});
bar.setOnMouseExited(e -> {
bar.setStyle(
createInlineStyle(
"-fx-background-color: " + toHex(baseColor) + ";",
"-fx-background-radius: 3 3 0 0;",
"-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 5, 0, 0, 1);"
)
);
hideValueLabel();
});
}
private void showValueLabel(Node bar, double value) {
Text valueLabel = new Text(String.format("$%.2f", value));
valueLabel.setFill(Color.WHITE);
valueLabel.setFont(Font.font("Arial", FontWeight.BOLD, 12));
StackPane labelContainer = new StackPane(valueLabel);
labelContainer.setStyle(
createInlineStyle(
"-fx-background-color: rgba(0,0,0,0.8);",
"-fx-background-radius: 3;",
"-fx-padding: 5px;"
)
);
// Position above the bar
Bounds barBounds = bar.getBoundsInParent();
labelContainer.setLayoutX(barBounds.getMinX() + barBounds.getWidth() / 2 - 30);
labelContainer.setLayoutY(barBounds.getMinY() - 30);
bar.setUserData(labelContainer);
((Pane) bar.getParent()).getChildren().add(labelContainer);
}
private void hideValueLabel() {
// Implementation to remove value label
}
public void setGradientFill(boolean enabled) {
if (enabled) {
barChart.getData().forEach(series -> {
series.getData().forEach(data -> {
data.getNode().setStyle(
"-fx-background-color: linear-gradient(to bottom, #3498db, #2980b9);"
);
});
});
}
}
public void addDataLabels() {
barChart.getData().forEach(series -> {
series.getData().forEach(data -> {
data.nodeProperty().addListener((obs, oldNode, newNode) -> {
if (newNode != null) {
Text dataLabel = new Text(data.getYValue().toString());
dataLabel.setFill(Color.WHITE);
dataLabel.setFont(Font.font("Arial", FontWeight.BOLD, 10));
StackPane labelPane = new StackPane(dataLabel);
labelPane.setStyle(
createInlineStyle(
"-fx-background-color: rgba(0,0,0,0.7);",
"-fx-background-radius: 2;",
"-fx-padding: 2px 4px;"
)
);
// Position label at top of bar
Bounds bounds = newNode.getBoundsInParent();
labelPane.setLayoutX(bounds.getMinX() + bounds.getWidth() / 2 - 15);
labelPane.setLayoutY(bounds.getMinY() - 20);
((Pane) newNode.getParent()).getChildren().add(labelPane);
}
});
});
});
}
public void setStacked(boolean stacked) {
if (stacked) {
barChart.setCategoryGap(0);
barChart.setBarGap(0);
} else {
barChart.setCategoryGap(20);
barChart.setBarGap(5);
}
}
public void applyPatternFills() {
String[] patterns = {
"M0,0 H10 V10 H0 Z",  // Squares
"M0,0 L10,10 M10,0 L0,10",  // Crosshatch
"M0,5 H10 M5,0 V10"   // Grid
};
int patternIndex = 0;
for (XYChart.Series<String, Number> series : barChart.getData()) {
for (XYChart.Data<String, Number> data : series.getData()) {
if (data.getNode() != null) {
String patternStyle = String.format(
"-fx-background-color: pattern(linear, 0, 0, 10, 10, false, null) %s;",
patterns[patternIndex % patterns.length]
);
data.getNode().setStyle(patternStyle);
}
}
patternIndex++;
}
}
private void setupColorScheme() {
categoryColors = new HashMap<>();
String[] colors = {"#3498db", "#e74c3c", "#2ecc71", "#f39c12", "#9b59b6", "#1abc9c"};
// Assign colors to categories
CategoryAxis xAxis = (CategoryAxis) barChart.getXAxis();
// Note: This would need to be updated as categories are added
}
private void applySeriesStyling(XYChart.Series<String, Number> series) {
series.getNode().lookup(".chart-bar").setStyle(
createInlineStyle(
"-fx-background-radius: 3 3 0 0;",
"-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 5, 0, 0, 1);"
)
);
}
private void setupInteractions() {
// Context menu
barChart.setNodeOrientation(NodeOrientation.INHERIT);
barChart.setOnContextMenuRequested(e -> {
ContextMenu contextMenu = new ContextMenu();
MenuItem exportItem = new MenuItem("Export as PNG");
exportItem.setOnAction(ev -> exportAsPNG());
MenuItem toggleAnimation = new MenuItem("Toggle Animation");
toggleAnimation.setOnAction(ev -> {
animated = !animated;
barChart.setAnimated(animated);
});
contextMenu.getItems().addAll(exportItem, toggleAnimation);
contextMenu.show(barChart, e.getScreenX(), e.getScreenY());
});
}
private void exportAsPNG() {
WritableImage image = barChart.snapshot(new SnapshotParameters(), null);
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Save Chart as PNG");
fileChooser.getExtensionFilters().add(
new FileChooser.ExtensionFilter("PNG files", "*.png")
);
File file = fileChooser.showSaveDialog(barChart.getScene().getWindow());
if (file != null) {
try {
ImageIO.write(SwingFXUtils.fromFXImage(image, null), "png", file);
} catch (IOException e) {
e.printStackTrace();
}
}
}
private String toHex(Color color) {
return String.format("#%02x%02x%02x",
(int) (color.getRed() * 255),
(int) (color.getGreen() * 255),
(int) (color.getBlue() * 255));
}
public BarChart<String, Number> getChart() {
return barChart;
}
}

Pie Chart Customization

Interactive Pie Chart with Advanced Features

public class CustomPieChart extends CustomChartBase {
private PieChart pieChart;
private Map<String, Color> sliceColors;
private boolean showPercentage = true;
private boolean explodeOnHover = true;
public CustomPieChart(String title) {
initializeChart(title);
setupColorScheme();
applyCustomStyling();
}
private void initializeChart(String title) {
pieChart = new PieChart();
pieChart.setTitle(title);
pieChart.setLegendVisible(true);
pieChart.setLabelsVisible(true);
pieChart.setStartAngle(90); // Start from top
pieChart.setClockwise(true);
}
public void addData(String name, double value) {
PieChart.Data slice = new PieChart.Data(name, value);
pieChart.getData().add(slice);
// Apply custom styling
applySliceStyling(slice);
// Setup interactions
setupSliceInteractions(slice);
}
private void applySliceStyling(PieChart.Data slice) {
slice.getNode().setStyle(
createInlineStyle(
"-fx-background-color: " + getSliceColor(slice.getName()) + ";",
"-fx-border-color: white;",
"-fx-border-width: 2;"
)
);
// Custom label formatting
if (showPercentage) {
slice.setName(String.format("%s (%.1f%%)", 
slice.getName(), 
(slice.getPieValue() / getTotalValue()) * 100));
}
}
private void setupSliceInteractions(PieChart.Data slice) {
Node sliceNode = slice.getNode();
sliceNode.setOnMouseEntered(e -> {
if (explodeOnHover) {
// Create explode effect
ScaleTransition st = new ScaleTransition(Duration.millis(200), sliceNode);
st.setToX(1.1);
st.setToY(1.1);
st.play();
// Add glow effect
sliceNode.setEffect(new DropShadow(10, Color.BLACK));
}
// Highlight slice
sliceNode.setStyle(
createInlineStyle(
"-fx-background-color: derive(" + getSliceColor(slice.getName()) + ", 20%);",
"-fx-border-color: white;",
"-fx-border-width: 2;"
)
);
});
sliceNode.setOnMouseExited(e -> {
if (explodeOnHover) {
ScaleTransition st = new ScaleTransition(Duration.millis(200), sliceNode);
st.setToX(1.0);
st.setToY(1.0);
st.play();
sliceNode.setEffect(null);
}
// Restore original color
sliceNode.setStyle(
createInlineStyle(
"-fx-background-color: " + getSliceColor(slice.getName()) + ";",
"-fx-border-color: white;",
"-fx-border-width: 2;"
)
);
});
sliceNode.setOnMouseClicked(e -> {
if (e.getClickCount() == 2) {
// Double-click to explode slice
explodeSlice(slice);
}
});
}
private void explodeSlice(PieChart.Data slice) {
// Calculate explode distance
double angle = calculateSliceAngle(slice);
double distance = 20; // pixels
double translateX = Math.cos(Math.toRadians(angle)) * distance;
double translateY = Math.sin(Math.toRadians(angle)) * distance;
TranslateTransition tt = new TranslateTransition(Duration.millis(500), slice.getNode());
tt.setToX(translateX);
tt.setToY(translateY);
tt.play();
}
private double calculateSliceAngle(PieChart.Data slice) {
// Calculate the middle angle of the slice
double total = getTotalValue();
double startAngle = pieChart.getStartAngle();
// Find the slice's position in the data
int sliceIndex = pieChart.getData().indexOf(slice);
double cumulativeValue = 0;
for (int i = 0; i < sliceIndex; i++) {
cumulativeValue += pieChart.getData().get(i).getPieValue();
}
double sliceStartAngle = startAngle + (cumulativeValue / total) * 360;
double sliceMiddleAngle = sliceStartAngle + (slice.getPieValue() / total) * 360 / 2;
return sliceMiddleAngle;
}
public void setDonutMode(boolean enabled, double innerRadiusPercentage) {
if (enabled) {
pieChart.setStyle(
createInlineStyle(
"-fx-pie-label-visible: false;",
"-fx-border-color: transparent;"
)
);
// Create donut hole effect
Circle hole = new Circle(innerRadiusPercentage);
hole.setFill(Color.TRANSPARENT);
hole.setStroke(Color.WHITE);
hole.setStrokeWidth(2);
StackPane chartContainer = new StackPane(pieChart, hole);
// You would need to replace the chart in its parent container
}
}
public void applyGradientFills() {
for (PieChart.Data slice : pieChart.getData()) {
Color baseColor = getSliceColor(slice.getName());
String gradientStyle = String.format(
"-fx-background-color: linear-gradient(from 0%% 0%% to 100%% 100%%, " +
"derive(%s, 30%%) 0%%, %s 50%%, derive(%s, -30%%) 100%%);",
toHex(baseColor), toHex(baseColor), toHex(baseColor)
);
slice.getNode().setStyle(gradientStyle);
}
}
public void addPatternFills() {
String[] patterns = {
"M0,0 L10,10 M9,-1 L11,1 M-1,9 L1,11", // Diagonal lines
"M0,5 H10 M5,0 V10",                   // Grid
"M2.5,2.5 L7.5,7.5 M7.5,2.5 L2.5,7.5" // X pattern
};
int patternIndex = 0;
for (PieChart.Data slice : pieChart.getData()) {
String patternStyle = String.format(
"-fx-background-color: pattern(linear, 0, 0, 10, 10, false, null) %s;",
patterns[patternIndex % patterns.length]
);
slice.getNode().setStyle(patternStyle);
patternIndex++;
}
}
public void setLabelPosition(LabelPosition position) {
switch (position) {
case INSIDE -> {
pieChart.setLabelsVisible(true);
pieChart.setStyle("-fx-pie-label-visible: true;");
}
case OUTSIDE -> {
pieChart.setLabelsVisible(true);
// Custom implementation for outside labels would be needed
}
case NONE -> pieChart.setLabelsVisible(false);
}
}
public void addLeaderLines() {
// Custom implementation for leader lines
// This would require extending PieChart skin
}
private double getTotalValue() {
return pieChart.getData().stream()
.mapToDouble(PieChart.Data::getPieValue)
.sum();
}
private String getSliceColor(String sliceName) {
return sliceColors.containsKey(sliceName) ? 
toHex(sliceColors.get(sliceName)) : toHex(Color.LIGHTGRAY);
}
private void setupColorScheme() {
sliceColors = new HashMap<>();
Color[] palette = {
Color.web("#3498db"), Color.web("#e74c3c"), Color.web("#2ecc71"),
Color.web("#f39c12"), Color.web("#9b59b6"), Color.web("#1abc9c"),
Color.web("#34495e"), Color.web("#d35400"), Color.web("#c0392b")
};
// Assign colors to slices
for (int i = 0; i < pieChart.getData().size(); i++) {
if (i < palette.length) {
sliceColors.put(pieChart.getData().get(i).getName(), palette[i]);
}
}
}
private void applyCustomStyling() {
// Legend styling
pieChart.lookup(".chart-legend").setStyle(
createInlineStyle(
"-fx-background-color: transparent;",
"-fx-border-color: #bdc3c7;",
"-fx-border-width: 1;",
"-fx-border-radius: 3;"
)
);
// Title styling
pieChart.lookup(".chart-title").setStyle(
createInlineStyle(
"-fx-font-size: 16px;",
"-fx-font-weight: bold;",
"-fx-text-fill: #2c3e50;"
)
);
}
public PieChart getChart() {
return pieChart;
}
public enum LabelPosition {
INSIDE, OUTSIDE, NONE
}
}

Area Chart Customization

Advanced Area Chart with Gradient Fills

public class CustomAreaChart extends CustomChartBase {
private AreaChart<Number, Number> areaChart;
private Map<String, Color> seriesColors;
public CustomAreaChart(String title, String xAxisLabel, String yAxisLabel) {
initializeChart(title, xAxisLabel, yAxisLabel);
setupColorScheme();
applyCustomStyling();
}
private void initializeChart(String title, String xAxisLabel, String yAxisLabel) {
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
xAxis.setLabel(xAxisLabel);
yAxis.setLabel(yAxisLabel);
areaChart = new AreaChart<>(xAxis, yAxis);
areaChart.setTitle(title);
areaChart.setLegendVisible(true);
areaChart.setCreateSymbols(false);
areaChart.setAnimated(true);
}
public void addSeries(String seriesName, List<XYChart.Data<Number, Number>> data) {
XYChart.Series<Number, Number> series = new XYChart.Series<>();
series.setName(seriesName);
series.getData().addAll(data);
areaChart.getData().add(series);
applySeriesGradient(series);
}
private void applySeriesGradient(XYChart.Series<Number, Number> series) {
series.getNode().setStyle(
"-fx-background-color: linear-gradient(from 0% 0% to 0% 100%, " +
"rgba(52, 152, 219, 0.8) 0%, rgba(52, 152, 219, 0.3) 80%, transparent 100%);"
);
}
public void setStacked(boolean stacked) {
// Area charts are naturally stacked, but we can adjust appearance
if (stacked) {
areaChart.getData().forEach(series -> {
series.getNode().setStyle(
"-fx-background-color: linear-gradient(from 0% 0% to 0% 100%, " +
"rgba(52, 152, 219, 0.6) 0%, rgba(52, 152, 219, 0.2) 100%);"
);
});
}
}
public void addThresholdLine(double threshold, String label) {
// Create a horizontal line at the threshold value
Line thresholdLine = new Line();
thresholdLine.setStroke(Color.RED);
thresholdLine.setStrokeWidth(2);
thresholdLine.getStrokeDashArray().addAll(5d, 5d);
// Bind line to chart coordinates
thresholdLine.startXProperty().bind(
Bindings.createDoubleBinding(() -> 
getXAxis().getDisplayPosition(0), 
getXAxis().boundsInParentProperty()
)
);
thresholdLine.endXProperty().bind(
Bindings.createDoubleBinding(() -> 
getXAxis().getDisplayPosition(getXAxis().getUpperBound()), 
getXAxis().boundsInParentProperty()
)
);
thresholdLine.startYProperty().bind(
Bindings.createDoubleBinding(() -> 
getYAxis().getDisplayPosition(threshold), 
getYAxis().boundsInParentProperty()
)
);
thresholdLine.endYProperty().bind(thresholdLine.startYProperty());
// Add to chart
((Pane) areaChart.lookup(".chart-plot-background")).getChildren().add(thresholdLine);
// Add label
Text thresholdLabel = new Text(label);
thresholdLabel.setFill(Color.RED);
thresholdLabel.setFont(Font.font("Arial", FontWeight.BOLD, 12));
StackPane labelPane = new StackPane(thresholdLabel);
labelPane.setStyle(
createInlineStyle(
"-fx-background-color: rgba(255,255,255,0.9);",
"-fx-border-color: red;",
"-fx-border-width: 1;",
"-fx-padding: 2 5;"
)
);
// Position label
labelPane.layoutXProperty().bind(
Bindings.createDoubleBinding(() -> 
getXAxis().getDisplayPosition(getXAxis().getUpperBound()) - 50, 
getXAxis().boundsInParentProperty()
)
);
labelPane.layoutYProperty().bind(
Bindings.createDoubleBinding(() -> 
getYAxis().getDisplayPosition(threshold) - 20, 
getYAxis().boundsInParentProperty()
)
);
((Pane) areaChart.lookup(".chart-plot-background")).getChildren().add(labelPane);
}
private NumberAxis getXAxis() {
return (NumberAxis) areaChart.getXAxis();
}
private NumberAxis getYAxis() {
return (NumberAxis) areaChart.getYAxis();
}
private void setupColorScheme() {
seriesColors = new HashMap<>();
Color[] palette = {
Color.web("#3498db"), Color.web("#e74c3c"), Color.web("#2ecc71"),
Color.web("#f39c12"), Color.web("#9b59b6")
};
int colorIndex = 0;
for (XYChart.Series<Number, Number> series : areaChart.getData()) {
if (colorIndex < palette.length) {
seriesColors.put(series.getName(), palette[colorIndex]);
colorIndex++;
}
}
}
private void applyCustomStyling() {
// Chart background
areaChart.setStyle(
createInlineStyle(
"-fx-background-color: #f8f9fa;",
"-fx-border-color: #dee2e6;",
"-fx-border-width: 1;",
"-fx-background-radius: 5;",
"-fx-border-radius: 5;"
)
);
// Plot background
areaChart.lookup(".chart-plot-background").setStyle(
createInlineStyle(
"-fx-background-color: white;"
)
);
}
public AreaChart<Number, Number> getChart() {
return areaChart;
}
}

CSS Styling for Charts

Comprehensive CSS Stylesheet

/* charts.css */
.chart {
-fx-background-color: #ffffff;
-fx-padding: 20px;
-fx-background-radius: 8px;
-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.1), 10, 0, 0, 2);
}
.chart-title {
-fx-font-size: 18px;
-fx-font-weight: bold;
-fx-text-fill: #2c3e50;
-fx-padding: 0 0 10px 0;
}
.chart-plot-background {
-fx-background-color: transparent;
}
.chart-vertical-grid-lines {
-fx-stroke: #ecf0f1;
-fx-stroke-dash-array: none;
}
.chart-horizontal-grid-lines {
-fx-stroke: #ecf0f1;
-fx-stroke-dash-array: none;
}
.chart-legend {
-fx-background-color: transparent;
-fx-border-color: #bdc3c7;
-fx-border-width: 1;
-fx-border-radius: 4px;
-fx-padding: 8px;
}
.chart-legend-item {
-fx-text-fill: #34495e;
}
/* Axis styling */
.axis {
-fx-tick-label-fill: #7f8c8d;
-fx-tick-length: 5px;
-fx-minor-tick-length: 3px;
-fx-border-color: transparent;
}
.axis-label {
-fx-text-fill: #34495e;
-fx-font-weight: bold;
}
.axis-tick-mark {
-fx-stroke: #bdc3c7;
-fx-stroke-width: 1;
}
.axis-minor-tick-mark {
-fx-stroke: #ecf0f1;
-fx-stroke-width: 1;
}
/* Line Chart specific */
.chart-line-symbol {
-fx-background-color: #3498db, white;
-fx-background-insets: 0, 2;
-fx-background-radius: 5px;
-fx-padding: 5px;
}
.chart-series-line {
-fx-stroke: #3498db;
-fx-stroke-width: 2px;
}
/* Bar Chart specific */
.chart-bar {
-fx-background-color: #3498db;
-fx-background-radius: 3 3 0 0;
-fx-border-color: #2980b9;
-fx-border-width: 0 0 0 0;
}
.data0.chart-bar {
-fx-background-color: #3498db;
}
.data1.chart-bar {
-fx-background-color: #e74c3c;
}
.data2.chart-bar {
-fx-background-color: #2ecc71;
}
/* Pie Chart specific */
.chart-pie {
-fx-border-color: white;
-fx-border-width: 2px;
}
.chart-pie-label {
-fx-fill: white;
-fx-font-weight: bold;
-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 2, 0, 1, 1);
}
.default-color0.chart-pie { -fx-pie-color: #3498db; }
.default-color1.chart-pie { -fx-pie-color: #e74c3c; }
.default-color2.chart-pie { -fx-pie-color: #2ecc71; }
.default-color3.chart-pie { -fx-pie-color: #f39c12; }
.default-color4.chart-pie { -fx-pie-color: #9b59b6; }
/* Area Chart specific */
.chart-area-symbol {
-fx-background-color: #3498db, white;
-fx-background-insets: 0, 2;
-fx-background-radius: 4px;
-fx-padding: 4px;
}
.chart-series-area-line {
-fx-stroke: #3498db;
-fx-stroke-width: 2px;
}
.chart-series-area-fill {
-fx-fill: linear-gradient(from 0% 0% to 0% 100%, 
rgba(52, 152, 219, 0.8) 0%, 
rgba(52, 152, 219, 0.3) 80%, 
transparent 100%);
}
/* Tooltip styling */
.chart-tooltip {
-fx-background-color: rgba(0, 0, 0, 0.8);
-fx-text-fill: white;
-fx-background-radius: 4px;
-fx-padding: 8px 12px;
-fx-font-size: 12px;
}
/* Animation classes */
.chart-animated {
-fx-animated: true;
}
.chart-smooth {
-fx-create-symbols: false;
}
/* Responsive design */
@media -fx-platform: "mobile" {
.chart {
-fx-padding: 10px;
}
.chart-title {
-fx-font-size: 16px;
}
.axis {
-fx-tick-label-font-size: 10px;
}
}

Usage Example

Complete Demo Application

public class ChartCustomizationDemo extends Application {
@Override
public void start(Stage primaryStage) {
VBox root = new VBox(20);
root.setPadding(new Insets(20));
root.setStyle("-fx-background-color: #f4f6f7;");
// Create customized charts
CustomLineChart lineChart = createLineChart();
CustomBarChart barChart = createBarChart();
CustomPieChart pieChart = createPieChart();
CustomAreaChart areaChart = createAreaChart();
// Add to layout
root.getChildren().addAll(
createTitle("JavaFX Charts Customization Demo"),
lineChart.getChart(),
barChart.getChart(),
createChartsRow(pieChart.getChart(), areaChart.getChart())
);
Scene scene = new Scene(root, 1200, 1000);
scene.getStylesheets().add(getClass().getResource("/styles/charts.css").toExternalForm());
primaryStage.setTitle("JavaFX Charts Customization");
primaryStage.setScene(scene);
primaryStage.show();
}
private CustomLineChart createLineChart() {
CustomLineChart chart = new CustomLineChart(
"Sales Performance", "Months", "Revenue ($)"
);
// Add sample data
String[] months = {"Jan", "Feb", "Mar", "Apr", "May", "Jun"};
double[] values = {12000, 18000, 15000, 22000, 19000, 25000};
for (int i = 0; i < months.length; i++) {
chart.addDataPoint(months[i], values[i]);
}
// Apply customizations
chart.setLineStyle(CustomLineChart.LineStyle.SOLID);
chart.applyGradientStroke();
chart.addVerticalMarker("Mar", "Campaign Start");
return chart;
}
private CustomBarChart createBarChart() {
CustomBarChart chart = new CustomBarChart(
"Product Sales by Category", "Categories", "Units Sold"
);
// Add sample data
Map<String, Number> data = Map.of(
"Electronics", 1500,
"Clothing", 2200,
"Books", 800,
"Home & Garden", 1200,
"Sports", 900
);
chart.addSeries("Q1 Sales", data);
chart.addDataLabels();
chart.setGradientFill(true);
return chart;
}
private CustomPieChart createPieChart() {
CustomPieChart chart = new CustomPieChart("Market Share");
// Add sample data
chart.addData("Company A", 35.5);
chart.addData("Company B", 25.2);
chart.addData("Company C", 18.7);
chart.addData("Company D", 12.4);
chart.addData("Others", 8.2);
chart.setLabelPosition(CustomPieChart.LabelPosition.INSIDE);
chart.applyGradientFills();
return chart;
}
private CustomAreaChart createAreaChart() {
CustomAreaChart chart = new CustomAreaChart(
"Website Traffic", "Time", "Visitors"
);
// Add sample data
List<XYChart.Data<Number, Number>> series1 = Arrays.asList(
new XYChart.Data<>(1, 100), new XYChart.Data<>(2, 150),
new XYChart.Data<>(3, 200), new XYChart.Data<>(4, 180),
new XYChart.Data<>(5, 250), new XYChart.Data<>(6, 300)
);
List<XYChart.Data<Number, Number>> series2 = Arrays.asList(
new XYChart.Data<>(1, 50), new XYChart.Data<>(2, 120),
new XYChart.Data<>(3, 180), new XYChart.Data<>(4, 220),
new XYChart.Data<>(5, 280), new XYChart.Data<>(6, 350)
);
chart.addSeries("Organic Traffic", series1);
chart.addSeries("Paid Traffic", series2);
chart.addThresholdLine(200, "Target");
return chart;
}
private Label createTitle(String text) {
Label title = new Label(text);
title.setStyle("""
-fx-font-size: 24px;
-fx-font-weight: bold;
-fx-text-fill: #2c3e50;
-fx-padding: 0 0 10px 0;
""");
return title;
}
private HBox createChartsRow(Node... charts) {
HBox row = new HBox(20);
row.getChildren().addAll(charts);
return row;
}
public static void main(String[] args) {
launch(args);
}
}

Key Customization Features Covered

  1. Visual Styling: Colors, gradients, patterns, borders, shadows
  2. Interactivity: Hover effects, click handlers, tooltips
  3. Animations: Transitions, scale effects, smooth updates
  4. Advanced Features: Trend lines, threshold markers, data labels
  5. Responsive Design: Mobile-friendly adaptations
  6. Export Capabilities: PNG export functionality
  7. CSS Integration: Comprehensive styling through CSS
  8. Performance: Optimized rendering and updates

These customization techniques allow you to create professional, interactive, and visually appealing charts that meet modern UI/UX standards while maintaining the power and flexibility of JavaFX.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper