byte-fe / intern-study

实习生互助学习
MIT License
33 stars 6 forks source link

chart in 2b project #24

Open deligent-ant opened 6 years ago

deligent-ant commented 6 years ago

2B的报表组件

主要使用 frappe-chart 组件,对其做了一个简单封装。主要提供以下几个功能。

  1. 图形的放大
  2. 图片的导出,可导出svg格式和导出excel表格(自己封装的一个组件)
  3. 插槽添加扩展按钮
  4. 显示最大值 最小值 平均值
  5. 对单点数据错误做了处理
  6. byte上的包解决import 错误和 stacked bar 在0值的ui问题

主要属性

chartData: {}
chartConfig:{}

chartData与chartConfig设置和官网一样 主要增加如下配置

chartConfig: {
btnOptions: {
sizeBtn: false,//{ show: true,}
exportBtn: { show: true, type: 'data', xlabel: '时间' }
},
showMax_Min_Mean: {
max: false,
min: false,
mean: false
}}

组件比较简短粗燥 ,所以直接端上来了。


<template>
  <div :class="{component_chart:true,chartLarge:hasLarged}"
    v-if="chartShow">
    <div class="chartWrap"
      ref="chartWrap">
      <div class="chart"
        ref="chart">
      </div>

      <!-- 扩展的按钮 -->
      <div class="anatherBtn">
        <slot>
        </slot>
      </div>

      <div :class="{largeBtn:(chartConfig.btnOptions||{}).sizeBtn, chartBtn:true}"
        @click="changeSize"
        v-if="(chartConfig.btnOptions||{}).sizeBtn">
        <i class="el-icon-rank"></i>
      </div>
      <div :class="{downloadBtn:(chartConfig.btnOptions||{}).exportBtn, chartBtn:true}"
        @click="exportChart"
        v-if="((chartConfig.btnOptions||{}).exportBtn||{}).show&&chartConfig.btnOptions.exportBtn.type=='pic'">
        <i class="el-icon-download"></i>
      </div>

      <out-put :class="{downloadBtn:(chartConfig.btnOptions||{}).exportBtn, chartBtn:true}"
        :params="outputParams"
        v-if="((chartConfig.btnOptions||{}).exportBtn||{}).show&&chartConfig.btnOptions.exportBtn.type=='data'">
        <i class="el-icon-download"></i>
      </out-put>
    </div>
  </div>
</template>

<script>
import _ from 'lodash'
import OutPut from '~/components/common/outputXLSX'
import { Chart } from 'frappe-charts'
export default {
  data() {
    return {
      chartShow: true,
      chartDom: {},
      hasLarged: false,
      chart: {},
      config: {}
    }
  },
  props: {
    chartData: { type: Object, default: () => ({}) },
    chartConfig: { type: Object, default: () => ({}) }
  },
  computed: {
    outputParams() {
      let jsonData = []
      let keyMap = {
        xlabel: ((this.chartConfig.btnOptions || {}).exportBtn || {}).xlabel
      }
      //验证数据格式
      if (
        !(
          Array.isArray(this.chartData.datasets) &&
          this.chartData.datasets.length &&
          Array.isArray(this.chartData.labels) &&
          this.chartData.labels.length
        )
      ) {
        return { jsonData, keyMap }
      }
      this.chartData.datasets.forEach((line, index) => {
        keyMap['key' + index] = line.name
      })

      this.chartData.labels.forEach((x, i) => {
        let oneRow = {}
        oneRow.xlabel = x
        this.chartData.datasets.forEach((line, index) => {
          oneRow['key' + index] = line.values[i]
        })
        jsonData.push(oneRow)
      })
      return { jsonData, keyMap, fileName: this.chartConfig.title }
    }
  },
  watch: {
    chartData(newValue, oldValue) {
      let chartDom = this.$refs.chart
      if (
        Array.isArray(newValue.labels) &&
        Array.isArray(oldValue.labels) &&
        newValue.labels.length == oldValue.labels.length &&
        newValue.datasets.length == oldValue.datasets.length
      ) {
        this.chart.update(newValue)
      } else {
        this.initChart()
      }
    }
  },
  components: { OutPut },
  methods: {
    changeSize() {
      this.hasLarged = !this.hasLarged
      if (this.hasLarged) {
        this.$set(this.config, 'height', this.config.height * 2)
      } else {
        this.$set(this.config, 'height', this.chartConfig.height)
      }
      this.initChart(true)
    },
    exportChart() {
      this.chart.export()
    },
    initChart(isSize) {
      if (!isSize) {
        this.config = _.cloneDeep(this.chartConfig)
      }
      //验证数据格式
      if (
        !(
          Array.isArray(this.chartData.datasets) &&
          this.chartData.datasets.length &&
          Array.isArray(this.chartData.labels) &&
          this.chartData.labels.length
        )
      ) {
        return
      }
      //单点情况
      if (this.chartData.labels.length == 1) {
        this.chartData.labels.push('')
        if (this.config.type == 'line') {
          this.config.lineOptions = {
            dotSize: 4,
            hideDots: 0
          }
        } else if (this.config.type == 'bar') {
          this.config.barOptions = {}
        }
      } else {
        if (!isSize) {
          this.config = _.cloneDeep(this.chartConfig)
        }
      }

      let allData = []
      this.chartData.datasets.forEach(element => {
        allData.push(...element.values)
      })
      //确保坐标最大值和最小值能显示
      if (this.chartConfig.showMax_Min_Mean) {
        if (
          !(this.chartData.yMarkers && Array.isArray(this.chartData.yMarkers))
        ) {
          this.chartData.yMarkers = []
        }
        if (this.chartConfig.showMax_Min_Mean.max) {
          this.chartData.yMarkers.push({
            label: 'max',
            value: Math.max(...allData),
            options: { labelPos: 'left' }
          })
        }
        if (this.chartConfig.showMax_Min_Mean.min) {
          this.chartData.yMarkers.push({
            label: 'min',
            value: Math.min(...allData),
            options: { labelPos: 'left' }
          })
        }
        if (this.chartConfig.showMax_Min_Mean.mean) {
          let sum = 0
          allData.forEach(item => {
            sum += item
          })
          this.chartData.yMarkers.push({
            label: 'mean',
            value: sum / allData.length,
            options: { labelPos: 'left' }
          })
        }
      }

      if (
        this.chartData.labels.length < 15 &&
        this.config.lineOptions &&
        !this.config.lineOptions.hideDots
      ) {
        this.chartConfig.lineOptions.dotSize = 4
      }
      setTimeout(() => {
        this.chart = new Chart(this.chartDom, {
          data: this.chartData,
          ...this.config
        })
      }, 50)
    }
  },
  mounted() {
    let chartDom = this.$refs.chart
    this.chartDom = chartDom
    this.initChart()
  },
  beforeDestroy() {
    //离开时候 注销chart
    if (
      this.chart.unbindWindowEvents &&
      typeof this.chart.unbindWindowEvents == 'function'
    ) {
      this.chart.unbindWindowEvents()
    }
  }
}
</script>
<style lang="stylus">
.chart
  .frappe-chart.chart
    text.title
      font-size 14px
      fill rgb(34 34 34)
</style>
<style scoped lang="stylus">
.component_chart
    font-family 'PingFangSC-Regular'
    display flex
    flex-direction column
    justify-content center
    width 100%
    overflow hidden
    .chartWrap
      position relative
      text-align center
      overflow hidden
      .chart
        width 100%
      .chartBtn
          font-size 18px
          padding 2px 5px
          position absolute
          color rgb(182 192 226)
          &:hover
            cursor pointer
        .largeBtn
          top 10px
          right 1px
        .downloadBtn
          bottom 0px
          right 1px
        .anatherBtn
          position absolute
          top 4px
          left 115px
          // top 10px
          // right 60px
.chartLarge
  width 970px
  height 500px
  .largeBtn
     top 10px
     right 20px!important
  .downloadBtn
     bottom 0px
     right 20px!important
</style>