SVG from Zero to Senior · Part 20 — Build a Chart From Scratch
The finale: build a bar chart with no D3 and no Chart.js. Implement scales, nice ticks, axes, gridlines and responsive sizing by hand — so you finally understand what every charting library does for you. With a live, editable chart.
This is the finale. {Đây là màn kết.} Everything in this series — the coordinate system, paths, text, transforms, scales, interactivity, accessibility — converges on one deceptively simple object: a chart. {Mọi thứ trong series — hệ toạ độ, path, text, transform, scale, tương tác, accessibility — hội tụ vào một thứ trông đơn giản: biểu đồ.} Charting libraries feel like magic until you build one by hand; then you realize it’s just arithmetic on a viewBox. {Thư viện biểu đồ như phép thuật cho tới khi con tự dựng một cái; rồi con nhận ra nó chỉ là số học trên một viewBox.}
Edit the data below — every keystroke recomputes the scale, ticks, and bars. {Sửa dữ liệu dưới đây — mỗi phím gõ tính lại scale, tick, và cột.}
Open the full demo {Mở demo đầy đủ}: /tools/svg-charts-demo/.
The margin convention {Quy ước lề}
Every chart needs room for axes and labels. {Mọi biểu đồ cần chỗ cho trục và nhãn.} The standard pattern is an outer SVG with a margin object and an inner drawing area you translate into: {Mẫu chuẩn là một SVG ngoài với object margin và một vùng vẽ trong mà con translate vào:}
const W = 600, H = 320;
const M = { top: 24, right: 16, bottom: 40, left: 44 };
const innerW = W - M.left - M.right;
const innerH = H - M.top - M.bottom;
// everything draws inside <g transform="translate(M.left, M.top)">
This is D3’s “margin convention,” and it’s the foundation under every chart. {Đây là “margin convention” của D3, nền tảng dưới mọi biểu đồ.} The axes live on the edges of the inner box; the data lives inside it. {Trục nằm ở mép hộp trong; dữ liệu nằm bên trong.}
A scale is just a function {Scale chỉ là một hàm}
The single most important concept in dataviz: a scale maps a data domain to a pixel range. {Khái niệm quan trọng nhất trong dataviz: một scale ánh xạ miền dữ liệu sang dải pixel.} For a linear y-axis, mapping value → pixel (remembering SVG y points down, so we flip): {Với trục y tuyến tính, ánh xạ giá trị → pixel (nhớ y của SVG hướng xuống, nên ta lật):}
// domain [0, top] → range [innerH, 0]
const yScale = (v) => innerH - (v / top) * innerH;
A value of 0 lands at the bottom (innerH); the max lands at the top (0). {Giá trị 0 rơi ở đáy (innerH); max rơi ở đỉnh (0).} That one line is what d3.scaleLinear() gives you — plus the bells. {Một dòng đó là cái d3.scaleLinear() cho con — cộng các tính năng phụ.} For categorical bars we use a band scale: divide the width into equal slots. {Với cột phân loại ta dùng band scale: chia chiều rộng thành các ô bằng nhau.}
const band = innerW / data.length;
const barW = band * 0.62; // leave gaps between bars
const x = i * band + (band - barW) / 2;
”Nice” ticks: the hidden hard part {Tick “đẹp”: phần khó ẩn giấu}
Naive ticks (max/5) give ugly axis labels like 4.6, 9.2, 13.8. {Tick ngây thơ (max/5) cho nhãn xấu như 4.6, 9.2, 13.8.} Real chart libraries round to nice numbers — 5, 10, 15, 20. {Thư viện thật làm tròn về số đẹp — 5, 10, 15, 20.} The algorithm: find a step that’s a multiple of 1, 2, 5 × a power of ten near max/target: {Thuật toán: tìm bước là bội của 1, 2, 5 × luỹ thừa của mười gần max/target:}
function niceTicks(max, target = 5) {
const raw = max / target;
const mag = Math.pow(10, Math.floor(Math.log10(raw)));
const norm = raw / mag;
const step = (norm >= 5 ? 10 : norm >= 2 ? 5 : norm >= 1 ? 2 : 1) * mag;
const top = Math.ceil(max / step) * step; // round the domain UP to a tick
const ticks = [];
for (let t = 0; t <= top + 1e-9; t += step) ticks.push(t);
return { ticks, top };
}
This is the unglamorous code that makes axes look professional, and it’s exactly what you don’t appreciate until you write it. {Đây là đoạn code không hào nhoáng làm trục trông chuyên nghiệp, và đúng là thứ con không trân trọng cho tới khi tự viết.}
Assembling the parts {Lắp ráp các phần}
With scales and ticks, the rest is drawing — each piece a concept from earlier parts: {Có scale và tick rồi, phần còn lại là vẽ — mỗi mảnh là một khái niệm từ các phần trước:}
- Gridlines — a
<line>acrossinnerWat each tick’syScale(t). {một<line>nganginnerWởyScale(t)của mỗi tick.} - Axes — two
<line>s along the left and bottom of the inner box. {hai<line>dọc trái và đáy hộp trong.} - Tick labels —
<text>withtext-anchor="end"for y,"middle"for x (Part 4). {<text>vớitext-anchor="end"cho y,"middle"cho x.} - Bars — a
<rect>per datum, heightinnerH - yScale(value), each with a<title>for an accessible native tooltip. {một<rect>mỗi điểm, caoinnerH - yScale(value), mỗi cái có<title>cho tooltip native truy cập được.}
We build the DOM with createElementNS (Part 9) and append once. {Ta dựng DOM bằng createElementNS và append một lần.}
Responsiveness for free {Co giãn miễn phí}
Because the chart is drawn in viewBox units and the <svg> has width:100%, it scales to any container with zero extra code — and text/strokes stay razor-sharp because it’s vector, not raster. {Vì biểu đồ vẽ trong đơn vị viewBox và <svg> có width:100%, nó co giãn vào mọi container không cần code thêm — và text/stroke vẫn sắc lẹm vì là vector, không phải raster.} This is the payoff of understanding the coordinate system from Part 1. {Đây là phần thưởng của việc hiểu hệ toạ độ từ Phần 1.}
When to use a library after all {Khi nào vẫn nên dùng thư viện}
Now that you can build one, you’ll know when not to. {Giờ con dựng được rồi, con sẽ biết khi nào không nên.} Hand-rolled is perfect for a few bespoke charts you fully control. {Tự code hoàn hảo cho vài biểu đồ riêng con kiểm soát hoàn toàn.} Reach for D3 (scales/axes/shapes, you keep the DOM) or a higher-level lib (Chart.js, visx, Recharts) when you need many chart types, axes with time/log scales, transitions, tooltips, brushing, and zoom — re-implementing all of that is a project, not a function. {Dùng D3 hoặc lib cấp cao hơn khi cần nhiều loại biểu đồ, trục với scale thời gian/log, transition, tooltip, brushing, và zoom — cài lại tất cả là một dự án, không phải một hàm.} The difference: now you’ll read their docs and understand every word. {Khác biệt: giờ con đọc docs của họ và hiểu từng chữ.}
The master’s warnings {Lời cảnh báo của sư phụ}
- Never forget the y-flip. SVG’s origin is top-left; data’s is bottom-left. Every bug in a hand-built chart is one missing subtraction. {Đừng quên lật y. Gốc SVG ở trên-trái; gốc dữ liệu ở dưới-trái.}
- Round the domain up to a tick. A bar that touches the top edge with no headroom looks broken. {Làm tròn miền lên tới một tick. Một cột chạm mép trên không có khoảng hở trông như lỗi.}
- Give every bar a
<title>. It’s free, native, hover tooltips and a step toward the accessibility of Part 16. {Cho mỗi cột một<title>. Đó là tooltip hover native miễn phí và một bước tới accessibility của Phần 16.}
Practice, or it didn’t happen {Luyện tập, không thì coi như chưa học}
- Add a line chart {Thêm biểu đồ đường}: reuse the scales, build a
<path>dfrom the points withLcommands (Part 2). {tái dùng scale, dựngdcủa<path>từ các điểm bằng lệnhL.} - Animate bars in {Animate cột vào}: grow each bar’s height from 0 with a CSS transition or a Part-6 rAF tween. {cho chiều cao mỗi cột mọc từ 0 bằng transition CSS hoặc tween rAF.}
- Make it fully accessible {Làm cho hoàn toàn truy cập được}: add the
<title>/<desc>and a visually-hidden data<table>from Part 16. {thêm<title>/<desc>và một<table>dữ liệu ẩn-thị-giác từ Phần 16.}
The end of the road — you’re a senior now {Cuối con đường — giờ con là senior}
Twenty parts ago SVG was a mysterious blob of XML. {Hai mươi phần trước SVG là một cục XML bí ẩn.} Now you command the coordinate system, the path language, painting, text, transforms, animation, filters, clipping, masking, interactivity, accessibility, sprites, performance, and — as of today — data visualization from first principles. {Giờ con làm chủ hệ toạ độ, ngôn ngữ path, painting, text, transform, animation, filter, clipping, masking, tương tác, accessibility, sprite, hiệu năng, và — từ hôm nay — trực quan hoá dữ liệu từ nguyên lý gốc.} You no longer reach for a library because SVG scares you — you reach for it because you’ve judged it’s the right tool, and you could build it yourself if you had to. {Con không còn dùng thư viện vì SVG làm con sợ — con dùng vì con đã cân nhắc đó là công cụ đúng, và con tự dựng được nếu phải.} That judgment is what makes a senior. {Sự cân nhắc đó là cái làm nên một senior.} Now go draw something. {Giờ đi vẽ gì đó đi.}