绘制第一个D3图表
从数据到可视化:第一个完整图表
本章将通过创建基础柱状图,一起来体验D3数据可视化的完整流程。以下是我们将构建的图表效果:
一、核心概念:SVG与数据绑定的可视化实现
SVG基础认知
<svg width="500" height="300"> <!-- SVG画布 -->
<rect x="0" y="0" width="100" height="50" fill="steelblue"/> <!-- 矩形 -->
<text x="10" y="20" font-size="12">数据标签</text> <!-- 文本 -->
</svg>
- SVG坐标系:以左上角为原点(0,0),X轴向右,Y轴向下
- 图形元素:
<rect>
矩形、<text>
文本、<path>
路径等 - 分组容器:
<g>
元素用于组合图形,可统一设置变换属性
图表容器规范
const margin = { top: 30, right: 20, bottom: 40, left: 50 };
const width = 600 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
const svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
const chart = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
最佳实践:通过margin对象管理边距,保证绘图区域与容器的清晰分离
二、基础示例:水平柱状图
👇 完整实现代码
<!DOCTYPE html>
<html>
<head>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
.bar { transition: all 0.3s; }
.bar:hover { opacity: 0.8; }
</style>
</head>
<body>
<svg width="600" height="300"></svg>
<script>
// 模拟数据
const dataset = [730, 530, 330, 230, 130];
// 1. 选择容器
const svg = d3.select("svg");
// 2. 创建图表区域
const chart = svg.append("g")
.attr("transform", "translate(50, 30)");
// 3. 数据绑定与图形创建
chart.selectAll(".bar")
.data(dataset)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", 0)
.attr("y", (d, i) => i * 35) // 每个柱形间隔35px
.attr("width", d => d) // 直接使用数据值作为宽度
.attr("height", 25) // 固定高度
.attr("fill", "#4CAF50"); // 初始颜色
</script>
</body>
</html>
👇 运行效果:
三、图表优化三部曲
1. 添加坐标轴(简单实现)
// X轴(后续章节会优化)
chart.append("line")
.attr("x1", 0)
.attr("y1", dataset.length * 35)
.attr("x2", d3.max(dataset))
.attr("y2", dataset.length * 35)
.attr("stroke", "#333")
.attr("stroke-width", 1);
2. 添加数据标签
chart.selectAll(".label")
.data(dataset)
.enter()
.append("text")
.attr("class", "label")
.attr("x", d => d + 5) // 在柱形右侧显示
.attr("y", (d, i) => i * 35 + 18)
.text(d => d)
.attr("font-size", "12px")
.attr("fill", "#666");
3. 样式美化
.bar {
rx: 3px; /* 圆角 */
filter: drop-shadow(2px 2px 2px rgba(0,0,0,0.1));
}
.label {
font-family: Arial;
dominant-baseline: middle; /* 垂直居中 */
}
👇 优化后效果:
四、交互增强:鼠标响应
1. 悬停效果
d3.selectAll(".bar")
.on("mouseover", function() {
d3.select(this)
.attr("fill", "#FF5722");
})
.on("mouseout", function() {
d3.select(this)
.attr("fill", "#4CAF50");
});
👇 优化后效果:
2. 简单工具提示
// 添加提示框
const tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("visibility", "hidden")
.style("background", "#fff")
.style("padding", "5px 10px")
.style("border-radius", "4px")
.style("box-shadow", "0 2px 4px rgba(0,0,0,0.2)");
// 交互逻辑
d3.selectAll(".bar")
.on("mouseover", function(event, d) {
tooltip
.style("visibility", "visible")
.html(`数值:${d}`);
})
.on("mousemove", function(event) {
tooltip
.style("left", `${event.pageX + 10}px`)
.style("top", `${event.pageY - 20}px`);
})
.on("mouseout", function() {
tooltip.style("visibility", "hidden");
});
👇 优化后效果:
五、当前实现的问题分析
1. 数据直接映射的问题
.attr("width", d => d) // 当数据超过容器宽度时会出现显示异常
解决方案预告:下一章将使用d3.scaleLinear()
比例尺解决此问题
2. 硬编码间距问题
.attr("y", (d, i) => i * 35) // 35是手动计算的间距值
优化方向:根据容器高度动态计算间距
小结
SVG绘图流程:
核心方法链:
selection.data(data) .enter() .append(element) .attr(property, value)
可视化要素:
- 图表容器与边距管理
- 数据到图形的映射关系
- 基础交互实现
下章预告:比例尺(Scale)的魔法
- 线性比例尺
scaleLinear
的应用 - 序数比例尺
scaleBand
实现自动间距 - 颜色比例尺
scaleOrdinal
的妙用 - 坐标轴的自动化生成