D3基础操作

2,863 阅读9分钟

知识点

坐标轴和比例尺

比例尺

连续性

scaleLinear

实际数据映射到屏幕空间

 var xScale = d3.scaleLinear().domain([0,d3.max(dataset)]).range([0,250])
  • domain对应的是原始数据,就是要分析的数据
  • range()对应的是屏幕显示的数据,也就是像素
  • ticks(),是设定整个坐标轴上到底要有几个标记,而不是标记之间的间隔,标记上显示的文字是比例尺的domain()值
  • tickSize()显示网格线,使其覆盖整个显示区域就可以用ticks绘制成网格线。在参考案例中还提供了透明度选项,可以避免网格线影响实际数据的呈现
  • nice() 优化定义域,一般常加
//比例尺
let x = d3.scaleLinear().range([0, width])
let xScale = x.domain([0, 10])
// x轴
let xAxis = svg.append('g')
               .attr('class', 'xAxis')
               .attr('transform', `translate(0, ${height})`)
               .call(d3.axisBottom(xScale))

使用d3.axisTopd3.axisBottom()来控制刻度显示在坐标轴的上方或者下方,

注意:如果是x轴要设置在底部,d3.axisBottom()之后还需要transform平移到画布(0,trueHeight)的位置

scaleTime

d3.scaleTime()类似于d3.scaleLinear()线性比例尺,只不过输入域变成了一个时间轴。

let scale = d3.scaleTime()
              .domain([new Date(2017, 0, 1, 0), new Date(2017, 0, 1, 2)])
              .range([0,100])

时间比例尺较多用在根据时间顺序变化的数据上。另外有一个d3.scaleUtc()是依据世界标准时间(UTC)来计算的

有时候时间是一组数组并不连续的话直接用时间比例尺会显示很多不需要用到的时间点在坐标轴上,可以用先处理好时间格式转换成字符串之后用scaleBand()

let x = d3.scaleTime().range([0, width])
let xScale = x.domain([new Date(2017, 1), new Date(2017, 6)])
// x轴
let xAxis = svg.append('g')
               .attr('class', 'xAxis')
               .attr('transform', `translate(0, ${height})`)
               .call(d3.axisBottom(xScale))

处理时间格式有一个插件 moment.js

scaleQuantize

量化比例尺d3.scaleQuantize()也属于连续性比例尺。定义域是连续的,而输出域是离散的。

let scale = d3.scaleQuantize().domain([0, 10]).range(["small", "medium", "long"]
image.png
image.png

非连续性

原点开始

scaleOrdinal
image.png
image.png

d3.scaleOrdinal()的输入域和输出域都使用离散的数据。

let scale = d3.scaleOrdinal().domain(['jack', 'rose', 'john']).range([10, 20, 30])

也可以用来填充颜色比例尺,使用d3自带的一些配色方案

d3.scaleOrdinal(d3.schemeCategory10)

domain()range()的数据是一一对应的,如果两边的值不一样呢?

image.png
image.png

domain()的值按照顺序循环依次对应range()的值

let height = 400
let width = 600
let x = d3.scaleOrdinal().range([150, 300, 450, 600])
let xScale = x.domain(['北京', '上海', '广州', '深圳'])
// x轴
let xAxis = svg.append('g')
               .attr('transform', `translate(0, ${height})`)
               .call(d3.axisBottom(xScale))

非原点开始

scaleBand
image.png
image.png
  • domain
  • range :设置输出范围
  • round :是否取整
  • rangeRound : 整合range and round
  • paddingInner : 设置paddingInner 【0,1】
  • paddingOuter : 设置paddingOuter 【0,1】
  • padding:整合paddingInner and paddingOuter
  • align : 设置刻度位置 默认0.5 范围【0,1】
  • bandwidth :获取bandwidth
  • step : 获取step 仅仅只是细化了API;padding、align 感觉不错
...其余同上
let x = d3.scaleBand().range([0, width])

基本上,柱状图都会采用这种x坐标轴。

axis.ticks()

从一个值到另一个分割为特定数量的数组,返回一个start到stop之间(包含stop)的接近count+1个值的数组,这个数组具有均匀的间隔(uniformly-spaced),进行了优雅地(nicely)四舍五入(round)。每个值都是1,2或5乘以10的幂(10x)。

场景:当通过比例尺创建一个y坐标轴的时候,可能你只想让你的y轴坐标刻度只显示两个,即只有收尾,怎么办?ticks(1)

注意ticks的分段,ticks的值填写什么,根本不是段数决定的,而是除数决定的,先在脑海中想一下除数是否为1,2,5及其10倍数,(不是的话,放心,得不到你想要的效果,)想一下除出来的结果,再填写这个结果。

https://www.tangshuang.net/3270.html

路径

属性

  • d

    • M = moveto(M X,Y) :将画笔移动到指定的坐标位置
    • L = lineto(L X,Y) :画直线到指定的坐标位置
    • H = horizontal lineto(H X):画水平线到指定的X坐标位置
    • V = vertical lineto(V Y):画垂直线到指定的Y坐标位置
    • C = curveto(C X1,Y1,X2,Y2,ENDX,ENDY):三次贝赛曲线
    • S = smooth curveto(S X2,Y2,ENDX,ENDY):平滑曲率
    • Q = quadratic Belzier curve(Q X,Y,ENDX,ENDY):二次贝赛曲线
    • T = smooth quadratic Belzier curveto(T ENDX,ENDY):映射
    • A = elliptical Arc(A RX,RY,XROTATION,FLAG1,FLAG2,X,Y):弧线
    • Z = closepath():关闭路径 以上所有命令均允许小写字母。大写表示绝对定位,小写表示相对定位
  • fill: 填充颜色

  • stroke:描边颜色

    • stroke-linecap 属性可以让你在线条末端控制图形。你可以选择对接(butt)、方形(square)和圆形(round)
image-20200526145144872.png
image-20200526145144872.png
    • stroke-linejoin 属性也是类似的,但是它控制的是两条线段之间的衔接。你可以在绘制折线时使用它。它有三个值,尖角(miter)、圆角(round)和斜角(bevel)
image-20200526145155814.png
image-20200526145155814.png
  • stroke-width:描边宽度
  • transform="translate(x,y)": 加了描边后需要平移(x=stroke-width/2, y=stroke-width/2)

直线命令

<path d="M10 20 H90 V90 H10 V20"></path>

可以通过闭合路径命令Z简化,代替了最后一笔指向原点

<path d="M10 20 H90 V90 H10 Z"></path>

直线相对命令(小写)

相对命令使用的是小写字母,它们的参数不是指定一个明确的坐标,而是表示相对于它前面的点需要移动多少距离

<path d="M100 20 h 50 v 50 h -50 Z"></path>

D3 Path生成器

https://github.com/d3/d3-shape/tree/v1.3.7

d3.line(…).x(…).y(…) 用于折线图

d3.geoPath().projection() 用于地图

d3.area() 用于主题河流

d3.arc(…).innerRadius(…).outerRadius(…) 用于饼图

d3.lineRadial().angle(…).radius(…) 极坐标系版本的d3.line(…)

数据绑定 Data Join

D3.js绑定数据的三个状态

image-20200525190758320.png
image-20200525190758320.png

绑定元素

    • datum(data)将一个数据str绑定到所有的选择集上
p.datum(str).text((value,index)=>{return `${index} is ${value}` })
    • data(data)绑定一组数据,dataset为数组
p.data(dataset).text((el,i)=>{return el})
  • .data(…)用于将一批图元与一批数据绑定
  • .datum(…)用于给特定的一个图元绑定一个数据
  • 数据集个数与选择集个数的不匹配情况 update/enter/exit image.png

    enter用途:表示添加足够多的的rect元素(匹配数据集个数)

g.selectAll('rect').data(dataset).enter().append('rect')
  • 插入元素

    • append(el):在选择集尾部插入元素
    • insert(el):在选择集前面插入元素
  • 删除元素 remove()

data-join的简洁形式

必须需要设置enter数据的初始图元属性,update会每次重新设置初始值,从而导致动画出现怪的效果

  d3.selectAll('rect').data(data2).join('rect').transition().duration(1500).attr('width',d=>xScale(d.value))

仍旧支持定制

.join(
    enter => enter.append("text").attr("fill", "green").text(d => d),
    update => (), 
    exit => ()
)

查询方式

基于层级的查询,经常在配置坐标轴的代码中使用

d3.selectAll('#new rect'):首先找到id为new的标签,进一步找new中子标签为rect的。

属性

SVG-attribute

数值类型的数据在HTML有字符串存储,需要及时转换 let num = +('2222.4')

Enter

适用场景:有数据没有图元,常用于第一次绑定数据

  • 根据新增的数据生成相应的图元
  • 生成图元的占位,占位的内容需要编程者自行添加(append)
const p = maingroup.selectAll(‘.class').data(data).enter().append(‘’).attr(…)

柱状图用enter实现

  • enter本质上生成指向父节点的指针
  • append操作相当于在父节点后添加指针数量的图元并将其与多出的数据绑定,数据集一般会在外面套一层forEach
    //原先实现方式
    data.forEach(d => {
      g.append('rect')
        .a·ttr('width', xScale(d.value))
        .attr('height', yScale.bandwidth())
        .attr('fill', 'aqua')
        .attr('y', yScale(d.name))
        .attr('opacity', '0.5')
    })

    // 用enter实现
    g.selectAll('.dataRect').data(data).enter().append('rect')
          .attr('class','dataRect')
          .attr('width', d => xScale(d.value))
          .attr('height',yScale.bandwidth())
          .attr('fill','green')
          .attr('y', d => yScale(d.name))
          .attr('opacity','0.5')

Update

适用场景:有图元有数据

占位符填满之后,直接进入update状态

函数设置图元属性

d3.selectAll('rect').data(dataset).attr('width',(d,i)=>{return xScale(d.value)})

为所有rect标签中的横轴的值通过函数设置,注意 dataset数据集格式需要与原数据集格式相同

.data(data,keyFunction)

image-20200525190143121.png
image-20200525190143121.png

data函数默认以index为索引,如果说给出的数据index发生变化(即数据顺序发生变化或者增加减少数据量),内容绑定顺序会错乱,解决方法:

用key绑定数据给图元,keyFunction为每条输入绑定的数据和每个包含数据的图元执行一次

d3.selectAll('rect').data(dataset, d => d.name)...

如果图元之前没有绑定过任何数据,keyFunction会报错

  • 第一次绑定时根据索引
  • 实际任务中,图元都是根据数据的“条”树动态添加删除,只需要在添加的时候指定DOM的id

Update作为实际可视化任务最常用的状态,经常被单独封装成一个函数

updateSelection.merge( enterSelection ).attr(…).attr(…)
  • 将两个selection合并到一起操作
  • enterSelection在与updateSelection merge之前需要至少调用append(…)语句添加好图元

Update与动画结合

.transition(…)经过调用后,后续的链式调用会变成数值上的渐变,渐变的时间由.duration(…)设定

d3.selectAll('rect').data(data2,d => d.name).transition().duration(1500).attr('width',d=>xScale(d.value))

Exit

适用场景:有图元没数据

移除没有数据的图元

.selectAll('.class').data(data).exit().remove()

数据处理

图片相关

添加图片

  • 一张图自身以link形式给出

  • image本身也是html元素

  • herf表示图片的路径

  • ViewBox:preserveAspectRatio表示图片对齐和缩放 可选参数:(meet:保留完整图片 slice:适应viewBox。本质是对viewbox这个属性进行修改)

    • none
    • xMidYMid slice 居中切片
    • xMidYMid meet 缩小保留完整内容居中显示在可视框中
    • xMinYMin slice 切片左边的
    • xMaxYMax meet 缩小保留完整内容居底部显示在可视框中
svg.append('image').attr('id','img')
    .attr('x',400).attr('y',300)
    .attr('preserveAspectRatio','none')
    .attr('href','sss.jpg')
图片纹理
<patterns>

pattern.Unit

插入 texture.js

数据集

CSV数据

本质文本,没有格式

读取方式,会正常向服务器请求数据,在请求并处理好之后,将结果扔给.then(…)中的回调函数

d3.csv(‘path/to/data.csv’).then( data => {…} )

.csv的返回值是一个promise对象

JSON数据

地图数据

TopoJson
image-20200526145902684.png
image-20200526145902684.png

本质上是JSON格式

对处理了 GeoJson数据冗余的
特点,节约存储空间

由D3的作者Mike Bostock制定

GeoJson
image-20200526145925447.png
image-20200526145925447.png

本质上是JSON格式

D3.js 的 geoPath 使用 GeoJson 格
式的地图数据

JSON读取

TopoJson需要做转换成GeoJson的处理

<script src="./source/topojson.js"></script>
//第二个参数表示数据专门针对的对象
worldmeta = topojson.feature(data, data.objects.countries);

D3事件机制

图元.on(事件类型,触发动作)

d3.select(‘#someprimitive’).on(‘click’, myCallBack)

D3-tip

自动在合适的位置显示对话框

初始化

<script src="./source/d3-tip.js"></script>    
const tip = d3.tip()
      .attr('class', 'd3-tip').html(function (d) {
        return d.properties.name
      });
svg.call(tip);

层级结构可视化

层级结构:树、Tree、Hierarchy

  • d3.hierarchy(data) 将json数据层级化,注意height参数的值表示的是根据当前节点的子节点而言的
image-20200527103437678.png
image-20200527103437678.png

直接的可视化方案

  • d3.tree

更直观的可视化方案

  • d3.partition & d3.arc for 'd' of <path>

步骤

  1. d3.hierarchy(data)将json数据层级化
  2. d3.tree对层级结构在画布上进行空间的划分(就像比例尺),为每个节点添加x,y

相关参数

node.descendants()用来生成后代数组,把包括root在内的其他所有子节点拍平成一个list返回,按照广度优先遍历原则

网络结构可视化

交互API

Drag

d3.drag()一般定义3个函数分别定义开始的操作、交互中的操作、结束的操作

  • dragstarted simulation.stop()
  • dragged 在这边更新坐标位置 ticked
  • dragended simulation.restart()

异步机制

实现

初始坐标轴

    const svg = d3.select('#main')
    // + 字符串转化为数字
    const width = +svg.attr('width')
    const height = +svg.attr('height')
    const trueWidth = width - margin.left - margin.right
    const trueHeight = height - margin.bottom - margin.top
    // 需要把分组的起始坐标点translate到真实画布起点的位置
    const g = svg.append('g').attr('id', 'mGroup').attr('transform', `translate(${margin.left},${margin.top})`)
    
    const xScale = d3.scaleLinear()
      .domain(d3.extent(data, d => d.value)).range([0, trueWidth])
      // 定义域优化
      .nice()
    const xAxis = d3.axisBottom(xScale)
      // 网格线
      .tickSize(-trueHeight)
    // 在画布上加上坐标轴
    g.append('g').call(xAxis)
      // 将x坐标轴移到画布底端
      .attr("transform", `translate(0,${trueHeight})`)

    const yScale = d3.scaleBand()
      .domain(data.map(d => d.name)).range([0, trueHeight])
      // 使柱状图每个rect保持0.2的空隙
      .padding(0.2)
    const yAxis = d3.axisLeft(yScale).tickSize(-trueWidth)
    g.append('g').call(yAxis)

柱状图

实现横向柱状图

image-20200529191821524.png
image-20200529191821524.png
  1. forEach实现
    data.forEach(d => {
      g.append('rect')
        .attr('width', xScale(d.value))
        .attr('height', yScale.bandwidth())
        .attr('fill', 'aqua')
        .attr('y', yScale(d.name))
    })
  1. enter实现
    g.selectAll('.dataRect').data(data).enter().append('rect')
      .attr('width', d => xScale(d.value))
      .attr('height', yScale.bandwidth())
      .attr('fill', 'green')
      .attr('y', d => yScale(d.name))

渐变效果实现

    // 线性渐变
    const linearGradient = svg.append("defs")
      .append("linearGradient")
      .attr("id", "gra")
    linearGradient.append('stop')
      .attr('offset', "5%")
      .attr('stop-color', "gold")
    linearGradient.append('stop')
      .attr('offset', "95%")
      .attr('stop-color', "red")
    //给rect的fill填充
    .attr('fill', 'url(#gra)')

动态散点图

image-20200529193019422.png
image-20200529193019422.png
  • 在坐标轴基础上需要重新const AxisGroup,需要做渲染时的移除remove
      xScale = d3.scaleLinear()
        .domain(d3.extent(data, xValue)) 
        .range([0, trueWidth])
        .nice();

      const xAxis = d3.axisBottom(xScale)
        //网格应该在xAxis中,不需要remove
        .tickSize(-trueHeight)
        .tickPadding(10);

      let xAxisGroup = g.append('g').call(xAxis)
        .attr('transform', `translate(${0}, ${trueHeight})`)
        .attr('id', 'xaxis')
      
      xAxisGroup.append('text')
        .attr('font-size', '2em')
        .attr('y', 60)
        .attr('x', trueWidth / 2)
        .attr('fill', '#333333')
        .text(xAxisLabel);
      xAxisGroup.selectAll('.domain').remove();
  • 散点渲染和文字匹配执行在更新中,使用定时器去定时的刷新视图
      // 散点
      let circleUpdates = g.selectAll('circle').data(seq, d => d['地区'])
      //enter()添加足够数量的circle  
      let circleEnter = circleUpdates.enter().append('circle')
        .attr('transform', `translate(${margin.left},${margin.top})`)
        // xScale必须包裹在外面,数据到像素的对应,先取值xValue然后调用映射到坐标
        .attr('cx', d => xScale(xValue(d)))
        .attr('cy', d => yScale(yValue(d)))
        .attr('r', 15)
        .attr('fill', d => color[d['地区']])
        .attr('opacity', 0.5)
        .text(d => d['地区'])

      //为散点添加城市名
      let textUpdates = g.selectAll('.province_text').data(seq);
      let textEnter = textUpdates.enter().append('text')
        .attr('transform', `translate(120,40)`)
        .attr("class", "province_text")
        .attr("x", (datum) => {
          return xScale(xValue(datum));
        })
        .attr("y", (datum) => {
          return yScale(yValue(datum));
        })
        .attr("dy", "1em")
        //以文本中间值作为锚点  
        .style("text-anchor", "middle")
        .attr("fill", "#333333")
        .text(function (d, i) {
          return d['地区'];
        });

      // console.log(textEnter.text);
      textUpdates.merge(textEnter).transition().ease(d3.easeLinear).duration(duration)
        .attr('x', (datum) => {
          return xScale(xValue(datum));
        })
        .attr('y', (datum) => {
          return yScale(yValue(datum));
        });

      // ease每一次更新图元应该如何变化,easeLinear表示线性
circleUpdates.merge(circleEnter).transition().ease(d3.easeLinear).duration(duration)
        .attr('cx', d => xScale(xValue(d)))
        .attr('cy', d => yScale(yValue(d)))
  • 日期实时更新
      //删除旧日期
      g.selectAll('.date_text').remove();
      g.append("text")
        .data(['seq'])
        .attr('class', 'date_text')
        .attr("x", margin.left)
        .attr("y", margin.top)
        // y轴偏移的距离
        .attr("dy", "1em")
        .attr("fill", "#504f4f")
        .attr('font-size', '3em')
        .attr('font-weight', 'bold')
        .attr('opacity', 0.7)
       .text(time);
  • d3.scv()调用数据
d3.csv('./data/hubeinxt.csv').then(data => {
      data = data.filter(d => {
        return d['地区'] !== '总计'
      })
      // 将人数由数值转换成字符串
      data.forEach(d => {
        d['确诊人数'] = +(d['确诊人数'])
        d['新增确诊'] = +(d['新增确诊'])
      })
      // 提取日期不重复的顺序日期
      allDates = [...new Set(data.map(d => d['日期']))].sort((a, b) => new Date(a) - new Date(b))
      sequential = []
      // 重组数据 按日期组成一级数据sequential[0]={{日期: "2020/1/21", 地区: "鄂州", 确诊人数: 0…},{日期: "2020/1/21", 地区: "恩施州", 确诊人数: 0…},...}
      allDates.forEach(d => {
        sequential.push([])
      })
      data.forEach(d => {
        sequential[allDates.indexOf(d['日期'])].push(d)
      })
      console.log(sequential)
      // 初始化渲染
      renderInit(data)

      let c = 0//计数器
      // 创建动画循环,每隔duration时间重新调用interval
      let interval = setInterval(() => {
        // 判断是否所有数据都已经渲染完
        if (c >= allDates.length) {
          clearInterval(interval)
        } else {
          // 数据还没更新完的话每隔duration时间调用update动态更新坐标点
          renderUpdate(sequential[c])
          c += 1
        }
      }, duration);
    })

动态折线图

image-20200529201726989.png
image-20200529201726989.png

对单一的数据,绑定了单一的图元实现

  1. 在renderInit函数中append折线
g.append('path').attr('id', 'alterPath');
  1. 在updateAlter中选择数据集并为其设置属性 d3.line()是一个关于d属性的可以直接把绑定的数据转换为路径的接口, .x() .y()传进来的数据集array在d3.line()中被自动的拆分成行(即,一line对应一行数据) .curve()插值的策略,用什么方式插入数据 (里面有各种方式介绍) https://github.com/d3/d3-shape/blob/v1.3.7/README.md#curves
const line = d3.line()
        .x(d => d['日期'])
        .y(d => d['确诊人数'])
        //.curve(d3.curveBasis)
//cardinal方式保证一定经过数据点,tension表示平滑程度
        .curve(d3.curveCardinal.tension(0.5))
  1. 折线设置属性
d3.select('#alterPath').datum(data)
        .attr('class', 'datacurve')
        // fill不设置“none”,填充会path自动闭合,一块黑色区域
        .attr("fill", "none")
        .attr("stroke", "green")
        .attr("stroke-width", 2.5)
        .transition().duration(2000)
        .attr("d", line)

堆叠Stack

image-20200529202306072.png
image-20200529202306072.png

y坐标轴中确立堆叠的最大高度

    const mStack = d3.stack()
      .keys(naiveKeys)
      .order(d3.stackOrderNone)(naiveData)
    //在stack中二轮循环,找mStack[i][j][1]中的最大值
    const yMaxValue = d3.max(mStack, d => d3.max(d, sub => sub[1]))

    const yScale = d3.scaleLinear().range([0, innerHeight].reverse())
      .domain([0, yMaxValue]).nice()
    const yAxis = d3.axisLeft(yScale).tickSize(-innerWidth);
    const yAxisGroup = g.append('g').attr('class', 'yAxis').call(yAxis);
image-20200527103437678.png
image-20200527103437678.png
  • .keys(naiveKeys) 设置要堆叠的属性有哪些;
image-20200526161808109.png
image-20200526161808109.png

这边传入的数组有四个元素,所以堆叠四个

  • .order(d3.stackOrderNone)这些属性要按照什么顺序堆叠 可选参数:

    • d3.stackOrderNone
    • d3.stackOrderAscending 求和值最大的放在最上面
    • d3.stackOrderDesending 求和值最小的放在最上面
  • .offset(d3.stackOffsetWiggle)起伏最优化,主题河流中常用

数据离散映射(把苹果映射成红色)

const color = d3.scaleOrdinal()
      .domain(naiveKeys)
    //自带配色可以这样直接调用,scaleOrdinal可以直接对颜色数组和数据匹配
      .range(d3.schemeSet3)

d3自带配色方案

https://github.com/d3/d3-scale-chromatic

堆叠数据的Data-join

每条数据绑定的是长度为 2 的数组;

  • 两个数字表示在堆叠数据中的区间
  • 数组的data属性可以映射到原本数据

用堆叠后的数据设置比例尺、位置、高度

注意:请尽可能先调用比例尺映射数据,再做运算!(绿色框)

image-20200526161404046.png
image-20200526161404046.png

data-join嵌套

image-20200526171609771.png
image-20200526171609771.png

第一层,join一组group,(apple为一组group...)

g.selectAll('.datagroup').data(naiveStack).join('g').attr('class', 'datagroup').attr('fill', d => color(d.key))

第二层嵌套selectAll,把上面某一个datagroup它得到的某一个外层的array做一进步的拆分.

并且第二层打开数组时,data()函数的参数得使用function的形式来接收上一层打开的的结果

接上面...//这边是处理每一个小矩形
    .selectAll('.datarect').data(d => d).join('rect').attr('class', 'datarect')
      .attr('y', d => yScale(d[1]))
        //需要通过小array中的data访问x
      .attr('x', d => xScale(xValue(d.data)))
      .attr('height', d => yScale(d[0]) - yScale(d[1]))
      .attr('width', xScale.bandwidth());

地图渲染

image-20200529202513962.png
image-20200529202513962.png
  1. 将拿到的数据转换成GeoJson格式
d3.json('./data/countries-110m.json').then(data => {
    worldmeta = topojson.feature(data, data.objects.countries);
}
  1. 选择投影方式
    // 投影手段的接口 https://github.com/d3/d3-geo for more different projections; 
    //const projection = d3.geoMercator();
    //const projection = d3.geoOrthographic();
    //const projection = d3.geoStereographic();
    //const projection = d3.geoEquirectangular();
    // 各种投影手段
    const projection = d3.geoNaturalEarth1();
    //const projection = d3.geoTransverseMercator();
    // 告诉GeoPath对数据到屏幕上如何进行投影
    const pathGenerator = d3.geoPath().projection(projection);
  1. geoPath接收的格式是worldmeta中的features属性
d3.json('./data/countries-110m.json').then(data => {
    ...
     const paths = g.selectAll('path')
        .data(worldmeta.features, d => d.properties.name)
        .enter().append('path')
}
  1. 对显示出来的地图进行对屏幕的适应,调用接口,不需要人为的去调整比例尺 第一个参数为可以铺展的画布尺寸
d3.json('./data/countries-110m.json').then(data => {
    ...
    projection.fitSize([innerWidth, innerHeight], worldmeta);
}

image-20200529202755146.png
image-20200529202755146.png

参照层级结构可视化

  1. 获取数据并对数据进行层次化,并对该层级结构进行空间上的划分
d3.json('./data/games.json').then(data => {
      console.log(data);
      // 将数据层级化
      root = d3.hierarchy(data)
      //把层级结构映射到画布
      root = d3.tree().size([innerWidth, innerHeight])(root)
      render(root)
    })
  1. render函数中,用link对层级结构进行连线
      g.selectAll('path').data(data.links()).join('path')
        .attr('fill', 'none')
        .attr('stroke', 'black')
        .attr('opacity', 0.4)
        //x(d=>d.y) 交换坐标,显示一颗从左到右的树,否则默认显示从上到下的树
        .attr('d', d3.linkHorizontal().x(d => d.y).y(d => d.x))
  1. 给节点赋值颜色,fill规定子节点颜色跟随第二层级的节点
    const fill = d => {
      if (d.depth === 0)
        return color(d.data.name)
      // 如果是第二层之后的节点,寻找他父节点的颜色直到找到第二层
      while (d.depth > 1)
        d = d.parent;
      return color(d.data.name);
    }
      g.selectAll('g').data(data.descendants()).join('circle')
        .attr('cx', d => d.y)
        .attr('cy', d => d.x)
        .attr('r', 6)
        // 节点颜色第二层个一个颜色,其子节点跟随其颜色
        .attr('fill', fill)
        .attr('stroke-width', 3)

icicle

image-20200529202850420.png
image-20200529202850420.png

在树图的基础上,修改为partition

d3.partition 创建新的分区布局(旭日图和冰柱图)

    d3.json('./data/games.json').then(data => {
      root = d3.partition().size([height, width])(
        // 告诉d3非叶子节点的value计算所有其叶子节点value的总和
        d3.hierarchy(data).sum(d => d.popularity)
        .sort((a, b) => b.popularity - a.popularity)
      )
      render(root)
    })

处理拍平的数组转换rect,使用join方式

      g.selectAll('.dataRect').data(data.descendants()).join('rect')
        .attr('class', 'dataRect')

饼图

需要用到d3.arc()接口,方法生成一个弧。你需要为弧设置内半径和外半径。如果内半径为 0,则结果将为饼图,否则结果将为圆环图

d3.pie() 方法用于生成饼图。它从数据集中获取数据并计算饼图的每个楔形的起始角度和结束角度。

const g = svg.append('g').attr('id', 'maingroup')
      .attr('transform', `translate(${width/2}, ${height/2})`);

sunburst

image-20200529202951173.png
image-20200529202951173.png
  1. 将icicle图绕成圆周
// 旭日图把size变成圆周的一圈
      root = d3.partition().size([2 * Math.PI, height / 1.6])(
        // 告诉d3非叶子节点的value计算所有其叶子节点value的总和
        d3.hierarchy(data).sum(d => d.popularity)
        .sort((a, b) => b.popularity - a.popularity)
      )
  1. 添加路径,填充圆环颜色 arc中的可选参数 : https://zhuyali.github.io/d3-doc/d3-shape/arc.html#arcpadangle--function
const arc = d3.arc()
        //起始角度,顺时针方向
      .startAngle(d => d.x0)
      .endAngle(d => d.x1)
        //内半径
      .innerRadius(d => d.y0)
        //外半径
      .outerRadius(d => d.y1)
      .padAngle(d => Math.min((d.x1 - d.x0) / 2, 0.005))
      g.selectAll('.dataPath').data(filterData).join('path')
        .attr('class', 'dataPath')
        .attr('fill', fill)
        .attr('d', arc)
  1. 过滤根节点之后圆心空白,可变成圆环
const filterData = data.descendants().filter(d => d.depth !== 0)
  1. 文字移到相应的位置 这边的transform应用顺序是从左到右的
g.selectAll('.dataText').data(filterData).join('text')
        .attr('text-anchor', 'middle')
        .attr('font-size', d => d.data.name.length > 13 ? '0.8em' : '0.9em')
        .attr("transform", function (d) {
          const x = (d.x0 + d.x1) / 2 * 180 / Math.PI;
          const y = (d.y0 + d.y1) / 2;
          // note that there is an implicit transform inherited from the maingroup;
          return `rotate(${x - 90}) translate(${y},0) rotate(${x < 180? 0 : 180}) translate(0, 5)`;
        })
        .text(d => d.data.name)

本文使用 mdnice 排版