如果需要复杂的图表样式,echarts
自带的 series
类型无法满足,那么就可以使用 custom
类型,这时就需要通过 canvas
手动绘制图表的形状了。
知识准备
坐标
假如 echarts
的容器拥有 800*800
尺寸的大小。
那么,左上角是 (0,0 )
,右上角是 (800, 0)
,左下角是 (0, 800)
,右下角是 (800, 800)
。
value() 函数
仅考虑简单图表。
value()
函数官方说法是根据维度的名称或索引获取对应的数据值。
对于本示例来讲,就是用它获取 x
轴的数据值以及 y
轴的数据值。
value(0)
用于获取 x
轴数据。
value(1)
用于获取 y
轴数据。
获取 X 轴数据值
在本示例中,x
轴类型是 category
,所以 value(0)
返回柱子的索引值。
上面一共有三个个柱子,每个柱子都有自己的索引值,从左至右,从 0
开始。
获取 Y 轴数据值
获取 y
轴数据要简单许多,value(1)
会返回每个柱子的数据值,如:100
, 50
, 20
。
coord() 函数
仅考虑简单图表。
类型:coord([x, y])
。
x
和 y
分别是 x
轴的数据值以及 y
轴的数据值。可以通过 value()
来获取。
coord()
函数的作用是根据 x
和 y
轴的数据值计算出坐标点。
renderItem
当 series
的类型为 custom
时,通过 renderItem
函数来绘制每个柱子的样子。伪代码:
const options = { series: [ { type: "custom", data: [100, 50, 20], renderItem(params, api) { // ... } } ] }
复制
params
和 api
参考官网:Echarts renderItem
value()
和 coord()
函数就在 api
对象下:api.value()
、api.coord()
。
在这个 series
下,data
有 3
个数据,那么 renderItem
就会执行 3
次。
- 第一次的数据值是
100
。 - 第二次的数据值是
50
。 - 第三次的数据值是
20
。
对于 data
中的每个数据项,都会调用一次 renderItem
。
函数格式如下:
function renderItem(params, api) { // ... return { // 我只用过 group type: "group", // 形状列表 children: [ { // 已注册的形状名称 type: "shapeName", // 形状参数,会被传递给 extendShape 函数 shape: {}, // 形状的样式,可以通过 api.style() 函数来获取基础样式 style: api.style(), // 也可以覆盖某个样式属性,例如单独为每个形状设置不通的背景 style: { ...api.style(), fill: "red", // 覆盖基础样式中的 fill } } ] } }
复制
扩展形状
在使用 renderItem
前,必须先使用 echarts.graphic.extendShape
函数来定义形状的样子。
const shapeName = echarts.graphic.extendShape({ buildPath(ctx, shape) { // ... } })
复制
ctx
可以获取到 canvas
的上下文,使用 canvas
来绘制形状。
shape
是使用时传递的 shape
对象数据。参考[renderItem](# 自定义类型)的返回值。
注册形状
要想在 renderItem
中使用形状,需要先扩展形状,最后注册形状。
echarts.graphic.registerShape('shapeName', shapeName);
复制
第一个参数是形状的名称,在使用时直接指定的这个名称即可。
第二个参数是扩展形状的变量名称,例如[扩展形状](# 扩展形状)中的变量名 shapeName
。
基础柱状图
我们会将下面的柱状图改造成立体柱状图。
const options = { title: { text: "不同级别的忍者数量", textStyle: { color: "#fff", }, }, backgroundColor: "#000", xAxis: { type: "category", data: ["下忍", "中忍", "上忍"], axisLabel: { color: "#fff", }, }, yAxis: { type: "value", max: 200, axisLabel: { color: "#fff", }, splitLine: { lineStyle: { color: "#222", }, }, }, tooltip: { trigger: "axis", }, series: [ { type: "bar", data: [100, 50, 20], barWidth: 30, }, ], };
复制
立体柱状图解析
在立体柱状图中,一共有三面:左侧面、右侧面、顶面
每一面都对应一个形状,一共需要创建三个形状。
顶部基础 y
轴是顶面每个点坐标的基础计算值。
基础 x
轴是左侧面和右侧面每个点坐标的基础计算值。
斜角高度:从中心点到两个侧面顶角的高度。
中心点:是 coord([value(index, value)])
计算后的坐标点。
分解
这些点就是我们要在 renderItem
中绘制的。
代码实现
修改 series
,并创建 renderItem
函数:
function renderItem(params, api) {} const options = { series: [ { type: "custom", data: [100, 50, 20], renderItem, }, ], }
复制
然后先创建三个形状的空壳子,并注册:
const leftShape = echarts.graphic.extendShape({ buildPath(ctx, shape) {}, }); const rightShape = echarts.graphic.extendShape({ buildPath(ctx, shape) {}, }); const topShape = echarts.graphic.extendShape({ buildPath(ctx, shape) {}, }); echarts.graphic.registerShape("leftShape", leftShape); echarts.graphic.registerShape("rightShape", rightShape); echarts.graphic.registerShape("topShape", topShape);
复制
完善 renderItem
函数,获取基础坐标并将坐标传递给自定义形状中:
function renderItem(params, api) { // 基础坐标 const basicsCoord = api.coord([api.value(0), api.value(1)]); // 顶部基础 y 轴 const topBasicsYAxis = basicsCoord[1]; // 基础 x 轴 const basicsXAxis = basicsCoord[0]; // 底部 y 轴 const bottomYAxis = api.coord([api.value(0), 0])[1]; return { type: "group", children: [ { type: "leftShape", shape: { topBasicsYAxis, basicsXAxis, bottomYAxis, }, style: api.style(), }, { type: "rightShape", shape: { topBasicsYAxis, basicsXAxis, bottomYAxis, }, style: api.style(), }, { type: "topShape", shape: { topBasicsYAxis, basicsXAxis, bottomYAxis, }, style: api.style(), }, ], }; }
复制
完善 leftShape
形状逻辑:
const leftShape = echarts.graphic.extendShape({ buildPath(ctx, shape) { const { topBasicsYAxis, bottomYAxis, basicsXAxis } = shape; // 侧面宽度 const WIDTH = 15; // 斜角高度 const OBLIQUE_ANGLE_HEIGHT = 3.6; const p1 = [basicsXAxis - WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT]; const p2 = [basicsXAxis - WIDTH, bottomYAxis]; const p3 = [basicsXAxis, bottomYAxis]; const p4 = [basicsXAxis, topBasicsYAxis]; ctx.moveTo(p1[0], p1[1]); ctx.lineTo(p2[0], p2[1]); ctx.lineTo(p3[0], p3[1]); ctx.lineTo(p4[0], p4[1]); }, });
复制
完善 rightShape
形状逻辑:
const rightShape = echarts.graphic.extendShape({ buildPath(ctx, shape) { const { topBasicsYAxis, bottomYAxis, basicsXAxis } = shape; // 侧面宽度 const WIDTH = 15; // 斜角高度 const OBLIQUE_ANGLE_HEIGHT = 3.6; const p1 = [basicsXAxis, topBasicsYAxis]; const p2 = [basicsXAxis, bottomYAxis]; const p3 = [basicsXAxis + WIDTH, bottomYAxis]; const p4 = [basicsXAxis + WIDTH, topBasicsYAxis + OBLIQUE_ANGLE_HEIGHT]; ctx.moveTo(p1[0], p1[1]); ctx.lineTo(p2[0], p2[1]); ctx.lineTo(p3[0], p3[1]); ctx.lineTo(p4[0], p4[1]); }, });
复制
完善 topShape
形状逻辑:
const topShape = echarts.graphic.extendShape({ buildPath(ctx, shape) { const { topBasicsYAxis, basicsXAxis } = shape; // 侧面宽度 const WIDTH = 15; // 斜角高度 const OBLIQUE_ANGLE_HEIGHT = 3.6; const p1 = [basicsXAxis, topBasicsYAxis]; const p2 = [basicsXAxis + WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT]; const p3 = [basicsXAxis, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT * 2]; const p4 = [basicsXAxis - WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT]; ctx.moveTo(p1[0], p1[1]); ctx.lineTo(p2[0], p2[1]); ctx.lineTo(p3[0], p3[1]); ctx.lineTo(p4[0], p4[1]); }, });
复制
最后为每个面都设置不同的颜色装饰一下:
function renderItem(params, api) { // 柱子索引值 const { seriesIndex } = params; // 基础坐标 const basicsCoord = api.coord([api.value(seriesIndex), api.value(1)]); // 顶部基础 y 轴 const topBasicsYAxis = basicsCoord[1]; // 基础 x 轴 const basicsXAxis = basicsCoord[0]; // 底部 y 轴 const bottomYAxis = api.coord([api.value(seriesIndex), 0])[1]; return { type: "group", children: [ { type: "leftShape", shape: { topBasicsYAxis, basicsXAxis, bottomYAxis, }, style: { ...api.style(), fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: "rgba(34, 7, 94, 1)" }, { offset: 1, color: "rgba(0, 0, 0, 0.26)" }, ]), // 覆盖基础样式 }, }, { type: "rightShape", shape: { topBasicsYAxis, basicsXAxis, bottomYAxis, }, style: { ...api.style(), fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: "rgba(57, 16, 133, 1)" }, { offset: 1, color: "rgba(0, 0, 0, 0.26)" }, ]), // 覆盖基础样式 }, }, { type: "topShape", shape: { topBasicsYAxis, basicsXAxis, bottomYAxis, }, style: { ...api.style(), fill: "#722ed1", // 覆盖基础样式 }, }, ], }; }
复制
效果图
参考
- 通过Echarts怎样实现立体柱状图