← Journal

Building a Real-Time Analytics Dashboard with React and D3.js

Creating an interactive dashboard to visualize real-time data streams — WebSocket connections, dynamic chart updates, and TimescaleDB for time-series storage.

I built a real-time analytics dashboard that displays live metrics from a data pipeline, updating every second with new data points sourced from a WebSocket stream.

Technology Stack

LayerTechnology
FrontendReact + TypeScript
VisualisationD3.js & Chart.js
BackendNode.js with Socket.IO
DatabasePostgreSQL + TimescaleDB

WebSocket Data Flow

Data flows from the ingestion layer → Kafka topic → Node.js consumer → Socket.IO broadcast → React state → Chart update — all within 50 ms end-to-end latency.

React Component

import React, { useEffect, useState } from 'react';
import { Line } from 'react-chartjs-2';
import io from 'socket.io-client';

interface DataPoint { ts: number; value: number; }

const Dashboard: React.FC = () => {
  const [data, setData] = useState<DataPoint[]>([]);

  useEffect(() => {
    const socket = io('ws://analytics-server:3001');

    socket.on('new-data', (point: DataPoint) => {
      setData(prev => [...prev.slice(-100), point]);
    });

    return () => { socket.disconnect(); };
  }, []);

  const chartData = {
    labels:   data.map(d => new Date(d.ts).toLocaleTimeString()),
    datasets: [{ label: 'Live Metric', data: data.map(d => d.value) }],
  };

  return <Line data={chartData} />;
};

Performance Metrics

MetricValue
Update frequency1 second
Data points rendered100 (rolling window)
End-to-end latency< 50 ms
Concurrent users500+

TimescaleDB for Storage

TimescaleDB hypertables compress time-series data 20× compared to vanilla PostgreSQL and allow sub-second time_bucket() aggregations over months of data — critical for the “zoom out” feature in the dashboard.

-- Create hypertable
SELECT create_hypertable('metrics', 'time');

-- 1-minute rolling average
SELECT time_bucket('1 minute', time) AS bucket,
       AVG(value) AS avg_value
FROM metrics
WHERE time > NOW() - INTERVAL '1 hour'
GROUP BY bucket
ORDER BY bucket;