「数据可视化 D3系列」入门第六章:比例尺的使用

比例尺的使用

比例尺是 D3.js 中非常重要的概念,它能帮助我们优雅地将数据从定义域映射到可视化的值域中。下一章我们将学习坐标轴的使用,它经常与比例尺配合使用。


一、比例尺是什么?

比例尺是数据可视化的核心工具,它能将"一个区间"的数据(通常是原始数据)映射到"另一个区间"(通常是像素值或颜色)。这种映射关系在数据可视化中极为常见。

典型示例:

  • 数值映射:[0, 1] 对应到 [0, 300],当输入 0.5 时,输出 150
  • 离散映射:[0, 1, 2] 对应到 ["red", "green", "blue"],当输入 2 时,输出 "blue"

在比例尺中,有三个关键概念:

  1. 定义域 (domain):原始数据的取值范围,如 [0, 1] 或 [0, 1, 2]
  2. 值域 (range):映射后的目标取值范围,如 [0, 300] 或 ["red", "green", "blue"]
  3. 对应法则:定义域和值域之间的映射规则

理解这三个概念是掌握比例尺的基础。D3.js 提供了多种比例尺类型,适用于不同的数据映射场景。


二、常用比例尺类型

1. 线性比例尺 (scaleLinear)

线性比例尺是最常用的比例尺类型,它将连续的定义域线性地映射到连续的值域。

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>比例尺的使用</title>
  <script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body></body>
<script>
  // 创建线性比例尺
  var scaleLinear = d3.scaleLinear()
      .domain([0, 3])    // 定义域:输入范围是0到3
      .range([0, 300]);  // 值域:输出范围是0到300像素

  // 测试比例尺
  console.log(scaleLinear(0));  // 输出 0
  console.log(scaleLinear(1));  // 输出 100
  console.log(scaleLinear(2));  // 输出 200
  console.log(scaleLinear(3));  // 输出 300
</script>
</html>

特性说明:

  • 输入值在定义域外时,默认会进行外推计算
  • 可以通过 .clamp(true) 方法限制输出不超过值域范围
  • 支持反向映射 .invert(),可以从输出值反推输入值

2. 序数比例尺 (scaleOrdinal)

序数比例尺用于离散数据的映射,它将离散的定义域映射到离散的值域。

js 复制代码
var colors = ['blue', 'red', 'green', 'pink'];
// 创建序数比例尺
var scaleOrdinal = d3.scaleOrdinal()
  .domain([0, 1, 2, 3])  // 定义域:离散值
  .range(colors);        // 值域:颜色数组

// 应用比例尺创建彩色文本
for (var i = 0; i < colors.length; i++) {
  d3.select('body')
    .append('div')
    .text('请看我的颜色')
    .style('color', scaleOrdinal(i));
}

特性说明:

  • 当输入值不在定义域中时,比例尺会循环使用值域中的值
  • 可以通过 .unknown(value) 方法设置未知输入的默认返回值
  • 常用于类别数据到颜色、符号等的映射

3. 其他常用比例尺类型:

  • scaleBand(): 用于条形图的波段比例尺

  • scaleTime(): 专门用于时间数据的线性比例尺

  • scaleQuantize(): 将连续数据离散化到多个区间

  • scaleSqrt(): 基于平方根的比例尺,适合面积映射


三、实际应用

👇 示例:让我们用比例尺改进上一章的柱状图示例:

html 复制代码
<!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;
    }

    .bar {
      rx: 3px;
      /* 圆角 */
      filter: drop-shadow(2px 2px 2px rgba(0, 0, 0, 0.1));
    }

    .label {
      font-family: Arial;
      dominant-baseline: middle;
      /* 垂直居中 */
    }
  </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. 创建比例尺
    const xScale = d3.scaleLinear()
      .domain([0, d3.max(dataset)])  // 定义域从0到数据最大值
      .range([0, 500]);              // 值域映射到0-500像素

    // 4. 数据绑定与图形创建(使用比例尺)
    chart.selectAll(".bar")
      .data(dataset)
      .enter()
      .append("rect")
      .attr("class", "bar")
      .attr("x", 0)
      .attr("y", (d, i) => i * 35)  // 每个柱形间隔35px
      .attr("width", d => xScale(d)) // 使用比例尺计算宽度
      .attr("height", 25)           // 固定高度
      .attr("fill", "#4CAF50");     // 初始颜色

    // X轴(使用比例尺的最大值)
    chart.append("line")
      .attr("x1", 0)
      .attr("y1", dataset.length * 35)
      .attr("x2", xScale(d3.max(dataset))) // 使用比例尺
      .attr("y2", dataset.length * 35)
      .attr("stroke", "#333")
      .attr("stroke-width", 1);

    // 数据标签(使用比例尺)
    chart.selectAll(".label")
      .data(dataset)
      .enter()
      .append("text")
      .attr("class", "label")
      .attr("x", d => xScale(d) + 5)  // 在柱形右侧显示(使用比例尺)
      .attr("y", (d, i) => i * 35 + 18)
      .text(d => d)
      .attr("font-size", "12px")
      .attr("fill", "#666");

    // 交互效果(保持不变)
    d3.selectAll(".bar")
      .on("mouseover", function () {
        d3.select(this)
          .attr("fill", "#FF5722");
      })
      .on("mouseout", function () {
        d3.select(this)
          .attr("fill", "#4CAF50");
      });

    // 添加提示框(保持不变)
    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");
      });
  </script>
</body>

</html>

👇 效果如下

主要改进点说明

  1. 添加了线性比例尺:

    js 复制代码
    const xScale = d3.scaleLinear()
      .domain([0, d3.max(dataset)])
      .range([0, 500]);
  2. 柱状图宽度使用比例尺:

    js 复制代码
    .attr("width", d => xScale(d)) // 替换原来的直接使用数据值
  3. X轴长度使用比例尺:

    js 复制代码
    .attr("x2", xScale(d3.max(dataset))) // 替换原来的d3.max(dataset)
  4. 数据标签位置使用比例尺:

    js 复制代码
    .attr("x", d => xScale(d) + 5) // 替换原来的d + 5

优势说明:

  • 自动适配数据范围: 当数据集变化时,比例尺会自动调整映射关系

  • 更好的控制: 可以通过调整range值来控制图表的最大宽度

  • 可维护性: 如果需要调整图表尺寸,只需修改比例尺的range值

  • 一致性: 所有基于数据的图形元素使用相同的比例尺,保证视觉一致性


四、比例尺的高级用法

1. 颜色比例尺

D3提供了专门处理颜色渐变的比例尺:

js 复制代码
// 创建颜色比例尺
var colorScale = d3.scaleLinear()
  .domain([0, 10])
  .range(["white", "steelblue"]);

// 使用
colorScale(5);  // 返回中间色

2. 时间比例尺

处理时间数据的专用比例尺:

js 复制代码
var timeScale = d3.scaleTime()
  .domain([new Date(2020, 0, 1), new Date(2020, 11, 31)])
  .range([0, 800]);

timeScale(new Date(2020, 5, 15));  // 返回年中位置

3. 分段比例尺

将连续数据分段映射:

js 复制代码
var quantizeScale = d3.scaleQuantize()
  .domain([0, 100])
  .range(["low", "medium", "high"]);

quantizeScale(30);  // 返回"low"
quantizeScale(60);  // 返回"medium"

小结

  1. 合理设置定义域: 使用 .domain([d3.min(data), d3.max(data)]) 自动适应数据范围
  2. 考虑边距: 在设置值域时预留边距空间
  3. 处理异常值: 使用 .clamp(true) 防止超出范围的值
  4. 保持比例尺的可读性: 给比例尺变量起有意义的名称
  5. 重用比例尺: 在多个可视化中共享相同的比例尺保证一致性

下章预告:结合比例尺使用坐标轴,创建更专业的图表

相关推荐
ywf121510 小时前
前端的dist包放到后端springboot项目下一起打包
前端·spring boot·后端
恋猫de小郭10 小时前
2026,Android Compose 终于支持 Hot Reload 了,但是收费
android·前端·flutter
hpoenixf16 小时前
2026 年前端面试问什么
前端·面试
还是大剑师兰特16 小时前
Vue3 中的 defineExpose 完全指南
前端·javascript·vue.js
泯泷17 小时前
阶段一:从 0 看懂 JSVMP 架构,先在脑子里搭出一台最小 JSVM
前端·javascript·架构
mengchanmian17 小时前
前端node常用配置
前端
华洛18 小时前
利好打工人,openclaw不是企业提效工具,而是个人助理
前端·javascript·产品经理
xkxnq18 小时前
第六阶段:Vue生态高级整合与优化(第93天)Element Plus进阶:自定义主题(变量覆盖)+ 全局配置与组件按需加载优化
前端·javascript·vue.js
A黄俊辉A19 小时前
vue css中 :global的使用
前端·javascript·vue.js
小码哥_常19 小时前
被EdgeToEdge适配折磨疯了,谁懂!
前端