【D3.js in Action 3 精译_044】5.1 饼图和环形图的创建(三):圆弧的绘制

当前内容所在位置:

  • 第五章 饼图布局与堆叠布局 ✔️
    • 5.1 饼图和环形图的创建 ✔️
      • 5.1.1 准备阶段(一)
      • 5.1.2 饼图布局生成器(二)
      • 5.1.3 圆弧的绘制(三) ✔️
      • 5.1.4 数据标签的添加(四)

《D3.js in Action》全新第三版封面

《D3.js in Action》全新第三版封面

【译者按】有了第四章绘制 D3 圆弧的基础后,再来实现本章的环形图就轻松多了,只是在一些细节处理上加入了新的知识点,比如 D3 色阶的配置。一起来看看吧。

5.1.3 圆弧的绘制 Drawing the arcs

有了带注解的数据集,下一步就可以绘制饼图的弧线了。接下来您会发现本节演示内容与上一章中弧线的绘制手法大同小异,因此本节将不再展开相关细节进行演示。如需了解相关细节,详见本书第 4 章内容。

在代码清单 5.4 中,我们先调用 d3.arc() 方法来初始化一个圆弧生成器,然后使用各种访问器函数(accessor functions)来设置环形图的内外半径、环形片段之间的间隙(即填充角)以及圆弧轨迹的圆角半径。如果内圆半径为 0,绘制的就是一个饼图;如若大于 0,则得到一个环形图(donut chart)。

本例与第 4 章采用的绘制策略相比,唯一的区别在于本例中可以在声明圆弧生成器时通过 startAngle()endAngle() 这两个访问器函数配置环形片段的初始角与终止角。而这些值都提前在数据集中算好了,直接使用 d.startAngled.endAngle 就能访问到。

最后,要让圆弧出现在屏幕上,还需要利用 D3 的数据绑定机制,为每个环形片段生成一个 SVG 路径元素 path。如以下代码清单 5.4 所示,我们给每段圆弧都指定了一个具体的类名(即 arc-${year}),然后通过它来选中元素并绑定数据。因为是在 For 循环中创建的环形图,这样一来可以防止 D3 在生成新的环形图时覆盖掉之前的图形。代码的最后,用到了圆弧生成器函数来计算各 path 元素的 d 属性的取值。

代码清单 5.4 生成并绘制圆弧的示例代码(详见 donut-charts.js

const arcGenerator = d3.arc()
  .startAngle(d => d.startAngle) // 配置访问器函数 startAngle() 和 endAngle(),并在带注解的数据集中指定对应取值的 key 值
  .endAngle(d => d.endAngle)     
  .innerRadius(60)
  .outerRadius(100)
  .padAngle(0.02)
  .cornerRadius(3);

const arcs = donutContainer
  .selectAll(`.arc-${year}`) // 利用数据绑定机制为每个片段生成一个 path 元素
  .data(annotatedData)          
  .join("path")                 
    .attr("class", `arc-${year}`)
    .attr("d", arcGenerator); // 调用 arcGenerator() 函数来配置 path 中的 d 属性

5.1.3.1 D3 色阶的用法 Using a color scale

保存项目并在浏览器中查看示例页,会看到绘制的环形图虽然形状有了,但圆弧全是黑色的。原因很简单:SVG 元素的默认填充色就是黑色。为了提高环形图的可读性,接下来将为每种音乐格式配置不同的颜色。

一个操作简单又方便复用的解决方案是给每段圆弧都配置好特定的颜色,即声明一组 色阶(color scale);色阶通常可以由 D3 的 d3.scaleOrdinal() 方法创建。D3 的 序数比例尺(ordinal scales) 能够将某个离散型的定义域映射到同样为离散型的某个值域上。在本例中,定义域即不同音乐格式组成的数组,而值域则是某个与格式一一对应的颜色值数组。

找到 scales.js 文件,并在里面声明一个 D3 序数比例尺,然后赋给一个常量 colorScale。然后提取 formatsInfo 数组中的格式 ID 字段,作为比例尺的定义域;对于颜色值也做类似处理。您也可以根据自己的喜好进行相应调整。本章将沿用该色阶来设置示例项目中的所有图表。

const colorScale = d3.scaleOrdinal();

const defineScales = (data) => {

  colorScale
    .domain(formatsInfo.map(f => f.id))
    .range(formatsInfo.map(f => f.color));

};

然后再回到 donut-charts.js 文件,并通过指定 path 元素的 fill 属性(attribute)来配置每个环形片段的具体颜色。只需按照下列格式调用上面声明的色阶函数即可:

const arcs = donutContainer
  .selectAll(`.arc-${year}`)
  .data(annotatedData)          
  .join("path")                 
    .attr("class", `arc-${year}`)
    .attr("d", arcGenerator)
    .attr("fill", d => colorScale(d.data.format));

保存项目并再次查看浏览器中的页面效果——看上去还不错!如图 5.7 所示,每段圆弧已按照从大到小的顺序排列好了,这样可以进一步提高图表的可读性。从图中可以清晰地看到在 1975 年、1995 年以及 2013 年三年中,每年的销售额在音乐格式方面发生的结构性转变;每年的主流音乐格式都各不相同。

图 5.7 分别代表 1975 年、1995 年以及 2013 年的环形图效果对比

【图 5.7 分别代表 1975 年、1995 年以及 2013 年的环形图效果对比】

译注
本章示例较前面章节都更为复杂,为了便于演示,作者都省略了完整的实现代码。学习时一定记得在本地搭建好服务器环境跟着文中的进度落地实操。以下为我自己的本地实现代码:

// donut-charts.js:
const drawDonutCharts = (data) => {

  const svg = d3.select("#donut")
    .append("svg")
      .attr("viewBox", `0 0 ${width} ${height}`); 

  // 环形图总容器的声明
  const donutContainers = svg
    .append("g")
      .attr("transform", `translate(${margin.left}, ${margin.top})`);

  // 定义指定的年份数组
  const years = [1975, 1995, 2013];
  // 从数据集中筛除年份列,仅保留音乐格式列
  const formats = data.columns
    .filter((format) => format !== "year");

  years.map((year) => {
    // 添加具体年份的环形图容器(共三个)
    const donutContainer = donutContainers
    .append("g")
      .attr("transform", `translate(${xScale(year)}, ${innerHeight/2})`);

    const yearData = data.filter((d) => d.year === year)[0];
    const formattedData = formats.map((format) => ({
      format,
      sales: yearData[format],
    }));

    // 定义饼图布局函数
    const pieGenerator = d3.pie()
      .value((d) => d.sales);
    // 获取带注解的数据集
    const annotatedData = pieGenerator(formattedData);

    // 定义圆弧生成器
    const arcGenerator = d3.arc()
      .startAngle(d => d.startAngle)
      .endAngle(d => d.endAngle)     
      .innerRadius(60)
      .outerRadius(100)
      .padAngle(0.02)
      .cornerRadius(3);

    // 绘制当前年份对应的环形图
    const arcs = donutContainer
      .selectAll(`.arc-${year}`)
      .data(annotatedData)          
      .join("path")                 
        .attr("class", `arc-${year}`)
        .attr("d", arcGenerator)
        .attr("fill", d => colorScale(d.data.format)); // 应用 D3 色阶
  });        
};

// scales.js:
// Initialize the scales here
const xScale = d3.scaleBand(); // 声明分段比例尺
const colorScale = d3.scaleOrdinal();

const defineScales = (data) => {
  // Define the scales domain and range here
  xScale
    .domain(data.map(d => d.year))
    .range([0, innerWidth]);
  
  colorScale
    .domain(formatsInfo.map(f => f.id))
    .range(formatsInfo.map(f => f.color));
};

最终实测页面效果:

补图:实测本节绘制环形图后的页面最终效果

【补图:实测本节绘制环形图后的页面最终效果】

(第三部分完)



另附:专栏文章连载期间 完全免费,后续 不排除 调整为收费专栏。对 D3.js 感兴趣、或者想要从零开始彻底掌握 D3 的朋友们强烈建议及时关注本专栏,一起学习交流,共同进步!

目前译好的其他章节内容如下(可进入专栏查看详情):

  • 第一部分 D3.js 基础知识
    • 第一章 D3.js 简介(已完结)
      • 1.1 何为 D3.js?
      • 1.2 D3 生态系统——入门须知
      • 1.3 数据可视化最佳实践(上)
      • 1.3 数据可视化最佳实践(下)
      • 1.4 本章小结
    • 第二章 DOM 的操作方法(已完结)
      • 2.1 第一个 D3 可视化图表
      • 2.2 环境准备
      • 2.3 用 D3 选中页面元素
      • 2.4 向选择集添加元素
      • 2.5 用 D3 设置与修改元素属性
      • 2.6 用 D3 设置与修改元素样式
      • 2.7 本章小结
    • 第三章 数据的处理(已完结)
      • 3.1 理解数据
      • 3.2 准备数据
      • 3.3 将数据绑定到 DOM 元素
        • 3.3.1 利用数据给 DOM 属性动态赋值
      • 3.4 让数据适应屏幕
        • 3.4.1 比例尺简介(上篇)
        • 3.4.2 线性比例尺(中篇)
          • 3.4.2.1 基于 Mocha 测试 D3 线性比例尺(DIY 实战)
        • 3.4.3 分段比例尺(下篇)
          • 3.4.3.1 使用 Observable 在线绘制 D3 条形图(DIY 实战)
      • 3.5 加注图表标签(上篇)
        • 3.5.1 人物专访:Krisztina Szűcs(下篇)
      • 3.6 本章小结
    • 第四章 直线、曲线与弧线的绘制
      • 4.1 坐标轴的创建(上篇)
        • 4.1.1 D3 中的边距约定(中篇)
        • 4.1.2 坐标轴的生成(中篇)
          • 4.1.2.1 比例尺的声明(中篇)
          • 4.1.2.2 坐标轴的添加(下篇)
          • 4.1.2.3 轴标签的添加(下篇)
      • 4.2 D3 折线图的绘制
        • 4.2.1 直线生成工具的使用
        • 4.2.2 对数据点作曲线插值处理
      • 4.3 D3 面积图的绘制
        • 4.3.1 面积图生成工具的用法
        • 4.3.2 用标签提高图表的可读性
      • 4.4 D3 弧形图的绘制
        • 4.4.1 D3 中的极坐标系
        • 4.4.2 圆弧生成器的使用
        • 4.4.3 圆弧形心的计算
        • 4.4.4 人物专访:Francis Gagnon、Patricia Angkiriwang 和 Olivia Gélinas
      • 4.5 本章小结
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安冬的码畜日常

您的鼓励是我持续优质内容的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值