google-site-verification: google61fe8ba583a51912.html
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