1. 程式人生 > >D3 v4.x 的echarts化(2-4)—— 餅圖擴充套件玫瑰圖

D3 v4.x 的echarts化(2-4)—— 餅圖擴充套件玫瑰圖

關於餅圖的拓展基本沒有什麼創新點。

設計思路:通過線性比例尺得到圓弧半徑和資料的關係,得到跟資料相關的圓環。有其他需求或需要一起探討的可以+我Q380205984。下面是實現該圖的程式碼和註釋。

<template>
  <div id = "rosePie"></div>
</template>

<script>
import * as d3 from 'd3'
export default {
  data: function () {
    return {
      data: [{
        name: '小米',
        value: 60.8
      }, {
        name: '華為',
        value: 30.8
      }, {
        name: '聯想',
        value: 30.4
      }, {
        name: '三星',
        value: 40.8
      }, {
        name: '蘋果',
        value: 70.8
      }, {
        name: '其他',
        value: 20.8
      } ],
      width: '',
      heigth: '',
      padding: {
        left: '30px',
        right: '30px',
        top: '5px',
        bottom: '20px'
      }
    }
  },
  methods: {
    getStyle: function (obj, attr) {
      if (obj.currentStyle) {
        return obj.currentStyle[attr]
      } else {
        return document.defaultView.getComputedStyle(obj, null)[attr]
      }
    }
  },
  mounted () {
    let _this = this
    let dom = document.getElementById('rosePie')
    // dom容器寬高,引數padding獲取
    let width = parseFloat(this.width) || parseFloat(this.getStyle(dom, 'width'))
    let height = parseFloat(this.height) || parseFloat(this.getStyle(dom, 'height'))
    let padLeft = parseFloat(this.padding.left)
    let padRight = parseFloat(this.padding.right)
    let padTop = parseFloat(this.padding.top)
    let padBottom = parseFloat(this.padding.bottom)
    // let color = d3.scaleOrdinal(d3.schemeCategory10)
    // 設定顏色陣列,這個自定義的好看一些
    let color = ['#c23531', '#2FC25B', '#FACC14', '#223273', '#8543E0', '#13C2C2', '#3436C7', '#F04864']
    if (isNaN(width) || isNaN(height)) {
      console.error('width 或 height 引數錯誤')
      return
    }
    // 檢查padding引數是否有問題
    if (isNaN(padLeft) || isNaN(padRight) || isNaN(padTop) || isNaN(padBottom)) {
      console.error('padding 引數錯誤')
      return
    }
    // 開始繪圖,建立svg畫布
    let svg = d3.select('#rosePie')
      .append('svg')
      .attr('width', width)
      .attr('height', height)
    // 還是利用d3.pie初始化資料
    let pie = d3.pie()
      // .sort(null)
      .sort(function (a, b) { return a.value - b.value }) // 從小到大排序
      .value((d) => d.value)(_this.data)
    // 先繪製一下圖例
    padTop += 40 // 先預留40畫素的高度放圖例
    let legend = d3.select('#rosePie')
      .append('div')
      // 建立dom容器
      .attr('class', 'legendContainer clearfix')
      .style('height', '40px')
      .style('position', 'absolute')
      .style('left', 'calc(50% + ' + _this.getStyle(dom, 'paddingLeft') + ')')
      .style('top', _this.getStyle(dom, 'paddingTop'))
      .style('transform', 'translate(-50%,0)')
      .style('display', 'inline-table')
      // 每個圖例容器
      .selectAll('.legend')
      .data(pie)
      .enter()
      .append('div')
      .attr('class', 'legend')
    legend.html(function (d, i) {
      return `<div class="chart" style="background: ${color[d.index]}"></div><span>${d.data.name}</span>`
    })
    // 求外半徑最大值
    let outerRadius = d3.min([width - padLeft - padRight, height - padTop - padBottom]) / 2
    let innerRadius = 0 // 設定內半徑為0,畫圓
    // 設定比例尺 range([innerRadius + outerRadius / 2.5, outerRadius / 1.5]) 輸出為了美觀可以進行一些改動
    let scale = d3.scaleLinear().domain([d3.min(_this.data, function (d) { return d.value }), d3.max(_this.data, function (d) { return d.value })]).range([innerRadius + outerRadius / 2, outerRadius / 1.2])
    // 繪製弧
    let arcs = svg.selectAll('.arcs')
      .data(pie)
      .enter()
      .append('path')
      .attr('transform', 'translate(' + (padLeft + (width - padLeft - padRight) / 2) + ',' + (padTop + (height - padTop - padBottom) / 2) + ')')
      .attr('fill', function (d, i) { return color[d.index] })
    // 新增動畫
    arcs.transition()
      .duration(function (d) { return 200 })
      .ease(d3.easeLinear)
      .delay(function (d, i) {
        return d.index * 200
      })
      .attrTween('d', function (d, i) {
        let arc = d3.arc()
          .innerRadius(innerRadius) // 設定環的內半徑,為0的時候則是圓
          .outerRadius(scale(d.value)) // 通過比例尺計算外半徑
        let interpolate = d3.interpolate({startAngle: d.startAngle, endAngle: d.startAngle}, {startAngle: d.startAngle, endAngle: d.endAngle})
        return function (t) {
          return arc(interpolate(t))
        }
      })
    // 新增互動事件
    arcs.on('mouseover', function (d, i) {
      d3.select(this)
        .style('opacity', 0.7)
        .transition()
        .duration(200)
        .ease(d3.easeBounceOut)
        .attr('d', function (d, i) {
          let arc = d3.arc()
            .innerRadius(0) // 設定環的內半徑,為0的時候則是圓
            .outerRadius(scale(d.value) * 1.2) // 通過比例尺計算外半徑
          return arc(d)
        })
    })
    // 新增tooltips
    let toolTips = d3.select('body').append('div')
      .attr('class', 'toolTips')
      .style('opacity', 0)
      .style('position', 'absolute')
    arcs.on('mousemove', function (d) {
      let html = `<div class="clearfix"><div class="border" style="background:${color[d.index]}"></div><span>${d.data.name}:${d.data.value}</span></div>`
      let mouseX = d3.event.clientX + 30
      let mouseY = d3.event.clientY - 30
      // 如果你的style用了scoped,那你的樣式應該寫到App.vue中去,否則插入元素的樣式不會生效
      toolTips.html(`<div class="tolTp">${html}</div>`)
        .style('opacity', 1)
        .style('left', mouseX + 'px')
        .style('top', mouseY + 'px')
    })
    arcs.on('mouseout', function (d, i) {
      d3.select(this)
        .style('opacity', 1)
        .transition()
        .duration(200)
        .ease(d3.easeLinear)
        .attr('d', function (d, i) {
          let arc = d3.arc()
            .innerRadius(0) // 設定環的內半徑,為0的時候則是圓
            .outerRadius(scale(d.value)) // 通過比例尺計算外半徑
          return arc(d)
        })
      toolTips.style('opacity', 0)
      toolTips.html('')
    })
  }
}
</script>

<style lang="less">
#rosePie{
  width: 600px;
  height: 600px;
  margin: 20px 20px;
  padding: 15px 25px;
  border:1px solid #cccccc;
  position: relative;
}
.tolTp{
  padding:8px 12px;
  background: rgba(0, 0, 0, 0.7);
  color:white;
  height: 20px;
  .border{
    width: 6px;
    height: 6px;
    border-radius: 3px;
    background: #83bff6;
    float: left;
    margin:7px 8px 7px 0;
  }
  span{
    float: left;
    line-height: 20px;
  }
}
.legend{
  float: left;
  .chart{
    width: 30px;
    height: 18px;
    margin: 11px 6px;
    background: rebeccapurple;
    float: left;
    border-radius: 4px;
    cursor: pointer;
    &:hover{
      opacity: 0.4;
    }
  }
  span{
    float: left;
    line-height: 40px;
    font-size: 12px;
    margin-right: 10px;
  }
}
</style>