Back to Blog
By AriesZhou · · 8 min read

Data Visualization

Data Visualization

Why Data Visualization Matters

The human brain processes images 60,000 times faster than text. In an era of data explosion, visualization is a key tool for understanding massive amounts of information.

Text description: “Q1 2024 sales were 1.2 million, 1.8 million, 2.1 million respectively”

After visualization:

{/* Pure SVG bar chart */}

{/* Y轴 */} {/* X轴 */} {/* 柱状图 */}

{/* Labels */} Jan Feb Mar

{/* Value labels */} 1.2M 1.8M 2.1M

You can see the growth trend at a glance. That’s the power of visualization.

Core Visualization Concepts

1. Data Processing

// 典型数据处理流程
const rawData = [
  { date: '2024-01', sales: 120000, category: '电子产品' },
  { date: '2024-02', sales: 180000, category: '电子产品' },
  { date: '2024-03', sales: 210000, category: '服装' },
];

// 数据清洗:处理缺失值
const cleanedData = rawData.filter(d => d.sales != null);

// 数据转换:聚合
const aggregated = d3.rollup(
  rawData,
  v => d3.sum(v, d => d.sales),
  d => d.category
);

2. Visual Channels

Visual channels are the bridge that maps data to graphical attributes:

Visual ChannelSuitable Data TypeExample
PositionQuantitative/OrdinalScatter plot coordinates
LengthQuantitativeBar chart height
AngleQuantitativePie chart sectors
AreaQuantitativeBubble chart size
ColorCategorical/QuantitativeCategory distinction/Heatmap
ShapeCategoricalDifferent marker types

3. Chart Selection

flowchart TD
    A[数据类型] --> B{比较类型}
    B -->|趋势| C[折线图]
    B -->|占比| D[饼图/环形图]
    B -->|排名| E[条形图]
    B -->|分布| F[直方图/密度图]
    B -->|关联| G[散点图]
    B -->|地理| H[地图]

Layered Architecture

Modern frontend visualization systems use a layered architecture, with clear responsibilities at each layer:

graph LR
    subgraph "数据层 [Data]"
        direction TB
        I[数据获取] --> J[数据清洗] --> K[数据转换]
    end

    subgraph "视觉层 [Visual]"
        direction TB
        E[比例尺] --> F[坐标轴] --> G[图形元素]
        E --> H[布局算法]
    end

    subgraph "交互层 [Interaction]"
        direction TB
        B[事件处理] --> C[动画引擎] --> D[工具提示]
    end

    subgraph "表现层 [Presentation]"
        direction TB
        P[用户界面]
    end

    K -->|数据流| E
    G -->|触发| B
    C -->|更新| P
    H -.->|计算布局| G

Data Layer

// 数据层示例:数据获取与清洗
async function loadData() {
  const response = await fetch('/api/sales');
  const raw = await response.json();

  return raw
    .filter(d => d.value !== null)      // 清洗
    .map(d => ({                        // 转换
      ...d,
      date: new Date(d.date),
      value: +d.value
    }))
    .sort((a, b) => a.date - b.date);  // 排序
}

Visual Layer

// 视觉层:创建 SVG 画布
const svg = d3.select('#chart')
  .append('svg')
  .attr('width', 800)
  .attr('height', 400);

// 创建比例尺
const xScale = d3.scaleTime()
  .domain(d3.extent(data, d => d.date))
  .range([0, 800]);

const yScale = d3.scaleLinear()
  .domain([0, d3.max(data, d => d.value)])
  .range([400, 0]);

// 创建坐标轴
svg.append('g')
  .call(d3.axisBottom(xScale));

svg.append('g')
  .call(d3.axisLeft(yScale));

Interaction Layer

// 交互层:工具提示
const tooltip = d3.select('body')
  .append('div')
  .style('position', 'absolute')
  .style('visibility', 'hidden')
  .style('background', 'white')
  .style('padding', '8px')
  .style('border', '1px solid #ccc');

// 绑定事件
svg.selectAll('circle')
  .data(data)
  .enter()
  .append('circle')
  .on('mouseover', function(event, d) {
    d3.select(this).attr('r', 8);
    tooltip
      .style('visibility', 'visible')
      .html(`日期: ${d.date}<br/>销售额: ${d.value}`);
  })
  .on('mousemove', function(event) {
    tooltip
      .style('top', (event.pageY - 10) + 'px')
      .style('left', (event.pageX + 10) + 'px');
  })
  .on('mouseout', function() {
    d3.select(this).attr('r', 5);
    tooltip.style('visibility', 'hidden');
  });

D3.js Core Mechanism

Enter-Update-Exit Pattern

This is D3’s most fundamental data-driven mechanism:

graph TD
    A[数据数组] --> B{比较}
    B -->|新数据更多| C[ENTER<br/>创建新元素]
    B -->|数量相同| D[UPDATE<br/>更新属性]
    B -->|数据更少| E[EXIT<br/>移除元素]

    C --> F[渲染新图形]
    D --> G[更新现有图形]
    E --> H[删除多余图形]

Complete Example: Dynamic Bar Chart

function updateBarChart(newData) {
  const svg = d3.select('#bar-chart');

  // 定义比例尺
  const x = d3.scaleBand()
    .domain(newData.map(d => d.category))
    .range([0, width])
    .padding(0.2);

  const y = d3.scaleLinear()
    .domain([0, d3.max(newData, d => d.value)])
    .range([height, 0]);

  // ENTER: 创建新元素
  const bars = svg.selectAll('.bar')
    .data(newData, d => d.category);

  bars.enter()
    .append('rect')
    .attr('class', 'bar')
    .attr('x', d => x(d.category))
    .attr('y', height)  // 从底部开始
    .attr('width', x.bandwidth())
    .attr('height', 0)
    .attr('fill', 'steelblue')
    .merge(bars)        // 合并 UPDATE
    .transition()
    .duration(500)
    .attr('y', d => y(d.value))
    .attr('height', d => height - y(d.value));

  // EXIT: 移除元素
  bars.exit()
    .transition()
    .duration(500)
    .attr('y', height)
    .attr('height', 0)
    .remove();
}

Scales

D3 provides several types of scales:

ScalePurposeExample
scaleLinearLinear mappingvalue → pixel position
scaleTimeTime mappingDate → pixel position
scaleBandBand mappingcategory → bar width
scaleOrdinalOrdinal mappingcategory → color
scaleSequentialSequential gradientvalue → color gradient
// 线性比例尺
const linear = d3.scaleLinear()
  .domain([0, 100])   // 数据范围
  .range([0, 500]);   // 输出范围

linear(0);   // 0
linear(50);  // 250
linear(100); // 500

// 颜色比例尺
const color = d3.scaleOrdinal()
  .domain(['A', 'B', 'C'])
  .range(['#1f77b4', '#ff7f0e', '#2ca02c']);

color('A'); // '#1f77b4'
color('B'); // '#ff7f0e'

Layouts

D3 includes several built-in layout algorithms:

// 力导向图布局
const simulation = d3.forceSimulation(nodes)
  .force('link', d3.forceLink(links).id(d => d.id))
  .force('charge', d3.forceManyBody())
  .force('center', d3.forceCenter(width / 2, height / 2));

// 饼图布局
const pie = d3.pie().value(d => d.value);
const arc = d3.arc().innerRadius(0).outerRadius(radius);

const arcs = svg.selectAll('arc')
  .data(pie(data))
  .enter()
  .append('g')
  .attr('transform', `translate(${width/2}, ${height/2})`);

arcs.append('path')
  .attr('d', arc)
  .attr('fill', d => color(d.data.category));

Demo

Example 1: Line chart

// 基础折线图
const margin = { top: 20, right: 30, bottom: 30, left: 40 };
const width = 800 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;

const svg = d3.select('#line-chart')
  .append('svg')
  .attr('width', width + margin.left + margin.right)
  .attr('height', height + margin.top + margin.bottom)
  .append('g')
  .attr('transform', `translate(${margin.left},${margin.top})`);

// 比例尺
const x = d3.scaleTime()
  .domain(d3.extent(data, d => d.date))
  .range([0, width]);

const y = d3.scaleLinear()
  .domain([0, d3.max(data, d => d.value)])
  .range([height, 0]);

// 添加路径
const line = d3.line()
  .x(d => x(d.date))
  .y(d => y(d.value))
  .curve(d3.curveMonotoneX);  // 平滑曲线

svg.append('path')
  .datum(data)
  .attr('fill', 'none')
  .attr('stroke', 'steelblue')
  .attr('stroke-width', 2)
  .attr('d', line);

// 添加数据点
svg.selectAll('circle')
  .data(data)
  .enter()
  .append('circle')
  .attr('cx', d => x(d.date))
  .attr('cy', d => y(d.value))
  .attr('r', 4)
  .attr('fill', 'steelblue');

Example 2: Interactive map

// 使用 GeoJSON 绘制地图
const projection = d3.geoMercator()
  .scale(100)
  .center([0, 20])
  .translate([width / 2, height / 2]);

const path = d3.geoPath().projection(projection);

svg.selectAll('path')
  .data(geoData.features)
  .enter()
  .append('path')
  .attr('d', path)
  .attr('fill', d => color(d.properties.region))
  .attr('stroke', '#fff')
  .attr('stroke-width', 0.5)
  .on('mouseover', function(event, d) {
    d3.select(this).attr('fill', 'orange');
    tooltip.text(d.properties.name);
  })
  .on('mouseout', function(event, d) {
    d3.select(this).attr('fill', color(d.properties.region));
  });

Example 3: Data dashboard

// 创建仪表盘组件
function Gauge(value, min, max, label) {
  const svg = d3.select(`#gauge-${label}`)
    .append('svg')
    .attr('width', 200)
    .attr('height', 120);

  const angle = d3.scaleLinear()
    .domain([min, max])
    .range([-Math.PI / 2, Math.PI / 2]);

  const arc = d3.arc()
    .innerRadius(60)
    .outerRadius(80)
    .startAngle(-Math.PI / 2);

  // 背景弧
  svg.append('path')
    .datum({ endAngle: Math.PI / 2 })
    .attr('d', arc)
    .attr('transform', 'translate(100, 100)');

  // 值弧
  svg.append('path')
    .datum({ endAngle: angle(value) })
    .attr('d', arc)
    .attr('fill', 'green')
    .attr('transform', 'translate(100, 100)');

  // 标签
  svg.append('text')
    .attr('x', 100)
    .attr('y', 100)
    .attr('text-anchor', 'middle')
    .text(value);
}

Performance optimization

1. Use Canvas instead of SVG

For large datasets (> 10,000 points), SVG performance degrades:

// SVG: 适合 < 1000 点
const svg = d3.select('body').append('svg');

// Canvas: 适合 > 1000 点
const canvas = d3.select('body').append('canvas')
  .attr('width', width)
  .attr('height', height);

const context = canvas.node().getContext('2d');

// 使用 D3 比例尺,但用 Canvas 绘制
data.forEach(d => {
  context.beginPath();
  context.arc(x(d.x), y(d.y), r, 0, 2 * Math.PI);
  context.fill();
});

2. Virtual scrolling

Render only the data in the visible area:

// 只渲染可见范围内的数据
const visibleData = data.filter(d =>
  x(d.date) >= 0 && x(d.date) <= width
);

svg.selectAll('circle')
  .data(visibleData, d => d.id)
  .join('circle')
  .attr('cx', d => x(d.date))
  .attr('cy', d => y(d.value));

3. Web Workers

Move data processing to a Web Worker:

// worker.js
self.onmessage = function(e) {
  const processed = heavyDataProcessing(e.data);
  self.postMessage(processed);
};

// 主线程
const worker = new Worker('worker.js');
worker.onmessage = function(e) {
  updateChart(e.data);
};
worker.postMessage(rawData);

Technology choices

ScenarioRecommended approach
Simple chartsD3, ECharts
Complex interactionsD3.js
Rapid developmentECharts, Chart.js
Geographic visualizationD3.js + TopoJSON
Large datasetsDeck.gl, Canvas
React ecosystemRecharts, Visx

Summary

Data visualization is a key technique for turning data into intuitive graphics:

  • Core workflow: data processing → visual mapping → interaction design
  • D3 core: the ENTER-UPDATE-EXIT pattern enables data-driven rendering
  • Layered architecture: data layer → visual layer → interaction layer
  • Design principles: clarity first, goal-oriented, responsive adaptation
  • Performance: use Canvas for large datasets, SVG for interaction

D3.js is the most powerful web visualization library, but it has a steep learning curve. It’s best to start with simple charts and gradually master data binding and interaction implementation.

Further reading