ARTICLE DETAIL

资讯详情

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

ECharts左右柱状图公用一个X轴:双grid布局与交互联动实践

ECharts左右柱状图公用一个X轴:双grid布局与交互联动实践 做图表这么些年我接过的左右柱状图公用一个X轴需求不下二十次。有做课程数据的有做销售对比的还有做问卷调查分析的。第一次接到这个需求时我以为只是把两个柱状图并排放一起结果被需求方连打回来三次X轴没对齐、鼠标悬停时两个图各显示各的、左边改了下轴范围右边纹丝不动。后来才明白所谓公用一个X轴要的不只是视觉上对齐还有交互上的联动和数据范围上的同步。这个话题很适合正在学ECharts做数据可视化课程作业的人也很适合工作中要用图表做对比分析的运营、产品和开发。它看起来是个小而美的标题但牵扯到grid布局、坐标轴联动、tooltip触发、甚至自定义图形渲染把这些搞懂ECharts的很多配置项你基本就通了。我先把完整的实现思路、可复现的代码和踩过的坑全部整理出来按照从需求拆解到最终落地的顺序来写。1. 先搞清楚需求哪种左右柱状图才是你要的1.1 三种常见形态别一开始就搞混我在处理这个需求时发现用户嘴里的左右柱状图公用X轴实际说的是三种完全不同的场景。第一种是左右并排的双柱子。比如每门课程男生和女生选修人数一个类目轴上有两个柱子一个向左或者两个紧挨着柱子在视觉上一左一右。这种其实不需要公用什么ECharts默认一个grid里放两个series就能搞定X轴天然就是同一个。第二种是左右两个独立的图表区域共用底部X轴。左边放男生人数柱状图右边放女生人数柱状图或者左边放2023年数据、右边放2024年数据两块图中间可以有一条明显的分隔但底下的X轴标签是同一条课程名称。这种通常用ECharts的grid数组配合多个xAxis来实现。第三种是正负发散式柱状图。经典的例子是男左女右或者收入支出一左一右X轴的是数值轴Y轴是类目轴柱子以X轴0值位置为界向左延伸表示一类向右延伸表示另一类。这种方式常用于人口金字塔、收支对比、满意度正负评价这类场景在Origin这类科研软件里也叫双向条形图或者发散条形图。如果你只是要第一种那直接默认配置即可本文不需要往下看。但你既然搜索了公用一个X轴大概率是第二种或第三种。第二种侧重于两块图共用同一个维度的类目轴第三种侧重于两条数据在0轴两侧对称展开。两者的实现思路完全不同不能混用。1.2 怎么选从数据结构和阅读目标出发我自己判断场景时会先问三个问题要对比的数据是同一维度下的两类比如同一批课程的男生、女生人数选第一种或第三种。要展示的是同一维度在不同分组下的整体形态比如每门课2023年选课人数和2024年选课人数选第二种左右两个图区方便分别观察各年度内不同课程的高低关系。强调的是对称和方向比如积温盈亏、收入vs支出、赞成vs反对这种强烈需要零点对齐的选第三种。从阅读目标来看第二种适合逐块对比读者先看左边图再看右边图最后对比同一门课程在左右两块中的高度差异。第三种适合一眼看出谁多谁少因为0轴在中间向左向右的长度就是多与少的比例视觉冲击力很强。确定形态之后再去写代码就顺了。我最开始就是没做这个拆解直接套双grid方案去做一个本来该用正负发散的图结果轴设置得非常别扭而且发给需求方后对方觉得左右不对齐不好看。所以做图表的第一件事不是打开编辑器而是把需求翻译成图表形态。2. 落地示例双grid方案实现课程男女选修人数图2.1 完整可运行的option配置我先拿一个最常见的场景来讲每门课程男生、女生选修人数对比左右两个图区左边显示男生总数右边显示女生总数共用底部X轴课程名称。这个场景对应前文说的第二种核心思路是画布上不只有一个绘图区而是通过grid数组划分出左右两个绘图区每个绘图区各自有一个xAxis和yAxis。底部X轴的类目数据完全一致所以左右两边的刻度标签会显示相同的课程名视觉上就是公用了一个X轴。下面是完整配置你可以直接复制到ECharts官网实例编辑器里跑起来看效果。option { title: { text: 每门课程男女选修人数对比, left: center }, tooltip: { trigger: axis, axisPointer: { type: shadow } }, legend: { data: [男生人数, 女生人数], top: 40 }, grid: [ { left: 6%, right: 54%, top: 20%, bottom: 8%, containLabel: true }, { left: 54%, right: 6%, top: 20%, bottom: 8%, containLabel: true } ], xAxis: [ { type: category, data: [高等数学, 大学英语, 程序设计, 数据库原理, 数据结构], gridIndex: 0, axisLabel: { interval: 0 } }, { type: category, data: [高等数学, 大学英语, 程序设计, 数据库原理, 数据结构], gridIndex: 1, axisLabel: { interval: 0 } } ], yAxis: [ { type: value, name: 男生人数, gridIndex: 0 }, { type: value, name: 女生人数, gridIndex: 1 } ], series: [ { name: 男生人数, type: bar, data: [45, 32, 58, 61, 39], xAxisIndex: 0, yAxisIndex: 0, itemStyle: { color: #3b82f6 } }, { name: 女生人数, type: bar, data: [55, 68, 42, 39, 61], xAxisIndex: 1, yAxisIndex: 1, itemStyle: { color: #ec4899 } } ] };跑起来之后你会看到画布正中间有一道看不见的分界线左边是男生柱状图右边是女生柱状图底部X轴标签都是那五门课程。鼠标悬停时默认只会显示当前鼠标所在那一侧的tooltip这个后面我会教你怎么改成左右联动。2.2 关键配置逐个拆解grid、xAxis、axisPointer这套配置里最容易让新手理解不了的是grid数组的left和right。我的经验是想在画布上放两个图就要先给它们分地。两个grid加起来要铺满整个画布而且中间要留出间距。第一个gridleft: 6%表示它的左边缘在画布宽度6%处right: 54%表示它的右边缘在画布宽度54%处。也就是说第一个grid占用的水平范围是6%到54%。第二个gridleft: 54%表示它的左边缘从54%开始right: 6%表示距离画布右边缘还有6%。它占用的范围是54%到94%。这样一来两个grid在54%处相邻中间没有重叠也不会留出过大空隙。我实际测试时喜欢留1%-2%的间距比如第一个grid的right设成55%第二个grid的left设成55%这样视觉上能看出两块图的边界又不会太生硬。这个间距完全看个人喜好但别留太大否则读者会以为这是两张无关的图。xAxis数组是这里最关键的。公用一个X轴其实是文字上的简化ECharts里每个grid都必须绑定一个独立的xAxis不能两个grid共用同一个xAxis实例。所以我们要做的是创建两个xAxis保持它们的type、data、axisLabel设置完全一致。只要数据一致又放在同一个水平位置视觉上就像是一根X轴贯穿了左右两块图。有个细节要注意两个yAxis的name不要相同因为一个代表男生人数一个代表女生人数。如果左侧和右侧想用同一个维度比如都叫人数那可以在图例里区分或者用标题分别标注。axisPointer在ECharts 5里通常配置在tooltip下默认不写也能用。但如果你想左右两块图在鼠标悬停时能像同一张图那样联动需要给tooltip添加axisPointer的link属性。这个我放在2.3节细说。2.3 让两个X轴在视觉和交互上都公用要做到视觉上公用布局参数算是解决了要做到交互上公用需要做两件事一是坐标轴数值范围同步二是鼠标悬停时左右同步显示提示。先说数值范围。因为男生人数和女生人数的最大值可能差很多如果不做处理左右两边的yAxis会各自根据数据自动计算最大值比如左边最大值70右边最大值80这会导致同样高度的柱子表示的意义不同读者会误判。我的做法是手动固定两个yAxis的min、max和splitNumber让两边的刻度完全一致yAxis: [ { type: value, name: 男生人数, gridIndex: 0, min: 0, max: 100, splitNumber: 5 }, { type: value, name: 女生人数, gridIndex: 1, min: 0, max: 100, splitNumber: 5 } ]这样两边都是从0到100每20一个刻度柱子的高度才具备直接可比性。如果数据波动很大可以先用函数统一计算数据的最大值再传给max比如const allData [45, 32, 58, 61, 39, 55, 68, 42, 39, 61]; const maxValue Math.ceil(Math.max(...allData) / 20) * 20;这里的20是刻度间隔用Math.ceil向上取整保证max是20的整数倍刻度不会出现小数。再说悬停联动。ECharts官方提供了tooltip.axisPointer.link配置可以把不同xAxis的坐标轴指针绑定在一起。具体写法是tooltip: { trigger: axis, axisPointer: { type: shadow, link: [{ xAxisIndex: all }] } }意思是当鼠标悬停在任意一个grid内部的柱子上时所有xAxis的阴影指针都会同时触发。这样左边图悬停在高等数学上右边图也会在高等数学上出现一块淡淡的阴影读者能立刻知道当前正在对比的是同一门课程。实测下来加上这个link之后图表的整体感会强很多。需要提醒的是link的生效要求左右两个grid的X轴数据顺序一致也就是说xAxis[0].data和xAxis[1].data必须是同一个数组或者内容完全相同的数组。如果左边是降序、右边是升序联动会错乱。别问我为什么知道我第一次试用link时就因为两个grid的类目顺序不一致阴影在左右两边指向了完全不同的课程。3. 从能用变好看渐变色、自定义图片、刻度对齐3.1 柱状图渐变色linearGradient的两种写法热搜词里有一句echarts柱状图设置渐变色这个在公用X轴的左右柱状图里特别好用。左右两边用不同色系的渐变色可以大幅提升图表的质感也能帮助读者快速区分两个区域。渐变色在ECharts里有两种写法一种是在series.itemStyle.color里直接写LinearGradient对象另一种是用color: { type: linear, ... }的组件式写法。我推荐后者因为它不需要从echarts里引入LinearGradient写起来更直观也方便复制到配置对象里。itemStyle: { color: { type: linear, x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: #6366f1 }, { offset: 1, color: #a5b4fc } ] } }这里的x、y、x2、y2定义的是渐变方向。(0,0)到(0,1)表示从柱子顶部向底部渐变颜色从深蓝变到浅蓝。如果你想让渐变横向就把x2改成1、y2改成0。关于渐变色我有两个建议一是颜色对比不要太强烈尤其左右两侧如果都用了高饱和色看久了眼睛很累二是渐变方向尽量统一要么都从上到下要么都从下到上混着用会显得很乱。我在之前的项目里试过左图从上到下渐变、右图从下到上渐变结果被同事吐槽看起来像两张图不像一个整体。3.2 柱子换成自定义图片pictorialBar与graphic热搜词里还有一个很有意思的问题echarts柱状图柱子可以用自定义图片显示不。答案是可以用而且有两种思路。第一种是用pictorialBar系列。它专门用来做这种图形柱子可以把柱子替换成一张图片或者SVG路径。比如你想用书本图标来表示每门课程的选课人数可以这样做series: [ { name: 男生人数, type: pictorialBar, data: [45, 32, 58, 61, 39], symbol: path://M862.4 480..., // SVG路径 symbolSize: [30, 60], symbolPosition: end, itemStyle: { color: #3b82f6 } } ]这种适合做汇报演示里的趣味化图表但要注意如果柱子数值太小图片会被压缩得很扁可读性会受影响。第二种思路是用graphic组件手动放置图片。这个更灵活但需要监听事件或者提前算好坐标。我通常只用在首页大屏这种不追求通用性的场景里。如果你只是想给柱子顶部加一个小图标还可以在series里用markPoint给每个柱子配一个自定义图片的标记点。这个比整体替换柱子要稳妥得多既保留了柱子的准确长度又增加了视觉趣味性。3.3 刻度对齐数值轴与类目轴的差异另一个热搜词是echarts折线图x轴刻度虽然说的是折线图但在公用X轴场景里同样适用。这个问题的本质是当数据是0、5、10、15这种间隔时X轴刻度应该显示在类目的中心还是边界。ECharts里类目轴type: category的刻度默认是包裹式的柱子和刻度标签是对齐的不用额外处理。但如果你混用了数值轴type: value和类目轴比如用数值轴散点图去模拟柱状图就会出现刻度对不齐的问题。在左右双grid的场景下最常见的刻度问题有两个一是X轴标签太长导致重叠。课程名称如果超过6个字5门课就会挤在一起。处理方法是开启axisLabel的旋转或换行axisLabel: { interval: 0, rotate: 20, fontSize: 12 }rotate设为20标签会逆时针旋转20度大部分情况都能解决重叠。如果不想旋转可以让宽度方向多留一些空间或者用formatter函数把长文本拆成两行。二是两个grid的X轴可视区域宽度不一致。虽然左右两边数据相同但左侧yAxis.name占用了较多空间右边没有可能导致右边的图比左边宽。我通常会在grid里手动分配left和right并开启containLabel: true。这个配置的作用是保证坐标轴标签不被裁切同时让grid的实际绘制区域在包含标签后重新计算。两个grid都加containLabel并且left、right的百分比相同或互补一般就不会出现一边宽一边窄的问题。4. 交互增强鼠标悬停与点击展示细节4.1 tooltip配置全局联动与独立显示鼠标点那儿在哪儿显示柱状图这个热搜词我理解是描述鼠标悬停在图表上某位置时需要展示对应位置的柱状图详细数据。ECharts里默认的tooltip就能做到但左右双grid的场景会有一些细节坑。默认情况鼠标悬停在左图某根柱子上只显示左图这个柱子的数据悬停在右图只显示右图的。加上link配置后悬停一边两边的阴影指针都会出现但tooltip内容还是只显示当前悬停那一个series的数据。如果你希望悬停左边时tooltip同时显示左右两边对应课程的数据需要设置tooltip的trigger为axis再配合axisPointer.link然后自定义formattertooltip: { trigger: axis, axisPointer: { type: shadow, link: [{ xAxisIndex: all }] }, formatter: function (params) { // params会包含所有触发的series数据 let result params[0].axisValue br/; params.forEach(item { result item.marker item.seriesName : item.value 人br/; }); return result; } }这样鼠标悬停在左图的程序设计上时tooltip会同时显示男生人数: 58人和女生人数: 42人右图对应课程也会被阴影高亮。对比效率一下子上来了。我会建议在需求阶段就确认清楚到底要独立显示还是联动显示。联动看起来高级但如果左右两侧数据量大tooltip会变得又长又乱。我的默认建议是左右两侧图例总共不超过6个series时用联动超过6个就分开显示。4.2 点击柱子显示详细柱状图如果你想实现鼠标点哪儿哪儿就显示柱状图这种更高级的交互需要借助ECharts的点击事件动态添加内容。比如场景是这样的左边是一张每门课程男女生人数的柱状图但柱子只显示总数点击任意柱子后下方出现一个小的堆叠柱状图展示该课程下细分维度比如不同年级的选修人数构成。这个点哪儿就在哪儿出现柱状图的效果本质上不是ECharts内置功能而是通过事件响应当前柱子然后渲染新的图。具体做法分三步第一步给图表实例绑定click事件myChart.on(click, function (params) { if (params.seriesType bar) { const courseName params.name; renderDetail(courseName); } });第二步在页面中准备一个隐藏的divrenderDetail函数里调用接口或本地数据拿到该课程的年级分布数据重新new一个ECharts实例在那个div里渲染一个堆叠柱状图。第三步为了让点哪儿就在哪儿显示更贴切可以把这个隐藏div定位到柱子附近。获取柱子屏幕坐标需要用到myChart.convertToPixelconst point myChart.convertToPixel({ seriesIndex: params.seriesIndex }, params.value); // 根据point的x/y坐标把详情浮层div移动到柱子附近convertToPixel是ECharts里非常实用的API能把数据坐标转换为像素坐标。我第一次用它时发现它需要传入一个包含gridIndex或seriesIndex的对象不然在双grid场景下可能算错位置。这里建议指定gridIndex: params.dataIndex 0 ? xAxisIndex : 0或者直接用seriesIndex最稳。视觉上点击柱子后柱子旁边弹出一个小图表浮层这种交互在数据大屏上很常见。实现起来也不难关键就是convertToPixel算坐标、浮层定位、以及点击空白处关闭浮层这三件事。5. 常见问题与排查技巧实录5.1 问题速查表我把公用X轴柱状图项目里最容易踩的问题整理成了一张表都是实际测试中遇到过的。问题现象根本原因解决办法左右两个X轴标签不一致xAxis数组里data写了两份不同的数据定义一个统一变量两个xAxis都引用它悬停没有左右联动tooltip.axisPointer.link未配置加上link: [{ xAxisIndex: all }]联动时阴影指向错误课程两个xAxis的类目顺序不一致确保两个data数组顺序完全一致左右柱子高度不可比两个yAxis自动计算的最大值不同手动设置相同的min和max左图比右图宽containLabel未开启或grid比例不对称两个grid都加containLabel调整left/right课程名称文字重叠多个类目且名称过长axisLabel加rotate或formatter换行柱子叠在一起分不开一个grid里多个series未设置barGap或stack错误同一grid内用barGap或修改stack逻辑鼠标点柱子没有反应click事件没有绑定或事件被tooltip拦截确认myChart.on绑定在setOption之后5.2 两个典型坑的完整排查过程第一个坑是双grid下点击事件拿不准坐标。我有一次做双grid图点击左边柱子要弹详情结果点击右边柱子时详情也跑到左边去了。排查发现我在click回调里用params.value去convertToPixel没有指定是哪个gridECharts默认取了第一个grid的坐标基准。解决办法是给convertToPixel传一个带gridIndex的对象myChart.on(click, function (params) { const gridIdx params.seriesIndex 0 ? 0 : 1; const point myChart.convertToPixel({ gridIndex: gridIdx }, params.value); });注意如果series比较多最好从params.componentIndex或自定义series里的一个字段去映射gridIndex别写死。写死的问题在数据顺序调整后会立刻暴露。第二个坑是加了渐变色后柱子变得很暗淡。ECharts默认的柱状图颜色是带透明度的如果手动覆盖为渐变色不小心把offset为0的color设成了非常浅的颜色柱子顶部会很模糊整根柱子看起来像褪色了。我调渐变色时一般都是顶部深、底部浅这样柱子的轮廓清晰视觉重量集中在顶端也更符合人的阅读习惯。如果你的场景是顶部浅、底部深要确保底部颜色足够深否则柱子底部会看不清边界。5.3 其它工具里的柱子叠在一起怎么分开热搜词里出现的origin柱状图叠在一起怎么分开其实是个非常经典的问题。很多用Origin做科研图的人导入多组数据后默认会得到一堆重叠的柱子像一坨没化开的雪糕。如果你用Origin选中图表后可以在Layer Contents里看每个柱子的数据列要分开展示一般用Offset功能——在数据列的Plot Details面板中把Group标签页里的Offset选项改成Increment每个柱子会自动产生一个小的水平偏移视觉上就分开了。如果还是叠在一起检查是不是把多个数据列绑定到了同一个Plot Type里的同一根柱子必要时新建Layer把其中一个柱状图移到独立图层。而我更推荐的做法是交互式对比的图表直接用ECharts来做最终论文/报告里需要静态图的再用Origin。ECharts的左右双grid方案可以做得比Origin更灵活鼠标悬停、点击联动、动态高亮全都方便Origin主要胜在统计分析和模板统一性。这事没有绝对的哪个工具更好只取决于你下一步要把图用到哪儿。6. 最后分享几个实战小技巧这个需求的最终效果很大程度上取决于细节。我观察到一个规律那些说公用X轴做出来不好看的人往往都没有统一两个grid的yAxis范围。只要左右两边的数值范围不一致读者第一眼就会觉得图表歪了。所以我的默认动作就是先算出两个数据集的公共最大值然后统一设置min和max。另外一个容易被忽略的是图例位置。双grid方案里legend默认是根据series的name自动生成的但左右两块图如果都只有一个series图例会显示男生人数、女生人数完全没问题。如果每块图里有多个series图例项会混在一起而且点击图例隐藏某个series时另一个grid里可能没有对应项会感觉联动断了。我处理过的最简单方案是左右各配一个独立图例用两个legend组件分别设置left和right对齐到对应的grid区域上方。还有一个提升质感的小技巧给柱子加圆角。在ECharts 5里直接设置itemStyle.borderRadius就可以itemStyle: { borderRadius: [6, 6, 0, 0] }这会让左右两个柱状图的顶部变得圆润配合浅色分割线整体会非常精致。但要注意如果你用了渐变色圆角区域的颜色过渡不会做到完美贴合这是canvas渲染的局限不必太纠结。最后说一个很多人不知道的功能双grid公用X轴时如果左右两边是同一个类目你甚至可以在两个grid之间画一条垂直的同轴提示线。做法是给xAxis加一个axisLine的样式或者用一个markLine配合xAxisIndex: [0, 1]。不过markLine不能跨grid我通常用另外的方式实现在两个grid之间放一个自定义的矩形或者干脆用背景色区分左右两区。背景色分隔的效果也很不错而且代码量更少grid: [ { left: 6%, right: 54%, top: 20%, bottom: 8%, containLabel: true, backgroundColor: #f8fafc }, { left: 54%, right: 6%, top: 20%, bottom: 8%, containLabel: true, backgroundColor: #f0f4ff } ]给左右两个绘图区分别加上非常浅的背景色读者不用看标题也能感知到两块区域的边界和公用X轴的视觉连续性完全不冲突。我在好几个项目中用这个方案反馈都是图看着干净、分区清楚。每次做这种带布局要求的图表我都提醒自己先定好场景形态再写option。双grid的代码本身并不复杂真正的复杂度在于左右两侧的参数是否保持一致、交互是否协调、视觉是否乱。把这几点理清左右柱状图公用一个X轴就是一个非常成熟的方案了。
返回列表