绘制环状图

利用D3按照部门构造环状图,构建年底出警数可视化。

1、修改 Left.vue

模板
<div class="caseResolved">
  <h2 class="chart-title">年度出警数</h2>
  <div class="case-resolved-chart" id="caseResolvedChart"></div>
</div>
逻辑
mounted() {
  axios.get(api.castleLeft)
    .then(result => {
      // ...

      // 1、在mounted里添加:
      this.dealCase(data.caseResolved)
    })
},

// 2、在 methods 里添加:
dealCase (data) {
  const config = {}
  const multiPie = new MultiPie('.case-resolved-chart', config)
  multiPie.render(data)
}

// 3、导入模块
import MultiPie from '../assets/scripts/charts/multiPie'

2、安装 d3.js

yarn add d3 -S

3、构建 D3 环图

在 /src/assets/scripts/charts/ 下创建 multiPie.js 文件:

import * as d3 from 'd3'

export default class MultiPie {
  /**
   *  默认配置项
   *  @return   {[Object]}  [默认配置项]
   */
  defaultSetting() {
    return {
      width: 900,
      height: 270,
      radius: [50, 66], // [innerRadius,outerRadius]
      gap: 100, // 相邻两个图形的间距
      margin: { // 多个图形布局:从左往右,竖直方向按容器高度居中放置,故只设置左侧距离left即可
        left: 10
      },
      label: { // 名称文本样式
        normal: {
          fontSize: 32,
          color: '#46aaff',
          anchor: 'middle',
          cursor: 'pointer',
          top: 46 // 名称文本距离图案顶部的距离
        },
        emphasis: {
          fontSize: 32,
          color: '#74ffd3',
          anchor: 'middle',
          cursor: 'pointer',
          top: 46 // 名称文本距离图案顶部的距离
        }
      },
      itemStyle: {
        label: { // value值文本样式
          fontSize: 32,
          color: '#46aaff',
          anchor: 'middle',
          cursor: 'pointer',
          top: 10 // value值文本距离容器中线的偏移距离,默认放在饼图正中间
        },
        color: [ // 饼图填充色
          ['#4a8ce5', 'black'],
          ['#44ff86', 'black'],
          ['#dccc5c', 'black']
        ]
      }
    }
  }

  /**
   *  初始化,创建容器
   *  @param    {String}  selector 图表容器,支持class或id
   *  @param    {Object}  option   配置项,控制图形样式
   *  @return   {[type]}  [description]
   */
  constructor(selector, option = {}) {
    const defaultSetting = this.defaultSetting()
    this.config = Object.assign(defaultSetting, option)
    const {
      width,
      height
    } = this.config
    // 创建svg
    this.svg = d3.select(selector)
      .append('svg')
      .attr('width', width)
      .attr('height', height)
  }

  /**
   *  处理原始数据,获取pie布局转换后的数据
   *  @param    {Array}  data    原始数据
   *  @return   {Array}  dataset 转换后的数据
   */

  getDataset(data) {
    let dataset = []
    const clockwisePie = d3.pie() // 顺时针,针对数据类型:[small,bigger]
    const anticlockwisePie = d3.pie() // 逆时针,针对数据类型:[bigger,small]
      .startAngle(0)
      .endAngle(-2 * Math.PI)
    // 求取总数:sum
    let sum = 0
    data.map(d => {
      sum += parseInt(d.value, 10)
    })
    data.map((d) => {
      let value = d.value
      let rate = Math.max(Math.floor(value * 100 / sum), 1)
      let rateData = [rate, 100 - rate]
      let dealData = rate >= 50 ? clockwisePie(rateData) : anticlockwisePie(rateData)
      dataset.push(dealData)
    })
    return dataset
  }

  /**
   *  绘制图案底部的名称文本
   *  @param    {Object}  chart 包裹文本的外层g容器
   *  @param    {Object}  info  单组原始数据,包括name和value
   *  @return   {[type]}  [description]
   */
  renderName(chart, info) {
    const {
      radius: [, outerRadius],
      label: {
        normal: {
          fontSize: fontSizeNor,
          color: colorNor,
          anchor: anchorNor,
          top: topNor,
          cursor: cursorNor
        },
        emphasis: {
          fontSize: fontSizeEmp,
          color: colorEmp,
          anchor: anchorEmp,
          top: topEmp,
          cursor: cursorEmp
        }
      }
    } = this.config
    chart.select('.pie-name')
      .attr('font-size', fontSizeNor)
      .attr('fill', colorNor)
      .attr('text-anchor', anchorNor)
      .attr('transform', `translate(0, ${outerRadius + topNor})`)
      .attr('cursor', cursorNor)
      .text(info.name)
      .on('mouseover', function () {
        d3.select(this)
          .attr('font-size', fontSizeEmp)
          .attr('fill', colorEmp)
          .attr('text-anchor', anchorEmp)
          .attr('transform', `translate(0, ${outerRadius + topEmp})`)
          .attr('cursor', cursorEmp)
      })
      .on('mouseout', function () {
        d3.select(this)
          .attr('font-size', fontSizeNor)
          .attr('fill', colorNor)
          .attr('text-anchor', anchorNor)
          .attr('transform', `translate(0, ${outerRadius + topNor})`)
          .attr('cursor', cursorNor)
      })
  }

  /**
   *  绘制图案中间的value值文本
   *  @param    {Object}  chart 包裹文本的外层g容器
   *  @param    {[type]}  info  单组原始数据,包括name和value
   *  @return   {[type]}  [description]
   */
  renderValue(chart, info) {
    const {
      itemStyle: {
        label: {
          fontSize,
          color,
          anchor,
          cursor,
          top
        }
      }
    } = this.config
    chart.select('.pie-value')
      .attr('font-size', fontSize)
      .attr('fill', color)
      .attr('text-anchor', anchor)
      .attr('transform', `translate(0,${top})`)
      .attr('cursor', cursor)
      .text(info.value)
  }

  /**
   *  绘制单个Pie图案
   *  @param    {Objec}  chartName  单个图案的外层g容器
   *  @param    {Array}   pieData   绘制饼图的数据(已经过布局处理)
   *  @param    {Object}  info      该图案的原始数据,包括name和value
   *  @param    {Array}   color     填充饼图的两个颜色值
   *  @return   {[type]}  [description]
   */
  creatPie(chartName, pieData, info, color) {
    const {
      radius: [innerRadius, outerRadius]
    } = this.config
    const arc = d3.arc()
      .innerRadius(innerRadius)
      .outerRadius(outerRadius)
    const chart = this.svg.select(chartName)
    const update = chart.selectAll('path').data(pieData)
    const enter = update.enter()
    update.exit().remove()
    // 绘制饼图图案
    enter.append('path')
    chart.selectAll('path').data(pieData)
      .attr('fill', (d, i) => {
        return color[i]
      })
      .attr('d', d => {
        return arc(d)
      })
    // 绘制名称--name
    enter.append('text').attr('class', 'pie-name')
    this.renderName(chart, info)

    // 绘制value值
    enter.append('text').attr('class', 'pie-value')
    this.renderValue(chart, info)
  }

  render(data) {
    let dataset = this.getDataset(data)
    const update = this.svg.selectAll('.item')
      .data(dataset)
    update.enter().append('g').attr('class', 'item')
    update.exit().remove()
    // 多个图形布局:从左往右,相邻图形间隔为配置项----config.gap
    const {
      height,
      radius: [, R],
      gap,
      margin: {
        left
      },
      itemStyle: {
        color
      }
    } = this.config
    this.svg.selectAll('.item').data(dataset)
      .attr('transform', (d, i) => {
        return `translate(${R + left + 2 * R * i + i * gap},${height / 2})`
      })
      .attr('class', (d, i) => {
        return `item${i} item`
      })
    // 逐个绘制饼图
    dataset.map((d, i) => {
      this.creatPie(`.item${i}`, d, data[i], color[i])
    })
  }
}

results matching ""

    No results matching ""