【前端数据可视化】D3.js入门:打造炫酷的数据可视化效果
前言
大家好,我是cannonmonster01!上一篇我们聊了ECharts,今天咱们来看看数据可视化领域的"瑞士军刀"——D3.js。如果说ECharts是傻瓜相机,那D3.js就是专业单反,虽然学习曲线陡峭,但一旦掌握,就能创造出令人惊叹的可视化效果。
什么是D3.js
D3.js(Data-Driven Documents)是一个基于Web标准的JavaScript库,用于操作文档的可视化。它的核心思想是:
- 数据驱动:数据决定DOM的呈现
- 声明式编程:描述"是什么"而非"怎么做"
- 链式调用:方法可以链式组合,代码优雅
环境搭建
引入方式
<!– 方式1:直接引入CDN –>
<script src="https://d3js.org/d3.v7.min.js"></script>
<!– 方式2:npm安装 –>
<script type="module">
import * as d3 from 'https://cdn.skypack.dev/d3@7';
</script>
创建第一个D3图表
<!DOCTYPE html>
<html>
<head>
<script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>
<svg width="600" height="400"></svg>
<script>
const svg = d3.select('svg');
const data = [4, 8, 15, 16, 23, 42];
const rects = svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('x', (d, i) => i * 100)
.attr('y', d => 400 – d * 10)
.attr('width', 80)
.attr('height', d => d * 10)
.attr('fill', '#5470c6');
</script>
</body>
</html>
D3核心概念
选择器与数据绑定
D3的选择器语法类似于CSS选择器:
// 选择所有p元素
d3.selectAll('p')
.style('color', 'red');
// 选择第一个p元素
d3.select('p')
.style('font-size', '20px');
数据绑定是D3的核心:
const data = [1, 2, 3, 4, 5];
const selection = d3.select('body')
.selectAll('div')
.data(data);
// enter()返回缺少的元素
selection.enter()
.append('div')
.text(d => `数值: ${d}`);
// exit()返回多余的元素
selection.exit()
.remove();
比例尺(Scale)
比例尺用于将数据映射到可视空间:
// 线性比例尺
const xScale = d3.scaleLinear()
.domain([0, 100]) // 数据范围
.range([0, 500]); // 像素范围
console.log(xScale(50)); // 输出: 250
// 序数比例尺
const colorScale = d3.scaleOrdinal()
.domain(['A', 'B', 'C'])
.range(['red', 'green', 'blue']);
console.log(colorScale('B')); // 输出: 'green'
坐标轴(Axis)
D3提供了坐标轴生成器:
const xScale = d3.scaleLinear()
.domain([0, 100])
.range([0, 500]);
const xAxis = d3.axisBottom(xScale)
.ticks(5)
.tickFormat(d => `${d}%`);
svg.append('g')
.attr('transform', 'translate(50, 350)')
.call(xAxis);
实战案例:交互式折线图
让我们创建一个交互式折线图:
const svg = d3.select('svg');
const margin = {top: 20, right: 30, bottom: 40, left: 50};
const width = 600 – margin.left – margin.right;
const height = 400 – margin.top – margin.bottom;
const g = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
const data = [
{date: '2023-01', value: 45},
{date: '2023-02', value: 52},
{date: '2023-03', value: 38},
{date: '2023-04', value: 65},
{date: '2023-05', value: 72},
{date: '2023-06', value: 58}
];
const xScale = d3.scaleBand()
.domain(data.map(d => d.date))
.range([0, width])
.padding(0.1);
const yScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)])
.range([height, 0]);
const line = d3.line()
.x(d => xScale(d.date) + xScale.bandwidth() / 2)
.y(d => yScale(d.value))
.curve(d3.curveMonotoneX);
g.append('path')
.datum(data)
.attr('fill', 'none')
.attr('stroke', '#5470c6')
.attr('stroke-width', 2)
.attr('d', line);
g.selectAll('.dot')
.data(data)
.enter().append('circle')
.attr('class', 'dot')
.attr('cx', d => xScale(d.date) + xScale.bandwidth() / 2)
.attr('cy', d => yScale(d.value))
.attr('r', 5)
.attr('fill', '#5470c6')
.on('mouseover', function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr('r', 8)
.attr('fill', '#91cc75');
tooltip.transition()
.duration(200)
.style('opacity', .9);
tooltip.html(`日期: ${d.date}<br/>数值: ${d.value}`)
.style('left', (event.pageX) + 'px')
.style('top', (event.pageY – 28) + 'px');
})
.on('mouseout', function() {
d3.select(this)
.transition()
.duration(200)
.attr('r', 5)
.attr('fill', '#5470c6');
tooltip.transition()
.duration(500)
.style('opacity', 0);
});
const tooltip = d3.select('body').append('div')
.attr('class', 'tooltip')
.style('opacity', 0);
数据加载与处理
D3支持多种数据格式:
// 加载CSV
d3.csv('data.csv').then(data => {
console.log(data);
});
// 加载JSON
d3.json('data.json').then(data => {
console.log(data);
});
// 加载TSV
d3.tsv('data.tsv').then(data => {
console.log(data);
});
数据转换:
// 过滤数据
const filteredData = data.filter(d => d.value > 50);
// 排序数据
const sortedData = data.sort((a, b) => a.value – b.value);
// 聚合数据
const groupedData = d3.group(data, d => d.category);
动画与过渡效果
D3的过渡效果非常强大:
d3.selectAll('rect')
.transition()
.duration(1000)
.delay((d, i) => i * 100)
.attr('height', d => d * 10)
.attr('fill', '#91cc75');
复杂动画:
d3.select('circle')
.transition()
.duration(750)
.ease(d3.easeBounce)
.attr('cx', 300)
.transition()
.duration(500)
.attr('cy', 200);
实战案例:力导向图
力导向图是D3的经典应用:
const nodes = [
{id: 'A', group: 1},
{id: 'B', group: 1},
{id: 'C', group: 2},
{id: 'D', group: 2},
{id: 'E', group: 3}
];
const links = [
{source: 'A', target: 'B'},
{source: 'B', target: 'C'},
{source: 'C', target: 'D'},
{source: 'D', target: 'E'},
{source: 'E', target: 'A'}
];
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 link = svg.append('g')
.selectAll('line')
.data(links)
.enter().append('line')
.attr('stroke', '#999')
.attr('stroke-width', 1);
const node = svg.append('g')
.selectAll('circle')
.data(nodes)
.enter().append('circle')
.attr('r', 5)
.attr('fill', d => colorScale(d.group))
.call(d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended));
simulation.on('tick', () => {
link
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y);
node
.attr('cx', d => d.x)
.attr('cy', d => d.y);
});
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
常见问题与解决方案
Q1: D3和ECharts该怎么选?
- ECharts适合快速开发,开箱即用
- D3适合高度定制化的复杂可视化
Q2: 如何处理大数据量?
- 使用Web Worker进行数据处理
- 采用渐进渲染策略
- 考虑使用Canvas代替SVG
Q3: 如何响应式调整图表?
function resize() {
const width = window.innerWidth;
const height = window.innerHeight;
svg.attr('width', width).attr('height', height);
// 更新比例尺和元素位置
}
window.addEventListener('resize', resize);
总结
D3.js是一个功能强大的数据可视化库,虽然学习曲线较陡,但掌握后能实现各种炫酷效果。今天我们学习了:
D3的生态系统非常丰富,还有很多高级功能等待你去探索。下一篇我们将聊聊图表性能优化的技巧!