d3.js

D3:Data-Driven Documents
• 通过D3提供的接口来基于数据操控文档的各个图元。

标题对于D3(本讲解)最为重要的标签,主要操作的对象(画布)

HTML - 导入D3.js
D3.js作为JavaScript的外库,必须先将其导入,如:

  • Python的import,C/C++的include、

  • Java的import、node.js的require… … …

  • 通过Script标签导入

    1. 直接通过互联网链接
      https://d3js.org/d3.v5.min.js
    2. 通过本地服务器链接(推荐)
      ./d3.min.js
    3. 通过unpkg链接
      https://unpkg.com/browse/[email protected]/dist/d3.js

    尽可能使用本地的d3.min.js库。

svg-可缩放矢量模型

  • svg d3用来绘制的"画布"
  • 可缩放矢量图形(英语:scalable vector graphics,svg)
  • svg是d3.js主要操作的对象
    • const svg=de3.select(‘svg’)
    • d3.js获取svg对象
  • svg同时也是一个容器,用于包含画在上面的各个图元
  • svg作为矢量图,不会随着图片的缩放而发生失真

d3.js_第1张图片

引入svg

  <svg height="200" width="200" style="display:block;margin:0 auto">
    <g transfrom="translate(0,60)">
      <rect width=100 height=100 fill="#eee" />
      <circle r=15 fill="#72bf67" cx=25 cy=30>circle>
      <circle r=15 fill="RGB(100,149,237)" cx=75 cy=30>circle>
      <g transform="translate(15,60) rotate(10)">
        <path d="M0,0 A40,40 10 0,0 65,0" fill="none" stroke="gray" stroke-width=5>path>
      g>
    g>
  svg>

HTML–文档对象模型

  • HTML -> DOM
  • DOM -> Document Object Model
  • 对于根节点的操作会影响到子节点;
  • 最常用的父节点 中的
    • Axis可封装成一个group
    • Legend(图例)可封装成一个group

d3.js_第2张图片

https://en.wikipedia.org/wiki/File:DOM-model.svg

JavaScript – D3中的常用接口

  • 模板字符串:
    • let a = 10;
    • let myString = abc-${a}; (myString最终为’abc-10’)
  • 数组 a = [1, 2, 3]
  • 对象 a = {name: ‘Shao-Kui’, age: 24.3, lab: ‘cscg’}
    • D3数据可视化中常见对象数组,如:
      • a = [{name: ‘Shao-Kui’, age: 25.3, dept: ‘cs’},
      • {name: ‘Wen-Yang’, age: 23, dept: ‘cs’},
      • {name: ‘Yuan’, age: 29, dept: ‘cs’}]
  • 数组的排序 a.sort()
    • 可加入回调函数来替代缺省的排序方案,如对日期排序
    • a.sort(function(a,b){ return new Date(b.date) - new Date(a.date); }
  • 数组的查询 a.find( d => d.name === ‘Wen-Yang’)
  • 把字符串转换成数值:+(‘3.14’)
  • D3.js经常读取CSV、JSON等文件,会涉及大量的数组、对象的操作!

D3语法基础概览

  • 使用D3获取、修改、增加与删除节点(图元)
  • 数据的读取 – CSV
  • D3.js的数值计算。
  • 比例尺:
    • 线性比例尺(Linear Scale)
    • “条带”比例尺(Band Scale)
  • 坐标轴的绘制:
    • Margin。
  • Data-Join基础
  • 基于D3的基础语法与Data-Join绘制柱状图

元素(标签)的标识

  • 当我们在一个同学群体中(比如微信群)对某些同学发出通知时
    • 学号为2020123456的同学在东主楼集合
    • 计算机系研一的同学在东主楼集合
  • 在一个群体中,索引个体:
    • 通过唯一的标识索引到唯一的个体
    • 通过共同点索引到一批个体

元素(标签)的标识

  • 操作元素首先需要知道元素的标识
    • 即要得到已有或已经创建的元素
  • 元素的ID
    • 可以唯一找到元素的标识符
  • 元素的Class
    • 人为赋予的“类别”可以标记元素的集合,其中的元素标签可以不相同
  • 元素的标签
    • HTML自带的标签名称,可以找到一批同类别的物体,如所有的“矩形”
    • 使用自带的标签往往难以直接索引到目标元素
    • , , 等

d3.js_第3张图片

使用D3查询SVG

  • d3.select(…)
    • d3.select(‘#rect1’)
    • 查询ID为’rect1’的元素
    • #表示后面的字符串是一个ID
    • 只找一个,若有重名也只返回第一个
  • d3.selectAll(…)
    • d3.selectAll(‘.class1’)
    • 查询所有class是’class1’的元素
    • d3.selectAll(‘rect’)
    • 查询所有标签是’rect’的元素(rect为SVG中的矩形标签)
    • 有多少返回多少
    • 可配合Data-Join选取‘不存在’的图元
  • ID前加‘#’,Class前加‘.’ ,标签名前不加符号。
  • 基于层级的查询:
    • d3.select(‘#maingroup rect’)
    • d3.selectAll('.tick text’)
    • d3.selectAll(‘#secondgroup rect’)
  • 如:’#secondgroup rect’
    • 首先会找到id为secondgroup的标签
    • 进一步找到secondgroup的子标签中是rect的
    • 仍然是对rect做查询,只是结果通过父标签做了筛选
  • d3.select(…)也可用于查询类别,如
    • d3.select(‘.class1’)
    • 但只会返回找到的第一个元素
  • 因此对于class、标签名称的查询建议使用d3.selectAll
  • 对于特定某一个元素的查询建议使用d3.select

使用D3设置SVG中的属性

  • 常见的属性
    • id, class(特殊的属性,可以使用.attr设置)
    • x, y, cx, cy
    • fill, stroke
    • height, width, r (圆的半径)
    • transform -> translate, rotate, scale
  • SVG的属性非常多,且属性的取值 **范围&类型 **各不同
    • tip1: 尽可能记住一些常见的属性,以提高编程速度
    • tip2: 遇到不认识or想要设置某个属性,一定要查阅

https://developer.mozilla.org/zh-CN/docs/Web/SVG/Attribute

  • 屏幕空间的坐标系与常见坐标系不同
    • 左上方为原点
    • Y、X分别垂直向下、水平向右

d3.js_第4张图片

element.attr(…)

  • 设置元素的属性: element.attr(‘attr_name’, ‘attr_value’)
    • 两个参数:属性名、设置的值
    • rect1.attr(‘y’, ‘100’)
    • d3.select(‘#rect1’).attr(‘y’, ‘100’)
  • 获取元素的属性: element.attr(‘attr_name’)
    • 一个参数:属性名
  • 链式调用
    • selection.attr(…).attr(…).attr(…)
    • .attr(…)返回的是选择的图元本身

修改整组属性

  • DOM
    • 父节点的属性会影响子节点
    • 子节点的属性会相对于父节点
  • 下方代码可以直接移动组内所有元素
    • d3.select('#maingroup’)
    • .attr(‘transform’, ‘translate(200, 100)’)

d3.js_第5张图片

d3.js_第6张图片


使用D3 添加&**删除 **SVG元素

  • element.append(…)
    • const myRect = svg.append(‘rect’);
    • const myRect = d3.select(‘#mainsvg’).append(‘rect’)
    • const myRect = d3.select(‘#mainsvg’).append(‘rect’).attr(‘x’, ‘100’)
  • D3的链式添加(调用)
    • const myRect = d3.select(‘#mainsvg’).append(‘g’).attr(‘id’, ‘maingroup’)
    • .append(‘rect’).attr(‘fill’, ‘yellow’)
  • element.remove()
    • 会移除整个标签
  • Tip:在debug的过程中可以考虑使用’opacity’属性hack出移除的效果
    • element.attr(‘opacity’, ‘0’)

操控SVG

d3.js_第7张图片

数据的读取 – CSV数据

  • 第一行为属性列表,后续每行对应一‘条’数据。
  • CSV本质上是纯文本,区别于EXCEL的格式。

d3.js_第8张图片

d3.js_第9张图片

  • d3.csv(…):

    • 读取目标路径下的某一个CSV文件。
    • 例:d3.csv(‘static/data/hello.csv’);
  • d3.csv是一个JavaScript异步函数:

    • 不可以直接获得它的返回值,如:
    • let myData = d3.csv(‘static/data/hello.csv’); ❌
  • d3.csv(‘path/to/data.csv’).then( data => { // ‘数据读取后的代码逻辑’ } )

    • 要通过.then( **data **=> {…} )的方式来获得读取后的数据。
    • then(…)中的 ‘data => {…}’ 是一个函数
    • 函数接受的输入(参数),即data,为读取后的数据。
  • JavaScript异步机制

    • d3.csv作为异步函数,即便没有读取好数据,后面的代码也会继续执行
    • d3.csv被调用后,其返回值是一个JavaScript的‘Promise’对象(object)
    • Promise‘询问’:数据读取好了之后要做什么?‘做什么’即对应.then()中函数的内容。
  • 代码调用示例:

d3.js_第10张图片

  • 读取后的数据格式(接口)与原本的CSV结构不同。

d3.js_第11张图片

D3.js的数值计算

  • 数据可视化常涉及对数据的处理与计算:
    • 下述三个接口分别用于计算数组的最大值、最小值、[最小值,最大值]。
  • d3.max(array)
    • 返回数组中的最大值。
    • d3.max([5,4,6,1,8,16,9]) // 16
  • d3.min(array)
    • 返回数组中的最小值。
    • d3.min([5,4,6,1,8,16,9]) // 1
  • d3.extent(array)
    • 同时返回最小值与最大值,以数组的形式,即[最小值,最大值]。
    • d3.extent([5,4,6,1,8,16,9]) // [1, 16]
  • 数组中的内容可以是任意对象:
    • 每个对象可能包含多个属性。
    • 具体取哪个属性的最大值通过回调函数来提示d3.max、d3.min与d3.extent。
  • 例:
    • let a = [ {name: ‘Shao-Kui’, age:25, height: 176}, {name:‘Wen-Yang’, age:24, height: 180}, {name:‘Liang Yuan’, age: 29, height: 172}, {name:‘Wei-Yu’, age:23, height: 173}]
    • d3.max(a, d => d.age) // 29
    • d3.max(a, d => d.height) // 180
    • d3.extent(a, d => d.height) // [172, 180]
    • d3.min(a, d => d.age) // 23

比例尺

  • 比例尺用于把实际数据空间映射到屏幕(画布)空间,即两个空间的转化。
  • 常用于映射数据and创建坐标轴。
  • 区别主要在于数据的尺度不同

d3.js_第12张图片
d3.js_第13张图片

Scale - Linear

  • d3.scaleLinear():
    • 定义一个线性比例尺,返回的是一个函数
    • let scale = d3.scaleLinear(); // scale为函数
  • scale.domain([min_d, max_d]).range([min, max]):
    • 设置比例尺的定义域值域
    • 线性比例尺的定义域和值域都是连续的(Continuous),需分别给出最大值与最小值。
    • const scale = d3.scaleLinear().domain([20, 80]).range([0, 120]);
  • 比例尺本质上是一个函数
    • scale(20) // 0
    • scale(50) // 60
  • 常结合读取的数据与d3.max等接口连用:
    • const xScale = d3.scaleLinear() .domain([0, d3.max(data, d => d.value)]).range([0, innerWidth]);

d3.js_第14张图片

d3.js_第15张图片

Scale - Band

  • d3.scaleBand():
    • 定义一个‘条带’比例尺,返回的是一个函数
    • let scale = d3.scaleBand();
  • scale.domain(array).range([min, max]):
    • 设置比例尺的定义域与值域
    • Band比例尺的定义域是离散的(Discrete),值域是连续的。
    • const scale = d3.scaleBand().domain([‘a’, ‘b’, ‘c’]).range([0, 120]);
  • 比例尺本质上是一个函数
    • scale(‘b’) // 40
    • scale(‘c’) // 80

d3.js_第16张图片

  • 常结合JavaScript的array.map接口一起使用:
    • let a = [{name: ‘Shao-Kui’, value:6}, {name:‘Wen-Yang’, value:6}, {name:‘Yuan Liang’, value:16}]
    • a.map(d => d.name) // [‘Shao-Kui’, ‘Wen-Yang’, ‘Yuan Liang’]
    • const yScale = d3.scaleBand().domain(data.map(d => d.name)).range([0, innerHeight])
    • scale.padding(0.1):
      • 设置条带的间距占各自区域的比重。
    • scale.bandwidth():
      • 返回条带的长度。

d3.js_第17张图片

引入坐标轴

  • 一个坐标轴为一个group( ),通常需要两个坐标轴。
  • 坐标轴中包含:
    • 一个 用于横跨坐标轴的覆盖范围
    • 若干个刻度(.tick)
      • 每个刻度也是一个group
    • 每个刻度下属还会包含一个 和一个
      • 用于展示坐标轴的轴线,如左到右或上到下
      • 用于展示坐标轴的刻度值,如实数、姓名、日期
    • (可选)一个标签用以描述坐标轴
  • 坐标轴的定义通常需要比例尺。

d3.js_第18张图片

  • 定义坐标轴(获得结果仍是函数):

    • const yAxis = d3.axisLeft(yScale);
    • const xAxis = d3.axisBottom(xScale);
    • axisLeft:左侧坐标轴。
    • axisBottom:底侧坐标轴。
    • 坐标轴的刻度对应比例尺的定义域。
    • 坐标轴在画布的绘制对应比例尺的值域。
    • 仅是对坐标轴的定义,还未绘制。
  • 绘制坐标轴:

    • const yAxisGroup = g.append(‘g’).call(yAxis);
    • const xAxisGroup = g.append(‘g’).call(xAxis);
    • 实际配置后会发现 中增添了与坐标轴相关的元素
  • 任何坐标轴在初始化之后会默认放置在坐标原点,需要进一步的平移。

  • 关于 selection.call(…)

  • 函数的输入为另一个函数

  • 另一个函数以selection本身(即图元)作为输入

  • 另一个函数中会根据函数体的内容修改selection对应的图元。

  • 定义一个空白的 ,D3会帮助我们定义好 另一个函数,我们通过.call(…)让 得以在 另一个函数中修改。

    • const yAxis = d3.axisLeft(yScale);
    • const yAxisGroup = g.append(‘g’).call(yAxis);

配置坐标轴

  • 可以对坐标轴的风格进行修改:
    • 坐标轴本质上是图元的集合。
    • d3.selectAll(‘.tick text’).attr(‘font-size’, ‘2em’);
    • .tick是D3对于坐标轴定义的统一class
  • 坐标轴的标签加入不在D3-Axis接口的负责范围内:
    • 通过对坐标轴的 标签 .append(‘text’)来实现
    • (左)纵轴坐标需要 .attr(‘transform’, ‘rotate(-90)’) 来旋转
    • 纵轴坐标旋转后,x / y 会颠倒甚至取值范围相反
    • 回忆DOM:父节点的属性会影响子节点,而坐标轴默认的’fill’属性是 ‘none’,因此请一定手动设置文字颜色 .attr(‘fill’, ‘black’)

引入坐标轴 - Margin

  • SVG对于D3.js是一个“画布”。
  • SVG范围外的任何内容属于画布之外,浏览器将不予显示。
    • 然而坐标轴通常初始化在所在父节点的左上角。
  • 定义Margin:
    • const margin = {top: 60, right: 30, bottom: 60, left: 200}
  • 计算实际操作的 inner 长/宽
    • const innerWidth = width - margin.left - margin.right;
    • const innerHeight = height - margin.top - margin.bottom;
  • 在SVG下额外定义一个组作为新的根节点
    • const g = svg.append(‘g’).attr(‘id’, ‘maingroup’).attr(‘transform’, translate(${margin.left}, ${margin.top}));
  • Tip: HTML确实在样式表中提供margin属性,然而设置其他图元的位置,仍需要计算innerWidth(Height)。

引入坐标轴

  • 调用示例:
    • 比例尺可通过坐标轴可视化。

d3.js_第19张图片

Data-Join

  • 本质上是将数据与图元进行绑定:

    • 每个国家的人数绑定到矩形的长度;
    • 疫情感染的人数比例绑定到圆的半径;
    • 产品的销量绑定到矩形的长度;
    • 各类别商品的销售占比绑定到扇形的弧度。
  • Why?

    • 以数据为中心(Data-Driven)的可视化操作:
      • 根据数据自动调整图元的属性。
      • .attr(…)接口可基于图元自己绑定的数据自动调整属性值
    • 数据发生变化时可以自动对图元增删改查:
      • 不再需要手动添加、‘修改’、删除图元。
      • 根据数据的增加or删除or更新,自动补充or移除or更新图元。
  • Data-Join并不是必要的操作,不使用Data-Join同样可以画出所有可视化作品。

  • Data-Join只是让D3.js编程变得更高效且语法更简洁。

  • d3.selectAll(‘.class’)**.data( dataArray ) **

  • dataArray在保证是一个数组的前提下可以是任何形式:

    • 例: [0, 2, 32, 18];
    • 例:[{name: ‘Sebastian’, value:384}, {name:’ Ciel’, value:32}, {name:‘Wen-Yang’, value:16}, {name:‘Shao-Kui’, value:19}];
  • .data(…)只考虑数据和图元数目相同的情况:

    • dataArray是一个数组,其中的每‘条’数据会与一个图元绑定。
  • 默认的绑定按照双方的索引顺序:

    • (Data的Key:后续D3中会讨论。)
  • 不调用.data(…),则图元不会与任何数据绑定

  • 数据的更新只需要重新绑定另一个 dataArray 即可。

d3.js_第20张图片

  • 调用形式:
    • **d3.selectAll(‘.class’).data(myData).join(‘图元’).attr(d => …).attr((d, i) => …) **
    • .join(…)会根据数据的条目补全or删除图元。
  • 若有新增的数据,则会自动增加对应图元。
  • 若有修改的数据,则会自动更新对应图元。
  • 若有删除的数据,则会自动移除对应图元。

d3.js_第21张图片

Data-Join – 用函数设置图元属性

  • selection.attr(‘attrbuteName’, ‘value’)
    • 通过值设置属性
  • selection.attr(‘attrbuteName’, (d, i) => {…})
    • 通过函数设置属性,函数的输入为绑定的数据,返回值为图元得到的属性值
    • d为Data-Join中,‘.data(array)’绑定给每个图元的数据。
    • i为Data-Join中,‘.data(array)’绑定图元的顺序,即图元对应原本数组的第几个
    • 例:d3.selectAll(‘rect’).attr(‘width’, (d, i) => 1000 * d.age )
    • 例:d3.selectAll(‘circle’).attr(‘cy’, (d, i) => 200 * i + 30);
    • 由于绑定数据的不同,故得到的结果也不同。
  • 设置图元属性的函数遵循如下规则(顺序性):
    • 函数可仅使用 d => {…},即只有一个参数,但此时函数体无法使用索引。
    • 即使未使用到绑定的数据,如需使用索引,仍需要完整的写出 (d, i) => {…}。

基于D3的基础语法与Data-Join绘制柱状图

  • 数据来源:
    • https://www.kaggle.com/gregorut/videogamesales

d3.js_第22张图片

Tip:颜色 – ‘fill’属性

  • PlanA:人为定义一系列颜色组合
  • PlanB:使用D3提供的颜色组合(见下页)
  • PlanC:采样

d3.js_第23张图片

d3.js_第24张图片

image.png

Tip:D3提供的各种色盘

  • 定义一个离散数据到离散数据的映射
    • 如:每个水果对应到某个颜色

d3.js_第25张图片

  • D3.js的内嵌(自带)配色方案?
    • https://github.com/d3/d3-scale-chromatic

d3.js_第26张图片
d3.js_第27张图片
d3.js_第28张图片

网络数据的数据结构?

  • 网络数据包括节点的集合与边的集合:
    • 节点与边通常分布在不同的文件中,通过节点的ID索引
  • D3.js也没有统一的网络数据结构规范:
    • 只要能整理成D3.js对应接口接受的格式即可
  • 常见的数据形式:
    • 【节点列表】+【连接矩阵】
    • 【节点数、边数与基于ID的连接】
    • 【节点列表】+【边列表】

d3.js_第29张图片【节点数、边数与基于ID的连接】
d3.js_第30张图片d3.js_第31张图片【节点列表】+【边列表】
d3.js_第32张图片d3.js_第33张图片【连接矩阵】

d3力模拟基础

  • d3的力模拟与“transition”是完全不同的两个体系
  • let nodes = [{}, {}, {}, {}, {}, {}];
  • let simulation = d3.forceSimulation(nodes) 定义后会发生…
    • 补全nodes中每个节点的数据结构:
      • 包括index, x, y, vx, vy,后两者为速度。
    • 开始模拟粒子运动:
      • 粒子质量为1。
      • 不断地通过内部timer触发’tick’事件。
    • 根据一系列的‘’来计算每个例子的加速度、速度、位置…
      • ‘力’都是哪来的呢?

image.png

不同力的作用

  • d3.forceManyBody().strength( value ):
    • 粒子之间两两的作用力,类似于‘万有引力’。
    • .strength(value)’用来设置力的大小,value为正互相吸引,为负则互相排斥。
  • d3.forceCenter(w, h).strength( value ):
    • 指向某一个中心的力,会尽可能让粒子向中心靠近。
    • .strength(…)的用法同上。
    • ‘d3.forceCenter(w, h)’中的‘w’与‘h’为中心的位置,通常为画布的中心。
  • d3.forceLink(links).strength(strength).distance(distance):
    • 部分粒子之间的两两作用力,不同于‘d3.forceManyBody’。
    • 'd3.forceLink’中,每个节点仅仅会与一部分节点有力的作用。
    • 有链接的节点间,受力的作用,保持在特定的距离即靠近互斥远离吸引
    • 是否有链接需要通过图的边集合给出。
    • .strength( vs )’ 与 ‘.distance( vd )’分别设置力的大小预期的距离
  • Link要通过一个数据格式给出,即link的source与target。
  • 格式非常类似于‘d3.hierarchy’给出的root.links()

d3.js_第34张图片

编程实例:

d3.js_第35张图片

d3.js_第36张图片


‘Tic-Toc’

  • forceSimulation会通过每次‘tick’来更新当前节点的状态:
    • 状态包括位置、速度、加速度等。
  • 更新后的状态仅仅为‘状态’:
    • 不会反映到任何图元,仅修改数据。
    • 需要添加修改图元属性的回调函数
  • 人为设置每次tick要如何更新图元
    • simulation.on(‘tick’, ticked);
  • 在初始化每个图元后,只要为simulation配置了’tick’的回调,simulation会自动开始模拟。

d3.js_第37张图片

基于‘d3-force’实现力导图

数据来源:http://networkrepository.com/socfb-Caltech36.php

编程实例:

d3.js_第38张图片
d3.js_第39张图片

Tip:带权重的图?

  • d3.forceLink(links).strength(…).distance(…):
    • 本质上根据link的权重设置forceLink的strength与distance。
    • 分别输入回调函数,基于每一个‘link’元素来设置各自的力与距离。
  • 编程实例:

d3.js_第40张图片

你可能感兴趣的:(javascript,信息可视化,开发语言)