作者简介:大家好,我是文艺理科生Owen,某车企前端开发,负责前端业务需求和可视化项目 目前在卷的技术方向:数据可视化、工程化系列,主要偏向最佳实践,以及开发中填补的坑 希望对大家有所帮助,有兴趣可以在评论区交流互动,感谢支持~~~
最近接到一个瀑布图的改造需求,产品原定只实现正半轴数据,直接在echarts官方示例中找到原始代码, echarts.apache.org/examples/zh…
样式改造和数据集成后,搞定。
具体解释可参考这个帖子:blog.csdn.net/Snow_GX/art…
主要思路是使用3个series对象,分别对应 占空柱、增加值、减少值。
如下图对应3个series对象数据分别为:
// 占空柱数据
placeholder = [0, 1000, 2000]
// 正增加值数据
positive = [1000, 2000, 0]
// 负增加值数据
negative = [0, 0, 1000]
结果产品说业务又要加负值逻辑,必须加。我:好的(为啥每次不早说呢)
然后开始分析,按照初始值、增量值、结果值分别为正负,一共有8种情况,如下图。然后按照常识(正+正=正,负+负=负)删掉两种异常组合,还剩6种组合。
接下来就依次尝试了。默认从接口中获取到的数据:
- 当前值数组:endList
- 当前值减去上一个值的变化值数组:addList(增加为正值,减少为负值)
然后尝试每一种情况,用 endList 和 addList 表示 占空柱、增加值、减少值 对应数组 。
下面是 echarts官方示例中可运行的代码,可以直接复制测试。
const addData = [1000, -2000]
const colors = ['#5470c6', '#91cc75']
option = {
title: {
text: 'Waterfall Chart'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: function (params) {
var tar = params[1];
return tar.name + '<br/>' + tar.seriesName + ' : ' + tar.value;
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
splitLine: { show: false },
data: ['Total', 'Rent', 'Utilities', 'Transportation', 'Meals', 'Other']
},
yAxis: {
type: 'value'
},
series: [
{
name: 'Placeholder',
type: 'bar',
stack: 'Total',
itemStyle: {
borderColor: 'transparent',
color: 'transparent'
},
emphasis: {
itemStyle: {
borderColor: 'transparent',
color: 'transparent'
}
},
data: [0, 0]
},
{
name: 'Life Cost',
type: 'bar',
stack: 'Total',
label: {
show: true,
position: 'top',
formatter: function(params) {
return addData[params.dataIndex]
}
},
itemStyle: {
color: function(params) {
return addData[params.dataIndex] >= 0 ? colors[0] : colors[1]
}
},
data: [1000, 1000]
},
{
name: 'Life Cost1',
type: 'bar',
stack: 'Total',
label: {
show: false,
position: 'top',
},
itemStyle: {
color: function(params) {
return addData[params.dataIndex] >= 0 ? colors[0] : colors[1]
}
},
data: [0, -1000]
}
]
};
- 初值值正 -> 增量值正 -> 结果值正
规律:
- 占空比数组:endList[i-1]
- 增加值数组: addList[i]
- 减少值数组: 0
- 初值值负 -> 增量值负 -> 结果值负
规律:
- 占空比数组:endList[i-1]
- 增加值数组: addList[i]
- 减少值数组: 0
- 初值值正 -> 增量值负 -> 结果值正
规律:
- 占空比数组:endList[i]
- 增加值数组: 0
- 减少值数组: addList[i]
- 初值值负 -> 增量值正 -> 结果值负
规律:
- 占空比数组:endList[i]
- 增加值数组: -addList[i-1]
- 减少值数组: 0
- 初值值正 -> 增量值负 -> 结果值负(跨轴)
规律:
- 占空比数组:0
- 增加值数组: endList[i-1]
- 减少值数组: endList[i]
- 初值值负 -> 增量值正 -> 结果值正(跨轴)
规律:
- 占空比数组:0
- 增加值数组: endList[i]
- 减少值数组: endList[i-1]
完整效果
源码
echarts演练场可运行代码
const endList = [1000, 2000, 1000, -1000, -2000, -1000, 1000]
const addList = [1000, 1000, -1000, -2000, -1000, 1000, 2000]
// 计算起始值数组
let startList = []
for(let i = 0; i < endList.length; i++) {
startList[i] = endList[i] - addList[i]
}
console.log(startList, 'startList')
let placeholder = [], positive = [], negative = []
const handleData = () => {
endList.forEach((_, i) => {
// 正+正=正,负-负=负
// ● 占空比数组:endList[i-1]
// ● 增加值数组: addList[i]
// ● 减少值数组: 0
if((startList[i] >= 0 && addList[i] > 0 && endList[i] > 0) || (startList[i] <=0 && addList[i] < 0 && endList[i] < 0)) {
placeholder[i] = endList[i-1] || startList[i]
positive[i] = addList[i]
negative[i] = 0
}
// 正+负=正
// ● 占空比数组:endList[i]
// ● 增加值数组: -addList[i]
// ● 减少值数组: 0
else if((startList[i] >=0 && addList[i] < 0 && endList[i] > 0)) {
placeholder[i] = endList[i]
positive[i] = -addList[i]
negative[i] = 0
}
// 负+正=负
// ● 占空比数组:endList[i]
// ● 增加值数组: -addList[i-1]
// ● 减少值数组: 0
else if((startList[i] <= 0 && addList[i] > 0 && endList[i] < 0)) {
placeholder[i] = endList[i]
positive[i] = addList[i-1]
negative[i] = 0
}
// 正+负=负
// ● 占空比数组:0
// ● 增加值数组: endList[i-1]
// ● 减少值数组: endList[i]
else if((startList[i] >=0 && addList[i] < 0 && endList[i] < 0)) {
placeholder[i] = 0
positive[i] = endList[i-1]
negative[i] = endList[i]
}
// 负+正=正
// ● 占空比数组:0
// ● 增加值数组: endList[i]
// ● 减少值数组: endList[i-1]
else if((startList[i] <=0 && addList[i] > 0 && endList[i] > 0)) {
placeholder[i] = 0
positive[i] = endList[i]
negative[i] = endList[i-1]
} else {
console.log(111)
}
})
}
handleData()
console.log(placeholder, 'placeholder')
console.log(positive, 'positive')
console.log(negative, 'negative')
const colors = ['green', 'red']
option = {
title: {
text: 'Waterfall Chart'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: function (params) {
var tar = params[1];
return tar.name + '<br/>' + tar.seriesName + ' : ' + tar.value;
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
splitLine: { show: false },
data: ['Total', 'Rent', 'Utilities', 'Transportation', 'Meals', 'Other']
},
yAxis: {
type: 'value'
},
series: [
{
name: 'Placeholder',
type: 'bar',
stack: 'Total',
itemStyle: {
borderColor: 'transparent',
color: 'transparent'
},
emphasis: {
itemStyle: {
borderColor: 'transparent',
color: 'transparent'
}
},
data: placeholder
},
{
name: 'Life Cost',
type: 'bar',
stack: 'Total',
label: {
show: true,
position: 'insideTop',
formatter: function(params) {
return addList[params.dataIndex]
}
},
itemStyle: {
color: function(params) {
return addList[params.dataIndex] >= 0 ? colors[0] : colors[1]
}
},
data: positive
},
{
name: 'Life Cost1',
type: 'bar',
stack: 'Total',
label: {
show: false,
position: 'top',
},
itemStyle: {
color: function(params) {
return addList[params.dataIndex] >= 0 ? colors[0] : colors[1]
}
},
data: negative
}
]
};
可运行html源码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ECharts 柱形图示例</title>
<script src="https://echarts.apache.org/zh/js/vendors/echarts/dist/echarts.min.js"></script>
</head>
<body>
<div id="main" style="width: 600px; height: 400px"></div>
<script>
// 初始化 echarts 实例
var myChart = echarts.init(document.getElementById("main"));
// 指定图表的配置项和数据
const endList = [1000, 2000, 1000, -1000, -2000, -1000, 1000];
const addList = [1000, 1000, -1000, -2000, -1000, 1000, 2000];
// 计算起始值数组
let startList = [];
for (let i = 0; i < endList.length; i++) {
startList[i] = endList[i] - addList[i];
}
console.log(startList, "startList");
let placeholder = [],
positive = [],
negative = [];
const handleData = () => {
endList.forEach((_, i) => {
// 正+正=正,负-负=负
// ● 占空比数组:endList[i-1]
// ● 增加值数组: addList[i]
// ● 减少值数组: 0
if (
(startList[i] >= 0 && addList[i] > 0 && endList[i] > 0) ||
(startList[i] <= 0 && addList[i] < 0 && endList[i] < 0)
) {
placeholder[i] = endList[i - 1] || startList[i];
positive[i] = addList[i];
negative[i] = 0;
}
// 正+负=正
// ● 占空比数组:endList[i]
// ● 增加值数组: -addList[i]
// ● 减少值数组: 0
else if (startList[i] >= 0 && addList[i] < 0 && endList[i] > 0) {
placeholder[i] = endList[i];
positive[i] = -addList[i];
negative[i] = 0;
}
// 负+正=负
// ● 占空比数组:endList[i]
// ● 增加值数组: -addList[i-1]
// ● 减少值数组: 0
else if (startList[i] <= 0 && addList[i] > 0 && endList[i] < 0) {
placeholder[i] = endList[i];
positive[i] = addList[i - 1];
negative[i] = 0;
}
// 正+负=负
// ● 占空比数组:0
// ● 增加值数组: endList[i-1]
// ● 减少值数组: endList[i]
else if (startList[i] >= 0 && addList[i] < 0 && endList[i] < 0) {
placeholder[i] = 0;
positive[i] = endList[i - 1];
negative[i] = endList[i];
}
// 负+正=正
// ● 占空比数组:0
// ● 增加值数组: endList[i]
// ● 减少值数组: endList[i-1]
else if (startList[i] <= 0 && addList[i] > 0 && endList[i] > 0) {
placeholder[i] = 0;
positive[i] = endList[i];
negative[i] = endList[i - 1];
} else {
console.log(111);
}
});
};
handleData();
console.log(placeholder, "placeholder");
console.log(positive, "positive");
console.log(negative, "negative");
const colors = ["green", "red"];
option = {
title: {
text: "Waterfall Chart",
},
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
},
formatter: function (params) {
var tar = params[1];
return tar.name + "<br/>" + tar.seriesName + " : " + tar.value;
},
},
grid: {
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true,
},
xAxis: {
type: "category",
splitLine: { show: false },
data: [
"Total",
"Rent",
"Utilities",
"Transportation",
"Meals",
"Other",
],
},
yAxis: {
type: "value",
},
series: [
{
name: "Placeholder",
type: "bar",
stack: "Total",
itemStyle: {
borderColor: "transparent",
color: "transparent",
},
emphasis: {
itemStyle: {
borderColor: "transparent",
color: "transparent",
},
},
data: placeholder,
},
{
name: "Life Cost",
type: "bar",
stack: "Total",
label: {
show: true,
position: "insideTop",
formatter: function (params) {
return addList[params.dataIndex];
},
},
itemStyle: {
color: function (params) {
return addList[params.dataIndex] >= 0 ? colors[0] : colors[1];
},
},
data: positive,
},
{
name: "Life Cost1",
type: "bar",
stack: "Total",
label: {
show: false,
position: "top",
},
itemStyle: {
color: function (params) {
return addList[params.dataIndex] >= 0 ? colors[0] : colors[1];
},
},
data: negative,
},
],
};
// 使用指定的配置项和数据显示图表
myChart.setOption(option);
</script>
</body>
</html>
关键修改点
1、柱子颜色
在增加值、减少值对应的series对象中,通过itemStyle的color函数,根据变化值动态展示颜色
itemStyle: {
color: function(params) {
return addList[params.dataIndex] >= 0 ? colors[0] : colors[1]
}
},
2、数据标签
在增加值的series对象中,通过label的formatter函数,关联params索引,获取对象变化值。索引取值 在echarts中常用的技巧。
label: {
show: true,
position: 'insideTop',
formatter: function(params) {
return addList[params.dataIndex]
}
},
3、跨轴处理
echarts中默认的瀑布图示例仅支持y轴的正半轴,对负半轴处理无法兼容,需要分情况逐一处理。比如上一个值是正值,当前值为负值,应该先减去正值(对应增加值的series),然后再减去负值(对应减少值的series)。
总结:本文通过实际项目中的echarts瀑布图为例,逐一分析解决了官方示例中未覆盖的y负半轴问题,提到了echarts中常用的索引取值的使用技巧,并给出了demo源码,供大家享用。
日拱一卒,功不唐捐。