作为一个常年泡在图表堆里的人我第一次看到 Marimekko 图时第一反应是“这东西真难伺候”第二反应是“如果数据对了高级感确实拉满”。Marimekko很多人叫它可变宽度图或马赛克图本质上是一种把“整体市场构成”和“个体内部细分”同时塞进一张图的双维度表达方式。它最大的本事是能让你一眼看出哪个玩家在整体盘子里占的地盘大同时还能看清楚它自己的业务是怎么切分的。Highcharts 官方对这类图的支持并不像柱状图、折线图那么“直给”需要动点脑筋用堆叠柱状图加一些配置魔法来实现。这篇文章我就拿一个实际做过的数据看板项目作为蓝本把思路、配置、踩坑点全部拆开讲一遍。不管你只是想快速复现一个示例还是想搞懂背后的计算逻辑这篇都能给你实在的参考。1. 内容整体设计与思路拆解1.1 Marimekko 图的核心机制双维度见缝插针我们先别急着写代码。要理解 Marimekko 在 Highcharts 里怎么做先得理解它的几何逻辑。一个标准柱状图横轴是品类纵轴是数值每根柱子宽相等。Marimekko 改变了其中两个规则第一柱子的宽度不再相等而是代表某个维度在整体中的占比第二柱子内部不再只有一个色块而是用堆叠的方式展示这个占比内部再次按另一个维度切分的结果。我习惯用一个生活化类比来解释想象你面前有一个矩形托盘托盘的总面积代表整个市场的总规模。每个玩家分到的托盘宽度代表它的市场规模占比而玩家在自己那块宽度里按高度叠放不同口味的小盒子代表它内部的业务结构。这样一来横轴不再表达“量”而是表达“份额”纵轴才表达“实际数值”或“更多份额”。这个特性决定了它最适合两类场景一类是市场格局分析比如移动端操作系统的份额与版本分布另一类是资源分配审计比如各部门预算总额占比与费用类型拆解。如果你只是想让不同类别的柱子好看一点千万别用 Marimekko那属于杀鸡用牛刀而且会让读者阅读成本暴涨。1.2 为什么选 Highcharts 而不是专门的可视化库市面上面向 Web 的图表库那么多为什么还要选 Highcharts说实话如果你用的是专用商业智能工具比如 Tableau、Power BIMarimekko 可能有内置组件你拖拽一下就行。但在前端自定义程度高、需要深度嵌入业务系统的场景下Highcharts 的模块化优势和兼容性就很关键了。Highcharts 本质上没有自带一个叫 Marimekko 的系列类型这是它和某些专门图表库不一样的地方。但它的堆叠柱状图能力极其成熟配合xAxis的linkedTo、tickPosition、pointPlacement等属性可以手搓出一个观感非常自然的 Marimekko 图。相比之下ECharts 也支持类似实现但 Highcharts 在处理区间宽度、图例交互和 tooltip 联动上更顺手特别是在老牌企业系统里Highcharts 的文档和商业授权模式更清晰对公业务也更友好。选它的另一个理由是性能。Marimekko 这种图一旦数据项变多其实就是普通堆叠柱状图的变形Highcharts 对 SVG 的渲染优化做得不错即使同时渲染七八个序列、每序列几个数据点交互也不会卡顿。1.3 实现路径选型堆叠柱状图的魔法配置接下来是核心问题怎么在一张柱状图里让不同数据点的柱子宽度不同Highcharts 默认地将所有柱子的 pointWidth 设为相同所以必须另辟蹊径。主流的实现思路有两种。第一种是“人工拆分类别法”我提前把每个玩家市场份额转换为“宽度权重”然后为每个玩家单独创建一个类别区间再通过pointPlacement: between和linkedTo来模拟跨度不同的柱子。这种做法的好处是逻辑直观坏处是数据的动态增删维护比较麻烦因为类别数组的长度和位置都要手工对齐。第二种是“百分比堆叠 数据微调法”用stacking: percent让每个类别内部各系列的比例自动变成百分比再配合一系列tooltip格式化函数去显示真实值。这种做法代码更精简适合演示场景。两种我都尝试过最终在真实项目中采用了第一种的变体。因为第二种虽然代码少但会丢失“实际数值”的纵轴意义tooltip 里需要额外拼回去一旦用户在图上悬停查看数据很容易产生“为什么图上明明是 30%提示框里却是 1200”的困惑。我更倾向保存原始数值的堆叠方式也就是stacking: normal但配合自定义宽度区间。所以下面这条路径最终成型用xAxis.categories预先定义所有玩家的绝对宽度值再用pointPlacement: between让柱子跨越对应的刻度区间。这种做法的巧妙之处在于它把“份额”的表达交给了坐标轴的类别间距而不是依赖 Highcharts 自带的某个宽度参数。2. 核心细节解析与实操要点2.1 数据的预处理份额计算与类别构造在使用 Highcharts 画 Marimekko 图前最关键的一步不在图表配置里而在数据准备阶段。你需要将原始数据整理成三类字段名称、总值、内部细分值。假设我们有三家手机厂商A 厂、B 厂、C 厂各自的销售占比分别是 50%、30%、20%每家的产品线分高中低三档。那么第一步要做的是把这三家的占比作为一个数组比如[50, 30, 20]。这里的数值不是真正的销量而是百分比权重它决定了整个图表中每根柱子的宽度。而每个系列高、中、低档在各分类下的值则代表这一类别中该系列的绝对值。注意在这个设计里纵轴的数值必须已经折算成真实单位比如销量万台。类别数组的构造也有讲究。因为 Highcharts 是线性等宽排列类别的如果我们要让 A 厂占 50% 的宽度就要让它对应 50 个“虚拟单位”的长度。以总宽度 100 个单位为例类别数组应该是categories: [ A厂, , , , , , , , // 这里省去一部分占位 ]但更聪明的做法是直接用区间整数。我通常会让每个分类对应一个连续区间比如 A 厂覆盖 0 到 50B 厂覆盖 50 到 80C 厂覆盖 80 到 100。这样就不需要去数占位空字符串了。2.2 边界对齐与pointPlacement的默契配合Highcharts 里最容易被忽略却又至关重要的属性是pointPlacement。在普通的条形图中数据点是落在刻度线上的但柱状图期望柱子以刻度为中心展开。这会导致一个问题如果 A 厂的数据点设在 25区间 0-50 的中点柱子会从它的左边和右边各占一半而这个宽度并不等长于 0 到 50 的区间视觉上会错位。所以必须设置pointPlacement: between对应英文文档中的between。在 Highcharts 中这个值会让柱子的矩形边界贴齐两个相邻刻度的中间线。放在我们的场景里就等于让 A 厂的数据点在类别“0”和“50”的边界之间对齐从而让柱体正好横跨 0 到 50 这个区间不会有误差。如果你跳过这一步大概率会遇到第一个坑柱子之间的缝隙会不均匀或者有的柱子明显覆盖了别的类别区域看起来像“狗啃的”。2.3 序列条数、颜色与图例语义Marimekko 图里一般会有两三个系列代表内部细分维度。但序列一多图例和颜色的管理就麻烦。我的建议是数量最好不超过五个。这不是审美洁癖而是另一层原因当柱体宽度不同时过细的柱体内没有足够空间去容纳细分色块的边界多个色块挤在一起用户难以从视觉上区分边界图例更是形同虚设。为了增强可读性我会给色块边界设置为白色描边宽度在 1 到 2 像素之间这样即使相邻色块颜色接近也能通过白线清晰分层。这个细节在深色主题下尤其明显。在图例方面Highcharts 默认会把所有数据系列都展示出来。对于 Marimekko我建议按内部维度显示图例不要展示厂商名称因为厂商名称已经通过横轴区间表达了。如果横轴上空间太挤也可以把厂商名称全部放进 tooltip 中避免图和文字互相干扰。2.4 动态数据更新的注意点真实项目中数据不可能永远固定。当我们用chart.setData()或其他异步方式更新数据时Marimekko 图的坑就来了因为每个类别的数据点数量和代表宽度都会变化所以categories数组也必须一致更新。我踩过最典型的一次坑是我更新了系列的数据值但忘了同步更新categories数组的间隔长度结果图上所有柱子宽度全错乱了A 厂的柱子直接插进了 B 厂的地盘。所以我后来总结出一个规矩每次数据更新时用同一个数据源同时生成series和categories千万不要让它们各自独立维护。如果你的数据是动态刷新的强烈建议在更新前调用一次全量重构函数直接基于最新的原始数据重新走一遍“预处理到渲染”的流程而不是局部改配置项。3. 实操过程与核心环节实现3.1 先搭个高仿示例电商销售渠道占比为了让你看到一条完整的落地链路我拿一个电商销售场景来演示假设一个公司有三种销售渠道——自营店、平台店、分销商市场份额占比分别为 55%、30%、15%每个渠道内又分新客销售和老客复购两个系列。最终我们要实现的图是横轴显示三个渠道自营店占一半以上的宽度每根柱子内部分为上下两段上段是老客复购的绝对值下段是新客销售的绝对值。纵轴显示累计值tooltip 显示真实数据和百分比。下面是一份简化的可运行配置代码Highcharts.chart(container, { chart: { type: column, spacing: [20, 20, 20, 20] }, title: { text: 各渠道销售结构Marimekko示例 }, xAxis: { // linkedTo 指向第0个类别并隐藏重复的轴 linkedTo: 0, categories: [自营店, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ], tickPosition: inside, labels: { // 只在边界处显示真实名称 formatter: function () { if (this.value 自营店 || this.value 平台店 || this.value 分销商) { return this.value; } return ; } } }, yAxis: { title: { text: 销售额万元 }, gridLineWidth: 1 }, plotOptions: { column: { stacking: normal, borderWidth: 1, borderColor: #ffffff, pointPlacement: between } }, series: [ { name: 新客销售, data: [3000, 1500, 700], color: #3b82f6 }, { name: 老客复购, data: [1200, 800, 500], color: #f59e0b } ] });不过你注意一下这段代码里categories的省略号位置其实还是手工的。为了让逻辑更健壮我会用一个函数动态生成这种包含大量空字符串的类别数组而不是靠手数。这个等一下讲。3.2 动态类别数组生成逻辑因为我经常要调整各渠道的占比手工维护类别空字符串肯定不可行。所以建议写一个工具函数给定各分类名称及对应权重输出一个categories数组每个名称只出现在区间的起点其余位置用空字符串填充。比如function buildCategories(namedWeights) { const totalUnits 100; const categories []; let cursor 0; namedWeights.forEach((item) { const width Math.round((item.weight / 100) * totalUnits); categories.push(item.name); for (let i 1; i width; i) { categories.push(); } cursor width; }); return categories; }这个函数输出的数组总长度约为 100每个分类的“地盘”大小正比于其权重。放到xAxis.categories后配合pointPlacement: between柱子的宽度就与分类权重一一对应了。不过这里还有一个边界问题Highcharts 的柱状图默认每个数据点对应一个类别位置如果你的分类宽度过大柱体会占据多个类别位置这没问题但 label 的位置只会出现在“数据点本身绑定的索引”上。所以把分类名放在区间起点是最自然的做法。3.3 tooltip 与数据标签的呈现策略Marimekko 图上同时存在两层信息占比、真实数值。默认 tooltip 只会显示系列名称和数值这不够直观。我通常会在tooltip.formatter里做一次完全自定义的展示。示例代码tooltip: { formatter: function () { const total this.points.reduce((sum, p) sum p.y, 0); const channelName this.x; // 我们定义的类别名称或区间起点 let html b${channelName}/bbr/; this.points.forEach((point) { const percent ((point.y / total) * 100).toFixed(1); html ${point.series.name}: ${point.y}万元 (${percent}%)br/; }); html 合计: ${total}万元; return html; }, shared: true }注意shared: true一定得开否则悬停时你只会看到单个系列的数据。另外如果是一个分类内有多个数据点因为使用了pointPlacement: between悬停区域可能会跨越多个类别索引。这时你需要处理this.points的归类逻辑不要直接把第一个点的类名当作绝对真相。3.4 完整响应式方案与端侧适配做企业级项目不可能不考虑移动端。Marimekko 图在手机屏幕上最大的问题是横向空间不够分类标签容易重叠。我的处理方式是在移动端不展示xAxis.labels改为在tooltip中强调分类名称同时用chart.scrollablePlotArea开启横向滚动区域。chart: { scrollablePlotArea: { minWidth: 700, scrollPositionX: 0 } }这样做的好处是桌面端一眼全览移动端可以滑动查看细节不会因为挤压而丢数据。3.5 数据标签dataLabels什么时候加什么时候不加数据标签本身在 Marimekko 图中很容易翻车因为柱体宽度不一内部空间不一致标签会互相遮盖。我的经验是分类柱体宽度小于 15 个单位时不要在柱体内部放数据标签改用外置引导线或直接省略只有当柱子宽度够大、内部空间有明显留白时才放置。而在演示示例里为了教学效果我通常会在每个数据点上显示数值但会关闭背景遮挡和阴影。4. 常见问题与排查技巧实录4.1 我的柱子宽度和预设占比对不上这是被问得最多的问题。排查要点如下检查xAxis.categories数组的长度和顺序是否和系列数据一致。如果 A 厂的权重是 50那么它对应的类别区间应该正好占 50 个位置如果类别数组里空缺个数不足会导致柱子无法完整覆盖区间视觉上宽度小于预期。检查pointPlacement是否设置成了between。如果不设置柱子会以数据点为中心向两边扩展可能把柱子“挤扁”或“拉伸”。排查是否误用了pointWidth。一旦在plotOptions或某个系列里设置了固定宽度的pointWidth它会覆盖掉类别区间赋予的宽度导致 Marimekko 图直接失效。4.2 柱体之间有缝隙或者颜色边界有白线异常Highcharts 默认给柱体设置了细边框。在 Marimekko 里如果边框颜色和背景不一致会显得柱体之间有明显的分割线。解决方案是统一设置borderColor为图表背景色或设置borderWidth: 0。如果你反而希望保留白色分割线来强调区分不同系列那就要故意设成白色并在柱体内部不发生叠加的区域保持一致的透明度和描边粗细。这个坑在我第一次实现时也出现过我设置了borderWidth: 1但没设边框颜色结果默认边框是深灰色和整体浅色调不搭视觉上非常碎。后来统一为#ffffff整体瞬间干净很多。4.3 图例点击后柱子全部消失Highcharts 的图例默认支持点击隐藏系列。在普通柱状图里没问题但 Marimekko 图里隐藏一个系列后剩余的堆叠柱体仍然保留很多新手会惊讶于“柱子宽度没有变”。这是正常的因为宽度由分类数据决定而不是由某个系列单独决定。如果你希望点击图例时同时改变柱子宽度分布那就需要自定义图例点击事件重新计算权重和分类。不过从信息设计的角度看我不建议这么做因为 Marimekko 的价值在于同时呈现双维度隐藏系列会破坏信息完整性。4.4 x 轴标签显示出现错位或重叠因为xAxis.categories里充满了空字符串labels的默认渲染逻辑会把所有空格都当作一个类别尝试显示导致标签重叠。解决方法是重写labels.formatter只返回非空值。另外也可以设定xAxis.labels.step让 Highcharts 自动跳步显示减少标签渲染压力。4.5 tooltip 悬停时显示 “undefined”这个错误几乎都是因为this.x使用了当前类别的原始索引。如果categories数组中大部分是空字符串this.x会返回一个数字或空字符串索引而不是分类名称。你需要用一个独立的映射把每个数据点的索引映射到真实分类名。例如在类别数组生成时同步记录indexMap然后在tooltip.formatter里查表。4.6 性能优化与大数据量场景如果你有几十个分类、加上多个内部系列Marimekko 图的数据点会爆炸式增长。Highcharts 在处理这种情况时建议开启boost模块。同时可以用dataGrouping思路在服务端提前聚合细分数据减小传输体积而不要把所有明细数据都丢给前端。我们项目里最终将原始明细在后台聚合到渠道级再上卷到系列级前端渲染速度提升了至少一倍。4.7 个性化扩展导出与自定义主题一旦 Marimekko 图在企业系统里稳定跑起来接下来一定会被问到导出图片。Highcharts 的导出功能在这里有两个隐藏问题第一是导出的 SVG 中自定义文字和空字符串标签需要额外处理第二是导出大图时chart.width可能与导出尺寸不一致导致柱体比例看起来变化。我的建议是在导出配置里显式指定chart.width为容器宽度禁用scrollablePlotArea的滚动导出改用长图模式并设置背景色。如果必须支持大量数据导出直接调用服务端渲染而不要靠前端 canvas 截图否则文字清晰度很难保证。5. 样式体系与视觉细节优化5.1 配色方案的选型逻辑Marimekko 图的色块布局紧凑配色必须同时满足两类需求一是各系列之间有明显辨识度二是不同分类之间即使相邻也不会因为色相相近而产生误解。我常用的是“双色相 邻近亮度”策略。比如系列一用蓝色系系列二用黄橙色系分类之间靠柱体宽度和内部比例差异区分不再额外添加分类底色。如果公司有设计规范务必直接引用设计规范变量。Highcharts 的colors数组可以配置全局调色板但要注意覆盖率。有时为了强调某一分类我会单独在某个系列的某个数据点上用color覆盖这样在整体统一中制造一个视觉焦点。5.2 柱体圆角与阴影建议Highcharts 支持通过borderRadius给柱体添加圆角。但在 Marimekko 图中同一根柱子内部有好几个色块堆叠如果每个色块都加圆角会产生一种“气泡挤压”的怪异感。我的经验是只在底部柱体的底部两端设置较大圆角顶部柱体的顶部两端设置较小圆角让整体形成一种圆角矩形的观感更柔和。但这个细节在导出和缩放时容易出 bug所以如果没有把握保持直角即可高级感不一定靠圆角来凑。5.3 深色主题适配企业数据大屏经常用深色背景。Marimekko 图在深色背景下会暴露出另一个问题色块间的白色描边过于刺眼反而喧宾夺主。我建议在深色模式下把描边颜色改为比背景稍亮的灰比如#333333或者直接去掉描边靠色相饱和度区分。同时数据标签的文字颜色要改成浅色并加一点文字阴影提升可读性。5.4 动画与交互节奏Highcharts 默认的动画效果是渐进浮现这在 Marimekko 图里看起来有点“廉价”尤其是多分类数据同时出现时容易让读者注意力分散。我通常会把plotOptions.series.animation设置为更短的时长比如 300 毫秒并关闭逐点动画。如果你希望点击某根柱子时高亮显示对应分类的整体占比可以绑定point.events.click事件利用this.series.data或图表内的柱体进行透明度调节。6. 总结与一点个人偏好很多人把 Marimekko 图当作一块硬骨头但其实它本质上是堆叠柱状图的“空间魔术”。一旦理解了类别数组、pointPlacement和堆叠模式三者的协作逻辑后续遇到任何变体比如“100% Marimekko”、“负值 Marimekko”都能很快上手。回到项目本身我最初在一个经营分析大屏里引入 Marimekko 图的真实原因其实是横向对比太多——老板既想看市场份额又想看品类结构一张图能承载的信息量远胜过三张普通柱状图。但我也想提醒你Marimekko 图的信息密度是一把双刃剑如果读者对图表逻辑不熟悉第一眼很可能看不懂。我的做法是在图表上方加一段 50 字以内的引导文案比如“柱子宽度代表渠道份额柱内分段代表新老客贡献”这能大幅降低理解门槛。无论你是在做市场分析报告、财务预算结构分析还是战略规划只要想清楚“宽度表示什么、高度表示什么、颜色表示什么”用 Highcharts 实现一张标准且耐看的 Marimekko 图真的不难。个别小坑频繁踩到等你看完这些记录应该能少走不少弯路。
阅读完成 · 觉得有帮助?