一张图只画一种图表类型,是 Excel 的默认行为,不是必须遵守的规则。同一份报表通常不止一种读法,适合的图形也不相同:看高低用柱形,看走势用折线,看连续累计用面积。自定义(组合)图表解决的正是这个问题:在同一张图表里,每个系列可以单独指定图表类型,如何搭配取决于需要并排回答哪几个问题,并没有固定搭配。柱形配折线是实践中用得最广的一种,因为它能同时容纳「看高低」和「看走势」两种读法。本文的示例就采用这种搭配:销售额画成柱形,目标值画成折线,读者看的是折线相对柱顶的位置。如果拆成两张图,必须先对齐两条横轴才能比较;合成一张,共用的分类轴已经把月份对齐了。报表里还有一类系列放不进主数据区,只能用手工添加的方式加进来。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成这些操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.XLS for JavaScript。以下示例默认已安装 Spire.XLS 并完成 WebAssembly 模块初始化。
组合柱形图与折线图
搭配方式并不固定:一张图里放哪几种类型,取决于需要并排回答哪几个问题,柱形配折线只是其中用得最广的一种。这个例子里,销售额适合用柱形表达,读者一眼就能比出高低;目标值适合用折线表达,读者看的是折线相对柱顶的位置。两个系列分别指定类型之后,一张图里就有了两种读法,共用的分类轴让月份严格对齐,不必在两张图之间来回对照。换成条形配折线、柱形配面积,做法完全一样。具体操作步骤如下:
- 把字体与测试数据文件载入 VFS。
- 用
workbook.LoadFromFile加载工作簿,取第一个工作表。 - 用
sheet.Charts.Add()新建图表,把chart.DataRange指向A1:C7:第一行成为系列名,第一列成为分类标签,后两列各成一个系列。 - 把
chart.SeriesDataFromRange设为false,让每个系列沿用自己绑定的取值。 - 用
chart.LeftColumn、chart.TopRow、chart.RightColumn、chart.BottomRow确定图表在表中的位置。 - 给
chart.Series.get(0).SerieType与chart.Series.get(1).SerieType分别指定ExcelChartType.ColumnClustered与ExcelChartType.Line。每个系列的类型都是这样单独指定的,配哪几种由需求决定。 - 用
chart.ChartTitle设置图表标题,再用workbook.SaveToFile保存工作簿。
以下为完整的代码示例,演示如何在 React 中组合柱形图与折线图:
function App() {
const combineColumnAndLine = 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 = 'SalesReport.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);
// 新建图表,数据区域取 A1:C7:第一列成为分类标签,后两列各成一个系列
const chart = sheet.Charts.Add();
chart.DataRange = sheet.Range.get('A1:C7');
chart.SeriesDataFromRange = false;
// 摆放图表位置
chart.LeftColumn = 1;
chart.TopRow = 10;
chart.RightColumn = 9;
chart.BottomRow = 26;
// 两个系列分别指定图表类型:销售额画成柱形,目标销售额画成折线
chart.Series.get(0).SerieType = xlsModule.ExcelChartType.ColumnClustered;
chart.Series.get(1).SerieType = xlsModule.ExcelChartType.Line;
chart.ChartTitle = '销售额与目标销售额';
// 保存工作簿
const outputFileName = 'CombineColumnAndLine.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 id="combine-column-and-line" onClick={combineColumnAndLine}>组合柱形图与折线图</button>
</div>
);
}
export default App;
运行后,柱形图与折线图组合的效果:
手工添加系列
DataRange 只能取一个连续的矩形区域,这是绑定数据时的硬限制。报表里常见两种情形:主数据在 A 到 C 列,参考数据隔着一列空白另起一列;或者要画的值是算出来的,根本不在单元格里。这两种情况都没法一次绑定,只能按系列逐个添加。手工添加的系列不依赖 DataRange:它的区域可以任意指定,图表类型也能单独设置,组合图的规则完全适用。本例把去年同期销售额加为第二个柱形系列,与销售额并排成对,目标折线横穿其上。具体操作步骤如下:
- 把字体与测试数据文件载入 VFS,加载工作簿并取第一个工作表。
- 用
sheet.Charts.Add()新建图表,chart.DataRange只指向主数据区A1:C7。 - 给主数据区的两个系列分别指定
ExcelChartType.ColumnClustered与ExcelChartType.Line。 - 用
chart.Series.Add({ name, serieType })新增一个系列,系列名与图表类型一次给出。 - 用
series.CategoryLabels与series.Values分别绑定分类标签和取值,两者都指向数据实际所在的区域。 - 用
chart.ChartTitle设置图表标题,再用workbook.SaveToFile保存工作簿。
以下为完整的代码示例,演示如何在 React 中手工添加系列:
function App() {
const addSeriesManually = 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 = 'SalesReport.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);
// 新建图表,数据区域只取主数据区 A1:C7
const chart = sheet.Charts.Add();
chart.DataRange = sheet.Range.get('A1:C7');
chart.SeriesDataFromRange = false;
// 摆放图表位置
chart.LeftColumn = 1;
chart.TopRow = 10;
chart.RightColumn = 9;
chart.BottomRow = 26;
// 主数据区的两个系列:销售额柱形,目标销售额折线
chart.Series.get(0).SerieType = xlsModule.ExcelChartType.ColumnClustered;
chart.Series.get(1).SerieType = xlsModule.ExcelChartType.Line;
// 去年同期值在 E 列,与主数据区隔着一个空列,一次绑不进数据区域,手工添加一个系列。
// 手工添加的系列同样可以指定自己的图表类型,这里与销售额并排成柱形
const lastYear = chart.Series.Add({ name: '去年同期销售额(万元)', serieType: xlsModule.ExcelChartType.ColumnClustered });
lastYear.CategoryLabels = sheet.Range.get('A2:A7');
lastYear.Values = sheet.Range.get('E2:E7');
chart.ChartTitle = '销售额、目标销售额与去年同期销售额';
// 保存工作簿
const outputFileName = 'AddSeriesManually.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 id="add-series-manually" onClick={addSeriesManually}>手工添加系列</button>
</div>
);
}
export default App;
运行后,手工添加系列的效果:
常见问题
手工添加的系列能和 DataRange 同时使用吗
原因:可以,两者互不干扰。DataRange 只生成主数据区各列对应的系列;手工添加的系列自带 Values 与 CategoryLabels,和 DataRange 覆盖的范围无关,也不必落在同一个矩形里。上面第二个例子里,图表就同时包含主数据区的两个系列和手工添加的去年同期系列。
解决:把 DataRange 指向主数据区,再按需用 chart.Series.Add({ name, serieType }) 增补系列。需要注意的是,手工添加的系列不会自动沿用主数据区的分类标签,CategoryLabels 必须显式赋值;指向同一列时,各系列就共用一套横轴。
// 主数据区的两个系列由 DataRange 生成,手工系列另行绑定
chart.DataRange = sheet.Range.get('A1:C7');
const lastYear = chart.Series.Add({ name: '去年同期销售额(万元)', serieType: xlsModule.ExcelChartType.ColumnClustered });
lastYear.CategoryLabels = sheet.Range.get('A2:A7');
lastYear.Values = sheet.Range.get('E2:E7');
Series.Add 不指定图表类型会得到什么类型
原因:会得到图表自身的类型。示例里 sheet.Charts.Add() 没有传参数,建出的图表默认就是 ExcelChartType.ColumnClustered,新系列也沿用这一类型。
解决:如果希望新系列也参与组合,可以在添加时用 serieType 指定,也可以在添加之后修改 SerieType:
// 添加时一次给出
const serie = chart.Series.Add({ name: '去年同期销售额(万元)', serieType: xlsModule.ExcelChartType.ColumnClustered });
// 或者添加之后再改
serie.SerieType = xlsModule.ExcelChartType.Line;
获取免费许可证
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。









