Skip to content

D3.js - 工作示例

本章演示如何结合多个 D3.js 概念来创建一个动态柱状图。我们将使用一个示例数据集,加载它,然后渲染一个具有鼠标悬停交互和入场动画的柱状图。

首先,创建一个 population-data.csv 文件(如果上一章中没有的话):

year,population
2006,40
2008,45
2010,48
2012,51
2014,53
2016,57
2017,62

现在,让我们在一个 HTML 文件中逐步构建这个柱状图。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3 Animated Bar Chart</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
body { font-family: Arial, sans-serif; }
.bar {
fill: steelblue;
transition: fill 0.2s ease-out;
}
.bar:hover, .bar.highlight {
fill: orange;
}
.chart-title {
font-size: 18px;
font-weight: bold;
text-anchor: middle;
}
.axis-label {
font-size: 12px;
text-anchor: middle;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 10px;
}
.tooltip {
position: absolute;
text-align: center;
padding: 8px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none; /* 对于柱子上的鼠标事件至关重要 */
opacity: 0;
}
</style>
</head>
<body>
<div id="chart-container"></div>
<script>
// D3 代码如下
</script>
</body>
</html>
const container = d3.select("#chart-container");
const margin = {top: 60, right: 30, bottom: 70, left: 70};
const width = 600 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
const svg = container.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})`);
// Tooltip div
const tooltip = d3.select("body").append("div")
.attr("class", "tooltip");

X 轴使用 d3.scaleBand()(年份,类别型),Y 轴使用 d3.scaleLinear()(人口,定量型)。

const xScale = d3.scaleBand()
.range([0, width])
.padding(0.2); // 柱子之间的间隔
const yScale = d3.scaleLinear()
.range([height, 0]); // Y 轴是反向的(0 在顶部)
d3.csv("population-data.csv", d => {
// 转换数据类型
return {
year: d.year, // 将年份保留为字符串用于 scaleBand
population: +d.population // 将人口转换为数字
};
}).then(data => {
// 检查数据
console.log(data);
// 根据数据更新比例尺的域
xScale.domain(data.map(d => d.year));
yScale.domain([0, d3.max(data, d => d.population)]);
// 调用函数绘制图表元素(将在下一步定义)
drawChart(data);
}).catch(error => {
console.error("加载或处理数据时出错:", error);
container.text("未能加载图表数据。");
});

步骤 5:绘制图表元素(坐标轴和柱子)

Section titled “步骤 5:绘制图表元素(坐标轴和柱子)”
function drawChart(data) {
// 添加 X 轴
svg.append("g")
.attr("class", "axis x-axis")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(xScale))
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-45)");
// 添加 Y 轴
svg.append("g")
.attr("class", "axis y-axis")
.call(d3.axisLeft(yScale));
// 添加柱子并带有入场动画
svg.selectAll(".bar")
.data(data)
.join("rect")
.attr("class", "bar")
.attr("x", d => xScale(d.year))
.attr("width", xScale.bandwidth())
.attr("y", d => yScale(0)) // 为动画效果从 y=0 开始
.attr("height", 0) // 为动画效果从 height=0 开始
.on("mouseover", handleMouseOver)
.on("mouseout", handleMouseOut)
.transition()
.duration(800)
.ease(d3.easeBounceOut) // 一个有趣的缓动函数
.delay((d, i) => i * 100) // 交错动画
.attr("y", d => yScale(d.population))
.attr("height", d => height - yScale(d.population));
// 添加图表标题
svg.append("text")
.attr("class", "chart-title")
.attr("x", width / 2)
.attr("y", 0 - (margin.top / 2))
.text("Population Growth Over Years");
// 添加 X 轴标签
svg.append("text")
.attr("class", "axis-label")
.attr("x", width / 2)
.attr("y", height + margin.bottom * 0.8)
.text("Year");
// 添加 Y 轴标签
svg.append("text")
.attr("class", "axis-label")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left * 0.8)
.attr("x", 0 - (height / 2))
.text("人口 (百万,虚构)");
}

步骤 6:鼠标事件处理器(Tooltip 和高亮)

Section titled “步骤 6:鼠标事件处理器(Tooltip 和高亮)”
function handleMouseOver(event, d) {
d3.select(this).classed("highlight", true);
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(`年份: ${d.year}<br/>人口: ${d.population}M`)
.style("left", (event.pageX + 5) + "px")
.style("top", (event.pageY - 28) + "px");
}
function handleMouseOut(event, d) {
d3.select(this).classed("highlight", false);
tooltip.transition()
.duration(500)
.style("opacity", 0);
}

将所有 JavaScript 代码放在 HTML 文件中的 <script> 标签内。在浏览器中打开该文件时,您应该会看到一个柱状图。柱子会动画显示到其位置,并且将鼠标悬停在柱子上会高亮显示该柱子并显示包含其数据的 Tooltip。

  • **模块化结构:**分离设置、数据加载和绘图等关注点。
  • **边距和 SVG 分组:**使用 D3 的边距惯例进行布局。
  • 比例尺(Scales):d3.scaleBand 用于 X 轴的类别型数据,d3.scaleLinear 用于 Y 轴的定量型数据。
  • **坐标轴(Axes):**使用 d3.axisBottom 和 d3.axisLeft 生成坐标轴。
  • **数据加载(d3.csv):**异步获取和解析 CSV 数据。
  • **数据连接(selection.join):**根据数据创建柱子。
  • **过渡和缓动(Transitions and Easings):**使柱子入场时具有动画效果(.transition()、.duration()、.ease()、.delay())。
  • **事件处理(Event Handling):**使用 .on("mouseover", ...) 和 .on("mouseout", ...) 实现交互。
  • **Tooltips:**创建一个跟随鼠标的简单 HTML div Tooltip。

此示例提供了一个可靠的模板。您可以通过添加排序、过滤或使用新数据更新图表等功能进一步增强它。