Highcharts增强型分组层级X轴标签:功能名称与实现咨询
Highcharts 实现多层级分组X轴标签
嘿,你说的这个功能在Highcharts里有专门的叫法,一般叫多层级轴(Multi-level Axes)或者分组轴标签(Grouped Axis Labels),官方是完全支持的,估计之前是关键词没找对才没搜到~
我给你拆解下实现思路和具体代码:
核心实现逻辑
Highcharts本身支持通过嵌套类别配合自定义渲染来实现三层标签结构,主要分两步:
- 把X轴的类别设置成嵌套数组,包含「月份→日期分段→具体日期」的层级
- 利用Highcharts的渲染API(
chart.renderer)在轴的上方绘制上层和中层的分组标签,同时控制底层只显示具体日期
完整代码示例
Highcharts.chart('container', { chart: { type: 'column', marginTop: 80 // 预留空间给上层标签,避免和标题/图表内容重叠 }, xAxis: { categories: [ // 5月1-10日 ['2024年5月', '1-10日', '5/1'], ['2024年5月', '1-10日', '5/2'], ['2024年5月', '1-10日', '5/3'], ['2024年5月', '1-10日', '5/4'], ['2024年5月', '1-10日', '5/5'], ['2024年5月', '1-10日', '5/6'], ['2024年5月', '1-10日', '5/7'], ['2024年5月', '1-10日', '5/8'], ['2024年5月', '1-10日', '5/9'], ['2024年5月', '1-10日', '5/10'], // 5月11-20日 ['2024年5月', '11-20日', '5/11'], ['2024年5月', '11-20日', '5/12'], ['2024年5月', '11-20日', '5/13'], ['2024年5月', '11-20日', '5/14'], ['2024年5月', '11-20日', '5/15'], ['2024年5月', '11-20日', '5/16'], ['2024年5月', '11-20日', '5/17'], ['2024年5月', '11-20日', '5/18'], ['2024年5月', '11-20日', '5/19'], ['2024年5月', '11-20日', '5/20'], // 5月21-31日 ['2024年5月', '21-31日', '5/21'], ['2024年5月', '21-31日', '5/22'], ['2024年5月', '21-31日', '5/23'], ['2024年5月', '21-31日', '5/24'], ['2024年5月', '21-31日', '5/25'], ['2024年5月', '21-31日', '5/26'], ['2024年5月', '21-31日', '5/27'], ['2024年5月', '21-31日', '5/28'], ['2024年5月', '21-31日', '5/29'], ['2024年5月', '21-31日', '5/30'], ['2024年5月', '21-31日', '5/31'], // 6月1-10日 ['2024年6月', '1-10日', '6/1'], ['2024年6月', '1-10日', '6/2'] ], labels: { useHTML: true, // 只显示最底层的具体日期 formatter: function() { return this.value[2]; } }, // 图表加载完成后绘制上层分组标签 events: { load: function() { var chart = this.chart, xAxis = this, categories = xAxis.categories, tickPositions = xAxis.tickPositions; let currentMonth = '', currentSegment = '', monthStartIndex = 0, segmentStartIndex = 0; // 遍历所有类别,找到每个分组的起始和结束位置 categories.forEach((cat, index) => { // 处理月份分组 if (cat[0] !== currentMonth) { // 如果不是第一个分组,先绘制上一个月份的标签 if (currentMonth) { drawGroupLabel( currentMonth, xAxis.toPixels(tickPositions[monthStartIndex]), xAxis.toPixels(tickPositions[index - 1]), xAxis.top - 40, chart ); } currentMonth = cat[0]; monthStartIndex = index; } // 处理日期分段分组 if (cat[1] !== currentSegment) { if (currentSegment) { drawGroupLabel( currentSegment, xAxis.toPixels(tickPositions[segmentStartIndex]), xAxis.toPixels(tickPositions[index - 1]), xAxis.top - 15, chart ); } currentSegment = cat[1]; segmentStartIndex = index; } // 处理最后一个分组 if (index === categories.length - 1) { drawGroupLabel(currentMonth, xAxis.toPixels(tickPositions[monthStartIndex]), xAxis.toPixels(tickPositions[index]), xAxis.top - 40, chart); drawGroupLabel(currentSegment, xAxis.toPixels(tickPositions[segmentStartIndex]), xAxis.toPixels(tickPositions[index]), xAxis.top - 15, chart); } }); } } }, series: [{ name: '业务数据', data: [ 12,19,15,21,18,25,22,17,14,20, 23,16,19,24,21,18,26,23,19,15, 22,27,24,20,16,23,28,25,21,17,24, 20,18 ] }] }); // 封装绘制分组标签的函数,方便自定义样式 function drawGroupLabel(text, startX, endX, y, chart) { // 计算分组的中心位置,让标签居中显示 const centerX = (startX + endX) / 2; // 绘制标签文本 chart.renderer.text(text, centerX, y) .attr({ align: 'center', fontSize: '14px', fontWeight: 'bold', fill: '#333', zIndex: 10 }) .add(); // 可选:绘制分组分隔线,增强层级感 chart.renderer.path(['M', endX + 2, y + 15, 'L', endX + 2, chart.plotTop + chart.plotHeight]) .attr({ stroke: '#ccc', strokeWidth: 1, zIndex: 5 }) .add(); }
额外说明
- 你可以通过调整
marginTop来预留足够的标签空间,避免和图表标题重叠 - 封装的
drawGroupLabel函数可以自定义标签的字体大小、颜色、位置,也可以选择是否添加分隔线 - 官方文档里搜索「Grouped Categories」或者「Multi-level Axes」就能找到官方示例,你可以基于这些示例进一步调整细节
内容的提问来源于stack exchange,提问作者Cyrus Downey




