Overview
Real-time data visualization in JavaFX involves creating dynamic, interactive charts and dashboards that update in real-time. JavaFX provides powerful charting components and animation capabilities for building responsive data visualization applications.
1. Basic Real-Time Charts
Line Chart with Real-Time Updates
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class RealTimeLineChart extends Application {
private ObservableList<XYChart.Data<Number, Number>> data;
private Timeline animation;
private double xValue = 0;
private final int MAX_DATA_POINTS = 100;
@Override
public void start(Stage primaryStage) {
// Create axes
NumberAxis xAxis = new NumberAxis();
xAxis.setLabel("Time");
xAxis.setForceZeroInRange(false);
NumberAxis yAxis = new NumberAxis();
yAxis.setLabel("Value");
// Create line chart
LineChart<Number, Number> lineChart = new LineChart<>(xAxis, yAxis);
lineChart.setTitle("Real-Time Data Visualization");
lineChart.setAnimated(false); // Disable animation for better performance
lineChart.setCreateSymbols(false); // Hide symbols for cleaner look
// Create series
XYChart.Series<Number, Number> series = new XYChart.Series<>();
series.setName("Sensor Data");
data = FXCollections.observableArrayList();
series.setData(data);
lineChart.getData().add(series);
// Setup animation
setupAnimation();
BorderPane root = new BorderPane(lineChart);
Scene scene = new Scene(root, 800, 600);
primaryStage.setTitle("Real-Time Line Chart");
primaryStage.setScene(scene);
primaryStage.show();
// Start animation
animation.play();
}
private void setupAnimation() {
animation = new Timeline(new KeyFrame(Duration.millis(100), e -> {
// Generate new data point
double yValue = Math.sin(xValue * 0.1) * 50 + 50 + Math.random() * 10;
// Add new data point
data.add(new XYChart.Data<>(xValue, yValue));
// Remove old data points to prevent memory issues
if (data.size() > MAX_DATA_POINTS) {
data.remove(0);
}
xValue += 1;
}));
animation.setCycleCount(Animation.INDEFINITE);
}
public static void main(String[] args) {
launch(args);
}
}
Multiple Real-Time Series
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class MultipleRealTimeSeries extends Application {
private ObservableList<XYChart.Data<Number, Number>> series1Data;
private ObservableList<XYChart.Data<Number, Number>> series2Data;
private ObservableList<XYChart.Data<Number, Number>> series3Data;
private Timeline animation;
private double xValue = 0;
private final int MAX_DATA_POINTS = 200;
@Override
public void start(Stage primaryStage) {
// Create axes
NumberAxis xAxis = new NumberAxis();
xAxis.setLabel("Time");
xAxis.setForceZeroInRange(false);
NumberAxis yAxis = new NumberAxis();
yAxis.setLabel("Value");
// Create line chart
LineChart<Number, Number> lineChart = new LineChart<>(xAxis, yAxis);
lineChart.setTitle("Multiple Real-Time Series");
lineChart.setAnimated(false);
lineChart.setCreateSymbols(false);
// Create multiple series
XYChart.Series<Number, Number> series1 = createSeries("Temperature", "ff4444");
XYChart.Series<Number, Number> series2 = createSeries("Pressure", "44ff44");
XYChart.Series<Number, Number> series3 = createSeries("Humidity", "4444ff");
series1Data = FXCollections.observableArrayList();
series2Data = FXCollections.observableArrayList();
series3Data = FXCollections.observableArrayList();
series1.setData(series1Data);
series2.setData(series2Data);
series3.setData(series3Data);
lineChart.getData().addAll(series1, series2, series3);
// Setup animation
setupAnimation();
BorderPane root = new BorderPane(lineChart);
Scene scene = new Scene(root, 1000, 700);
primaryStage.setTitle("Multiple Real-Time Series");
primaryStage.setScene(scene);
primaryStage.show();
animation.play();
}
private XYChart.Series<Number, Number> createSeries(String name, String color) {
XYChart.Series<Number, Number> series = new XYChart.Series<>();
series.setName(name);
// Apply CSS for color (would need additional CSS file)
series.getNode().setStyle("-fx-stroke: #" + color + ";");
return series;
}
private void setupAnimation() {
animation = new Timeline(new KeyFrame(Duration.millis(50), e -> {
// Generate different patterns for each series
double temp = 20 + 10 * Math.sin(xValue * 0.05) + Math.random() * 2;
double pressure = 1000 + 50 * Math.cos(xValue * 0.03) + Math.random() * 5;
double humidity = 50 + 30 * Math.sin(xValue * 0.02) + Math.random() * 3;
// Add new data points
addDataPoint(series1Data, xValue, temp);
addDataPoint(series2Data, xValue, pressure);
addDataPoint(series3Data, xValue, humidity);
xValue += 0.5;
}));
animation.setCycleCount(Animation.INDEFINITE);
}
private void addDataPoint(ObservableList<XYChart.Data<Number, Number>> data,
double x, double y) {
data.add(new XYChart.Data<>(x, y));
// Remove old data points
if (data.size() > MAX_DATA_POINTS) {
data.remove(0);
}
}
public static void main(String[] args) {
launch(args);
}
}
2. Advanced Real-Time Dashboard
Comprehensive Dashboard with Multiple Charts
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.chart.*;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.text.SimpleDateFormat;
import java.util.Date;
public class RealTimeDashboard extends Application {
private Timeline animation;
private double timeCounter = 0;
// Data series
private ObservableList<XYChart.Data<Number, Number>> cpuData;
private ObservableList<XYChart.Data<Number, Number>> memoryData;
private ObservableList<XYChart.Data<Number, Number>> networkData;
private ObservableList<PieChart.Data> diskData;
// UI Components
private Label cpuLabel, memoryLabel, networkLabel, statusLabel;
private LineChart<Number, Number> cpuChart, memoryChart, networkChart;
private PieChart diskChart;
@Override
public void start(Stage primaryStage) {
// Create main layout
GridPane grid = new GridPane();
grid.setPadding(new Insets(10));
grid.setHgap(10);
grid.setVgap(10);
// Create charts
cpuChart = createLineChart("CPU Usage", "%");
memoryChart = createLineChart("Memory Usage", "MB");
networkChart = createLineChart("Network I/O", "KB/s");
diskChart = createPieChart("Disk Usage");
// Create status labels
VBox statusPanel = createStatusPanel();
// Add components to grid
grid.add(cpuChart, 0, 0);
grid.add(memoryChart, 1, 0);
grid.add(networkChart, 0, 1);
grid.add(diskChart, 1, 1);
grid.add(statusPanel, 0, 2, 2, 1);
// Initialize data
initializeData();
// Setup animation
setupAnimation();
Scene scene = new Scene(grid, 1200, 800);
scene.getStylesheets().add(getClass().getResource("/dashboard.css").toExternalForm());
primaryStage.setTitle("Real-Time System Monitoring Dashboard");
primaryStage.setScene(scene);
primaryStage.show();
animation.play();
}
private LineChart<Number, Number> createLineChart(String title, String yLabel) {
NumberAxis xAxis = new NumberAxis();
xAxis.setLabel("Time (s)");
xAxis.setForceZeroInRange(false);
xAxis.setAutoRanging(false);
xAxis.setLowerBound(0);
xAxis.setUpperBound(60);
xAxis.setTickUnit(10);
NumberAxis yAxis = new NumberAxis();
yAxis.setLabel(yLabel);
LineChart<Number, Number> chart = new LineChart<>(xAxis, yAxis);
chart.setTitle(title);
chart.setAnimated(false);
chart.setCreateSymbols(false);
chart.setLegendVisible(false);
return chart;
}
private PieChart createPieChart(String title) {
PieChart chart = new PieChart();
chart.setTitle(title);
chart.setLabelsVisible(true);
chart.setLegendVisible(false);
return chart;
}
private VBox createStatusPanel() {
VBox statusPanel = new VBox(10);
statusPanel.setPadding(new Insets(10));
statusPanel.setStyle("-fx-background-color: #f4f4f4; -fx-border-color: #cccccc; -fx-border-width: 1;");
Label title = new Label("System Status");
title.setStyle("-fx-font-size: 16px; -fx-font-weight: bold;");
cpuLabel = createStatusLabel("CPU: --%");
memoryLabel = createStatusLabel("Memory: --/-- MB");
networkLabel = createStatusLabel("Network: -- KB/s");
statusLabel = createStatusLabel("Last Update: --");
statusPanel.getChildren().addAll(title, cpuLabel, memoryLabel, networkLabel, statusLabel);
return statusPanel;
}
private Label createStatusLabel(String text) {
Label label = new Label(text);
label.setStyle("-fx-font-size: 14px;");
return label;
}
private void initializeData() {
// Initialize CPU chart
XYChart.Series<Number, Number> cpuSeries = new XYChart.Series<>();
cpuData = FXCollections.observableArrayList();
cpuSeries.setData(cpuData);
cpuChart.getData().add(cpuSeries);
// Initialize Memory chart
XYChart.Series<Number, Number> memorySeries = new XYChart.Series<>();
memoryData = FXCollections.observableArrayList();
memorySeries.setData(memoryData);
memoryChart.getData().add(memorySeries);
// Initialize Network chart
XYChart.Series<Number, Number> networkSeries = new XYChart.Series<>();
networkData = FXCollections.observableArrayList();
networkSeries.setData(networkData);
networkChart.getData().add(networkSeries);
// Initialize Disk chart
diskData = FXCollections.observableArrayList(
new PieChart.Data("Used", 40),
new PieChart.Data("Free", 60)
);
diskChart.setData(diskData);
// Style pie chart slices
stylePieChart();
}
private void stylePieChart() {
diskChart.getData().forEach(data -> {
String color = data.getName().equals("Used") ? "#ff4444" : "#44ff44";
data.getNode().setStyle("-fx-pie-color: " + color + ";");
});
}
private void setupAnimation() {
animation = new Timeline(new KeyFrame(Duration.millis(500), e -> {
updateCharts();
updateStatus();
}));
animation.setCycleCount(Animation.INDEFINITE);
}
private void updateCharts() {
// Simulate system metrics
double cpuUsage = 20 + 30 * Math.sin(timeCounter * 0.1) + Math.random() * 10;
double memoryUsage = 800 + 200 * Math.cos(timeCounter * 0.05) + Math.random() * 50;
double networkUsage = 100 + 80 * Math.sin(timeCounter * 0.2) + Math.random() * 20;
// Update line charts
updateLineChart(cpuChart, cpuData, cpuUsage);
updateLineChart(memoryChart, memoryData, memoryUsage);
updateLineChart(networkChart, networkData, networkUsage);
// Update pie chart with some variation
double usedSpace = 40 + 10 * Math.sin(timeCounter * 0.05);
double freeSpace = 100 - usedSpace;
diskData.get(0).setPieValue(usedSpace);
diskData.get(1).setPieValue(freeSpace);
timeCounter += 0.5;
}
private void updateLineChart(LineChart<Number, Number> chart,
ObservableList<XYChart.Data<Number, Number>> data,
double value) {
data.add(new XYChart.Data<>(timeCounter, value));
// Remove old data points
if (data.size() > 120) { // 60 seconds at 0.5s intervals
data.remove(0);
}
// Update X-axis range
NumberAxis xAxis = (NumberAxis) chart.getXAxis();
xAxis.setLowerBound(Math.max(0, timeCounter - 60));
xAxis.setUpperBound(timeCounter);
}
private void updateStatus() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
String timestamp = sdf.format(new Date());
if (!cpuData.isEmpty()) {
double latestCpu = cpuData.get(cpuData.size() - 1).getYValue().doubleValue();
cpuLabel.setText(String.format("CPU: %.1f%%", latestCpu));
}
if (!memoryData.isEmpty()) {
double latestMemory = memoryData.get(memoryData.size() - 1).getYValue().doubleValue();
memoryLabel.setText(String.format("Memory: %.0f MB", latestMemory));
}
if (!networkData.isEmpty()) {
double latestNetwork = networkData.get(networkData.size() - 1).getYValue().doubleValue();
networkLabel.setText(String.format("Network: %.1f KB/s", latestNetwork));
}
statusLabel.setText("Last Update: " + timestamp);
}
public static void main(String[] args) {
launch(args);
}
}
3. Stock Market Real-Time Visualization
Real-Time Stock Ticker
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.text.DecimalFormat;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Random;
public class StockMarketVisualization extends Application {
private Timeline animation;
private ObservableList<XYChart.Data<String, Number>> stockData;
private ObservableList<XYChart.Data<String, Number>> volumeData;
private double lastPrice = 100.0; // Initial price
private Random random = new Random();
private DecimalFormat priceFormat = new DecimalFormat("$#0.00");
private DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("HH:mm:ss");
@Override
public void start(Stage primaryStage) {
// Create main layout
VBox root = new VBox(10);
root.setPadding(new Insets(10));
// Create header with stock info
HBox header = createHeader();
// Create charts
LineChart<String, Number> priceChart = createPriceChart();
LineChart<String, Number> volumeChart = createVolumeChart();
root.getChildren().addAll(header, priceChart, volumeChart);
// Initialize data
initializeData(priceChart, volumeChart);
// Setup animation
setupAnimation();
Scene scene = new Scene(root, 1000, 700);
primaryStage.setTitle("Real-Time Stock Market Visualization");
primaryStage.setScene(scene);
primaryStage.show();
animation.play();
}
private HBox createHeader() {
HBox header = new HBox(20);
header.setPadding(new Insets(10));
header.setStyle("-fx-background-color: #2c3e50; -fx-border-color: #34495e; -fx-border-width: 2;");
Label stockLabel = new Label("AAPL - Apple Inc.");
stockLabel.setStyle("-fx-text-fill: white; -fx-font-size: 24px; -fx-font-weight: bold;");
Label priceLabel = new Label(priceFormat.format(lastPrice));
priceLabel.setStyle("-fx-text-fill: #2ecc71; -fx-font-size: 24px; -fx-font-weight: bold;");
priceLabel.setId("current-price");
Label changeLabel = new Label("+0.00 (0.00%)");
changeLabel.setStyle("-fx-text-fill: #2ecc71; -fx-font-size: 16px;");
changeLabel.setId("price-change");
header.getChildren().addAll(stockLabel, priceLabel, changeLabel);
return header;
}
private LineChart<String, Number> createPriceChart() {
CategoryAxis xAxis = new CategoryAxis();
xAxis.setLabel("Time");
xAxis.setAnimated(false);
NumberAxis yAxis = new NumberAxis();
yAxis.setLabel("Price ($)");
yAxis.setAutoRanging(true);
LineChart<String, Number> chart = new LineChart<>(xAxis, yAxis);
chart.setTitle("Stock Price");
chart.setAnimated(false);
chart.setCreateSymbols(false);
chart.setLegendVisible(false);
return chart;
}
private LineChart<String, Number> createVolumeChart() {
CategoryAxis xAxis = new CategoryAxis();
xAxis.setLabel("Time");
xAxis.setAnimated(false);
NumberAxis yAxis = new NumberAxis();
yAxis.setLabel("Volume");
yAxis.setAutoRanging(true);
LineChart<String, Number> chart = new LineChart<>(xAxis, yAxis);
chart.setTitle("Trading Volume");
chart.setAnimated(false);
chart.setCreateSymbols(false);
chart.setLegendVisible(false);
return chart;
}
private void initializeData(LineChart<String, Number> priceChart,
LineChart<String, Number> volumeChart) {
// Initialize price data
XYChart.Series<String, Number> priceSeries = new XYChart.Series<>();
stockData = FXCollections.observableArrayList();
priceSeries.setData(stockData);
priceChart.getData().add(priceSeries);
// Initialize volume data
XYChart.Series<String, Number> volumeSeries = new XYChart.Series<>();
volumeData = FXCollections.observableArrayList();
volumeSeries.setData(volumeData);
volumeChart.getData().add(volumeSeries);
// Add some initial data points
LocalTime time = LocalTime.now().minusSeconds(60);
for (int i = 0; i < 10; i++) {
String timeStr = time.plusSeconds(i * 6).format(timeFormat);
stockData.add(new XYChart.Data<>(timeStr, lastPrice));
volumeData.add(new XYChart.Data<>(timeStr, 1000000 + random.nextInt(500000)));
}
}
private void setupAnimation() {
animation = new Timeline(new KeyFrame(Duration.millis(1000), e -> {
updateStockData();
}));
animation.setCycleCount(Animation.INDEFINITE);
}
private void updateStockData() {
// Simulate stock price movement (geometric Brownian motion)
double volatility = 0.02;
double drift = 0.001;
double randomFactor = random.nextGaussian();
double change = lastPrice * (drift + volatility * randomFactor);
double newPrice = lastPrice + change;
// Ensure price doesn't go negative
newPrice = Math.max(newPrice, 0.1);
// Generate volume
int volume = 1000000 + random.nextInt(1000000);
// Get current time
String currentTime = LocalTime.now().format(timeFormat);
// Add new data points
stockData.add(new XYChart.Data<>(currentTime, newPrice));
volumeData.add(new XYChart.Data<>(currentTime, volume));
// Remove old data points (keep last 30 points)
if (stockData.size() > 30) {
stockData.remove(0);
volumeData.remove(0);
}
// Update header
updateHeader(newPrice, lastPrice);
lastPrice = newPrice;
}
private void updateHeader(double newPrice, double oldPrice) {
double change = newPrice - oldPrice;
double changePercent = (change / oldPrice) * 100;
// Find the price label and update it
Scene scene = ((Label) scene.lookup("#current-price")).getScene();
Label priceLabel = (Label) scene.lookup("#current-price");
Label changeLabel = (Label) scene.lookup("#price-change");
if (priceLabel != null) {
priceLabel.setText(priceFormat.format(newPrice));
// Set color based on price change
String color = change >= 0 ? "#2ecc71" : "#e74c3c";
priceLabel.setStyle("-fx-text-fill: " + color + "; -fx-font-size: 24px; -fx-font-weight: bold;");
}
if (changeLabel != null) {
String sign = change >= 0 ? "+" : "";
changeLabel.setText(String.format("%s%.2f (%.2f%%)", sign, change, changePercent));
changeLabel.setStyle("-fx-text-fill: " + color + "; -fx-font-size: 16px;");
}
}
public static void main(String[] args) {
launch(args);
}
}
4. Real-Time Data Streaming with WebSocket
WebSocket Data Source with JavaFX
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
import javax.websocket.*;
import java.io.IOException;
import java.net.URI;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
@ClientEndpoint
public class WebSocketRealTimeChart extends Application {
private Session session;
private BlockingQueue<Double> dataQueue = new LinkedBlockingQueue<>();
private ObservableList<XYChart.Data<Number, Number>> data;
private Timeline animation;
private double xValue = 0;
private Label connectionStatus;
private Button connectButton;
@Override
public void start(Stage primaryStage) {
// Create UI components
LineChart<Number, Number> chart = createChart();
HBox controlPanel = createControlPanel();
// Initialize data
initializeData(chart);
// Setup animation for processing queued data
setupAnimation();
BorderPane root = new BorderPane();
root.setTop(controlPanel);
root.setCenter(chart);
Scene scene = new Scene(root, 800, 600);
primaryStage.setTitle("WebSocket Real-Time Data Visualization");
primaryStage.setScene(scene);
primaryStage.show();
}
private LineChart<Number, Number> createChart() {
NumberAxis xAxis = new NumberAxis();
xAxis.setLabel("Time");
xAxis.setForceZeroInRange(false);
NumberAxis yAxis = new NumberAxis();
yAxis.setLabel("Value");
LineChart<Number, Number> chart = new LineChart<>(xAxis, yAxis);
chart.setTitle("Real-Time WebSocket Data");
chart.setAnimated(false);
chart.setCreateSymbols(false);
return chart;
}
private HBox createControlPanel() {
HBox controlPanel = new HBox(10);
controlPanel.setStyle("-fx-padding: 10; -fx-background-color: #f0f0f0;");
connectionStatus = new Label("Disconnected");
connectionStatus.setStyle("-fx-text-fill: red; -fx-font-weight: bold;");
connectButton = new Button("Connect");
connectButton.setOnAction(e -> toggleConnection());
Button clearButton = new Button("Clear");
clearButton.setOnAction(e -> clearData());
controlPanel.getChildren().addAll(connectButton, connectionStatus, clearButton);
return controlPanel;
}
private void initializeData(LineChart<Number, Number> chart) {
XYChart.Series<Number, Number> series = new XYChart.Series<>();
series.setName("Live Data");
data = FXCollections.observableArrayList();
series.setData(data);
chart.getData().add(series);
}
private void setupAnimation() {
animation = new Timeline(new KeyFrame(Duration.millis(100), e -> {
processQueuedData();
}));
animation.setCycleCount(Animation.INDEFINITE);
animation.play();
}
private void processQueuedData() {
// Process all available data in the queue
while (!dataQueue.isEmpty()) {
Double value = dataQueue.poll();
if (value != null) {
Platform.runLater(() -> {
data.add(new XYChart.Data<>(xValue, value));
xValue += 1;
// Keep only last 100 points
if (data.size() > 100) {
data.remove(0);
}
});
}
}
}
private void toggleConnection() {
if (session == null || !session.isOpen()) {
connectToWebSocket();
} else {
disconnectFromWebSocket();
}
}
private void connectToWebSocket() {
try {
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
session = container.connectToServer(this,
URI.create("ws://localhost:8080/websocket/data"));
Platform.runLater(() -> {
connectionStatus.setText("Connected");
connectionStatus.setStyle("-fx-text-fill: green; -fx-font-weight: bold;");
connectButton.setText("Disconnect");
});
} catch (Exception e) {
Platform.runLater(() -> {
connectionStatus.setText("Connection Failed");
connectionStatus.setStyle("-fx-text-fill: red; -fx-font-weight: bold;");
});
e.printStackTrace();
}
}
private void disconnectFromWebSocket() {
if (session != null && session.isOpen()) {
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Platform.runLater(() -> {
connectionStatus.setText("Disconnected");
connectionStatus.setStyle("-fx-text-fill: red; -fx-font-weight: bold;");
connectButton.setText("Connect");
});
session = null;
}
private void clearData() {
Platform.runLater(() -> {
data.clear();
xValue = 0;
});
}
@OnOpen
public void onOpen(Session session) {
System.out.println("WebSocket connected: " + session.getId());
}
@OnMessage
public void onMessage(String message) {
try {
// Parse the incoming message (assuming it's a number)
double value = Double.parseDouble(message);
dataQueue.offer(value);
} catch (NumberFormatException e) {
System.err.println("Invalid data format: " + message);
}
}
@OnClose
public void onClose(Session session, CloseReason closeReason) {
System.out.println("WebSocket closed: " + closeReason.getReasonPhrase());
Platform.runLater(() -> {
connectionStatus.setText("Disconnected");
connectionStatus.setStyle("-fx-text-fill: red; -fx-font-weight: bold;");
connectButton.setText("Connect");
});
}
@OnError
public void onError(Session session, Throwable throwable) {
System.err.println("WebSocket error: " + throwable.getMessage());
throwable.printStackTrace();
}
public static void main(String[] args) {
launch(args);
}
}
5. Performance Optimization Techniques
Efficient Real-Time Chart Updates
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.concurrent.atomic.AtomicLong;
public class OptimizedRealTimeChart extends Application {
private static final int MAX_POINTS = 1000;
private static final int UPDATE_INTERVAL_MS = 16; // ~60 FPS
private ObservableList<XYChart.Data<Number, Number>> data;
private AtomicLong dataPointsAdded = new AtomicLong(0);
private long startTime;
@Override
public void start(Stage primaryStage) {
// Create optimized chart
LineChart<Number, Number> chart = createOptimizedChart();
// Initialize data
initializeData(chart);
// Setup high-performance animation
setupHighPerformanceAnimation();
BorderPane root = new BorderPane(chart);
Scene scene = new Scene(root, 1000, 600);
primaryStage.setTitle("Optimized Real-Time Chart");
primaryStage.setScene(scene);
primaryStage.show();
startTime = System.currentTimeMillis();
}
private LineChart<Number, Number> createOptimizedChart() {
NumberAxis xAxis = new NumberAxis();
xAxis.setLabel("Time");
xAxis.setForceZeroInRange(false);
xAxis.setAutoRanging(false);
xAxis.setAnimated(false);
NumberAxis yAxis = new NumberAxis();
yAxis.setLabel("Value");
yAxis.setAutoRanging(true);
yAxis.setAnimated(false);
LineChart<Number, Number> chart = new LineChart<>(xAxis, yAxis);
chart.setTitle("High-Performance Real-Time Data");
chart.setAnimated(false); // Critical for performance
chart.setCreateSymbols(false); // Improves performance
chart.setLegendVisible(false);
return chart;
}
private void initializeData(LineChart<Number, Number> chart) {
XYChart.Series<Number, Number> series = new XYChart.Series<>();
data = FXCollections.observableArrayList();
series.setData(data);
chart.getData().add(series);
}
private void setupHighPerformanceAnimation() {
Timeline animation = new Timeline(new KeyFrame(
Duration.millis(UPDATE_INTERVAL_MS),
e -> updateData()
));
animation.setCycleCount(Animation.INDEFINITE);
animation.play();
}
private void updateData() {
long currentTime = System.currentTimeMillis() - startTime;
double value = generateDataValue(currentTime);
// Add new data point
data.add(new XYChart.Data<>(currentTime / 1000.0, value));
dataPointsAdded.incrementAndGet();
// Efficiently manage data size
if (data.size() > MAX_POINTS) {
// Remove in chunks for better performance
int pointsToRemove = data.size() - MAX_POINTS;
if (pointsToRemove > 0) {
data.remove(0, pointsToRemove);
}
}
// Update X-axis range to show recent data
if (!data.isEmpty()) {
double latestX = data.get(data.size() - 1).getXValue().doubleValue();
double windowSize = 60.0; // Show last 60 seconds
NumberAxis xAxis = (NumberAxis) ((LineChart<Number, Number>)
data.get(0).getChart()).getXAxis();
xAxis.setLowerBound(Math.max(0, latestX - windowSize));
xAxis.setUpperBound(latestX);
}
}
private double generateDataValue(double time) {
// Simulate multiple frequency components
double baseFreq = 0.1;
double highFreq = 1.0;
double noise = Math.random() * 2 - 1;
return 50 +
20 * Math.sin(time * 0.001 * baseFreq) +
10 * Math.sin(time * 0.001 * highFreq) +
noise;
}
// Method to get performance statistics
public void printPerformanceStats() {
long totalTime = System.currentTimeMillis() - startTime;
double pointsPerSecond = (dataPointsAdded.get() * 1000.0) / totalTime;
System.out.printf("Performance Stats:%n");
System.out.printf("Total data points: %d%n", dataPointsAdded.get());
System.out.printf("Total time: %.2f seconds%n", totalTime / 1000.0);
System.out.printf("Points per second: %.2f%n", pointsPerSecond);
System.out.printf("Current data size: %d%n", data.size());
}
public static void main(String[] args) {
launch(args);
}
}
Key Features Covered:
- Basic Real-Time Charts - Simple line charts with live updates
- Multiple Series - Handling multiple data streams
- Dashboard Layout - Comprehensive monitoring interface
- Stock Market Visualization - Financial data with real-time updates
- WebSocket Integration - Real-time data streaming
- Performance Optimization - Efficient data handling and rendering
These examples demonstrate how to create responsive, real-time data visualizations in JavaFX with proper performance considerations and modern UI patterns.