跳到主要内容

散点图配置化:子弹图

​ 综合参考子弹图的设计规范以及ECharts目前支持的图表类型和功能点,我们决定通过配置ECharts散点图的方式,实现子弹图。

与普通折柱图表不同,子弹图是经过当前已有的散点图魔改而来,修改的范围不再局限在series, 与其他图表组合时,会在公共组件(坐标轴、数据面板等)的配置项有较大冲突,因此子弹图目前不支持与其他任何图表组合,仅限单独展示。

一、参考文档

子弹图交互设计规范https://frontend.myhexin.com/design-resource/access?jira=UED-9468&type=UE
子弹图UI设计规范子弹图 交互设计规范 – Figma
Echarts散点图配置项https://echarts.apache.org/zh/option.html#series-scatter
子弹图在线Demohttps://t.zhouchangju.com/test/qianxiaodong/bullet/pc-simple.html
在线Demo的配置项处理源码https://t.zhouchangju.com/test/qianxiaodong/bullet/js/pc-simple.js

二、方案概述

拆分子弹图的设计规范可知,图表可划分为8个模块,并通过以下方案解决:

  • 坐标轴:目前范式组件的坐标轴即可支持
  • 若干个(图中为2个)不同颜色的标记区域:echarts原生的标记区域即可支持
  • 表示平均值的虚线标记:echarts原生的标记线即可支持
  • 表示目标点的菱形点:echarts原生的散点图标记即可支持
  • 目标点上方的倒三角定位:echarts原生的标记点即可支持
  • 数据面板:echarts原生的tooltip即可支持
  • 各部分的定位:通过两个连续轴组成的垂直坐标系即可完成以上几个部分的定位
  • 黑白主题色样式配置: 记录不同主题的颜色样式插入配置项即可

定位方案:y轴数据归一化 + getPercent

信息

从设计稿来看,子弹图各图形元素在竖直方向上的定位都是固定不变的,按理来说应当以单轴定位,配置上无需单独计算。但是从echarts配置项的角度出发,标记点、标记区域、标记线的坐标定位目前暂不支持单轴坐标系。因此子弹图实际上还是以笛卡尔坐标系(垂直坐标系)来定位图表各元素,不过在竖直方向上做了特殊处理。

​ 由上可知,子弹图在y轴上并不需要展示数据,因此我们把y轴的范围固定为[-1, 1],这样使得x轴上下对称并且两部分在逻辑上的范围的绝对值都是[0, 1.00],对应的,在像素上的范围绝对值都是[0, grid.height / 2],其中grid.height是配置项option.grid.height的值。如此,前面提到的元素定位就可以将设计稿上的具体像素转换成百分比来完成定位了。

getPercent

​ 按照上面的思路,可以得到以下归一化转换方法,完成定位:

const getPercent = (targetVal, sumVal) => {
return parseFloat(targetVal) / parseFloat(sumVal);
}

三、配置项

option.grid

注意

因为后面在给一部分元定位时需要用到画布的高度grid.height,所以这里的高度并不是设计稿中标记区域的高度,所以grid.height需要比标记区域的高度设置的大一点。这里需要注意一下​~🙏

子弹图的画布配置,配置包括多个子弹图间的布局(上下左右间距),和单个子弹图的宽高。

grid: {
// 画布的高度,后面会根据这个值计算百分比,定位图表内一部分元素
height: 48,
width: 480,
left: 0,
right: 0,
top: 100
}

option.xAxis

信息

通常情况下,x轴都是以类目轴的形式计算绘制,但是子弹图为了定位元素,采用了双数值轴的形式定义坐标系。

x轴的配置,子弹图的x轴默认为只显示轴坐标标签,在设计规范中,坐标标签根据主题不同需要表现为不同的颜色。

xAxis: {
// x轴依赖的画布的索引
gridIndex: 0,
// x轴类型,这里定义为数值轴
type: 'value',
// x轴分割线: 不显示
splitLine: {
show: false
},
// x轴轴线:不显示
axisLine: {
show: false
},
// x轴刻度:不显示
axisTick: {
show: false
},
// x轴坐标标签
axisLabel: {
// x轴标签距离轴线的间距
margin: -10,
// 坐标标签的文字颜色
color: '#000',
// 轴两端的标签分别左、右对齐
dvAlignEdge: true
}
}

option.yAxis

信息

子弹图的y轴的数值范围是固定且对称的,因为子弹图的y轴并不展示数据,只为方便定位元素。

y轴的配置,子弹图的y轴范围固定为[-1, 1]。并且不在图表显示。

yAxis: {
// y轴依赖的画布的索引
gridIndex: 0,
// y轴类型,这里定义为数值轴
type: 'value',
// 子弹图的y轴不展示数据
show: false,
// y轴最大值:固定为1
min: -1,
// y轴最小值:固定为-1
max: 1,
}

option.tooltip

信息

设计范式中,子弹图的tooltip样式不随主题变化而变化,因此在线Demo中把配置项置为固定值

数据面板tooltip的全局配置

tooltip: {
// 数据面板的定位,在鼠标右侧
position: (mousePoint, params, dom, rect, size) => {
return [mousePoint[0], mousePoint[1] - size.contentSize[1] / 2];
},
// 渲染模式,默认为富文本渲染, 为支持后续组件化模块化和低代码
renderMode: 'richText',
// 背景色,见设计规范
backgroundColor: '#242424',
// 边框色,见设计规范
borderColor: '#242424',
// 边框线宽,见设计规范
borderWidth: 0,
// 文字样式。见设计规范
textStyle: { color: 'rgba(255, 255, 255, 0.84)' },
// 面板内边距,见设计规范
padding: [8, 12, 8, 12],
// 其余样式
extraCssText: 'border-redius: 2px; box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.2); border-color: #242424;'
};

option.series

子弹图的主体部分,通过散点图配置修改得来。主要分为散点、标记区域、标记点、平均参考线四部分,下面也分四部分解析。整体结构如下:

series: {
type: 'scatter',
// 散点配置
xxx,
markPoint: {
// 标记点(倒三角)的配置
xxx,
},
markLine: {
// 平均参考线的配置
xxx,
},
markArea: {
// 标记区域的配置
xxx
}
}

series

信息

子弹图里的刻度线。存在两种数据,一种是用于对照的数据,一种是该子弹图的目标数据值。对照数据用短细线表示,目标数据用菱形表示。两种数据对应的刻度有不同的样式。

:heavy_exclamation_mark:这部分配置也是option.series的一部分,为了缩短配置说明的篇幅,所以把这部分拆解出来,详情见option.series:heavy_exclamation_mark:

子弹图里的刻度线。

series: {
// 系列类型: 散点图
type: 'scatter',
// 系列依赖的xy轴的索引
xAxisIndex: 0,
yAxisIndex: 0,
// 系列的数据面板
tooltip: {
// 面板内容的格式函数
formatter: (params) => {return `${params.name}: ${params.value[0]}`}
},
// 标记的旋转角度:因为短细线标记默认是水平的,所以需要旋转90度
symbolRotate: 90,
// 系列的数据, 可传入若干个数据,被分为目标数据和对照数据, 两者在样式上有一定区别
data: [
// 目标数据
{
// x,y坐标值,其中y坐标只表示定位, 一般为固定值0:y轴范围是[-1, 1],所以0表示竖直居中
// x则是数据值
value: [4.5, 0],
// 该数据的名称,用于在tooltip中显示
name: '同花顺',
// 数据对应的标记类型,目标标记对应的是菱形 'diamond'
symbol: 'diamond',
// 数据对应的标记大小, 在大号子弹图中,该标记大小为12
symbolSize: 12,
// 数据标记的样式
itemStyle: {
// 标记填充色, 和主题相关
color: '#000',
// 标记透明度,目标数据透明度为1
opacity: 1,
// 标记边框宽度 1
borderWidth: 1,
// 标记边框颜色
borderColor: '#000'
},
// 高亮时的样式
emphasis: {
itemStyle: {
// 透明度
opacity: 1,
// 填充色
color: '#ffc430',
// 边框线颜色
borderColor: '#000'
}
}
},
// 对照数据
{
// 同目标数据
value: [4.5, 0],
// 同目标数据
name: '同花顺',
// 同目标数据 但对照数据的标记类型为短细线 'line'
symbol: 'line',
// 同目标数据 在大号子弹图中,对照数据的标记大小为8
symbolSize: 8,
// 以下样式同目标数据 主题样式在附录中列出
itemStyle: {
color: '#000',
opacity: 1,
borderWidth: 1,
borderColor: '#eee'
},
emphasis: {
itemStyle: {
opacity: 1,
color: 'red',
borderColor: 'red'
}
}
},
]
}

series.markPoint

信息

子弹图中目标数据点上方的倒三角标记点,通过series.markPoint实现。

:heavy_exclamation_mark:这部分配置也是option.series的一部分,为了缩短配置说明的篇幅,所以把这部分拆解出来,详情见option.series:heavy_exclamation_mark:

提示

配置示例中的getPercent()方法可见getPercent()

子弹图里的倒三角标记点,配置文档见series.markPoint

markPoint: {
// 标记点的图形类别:三角形
symbol: 'triangle',
// 标记大小:设计规范中指定为宽6高4
symbolSize: [6, 4],
// 标记旋转角度:三角标记的默认朝向是向上的, 倒三角需要旋转180度
symbolRotate: 180,
// 标记点的样式
itemStyle: {color: ''},
// 高亮时的样式
emphasis: {
// 禁用高亮时放大标记
scale: false,
// 高亮样式
itemStyle: {color: ''}
},
// 标记点数据,可传多个,但子弹图中只有一个标记点
data: [
// 标记点的坐标,getPercent将标记点的位置pointHeight转换为y轴的值,用于定位
{coord: [0, getPercent(pointHeight, height / 2)]},
...
]
}

series.markLine

信息

子弹图中的平均线,通过series.markLine实现。子弹图目前支持任意数值的参考线,设计规范中只有平均线

:heavy_exclamation_mark:这部分配置也是option.series的一部分,为了缩短配置说明的篇幅,所以把这部分拆解出来,详情见option.series:heavy_exclamation_mark:

提示

配置示例中的getPercent()方法可见getPercent()

子弹图里的参考标记线,配置文档见series.markLine

markLine: {
// 标记线两端点的标记类型:设计规范中两端没有标记 'none'
symbol: 'none',
//标记线上的文字:不显示
label: { show: false },
// 标记线的样式: 设计规范中线条为2,2的虚线,颜色和主题相关
lineStyle: { type: [2, 2], color: '' },
// 高亮时的样式
emphasis: {
// 高亮样式: 设计规范中参考线高亮时为实线,宽度为1
lineStyle: {type: 'solid', width: 1}
},
// 标记线数据:可传多个,但子弹图中只有一条标记线
data: [
// 标记线的坐标,由线段的两个端点坐标确定。getPercent将标记线两个端点的位置lineHeight转换为y轴的值,用于定位
[
// 上端点
{yAxis: getPercent(lineHeight, height / 2), xAxis: 'average'},
// 下端点
{yAxis: -getPercent(lineHeight, height / 2), xAxis: 'average'}
],
...
]
}

series.markArea

信息

子弹图中的标记区域,通过series.markArea实现。子弹图目前支持任意个数的标记区域,但设计规范中只有以目标数据为分界点的两个标记区域。

:heavy_exclamation_mark:这部分配置也是option.series的一部分,为了缩短配置说明的篇幅,所以把这部分拆解出来,详情见option.series:heavy_exclamation_mark:

提示

配置示例中的getPercent()方法可见getPercent()

子弹图里的标记区域,配置文档见series.markArea

markArea: {
//标记线上的文字:不显示
label: { show: false },
// 标记标记区域的数据:可传多个,但设计规范目前只要求以目标数据为分界的两个标记区域
data: [
// 一个标记区域的数据结构,由标记区域对角的两个端点。getPercent将两个端点的位置areaHeight转换为y轴的值,用于定位
// 第一个标记区域:表示区间[0, seriesData[123].value[0]]的范围
[
// 端点一: 一个标记区域中,两个端点有一个带样式就能生效
{
// 坐标:y坐标定义标记区域的上边界,不表示数据;
coord: [0, -getPercent(areaHeight, height / 2)],
// 标记区域的名字:在tooltip中会显示
name: '目标数据之前',
// 标记区域的值:一般用于展示标记区域内对照标记的数量,在tooltip中会显示
value: 123,
// 标记区域的样式
itemStyle: { color: '' },
// 高亮时的样式
emphasis: {
itemStyle: { color: '' }
},
}
// 端点二
// coord[0]可以直接填数据,当然也可以把前面`series.data`中的数据存成变量,如seriesData,然后用以下方式填数据
{coord: [seriesData[123].value[0], getPercent(areaHeight, height / 2)],}
]
]
}

四、主题样式查询表

信息

下面是子弹图设计规范中各主题下图表元素的颜色配置值。目前共三种主题白色、灰色(国际版)、黑色。

const themeColorsMap = {
light: {
// 坐标轴标签颜色
axisLabel: '#646464',
// 倒三角标记点颜色
markPoint: '#222',
// 倒三角标记点高亮颜色
markPoint_emphasis: '#ffc430',
// 平均线颜色
markLine: '#000',
// 对照数据标记颜色
otherSymbol: '#000',
// 对照数据标记高亮颜色
otherSymbol_emphasis: '#000',
// 目标数据标记颜色
targetSymbol: '#000',
// 目标数据标记高亮颜色
targetSymbol_emphasis: '#ffc430',
// 目标、对照数据边框颜色
symbolBorderColor: '#000',
// 目标数据左侧标记区域填充色
markArea_1: '#6aa1fa',
// 目标数据右侧标记区域填充色
markArea_2: '#dcdcdc',
// 目标数据左侧标记区域高亮填充色
markArea_1_emphasis: '#9bc2fc',
// 目标数据右侧标记区域高亮填充色
markArea_2_emphasis: '#f2f2f2'
},
grey: {
axisLabel: '#9e9e9e',
markPoint: '#dcdcdc',
markPoint_emphasis: '#ffc430',
markLine: '#fff',
otherSymbol: '#fff',
otherSymbol_emphasis: '#ffd466',
targetSymbol: '#fff',
targetSymbol_emphasis: '#ffc430',
symbolBorderColor: '#000',
markArea_1: '#005bf7',
markArea_2: '#323232',
markArea_1_emphasis: '#3580f9',
markArea_2_emphasis: '#484848'
},
dark: {
axisLabel: '#9e9e9e',
markPoint: '#dcdcdc',
markPoint_emphasis: '#ffc430',
markLine: '#fff',
otherSymbol: '#fff',
otherSymbol_emphasis: '#ffd466',
targetSymbol: '#fff',
targetSymbol_emphasis: '#ffc430',
symbolBorderColor: '#000',
markArea_1: '#005bf7',
markArea_2: '#222',
markArea_1_emphasis: '#3580f9',
markArea_2_emphasis: '#323232'
}
};

五、子弹图大小规格的数据参照表

信息

在设计规范中,子弹图有大、中、小三种规格的设计。不同大小的图表对应图表元素的UI数据不同,以下是三种图表的相关数据

const sizeMap = {
small: {
// 画布高度:用于元素定位
gridHeight: 48,
// 倒三角标记点到y轴0点的距离
pointHeight: 14,
// 平均参考线两个端点到y轴0点的距离
lineHeight: 7,
// 标记区域上下边界到y轴0点的距离
areaHeight: 4,
// 数据标记的大小
symbolSize: {
// 目标菱形的大小
targetSymbol: 6,
// 对照竖线的大小
otherSymbol: 4,
}
},
middle: {
gridHeight: 48,
pointHeight: 16,
lineHeight: 9,
areaHeight: 6,
symbolSize: {
targetSymbol: 8,
otherSymbol: 6,
}
},
large: {
gridHeight: 48,
pointHeight: 20,
lineHeight: 11,
areaHeight: 8,
symbolSize: {
targetSymbol: 12,
otherSymbol: 8,
}
}
}