WORK 06UI ENGINEERING · ● LIVE DEMO
← ALL WORKS

D3-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
1
import React, { useEffect, useRef, useState } from "react";
2
import * as d3 from "d3";
3
4
interface DataPoint {
5
Value: number;
6
Timestamp: string;
7
MetricId: string;
8
Entity: string;
9
}
10
11
export function D3ServerGraph({ data }: { data: DataPoint[] }): JSX.Element {
12
const chartRef = useRef<SVGSVGElement | null>(null);
13
const containerRef = useRef<HTMLDivElement | null>(null);
14
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
15
16
useEffect(() => {
17
const updateDimensions = () => {
18
if (containerRef.current) {
19
setDimensions({
20
width: containerRef.current.clientWidth,
21
height: containerRef.current.clientHeight,
22
});
23
}
24
};
25
26
window.addEventListener("resize", updateDimensions);
27
updateDimensions();
28
29
return () => window.removeEventListener("resize", updateDimensions);
30
}, []);
31
32
useEffect(() => {
33
if (
34
!data ||
35
data.length === 0 ||
36
dimensions.width === 0 ||
37
dimensions.height === 0 ||
38
!chartRef.current
39
)
40
return;
41
42
const margin = { top: 50, right: 30, bottom: 50, left: 60 };
43
const width = dimensions.width - margin.left - margin.right;
44
const height = dimensions.height - margin.top - margin.bottom;
45
46
// Clear existing chart
47
d3.select(chartRef.current).selectAll("*").remove();
48
49
const svg = d3
50
.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})`);
55
56
// Define gradient
57
const gradient = svg
58
.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%");
65
66
gradient.append("stop").attr("offset", "0%").attr("stop-color", "#f3f4f6");
67
68
gradient
69
.append("stop")
70
.attr("offset", "100%")
71
.attr("stop-color", "#ffffff");
72
73
// Parse dates and group data by Entity
74
const parseDate = d3.timeParse("%m/%d/%y");
75
const groupedData = d3.group(data, (d) => d.Entity);
76
77
// Set up scales
78
const x = d3
79
.scaleTime()
80
.domain(
81
d3.extent(data, (d) => parseDate(d.Timestamp) as Date) as [Date, Date]
82
)
83
.range([0, width]);
84
85
const y = d3.scaleLinear().domain([0, 100]).range([height, 0]);
86
87
// Set up line generator
88
const line = d3
89
.line<DataPoint>()
90
.curve(d3.curveMonotoneX)
91
.x((d) => x(parseDate(d.Timestamp) as Date))
92
.y((d) => y(d.Value));
93
94
// Add X axis with modified tick format
95
svg
96
.append("g")
97
.attr("transform", `translate(0,${height})`)
98
.call(
99
d3
100
.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) =>
106
g
107
.selectAll(".tick line")
108
.clone()
109
.attr("y2", -height)
110
.attr("stroke-opacity", 0.1)
111
);
112
113
// Add X axis label
114
svg
115
.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");
123
124
// Add Y axis
125
svg
126
.append("g")
127
.call(
128
d3
129
.axisLeft(y)
130
.tickFormat((d) => `${d}%`)
131
.ticks(10)
132
)
133
.call((g) => g.select(".domain").remove())
134
.call((g) =>
135
g
136
.selectAll(".tick line")
137
.clone()
138
.attr("x2", width)
139
.attr("stroke-opacity", 0.1)
140
);
141
142
// Add Y axis label
143
svg
144
.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 (%)");
153
154
// Define color scale
155
const color = d3
156
.scaleOrdinal<string>()
157
.domain(Array.from(groupedData.keys()))
158
.range(d3.schemeTableau10);
159
160
// Add lines
161
groupedData.forEach((values, key) => {
162
svg
163
.append("path")
164
.datum(values)
165
.attr("fill", "none")
166
.attr("stroke", color(key))
167
.attr("stroke-width", 3)
168
.attr("d", line);
169
});
170
171
// Add legend
172
const legend = svg
173
.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})`);
181
182
legend
183
.append("rect")
184
.attr("x", -19)
185
.attr("width", 19)
186
.attr("height", 19)
187
.attr("fill", color);
188
189
legend
190
.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);
196
197
// Add chart title
198
svg
199
.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]);
207
208
return (
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
1
import React, { useEffect, useRef, useState } from "react";
2
import * as d3 from "d3";
3
4
export function D3ServerDiagram(): JSX.Element {
5
const svgRef = useRef<SVGSVGElement | null>(null);
6
const containerRef = useRef<HTMLDivElement | null>(null);
7
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
8
9
useEffect(() => {
10
const updateDimensions = () => {
11
if (containerRef.current) {
12
setDimensions({
13
width: containerRef.current.clientWidth,
14
height: containerRef.current.clientHeight,
15
});
16
}
17
};
18
19
window.addEventListener("resize", updateDimensions);
20
updateDimensions();
21
22
return () => window.removeEventListener("resize", updateDimensions);
23
}, []);
24
25
useEffect(() => {
26
if (!svgRef.current) return;
27
28
const svg = d3.select(svgRef.current);
29
30
// Clear previous content
31
svg.selectAll("*").remove();
32
33
// Set viewBox for responsiveness
34
svg.attr("viewBox", `0 0 600 260`);
35
36
// Define the data
37
const nodes = [
38
{ id: "client", label: "Client", color: "#e15759", x: 70, y: 130 },
39
{
40
id: "loadBalancer",
41
label: "Load Balancer",
42
color: "#e76f51",
43
x: 220,
44
y: 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
];
51
52
const 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
];
61
62
// Draw links
63
svg
64
.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);
74
75
// Draw nodes
76
const nodeGroups = svg
77
.selectAll("g")
78
.data(nodes)
79
.enter()
80
.append("g")
81
.attr("transform", (d) => `translate(${d.x},${d.y})`);
82
83
nodeGroups
84
.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);
94
95
nodeGroups
96
.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]);
103
104
return (
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