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.

4 MIN READ

Đây là màn kết. 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 đồ. 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.

Sửa dữ liệu dưới đây — mỗi phím gõ tính lại scale, tick, và cột.

Mở demo đầy đủ:

Quy ước lề

Mọi biểu đồ cần chỗ cho trục và nhãn. 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)">

Đây là “margin convention” của D3, nền tảng dưới mọi biểu đồ. Trục nằm ở mép hộp trong; dữ liệu nằm bên trong.

Scale chỉ là một hàm

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. 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;

Giá trị 0 rơi ở đáy (innerH); max rơi ở đỉnh (0). Một dòng đó là cái d3.scaleLinear() cho con — cộng các tính năng phụ. 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;

Tick “đẹp”: phần khó ẩn giấu

Tick ngây thơ (max/5) cho nhãn xấu như 4.6, 9.2, 13.8. Thư viện thật làm tròn về số đẹp5, 10, 15, 20. 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 };
}

Đâ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.

Lắp ráp các phần

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:

  • một <line> ngang innerWyScale(t) của mỗi tick.
  • hai <line> dọc trái và đáy hộp trong.
  • <text> với text-anchor="end" cho y, "middle" cho x.
  • một <rect> mỗi điểm, cao innerH - yScale(value), mỗi cái có <title> cho tooltip native truy cập được.

Ta dựng DOM bằng createElementNS và append một lần.

Co giãn miễn phí

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. Đây là phần thưởng của việc hiểu hệ toạ độ từ Phần 1.

Khi nào vẫn nên dùng thư viện

Giờ con dựng được rồi, con sẽ biết khi nào không nên. Tự code hoàn hảo cho vài biểu đồ riêng con kiểm soát hoàn toàn. 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. Khác biệt: giờ con đọc docs của họ và hiểu từng chữ.

Lời cảnh báo của sư phụ

  • Đừng quên lật y. Gốc SVG ở trên-trái; gốc dữ liệu ở dưới-trái.
  • 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.
  • 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.

Luyện tập, không thì coi như chưa học

  1. Thêm biểu đồ đường: tái dùng scale, dựng d của <path> từ các điểm bằng lệnh L.
  2. Animate cột vào: cho chiều cao mỗi cột mọc từ 0 bằng transition CSS hoặc tween rAF.
  3. Làm cho hoàn toàn truy cập được: thêm <title>/<desc> và một <table> dữ liệu ẩn-thị-giác từ Phần 16.

Cuối con đường — giờ con là senior

Hai mươi phần trước SVG là một cục XML bí ẩn. 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. 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. Sự cân nhắc đó là cái làm nên một senior. Giờ đi vẽ gì đó đi.