工作表里的数据很少是一块齐整的矩形。明细之间常夹着小计行、分组标题或说明行,而图表要画的恰恰是明细本身;另有一些数字根本不落在单元格里,它们由接口返回、由代码算出,或者只是本次要展示的一组目标值。这两种情形都会让「先框选一块区域、再插入图表」的做法失效。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成此操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.XLS for JavaScript。以下示例默认已安装 Spire.XLS 并完成 WebAssembly 模块初始化。
用不连续数据源创建图表
小计行、分组标题、隔行的说明文字,都会把一张明细表切成若干段。插入图表时若直接框选整列,这些行与明细行并无区别,一并会被画成柱子:示例数据里每两个季度之后跟着一行小计,整列取数会让柱子从 6 根变成 9 根,每个区域的高度也凭空翻了约一倍。为系列指明数据来源时,允许把几段互不相邻的区域拼成一串引用,图表只取这些段落里的行,中间被跳过的部分不参与绘制。这样一来,明细表不必为了作图而重排,小计行也可以留在原位。具体操作步骤如下:
- 将字体与测试数据文件载入 VFS。
- 用
workbook.LoadFromFile加载工作簿,以workbook.Worksheets.get(0)取得工作表。 - 用
sheet.Charts.Add添加柱状图,并把chart.SeriesDataFromRange设为false,声明系列的数据由代码逐段给出,不取自chart.DataRange。 - 用
chart.Series.Add添加系列,把serie.Name设为表头单元格的Value。 - 用
Range.get取到每个区域的季度行,再以AddCombinedRange依次拼接,分别得到分类标签与数值两串引用。 - 对第二个系列重复同样的拼接,使两个系列共用同一组分类标签,最后保存工作簿。
以下为完整的代码示例,演示如何在 React 中用不连续数据源创建图表:
function App() {
const chartFromDiscontinuousData = 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 = 'ChartSourceData.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.ChartTitleArea.Size = 12;
chart.SeriesDataFromRange = false;
// 设置图表在工作表中的位置
chart.TopRow = 12;
chart.BottomRow = 28;
chart.LeftColumn = 1;
chart.RightColumn = 10;
// 把三个区域的季度行拼成一段引用,中间的小计行被跳过
const categoryLabels = sheet.Range.get("A2:A3")
.AddCombinedRange(sheet.Range.get("A5:A6"))
.AddCombinedRange(sheet.Range.get("A8:A9"));
// 线上销售系列:名称取自表头,数值同样由三段区域拼成
const onlineSerie = chart.Series.Add();
onlineSerie.Name = sheet.Range.get("B1").Value;
onlineSerie.CategoryLabels = categoryLabels;
onlineSerie.Values = sheet.Range.get("B2:B3")
.AddCombinedRange(sheet.Range.get("B5:B6"))
.AddCombinedRange(sheet.Range.get("B8:B9"));
// 门店销售系列:与上一系列共用同一组分类标签
const storeSerie = chart.Series.Add();
storeSerie.Name = sheet.Range.get("C1").Value;
storeSerie.CategoryLabels = categoryLabels;
storeSerie.Values = sheet.Range.get("C2:C3")
.AddCombinedRange(sheet.Range.get("C5:C6"))
.AddCombinedRange(sheet.Range.get("C8:C9"));
// 保存工作簿
const outputFileName = "DiscontinuousData.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="discontinuous-data" onClick={chartFromDiscontinuousData}>用不连续数据源创建图表</button>
</div>
);
}
export default App;
运行后,用不连续数据源创建图表的效果:

用无数据源创建图表
图表的数据通常取自单元格,但并非总是如此:目标值可能写在配置里,汇总数字由接口返回,或者只是演示用的一组常数。这些数字既然没有落进工作表,图表也就无区域可指。系列允许把数值直接写在自身内部,图表因此可以脱离工作表的数据独立存在;数值随代码改动,重新生成即可,不必为了作图在工作表里再维护一份副本。由于数值不来自单元格,图表也不带任何文字分类,横坐标按 1、2、3 的顺序编号。具体操作步骤如下:
- 将字体与测试数据文件载入 VFS。
- 用
workbook.LoadFromFile加载工作簿,以workbook.Worksheets.get(0)取得工作表;新图表仍要挂在一张已有工作表上。 - 用
sheet.Charts.Add添加柱状图,并设置图表的标题与位置。 - 用
chart.Series.Add添加系列,把serie.Name设成系列名。 - 用
xlsModule.Int32.Create把每个数值装箱,按顺序放进serie.EnteredDirectlyValues,最后保存工作簿。
以下为完整的代码示例,演示如何在 React 中用无数据源创建图表:
function App() {
const chartWithoutSourceData = 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 = 'ChartSourceData.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.ChartTitleArea.Size = 12;
// 设置图表在工作表中的位置
chart.TopRow = 12;
chart.BottomRow = 28;
chart.LeftColumn = 1;
chart.RightColumn = 10;
// 添加系列,把数值逐个写入 EnteredDirectlyValues
// 分类标签不另行给出,图表按 1、2、3 的顺序编号
const targetSerie = chart.Series.Add();
targetSerie.Name = "销售目标";
targetSerie.EnteredDirectlyValues = [
xlsModule.Int32.Create(260),
xlsModule.Int32.Create(210),
xlsModule.Int32.Create(190),
];
// 保存工作簿
const outputFileName = "NoSourceData.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="no-source-data" onClick={chartWithoutSourceData}>用无数据源创建图表</button>
</div>
);
}
export default App;
运行后,用无数据源创建图表的效果:

常见问题
系列名称必须指向单元格吗
原因:serie.Name 接受一段普通文字,赋值后即为系列名,图例照此显示,保存后落在系列自身而不是某个单元格的引用里;取表头单元格的 Value 只是另一种取法,并非必需。数值与分类标签仍需从区域取得,只有名称可以脱开单元格。
解决:直接把文字赋给 serie.Name 即可:
const serie = chart.Series.Add();
serie.Name = "销售目标";
serie.CategoryLabels = sheet.Range.get("A2:A3").AddCombinedRange(sheet.Range.get("A5:A6")).AddCombinedRange(sheet.Range.get("A8:A9"));
serie.Values = sheet.Range.get("B2:B3").AddCombinedRange(sheet.Range.get("B5:B6")).AddCombinedRange(sheet.Range.get("B8:B9"));
两个系列共用一组分类标签,要各设一遍吗
原因:CategoryLabels 属于系列自身,添加第二个系列时不会自动沿用上一个系列的设置,需要再赋一次。
解决:拼接只做一次,把结果存进变量后分别赋给各系列,不必重复写 AddCombinedRange:
const labels = sheet.Range.get("A2:A3")
.AddCombinedRange(sheet.Range.get("A5:A6"))
.AddCombinedRange(sheet.Range.get("A8:A9"));
onlineSerie.CategoryLabels = labels;
storeSerie.CategoryLabels = labels;
获取免费许可证
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。







