学习D3.js(十八)桑基图

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情

引入D3模块

  • 引入整个D3模块。
<!-- D3模块 -->
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://unpkg.com/d3-sankey@0"></script>
  • 桑基图布局计算需要使用d3.sankey()函数。该函数不在d3整体包中,需要单独引入。

数据

 const data = {
  nodes: [
    { name: 'a' },
    { name: 'b' },
    { name: 'c' },
    { name: 'd' },
    { name: 'e' },
    { name: 'f' },
    { name: 'g' },
    { name: 'h' },
    { name: 'i' }
  ],
  links: [
    { source: 'a', target: 'd', value: 10 },
    { source: 'a', target: 'i', value: 2 },
    { source: 'b', target: 'd', value: 8 },
    { source: 'b', target: 'e', value: 6 },
    { source: 'c', target: 'e', value: 5 },
    { source: 'b', target: 'e', value: 2 },
    { source: 'b', target: 'i', value: 4 },
    { source: 'd', target: 'f', value: 3 },
    { source: 'd', target: 'g', value: 4 },
    { source: 'd', target: 'h', value: 5 },
    { source: 'e', target: 'g', value: 7 },
    { source: 'e', target: 'f', value: 3 },
    { source: 'e', target: 'h', value: 5 }
  ]
}
  • 数据中nodes代表节点,links代表连线数据。所以sourcetarget的值必须是节点中的值。

添加画布

  • 初始化画布。
    // 画布
    const width = 500
    const height = 500
    const margin = 30
    const svg = d3
      .select('.d3Chart')
      .append('svg')
      .attr('width', width)
      .attr('height', height)
      .style('background-color', '#1a3055')
    // 图
    const chart = svg.append('g').attr('transform', `translate(${margin}, ${margin})`)

比例尺和配置信息

const colorScale = d3.scaleOrdinal(d3.schemeSet3)
  • 颜色比例尺。
const sankey = d3
  .sankey()
  .nodeWidth(30)
  .nodePadding(20)
  .size([width - 2 * margin, height - 2 * margin])
  .nodeId((d) => d.name)
  1. d3.sankey() 桑基图生成器。
  2. .nodeWidth(30) 设置节点的宽度。
  3. .nodePadding(30) 设置每列节点之间的垂直间隔。
  4. .size([x,y]) 设置桑基图的范围。从[0,0]点到[x,y]。
  5. .nodeId() 设置了id,节点id访问器设置为指定的函数,并返回此Sankey生成器。
  • 创建桑基图生成器。
const { nodes, links } = sankey({
  nodes: data.nodes,
  links: data.links
})
  • 使用桑基图生成器,获取节点位置信息(nodes)和连线位置信息(links)。

绘制桑基图

chart
  .append('g')
  .selectAll()
  .data(nodes)
  .join('g')
  .attr('class', 'node')
  .attr('indexName', (d) => d.name)
  .append('rect')
  .attr('fill', (d, i) => colorScale(d.name))
  .attr('x', (d) => d.x0)
  .attr('y', (d) => d.y0)
  .attr('height', (d) => d.y1 - d.y0)
  .attr('width', (d) => d.x1 - d.x0)
  .append('title')
  .text((d) => `${d.name}`)

image.png

  • 创建一个节点绘制组,绑定节点数据(nodes)。
  • 给每个节点创建一个绘制组,并在其内部绘制recttitle
  • indexName为自定义属性用做标识符,在交互中使用。
chart
  .append('g')
  .attr('fill', 'none')
  .selectAll()
  .data(links)
  .join('path')
  .attr('indexName', (d) => d.source.name + '-' + d.target.name)
  .attr('d', d3.sankeyLinkHorizontal())
  .attr('stroke', (d, i) => colorScale(d.source.name))
  .attr('stroke-width', (d) => d.width)
  .attr('stroke-opacity', '0.5')
  .append('title')
  .text((d) => `${d.value.toLocaleString()}`)

image.png

  1. d3.sankeyLinkHorizontal() sankey 插件中的API,根据连线数据生成路径坐标点。
  • 创建一个连线绘制组,绑定连线数据(links)。绘制路径图形形成连线。
chart
  .selectAll('.node')
  .append('text')
  .attr('class', 'text')
  .attr('x', (d) => (d.x0 + d.x1) / 2)
  .attr('y', (d) => (d.y0 + d.y1) / 2)
  .attr('stroke', '#000000')
  .attr('text-anchor', 'middle')
  .attr('dy', 6)
  .text((d) => d.name)
  • 通过标识符(.node)获取节点绘制组。节点对象已经和数据绑定过,这里可以直接得到数据进行文本添加。

image.png

添加交互

d3.selectAll('.node')
  .on('mouseover', function (e, d) {
    d3.selectAll('.node, path').attr('fill-opacity', '0.1').attr('stroke-opacity', '0.1')

    d3.selectAll('[indexName*=' + d.name + ']')
      .attr('fill-opacity', '1')
      .attr('stroke-opacity', '0.6')
  })
  .on('mouseleave', function () {
    d3.selectAll('.node, path').attr('fill-opacity', '1').attr('stroke-opacity', '0.5')
  })
  • 选中节点绘制组,添加鼠标事件。
  • 修改所有节点和连线的透明度为0.1。通过属性选择获取当前节点相关的节点和连线,设置其透明度加深。
// 连线
d3.selectAll('path')
  .on('mouseover', function (e, d) {
    d3.selectAll('.node, path').attr('fill-opacity', '0.1').attr('stroke-opacity', '0.1')

    const hoverNodes = d3.select(e.target).attr('stroke-opacity', '0.5').attr('indexName').split('-')

    hoverNodes.forEach((name) => {
      d3.selectAll('[indexName=' + name + ']').attr('fill-opacity', '1')
    })
  })
  .on('mouseleave', function () {
    d3.selectAll('.node, path').attr('fill-opacity', '1').attr('stroke-opacity', '0.5')
  })
  • 选中连线对象,添加鼠标事件。
  • 修改所有节点和连线的透明度为0.1。获取当前自定义属性indexName,因为连线是和两端节点都有关联,使用循环都进行透明度修改。

1.gif

猜你喜欢

转载自juejin.im/post/7127559319402315806