学习D3.js(三)多列柱状图

881 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第19天,点击查看活动详情

开始绘制

引入D3模块

  <!-- 选择器模块 -->
  <script src="https://cdn.jsdelivr.net/npm/d3-selection@3"></script>

  <!-- 比例尺模块 和 依赖 -->
  <script src="https://cdn.jsdelivr.net/npm/d3-array@3"></script>
  <script src="https://cdn.jsdelivr.net/npm/d3-color@3"></script>
  <script src="https://cdn.jsdelivr.net/npm/d3-format@3"></script>
  <script src="https://cdn.jsdelivr.net/npm/d3-interpolate@3"></script>
  <script src="https://cdn.jsdelivr.net/npm/d3-time@3"></script>
  <script src="https://cdn.jsdelivr.net/npm/d3-time-format@4"></script>
  <script src="https://cdn.jsdelivr.net/npm/d3-scale@4"></script>

  <!-- 坐标轴 -->
  <script src="https://cdn.jsdelivr.net/npm/d3-axis@3"></script>

创建数据

  • 数据格式随意,方便在绑定时使用。
var bColor = ['#4385F4', '#34A853', '#FBBC05', '#E94335', '#01ACC2', '#AAACC2']
    var dataArr = [
      {
        label: '1月',
        value: 10.5,
        value2: 20.5
      },
      {
        label: '2月',
        value: 70.5,
        value2: 22.5
      },
      {
        label: '3月',
        value: 60.5,
        value2: 30.5
      },
      {
        label: '4月',
        value: 10.5,
        value2: 20.5
      },
      {
        label: '5月',
        value: 20.5,
        value2: 40.5
      },
      {
        label: '6月',
        value: 30.5,
        value2: 30.5
      }
    ]

创建画布

  • 创建SVG元素,设置样式
var svg = d3
  .select('.d3Chart')
  .append('svg')
  .attr('width', 500)
  .attr('height', 500)
  .style('background-color', '#1a3055')

创建比例尺

  • 比例尺用于数据转换。简单理解就是输入一个规定好的值,输出规定范围内对应的值。
  • 详细介绍可以看之前的文章或看官方文档。
    // X轴比例尺
    var xScale = d3
      .scaleBand()
      .range([0, 400])
      .domain(dataArr.map((s) => s.label))
      .padding(0.4)
    // y轴比例尺
    var yScale = d3.scaleLinear().range([400, 0]).domain([0, 100])

绘制坐标轴

    var chart = svg.append('g').attr('transform', 'translate(50, 40)')

    chart.append('g').attr('class', 'xAxis')
    .attr('transform', 'translate(15, 400)').call(d3.axisBottom(xScale))
    var makeYlines = () =>
      d3
        .axisLeft()
        .scale(yScale)
        .tickSize(-400)
        .tickFormat((d) => {
          return d + '%'
        })
    chart.append('g').attr('class', 'yAxis').attr('transform', 'translate(15, 0)').call(makeYlines())

    // 标签
    d3.select('.yAxis')
      .append('g')
      .attr('transform', 'translate(-40, 0)')
      .append('text')
      .attr('class', 'axisTextY')
      .style('font-size', '24px')
      .attr('transform', 'rotate(-90)')
      .text('比例(%)')
    // 标签居中
    d3.select('.axisTextY').attr('x', function () {
      return -200 + this.getBoundingClientRect().height / 2
    })

    d3.selectAll('.d3Chart text').style('fill', '#fff')
    d3.selectAll('.d3Chart line').style('stroke', '#fff')
    d3.selectAll('.d3Chart path').style('stroke', '#fff')

image.png

  • 在SVG中创建g组并设定好位置,后续绘图都在它之上绘制。
  • 使用(axis)坐标轴模块,分别绘制 X轴、Y轴。在创建坐标轴的时候分别设置了class属性,作为唯一标识,获取获取元素使用。
  • 根据定义的class属性,获取坐标轴元素。在其子节点创建文本元素,修改其样式以生成标题。
  1. .getBoundingClientRect() javascript中的方法。返回元素的大小及其相对于视口的位置。

绘制柱状

  • 前面数据格式是随意制定的。这里重新组装,便于D3绑定数据。
    // 组装数据 便于绘制
    const items = dataArr.map((row) => {
      let item = []
      let index = 0
      Object.keys(row).forEach((key) => {
        // 非数据 不绘制统计图
        if (key !== 'label') {
          item.push([row.label, row[key], key, index])
          index++
        }
      })
      return item
    })

image.png

  • 绑定数据。
    // 绑定数据
    const groups = chart.selectAll().data(items)

    const bars = groups
      .enter()
      .append('g')
      .selectAll()
      .data((d) => d)
  • 选中最开始创建的g元素,绑定数据。
  • 使用.enter().append('g')创建绑定数据个数的子g元素并使数据和元素一一对应。
  • 因为数据格式修改为二维数组,最后给每个子g元素绑定数据。

image.png

  • 返回结果bars,简单理解就是选中了所有子g元素,并且给每个子g元素都绑定了数据但是现在数据还没生成对应的元素。
bars
  .enter()
  .append('rect')
  .attr('class', 'rectTool')
  .style('fill', (d, i) => bColor[i])
  .attr(
    'x',
    (g, i) => xScale(g[0]) + xScale.bandwidth() / 2 - 4 + 
    (xScale.bandwidth() / items[0].length + 3) * g[3]
  )
  .attr('y', (g) => yScale(g[1]))
  .attr('width', xScale.bandwidth() / items[0].length)
  .attr('height', (g) => 400 - yScale(g[1]))
  • .enter().append('rect') 给每个子g元素的绑定数据生成对应的rect元素。
  • 然后给每个rect元素设置样式。
  • 这注意:展示的是多列统计图,不能直接使用.bandwidth()需要计算一下。

添加交互

    var tooltips = d3
      .select('body')
      .append('div')
      .style('width', '100px')
      .style('height', '40px')
      .style('background-color', '#fff')
      .style('dispaly', 'flex')
      .style('justify-content', 'center')
      .style('padding', '10px')
      .style('border-radius', '5px')
      .style('opacity', 0)

    d3.selectAll('.rectTool')
      .on('mouseenter', (e, g) => {
        tooltips
          .html(`月份:${g[0]}<br /> 数据:${g[1]}%`)
          .style('position', 'absolute')
          .style('left', `${e.clientX}px`)
          .style('top', `${e.clientY}px`)
          .style('opacity', 1)
      })
      .on('mouseleave', (e, g) => {
        tooltips.style('opacity', 0).style('left', `0px`).style('top', `0px`)
      })

4.gif

  • 根据前面设置的class属性,选中所有的rect元素。给它们添加监听事件,修改提示框的位置和数据。一个简单的多列统计图就完成了。
  • 代码地址