共计 6050 个字符,预计需要花费 16 分钟才能阅读完成。
我的项目需要
须要在一个折线图上减少一条回归趋势线。看了看文档发现目前在用的 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 = null
this.$refs.ChartContainer.innerHTML = ''
// 而后再从新 new chart
this.chart = new Chart({
container: this.$refs.ChartContainer,
autoFit: true,
height: 300,
appendPadding: 10
})
最初
因为 g2 4.x 对于趋势线的形容很少,所以让我走了不少的弯路。去网上查资料呢也没找到太有用的,所以记录下,心愿能帮到同样遇到问题的敌人吧。
g2 最新反对
就在发文的 2023/02/15 当天,g2 更新了官网文档及相干示例。相比于老版文档中很小一块儿讲趋势线不同,新版本有专门的示例来演示新版 g2 对趋势线的利用。