学习D3.js(十三)雷达图

933 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情

开始绘制

引入D3模块

  • 引入整个D3模块。
<script src="https://d3js.org/d3.v7.min.js"></script>

数据

  • 自定义数据,用户绘图使用。
var dataArr = [
      {
        label: '1月',
        value: [15.5, 10]
      },
      {
        label: '2月',
        value: [10.5, 60]
      },
      {
        label: '3月',
        value: [40.5, 100]
      },
      {
        label: '4月',
        value: [50.5, 40]
      },
      {
        label: '5月',
        value: [20.5, 10]
      },
      {
        label: '6月',
        value: [90.5, 20]
      }
    ]

添加画布

  • 初始化画布。
    var width = 450
    var height = 450
    var margin = 20
    var svg = d3
      .select('.d3Chart')
      .append('svg')
      .attr('width', width)
      .attr('height', height)
      .style('background-color', '#1a3055')

    // 图
    var chart = svg.append('g').attr('transform', `translate(${margin}, ${margin})`)

绘制雷达图层级

    /**
     * @param vertexNum 顶点数
     * @param radius 图半径
     * @param tickNum 层级数
     * */
    function getPolygonPoints(vertexNum, radius, tickNum) {
      const points = [] // 点数组
      let polygon = [] // 多边形

      // 计算 顶点间的弧度
      const anglePiece = (Math.PI * 2) / vertexNum
      // 每个层级高度
      const radiusReduce = radius / tickNum

      for (let r = radius; r > 0; r -= radiusReduce) {
        polygon = []
        // 获取 每个层顶点位置
        for (let i = 0; i < vertexNum; i++) {
          polygon.push(Math.sin(i * anglePiece) * r + ',' + Math.cos(i * anglePiece) * r)
        }

        points.push(polygon.join(' '))
      }

      return points
    }
    const points = getPolygonPoints(dataArr.length, 100, 5)
  • 层级由多个,正多边形组成。
  • 通过设定好的参数,计算出每个层级的,多边形顶点的坐标。
    const axes = chart
      .append('g')
      .attr('class', 'axes')
      .attr('transform', 'translate(' + (width - margin * 4) / 2 + ',' + (height - margin * 2) / 2 + ')')
  • 创建雷达图绘制组g,设置好组的位置。
    axes
      .selectAll()
      .data(points)
      .enter()
      .append('polygon')
      .attr('class', 'axis')
      .attr('points', (d) => d)
      .attr('fill', (d, i) => (i % 2 === 0 ? 'white' : '#ddd'))
      .attr('stroke', 'gray')
  • 绑定计算出来的多边形点数据。
  • 通过SVG的polygon元素,交替颜色绘制多边形。

image.png

    // 得到顶层 多边形 顶点
    const pointsTop = points[0].split(' ').map((d) => d.split(','))
    const line = d3.line()
    axes
      .selectAll()
      .data(pointsTop)
      .enter()
      .append('path')
      .attr('class', 'line')
      .attr('d', (d) => {
        return line([
          [0, 0],
          [d[0], d[1]]
        ])
      })
      .attr('stroke', 'gray')
  • 获取顶层正多边形的顶点。
  • 雷达图绘制组的原点和多边形顶点连线。

image.png

绘制标签

    // 文本
    function computeTextAnchor(num, i) {
      const angle = (i * 360) / num
      if (angle === 0 || Math.abs(angle - 180) < 0.01) {
        return 'middle'
      } else if (angle > 180) {
        return 'end'
      } else {
        return 'start'
      }
    }
    axes
      .selectAll()
      .data(dataArr)
      .enter()
      .append('text')
      .attr('fill', '#fff')
      .attr('x', (d, i) => Math.sin((i * Math.PI * 2) / dataArr.length) * 120)
      .attr('y', (d, i) => Math.cos((i * Math.PI * 2) / dataArr.length) * 120)
      .attr('text-anchor', (d, i) => computeTextAnchor(dataArr.length, i))
      .attr('dy', 6.5) // 对齐文字中部
      .text((d) => d.label)
  • 创建文本排列方式计算的方法。
  • 和计算层顶点位置的方式一样,使用三角函数计算文本坐标。需要比层半径多出一些距离,设置文本在图形外。

image.png

绘制数据

let newData = []
dataArr[0].value.forEach((item, j) => {
  const row = []
  dataArr.forEach((item, i) => {
    row.push(item.value[j])
  })
  newData.push(row)
})
  • 装换数据格式,方便绘图。
const colorScale = d3.scaleOrdinal(d3.schemeSet2)
  • 创建颜色获取对象。
    // 计算多边形的顶点并生成顶点圆圈
    function generatePolygons(d, index) {
      const points = []
      const anglePiece = (Math.PI * 2) / d.length

      d.forEach((item, i) => {
        const x = Math.sin(i * anglePiece) * item
        const y = Math.cos(i * anglePiece) * item

        //添加交点圆圈
        axes
          .append('circle')
          .attr('fill', 'white')
          .attr('stroke', colorScale(index))
          .attr('cx', 0)
          .attr('cy', 0)
          .attr('r', 3)
          .transition()
          .duration(1000)
          .attr('cx', x)
          .attr('cy', y)

        points.push(x + ',' + y)
      })
      return points.join(' ')
    }
  • 使用三角函数计算出数据顶点的位置。
  • 数据顶点还需要绘制小圆点,在计算出位置后直接在顶点位置,绘制小圆点。
    axes
      .selectAll()
      .data(newData)
      .enter()
      .append('polygon')
      .attr('class', 'polygon')
      .attr('fill', 'none')
      .attr('stroke', (d, i) => colorScale(i))
      .attr('stroke-width', '2')
      .attr('points', (d, i) => {
        const miniPolygon = []
        d.forEach(() => {
          miniPolygon.push('0,0')
        })
        return miniPolygon.join(' ')
      })
      .transition()
      .duration(1000)
      .attr('points', generatePolygons)

3.gif

  • 绑定数据,绘制数据多边形展示。

添加交互

d3.selectAll('.polygon')
  .on('mouseover', function (e, d) {
    d3.select(e.target).attr('stroke-width', '5')
  })
  .on('mouseleave', function (e, d) {
    d3.select(e.target).attr('stroke-width', '2')
  })

1.gif

  • 这里只添加了一个简单的加粗交互。