數(shù)據(jù)可視化前端項(xiàng)目開(kāi)發(fā) - 圖表

圖表

highchart vs g2plot

在 chart 庫(kù)的選擇上,項(xiàng)目先后使用了 highchart 和 g2plot。在我接手項(xiàng)目之前,用的是 highchart 庫(kù),但 highchart 有 3 點(diǎn)是讓我挺難受的。

  1. 無(wú)法直接將數(shù)據(jù)源轉(zhuǎn)為 chart ,必須進(jìn)行一輪復(fù)雜的數(shù)據(jù)轉(zhuǎn)換去匹配 highchart 的配置方式。
  2. 由于無(wú)法直接將數(shù)據(jù)源變成 chart,老系統(tǒng)非常粗暴的將整個(gè) highchart 配置都交給后端來(lái)編寫,前端側(cè)無(wú)法控制 chart 呈現(xiàn),而后端同學(xué)幾經(jīng)轉(zhuǎn)手難以理解其中的配置,每每有 chart 改動(dòng)需求都非常頭疼。
  3. 就我個(gè)人而言,highchart 的官方文檔寫的很差勁,看文檔很累。

在我們前后端同學(xué)再 highchart 的泥潭中掙扎一段時(shí)間后,逐步使用螞蟻的 g2plot 來(lái)替代。收益也很明顯。

  1. g2 和 g2plot 的文檔和示例非常友好,很容易入手。
  2. g2plot 的 chart 呈現(xiàn)是將數(shù)據(jù)源和 chart 配置分離的,后端同學(xué)只需要提供一串?dāng)?shù)據(jù)數(shù)組,chart 配置的事情都交給前端即可。
  3. g2 的功能非常強(qiáng)大,目前項(xiàng)目已經(jīng)基于 g2 實(shí)現(xiàn)了中國(guó)地圖、折線圖、雙軸圖、餅圖、環(huán)圖、柱狀圖、條形圖以及更加復(fù)雜的組合圖。

g2plot 在 Vue 中的封裝

因?yàn)?g2plot 并沒(méi)有特別針對(duì) Vue 的封裝,簡(jiǎn)單寫了個(gè)組件用于 Vue 項(xiàng)目中。

<template>
  <div ref="chart" />
</template>

<script>
import { Area, Column, Line, Bar, Pie, DualAxes, Gauge } from '@antv/g2plot'
import { getChartColorsByGameId } from '@/utils/theme.js'

export default {
  name: 'GmChart',
  props: {
    type: String,
    data: Array,
    options: Object
  },
  mounted() {
    this.chartColors = getChartColorsByGameId(this.$store.state.gameId)
    this.chartInstanse = this.getChartInstance()
    this.chartInstanse.update(this.options)
    this.chartInstanse.render()
  },
  beforeDestroy() {
    this.chartInstanse.destroy()
  },
  methods: {
    getChartInstance() {
      const option = {
        data: this.data,
        height: 300,
        color: this.chartColors,
        ...this.options
      }
      switch (this.type) {
        case 'area':
          return new Area(this.$refs.chart, option)
        case 'line':
          return new Line(this.$refs.chart, option)
        case 'column':
          return new Column(this.$refs.chart, option)
        case 'bar':
          return new Bar(this.$refs.chart, option)
        case 'pie':
          return new Pie(this.$refs.chart, option)
        case 'dual-axes':
          return new DualAxes(this.$refs.chart, option)
        case 'gauge':
          return new Gauge(this.$refs.chart, option)
        default:
          return null
      }
    }
  },
  watch: {
    data(val) {
      this.chartInstanse.changeData(val) // 更新 data 數(shù)據(jù)
    },
    options(val) {
      this.chartInstanse.update(val) // 更新 chart 配置
    }
  }
}
</script>

中國(guó)地圖數(shù)據(jù)占比圖

在 g2plot 中并沒(méi)有快捷的地圖選項(xiàng),這就得用到更強(qiáng)大的 g2 的地圖了。做地圖要注意幾點(diǎn):

  1. 中國(guó)地圖的 geo 數(shù)據(jù) g2 并沒(méi)有提供,我用的是 github 上的 china-geojson 庫(kù),里面包含了中國(guó)及各省市的經(jīng)緯度信息。
  2. 當(dāng)時(shí)后端同學(xué)給我的省市名稱是簡(jiǎn)寫,如上海、新疆這種,而非全稱。這樣是無(wú)法匹配數(shù)據(jù)顯示到地圖上的。后端提供的數(shù)據(jù)源必須和地圖 json 文件中的信息一致。
<template>
  <div id="china-province-map-container" />
</template>

<script>
import DataSet from '@antv/data-set'
import { Chart } from '@antv/g2'

// Geo JOSN file path
const GeoPath =
  process.env.NODE_ENV === 'development'
    ? '/china-geo.json'
    : '/spa/china-geo.json'

export default {
  name: 'chinaProvinceMap',
  props: {
    chartData: Array,
  },
  data() {
    return {
      mapData: null,
      chart: null,
    }
  },
  async mounted() {
    await this.getChinaMapGeo()
    this.createMapChartInstance()
    this.renderMap()
  },
  beforeDestroy() {
    if (this.chart) {
      this.chart.destroy()
    }
  },
  methods: {
    createMapChartInstance() {
      this.chart = new Chart({
        container: 'china-province-map-container',
        // autoFit: true,
        width: 500,
        height: 400,
      })

      // set tootip
      this.chart.tooltip({
        showTitle: false,
        showMarkers: false,
        shared: true,
        customItems(items) {
          return items.map((item) => ({
            ...item,
            name: item.data.tag_value,
          }))
        },
        domStyles: {
          'g2-tooltip': {
            width: '180px',
          },
        },
      })
      // set scale sync
      this.chart.scale({
        longitude: {
          sync: true,
        },
        latitude: {
          sync: true,
        },
      })
      // close axis
      this.chart.axis(false)
      // close legend
      this.chart.legend(false)
    },
    getChinaMapGeo() {
      return fetch(GeoPath)
        .then((res) => res.json()) // transfer
        .then((mapData) => {
          this.mapData = mapData
        })
    },
    renderMap(isUpdate) {
      this.chart.clear()
      const geoDv = new DataSet.View().source(this.mapData, {
        type: 'GeoJSON',
      })

      const geoView = this.chart.createView()
      geoView.data(geoDv.rows)
      geoView.polygon().position('longitude*latitude').color('#ebedf0').style({
        lineWidth: 1,
        stroke: '#fafbfc',
      })
      geoView.tooltip(false)

      const userView = this.chart.createView()
      userView.data(this.chartData)
      userView
        .point()
        .position('longitude*latitude')
        .color('#1890ff')
        .shape('circle')
        .size('num', [3, 11])
        .style({
          lineWidth: 1,
          stroke: '#1890ff',
        })
      userView.interaction('element-active')
      this.chart.render(isUpdate)
    },
  },
  watch: {
    chartData() {
      if (this.chart) {
        this.renderMap(true)
      }
    },
  },
}
</script>

<style lang="scss" scoped>
</style>

效果如下


map

復(fù)雜圖表的實(shí)現(xiàn)

另外還有一種比較麻煩的圖表是那種用戶想要的定制化圖表,比如下面兩個(gè):


組合條形圖

組合雙軸圖

以上 chart 都可以通過(guò) g2plot 的 多圖層圖表 Mix 來(lái)實(shí)現(xiàn)的。想要實(shí)現(xiàn)多圖層圖表其實(shí)并不復(fù)雜,只要將它拆分為最小單位的一個(gè)個(gè)圖表,就像 PhotoShop 的圖層一樣堆疊實(shí)現(xiàn)就可以了。

不過(guò)多圖層圖表是有一些坑的:

  • 多圖層圖表的 legend 圖例并無(wú)法顯示完全,暫時(shí)沒(méi)有找到原因。我是通過(guò)異常 g2 的圖例自己手寫替代來(lái)繞過(guò)的。
  • 多圖層的圖標(biāo) tooltip 提示信息在某些地方會(huì)出現(xiàn)不停閃爍的問(wèn)題,暫時(shí)沒(méi)有解決。
  • 圖表的標(biāo)注屬性 annotations 會(huì)出現(xiàn)在整個(gè)圖表的最上層,而不是當(dāng)前圖表上層,會(huì)出現(xiàn)遮擋其他圖層的圖表的問(wèn)題,暫時(shí)沒(méi)有解決。

另外一些需求

  • 在折線圖上增加關(guān)鍵點(diǎn)的小旗標(biāo)識(shí)(使用 g2 的繪圖功能)。
G2.registerShape('point', 'flag-point', {
  draw(cfg, container) {
    const point = { x: cfg.x, y: cfg.y }
    const group = container.addGroup()
    if (cfg.data.operation_event && cfg.data.operation_event.length > 0) {
      group.addShape('image', {
        attrs: {
          x: point.x - 4,
          y: point.y - 20,
          img: FlagImg,
          width: 16,
          height: 16,
        },
      })
    }

    return group
  },
})
  • 在 tooltip 中增加額外信息(使用 tooltip 的 customContent 方法)。
  • 在折線圖中將某一段標(biāo)為虛線(使用 Line 的 annotations 屬性)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容