WebSockets & Real-Time Apps
Real-Time Data Visualization
Real-Time Data Visualization
Real-time data visualization transforms streaming data into dynamic, interactive visual representations. WebSockets enable live dashboards that update instantly without page refreshes, providing users with up-to-the-second insights.
Building a Live Dashboard
Let's create a real-time analytics dashboard that displays live metrics:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Live Dashboard</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
.dashboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
padding: 20px;
}
.metric-card {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.metric-value {
font-size: 2.5rem;
font-weight: bold;
color: #2563eb;
}
.metric-label {
color: #666;
font-size: 0.9rem;
}
</style>
</head>
<body>
<div class="dashboard">
<div class="metric-card">
<div class="metric-value" id="activeUsers">0</div>
<div class="metric-label">Active Users</div>
</div>
<div class="metric-card">
<div class="metric-value" id="requestsPerMin">0</div>
<div class="metric-label">Requests/Min</div>
</div>
<div class="metric-card">
<canvas id="trafficChart"></canvas>
</div>
</div>
<script>
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
console.log('Connected to analytics stream');
ws.send(JSON.stringify({ type: 'subscribe', channel: 'analytics' }));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
updateDashboard(data);
};
function updateDashboard(data) {
if (data.activeUsers) {
animateValue('activeUsers', data.activeUsers);
}
if (data.requestsPerMin) {
animateValue('requestsPerMin', data.requestsPerMin);
}
if (data.traffic) {
updateTrafficChart(data.traffic);
}
}
function animateValue(id, value) {
const element = document.getElementById(id);
const current = parseInt(element.textContent);
const increment = (value - current) / 20;
let step = 0;
const timer = setInterval(() => {
step++;
element.textContent = Math.round(current + (increment * step));
if (step >= 20) clearInterval(timer);
}, 50);
}
</script>
</body>
</html>
Streaming Chart Data with Chart.js
Chart.js integrates seamlessly with WebSockets for live charts:
// Initialize chart
const ctx = document.getElementById('trafficChart').getContext('2d');
const trafficChart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Traffic (req/s)',
data: [],
borderColor: 'rgb(37, 99, 235)',
backgroundColor: 'rgba(37, 99, 235, 0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: {
duration: 750
},
scales: {
x: {
display: true,
title: {
display: true,
text: 'Time'
}
},
y: {
display: true,
title: {
display: true,
text: 'Requests per Second'
},
beginAtZero: true
}
}
}
});
function updateTrafficChart(traffic) {
const now = new Date().toLocaleTimeString();
// Add new data point
trafficChart.data.labels.push(now);
trafficChart.data.datasets[0].data.push(traffic);
// Keep only last 20 points
if (trafficChart.data.labels.length > 20) {
trafficChart.data.labels.shift();
trafficChart.data.datasets[0].data.shift();
}
// Update chart
trafficChart.update('none'); // Disable animation for smooth updates
}
Performance Tip: Use
chart.update('none') to disable animations when updating frequently (more than once per second) to maintain smooth performance.
Server-Side Data Streaming
The backend generates and broadcasts real-time metrics:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
// Store subscribed clients
const analyticsSubscribers = new Set();
wss.on('connection', (ws) => {
console.log('Client connected');
ws.on('message', (message) => {
const data = JSON.parse(message);
if (data.type === 'subscribe' && data.channel === 'analytics') {
analyticsSubscribers.add(ws);
console.log('Client subscribed to analytics');
// Send initial data
ws.send(JSON.stringify({
activeUsers: getActiveUsers(),
requestsPerMin: getRequestsPerMin(),
traffic: getCurrentTraffic()
}));
}
});
ws.on('close', () => {
analyticsSubscribers.delete(ws);
console.log('Client disconnected');
});
});
// Broadcast analytics data every 2 seconds
setInterval(() => {
const analyticsData = {
activeUsers: getActiveUsers(),
requestsPerMin: getRequestsPerMin(),
traffic: getCurrentTraffic()
};
analyticsSubscribers.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(analyticsData));
}
});
}, 2000);
// Simulated metric functions
function getActiveUsers() {
return Math.floor(Math.random() * 1000) + 500;
}
function getRequestsPerMin() {
return Math.floor(Math.random() * 5000) + 2000;
}
function getCurrentTraffic() {
return Math.floor(Math.random() * 100) + 50;
}
Throttling Updates
Prevent overwhelming the UI with too many updates:
class DataThrottler {
constructor(callback, delay = 1000) {
this.callback = callback;
this.delay = delay;
this.lastUpdate = 0;
this.pendingData = null;
this.timer = null;
}
update(data) {
this.pendingData = data;
const now = Date.now();
if (now - this.lastUpdate >= this.delay) {
this.flush();
} else if (!this.timer) {
this.timer = setTimeout(() => {
this.flush();
}, this.delay - (now - this.lastUpdate));
}
}
flush() {
if (this.pendingData) {
this.callback(this.pendingData);
this.lastUpdate = Date.now();
this.pendingData = null;
this.timer = null;
}
}
}
// Usage
const dashboardThrottler = new DataThrottler(updateDashboard, 500);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
dashboardThrottler.update(data);
};
Data Aggregation Strategies
Aggregate data on the server to reduce bandwidth:
class MetricsAggregator {
constructor(window = 5000) {
this.window = window;
this.data = [];
}
add(value, timestamp = Date.now()) {
this.data.push({ value, timestamp });
this.cleanup();
}
cleanup() {
const cutoff = Date.now() - this.window;
this.data = this.data.filter(d => d.timestamp > cutoff);
}
getAverage() {
if (this.data.length === 0) return 0;
const sum = this.data.reduce((acc, d) => acc + d.value, 0);
return Math.round(sum / this.data.length);
}
getMax() {
if (this.data.length === 0) return 0;
return Math.max(...this.data.map(d => d.value));
}
getMin() {
if (this.data.length === 0) return 0;
return Math.min(...this.data.map(d => d.value));
}
getPercentile(p) {
if (this.data.length === 0) return 0;
const sorted = this.data.map(d => d.value).sort((a, b) => a - b);
const index = Math.ceil((p / 100) * sorted.length) - 1;
return sorted[index];
}
}
// Usage
const trafficAggregator = new MetricsAggregator(5000);
// Add data points as they come in
app.use((req, res, next) => {
trafficAggregator.add(1);
next();
});
// Broadcast aggregated data
setInterval(() => {
const stats = {
avgTraffic: trafficAggregator.getAverage(),
maxTraffic: trafficAggregator.getMax(),
p95Traffic: trafficAggregator.getPercentile(95)
};
broadcast(stats);
}, 2000);
Note: Aggregation reduces data transmission by up to 90% while maintaining statistical accuracy. Send summaries instead of raw data points.
D3.js Integration
Use D3.js for advanced real-time visualizations:
const margin = { top: 20, right: 20, bottom: 30, left: 50 };
const width = 600 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
const svg = d3.select('#chart')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
const data = [];
const maxDataPoints = 50;
const x = d3.scaleTime().range([0, width]);
const y = d3.scaleLinear().range([height, 0]);
const line = d3.line()
.x(d => x(d.timestamp))
.y(d => y(d.value))
.curve(d3.curveMonotoneX);
const path = svg.append('path')
.datum(data)
.attr('class', 'line')
.attr('fill', 'none')
.attr('stroke', 'steelblue')
.attr('stroke-width', 2);
const xAxis = svg.append('g')
.attr('transform', `translate(0,${height})`);
const yAxis = svg.append('g');
function updateChart(newValue) {
data.push({
timestamp: new Date(),
value: newValue
});
if (data.length > maxDataPoints) {
data.shift();
}
x.domain(d3.extent(data, d => d.timestamp));
y.domain([0, d3.max(data, d => d.value) * 1.1]);
path.datum(data)
.transition()
.duration(300)
.attr('d', line);
xAxis.transition().duration(300).call(d3.axisBottom(x));
yAxis.transition().duration(300).call(d3.axisLeft(y));
}
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
updateChart(data.value);
};
Updating DOM Efficiently
Optimize DOM updates for smooth real-time rendering:
class DOMBatcher {
constructor() {
this.updates = [];
this.rafId = null;
}
schedule(callback) {
this.updates.push(callback);
if (!this.rafId) {
this.rafId = requestAnimationFrame(() => {
this.flush();
});
}
}
flush() {
this.updates.forEach(callback => callback());
this.updates = [];
this.rafId = null;
}
}
const batcher = new DOMBatcher();
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
batcher.schedule(() => {
document.getElementById('metric1').textContent = data.metric1;
document.getElementById('metric2').textContent = data.metric2;
document.getElementById('metric3').textContent = data.metric3;
});
};
Warning: Avoid updating the DOM on every WebSocket message if messages arrive faster than 60 times per second. Batch updates using
requestAnimationFrame.
Exercise: Live Stock Ticker
Build a Real-Time Stock Ticker:
- Create a WebSocket server that streams simulated stock prices every 500ms
- Display 5 stocks with color-coded price changes (green for up, red for down)
- Show a sparkline chart for each stock using Canvas API
- Implement a throttling mechanism to update UI at most 10 times per second
- Add a rolling 24-hour high/low display
- Bonus: Add sound alerts when a stock moves more than 5%
Summary
- Real-time dashboards provide instant insights without page refreshes
- Chart.js and D3.js integrate well with WebSockets for live charts
- Throttle updates to prevent UI overload (use requestAnimationFrame)
- Aggregate data on the server to reduce bandwidth usage
- Batch DOM updates for better performance
- Consider user experience: smooth animations and meaningful transitions