Learn WebSocket - Live Dashboard & Data Streaming
Episode 22 of 34

Learn WebSocket - Live Dashboard & Data Streaming

This episode builds a live dashboard: high-frequency update needs, streaming and delta update patterns, Chart.js and D3 integration, and rendering optimization with throttling and Web Workers.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

A monitoring dashboard is the most visible face of a real-time application: moving charts, changing numbers, appearing alerts. But a bad dashboard is a sin — a chart rendered 60 times per second freezes the browser, and late numbers are a lie.

Episode 22 covers live dashboard & data streaming: receiving high-frequency updates, choosing the right streaming pattern, integrating chart libraries, and keeping the browser smooth amid thousands of data points.

Real-Time Dashboard Needs

Different Data Loads

Not all dashboards are the same. First understand their characteristics:

Dashboard update frequency
Trading chart : puluhan update per detik
Monitoring    : satu hingga beberapa per detik
Log viewer    : burst, lalu hening
Analytics     : satu per menit sudah cukup

The load determines the architecture: trading needs batching and sampling, a log viewer needs a queue that does not pile up. A dashboard that forces all data to all clients will collapse on itself.

Data Streaming Patterns

Delta and Snapshot

There are two ways to send updates: a full snapshot or a delta.

JSThe delta update pattern
const snapshot = {
  type: "snapshot",
  data: semuaPoin,
};
 
const delta = {
  type: "delta",
  data: { harga: 1023, pada: Date.now() },
};

A snapshot is sent once when the client connects — it holds the full state. After that only delta: small changes that replace values. This pattern saves bandwidth many times over compared to sending the whole dataset on every update.

Chunking Large Data

A large snapshot is split into several frames so it does not block the event loop and the socket.

JSSending a chunked snapshot
function kirimChunk(socket, data) {
  const ukuran = 1000;
  for (let i = 0; i < data.length; i += ukuran) {
    socket.emit("snapshot:chunk", data.slice(i, i + ukuran));
  }
  socket.emit("snapshot:selesai");
}

data.slice(i, i + ukuran) sends 1000 points per frame. The client waits for the snapshot:selesai event, then renders all the data at once.

Chart Library Integration

Chart.js with Real-Time Updates

Chart.js fits dashboards that update a few times per second.

JSReal-time Chart.js updates
const chart = new Chart(ctx, {
  type: "line",
  data: { labels: [], datasets: [{ data: [] }] },
});
 
ws.onmessage = (event) => {
  const d = JSON.parse(event.data);
  chart.data.labels.push(d.label);
  chart.data.datasets[0].data.push(d.nilai);
  if (chart.data.labels.length > 60) {
    chart.data.labels.shift();
    chart.data.datasets[0].data.shift();
  }
  chart.update();
};

chart.data.datasets[0].data.push(d.nilai) adds a new point, shift() discards the oldest so only the last 60 points are rendered. This is an example of windowing: show a rolling window, not the whole history.

D3 for Dense Data

D3.js is more flexible for complex visualizations and large datasets. The key: do not hand D3 a full render on every update — use an update pattern that only moves the elements that changed.

Rendering Optimization

Throttling Visual Updates

The browser renders about 60 frames per second. Data updates arriving 200 times per second must be throttled.

JSThrottling chart updates
let terakhir = 0;
 
ws.onmessage = (event) => {
  const kini = performance.now();
  if (kini - terakhir < 100) return;
  terakhir = kini;
  prosesUpdate(JSON.parse(event.data));
};

kini - terakhir < 100 caps visual processing at 10 times per second. Data is still fully received, but rendering is kept at a speed that is comfortable for the eyes.

Web Workers for Processing

Heavy data aggregation and transformation should not run on the main thread.

JSDelegating aggregation to a Web Worker
const worker = new Worker("aggregator.js");
 
ws.onmessage = (event) => {
  worker.postMessage(event.data);
};
 
worker.onmessage = (event) => {
  renderChart(event.data);
};

worker.postMessage(event.data) sends raw data to the worker, and the aggregation result returns to the main thread only for rendering. The main thread stays busy rendering the UI, not computing.

Closing

Episode 22 made a dashboard that is honest and smooth: the frugal delta pattern, chunked snapshots, windowing that protects memory, and throttling that protects the browser.

Key takeaways:

  • Data frequency determines the dashboard architecture.
  • Send a snapshot once, then deltas to save bandwidth.
  • Split large snapshots into chunks that do not block.
  • Chart.js is good for light updates, D3 for complex visualizations.
  • Throttle rendering to match the eye's capability, not the data speed.
  • Web Workers move heavy processing off the main thread.

In the next episode we build multiplayer game basics: authoritative servers, game state synchronization, input handling, latency mitigation, and game-specific optimization.