我的项目需要
须要在一个折线图上减少一条回归趋势线。看了看文档发现目前在用的 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
函数,上面代码中趋势线 trendY
和 Y
进行了同步。
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 对趋势线的利用。