jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

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.

7 MIN READ

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ố đẹp5, 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> across innerW at each tick’s yScale(t). {một <line> ngang innerWyScale(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> with text-anchor="end" for y, "middle" for x (Part 4). {<text> với text-anchor="end" cho y, "middle" cho x.}
  • Bars — a <rect> per datum, height innerH - yScale(value), each with a <title> for an accessible native tooltip. {một <rect> mỗi điểm, cao innerH - 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<svg>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}

  1. Add a line chart {Thêm biểu đồ đường}: reuse the scales, build a <path> d from the points with L commands (Part 2). {tái dùng scale, dựng d của <path> từ các điểm bằng lệnh L.}
  2. 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.}
  3. 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.}