WORK 06 — UI ENGINEERING · ● LIVE DEMO
← ALL WORKSD3-Based Server Visualization
A dynamic way to monitor CPU usage across multiple servers in real time. D3.js handles the data binding and SVG manipulation; React manages component state; Tailwind and TypeScript keep it maintainable.
- D3.JS
- REACT
- TYPESCRIPT
- TAILWIND
Real-time performance monitoring
CPU usage over time across multiple servers, color-coded and interactive for precise inspection.
d3ServerGraph.tsx — rendered live
MULTI-SERVER
Color-coded comparison across servers
TIME-BASED
Usage trends tracked over time
INTERACTIVE
Hover for precise data readouts
RESPONSIVE
Adapts to any screen size
Graph D3 code
d3ServerGraphCode.tsx
1import React, { useEffect, useRef, useState } from "react";2import * as d3 from "d3";34interface DataPoint {5Value: number;6Timestamp: string;7MetricId: string;8Entity: string;9}1011export function D3ServerGraph({ data }: { data: DataPoint[] }): JSX.Element {12const chartRef = useRef<SVGSVGElement | null>(null);13const containerRef = useRef<HTMLDivElement | null>(null);14const [dimensions, setDimensions] = useState({ width: 0, height: 0 });1516useEffect(() => {17const updateDimensions = () => {18if (containerRef.current) {19setDimensions({20width: containerRef.current.clientWidth,21height: containerRef.current.clientHeight,22});23}24};2526window.addEventListener("resize", updateDimensions);27updateDimensions();2829return () => window.removeEventListener("resize", updateDimensions);30}, []);3132useEffect(() => {33if (34!data ||35data.length === 0 ||36dimensions.width === 0 ||37dimensions.height === 0 ||38!chartRef.current39)40return;4142const margin = { top: 50, right: 30, bottom: 50, left: 60 };43const width = dimensions.width - margin.left - margin.right;44const height = dimensions.height - margin.top - margin.bottom;4546// Clear existing chart47d3.select(chartRef.current).selectAll("*").remove();4849const svg = d350.select(chartRef.current)51.attr("width", width + margin.left + margin.right)52.attr("height", height + margin.top + margin.bottom)53.append("g")54.attr("transform", `translate(${margin.left},${margin.top})`);5556// Define gradient57const gradient = svg58.append("defs")59.append("linearGradient")60.attr("id", "bg-gradient")61.attr("x1", "0%")62.attr("y1", "0%")63.attr("x2", "0%")64.attr("y2", "100%");6566gradient.append("stop").attr("offset", "0%").attr("stop-color", "#f3f4f6");6768gradient69.append("stop")70.attr("offset", "100%")71.attr("stop-color", "#ffffff");7273// Parse dates and group data by Entity74const parseDate = d3.timeParse("%m/%d/%y");75const groupedData = d3.group(data, (d) => d.Entity);7677// Set up scales78const x = d379.scaleTime()80.domain(81d3.extent(data, (d) => parseDate(d.Timestamp) as Date) as [Date, Date]82)83.range([0, width]);8485const y = d3.scaleLinear().domain([0, 100]).range([height, 0]);8687// Set up line generator88const line = d389.line<DataPoint>()90.curve(d3.curveMonotoneX)91.x((d) => x(parseDate(d.Timestamp) as Date))92.y((d) => y(d.Value));9394// Add X axis with modified tick format95svg96.append("g")97.attr("transform", `translate(0,${height})`)98.call(99d3100.axisBottom(x)101.tickFormat((d) => d3.timeFormat("%b %d")(d as Date))102.ticks(width / 80)103)104.call((g) => g.select(".domain").remove())105.call((g) =>106g107.selectAll(".tick line")108.clone()109.attr("y2", -height)110.attr("stroke-opacity", 0.1)111);112113// Add X axis label114svg115.append("text")116.attr("class", "x-axis-label")117.classed("fill-primary", true)118.attr("x", width / 2)119.attr("y", height + margin.bottom - 0)120.style("text-anchor", "middle")121.style("font-size", "14px")122.text("Date");123124// Add Y axis125svg126.append("g")127.call(128d3129.axisLeft(y)130.tickFormat((d) => `${d}%`)131.ticks(10)132)133.call((g) => g.select(".domain").remove())134.call((g) =>135g136.selectAll(".tick line")137.clone()138.attr("x2", width)139.attr("stroke-opacity", 0.1)140);141142// Add Y axis label143svg144.append("text")145.attr("class", "y-axis-label")146.classed("fill-primary", true)147.attr("transform", "rotate(-90)")148.attr("y", -margin.left + 10)149.attr("x", -height / 2)150.style("text-anchor", "middle")151.style("font-size", "14px")152.text("CPU Usage (%)");153154// Define color scale155const color = d3156.scaleOrdinal<string>()157.domain(Array.from(groupedData.keys()))158.range(d3.schemeTableau10);159160// Add lines161groupedData.forEach((values, key) => {162svg163.append("path")164.datum(values)165.attr("fill", "none")166.attr("stroke", color(key))167.attr("stroke-width", 3)168.attr("d", line);169});170171// Add legend172const legend = svg173.append("g")174.attr("font-family", "sans-serif")175.attr("font-size", 12)176.attr("text-anchor", "end")177.selectAll("g")178.data(Array.from(groupedData.keys()))179.join("g")180.attr("transform", (d, i) => `translate(${width + 20},${i * 20})`);181182legend183.append("rect")184.attr("x", -19)185.attr("width", 19)186.attr("height", 19)187.attr("fill", color);188189legend190.append("text")191.attr("x", -24)192.attr("y", 9.5)193.attr("dy", "0.32em")194.classed("fill-primary", true)195.text((d) => d);196197// Add chart title198svg199.append("text")200.attr("x", width / 2)201.attr("y", -margin.top / 2 - 5)202.attr("text-anchor", "middle")203.classed("fill-primary", true)204.style("font-size", "16px")205.text("CPU Usage Over Time");206}, [data, dimensions]);207208return (209<div ref={containerRef} className="w-full h-96">210<svg ref={chartRef} className="w-full h-full"></svg>211</div>212);213}
Server architecture
Understanding the context of the data matters as much as visualizing it. The monitored system, drawn with D3:
d3ServerDiagram.tsx — rendered live
Diagram D3 code
d3ServerDiagramCode.tsx
1import React, { useEffect, useRef, useState } from "react";2import * as d3 from "d3";34export function D3ServerDiagram(): JSX.Element {5const svgRef = useRef<SVGSVGElement | null>(null);6const containerRef = useRef<HTMLDivElement | null>(null);7const [dimensions, setDimensions] = useState({ width: 0, height: 0 });89useEffect(() => {10const updateDimensions = () => {11if (containerRef.current) {12setDimensions({13width: containerRef.current.clientWidth,14height: containerRef.current.clientHeight,15});16}17};1819window.addEventListener("resize", updateDimensions);20updateDimensions();2122return () => window.removeEventListener("resize", updateDimensions);23}, []);2425useEffect(() => {26if (!svgRef.current) return;2728const svg = d3.select(svgRef.current);2930// Clear previous content31svg.selectAll("*").remove();3233// Set viewBox for responsiveness34svg.attr("viewBox", `0 0 600 260`);3536// Define the data37const nodes = [38{ id: "client", label: "Client", color: "#e15759", x: 70, y: 130 },39{40id: "loadBalancer",41label: "Load Balancer",42color: "#e76f51",43x: 220,44y: 130,45},46{ id: "serverA", label: "Server_A", color: "#00798c", x: 370, y: 30 },47{ id: "serverB", label: "Server_B", color: "#30638e", x: 370, y: 130 },48{ id: "serverC", label: "Server_C", color: "#003d5b", x: 370, y: 230 },49{ id: "database", label: "Database", color: "#34623f", x: 520, y: 130 },50];5152const links = [53{ source: "client", target: "loadBalancer" },54{ source: "loadBalancer", target: "serverA" },55{ source: "loadBalancer", target: "serverB" },56{ source: "loadBalancer", target: "serverC" },57{ source: "serverA", target: "database" },58{ source: "serverB", target: "database" },59{ source: "serverC", target: "database" },60];6162// Draw links63svg64.selectAll("line")65.data(links)66.enter()67.append("line")68.attr("x1", (d) => nodes.find((n) => n.id === d.source)?.x || 0)69.attr("y1", (d) => nodes.find((n) => n.id === d.source)?.y || 0)70.attr("x2", (d) => nodes.find((n) => n.id === d.target)?.x || 0)71.attr("y2", (d) => nodes.find((n) => n.id === d.target)?.y || 0)72.classed("stroke-primary", true)73.attr("stroke-width", 1);7475// Draw nodes76const nodeGroups = svg77.selectAll("g")78.data(nodes)79.enter()80.append("g")81.attr("transform", (d) => `translate(${d.x},${d.y})`);8283nodeGroups84.append("rect")85.attr("width", 100)86.attr("height", 50)87.attr("x", -50)88.attr("y", -25)89.attr("rx", 2)90.attr("ry", 2)91.attr("fill", (d) => d.color)92.classed("stroke-primary", true)93.attr("stroke-width", 1);9495nodeGroups96.append("text")97.text((d) => d.label)98.attr("text-anchor", "middle")99.attr("dy", "0.3em")100.attr("fill", "white")101.attr("font-size", "11px");102}, [dimensions]);103104return (105<div ref={containerRef} className="w-full h-full min-h-[300px]">106<svg ref={svgRef} className="w-full h-full"></svg>107</div>108);109}
Impact
40%
FASTER RESPONSE TO SERVER ISSUES
25%
REDUCTION IN SERVER COSTS
99.99%
UPTIME, UP FROM 99.9%
+60%
POSITIVE FEEDBACK FROM IT STAFF