Skip to content

D3.js - 比例尺 API

比例尺(Scales)是 D3.js 的基石,它提供的函数可以将输入数据域(Domain)(你的数据值,例如 0 到 100)映射到输出范围(Range)(视觉值,例如 0 到 500 像素,或一组颜色)。这种转换对于将数据适配到 SVG 画布等视觉媒介至关重要。d3-scale 模块提供了各种类型的比例尺。

要使用 D3.js 比例尺 API,需要包含主 D3.js 库,其中包含 d3-scale 模块及其依赖项,如 d3-array、d3-interpolate 等。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3.js Scales Example</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>
<svg id="chart" width="500" height="150"></svg>
<script>
// Your D3.js code using scales will go here
</script>
</body>
</html>
  • 数据域(Domain): 数据域定义了输入数据值的范围。对于定量数据,它通常是两个数字构成的数组 [min, max]。对于定性数据,它是一个包含不同类别名称的数组 ["A", "B", "C"]。
  • 范围(Range): 范围定义了输出视觉值的范围。对于定位,它可能是 [0, width] 或 [height, 0](用于 y 轴)。对于颜色,它可能是一个数组 ["red", "blue"] 或一组颜色代码。

D3 为不同的数据特征和视觉映射提供了几种类型的比例尺:

这些比例尺将连续的、定量的输入数据域映射到连续的输出范围。

  • d3.scaleLinear(): 构造一个线性比例尺。它是定量数据最常用的比例尺,输入和输出之间呈线性关系。scale(value) 返回映射后的值。
  • .domain([min, max]): 设置输入数据域。
  • .range([start, end]): 设置输出范围。
  • .invert(value): 返回对应于某个输出范围值的输入数据域值(反向映射)。
  • .nice(): 将数据域扩展到更容易理解的整数或 round 值。
  • d3.scalePow(): 一个幂次比例尺(output = m * input^k + b)。使用 .exponent(k) 设置幂次。
  • d3.scaleSqrt(): 一个平方根比例尺(相当于 d3.scalePow().exponent(0.5))。
  • d3.scaleLog(): 一个对数比例尺。当数据量级变化很大时非常有用。
  • .base(b): 设置对数底(默认为 10)。
  • d3.scaleTime(): 用于时间数据(JavaScript Date 对象)的线性比例尺。
  • d3.scaleSequential(interpolator): 将连续的数据域(通常是 [0, 1])映射到由插值器(interpolator)函数定义的范围(例如,d3.interpolateViridis 用于颜色方案)。

这些比例尺将离散的输入数据域(类别)映射到离散或连续的输出范围。

  • d3.scaleOrdinal(): 将离散的数据域映射到离散的范围(例如,将类别名称映射到颜色)。如果范围的值少于数据域,它会重复使用。
  • .domain(["A", "B", "C"])
  • .range(["red", "green", "blue"]) 或 d3.schemeCategory10(一个包含 10 种分类颜色的数组)。
  • d3.scaleBand(): 用于定性数据,其输出范围是连续且数值型的,通常用于条形图。它将范围分割成均匀的条带(bands)。
  • .domain(["A", "B", "C"])
  • .range([0, width])
  • .padding(value): 设置条带之间的填充(padding)(值在 0 到 1 之间)。
  • scale.bandwidth(): 返回每个条带的宽度。
  • d3.scalePoint(): 类似于 scaleBand,但用于点标记(例如,坐标轴上的刻度线)。bandwidth() 始终为 0。

这些比例尺将连续的输入数据域映射到离散的输出范围(例如,用于分级统计地图 choropleth maps)。

  • d3.scaleQuantize(): 将连续的数据域划分为均匀的段,并将其映射到范围中的离散值。
  • .domain([0, 100])
  • .range(["low", "medium", "high"])
  • d3.scaleQuantile(): 根据样本分位数将连续的数据域映射到离散范围。值根据其在输入数据中的排名被分配到范围内的各个桶(buckets)。
  • d3.scaleThreshold(): 根据指定的阈值将任意连续数据域子集映射到离散范围值。

让我们使用 d3.scaleLinear() 创建一个简单的条形图,将数据值映射到条的宽度。

步骤 1:定义数据和 SVG 尺寸。

const data = [100, 200, 50, 300, 150];
const svgWidth = 500;
const barHeight = 20;
const barMargin = 1; // 条之间的边距
const svgHeight = (barHeight + barMargin) * data.length;

步骤 2:创建线性比例尺。

const xScale = d3.scaleLinear()
.domain([0, d3.max(data)]) // 输入数据:0 到数据中的最大值
.range([0, svgWidth]); // 输出:0 到 SVG 宽度,用于条的长度

这里,d3.min(data) 和 d3.max(data)(来自 d3-array)用于查找数据域边界很方便。我们将 0 用作数据域的最小值,这在条形图通常从零基线开始时很常见。

步骤 3:设置 SVG 画布。

const svg = d3.select("#chart") // 假设 <svg id="chart"> 存在
.attr("width", svgWidth)
.attr("height", svgHeight);

步骤 4:为每个条创建分组并应用变换。

const barGroups = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.attr("transform", (d, i) => `translate(0, ${i * (barHeight + barMargin)})`);

步骤 5:为条添加 <rect> 元素,使用比例尺确定宽度。

barGroups.append("rect")
.attr("width", d => xScale(d)) // 在这里使用比例尺!
.attr("height", barHeight)
.attr("fill", "steelblue");

步骤 6:为标签添加 <text> 元素,也使用比例尺定位。

barGroups.append("text")
.attr("x", d => xScale(d) + 5) // 将文本定位在条的末端稍微靠后的位置
.attr("y", barHeight / 2)
.attr("dy", ".35em") // 垂直对齐
.text(d => d)
.attr("fill", "black");

完整的示例 HTML:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3.js scaleLinear Example</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
#chart text {
font-size: 10px;
font-family: sans-serif;
}
</style>
</head>
<body>
<svg id="chart"></svg>
<script>
const data = [100, 200, 50, 300, 150];
const svgWidth = 500;
const barHeight = 20;
const barMargin = 1;
const svgHeight = (barHeight + barMargin) * data.length;
const xScale = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([0, svgWidth]);
const svg = d3.select("#chart")
.attr("width", svgWidth)
.attr("height", svgHeight);
const barGroups = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.attr("transform", (d, i) => `translate(0, ${i * (barHeight + barMargin)})`);
barGroups.append("rect")
.attr("width", d => xScale(d))
.attr("height", barHeight)
.attr("fill", "steelblue");
barGroups.append("text")
.attr("x", d => xScale(d) + 5)
.attr("y", barHeight / 2)
.attr("dy", ".35em")
.text(d => d)
.attr("fill", "black");
</script>
</body>
</html>

在浏览器中打开时,此代码将显示一个水平条形图,其中每个条的长度与其数据值成比例,并根据 SVG 宽度进行缩放。文本标签将显示每个条旁边的原始数据值。