当一张图表里的分类本身是分层的——比如先按大区、再按月份,或者先按年份、再按季度——把两列标签压成一行会让读者难以判断某个数据点究竟属于哪个大区、哪个月份。分层数据的另一面是量级差异:销售额以万元计,同比增长率却只有个位数,两者放在同一条数值轴上时,增长率会被压成一条贴底的直线。多层分类标签和次坐标轴分别解决这两个问题。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成上述操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.XLS for JavaScript。以下示例默认已安装 Spire.XLS 并完成 WebAssembly 模块初始化。
创建含多层分类标签的图表
多层分类标签由分类轴呈现,而分类轴有几层取决于系列的分类标签指向几列数据。测试数据中外层标签已经按大区合并好,因此代码里只需让分类标签同时覆盖外层与内层两列。具体操作步骤如下:
- 将字体与测试数据文件载入 VFS。
- 加载工作簿并取得工作表。
- 添加柱状图,并添加带名称的销售额系列。
- 把分类标签指向大区与月份两列。
- 开启分类轴的多层标签,保存工作簿。
以下为完整的代码示例,演示如何在 React 中创建含多层分类标签的图表:
function App() {
const createMultiLevelChart = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体和测试数据文件载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'MultiLevelChartData.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 加载工作簿,取第一个工作表
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
const sheet = workbook.Worksheets.get(0);
// 添加柱状图
const chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.ColumnClustered });
chart.ChartTitle = "销售额";
chart.Legend.Delete();
// 添加销售额系列,并指定系列名称
const serie = chart.Series.Add({ name: "销售额", serieType: xlsModule.ExcelChartType.ColumnClustered });
serie.Values = sheet.Range.get("C2:C7");
// 把大区和月份两列一并设为分类标签
serie.CategoryLabels = sheet.Range.get("A2:B7");
// 开启多层分类标签,让两层标签各占一行
chart.PrimaryCategoryAxis.MultiLevelLable = true;
// 设置图表在工作表中的位置
chart.LeftColumn = 5;
chart.TopRow = 1;
chart.RightColumn = 14;
// 保存工作簿
const outputFileName = "MultiLevelLabels.xlsx";
workbook.SaveToFile({ fileName: outputFileName });
// 释放 workbook 对象以释放资源
workbook.Dispose();
// 从 VFS 读取结果文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>多层分类标签</h1>
<button onClick={createMultiLevelChart}>Start</button>
</div>
);
}
export default App;
CategoryLabels 指向的列数决定了分类轴有几层,因此绑定单列区域时得到的仍是单层标签;MultiLevelLable 控制的是这些层是否按多层排布。
运行后,创建含多层分类标签的图表的效果:

为图表添加次坐标轴
当同一张图表中的两个系列量级相差悬殊时,共用一条数值轴会让小量级的那个被压成贴底的直线,它的起伏也就无从读起。次坐标轴正是为这类情形准备的:它为该系列单独提供一条数值轴,两套刻度各按自己的量级铺开,互不干涉。做法是把需要独立刻度的系列移出主轴,并把它换成折线——折线不占柱宽,与主轴的柱状系列叠在同一组分类上更易分辨。具体操作步骤如下:
- 将字体与测试数据文件载入 VFS。
- 加载工作簿并取得工作表。
- 添加柱状图,并添加带名称的销售额系列。
- 添加同比增长率系列,类型设为折线图。
- 把同比增长率系列移到次坐标轴,保存工作簿。
以下为完整的代码示例,演示如何在 React 中为图表添加次坐标轴:
function App() {
const addSecondaryAxis = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体和测试数据文件载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'MultiLevelChartData.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 加载工作簿,取第一个工作表
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
const sheet = workbook.Worksheets.get(0);
// 添加柱状图
const chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.ColumnClustered });
chart.ChartTitle = "销售额与同比增长率";
// 添加销售额系列,留在主坐标轴上
const salesSerie = chart.Series.Add({ name: "销售额", serieType: xlsModule.ExcelChartType.ColumnClustered });
salesSerie.Values = sheet.Range.get("C2:C7");
// 把大区和月份两列一并设为分类标签
salesSerie.CategoryLabels = sheet.Range.get("A2:B7");
// 添加同比增长率系列,类型为折线图
const growthSerie = chart.Series.Add({ name: "同比增长率", serieType: xlsModule.ExcelChartType.Line });
growthSerie.Values = sheet.Range.get("D2:D7");
// 把同比增长率系列移到次坐标轴,让它按百分比刻度单独绘制
growthSerie.UsePrimaryAxis = false;
// 开启多层分类标签
chart.PrimaryCategoryAxis.MultiLevelLable = true;
// 设置图表在工作表中的位置
chart.LeftColumn = 5;
chart.TopRow = 1;
chart.RightColumn = 14;
// 保存工作簿
const outputFileName = "SecondaryAxis.xlsx";
workbook.SaveToFile({ fileName: outputFileName });
// 释放 workbook 对象以释放资源
workbook.Dispose();
// 从 VFS 读取结果文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>次坐标轴</h1>
<button onClick={addSecondaryAxis}>Start</button>
</div>
);
}
export default App;
UsePrimaryAxis = false 只影响被设置的那个系列,其余系列仍留在主轴;图表会因此多出一组数值轴与分类轴,形成两个刻度区间。Series.Add 可以一并指定系列名称,图例中显示的便是这里传入的名字,而不是自动生成的「系列 1」。
运行后,为图表添加次坐标轴的效果:

常见问题
为什么分类轴只显示了一层标签?
原因:分类轴的层数由 CategoryLabels 指向的区域决定。若绑定的是单列区域(例如 B2:B7),区域里只有一层分类信息,此时把 PrimaryCategoryAxis.MultiLevelLable 设为 true 也只会得到一层标签——该属性控制的是多层标签是否展开显示,并不会补出一层数据。
解决:把 CategoryLabels 指向包含外层标签的多列区域,外层标签所在的单元格则需要在数据中纵向合并:
// 让分类标签覆盖外层与内层两列;外层标签的单元格需纵向合并
serie.CategoryLabels = sheet.Range.get("A2:B7");
为什么两条数值轴的刻度不一样?
原因:主、次两条数值轴各自独立计算刻度,互不相干。PrimaryValueAxis 上的 MinValue、MaxValue、MajorUnit 也只作用于主轴,改它不会影响次轴——两个系列量级悬殊时,次轴自动算出的区间往往并不合适。
解决:用 chart.SecondaryValueAxis 单独指定次轴的刻度:
// 次轴使用 0–20 的刻度,每 5 为一个主刻度
chart.SecondaryValueAxis.MinValue = 0;
chart.SecondaryValueAxis.MaxValue = 20;
chart.SecondaryValueAxis.MajorUnit = 5;
刻度要在系列已经移到次轴之后再设:图表里还没有系列使用次轴时,赋值会被接受,但不会写进文件。
获取免费许可证
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。









