Skip to content

D3.js - 绘制图表

通过操作 SVG 元素,D3.js 在创建各种静态和交互式图表方面非常灵活。常见的图表类型包括:

  • 柱状图 (Bar Charts)
  • 散点图 (Scatter Plots,通常使用圆形)
  • 饼图 (Pie Charts) 和环形图 (Donut Charts)
  • 折线图 (Line Charts)
  • 气泡图 (Bubble Charts) 等。

本章将指导您使用 D3.js 和 SVG 绘制一些基本的图表类型。

柱状图 (Bar Charts) 用于显示和比较分类数据,其中每个柱子的长度或高度与其值成比例。x 轴通常表示类别,而 y 轴(对于水平柱状图则是 x 轴)表示测量值。

让我们创建一个简单的水平柱状图。我们将使用 SVG 的 <rect> 元素作为柱子,使用 <text> 元素作为标签。

步骤 1: 定义 HTML 结构和样式。创建一个 HTML 文件 (例如,barchart.html)。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3 Bar Chart</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
.bar-chart-container svg {
border: 1px solid #ccc; /* Optional: to see SVG boundary */
}
.bar-chart-container .bar rect {
fill: steelblue;
}
.bar-chart-container .bar rect:hover {
fill: orange;
}
.bar-chart-container .bar text {
fill: black;
font: 10px sans-serif;
text-anchor: start; /* For labels next to bars */
dominant-baseline: middle;
}
</style>
</head>
<body>
<div class="bar-chart-container">
<h3>Simple Horizontal Bar Chart</h3>
<div id="chart-area"></div>
</div>
<script>
// D3 code will go here
</script>
</body>
</html>

步骤 2: JavaScript - 定义数据和图表参数。

// 在 barchart.html 文件底部 <script> 标签内
const data = [10, 5, 12, 15, 8, 10];
const chartWidth = 400;
const barHeight = 20;
const barPadding = 5; // 柱子之间的间距
const labelOffset = 5; // 文本标签与柱子末端的间距
const chartHeight = data.length * (barHeight + barPadding);
// 创建一个用于计算柱子宽度的比例尺 (将数据值映射到像素)
// 注意:对于更健壮的图表,请使用 d3.scaleLinear().domain([0, d3.max(data)]).range([0, chartWidth - labelOffsetArea])
// 为简单起见,这里我们将使用手动计算的 scaleFactor 作为非常基础的示例。
const scaleFactor = (chartWidth - 50) / d3.max(data); // 50 是为标签预留的任意空间

一种更健壮的缩放方法是使用 d3.scaleLinear():const xScale = d3.scaleLinear().domain([0, d3.max(data)]).range([0, chartWidth - 50]); 然后柱子的宽度将是 d => xScale(d)。

步骤 3: 创建 SVG 和柱子组 (group)。

const svg = d3.select("#chart-area")
.append("svg")
.attr("width", chartWidth)
.attr("height", chartHeight);
const bars = svg.selectAll(".bar")
.data(data)
.enter()
.append("g")
.attr("class", "bar")
.attr("transform", (d, i) => `translate(0, ${i * (barHeight + barPadding)})`);

步骤 4: 添加 <rect> 元素作为柱子。

bars.append("rect")
.attr("width", d => d * scaleFactor) // 如果使用 d3.scaleLinear,则改为 xScale(d)
.attr("height", barHeight);

步骤 5: 添加 <text> 元素作为标签。

bars.append("text")
.attr("x", d => (d * scaleFactor) + labelOffset) // 如果使用 d3.scaleLinear,则改为 xScale(d) + labelOffset
.attr("y", barHeight / 2)
// .attr("dy", ".35em") // dominant-baseline: middle 通常效果更好
.text(d => d);

完整的 barchart.html 文件 (包含脚本部分):

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3 Bar Chart</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
.bar-chart-container svg {
border: 1px solid #ccc;
}
.bar-chart-container .bar rect {
fill: steelblue;
}
.bar-chart-container .bar rect:hover {
fill: orange;
}
.bar-chart-container .bar text {
fill: black;
font: 10px sans-serif;
text-anchor: start;
dominant-baseline: middle;
}
</style>
</head>
<body>
<div class="bar-chart-container">
<h3>Simple Horizontal Bar Chart</h3>
<div id="chart-area"></div>
</div>
<script>
const data = [10, 5, 12, 15, 8, 10];
const chartWidth = 400;
const barHeight = 20;
const barPadding = 5;
const labelOffset = 5;
const chartHeight = data.length * (barHeight + barPadding);
const scaleFactor = (chartWidth - 50) / d3.max(data);
const svg = d3.select("#chart-area")
.append("svg")
.attr("width", chartWidth)
.attr("height", chartHeight);
const bars = svg.selectAll(".bar")
.data(data)
.enter()
.append("g")
.attr("class", "bar")
.attr("transform", (d, i) => `translate(0, ${i * (barHeight + barPadding)})`);
bars.append("rect")
.attr("width", d => d * scaleFactor)
.attr("height", barHeight);
bars.append("text")
.attr("x", d => (d * scaleFactor) + labelOffset)
.attr("y", barHeight / 2)
.text(d => d);
</script>
</body>
</html>

在浏览器中打开此文件时,将显示一个简单的水平柱状图。每个柱子的长度对应其数据值,数据值会打印在柱子旁边。

此示例演示了如何绘制圆形,其半径基于数据值,并水平排列。

步骤 1: HTML 结构 (例如,circlechart.html)。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3 Circle Chart</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
#circle-chart-area svg {
border: 1px solid #ccc;
}
#circle-chart-area .data-circle {
stroke: black;
stroke-width: 1px;
}
#circle-chart-area .data-label {
font: 10px sans-serif;
text-anchor: middle;
}
</style>
</head>
<body>
<h3>Simple Circle Chart</h3>
<div id="circle-chart-area"></div>
<script>
// D3 code here
</script>
</body>
</html>

步骤 2: JavaScript - 数据和参数。

// 在 <script> 标签内
const circleData = [10, 20, 30, 15, 25];
const circleColors = ['green', 'purple', 'yellow', 'blue', 'red'];
const svgWidth = 500;
const svgHeight = 150; // 容纳最大圆形和标签所需的高度
const circleYPosition = svgHeight / 2; // 垂直居中圆形
const circleSpacing = 100; // 圆形中心点之间的水平间距

步骤 3: 创建 SVG 和每个圆形-标签对的组 (group)。

const svgCircles = d3.select("#circle-chart-area")
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight);
const circleGroups = svgCircles.selectAll(".circle-group")
.data(circleData)
.enter()
.append("g")
.attr("class", "circle-group")
.attr("transform", (d, i) => `translate(${i * circleSpacing + 50}, 0);`); // +50 作为初始内边距

步骤 4: 添加 <circle> 元素。

circleGroups.append("circle")
.attr("class", "data-circle")
.attr("cy", circleYPosition)
.attr("r", d => d * 1.5) // 基于数据并经过缩放的半径
.attr("fill", (d, i) => circleColors[i % circleColors.length]);

步骤 5: 添加 <text> 标签。

circleGroups.append("text")
.attr("class", "data-label")
.attr("y", circleYPosition)
// .attr("dy", d => (d * 1.5) + 15) // 将标签置于圆形下方,根据需要调整
.attr("dy", ".35em") // 如果不偏移到圆形下方,则在 cy 坐标上居中文本
.text(d => d);

完整的 circlechart.html 文件中的脚本部分:

const circleData = [10, 20, 30, 15, 25];
const circleColors = ['green', 'purple', 'yellow', 'blue', 'red'];
const svgWidth = 500;
const svgHeight = 150;
const circleYPosition = svgHeight / 2;
const circleSpacing = (svgWidth - 50) / circleData.length; // Dynamic spacing
const svgCircles = d3.select("#circle-chart-area")
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight);
const circleGroups = svgCircles.selectAll(".circle-group")
.data(circleData)
.enter()
.append("g")
.attr("class", "circle-group")
.attr("transform", (d, i) => `translate(${i * circleSpacing + circleSpacing/2 + 25}, 0)`); // Center in allocated space, +25 for initial margin
circleGroups.append("circle")
.attr("class", "data-circle")
.attr("cy", circleYPosition)
.attr("r", d => d * 1.5)
.attr("fill", (d, i) => circleColors[i % circleColors.length]);
circleGroups.append("text")
.attr("class", "data-label")
.attr("y", circleYPosition)
.attr("dy", ".35em")
.text(d => d);

这将渲染一系列彩色圆形,每个圆形的半径与其数据值成比例,数据值本身则打印在圆形中心。

饼图 (Pie Charts) 将数据表示为圆形的切片,每个切片的角度与其相对于总值的比例成正比。D3 提供了 d3.pie() 用于计算角度,以及 d3.arc() 用于为切片生成 SVG path 数据。

步骤 1: HTML 结构和样式 (例如,piechart.html)。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3 Pie Chart</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
#pie-chart-area svg {
border: 1px solid #ccc;
}
.pie-slice path {
stroke: #fff; /* White border between slices */
stroke-width: 1px;
}
.pie-label text {
font: 12px sans-serif;
text-anchor: middle;
fill: white;
}
.chart-title {
font: bold 16px sans-serif;
text-anchor: middle;
}
</style>
</head>
<body>
<h3>Population Distribution (Pie Chart)</h3>
<div id="pie-chart-area"></div>
<script>
// D3 code for pie chart
</script>
</body>
</html>

步骤 2: 数据和图表参数。我们将使用一个示例数据集 (例如,人口数据)。在实际场景中,您可能使用 d3.csv() 或 d3.json() 从 CSV 或 JSON 文件加载数据,这些函数返回 Promise。

// 在 <script> 标签内
const populationData = [
{ state: "UP", percent: 20.00 },
{ state: "MH", percent: 15.00 },
{ state: "BR", percent: 12.50 },
{ state: "WB", percent: 10.00 },
{ state: "MP", percent: 8.00 },
{ state: "Other", percent: 34.50 }
];
const pieWidth = 400;
const pieHeight = 400;
const radius = Math.min(pieWidth, pieHeight) / 2 - 10; // -10 作为外边距

步骤 3: 创建 SVG 和一个用于饼图的组 (group) 元素,并居中。

const svgPie = d3.select("#pie-chart-area")
.append("svg")
.attr("width", pieWidth)
.attr("height", pieHeight)
.append("g") // 用于居中饼图的组
.attr("transform", `translate(${pieWidth / 2}, ${pieHeight / 2})`);

步骤 4: 颜色比例尺和饼图生成器。

const colorScale = d3.scaleOrdinal(d3.schemeCategory10); // 内置颜色方案
const pieGenerator = d3.pie()
.value(d => d.percent) // 访问器,用于获取值以确定切片大小
.sort(null); // 可选:保留原始数据顺序或按值排序

步骤 5: 用于绘制切片的圆弧生成器 (Arc Generator)。

const arcGenerator = d3.arc()
.innerRadius(0) // 饼图内半径为 0;环形图则 > 0
.outerRadius(radius);

步骤 6: 用于定位标签的圆弧生成器 (可选,用于将标签放置在切片内部/外部)。

const labelArcGenerator = d3.arc()
.innerRadius(radius * 0.6) // 将标签放置在切片内部
.outerRadius(radius * 0.6);

步骤 7: 绑定数据并绘制切片。

const arcs = svgPie.selectAll(".pie-slice")
.data(pieGenerator(populationData))
.enter()
.append("g")
.attr("class", "pie-slice");
arcs.append("path")
.attr("d", arcGenerator)
.attr("fill", d => colorScale(d.data.state));

步骤 8: 为切片添加标签。

arcs.append("text")
.attr("class", "pie-label")
.attr("transform", d => `translate(${labelArcGenerator.centroid(d)})`) // 使用 labelArc 的质心 (centroid) 定位
.text(d => `${d.data.state} (${d.data.percent}%)`)
.each(function(d) { // 如果切片太小则隐藏标签
const angle = d.endAngle - d.startAngle;
if (angle < 0.25) { // 角度阈值(以弧度为单位),根据需要调整
d3.select(this).remove();
}
});

步骤 9: 添加标题 (可选)。

d3.select("#pie-chart-area svg") // 再次选择主 SVG 元素
.append("text")
.attr("class", "chart-title")
.attr("x", pieWidth / 2)
.attr("y", 20) // 定位在顶部
.text("State Population Distribution");

完整的 piechart.html 文件中的脚本部分将结合这些 JavaScript 代码片段。查看时,它会显示一个表示人口分布的饼图,切片上带有标签。

这些示例提供了基础。实际图表通常涉及更复杂的比例尺 (scales)、坐标轴 (axes)、图例 (legends) 和交互 (interactions),所有这些 D3.js 都可以处理。