4

前言

在开始之前,希望读者听说过或者了解过以下名词

准备活动

项目初始化

假设你已经有了一个构建好了的vue项目,or请移步npm构建vue项目
a. 安装依赖
在项目根目录下打开命令行工具安装相关的依赖 npm i d3 element-ui babel-preset-stage-3 babel-plugin-component -S
b. 修改.babelrc文件,修改后为:

{
  "presets": [
    ["env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }],
    // 兼容es6的针对对象的扩展运算符
    "stage-3"
  ],
  "plugins": ["transform-vue-jsx", 
    "transform-runtime",
    // element 按需加载配置
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]],
  "env": {
    "test": {
      "presets": ["env", "stage-3"],
      "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
    }
  }
}

c. 引入样式 main.js

import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

// 引入样式
Vue.use(ElementUI)
Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

d. 创建相应的文件,目录结构
clipboard.png

  • BarChart.vue

    <template>
      <div class="bar-chart"></div>
    </template>
    
    <script>
    export default {
      name: 'BarChart'
    }
    </script>
    
    <style></style>
  • router/index.vue

    import Vue from 'vue'
    import Router from 'vue-router'
    import HelloD3 from '@/view/HelloD3'
    
    Vue.use(Router)
    
    export default new Router({
      routes: [
        {
          path: '/',
          name: 'HelloD3',
          component: HelloD3
        }
      ]
    })
    
  • HelloD3.vue

    <template>
      <el-tabs value="barChart" type="card">
        <el-tab-pane label="条形统计图" name="barChart">用户管理</el-tab-pane>
      </el-tabs>
    </template>
    
    <script>
    export default {
      name: 'HelloD3'
    }
    </script>
    
    <style scoped></style>
  • Title.vue

    <template>
      <h1>MY D3.JS</h1>
    </template>
    
    <script>
    export default {
      name: 'title'
    }
    </script>
    
    <style></style>

e. 运行测试

检查下代码然后运行试试看

npm start

开始

  1. HelloD3.vue中引用柱形统计图的组件:BarChart.vue

    HelloD3.vue
    
    <template>
      <el-tabs value="barChart" type="card">
        <el-tab-pane label="条形统计图" name="barChart">
          <comp-bar-chart />
        </el-tab-pane>
      </el-tabs>
    </template>
    
    <script>
    import CompBarChart from '../package/components/BarChart'
    export default {
      ... ...
      components: {
        CompBarChart,
      }
      ... ...
    }
    </script>
  2. 开始d3-柱形统计图之旅
     在此之前,希望你有一点d3的基础,一点点就够,首先你要明白d3.js是大部分是通过svg来进行绘图的
     

      OK! BEGIN!
     

      对于柱形统计图组件,我们肯定是想多一些配置属性,让我们的统计图更加灵活,更加容易适合多种场景
      Let's go !

    • BarChart组件设置props,接收父组件传来的参数。
         假想我们有如下参数
         * svg元素的宽和高(即整个统计图的大小)
         * 数据
         * 横坐标(纵坐标一般用具体的数值表示)
         * 条形统计图每一项的背景色、宽度
         * 整个统计图的间距
         ... ...
    • 设置props BarChart.vue

      <template>
        <div id="barGraph"></div>
      </template>
      <script>
      ...
      export default { 
          props: {
              // svg元素宽度
              width: {
                type: Number,
                default: 400
              },
              // svg元素高度
              height: {
                type: Number,
                default: 400
              },
              // 坐标轴
              axis: {
                // 横坐标参数
                xAxisData: {
                  type: Array,
                  required: true,
                  default: () => ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'new']
                }
              },
              // 单个矩形宽度
              barWidth: {
                type: [Number, String],
                default: '70'
              },
              // 矩形背景颜色
              color: {
                type: String,
                default: '#3398DB'
              },
              // 间距
              padding: {
                type: Object,
                default: () => { return { top: 50, right: 50, bottom: 50, left: 50 } }
              },
              // 数据
              data: {
                type: Array,
                default: () => [10, 52, 200, 334, 390, 330, 220]
              },
          }
      }
      </script>
    • 传入数据 HelloD3.vue

      <template>
        <el-tabs value="barChart" type="card">
          <el-tab-pane label="条形统计图" name="barChart">
            <comp-bar-chart
            :data='data'
            :axis="axis"
            :color="barGraph.color"
            :width="barGraph.width"/>
          </el-tab-pane>
        </el-tabs>
      </template>
      <script>
      ...
      export default {
      ...
          data () {
              return {
                // 总数据
                data: [50, 43, 120, 87, 99, 167, 188, 123, 355],
                axis: {
                  // 横坐标参数
                  xAxisData: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', '华为', '中兴', 'oppo'] 
                },
                barGraph: {
                  width: 700, // 由于在条形统计图组件中,各项都有默认值,故而只传width,height使用默认值
                  color: '#3398DB' // 条形块背景色
                }
              }
          }
      ...
      }
      </script>
    • 开始使用d3作图 (全程 BarChart.vue )


      首先引入d3.js  

       import * as d3 from 'd3'

      在组件被mounted的时候,渲染统计图

      export default {
          ...
          mounted {
              try {
                const {axis, data} = this.$props
                // 检测横坐标参数数量是否等于数据数量,保证正常显示
                if (axis.xAxisData.length !== data.length) {
                  throw Error('xAxis.length !== data.length')
                }
                // 渲染图表
                this.render(this.data)
              } catch (e) {
                console.log(e)
              }
          },
          methods: {
              // 渲染图表总函数
              render (data) {
                const bar = this.createAllAttr() // 获取到加工后的数据
                const svg = this.createSvg() // 创建画布
                this.addScale() // 绘制坐标轴
                this.addRect() // 绘制每一个条形
              },
              // 组织数据
              createAllAttr () {
                // 对传入的数据进行加工
              }
          }
          ...
      }
    • 实现上述方法
      组织数据:function createAllAttr()

      createAllAttr () {
        const { padding, axis, width, barWidth, ...otherParams } = this.$props
        const currentWidth = width - padding.left - padding.right // 统计图实际占用宽度
        const barStep = currentWidth / axis.xAxisData.length // 把整个统计图分成横坐标数量那么多等分
        /** 
            getTrueNum() 方法的作用是获取当前元素的实际宽度  
            getTrueNum (father, percent) {
              if (typeof percent === 'number') {
                return percent > father ? father : percent
              } else {
                return father * this.percentToPoint(percent)
              }
            }
            percentToPoint (percent) {
              let point = percent.replace('%', '').replace(' ', '')
              point = point / 100
              return point
            }
            根据父级宽度与子元素百分比单位的宽度,计算子元素实际所占的宽度
        */
        const myBarWidth = Tools.prototype.getTrueNum(barStep, barWidth)
        const spacing = (barStep - myBarWidth) // 根据紧挨着的每等分的宽度,与条形实际宽度,计算相邻的条形之间的间距
        // 返回加工好的数据
        return {
          width,
          padding,
          axis,
          ...otherParams,
          contentWidth: currentWidth,
          barWidth: myBarWidth,
          spacing: spacing
        } 
      }

      创建画布:function createSvg()

      createSvg (data, bar) {
        const { width, height } = bar
        const svg = d3.select('#barGraph')
          .append('svg')
          .attr('width', width)
          .attr('height', height)
        return svg
      }

      绘制坐标轴: function addScale()

      addScale (svg, data, bar) {
        const {contentWidth, height, axis, padding} = bar
        /** 
            建立x轴的比例尺
            scaleBand() 与 scaleLinear() 是d3中比例尺的类型,其他的可以自行了解
            scaleLinear: 线性比例尺
            scaleBand:非连续性比例尺
            range:输出域
            domain:输入域
            将domain中的数据集映射到range数据集中
        */
        const xScale = d3.scaleBand().range([0, contentWidth]).domain(axis.xAxisData)
        // 定义x轴
        const xAxis = d3.axisBottom(xScale)
        // 建立y轴的比例尺
        const yScale = d3.scaleLinear().range([height - padding.top - padding.bottom, 0]).domain([0, d3.max(data)])
        // 定义y轴
        const yAxis = d3.axisLeft(yScale)
        // 将x轴添加到svg元素中
        svg.append('g')
          .classed('axis-x', true)
          .attr('transform', `translate(${padding.left}, ${height - padding.bottom})`)
          .call(xAxis)
        // 将y轴添加到svg元素中
        svg.append('g')
          .classed('axis-y', true)
          .attr('transform', `translate(${padding.left}, ${padding.bottom})`)
          .call(yAxis)
        // 返回比例尺,在绘制条形等情况时有用
        return {
          xScale,
          yScale
        }
      }

      d3比例尺大全
      绘制条形:function addRect()

      // 每一个rect是通过左上角坐标定位的
      addRect (svg, data, bar, xScale, yScale) {
        const { barWidth, height, axis, color, padding, spacing } = bar
        const rect = svg.selectAll('rect')
          .data(data)
          .enter()
          .append('rect')
          // 添加背景色与样式
          .attr('fill', color)
          .attr('class', 'my-rect')
          // 整个统计图相对于外层svg元素的位置,在此为左偏移padding.left上偏移padding.top
          .attr('transform', `translate(${padding.left},${padding.top})`)
          // 左上角横坐标
          .attr('x', (d, i) => { return xScale(axis.xAxisData[i]) + spacing })
          // rect 的宽 
          .attr('width', barWidth)
        // 左上角纵坐标
        rect.attr('y', d => { return yScale(d) })
          // rect 的高 
          .attr('height', d => { return height - padding.top - padding.bottom - yScale(d) })
      }
      ... ...
      <style lang="scss">
        .my-rect {
          cursor: pointer;
        }
      </style>

      部分同学可能没有安装sass-loader依赖,会导致webpack无法打包scss类型样式
      解决方案是: npm i sass-loader node-sass -S

    • 由于之前render方法内为写参数传递,在此补上

      render (data) {
        const bar = this.createAllAttr()
        const svg = this.createSvg(data, bar)
        const { xScale, yScale } = this.addScale(svg, data, bar)
        this.addRect(svg, data, bar, xScale, yScale)
      }

      一个最简单的条形统计图绘制完成,可以试着run start

下面是我这个项目的github地址,以后会持续更新一部分可视化组件的封装
d3 + vue 可视化组件的封装
纯手打,please give me a star ~~


Winer
458 声望202 粉丝

一入前端深似海