我的项目需要

须要在一个折线图上减少一条回归趋势线。看了看文档发现目前在用的 g2plot 没有相干的反对,于是用来能够反对趋势线的 g2 来做。

我的项目中应用的 g2 版本是 "@antv/g2": "^4.1.26"

数据源

首先看下数据源,数据是某时间段内的月流水折线图。

export default [  { legend: '流水', x: '2020-02', y: 9422524800 },  { legend: '流水', x: '2020-03', y: 9384111400 },  { legend: '流水', x: '2020-04', y: 9738050100 },  { legend: '流水', x: '2020-05', y: 9131817000 },  { legend: '流水', x: '2020-06', y: 8214020510 },  { legend: '流水', x: '2020-07', y: 8846402001 },  { legend: '流水', x: '2020-08', y: 8688620800 },  { legend: '流水', x: '2020-09', y: 8394596700 },  { legend: '流水', x: '2020-10', y: 7316115400 },  { legend: '流水', x: '2020-11', y: 7511430100 },  { legend: '流水', x: '2020-12', y: 7566870200 },  { legend: '流水', x: '2021-01', y: 6587654400 },  { legend: '流水', x: '2021-02', y: 5228583200 },  { legend: '流水', x: '2021-03', y: 4103783800 },  { legend: '流水', x: '2021-04', y: 3220705200 },  { legend: '流水', x: '2021-05', y: 2503257600 },  { legend: '流水', x: '2021-06', y: 2802926800 },  { legend: '流水', x: '2021-07', y: 2491700010 },  { legend: '流水', x: '2021-08', y: 3044604008 },  { legend: '流水', x: '2021-09', y: 3025648200 },  { legend: '流水', x: '2021-10', y: 5056173600 },  { legend: '流水', x: '2021-11', y: 6031860100 },  { legend: '流水', x: '2021-12', y: 6377486200 },  { legend: '流水', x: '2022-01', y: 6308561500 },  { legend: '流水', x: '2022-02', y: 7015006800 },  { legend: '流水', x: '2022-03', y: 7213882100 },  { legend: '流水', x: '2022-04', y: 8600740100 },  { legend: '流水', x: '2022-05', y: 9593594010 },  { legend: '流水', x: '2022-06', y: 6926563800 },  { legend: '流水', x: '2022-07', y: 9748705600 },  { legend: '流水', x: '2022-08', y: 9545976190 },  { legend: '流水', x: '2022-09', y: 3650933130 },  { legend: '流水', x: '2022-10', y: 4269464400 },  { legend: '流水', x: '2022-11', y: 2650898700 },  { legend: '流水', x: '2022-12', y: 2679398600 },  { legend: '流水', x: '2023-01', y: 2946118600 }]

组件

上面是组件,如有须要可间接应用。

<template>  <div ref="ChartContainer"></div></template><script>import { Chart } from '@antv/g2'import DataSet from '@antv/data-set'import { formatData } from '@/utils/formatter'export default {  name: 'LineChart',  components: {},  props: {    chartData: Array,    formatType: String,    showTrend: Boolean,  },  computed: {    // 每年一月份备注    annotations() {      const startOfYears = this.chartData        .map((item, index) => ({ ...item, index }))        .filter(item => item.x.endsWith('-01'))      return startOfYears.map(item => ({        type: 'line',        start: [`${item.index}`, 'start'],        end: [`${item.index}`, 'end'],        top: true,        style: {          stroke: '#606266',          lineWidth: 1,          lineDash: [4, 4]        }      }))    }  },  mounted() {    this.initChart()    this.renderChart()  },  beforeDestroy() {    this.chart = null  },  methods: {    initChart() {      this.chart = new Chart({        container: this.$refs.ChartContainer,        autoFit: true,        height: 300,        appendPadding: 10      })    },    renderChart() {      this.chart.clear()      const transData = this.chartData.map((item, index) => ({        ...item,        index      }))      this.chart.scale({        trendX: {          range: [0, 1]        },        y: {          min: 0,          sync: true,          nice: true        },        trendY: {          min: 0,          sync: 'y',          nice: true        }      })      this.chart.tooltip({        showCrosshairs: true,        title: (title, datum) => {          return datum.x        },        customItems: items => {          return items.map(item => {            return {              ...item,              name: item.data.legend,              value: formatData(item.data.y, this.formatType)            }          })        }      })      const view1 = this.chart.createView()      view1.axis('index', {        label: {          formatter: val => {            return transData[val].x          },          style: {            fill: '#000000'          }        }      })      view1.axis('y', {        label: {          formatter: val => {            return formatData(val, this.formatType)          },          style: {            fill: '#000000'          }        }      })      if (this.chartData.length > 0) {        view1.legend({          custom: true,          items: [            {              value: this.chartData[0].legend,              name: this.chartData[0].legend,              marker: { style: { fill: '#ed786c' } }            }          ]        })      }      view1.data(transData)      view1        .line()        .position('index*y')        .color('#ed786c')        .shape('smooth')      this.annotations.forEach(option => {        view1.annotation().line(option)      })      if (this.showTrend) {        const ds = new DataSet()        const dv = ds.createView().source(transData)        dv.transform({          type: 'regression',          method: 'polynomial',          fields: ['index', 'y'],          bandwidth: 0.1,          as: ['trendX', 'trendY']        })        const view2 = this.chart.createView({          padding: [5, 0, 48, 72]        })        view2.axis(false)        view2.data(dv.rows)        view2          .line()          .position('trendX*trendY')          .style({            stroke: '#969696',            lineDash: [3, 3]          })          .tooltip(false)      }      this.chart.render()    }  },  watch: {    showTrend() {      if (this.chart) {        this.renderChart()      }    },    chartData() {      if (this.chart) {        this.renderChart()      }    }  }}</script>

# 组件成果

遇到的坑

x 轴应用月份字符串导致浏览器卡死

一开始,我认为 DataSet 是辨认日期的,于是 x 轴间接提供的 2022-04 这种字符串,后果……间接浏览器解体了。猜想是转换出的数据量太大导致。

于是去看了看示例,示例用的是年份 2022,它应用 DataSet 的 map 转换将字符串转成了数字类型。

const dv = ds.createView().source(data);dv.transform({  type: 'map',  callback: row => {    row.year = parseInt(row.year, 10);    return row;  }}).transform({  type: 'regression',  method: 'polynomial',  fields: ['year', 'value'],  bandwidth: 0.1,  as: ['Year', 'Value']});

如果我将示例外面的 map 局部去掉浏览器仍旧卡死。所以我试着将 X 转为数组类型的值。

应用月份工夫戳导致趋势线离谱

我试着将月份转成了两种数字格局,如果月份是 2022-02,第一种我给他转成了 202202,第二种我则是应用了 1548381600 这种进位到秒的 unix 工夫戳。

第一种转换确实出了数据,然而达不到预期。我想了想其实月份并不是递增的,比方 202212 前面并不是 202213 而是 202301 了。所以这种 X 轴算趋势线应该是不成立的。

第二种转换须要特地留神的一点是,因为 unix 工夫戳有 10 位数,所以 bandwidth 不能再用 0.1 了,否则会转换出 100000+ 条数据进去,我给的距离是 60*60*24 也就是一天。后果趋势线确实进去了,然而趋势线的值却是一堆正数。实测了一下 g2 示例中的转换趋势线,发现也是一堆正数。所以并不能将数据线和趋势线的 Y 轴同步。同步后就会呈现数据线和趋势线相隔很远的状况。

我不分明其中的计算形式,不过无奈同步 Y 轴总让人好受。

两种形式都不称心后,忽然灵光一闪。既然要间断的数,那么我不论月份,而是把这些 X 轴的点当作是 1 2 3 4 5 6 7 8 9 10 这种间断数字呢?不肯定非得要装置月份数据来安顿 X 轴啊。

// 记录下索引值const transData = this.chartData.map((item, index) => ({    ...item,    index}))// 将索引值当做 X 轴dv.transform({    type: 'regression',    method: 'polynomial',    fields: ['index', 'y'],    bandwidth: 0.1,    as: ['trendX', 'trendY']})

后果是能够的,岂但趋势线的走势和预期的统一,而且趋势线 Y 轴数据也能和折线图 Y 轴数据对上,完满解决。

两个 chart view 数据不同步

须要正确配置 scale 函数,上面代码中趋势线 trendYY 进行了同步。

this.chart.scale({trendX: {  range: [0, 1]},y: {  min: 0,  sync: true,  nice: true},trendY: {  min: 0,  sync: 'y',  nice: true}})

两个 chart view 不重合

因为 chart 是应用了两个 view 来进行重叠绘制,而两个图并没有齐全对齐。

解决方案是,关上两个 view 的 axis 坐标,配置 padding 让两个图的 x 轴和 y 轴都重合。

Y 轴数值太小导致趋势线出现异常

在测试过程中,发现由一条条水平线组成的趋势线,这显然是有问题的。经测试发现是 transform 函数只保留两位小数,所以会呈现 64,64,64,64,65,65,65,65,66,66,66,66 这种 y 轴信息。

解决方案也很简略,先全副乘以 10000,而后进行趋势线转换,转后再全副除以 10000 就能够了。

dv.transform({  type: 'map',  callback: row => {    row.y = Number(row.y) * 10000    return row  }})dv.transform({  type: 'regression',  method: 'polynomial',  fields: ['index', 'y'],  bandwidth: 0.1,  as: ['trendX', 'trendY']})const rows = dv.rows.map(row => ({  ...row,  trendY: row.trendY / 10000}))

试图清掉现有画布从新渲染

屡次 render 画布会呈现一些奇怪问题,所以每次数据更新都须要刷新画布。本来我应用的是 g2 提供的 clear 函数。

this.chart.clear()

但理论利用中 clear 函数无奈分明自定义的 legend 图例,不晓得为什么,于是只能暴力破解了。

this.chart = nullthis.$refs.ChartContainer.innerHTML = ''// 而后再从新 new chartthis.chart = new Chart({    container: this.$refs.ChartContainer,    autoFit: true,    height: 300,    appendPadding: 10})

最初

因为 g2 4.x 对于趋势线的形容很少,所以让我走了不少的弯路。去网上查资料呢也没找到太有用的,所以记录下,心愿能帮到同样遇到问题的敌人吧。

g2 最新反对

就在发文的 2023/02/15 当天,g2 更新了官网文档及相干示例。相比于老版文档中很小一块儿讲趋势线不同,新版本有专门的示例来演示新版 g2 对趋势线的利用。