ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ECharts柱状图与legend颜色自定义:从原理到实战的完整指南

ECharts柱状图与legend颜色自定义:从原理到实战的完整指南 做可视化大屏的人多多少少都被 echarts 自定义柱状图和 legend 颜色这件事折磨过。图例颜色和柱子对不上、默认方块太丑、想换成圆点或者箭头又不知道去哪改这些问题几乎每个项目都会遇到。这篇文章就把柱状图这块从颜色到图例形状的定制逻辑一次说清楚。内容以 echarts 柱状图为主线把 legend 颜色来源、颜色联动关系、形状自定义的几种方案串起来适合正在做数据可视化大屏、图表组件封装或者单纯被 legend 默认样式逼疯的前端同学参考。1. 柱状图颜色控制从这三层配置说起1.1 全局色板color 数组怎么用才不翻车先看最基础的问题柱子颜色到底由谁决定。ECharts 里有一个全局color配置项它是一个颜色数组。当你没有给任何 series 单独指定颜色时ECharts 会按照 series 的声明顺序依次从color数组里取色。如果 series 数量超过了颜色数组的长度就从头开始循环取。option { color: [#3A7BFD, #00E4FF, #FFB938], series: [ { type: bar, data: [120, 200, 150] }, { type: bar, data: [80, 160, 220] } ] };这段配置里第一个柱状图系列拿到#3A7BFD第二个系列拿到#00E4FF。全局color的好处是省事适合系列多、颜色只需要一套固定品牌色的场景。但它的弊端也很明显一旦需要单独调整某个系列的颜色你就得去 series 里覆盖一旦系列顺序调整颜色就可能全部错位。所以我在实际项目里更推荐显式配置也就是下面要说的 series 级别和 data 级别。还有一种常见用法是直接修改color数组本身来控制所有图表的默认配色比如echarts.registerTheme(myTheme, { color: [#3A7BFD, #00E4FF, #FF6B6B, #7B61FF] });初始化图表时用echarts.init(dom, myTheme)这样整个项目的柱子、折线、饼图都会遵循同一套配色。对于大屏项目来说把主题色收敛到一处后续改需求能省非常多时间。1.2 按数据项着色阈值高亮与渐变柱子实际业务里柱状图很少是清一色同一个颜色的。最常见的需求有两个一是让某根柱子超过阈值后变成警示色二是让所有柱子走渐变。先说按数据项着色。ECharts 的数据项data里可以单独配itemStyle优先级高于 series 级别的itemStyleoption { series: [{ type: bar, data: [ { value: 120, itemStyle: { color: #3A7BFD } }, { value: 200, itemStyle: { color: #3A7BFD } }, { value: 180, itemStyle: { color: #F56C6C } } ] }] };如果原始数据来自接口可以用map批量处理const rawData [120, 200, 180, 90, 260]; const threshold 150; const data rawData.map(v ({ value: v, itemStyle: v threshold ? { color: #F56C6C } : { color: #3A7BFD } }));这里要注意一个很容易误导新手的点这种数据项级别的颜色只会改柱子的颜色不会影响 legend 图例项的颜色。legend 取的是 series 整体颜色不是某根柱子的颜色。这个联动关系我在第二章详细展开。再聊聊渐变。ECharts 5 里给柱子加渐变有两种写法。一种是传统的echarts.graphic.LinearGradientitemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: #3A7BFD }, { offset: 1, color: rgba(58, 123, 253, 0.2) } ]) }四个参数分别是渐变起点 x、起点 y、终点 x、终点 y取值范围是 0 到 1。(0, 0, 0, 1)表示从上到下的垂直渐变(0, 0, 1, 0)表示从左到右(0, 0, 1, 1)是从左上角到右下角的对角渐变。另一种是 ECharts 5 推荐的纯对象写法不依赖echarts.graphic更利于配置序列化itemStyle: { color: { type: linear, x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: #3A7BFD }, { offset: 1, color: rgba(58, 123, 253, 0.2) } ] } }两种效果一样看团队习惯。我自己的经验是如果项目里有用 JSON 动态生成 option 的场景推荐用对象写法如果是写死在代码里的配置两种都可以。1.3 渐变柱子的连带反应legend 图例的视觉问题柱子走了渐变之后一个隐藏问题就浮出来了legend 的图例色块也会变成渐变色。因为 legend 的图标颜色默认就是从 series 的itemStyle.color取的你给的是LinearGradient对象图例渲染的时候就把这个渐变对象应用到了一个很小的图标区域里。柱子大渐变看得清图例图标那么小渐变基本上就是糊成一团视觉体验很糟糕。解决办法不是去改 legend 的什么隐藏属性而是换思路让 legend 用 formatter 富文本自己画颜色或者干脆不用原生 legend自己写 HTML 图例。这两个方案我会在第三章和第四章分别讲这里先留个印象。2. legend 颜色到底跟谁走怎么让它听话2.1 legend 没有独立 color 属性它取的是 series 颜色这是整个问题里的核心认知值得单独拿出来说ECharts 的 legend 没有独立的 color 配置。很多人第一反应是去 legend 配置里找一个类似itemStyle.color的字段但翻遍官方文档你会发现legend.data 里的每个图例项只能配置name、icon、textStyle、tooltip没有 color。图例的色块颜色是渲染时从对应 series 的数据样式里取出来的。具体来说柱状图的图例颜色来自该 series 的itemStyle.color折线图的图例颜色来自该 series 的lineStyle.color如果 series 自己没设置就取全局color数组分配到的颜色。所以想让图例变成某个颜色最直接的手段是修改对应 series 的颜色。这是合法的、也是官方支持的做法。但这引发了一个现实问题我只想改图例颜色不想动柱子颜色怎么办比如柱子要走渐变图例想显示一个干净的纯色圆点。这种需求用原生 legend 就有点拧巴了需要借助 formatter 来“伪装”。2.2 三招控制 legend 颜色第一招最省事直接改 series 颜色让 legend 跟着变。适合柱子颜色和 legend 颜色一致的情况比如统一用品牌色。第二招用 formatter 富文本覆盖图例的视觉呈现。原理是先让 legend 不渲染默认图标然后在 formatter 回调里返回一段包含富文本占位符的字符串利用富文本的 rich 样式画一个自定义色块或者带颜色的符号。legend: { icon: none, formatter: (name) { const colorMap { 实际销量: #3A7BFD, 目标销量: #FFB938 }; return {dot|●} {txt|${name}}; }, textStyle: { rich: { dot: { color: #3A7BFD, fontSize: 14, padding: [0, 6, 0, 0] }, txt: { color: #D3E1FF, fontSize: 13 } } } }这段代码里有几个关键点。icon: none让默认小方块不绘制否则默认图标和 formatter 里的符号会叠加出现。富文本占位符{dot|●}的含义是以 rich 中名为dot的样式渲染●这个字符。●是一个 Unicode 实心圆你还可以换成■、▲、◆等符号形状也就跟着变了。需要注意不同图例项需要不同颜色时富文本样式名不能用同一个。上面这个写法里所有图例项都用dot这个样式颜色写死成同一个两个系列会一样。解决办法是让 formatter 返回不同的占位符名formatter: (name) { const idx seriesConfig.findIndex(s s.name name); return {dot${idx}|●} {txt|${name}}; }, textStyle: { rich: { dot0: { color: #3A7BFD, fontSize: 14, padding: [0, 6, 0, 0] }, dot1: { color: #FFB938, fontSize: 14, padding: [0, 6, 0, 0] }, txt: { color: #D3E1FF, fontSize: 13 } } }这个方法可以把图例完全做成你想要的样子而且点击图例控制系列显隐的交互依然有效。第三招放弃原生 legend用自定义 HTML 图例。这个方案自由度最高我把完整实现放到第四章专门讲。2.3 多系列柱状图的图例匹配规则当一个图表里同时有多个柱状图系列时legend 的匹配逻辑是按name来关联的。ECharts 默认会自动生成图例项但如果手动配置了legend.data就必须保证每一项的name和对应 series 的name完全一致包括空格和大小写。series: [ { name: 实际销量, type: bar, data: [...] }, { name: 目标销量, type: bar, data: [...] } ] legend: { data: [ { name: 实际销量, icon: circle }, { name: 目标销量, icon: circle } ] }这里有个很容易踩的坑如果legend.data里只写了一个项另一个系列虽然存在图例也不会显示如果写了不存在的 name图例会多出一个小项点击时候事件也找不到对应 series交互就出 bug 了。还有一个实际经验多系列柱子经常需要“图例顺序和柱子显示顺序一致”但图例顺序并不决定 series 的绘制层级。绘制层级是由 series 数组顺序决定的后者声明的系列会盖在前面系列的上方。如果你发现某个系列的柱子被挡住了去调整 series 数组顺序而不是调 legend.data 顺序。如果你希望图例纯展示、不让用户点击隐藏系列可以把selectedMode设为falselegend: { selectedMode: false }这样图例不能交互系列始终全部显示适合大屏展示这类不允许用户随意操作的场景。3. 自定义 legend 形状的四种方案3.1 内置 icon 形状够用了吗ECharts 的 legend 图标支持几种内置形状circle圆、rect矩形、roundRect圆角矩形、triangle三角、diamond菱形、pin水滴/图钉、arrow箭头、none不显示。icon 值形状适用场景circle圆形大屏圆点图例最常用rect矩形默认样式柱状图标配roundRect圆角矩形想要柔和一点的柱状图形状triangle三角形特殊装饰风格diamond菱形折线图或雷达图风格pin水滴/图钉地图类图例常用arrow箭头方向性数据none不渲染图标配合 formatter 使用全局统一设置legend: { icon: circle, itemWidth: 10, itemHeight: 10 }itemWidth和itemHeight控制图例图标的尺寸注意不同形状在不同尺寸下视觉表现差别很大。比如圆点 10px 看着正好矩形如果设成 10x10 会显得有点小我一般给矩形设 14x8更协调。也可以针对每个图例单独指定形状用legend.data数组legend: { data: [ { name: 实际销量, icon: circle }, { name: 目标销量, icon: diamond } ] }这种“不同系列不同形状”的做法在混合图表里很常见比如柱状图配折线图时柱状图用方块、折线图用圆点视觉上就能直接区分。3.2 path 路径想要什么图标都能画内置形状满足不了需求时ECharts 支持用 SVG path 自定义图标。先看一个最简单的自定义legend: { data: [{ name: 实际销量, icon: path://M0,0 L1024,0 L1024,1024 L0,1024 Z }] }path://后面跟的是 SVG 路径字符串坐标系范围一般建议控制在 0 到 1024 之间。上面这个路径画的是从原点出发到 (1024,0)再到 (1024,1024)再到 (0,1024)最后闭合实际上就是一个正方形。如果是从设计师那边拿到的 SVG 图标打开 SVG 源码把path标签的d属性值复制出来拼上path://前缀就行。比如一个简单的闪电图标icon: path://M 512 1024 C 256 640 160 512 160 352 C 160 157.44 317.44 0 512 0 C 706.56 0 864 157.44 864 352 C 864 512 768 640 512 1024 Z这段路径画出来的其实是一滴水的形状常用于地图上的位置标记。使用 path 自定义图例有几点要注意。第一路径的边界不一定是 0-1024如果图形显示偏移或者被裁剪多半是路径坐标范围不对需要用矢量软件把图标整体挪到合适位置。第二icon的大小仍然受itemWidth和itemHeight控制但 path 内部的宽高比不会自动缩放为正方形需要自己在路径数据里控制好。第三path 图标的填充色依然来自 series 的颜色配置所以它表现出来的是“形状自定义 颜色跟随系列”的效果。3.3 图片和 base64最省事的兜底方案不想折腾 path 的话image://方案最直接。legend: { data: [{ name: 实际销量, icon: image://https://example.com/icon.png }] }也可以直接把图片转成 base64 字符串塞进去icon: image://data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...这里有个容易出问题的细节image://后面的 URL 如果有特殊字符比如某些 CDN 地址带了签名参数可能需要做 URL 编码否则加载失败。而且开发环境和生产环境的图片路径往往不一致建议封装一个公共方法统一拼接图片前缀。图片方案的坑主要在两个方面。一是图片尺寸和itemWidth、itemHeight的配合图片不会被裁剪但会等比缩放如果源图标比例和 item 尺寸比例差别太大图例看起来会有留白或者被压缩的感觉。二是跨域图片在部分浏览器下可能无法绘制到 canvas 上导致图例图标空白这种情况一般切换成 base64 或者用本地图片资源能解决。3.4 formatter 富文本用文字符号伪装图例形状这是我在大屏项目里用得最多的方案因为它不用切图、不用调 path 坐标灵活性最好。核心思路是利用legend.formatter返回富文本字符串在字符串里用{样式名|内容}的语法插入一个任意字符当作图形。legend: { icon: none, formatter: (name) { return {colorBlock| } {txt| name }; }, textStyle: { rich: { colorBlock: { backgroundColor: #3A7BFD, width: 12, height: 12, borderRadius: 6, padding: [0, 0, 0, 0] }, txt: { color: #D3E1FF, fontSize: 13, padding: [0, 0, 0, 6] } } } }这段配置和前面用●字符不一样的地方在于colorBlock里没有放任何字符而是通过富文本的backgroundColor配合width、height画了一个矩形块再用borderRadius: 6把它变成圆角矩形甚至圆形。这样一来图例的颜色完全由富文本的backgroundColor控制和 series 的颜色没有任何关系了。柱子的渐变、阈值变色都不会再影响图例。而且每个图例项可以单独声明不同的 rich 样式名实现“第一个圆点蓝色第二个圆点黄色”这种需求。这个方法唯一的限制是富文本的样式是静态写在textStyle.rich里的不能像 HTML 那样直接写行内样式。但实际项目中系列数量有限为每个系列预定义几个样式名完全够用。4. 进阶玩法柱状图叠加折线图时的 legend 统一4.1 混合图表图例形态乱怎么办做数据大屏时柱状图叠加折线图是高频需求。这种图表里柱子系列的默认图例是矩形折线系列的默认图例是线条加小标记两种形态混在一起视觉风格很不统一。推荐的做法是把所有图例形状统一成同一种。比如都改成圆点legend: { icon: circle, itemWidth: 10, itemHeight: 10 }注意折线图系列的图例颜色取的是lineStyle.color不是itemStyle.color。如果你设置了柱子颜色又单独设置了折线的线条颜色那么图例会自动根据各自的系列取色不需要额外处理。如果折线图里数据点的小标记和线条颜色不一致比如线条是蓝色、数据点是白色图例默认取的是线条颜色。这个细节很多新人会忽略想让图例显示数据点的颜色需要把lineStyle.color和数据点的颜色保持一致。还有一种情况是柱子和折线都走渐变图例的渐变糊成一片。这时候第四章的 formatter 富文本或者自定义 HTML 图例就派上用场了把所有图例统一成实色圆点看起来会干净很多。4.2 自定义 HTML legend跳出 canvas 的限制原生 legend 不管怎么调总归是在 canvas 里绘制样式能力有限。当需求上升到“图例要带阴影、要渐变色块、要图标加文字换行、要 hover 高亮背景”这种程度原生 legend 就力不从心了。这时候我通常直接关掉原生 legend在图表容器旁边自己画一套 HTML 图例。关掉原生图例只需要legend: { show: false }然后在页面里准备一个普通的 div 当作图例容器用 JavaScript 渲染图标和文字。div idchart stylewidth: 100%; height: 400px;/div div idcustomLegend/divconst chart echarts.init(document.getElementById(chart)); const seriesConfig [ { name: 实际销量, color: #3A7BFD }, { name: 目标销量, color: #FFB938 } ]; function renderLegend(selectedMap) { const el document.getElementById(customLegend); el.innerHTML seriesConfig.map(s { const isActive selectedMap ? selectedMap[s.name] ! false : true; return span classlegend-item ${isActive ? active : }>.legend-item { display: inline-flex; align-items: center; margin-right: 16px; cursor: pointer; color: #A3B5D6; font-size: 13px; user-select: none; } .legend-dot { width: 10px; height: 10px; border-radius: 50%; margin-right: 6px; } .legend-item.active { color: #D3E1FF; } .legend-item:not(.active) { opacity: 0.5; }4.3 交互联动与事件清理自定义 HTML 图例的关键点是交互联动点击 HTML 图例 → 派发legendToggleSelect动作 → ECharts 内部切换系列显隐 → 触发legendselectchanged事件 → 回调里重新渲染 HTML 图例的高亮状态。这套链路有个细节legendselectchanged事件里params.selected是一个对象key 是系列名称value 是布尔值。我上面renderLegend的写法是selectedMap[s.name] ! false这样在第一次没有传入 selectedMap 时默认全部显示。还有一点容易被忽略如果页面上有多个图表实例或者图表在路由切换后重新创建事件监听可能会叠加。单页应用里尤其要小心每次创建图表实例时最好在合适的时机调用chart.off(legendselectchanged)再重新绑定或者直接chart.dispose()销毁实例。window.addEventListener(resize, () chart.resize()); // 页面销毁时 chart.dispose();resize事件同理如果组件卸载后没有移除监听图表已经被 dispose 了resize 回调里再调用chart.resize()就会报错。正确做法是把 resize 回调抽成具名函数销毁时移除监听。5. 完整实操渐变柱状图 圆点图例的大屏案例5.1 需求拆解用一个实际需求来收拢前面所有知识点。假设我们要做一张大屏图表展示五个城市的“实际销量”和“目标销量”。需求有四条第一柱子要使用渐变色从上往下从亮到透明。第二“实际销量”柱状图的柱子如果超过 400整根变成警示红。第三图例要显示成小圆点颜色和柱子的主色调保持一致但必须是纯色不能出现渐变糊掉的情况。第四整体是深色大屏风格文字颜色、坐标轴颜色都要适配。5.2 完整代码const chartDom document.getElementById(chart); const chart echarts.init(chartDom); const seriesConfig [ { name: 实际销量, color: #3A7BFD }, { name: 目标销量, color: #FFB938 } ]; const cities [北京, 上海, 广州, 深圳, 杭州]; const actualData [320, 280, 430, 390, 510]; const targetData [350, 300, 400, 380, 480]; function buildSeries() { return seriesConfig.map((item, index) { const rawData index 0 ? actualData : targetData; const data rawData.map(v { // 实际销量超过 400 的柱子整根变警示色 const isOver index 0 v 400; return { value: v, itemStyle: isOver ? { color: #F56C6C } : { borderRadius: [4, 4, 0, 0], color: { type: linear, x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: item.color }, { offset: 1, color: rgba(58, 123, 253, 0.15) } ] } } }; }); return { name: item.name, type: bar, barWidth: 16, data }; }); } chart.setOption({ backgroundColor: #0B1230, grid: { left: 48, right: 24, top: 60, bottom: 32 }, tooltip: { trigger: axis }, legend: { top: 16, right: 24, icon: none, itemWidth: 14, itemHeight: 14, formatter: (name) { const idx seriesConfig.findIndex(s s.name name); return {dot${idx}|●} {txt|${name}}; }, textStyle: { rich: { dot0: { color: #3A7BFD, fontSize: 14, padding: [0, 6, 0, 0] }, dot1: { color: #FFB938, fontSize: 14, padding: [0, 6, 0, 0] }, txt: { color: #D3E1FF, fontSize: 13 } } } }, xAxis: { type: category, data: cities, axisLine: { lineStyle: { color: #2A3A6A } }, axisLabel: { color: #A3B5D6 } }, yAxis: { type: value, splitLine: { lineStyle: { color: #1D2B50 } } }, series: buildSeries() });5.3 关键点复盘这段代码是整套知识点的综合应用。系列配置抽成了seriesConfig数组颜色只维护一份。柱子的渐变起始色取自item.color图例富文本dot0、dot1的颜色也手动对齐了seriesConfig里的颜色。如果后面要改颜色只改seriesConfig里的一项就行所有地方同步。阈值标红的逻辑放在buildSeries的数据映射里。注意红色柱子同时失去了渐变效果这是有意的弹出警示超过 400 的柱子用高饱和纯色才能第一时间抓住眼球。如果你希望红色也是渐变可以把color也换成LinearGradient对象改成红色系从亮到暗。图例部分用icon: none关闭了默认图标再通过 formatter 返回{dot0|●}或{dot1|●}。富文本里dot0和dot1分别配了不同颜色所以实际销量显示蓝色圆点目标销量显示黄色圆点。因为 formatter 里用的是findIndex动态计算序号即使seriesConfig增加系列只要在textStyle.rich里补上对应的dot2、dot3样式就能继续工作。这套代码拉到深色大屏环境里字体颜色、轴线和背景色都做了适配标题区域如果还需要加直接在backgroundColor之上再加一个title配置即可。6. 常见问题与排查技巧6.1 高频问题速查表问题现象可能原因解决方案图例颜色和柱子颜色对不上数据项单独设置了 itemStyle.colorlegend 取的是 series 整体颜色把 series.itemStyle.color 设置为和图例期望一致或用 formatter 富文本覆盖图例颜色柱子用了渐变后图例很糊legend 把 LinearGradient 对象应用到小图标上用 icon:none 配合 formatter 画纯色圆点或自定义 HTML 图例图例项点击后没反应legend.data 的 name 和 series.name 不一致检查 name 是否完全一致包括空格图例多了就换行或者消失legend 默认不滚动分页设置 legend.type:scroll大屏上文字不随屏幕缩放canvas 里的文字不会自动适配 rem监听 resize根据容器宽度动态计算 fontSize图表初始化后图例空白容器初始化时处于 display:none 状态确保容器可见后再 init或延迟初始化自定义 HTML 图例点击失效事件绑定时图表实例已被销毁使用具名回调函数销毁时 chart.off()path 自定义图标位置偏移path 坐标范围不标准将 path 坐标统一调整到 0-1024 区间6.2 调试工具与排查思路ECharts 调试里最实用的是官方社区和实例编辑器。遇到图例样式问题先删掉业务代码里的样式干扰用最小化配置复现比如只保留一个 series、一个 legend逐步加回其他配置很快能定位到是哪一层配置导致的。还有一个容易踩的坑初始化图表时容器被某个 loading 遮罩盖住了导致图例区域宽高为 0。这种问题看代码很难看出来直接在浏览器 DevTools 里用元素检查看容器实际尺寸就行。如果图例的某个 item 始终不显示检查两步。第一步看 series 的name是不是空字符串空字符串会导致图例无法正确匹配。第二步看legend.data里有没有漏掉某个系列手动配置 legend.data 时漏写是最高频原因。6.3 我在实际项目里踩过的坑我自己做大屏项目时图例相关的问题排前三的分别是渐变图例糊、多系列颜色对不上、图例文字和整体设计风格不搭。前两个问题在上面的速查表里都有解第三个问题是我后来下定决心用自定义 HTML 图例的直接原因。原生 legend 的textStyle能控制的样式有限无法做不同图例项不同颜色、不同字号也无法让图例项和旁边的内容对齐。换成 HTML 图例之后这些问题全部变成普通的 CSS 问题设计稿什么样就能还原成什么样。还有一个小经验图例数据如果是异步加载的一定要在setOption之后、数据返回之前的这段时间里先把图例容器撑起来否则数据回来后容器尺寸突变图表可能闪烁。最简单的做法是给图例容器一个min-height或者在 loading 状态时就渲染好空状态图例。最后分享一个我常用的配置习惯把 seriesConfig 抽成公共配置数组series、legend、tooltip 的回调都从这个数组里取值。颜色、名称、是否显示、图标形状全部收敛到一处改需求时只需要改一个地方。这套方案在我多个项目里验证过省下的沟通和时间成本非常可观。如果你也经常和 ECharts 图例打交道建议下次重构时试试。
返回列表