需求场景
用品牌持机量和品牌净流入量两个维度来帮助运营分析所选品牌的竞争能力健康度。
最终实现效果如下图所示:
- 竞争能力健康度:判断品牌具备正向竞争能力,还是处于危险状态。
- 维度呈现
横坐标:品牌持机量,中心点为取所有11个品牌持机量的中位数(注:一期为取“平均数”);
纵坐标:品牌净流入量=本月流入量-本月流出量,中心点为0,即向上为净流入量增加,向下为净流入量减少;
四个象限:分别表示“上升品牌”、“潜力品牌”、“高危品牌”、“下滑品牌”。
需求拆解(从实现的角度)
- 确定使用的基本图表类型及数据格式:以散点图为基础;每个散点的数据对应一个(品牌)名称和(坐标)值。
- 四象限划分:确定横、纵轴的分割临界点。
- 确定散点的样式、交互效果:每个散点大小一致;处于不同象限的散点使用指定不同颜色区分(此时注意考虑散点处于临界点的情况);散点上显示名称,hover时显示对应维度值信息。
- 使用图例展示对应象限表示的含义。
问题及解决方案
- 后续示例基于以下模拟数据做调整:
let data = [ { name: '三星', value: [10.0, -6.04], }, { name: '一加', value: [8.0, 1.95], }, { name: '华为', value: [12.0, 7.58], }, { name: '苹果', value: [15.0, -7.81], } ]
问题一:四象限的划分,用ECharts原生提供的xAxis、yAxis属性无法做到。
因为x轴、y轴的位置是不可按具体值灵活控制的。
例如y轴在左侧时,如果x轴上的最小值<=0,则y轴位于x=0上;否则按xAxis.min的设定规则(即坐标轴刻度最小值,不设置时会自动计算最小值保证坐标轴刻度的均匀分布)计算。
几种情况的效果示例如下图所示:
- 思路:利用其它属性来模拟轴?
- 方案:由于markLine属性的位置完全可控,可使用markLine模拟划分象限的轴。参考文档series-scatter.markLine.data中笛卡尔坐标系的相关配置说明。
- 相关代码片段:
const median = 11.0 // 实际值由后台计算给出,为固定值 /* 隐藏默认的x轴和y轴 */ xAxis: { show: false }, yAxis: { show: false }, /* 注:以下配置项位于series内 */ label: { show: true, formatter: (param) => param.name || '未命名' }, // 显示名称 symbolSize: 50, // 散点大小 type: 'scatter', data, markLine: { lineStyle: { normal: { color: '#555555', type: 'solid' } }, data: [ { xAxis: median, // 注释为一期实现 // type: 'average', // valueDim: 'x', label: { normal: { show: true, formatter: '品牌净流入量', }, } }, { yAxis: 0, label: { normal: { show: true, formatter: '品牌持机量', } } }, ] },
-
脑洞成果:
问题二:左侧视野出现的大片空白,在本图中无意义。
- 思路:将y轴偏移到所有数据的横轴最小值。
- 方案:利用问题一总结出来的规则即可解决。
- 相关代码片段:
xAxis: { min: 'dataMin' // 取数据在该轴上的最小值作为最小刻度 },
-
脑洞成果:
问题三:处于不同象限的散点使用指定不同颜色区分
- 思路:通过对每个点表示的坐标值分别与临界值比较,从而确定所在区间,设置相应的颜色值。
- 方案:与产品经理确认,处于临界值的点按偏“右、上”规则处理。
- 相关代码片段:
const xSeparate = median const ySeparate = 0 const getAreaPointColor = (value = [0, 0]) => { const [x, y] = value switch ([x, y]) { case (x >= xSeparate && y >= ySeparate): return '#3583FF' case (x < xSeparate && y >= ySeparate): return '#33BB7B' case (x < xSeparate && y < ySeparate): return '#FB7962' case (x >= xSeparate && y < ySeparate): return '#8560C5' } } data = data.map(d => ({ ...d, itemStyle: { color: getAreaPointColor(d.value) } }))
-
脑洞成果:
问题四:使用不同颜色图例展示对应不同象限的含义的实现方式及其限制。
首先,查看图例legend的配置规则:“图例组件展现了不同系列的标记(symbol),颜色和名字”,这说明了展示多个图例需要依赖于有多组series。
- 基于一期需求(横坐标以所选品牌持机量的平均数来分割),参考series-scatter.markLine.data.0.type可知,取平均数是基于设置了markLine的该组数据计算的,此时数据只能放在一组series中才有意义,否则横坐标的分割线会永远处于加了平均数配置的那组数据的平均值上(可看以下反例帮助理解),因此图例的展示就不能用ECharts提供的legend属性实现了。
反例1:series的每项都加上平均数配置时
- 反例2:只给其中一个子项加上平均数配置时
- 思路:在不需要图例的任何交互的情况下,可简单地通过将图例作为渲染的Canvas以外的dom元素来实现;否则需要考虑更复杂的实现(衡量实现成本)。
- 方案:直接用html+css实现。
- 基于第二版需求(横坐标以所选固定的11个品牌持机量的中位数来分割)
- 思路:由于markLine.xAxis取固定值,因此根据象限拆分多组series变得可行了。
- 方案:实现一个根据象限拆分多组series的函数,图例信息从series获取即可。
问题五:将散点整体缩小后,由于markLine层级过高,导致散点文字被遮挡。
- 思路:将散点元素的层级调高或调低markLine的层级。
- 方案:markLine层级暂无法调整,使用series-scatter.z将散点层级适当调高即可。
- 相关代码片段:
label: { show: true, fontSize: 8, formatter: (params) => params.name }, symbolSize: 28, z: 9, // 将散点层级调高至不被markLine遮挡
-
脑洞成果:
效果样例
可直接粘贴在https://echarts.apache.org/examples/zh/editor.html?c=line-simple
查看效果。
- 样例1:简版配置(省略legend、tooltip),可作为研究基础
const median = 11.0
option = {
title: {
text: '品牌健康度分析',
},
xAxis: {
min: 'dataMin',
show: false
},
yAxis: {
show: false
},
series: [{
label: {
show: true,
formatter: (param) => param.name || '未命名'
},
symbolSize: 50,
z: 9,
data: [
{
name: '三星',
value: [10.0, -6.04],
itemStyle: {
color: '#FB7962'
}
},
{
name: '一加',
value: [8.0, 1.95],
itemStyle: {
color: '#33BB7B'
}
},
{
name: '华为',
value: [12.0, 7.58],
itemStyle: {
color: '#3583FF'
}
},
{
name: '苹果',
value: [15.0, -7.81],
itemStyle: {
color: '#8560C5'
}
},
// [9.0, 8.81],
// [11.0, 8.33],
// [14.0, 9.96],
// [6.0, 7.24],
// [4.0, -4.26],
// [12.0, 10.84],
// [7.0, 4.82],
// [5.0, 5.68]
],
type: 'scatter',
markLine: {
lineStyle: {
normal: {
color: '#555555',
type: 'solid'
}
},
data: [
{
xAxis: median,
label: {
normal: {
show: true,
formatter: '品牌净流入量',
},
}
},
{
yAxis: 0,
label: {
normal: {
show: true,
formatter: '品牌持机量',
}
}
},
]
},
}]
}
- 样例2:最终版
// 思路已在本文提供,具体业务代码不公开