Chart (16)
Add a Data Table to an Excel Chart with JavaScript in React
2026-10-19 09:00:23 Written by liu taliaA chart makes a trend visible at a glance, yet it does not give the exact numbers: the height of a column has to be read against the value axis, and the estimate drifts as more series crowd in. A data table lists the category names, series names and values directly below the chart, so readers do not have to go back to the source cells or add a data label to every point. As part of the chart, the table scales and moves with it and stays in place when the chart is exported as an image or printed. Spire.XLS for JavaScript performs these operations in the browser through WebAssembly; the input and output files are managed by a virtual file system (VFS), with no backend service required.
This article covers two feature points:
For installation and project setup, see Integrating Spire.XLS for JavaScript in a React Project. The examples below assume Spire.XLS is installed and the WebAssembly module has been initialized.
Add a data table to the chart
Adding a data table to a chart solves the problem of numbers living apart from the picture. A default column chart only draws the columns; the values stay in the source worksheet and the reader has to move between the two. With several series, a data table sits directly under the category axis, one row per series, its columns aligned with the categories and its rows matching the colours in the legend, so the trend and the figures appear in one place. The sample workbook already holds a quarterly sales table and a column chart; what it lacks is the table below the chart.
Turning the data table on:
| Syntax | Result |
|---|---|
workbook.Worksheets.get(0) |
Takes the first worksheet of the workbook |
sheet.Charts.get(0) |
Takes the first chart on the worksheet |
chart.HasDataTable = true |
Shows a data table below the chart with the categories, series names and values |
workbook.SaveToFile |
Saves the workbook with its data table into the VFS |
The steps are:
- Load the font file and the test data file into the VFS with
window.spire.FetchFileToVFS. - Create a workbook with
new Workbook(), load the input file withworkbook.LoadFromFile, and take the first worksheet withworkbook.Worksheets.get(0). - Take the first chart on the worksheet with
sheet.Charts.get(0). - Set
chart.HasDataTabletotrue, which adds the data table below the chart. - Save the workbook with
workbook.SaveToFileand dispose of it withworkbook.Disposeto free resources.
The full code example below shows how to add a data table to an Excel chart in React:
function App() {
const addDataTable = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check that the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and the test data file into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'ChartSales.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook and take the first worksheet and its chart
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
const sheet = workbook.Worksheets.get(0);
const chart = sheet.Charts.get(0);
// Turn on the chart's data table, which lists the categories and series values below the plot
chart.HasDataTable = true;
// Save the workbook
const outputFileName = 'AddDataTable.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// Dispose of the workbook to release its resources
workbook.Dispose();
// Read the result back out of the VFS and trigger the download
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>Add a Data Table to an Excel Chart</h1>
<button id="add-data-table" onClick={addDataTable}>Add a Data Table to the Chart</button>
</div>
);
}
export default App;
The sales report after turning on the chart data table:

Adjust the data table borders, legend keys and text formatting
Adjusting the data table borders, legend keys and text formatting keeps the table readable when there are many series and lets it blend into the layout of a report. A newly opened data table carries an outer frame and both horizontal and vertical separators; with three rows of values stacked together it looks like a dense grid. Dropping the outer frame and the vertical lines leaves only the horizontal ones, so the eye follows a row straight to its numbers without being cut off by vertical rules. Turning on the legend keys then puts a colour swatch of the matching series in front of every series name, so readers no longer have to compare each row with the legend. By default the text draws on the chart's own settings -- ten points, black, not bold -- which does not stand out against dark columns and gridlines; dropping the size to nine points fits more numbers on a line, while bolding the text and switching it to a deeper blue ties it to the title and the header row. These settings apply to the data table alone; the chart title, axis labels and legend keep their own fonts.
Borders, legend keys and text formatting:
| Syntax | Result |
|---|---|
chart.DataTable |
Takes the data table object of the chart |
table.HasBorders = false |
Removes the outer frame of the data table |
table.HasVertBorder = false |
Removes the vertical separators between columns |
table.HasHorzBorder = true |
Keeps the horizontal separators between rows |
table.ShowSeriesKeys = true |
Shows a legend key in front of every series name |
table.FontName = 'Arial' |
Sets the data table text to Arial |
table.Size = 9 |
Sets the data table text to 9 points |
table.IsBold = true |
Bolds the data table text |
table.Color = xlsModule.Color.FromArgb(255, 28, 68, 118) |
Sets the data table text to a deep blue |
The steps are:
- Load the font and the test data file into the VFS with
window.spire.FetchFileToVFS, then load the workbook withworkbook.LoadFromFile. - Take the first worksheet with
workbook.Worksheets.get(0)and the first chart withsheet.Charts.get(0). - Set
chart.HasDataTabletotrueto open the data table, then take the object withchart.DataTable. - Set
table.HasBordersandtable.HasVertBordertofalseandtable.HasHorzBordertotrue, so only the horizontal lines remain. - Set
table.ShowSeriesKeystotrueto show the legend key before each series name. - Set
table.FontName,table.Sizeandtable.IsBoldto choose the typeface, the size and the bold weight. - Build a deep blue with
xlsModule.Color.FromArgband assign it totable.Color. - Save the workbook with
workbook.SaveToFileand dispose of it withworkbook.Disposeto free resources.
The full code example below shows how to adjust the data table borders, legend keys and text formatting in React:
function App() {
const customizeDataTable = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check that the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and the test data file into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'ChartSales.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook and take the first worksheet and its chart
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
const sheet = workbook.Worksheets.get(0);
const chart = sheet.Charts.get(0);
// Turn on the data table and take its object
chart.HasDataTable = true;
const table = chart.DataTable;
// Drop the outer frame and the vertical lines, keeping only the horizontal ones
table.HasBorders = false;
table.HasVertBorder = false;
table.HasHorzBorder = true;
// Show the legend key in front of each series name
table.ShowSeriesKeys = true;
// Set the data table font, size, bold weight and text colour
table.FontName = 'Arial';
table.Size = 9;
table.IsBold = true;
table.Color = xlsModule.Color.FromArgb(255, 28, 68, 118);
// Save the workbook
const outputFileName = 'DataTableFormatting.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// Dispose of the workbook to release its resources
workbook.Dispose();
// Read the result back out of the VFS and trigger the download
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>Add a Data Table to an Excel Chart</h1>
<button id="data-table-formatting" onClick={customizeDataTable}>Adjust the Borders, Legend Keys and Text</button>
</div>
);
}
export default App;
The data table after adjusting the borders, legend keys and text formatting:

FAQ
What is the difference between a data table and data labels?
Cause: Data labels sit next to each data point and show only that point's value. A data table sits below the chart, one row per series, and gives the category names and all the values together.
Solution: Use data labels when each point needs a callout without a table taking up space; use a data table when readers should take in a whole set of values at once. Both can be present on the same chart and turned on or off as needed.
How do I remove the data table from a chart?
Cause: The data table is controlled by a single switch on the chart; once it is on, it does not disappear with a change of source data or chart type.
Solution: Set chart.HasDataTable to false and save the workbook; the data table below the chart is removed.
How can the data table use a different font from the rest of the chart?
Cause: The data table carries its own text formatting. Until it is set, it follows the rest of the chart's text; once set, only the data table changes.
Solution: Set FontName, Size, IsBold and Color on chart.DataTable. These properties act on the data table only and leave the chart title, axis labels and legend untouched.
What is the legend key in a data table?
Cause: A legend key is the small colour swatch shown in front of a series name, matching the series' marker in the legend. It is off by default, so the data table shows plain text.
Solution: Set table.ShowSeriesKeys to true; a swatch then appears in front of every series name, so a reader can match a row of the data table with the legend directly.
Get a Free License
Spire.XLS for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.
Create a Custom (Combination) Chart in React with JavaScript
2026-10-10 01:21:27 Written by liu taliaDrawing a single chart type per figure is Excel's default behaviour, not a rule anyone has to keep. A report usually has more than one thing to say, and each reading has a shape that suits it: height suits columns, movement suits a line, a running total suits an area. A custom (combination) chart exists for exactly this: within one chart, each series can be given a chart type of its own, and which types are put together is decided by the questions the figure has to answer side by side -- there is no fixed pairing. Columns with a line is the pairing most widely used in practice, because it accommodates both readings at once. The example below takes that pair, drawing sales as columns and the target as a line, so that what the reader compares is the line's position against the column tops. Two separate charts would need their category axes aligned first, before any comparison could be made; one chart, with a category axis the series share, keeps the months strictly aligned. A report also often holds a series that cannot be brought into the main data range, and such a series has to be added by hand. Spire.XLS for JavaScript performs all of this in the browser on top of WebAssembly, managing input and output files through a virtual file system (VFS), with no backend service required.
This article covers two key features:
For installation and project setup, see Integrating Spire.XLS for JavaScript in a React Project. The examples below assume Spire.XLS is installed and the WebAssembly module has been initialised.
Combine a column chart with a line chart
A pairing is not fixed: which types share one figure depends on which questions have to be answered side by side, and columns with a line is only the most widely used of them. In this example sales are best expressed as columns, where heights are compared at a glance, and the target is best expressed as a line, where what the reader compares is its position against the column tops. Once the two series have a type each, one figure carries both readings, and the category axis they share keeps the months strictly aligned, so no comparison across two figures is needed. Substituting clustered bars with a line, or columns with an area, works the same way. The steps are:
- Load the font and the test data file into the VFS.
- Load the workbook with
workbook.LoadFromFileand take the first worksheet. - Add a chart with
sheet.Charts.Add()and pointchart.DataRangeatA1:C7: the first row supplies the series names, the first column the category labels, and the next two columns become one series each. - Set
chart.SeriesDataFromRangetofalseso that each series keeps the values bound to it. - Place the chart on the sheet with
chart.LeftColumn,chart.TopRow,chart.RightColumnandchart.BottomRow. - Give
chart.Series.get(0).SerieTypeandchart.Series.get(1).SerieTypethe valuesExcelChartType.ColumnClusteredandExcelChartType.Line. Every series names its own type this way, so which types go together is yours to choose. - Set the title with
chart.ChartTitle, then save the workbook withworkbook.SaveToFile.
The complete code example below shows how to combine a column chart with a line chart in React:
function App() {
const combineColumnAndLine = async () => {
// get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// check that the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// load the font and the test data file into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'SalesReport.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// load the workbook
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// take the first worksheet
const sheet = workbook.Worksheets.get(0);
// add a chart and bind its data range to A1:C7: the first column becomes the category labels, the next two become one series each
const chart = sheet.Charts.Add();
chart.DataRange = sheet.Range.get('A1:C7');
chart.SeriesDataFromRange = false;
// place the chart
chart.LeftColumn = 1;
chart.TopRow = 10;
chart.RightColumn = 9;
chart.BottomRow = 26;
// give the two series a chart type each: sales drawn as columns, target sales drawn as a line
chart.Series.get(0).SerieType = xlsModule.ExcelChartType.ColumnClustered;
chart.Series.get(1).SerieType = xlsModule.ExcelChartType.Line;
chart.ChartTitle = 'Sales and Target Sales';
// save the workbook
const outputFileName = 'CombineColumnAndLine.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// dispose of the workbook to free resources
workbook.Dispose();
// read the result file from the VFS and start the download
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>Create a Custom (Combination) Chart</h1>
<button id="combine-column-and-line" onClick={combineColumnAndLine}>Combine a column chart with a line chart</button>
</div>
);
}
export default App;
After running, the effect of combining a column chart with a line chart:
Add a series by hand
DataRange can only ever be one continuous rectangle, and that is a hard limit on how data gets bound. Reports commonly present two such cases: the main data occupies columns A to C while the reference values start a column further along, with an empty column in between; or the values to be plotted are derived by calculation and occupy no cell at all. Neither case can be bound in a single call, so the chart has to be built series by series instead. A hand-added series does not depend on DataRange: its range can point anywhere, and it still takes a chart type of its own, so the rules of a combination chart apply to it in full. Here last year's sales are added as a second column series, paired alongside the current year, with the target line running across both. The steps are:
- Load the font and the test data file into the VFS, load the workbook and take the first worksheet.
- Add a chart with
sheet.Charts.Add()and pointchart.DataRangeat the main block alone,A1:C7. - Give the two series of that block
ExcelChartType.ColumnClusteredandExcelChartType.Line. - Add a series with
chart.Series.Add({ name, serieType }), which takes the series name and its chart type in one call. - Bind the category labels and the values with
series.CategoryLabelsandseries.Values, each pointing at the range the data actually occupies. - Set the title with
chart.ChartTitle, then save the workbook withworkbook.SaveToFile.
The complete code example below shows how to add a series by hand in React:
function App() {
const addSeriesManually = async () => {
// get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// check that the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// load the font and the test data file into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'SalesReport.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// load the workbook
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// take the first worksheet
const sheet = workbook.Worksheets.get(0);
// add a chart and bind its data range to the main block, A1:C7
const chart = sheet.Charts.Add();
chart.DataRange = sheet.Range.get('A1:C7');
chart.SeriesDataFromRange = false;
// place the chart
chart.LeftColumn = 1;
chart.TopRow = 10;
chart.RightColumn = 9;
chart.BottomRow = 26;
// the two series of the main block: sales as columns, target sales as a line
chart.Series.get(0).SerieType = xlsModule.ExcelChartType.ColumnClustered;
chart.Series.get(1).SerieType = xlsModule.ExcelChartType.Line;
// last year's figures sit in column E, one empty column away from the main block, so they cannot come along with the data range; add a series by hand instead.
// a hand-added series takes a chart type of its own too; here it is drawn as columns alongside sales
const lastYear = chart.Series.Add({ name: 'Last Year Sales (K)', serieType: xlsModule.ExcelChartType.ColumnClustered });
lastYear.CategoryLabels = sheet.Range.get('A2:A7');
lastYear.Values = sheet.Range.get('E2:E7');
chart.ChartTitle = 'Sales, Target Sales and Last Year Sales';
// save the workbook
const outputFileName = 'AddSeriesManually.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// dispose of the workbook to free resources
workbook.Dispose();
// read the result file from the VFS and start the download
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>Create a Custom (Combination) Chart</h1>
<button id="add-series-manually" onClick={addSeriesManually}>Add a series by hand</button>
</div>
);
}
export default App;
After running, the effect of adding a series by hand:
FAQ
Can a hand-added series be used alongside DataRange?
Cause: Yes, and the two do not interfere. DataRange produces only the series that correspond to the columns of the main block; a hand-added series carries its own Values and CategoryLabels, which are unrelated to the range DataRange covers and need not lie within the same rectangle. In the second example above the chart holds both series of the main block and the hand-added last-year series at once.
Solution: Point DataRange at the main block, then add each extra series with chart.Series.Add({ name, serieType }). Note that a hand-added series does not inherit the category labels of the main block: CategoryLabels has to be assigned explicitly, and if it is pointed at the same column the series share one category axis.
// the two series of the main block come from DataRange; the hand-added one is bound separately
chart.DataRange = sheet.Range.get('A1:C7');
const lastYear = chart.Series.Add({ name: 'Last Year Sales (K)', serieType: xlsModule.ExcelChartType.ColumnClustered });
lastYear.CategoryLabels = sheet.Range.get('A2:A7');
lastYear.Values = sheet.Range.get('E2:E7');
What type does a series added without serieType get?
Cause: It takes the chart's own type. sheet.Charts.Add() is called with no arguments in the example, and the chart it builds defaults to ExcelChartType.ColumnClustered, which is the type a new series keeps as well.
Solution: For a new series to join a combination chart, name its type at the call, or change SerieType after adding it:
// named at the call
const serie = chart.Series.Add({ name: 'Last Year Sales (K)', serieType: xlsModule.ExcelChartType.ColumnClustered });
// or changed after adding
serie.SerieType = xlsModule.ExcelChartType.Line;
Get a Free License
Spire.XLS for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.
Insert Pictures and Text Boxes in a Chart in React with JavaScript
2026-09-30 09:20:11 Written by liu taliaA chart makes the data clear; it says nothing about whose brand it belongs to, or what the one-line conclusion is. The usual fix in a report is a logo in one corner of the chart plus a note such as "Online: 1,208K USD in total" in the empty space — putting the conclusion where the reader's eye already is instead of starting another paragraph of prose. In Excel these elements belong to the chart's own shape layer, positioned against the chart rather than against the cells, and that is where code that adds them most often goes wrong. The plot area's default white background is a separate matter again: it can be swapped for a light texture so that the chart and the rest of the report look like one piece. Spire.XLS for JavaScript does all of this in the browser on top of WebAssembly, managing input and output files through a virtual file system (VFS), with no backend service required.
This article covers three key features:
For installation and project setup, see Integrating Spire.XLS for JavaScript in a React Project. The examples below assume Spire.XLS is installed and the WebAssembly module has been initialised.
Insert a picture into a chart
Charts in a report often need to carry a brand mark or a product shot. Once a picture is inside the chart it becomes part of it: move or resize the chart and the picture comes along, and copying the chart into another document or exporting it as an image keeps the picture too. A picture floating above the cells, by contrast, is out of alignment the moment the chart moves. The steps are:
- Load the font, the test data file and the picture into the VFS.
- Load the workbook with
workbook.LoadFromFileand take the first chart on the first worksheet. - Add the picture to the chart with
chart.Shapes.AddPicture; the value it returns is that shape. - Set the shape's
Left,Top,WidthandHeight. A shape inside a chart is measured against the chart itself: each of the four is in units of 1/4000 of the chart's width (Left,Width) or height (Top,Height). - Save the workbook with
workbook.SaveToFile.
The complete code example below shows how to insert a picture into a chart in React:
function App() {
const addPictureInChart = async () => {
// get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// check that the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// load the font, the test data file and the picture into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'ChartReport.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
await window.spire.FetchFileToVFS('logo.png', '', `${process.env.PUBLIC_URL}static/image/`);
// load the workbook
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// take the first worksheet and the chart on it
const sheet = workbook.Worksheets.get(0);
const chart = sheet.Charts.get(0);
// insert the picture into the chart; the object returned is that picture
const picture = chart.Shapes.AddPicture('logo.png');
// place it in the top-right corner and scale it down. Left unset, the picture is laid out at
// its natural pixel size and usually covers most of the chart
picture.Left = 2850; // 2850/4000 from the left edge of the chart
picture.Top = 110; // 110/4000 from the top edge of the chart
picture.Width = 900; // width 900/4000
picture.Height = 532; // height 532/4000, matching the 320x120 of the source picture
// save the workbook
const outputFileName = 'AddPictureInChart.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// dispose of the workbook to free resources
workbook.Dispose();
// read the result file from the VFS and start the download
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>Insert a Picture and a Text Box in a Chart</h1>
<button id="add-picture-in-chart" onClick={addPictureInChart}>Add a picture to the chart</button>
</div>
);
}
export default App;
After running, inserting a picture into a chart:

Insert a text box into a chart
A chart shows a trend but cannot state a conclusion. A total for one series, a year-on-year remark or a callout on an outlier can all be written straight onto the chart with a text box, sparing the reader a second trip to the body text for the number. A text box is a shape inside the chart like the picture, measured on the same scale; the difference is that its size has to be sized to the length of the text. Leave it too narrow and the text wraps, and the wrapped line is clipped by the box height — it looks as though half the words went missing. The steps are:
- Load the font and the test data file into the VFS.
- Load the workbook with
workbook.LoadFromFileand take the first chart on the first worksheet. - Create the text box inside the chart with
chart.Shapes.AddTextBox. - Set
Left,Top,WidthandHeightaccording to the length of the text so that the content fits on one line. - Write the text into the
Textproperty. - Centre the text with
HAlignmentandVAlignment, taking the values fromxlsModule.CommentHAlignTypeandxlsModule.CommentVAlignType. - Set the fill, the border colour and the border width with
Fill.ForeColor,Line.ForeColorandLine.Weight, so that the box stands out on the chart. - Save the workbook with
workbook.SaveToFile.
The complete code example below shows how to insert a text box into a chart in React:
function App() {
const addTextBoxInChart = async () => {
// get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// check that the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// load the font and the test data file into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'ChartReport.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// load the workbook
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// take the first worksheet and the chart on it
const sheet = workbook.Worksheets.get(0);
const chart = sheet.Charts.get(0);
// Add a text box to the chart
const textBox = chart.Shapes.AddTextBox();
// set the position and size of the text box, again in 1/4000 of the chart
textBox.Left = 450;
textBox.Top = 530;
textBox.Width = 2100;
textBox.Height = 340;
// write the text
textBox.Text = 'Online: 1,208K USD in total';
// centre the text and give the box a pale yellow fill and a blue border so that it stands
// out on the chart
textBox.HAlignment = xlsModule.CommentHAlignType.Center;
textBox.VAlignment = xlsModule.CommentVAlignType.Center;
textBox.Fill.ForeColor = xlsModule.Color.FromArgb(255, 255, 245, 214);
textBox.Line.ForeColor = xlsModule.Color.FromArgb(255, 46, 106, 176);
textBox.Line.Weight = 1;
// save the workbook
const outputFileName = 'AddTextBoxInChart.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// dispose of the workbook to free resources
workbook.Dispose();
// read the result file from the VFS and start the download
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>Insert a Picture and a Text Box in a Chart</h1>
<button id="add-textbox-in-chart" onClick={addTextBoxInChart}>Add a text box to the chart</button>
</div>
);
}
export default App;
After running, inserting a text box into a chart:

Fill the plot area with a picture
The white background of the plot area is the chart's default, and a report that has been around for a while starts to look the same everywhere. Replacing it with a light texture keeps the columns, the gridlines and the axis labels perfectly legible while giving the background some depth, and pulls the chart into the same visual language as the rest of the report. The fill and the picture shapes placed on the chart do not interfere with each other; the two can be used together. The steps are:
- Load the font, the test data file and the background picture into the VFS.
- Load the workbook with
workbook.LoadFromFileand take the first chart on the first worksheet. - Build an
xlsModule.Streamin memory from the background picture. - Hand it to
chart.PlotArea.Fill.CustomPicture; the second parameter,name, names an existing texture in the workbook, and'None'is passed when there is none. - Save the workbook with
workbook.SaveToFile.
The complete code example below shows how to fill the plot area with a picture in React:
function App() {
const fillPlotAreaWithPicture = async () => {
// get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// check that the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// load the font, the test data file and the background picture into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'ChartReport.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
await window.spire.FetchFileToVFS('background.png', '', `${process.env.PUBLIC_URL}static/image/`);
// load the workbook
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// take the first worksheet and the chart on it
const sheet = workbook.Worksheets.get(0);
const chart = sheet.Charts.get(0);
// read the background picture into a memory stream and use it to fill the plot area
const background = new xlsModule.Stream('background.png');
chart.PlotArea.Fill.CustomPicture({ im: background, name: 'None' });
// save the workbook
const outputFileName = 'FillPlotAreaWithPicture.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// dispose of the workbook to free resources
workbook.Dispose();
// read the result file from the VFS and start the download
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>Insert a Picture and a Text Box in a Chart</h1>
<button id="fill-plot-area" onClick={fillPlotAreaWithPicture}>Fill the plot area with a picture</button>
</div>
);
}
export default App;
After running, filling the plot area with a picture:

FAQ
Can I add an arrow or a callout line to a chart?
Solution: An arrow or a callout line that points at something has no API of its own, but a text box can stand in for one: stretch it into a thin strip, drop the fill and keep only the border, and place it where the callout should point.
When filling with a picture, is it the plot area or the whole chart that gets filled?
Cause: PlotArea.Fill and ChartArea.Fill are two different objects. The first covers only the region enclosed by the axes, leaving the chart title, the legend and the axis labels outside the picture. The second covers the entire chart, so the title and the legend end up on top of the picture as well.
Solution: Pick whichever one you need. After the fill, read Fill.FillType; it returns ShapeFillType.Picture on success:
// fill only the plot area: the title, the legend and the axis labels stay as they were
const background = new xlsModule.Stream('background.png');
chart.PlotArea.Fill.CustomPicture({ im: background, name: 'None' });
// fill the whole chart: the picture runs under the title and the legend
chart.ChartArea.Fill.CustomPicture({ im: new xlsModule.Stream('background.png'), name: 'None' });
Get a Free License
Spire.XLS for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.
Worksheet data is rarely a tidy rectangle. Detail rows are interrupted by subtotal lines, group headings and notes, and the chart is meant to draw the detail alone; other numbers never reach a cell at all, because they come back from an API, are computed in code, or are simply a set of targets for this one report. Both cases defeat the usual routine of selecting a block and inserting a chart. Spire.XLS for JavaScript does this in the browser on top of WebAssembly, managing input and output files through a virtual file system (VFS), with no backend service required.
This article covers two key features:
For installation and project setup, see Integrating Spire.XLS for JavaScript in a React Project. The examples below assume Spire.XLS is installed and the WebAssembly module has been initialised.
Create a chart from a discontinuous data source
Subtotal rows, group headings and notes all cut a table of details into several blocks. Selecting the whole column and inserting a chart makes no distinction between them and the detail rows, so they are drawn as columns too: in the sample data a subtotal follows every two quarters, and reading the whole column turns six columns into nine and roughly doubles the height of every region. A series can be given its data as several non-adjacent blocks joined into a single reference, so the chart takes only the rows inside those blocks and skips over the rest. The table then needs no rearranging for the sake of the chart, and the subtotal rows can stay where they are. The steps are:
- Load the font and the test data file into the VFS.
- Load the workbook with
workbook.LoadFromFileand get the first worksheet withworkbook.Worksheets.get(0). - Add a column chart with
sheet.Charts.Addand setchart.SeriesDataFromRangetofalse, declaring that the series data is supplied block by block in code rather than taken fromchart.DataRange. - Add a series with
chart.Series.Addand setserie.Nameto theValueof the header cell. - Take each region's quarter rows with
Range.getand join them withAddCombinedRange, producing one reference for the category labels and one for the values. - Repeat the same joins for the second series so both share the same category labels, then save the workbook.
The complete code example below shows how to create a chart from a discontinuous data source in React:
function App() {
const chartFromDiscontinuousData = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check that the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and the sample workbook into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'ChartSourceData.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook and get the first worksheet
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
const sheet = workbook.Worksheets.get(0);
// Add a column chart; the series data is given block by block in code, not as one range
const chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.ColumnClustered });
chart.ChartTitle = "Quarterly Sales by Region";
chart.ChartTitleArea.Size = 12;
chart.SeriesDataFromRange = false;
// Place the chart on the worksheet
chart.TopRow = 12;
chart.BottomRow = 28;
chart.LeftColumn = 1;
chart.RightColumn = 10;
// Join the three regions' quarter rows into one reference, skipping the subtotal rows
const categoryLabels = sheet.Range.get("A2:A3")
.AddCombinedRange(sheet.Range.get("A5:A6"))
.AddCombinedRange(sheet.Range.get("A8:A9"));
// Online series: the name comes from the header, the values are joined the same way
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"));
// In-store series: it shares the same category labels
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"));
// Save the workbook
const outputFileName = "DiscontinuousData.xlsx";
workbook.SaveToFile({ fileName: outputFileName });
// Dispose of the workbook object to free resources
workbook.Dispose();
// Read the result file from the VFS and trigger the download
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>Discontinuous Data Chart</h1>
<button id="discontinuous-data" onClick={chartFromDiscontinuousData}>Create a chart from a discontinuous source</button>
</div>
);
}
export default App;
After running, the chart created from a discontinuous data source:

Create a chart without a data source
A chart normally takes its data from cells, but not always: a target may live in a configuration file, a summary figure may come back from an API, or the numbers may just be a set of constants for a demonstration. None of them has landed in the worksheet, so there is no range for the chart to point at. A series can carry its values inside itself instead, which lets the chart stand without any worksheet data behind it; the numbers follow the code and are regenerated with it, so no copy has to be maintained in the sheet for the sake of the chart. Because the values come from nowhere on the sheet, the chart carries no text categories either, and the horizontal axis is numbered 1, 2, 3. The steps are:
- Load the font and the test data file into the VFS.
- Load the workbook with
workbook.LoadFromFileand get the first worksheet withworkbook.Worksheets.get(0); the new chart still has to hang on an existing worksheet. - Add a column chart with
sheet.Charts.Add, and set the chart title and position. - Add a series with
chart.Series.Addand setserie.Nameto the series name. - Box each number with
xlsModule.Int32.Create, put them in order intoserie.EnteredDirectlyValues, and save the workbook.
The complete code example below shows how to create a chart without a data source in React:
function App() {
const chartWithoutSourceData = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check that the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and the sample workbook into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'ChartSourceData.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook and get the first worksheet
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
const sheet = workbook.Worksheets.get(0);
// Add a column chart; this time the numbers come from no cell at all
const chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.ColumnClustered });
chart.ChartTitle = "Sales Target by Region";
chart.ChartTitleArea.Size = 12;
// Place the chart on the worksheet
chart.TopRow = 12;
chart.BottomRow = 28;
chart.LeftColumn = 1;
chart.RightColumn = 10;
// Add a series and write the numbers into EnteredDirectlyValues one by one
// No category labels are given, so the chart numbers them 1, 2, 3
const targetSerie = chart.Series.Add();
targetSerie.Name = "Sales Target";
targetSerie.EnteredDirectlyValues = [
xlsModule.Int32.Create(260),
xlsModule.Int32.Create(210),
xlsModule.Int32.Create(190),
];
// Save the workbook
const outputFileName = "NoSourceData.xlsx";
workbook.SaveToFile({ fileName: outputFileName });
// Dispose of the workbook object to free resources
workbook.Dispose();
// Read the result file from the VFS and trigger the download
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>Chart Without a Data Source</h1>
<button id="no-source-data" onClick={chartWithoutSourceData}>Create a chart without a data source</button>
</div>
);
}
export default App;
After running, the chart created without a data source:

FAQ
Does the series name have to point at a cell?
Cause: serie.Name takes a plain string. Whatever you assign becomes the series name and is what the legend shows; after saving it sits in the series itself rather than in a reference to a cell. Reading the Value of a header cell is just another way to get there, not a requirement. The values and the category labels still have to come from a range — only the name can stand free of one.
Solution: assign the text straight to serie.Name:
const serie = chart.Series.Add();
serie.Name = "Sales Target";
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"));
Do both series need their own category labels?
Cause: CategoryLabels belongs to the series itself. When a second series is added it does not pick up the setting from the one before it, so it has to be assigned again.
Solution: join the blocks once, keep the result in a variable and point each series at it, instead of writing AddCombinedRange out twice:
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;
Get a Free License
Spire.XLS for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.
When a chart is inserted, Excel determines a scale automatically: the lower and upper bounds of the value axis and its intervals are all derived from the data range. These defaults are adequate in most cases, but they fall short in two situations. First, when the data is concentrated in a narrow band that sits well away from zero, the automatic scale lifts the bottom of the axis to just below the data, so a modest fluctuation is drawn as a near full-height swing and the differences between the columns bear no proportion to the differences between the values. Second, because the tick labels follow the formatting of the source cells, values carrying decimals fill the value axis with fractional digits, which are both harder to read and wider on the page. Axis formatting addresses both: setting the scale and the units keeps the axis from shifting with the data, holds the column heights in proportion to the values, and places every chart on the same measure, choosing the tick marks and the label position controls where the readings fall, rewriting the number format makes the labels easier to compare, and adding titles to both axes tells the reader what each direction represents. Spire.XLS for JavaScript does all of this in the browser on top of WebAssembly, managing input and output files through a virtual file system (VFS), with no backend service required.
This article covers two key features:
For installation and project setup, see Integrating Spire.XLS for JavaScript in a React Project. The examples below assume Spire.XLS is installed and the WebAssembly module has been initialised.
Format the value axis scale
An automatic scale is derived by Excel from the data range, and it is chosen to make the chart look even rather than to make the readings easy to compare. When the data is concentrated in a narrow band that sits well away from zero, that derivation lifts the bottom of the axis to just below the data: with the axis no longer starting at 0, the columns only show their differences from one another, and a modest fluctuation is drawn as a near full-height swing, out of proportion to the values behind it. The tick labels, too, follow the display format of the source cells by default, so data carrying decimals produces a run of fractional values along the value axis, which is both harder to read and wider on the page. Setting the value axis scale by hand addresses both at once: once the scale and the intervals are given by the code, the bottom and top of the axis no longer shift with the data, the column heights stay in proportion to the values, and a set of charts can be measured against the same ruler; once the label format is independent of the source cells, the readings are more even as well. The steps are:
- Load the font and the test data file into the VFS.
- Load the workbook with
workbook.LoadFromFileand get the first worksheet withworkbook.Worksheets.get(0). - Add a column chart with
sheet.Charts.Add, pointchart.DataRangeat the sales column, and assign the month column toserie.CategoryLabels. - On
chart.PrimaryValueAxis, set the scale withMinValueandMaxValue, and the steps of the major and minor divisions withMajorUnitandMinorUnit. - Set the types of the major and minor tick marks with
MajorTickMarkandMinorTickMark, and the position of the tick labels withTickLabelPosition. - Set
NumberFormatto#,##0for the label number format, setIsSourceLinkedtofalseto unlink the format from the source cells, and save the workbook.
The complete code example below shows how to format the value axis scale in React:
function App() {
const formatValueAxis = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check that the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and the sample workbook into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'ChartAxisData.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook and get the first worksheet
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
const sheet = workbook.Worksheets.get(0);
// Add a column chart whose data range is the sales column
const chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.ColumnClustered });
chart.ChartTitle = "Monthly Sales";
chart.ChartTitleArea.IsBold = true;
chart.ChartTitleArea.Size = 12;
chart.DataRange = sheet.Range.get("B1:B9");
chart.SeriesDataFromRange = false;
chart.PlotArea.Visible = false;
// Place the chart on the worksheet
chart.TopRow = 10;
chart.BottomRow = 28;
chart.LeftColumn = 2;
chart.RightColumn = 10;
// Take the series and use the month column as its category labels
const serie = chart.Series.get(0);
serie.CategoryLabels = sheet.Range.get("A2:A9");
// Set the value axis bounds: 0 at the bottom, 5000 at the top
const valueAxis = chart.PrimaryValueAxis;
valueAxis.MinValue = 0;
valueAxis.MaxValue = 5000;
// Set the major unit to 1000 and the minor unit to 500
valueAxis.MajorUnit = 1000;
valueAxis.MinorUnit = 500;
// Major tick marks outside the axis, minor tick marks inside
valueAxis.MajorTickMark = xlsModule.TickMarkType.TickMarkOutside;
valueAxis.MinorTickMark = xlsModule.TickMarkType.TickMarkInside;
// Tick labels sit next to the axis, which the category axis crosses at 0
valueAxis.TickLabelPosition = xlsModule.TickLabelPositionType.TickLabelPositionNextToAxis;
valueAxis.CrossesAt = 0;
// Give the tick labels a thousands separator and drop the decimals
valueAxis.NumberFormat = "#,##0";
// Unlink the source format so the tick labels follow NumberFormat
valueAxis.IsSourceLinked = false;
// Save the workbook
const outputFileName = "AxisFormat.xlsx";
workbook.SaveToFile({ fileName: outputFileName });
// Dispose of the workbook object to free resources
workbook.Dispose();
// Read the result file from the VFS and trigger the download
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>Value Axis Scale</h1>
<button onClick={formatValueAxis}>Start</button>
</div>
);
}
export default App;
After running, the effect of formatting the value axis scale:

Add axis titles
The axes carry only their own divisions and categories, so the chart itself does not state what each data point represents or what unit the numbers are in; opening the file again after some time, a reader often has to check the chart title first to confirm the subject. Axis titles supply precisely that context: the category axis title states what each data point represents, the value axis title states the unit of the numbers, and together with the chart title they complete the chart so that it can be read without its surrounding context. The size of an axis title can also be adjusted separately, which keeps it distinct from the chart title. The steps are:
- Load the font and the test data file into the VFS.
- Load the workbook with
workbook.LoadFromFileand get the first worksheet withworkbook.Worksheets.get(0). - Add a column chart with
sheet.Charts.Add, pointchart.DataRangeat the sales column, and assign the month column toserie.CategoryLabels. - Assign the title text to
chart.PrimaryCategoryAxis.Titleandchart.PrimaryValueAxis.Titlerespectively. - Set both titles to size 12 through the
Font.Sizeof the two axis objects, and save the workbook.
The complete code example below shows how to add titles to the axes in React:
function App() {
const setAxisTitle = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check that the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and the sample workbook into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'ChartAxisData.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook and get the first worksheet
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
const sheet = workbook.Worksheets.get(0);
// Add a column chart whose data range is the sales column
const chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.ColumnClustered });
chart.ChartTitle = "Monthly Sales";
chart.ChartTitleArea.IsBold = true;
chart.ChartTitleArea.Size = 12;
chart.DataRange = sheet.Range.get("B1:B9");
chart.SeriesDataFromRange = false;
chart.PlotArea.Visible = false;
// Place the chart on the worksheet
chart.TopRow = 10;
chart.BottomRow = 28;
chart.LeftColumn = 2;
chart.RightColumn = 10;
// Take the series and use the month column as its category labels
const serie = chart.Series.get(0);
serie.CategoryLabels = sheet.Range.get("A2:A9");
// Give the category axis and the value axis their titles
chart.PrimaryCategoryAxis.Title = "Month";
chart.PrimaryValueAxis.Title = "Sales (CNY)";
// Set both axis titles to size 12
chart.PrimaryCategoryAxis.Font.Size = 12;
chart.PrimaryValueAxis.Font.Size = 12;
// Save the workbook
const outputFileName = "AxisTitle.xlsx";
workbook.SaveToFile({ fileName: outputFileName });
// Dispose of the workbook object to free resources
workbook.Dispose();
// Read the result file from the VFS and trigger the download
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>Axis Titles</h1>
<button onClick={setAxisTitle}>Start</button>
</div>
);
}
export default App;
After running, the effect of adding axis titles:

FAQ
MinorUnit is set but no minor tick marks show up
Cause: MinorUnit determines only the interval of the minor divisions — how many steps each major division is split into. Whether those steps are drawn as short strokes is decided by a separate property. MinorTickMark defaults to TickMarkNone, and in that state the interval is in effect but no strokes are drawn.
Fix: Set MinorTickMark at the same time, taking its value from TickMarkType:
const valueAxis = chart.PrimaryValueAxis;
valueAxis.MinorUnit = 500;
valueAxis.MinorTickMark = xlsModule.TickMarkType.TickMarkInside;
CrossesAt is set but the category axis does not move
Cause: CrossesAt determines where the category axis meets the value axis, and the move becomes visible only when it is set to a position on the scale other than the one the category axis already occupies. With the value axis starting at 0, the category axis already sits at the bottom of the axis line, so setting CrossesAt to 0 leaves it in place.
Fix: Set CrossesAt to another position on the value axis scale, 3000 for example, and the category axis moves up to that level:
const valueAxis = chart.PrimaryValueAxis;
valueAxis.MinValue = 0;
valueAxis.MaxValue = 5000;
valueAxis.CrossesAt = 3000;
Get a Free License
Spire.XLS for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.
A chart makes the trend behind one set of numbers visible, but it is far less good at showing the trends behind dozens of sets at once. Sparklines are built for exactly that: each one compresses a trend into a single cell, with no axes and no legend, so a whole column of trends can be taken in at a glance. Give every region's quarterly sales figures their own sparkline and you can see which row is climbing and which one turned back long before you could read it out of a column of numbers. Spire.XLS for JavaScript performs this directly in the browser through WebAssembly, managing input and output files with a virtual file system (VFS) and requiring no backend service.
This article covers three feature points:
- Add line sparklines
- Add column sparklines and highlight the high and low points
- Clear sparklines from a worksheet
For installation and project setup, see Integrating Spire.XLS for JavaScript in a React Project. The examples below assume Spire.XLS is installed and the WebAssembly module has been initialized.
Add Line Sparklines
A line sparkline joins a row of values in order into a single thin line, making the rise and fall of the trend immediately readable. It occupies no cell of its own and hides none of the numbers underneath, so a single narrow column can carry the trends of dozens of rows at once, and which one is climbing and which one turned back is there at a glance. A sparkline cannot exist outside a sparkline group, and the group decides the type, colors and marker settings shared by the whole batch. The steps are:
- Load the font and the sample data file into the VFS.
- Load the workbook and get the first worksheet.
- Create a line sparkline group with
SparklineGroups.AddGroup()and set the line weight and marker color. - Get the group's sparkline collection with
Add()and add one sparkline per data row in column F. - Save the workbook.
The following is the complete code example, showing how to add line sparklines to Excel data in React:
function App() {
const addLineSparkline = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check if the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and the sample data file into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'SparklineData.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook and get the first worksheet
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
const sheet = workbook.Worksheets.get(0);
// Create a line sparkline group on the worksheet
const sparklineGroup = sheet.SparklineGroups.AddGroup({ sparklineType: xlsModule.SparklineType.Line });
// Style the line: thicker stroke and visible data point markers
sparklineGroup.LineWeight = 1.5;
sparklineGroup.ShowMarkers = true;
sparklineGroup.MarkersColor = xlsModule.Color.get_Red();
// Get the sparkline collection of the group
const sparklines = sparklineGroup.Add();
// Add one sparkline per data row in column F
for (let row = 2; row <= 9; row++) {
sparklines.Add({
dataRange: sheet.Range.get(`B${row}:E${row}`),
referenceRange: sheet.Range.get(`F${row}`),
});
}
// Save the workbook
const outputFileName = 'AddLineSparkline.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// Dispose of the workbook object to free resources
workbook.Dispose();
// Read the result file from the VFS and trigger the download
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>Line Sparkline</h1>
<button onClick={addLineSparkline}>Start</button>
</div>
);
}
export default App;
After running, the effect of adding line sparklines:

Add Column Sparklines and Highlight the High and Low Points
A column sparkline represents values as a row of thin bars, which reads more directly than a line when you are comparing several numbers within the same row. It works the same way as the line sparkline, except that the color lands on each bar itself rather than on a connecting stroke.
What really makes column sparklines useful is the high and low point markers: the strongest and the weakest quarter in each row are picked out and colored on their own, so you no longer have to compare the numbers by eye to see where a row peaked and where it fell away. The steps are:
- Load the font and the sample data file into the VFS.
- Load the workbook and get the first worksheet.
- Create a column sparkline group with
SparklineGroups.AddGroup()(passingSparklineType.Column) and set the series color withSparklineColor. - Turn on
ShowHighPointandShowLowPoint, and give each its own color withHighPointColorandLowPointColor. - Get the group's sparkline collection with
Add(), add one sparkline per data row in column G and save the workbook.
The following is the complete code example, showing how to add column sparklines to Excel data and highlight the high and low points in React:
function App() {
const addColumnSparkline = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check if the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and the sample data file into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'SparklineData.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook and get the first worksheet
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
const sheet = workbook.Worksheets.get(0);
// Create a column sparkline group
const sparklineGroup = sheet.SparklineGroups.AddGroup({ sparklineType: xlsModule.SparklineType.Column });
// Set the color of the sparkline itself
sparklineGroup.SparklineColor = xlsModule.Color.get_CadetBlue();
// Turn on the high and low point markers and give each its own color
sparklineGroup.ShowHighPoint = true;
sparklineGroup.HighPointColor = xlsModule.Color.get_Red();
sparklineGroup.ShowLowPoint = true;
sparklineGroup.LowPointColor = xlsModule.Color.get_Purple();
const sparklines = sparklineGroup.Add();
// Add one sparkline per data row in column G
for (let row = 2; row <= 9; row++) {
sparklines.Add({
dataRange: sheet.Range.get(`B${row}:E${row}`),
referenceRange: sheet.Range.get(`G${row}`),
});
}
// Save the workbook
const outputFileName = 'AddColumnSparkline.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// Dispose of the workbook object to free resources
workbook.Dispose();
// Read the result file from the VFS and trigger the download
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>Column Sparkline</h1>
<button onClick={addColumnSparkline}>Start</button>
</div>
);
}
export default App;
After running, the effect of adding column sparklines and highlighting the high and low points:

Clear Sparklines from a Worksheet
When a report is revised or the figures are restated, the sparklines drawn beside the old numbers may no longer apply and need to be removed and drawn again. Sparkline groups are held by the worksheet, and once they are gone the cells return to their ordinary state, with their data and other formatting untouched. The steps are:
- Load the font and the sample data file into the VFS.
- Load the workbook and get the second worksheet, which already carries a line sparkline group.
- Clear all sparkline groups on that worksheet with
SparklineGroups.Clear(). - Save the workbook.
The following is the complete code example, showing how to clear sparklines from a worksheet in React:
function App() {
const clearSparklines = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check if the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and the sample data file into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'SparklineData.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook and get the second worksheet
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
const sheet = workbook.Worksheets.get(1);
// Clear all sparkline groups on that worksheet
sheet.SparklineGroups.Clear();
// Save the workbook
const outputFileName = 'ClearSparklines.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// Dispose of the workbook object to free resources
workbook.Dispose();
// Read the result file from the VFS and trigger the download
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>Clear Sparklines</h1>
<button onClick={clearSparklines}>Start</button>
</div>
);
}
export default App;
After running, the effect of clearing sparklines from a worksheet:

FAQ
Reading the cells back does not show the sparklines
Cause: A sparkline is not cell content. It belongs to a sparkline group on the worksheet and is drawn over its anchor cell, while its data and settings are written into the worksheet's extension block, so walking the cells returns an empty value for the anchor column and no cell-level call can tell you whether a sparkline is there.
Solution: Read it from the worksheet-level SparklineGroups instead. get_Item(0) returns the first group, and its SparklineType tells you whether the batch is a line or a column one; the color settings are read from the same place:
const group = sheet.SparklineGroups.get_Item(0);
console.log(group.SparklineType); // SparklineType.Line or SparklineType.Column
Setting the line weight and markers on a column sparkline has no effect
Cause: LineWeight and ShowMarkers apply to line sparklines only. A column sparkline represents values as bars, so it has neither a connecting stroke nor data point markers, and although the assignment succeeds, neither property is written to the result file.
Solution: If you need a stroke and markers, create the sparkline group as a line type and set them there:
const sparklineGroup = sheet.SparklineGroups.AddGroup({ sparklineType: xlsModule.SparklineType.Line });
sparklineGroup.LineWeight = 1.5;
sparklineGroup.ShowMarkers = true;
sparklineGroup.MarkersColor = xlsModule.Color.get_Red();
Get a Free License
Spire.XLS for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.
A chart plots averages, and an average says nothing about how steady the underlying readings were. The same rising line can sit on top of six near-identical measurements or six wildly scattered ones -- the curve alone will not tell you which. Error bars supply that missing layer: a short segment drawn at each data point marks the spread or uncertainty around it, so the reader can see how much confidence each turn of the line deserves. Percentage error bars derive their range from a fixed proportion of the value, while standard error bars derive theirs from the data itself, and the two suit different situations. Spire.XLS for JavaScript performs both directly in the browser through WebAssembly, managing input and output files with a virtual file system (VFS) and requiring no backend service.
This article covers two key features:
For installation and project setup, see Integrating Spire.XLS for JavaScript in a React Project. The examples below assume Spire.XLS is installed and the WebAssembly module has been initialised.
Add percentage error bars to a line chart
A percentage error bar takes the value of each data point as its base and works out the range from the given percentage. Set it to 10, and a point valued at 4.0 gets an error amount of 0.4; which side of the data point that length is drawn on is decided by a separate parameter. This kind of error bar expresses a tolerance -- a production target allowed to drift by a tenth, or an instrument reading with a fixed relative precision. Error bars belong to a series, so the series object has to be fetched first and its ErrorBar method called on it. The steps are:
- Load the font and the test data file into the VFS.
- Load the workbook and get the first worksheet.
- Add a line chart whose data range is the planned output column.
- Use the month column as the category labels.
- Add a percentage error bar to the series, setting its direction and amount, then save the workbook.
The complete code example below shows how to add percentage error bars to a line chart in React:
function App() {
const addPercentageErrorBar = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check if the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and the test data file into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'ErrorBarChartData.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook and get the first worksheet
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
const sheet = workbook.Worksheets.get(0);
// Add a line chart whose data range is the planned output column
const chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.Line });
chart.ChartTitle = "Planned Output";
chart.ChartTitleArea.IsBold = true;
chart.ChartTitleArea.Size = 12;
chart.DataRange = sheet.Range.get("B1:B7");
chart.SeriesDataFromRange = false;
// Set the chart position on the worksheet
chart.TopRow = 9;
chart.BottomRow = 26;
chart.LeftColumn = 1;
chart.RightColumn = 9;
// Get the series and use the month column as its category labels
const serie = chart.Series.get(0);
serie.CategoryLabels = sheet.Range.get("A2:A7");
// Add a percentage error bar to the series: plus direction, 10% of the value
serie.ErrorBar({
bIsY: true,
include: xlsModule.ErrorBarIncludeType.Plus,
type: xlsModule.ErrorBarType.Percentage,
numberValue: 10.0,
});
// Save the workbook
const outputFileName = "PercentageErrorBar.xlsx";
workbook.SaveToFile({ fileName: outputFileName });
// Dispose of the workbook object to free resources
workbook.Dispose();
// Read the result file from the VFS and trigger the download
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>Percentage Error Bars</h1>
<button onClick={addPercentageErrorBar}>Start</button>
</div>
);
}
export default App;
After running, the effect of adding percentage error bars to a line chart:

Add standard error bars to a column chart
A standard error bar takes no percentage from you; it computes the standard error of the series itself, so a series with scattered values automatically gets longer error bars. That is the division of labour between the two kinds: the standard error bar describes how steady the data is, the percentage one describes how far you are willing to let it drift. Column charts are usually there to compare several groups side by side, and hanging a standard error bar on each group shows at a glance which one's readings cluster more tightly. The steps are:
- Load the font and the test data file into the VFS.
- Load the workbook and get the first worksheet.
- Add a column chart covering both the planned and the actual output columns.
- Add a standard error bar to each of the two series.
- Save the workbook.
The complete code example below shows how to add standard error bars to a column chart in React:
function App() {
const addStandardErrorBar = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check if the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and the test data file into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'ErrorBarChartData.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook and get the first worksheet
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
const sheet = workbook.Worksheets.get(0);
// Add a column chart covering both the planned and the actual output columns
const chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.ColumnClustered });
chart.ChartTitle = "Planned vs. Actual Output";
chart.ChartTitleArea.IsBold = true;
chart.ChartTitleArea.Size = 12;
chart.DataRange = sheet.Range.get("B1:C7");
chart.SeriesDataFromRange = false;
// Set the chart position on the worksheet
chart.TopRow = 9;
chart.BottomRow = 26;
chart.LeftColumn = 1;
chart.RightColumn = 9;
// Get the first series and use the month column as its category labels
const serie = chart.Series.get(0);
serie.CategoryLabels = sheet.Range.get("A2:A7");
// Draw a standard error bar on the first series, minus direction
serie.ErrorBar({
bIsY: true,
include: xlsModule.ErrorBarIncludeType.Minus,
type: xlsModule.ErrorBarType.StandardError,
numberValue: 0.3,
});
// Draw a standard error bar on the second series as well, both directions
const serie2 = chart.Series.get(1);
serie2.ErrorBar({
bIsY: true,
include: xlsModule.ErrorBarIncludeType.Both,
type: xlsModule.ErrorBarType.StandardError,
numberValue: 0.5,
});
// Save the workbook
const outputFileName = "StandardErrorBar.xlsx";
workbook.SaveToFile({ fileName: outputFileName });
// Dispose of the workbook object to free resources
workbook.Dispose();
// Read the result file from the VFS and trigger the download
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>Standard Error Bars</h1>
<button onClick={addStandardErrorBar}>Start</button>
</div>
);
}
export default App;
After running, the effect of adding standard error bars to a column chart:

FAQ
Which directions do error bars support?
The direction comes from the include parameter of ErrorBar, which takes three values from ErrorBarIncludeType:
| Value | Effect |
|---|---|
Both |
Both directions, a segment drawn above and below the data point |
Minus |
Negative only, drawn below the data point |
Plus |
Positive only, drawn above the data point |
// Draw it in both directions
serie.ErrorBar({
bIsY: true,
include: xlsModule.ErrorBarIncludeType.Both,
type: xlsModule.ErrorBarType.Percentage,
numberValue: 10.0,
});
Direction only decides which side of the data point the error bar is drawn on, and does not change its length. The same parameters give the same error amount in all three directions.
Which error amount types are supported?
The error amount type comes from the type parameter of ErrorBar, which takes five values from ErrorBarType:
| Type | Description |
|---|---|
Fixed |
A fixed value, given by numberValue |
Percentage |
A percentage of each data point's value |
StandardDeviation |
A standard deviation, given by numberValue |
StandardError |
A standard error, computed from the series data; numberValue plays no part |
Custom |
Custom ranges,needs a different overload |
The first four take their amount in numberValue:
serie.ErrorBar({
bIsY: true,
include: xlsModule.ErrorBarIncludeType.Both,
type: xlsModule.ErrorBarType.StandardDeviation,
numberValue: 2,
});
Get a Free License
Spire.XLS for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.
Multi-Level Labels with a Secondary Axis in React with JavaScript
2026-09-18 06:28:49 Written by liu taliaWhen the categories in a chart are themselves layered -- a region that contains months, or a year that contains quarters -- squeezing both columns into a single row of labels leaves the reader guessing which region or which month a data point belongs to. Layered data brings a second problem with it: sales run into the millions while a growth rate sits in the low teens, and on one shared value axis the growth rate flattens into a line pinned to the baseline. Multi-level category labels and the secondary axis solve these two problems respectively. Spire.XLS for JavaScript performs both directly in the browser through WebAssembly, managing input and output files with a virtual file system (VFS) and requiring no backend service.
This article covers two key features:
For installation and project setup, see Integrating Spire.XLS for JavaScript in a React Project. The examples below assume Spire.XLS is installed and the WebAssembly module has been initialised.
Create a Chart with Multi-Level Category Labels
Multi-level category labels are drawn by the category axis, and how many levels that axis has depends on how many columns the series' category labels point at. In the test data the outer labels are already merged by region, so the code only has to make the category labels span both the outer and the inner column. The steps are:
- Load the font and the test data file into the VFS.
- Load the workbook and get the worksheet.
- Add a column chart and add a named sales series.
- Point the category labels at both the region and the month column.
- Turn on multi-level labels for the category axis and save the workbook.
The complete code example below creates a chart with multi-level category labels in React:
function App() {
const createMultiLevelChart = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check if the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and the test data file into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'MultiLevelChartData.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook and get the first worksheet
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
const sheet = workbook.Worksheets.get(0);
// Add a column chart
const chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.ColumnClustered });
chart.ChartTitle = "Sales";
chart.Legend.Delete();
// Add the sales series and give it a name
const serie = chart.Series.Add({ name: "Sales", serieType: xlsModule.ExcelChartType.ColumnClustered });
serie.Values = sheet.Range.get("C2:C7");
// Point the category labels at both the region and the month column
serie.CategoryLabels = sheet.Range.get("A2:B7");
// Turn on multi-level category labels so each level gets its own row
chart.PrimaryCategoryAxis.MultiLevelLable = true;
// Place the chart on the worksheet
chart.LeftColumn = 5;
chart.TopRow = 1;
chart.RightColumn = 14;
// Save the workbook
const outputFileName = "MultiLevelLabels.xlsx";
workbook.SaveToFile({ fileName: outputFileName });
// Dispose of the workbook object to free resources
workbook.Dispose();
// Read the result file from the VFS and trigger the download
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>Multi-Level Labels</h1>
<button onClick={createMultiLevelChart}>Start</button>
</div>
);
}
export default App;
How many levels the category axis has is decided by how many columns CategoryLabels points at, so binding a single-column range still yields a single level of labels; MultiLevelLable controls whether those levels are laid out as multiple rows.
After running, the effect of creating a chart with multi-level category labels:

Add a Secondary Axis to the Chart
When two series in the same chart differ sharply in magnitude, sharing one value axis squashes the smaller of the two into a line pinned to the baseline, and its movement can no longer be read. A secondary axis is meant for exactly that case: it gives the series a value axis of its own, so each scale spreads across its own range without interfering with the other. The way to do it is to move the series off the primary axis and draw it as a line -- a line takes up no bar width, so it reads clearly against the column series sharing the same categories. The steps are:
- Load the font and the test data file into the VFS.
- Load the workbook and get the worksheet.
- Add a column chart and add a named sales series.
- Add the growth series as a line.
- Move the growth series to the secondary axis and save the workbook.
The complete code example below adds a secondary axis to the chart in React:
function App() {
const addSecondaryAxis = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check if the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and the test data file into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'MultiLevelChartData.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook and get the first worksheet
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
const sheet = workbook.Worksheets.get(0);
// Add a column chart
const chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.ColumnClustered });
chart.ChartTitle = "Sales and YoY Growth";
// Add the sales series, which stays on the primary axis
const salesSerie = chart.Series.Add({ name: "Sales", serieType: xlsModule.ExcelChartType.ColumnClustered });
salesSerie.Values = sheet.Range.get("C2:C7");
// Point the category labels at both the region and the month column
salesSerie.CategoryLabels = sheet.Range.get("A2:B7");
// Add the growth series as a line
const growthSerie = chart.Series.Add({ name: "YoY Growth", serieType: xlsModule.ExcelChartType.Line });
growthSerie.Values = sheet.Range.get("D2:D7");
// Move the growth series to the secondary axis so it plots on its own percentage scale
growthSerie.UsePrimaryAxis = false;
// Turn on multi-level category labels
chart.PrimaryCategoryAxis.MultiLevelLable = true;
// Place the chart on the worksheet
chart.LeftColumn = 5;
chart.TopRow = 1;
chart.RightColumn = 14;
// Save the workbook
const outputFileName = "SecondaryAxis.xlsx";
workbook.SaveToFile({ fileName: outputFileName });
// Dispose of the workbook object to free resources
workbook.Dispose();
// Read the result file from the VFS and trigger the download
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>Secondary Axis</h1>
<button onClick={addSecondaryAxis}>Start</button>
</div>
);
}
export default App;
UsePrimaryAxis = false affects only the series it is set on; the other series stay on the primary axis. The chart gains a second pair of value and category axes as a result, giving it two separate scale ranges. Series.Add takes the series name at the same time, so the legend shows the name passed in rather than an auto-generated "Series 1".
After running, the effect of adding a secondary axis to the chart:

Frequently Asked Questions
Why does the category axis show only one level of labels?
Cause: How many levels the category axis has is decided by the range CategoryLabels points at. If that is a single-column range such as B2:B7, the range holds only one level of category information, and setting PrimaryCategoryAxis.MultiLevelLable to true will still give you one level of labels -- the property controls whether multiple levels are expanded, not whether a level of data is created.
Solution: Point CategoryLabels at a multi-column range that includes the outer labels; the cells the outer labels occupy then need to be merged in the data:
// Cover both columns with the category labels; the outer label cells need merging in the data
serie.CategoryLabels = sheet.Range.get("A2:B7");
Why do the two value axes have different scales?
Cause: The primary and secondary value axes work out their scales independently of each other, and MinValue, MaxValue and MajorUnit on PrimaryValueAxis apply to the primary axis only -- changing them leaves the secondary axis untouched. When the two series differ sharply in magnitude, the range the secondary axis picks for itself is often not a good fit.
Solution: Give the secondary axis its own scale through chart.SecondaryValueAxis:
// Give the secondary axis a 0-20 scale with a major unit of 5
chart.SecondaryValueAxis.MinValue = 0;
chart.SecondaryValueAxis.MaxValue = 20;
chart.SecondaryValueAxis.MajorUnit = 5;
Set the scale after the series has been moved to the secondary axis: while no series uses the secondary axis, the assignment is accepted but never written to the file.
Get a Free License
Spire.XLS for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.
When you need to compare several metrics across multiple dimensions at the same time, a radar chart is a very intuitive way to present them — each dimension is placed on an axis radiating out from the center, and the values on those axes are joined into a polygon, so the shape immediately shows where the strengths and weaknesses are. Spire.XLS for JavaScript provides a complete charting API that supports creating radar charts directly in the browser via WebAssembly, without requiring a backend service.
This article covers two core features:
For installation and project configuration, please refer to Integrating Spire.XLS for JavaScript in a React Project. The following examples assume Spire.XLS is already installed and the WebAssembly module has been initialized.
Create a Radar Chart
You can add a radar chart to a worksheet with the sheet.Charts.Add() method. The steps are as follows:
- Load the Excel file that contains the data and get the first worksheet.
- Add a radar chart with
Charts.Add({ chartType: ExcelChartType.Radar }). - Set the
DataRangeproperty to specify the chart data range: the first row holds the series names, the first column holds the category names for each axis, and the remaining cells hold the values. - Set
SeriesDataFromRange = falseso that the data is not taken from a row/column layout. - Set the chart title, position, and legend position.
- Save the workbook with the
Workbook.SaveToFile()method.
Below is a complete code example that shows how to create a radar chart in React:
function App() {
const createRadarChart = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check if the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the Excel file into VFS
const inputFileName = 'RadarChartData.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/static/data/`);
// Load the workbook
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile(inputFileName);
// Get the first worksheet
let sheet = workbook.Worksheets.get(0);
// Add a radar chart
let chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.Radar });
// Set the chart data range
chart.DataRange = sheet.Range.get("A1:C5");
chart.SeriesDataFromRange = false;
// Set the chart position
chart.LeftColumn = 7;
chart.TopRow = 6;
chart.RightColumn = 16;
chart.BottomRow = 29;
// Set the chart title
chart.ChartTitle = "Product Sales by Region";
chart.ChartTitleArea.IsBold = true;
chart.ChartTitleArea.Size = 12;
// Set the legend position
chart.Legend.Position = xlsModule.LegendPositionType.Corner;
// Save the workbook
const outputFileName = 'CreateRadarChart.xlsx';
workbook.SaveToFile(outputFileName);
// Release resources
workbook.Dispose();
// Read the converted file from VFS and trigger download
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>Create a Radar Chart</h1>
<button onClick={createRadarChart}>Start</button>
</div>
);
}
export default App;
After running, the effect of creating a radar chart:

Style the Radar Chart
After creating the radar chart, you can further improve its appearance by setting the chart area, plot area, and series line colors. The steps are as follows:
- Get the radar chart object that has been created.
- Set the
ChartArea.Fill.ForeColorproperty to set the chart background color. - Set the
PlotArea.Fill.ForeColorproperty to set the plot area background color. - Set the
Series[i].Format.LineProperties.Colorproperty to specify the line color of each series;Series.get(0)andSeries.get(1)correspond to the two series in the data range. - Save the workbook.
Below is a complete code example that shows how to style the radar chart:
function App() {
const styleRadarChart = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check if the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the Excel file into VFS
const inputFileName = 'RadarChartData.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/static/data/`);
// Load the workbook
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile(inputFileName);
// Get the first worksheet
let sheet = workbook.Worksheets.get(0);
// Add a radar chart
let chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.Radar });
// Set the chart data range
chart.DataRange = sheet.Range.get("A1:C5");
chart.SeriesDataFromRange = false;
// Set the chart position
chart.LeftColumn = 7;
chart.TopRow = 6;
chart.RightColumn = 16;
chart.BottomRow = 29;
// Set the chart title
chart.ChartTitle = "Product Sales by Region";
chart.ChartTitleArea.IsBold = true;
chart.ChartTitleArea.Size = 12;
// Style the radar chart
// Set the chart area background color
chart.ChartArea.Fill.ForeColor = xlsModule.Color.get_LightCyan();
// Set the plot area background color
chart.PlotArea.Fill.ForeColor = xlsModule.Color.get_LightYellow();
// Set the color of the first series line
chart.Series.get(0).Format.LineProperties.Color = xlsModule.Color.get_Orange();
// Set the color of the second series line
chart.Series.get(1).Format.LineProperties.Color = xlsModule.Color.get_CornflowerBlue();
// Save the workbook
const outputFileName = 'StyledRadarChart.xlsx';
workbook.SaveToFile(outputFileName);
// Release resources
workbook.Dispose();
// Read the converted file from VFS and trigger download
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>Style the Radar Chart</h1>
<button onClick={styleRadarChart}>Start</button>
</div>
);
}
export default App;
After running, the effect of styling the radar chart:

Frequently Asked Questions
The radar chart does not change after setting a series color
Reason: A radar chart draws each series as a line, so setting a fill color with Series.get(i).Format.Fill has no effect.
Solution: Set the line color through Series.get(i).Format.LineProperties.Color, for example:
chart.Series.get(0).Format.LineProperties.Color = xlsModule.Color.get_Orange();
How do I adjust the legend position of a radar chart?
Reason: The legend is docked to the right of the chart by default, where it competes with the plot area for width — especially noticeable on a radar chart, which already takes up a lot of horizontal space.
Solution: Set the legend position with the chart.Legend.Position property. The available values are LegendPositionType.Bottom, Corner, Top, Right, Left, and NotDocked. For example, to move the legend to the top-right corner:
chart.Legend.Position = xlsModule.LegendPositionType.Corner;
Get a Free License
Spire.XLS for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.
In data analysis scenarios, bubble charts help intuitively display multi-dimensional data relationships. Each data point in a bubble chart is defined by three values: X-axis value, Y-axis value, and bubble size. Spire.XLS for JavaScript provides rich charting APIs that support creating bubble charts directly in the browser via WebAssembly, without requiring a backend service.
This article covers two core features:
For installation and project configuration, please refer to Integrating Spire.XLS for JavaScript in a React Project. The following examples assume Spire.XLS is already installed and the WebAssembly module has been initialized.
Create a Bubble Chart
A bubble chart can be added to a worksheet using the sheet.Charts.Add() method. The specific steps are as follows:
- Create a
Workbookobject and get the first worksheet. - Add a bubble chart via
Charts.Add(ExcelChartType.Bubble). - Set the
DataRangeproperty to specify the chart data area. - Set
SeriesDataFromRange = falseto indicate that data is not obtained from row/column layout. - Set
Series[0].Bubblesto specify the bubble size data range. - Set the chart title, position, and dimensions.
- Save the workbook via
Workbook.SaveToFile().
The following is a complete code example that demonstrates creating a bubble chart in React:
function App() {
const createBubbleChart = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check if the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the Excel file into VFS
const inputFileName = 'CreateBubbleChart.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/static/data/`);
// Load the workbook
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile(inputFileName);
// Get the first worksheet
let sheet = workbook.Worksheets.get(0);
// Add a bubble chart
let chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.Bubble });
// Set the chart data range
chart.DataRange = sheet.Range.get("A1:C5");
chart.SeriesDataFromRange = false;
// Set the bubble sizes
chart.Series.get(0).Bubbles = sheet.Range.get("C2:C5");
// Set the chart position
chart.LeftColumn = 7;
chart.TopRow = 6;
chart.RightColumn = 16;
chart.BottomRow = 29;
// Set the chart title
chart.ChartTitle = "Bubble Chart";
chart.ChartTitleArea.IsBold = true;
chart.ChartTitleArea.Size = 12;
// Save the workbook
const outputFileName = 'CreateBubbleChart.xlsx';
workbook.SaveToFile(outputFileName);
// Dispose resources
workbook.Dispose();
// Read the converted file from VFS and trigger download
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>Create Bubble Chart</h1>
<button onClick={createBubbleChart}>Start</button>
</div>
);
}
export default App;
After running the code, the effect of creating a bubble chart is as follows:

Style the Bubble Chart
After creating a bubble chart, you can enhance its appearance by setting the chart area, plot area, and series colors. The specific steps are as follows:
- Get the created bubble chart object.
- Set the
ChartArea.Fill.ForeColorproperty to set the chart background color. - Set the
PlotArea.Fill.ForeColorproperty to set the plot area background color. - Set the
Series[0].Format.Fill.ForeColorproperty to set the series color. - Set the
Series[0].HasDataLabelsproperty to enable data labels, and specify the label content throughDataPoints.DefaultDataPoint.DataLabels. - Save the workbook.
The following is a complete code example that demonstrates how to style a bubble chart:
function App() {
const styleBubbleChart = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check if the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the Excel file into VFS
const inputFileName = 'CreateBubbleChart.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/static/data/`);
// Load the workbook
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile(inputFileName);
// Get the first worksheet
let sheet = workbook.Worksheets.get(0);
// Add a bubble chart
let chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.Bubble });
// Set the chart data range
chart.DataRange = sheet.Range.get("A1:C5");
chart.SeriesDataFromRange = false;
// Set the bubble sizes
chart.Series.get(0).Bubbles = sheet.Range.get("C2:C5");
// Set the chart position
chart.LeftColumn = 7;
chart.TopRow = 6;
chart.RightColumn = 16;
chart.BottomRow = 29;
// Set the chart title
chart.ChartTitle = "Bubble Chart";
chart.ChartTitleArea.IsBold = true;
chart.ChartTitleArea.Size = 12;
// Style the bubble chart
// Set chart area background color
chart.ChartArea.Fill.ForeColor = xlsModule.Color.get_LightCyan();
// Set plot area background color
chart.PlotArea.Fill.ForeColor = xlsModule.Color.get_LightYellow();
// Set series color
chart.Series.get(0).Format.Fill.FillType = xlsModule.ShapeFillType.SolidColor;
chart.Series.get(0).Format.Fill.ForeColor = xlsModule.Color.get_Orange();
// Enable and set data labels
chart.Series.get(0).HasDataLabels = true;
chart.Series.get(0).DataPoints.DefaultDataPoint.DataLabels.HasCategoryName = true;
chart.Series.get(0).DataPoints.DefaultDataPoint.DataLabels.HasValue = true;
// Save the workbook
const outputFileName = 'StyledBubbleChart.xlsx';
workbook.SaveToFile(outputFileName);
// Dispose resources
workbook.Dispose();
// Read the converted file from VFS and trigger download
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>Style Bubble Chart</h1>
<button onClick={styleBubbleChart}>Start</button>
</div>
);
}
export default App;
After running the code, the effect of styling the bubble chart is as follows:

Frequently Asked Questions
What do columns A, B, and C in DataRange represent?
Reason: Each data point in a bubble chart needs three values — an X-axis value, a Y-axis value, and a bubble size — and the three columns specified by DataRange correspond to them exactly.
Solution: Taking chart.DataRange = sheet.Range.get("A1:C5") as an example, column A serves as the category (X axis), column B as the Y-axis value, and column C is set as the bubble size through chart.Series.get(0).Bubbles = sheet.Range.get("C2:C5"). The order of these three columns cannot be swapped, or both the point positions and the bubble sizes will be wrong.
Why does the data range start at A1 instead of A2?
Reason: The first row of the data range is used as the series name.
Solution: Include the header row in DataRange. In the example, the "Sales" shown in the legend comes from cell B1, so the data range is written as A1:C5 rather than A2:C5.
Get a Free License
Spire.XLS for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.