
Гистограмма с регионами и месяцами на одной и той же оси имеет две проблемы, и это не одна и та же проблема. Первая — подписи категорий сливаются в одну строку — "North", "Jan", "North", "Feb" — и читателю приходится мысленно заново группировать, какой месяц относится к какому региону. Вторая — когда ряд темпов роста добавляется рядом с рядом продаж, измеряемым миллионами, темп роста превращается в плоскую линию, прижатую к базовой линии, потому что одна ось значений не может обслуживать два порядка величин одновременно.
Многоуровневые подписи категорий решают первую проблему. Вспомогательная ось решает вторую. Это независимые возможности, которые просто оказываются полезны на одной диаграмме, и Spire.XLS for JavaScript поддерживает обе через API осей диаграммы — прямо в браузере на WebAssembly, с перемещением файлов через виртуальную файловую систему (VFS) и без участия серверной части.
О настройке проекта см. в разделе Интеграция Spire.XLS for JavaScript в проект React. Примеры ниже предполагают, что пакет установлен, а модуль WebAssembly инициализирован.
Когда одной оси недостаточно
Обе проблемы встречаются на одном и том же типе листа — там, где категории имеют иерархию, а значения сильно различаются по величине, — но возникают они по разным причинам:
| Проблема | Из-за чего возникает | Как выглядит диаграмма | Что это исправляет |
|---|---|---|---|
| Подписи скучиваются в одну строку | Категории иерархичны (регион → месяц, год → квартал), но ось обрабатывает их как плоские | Одна строка подписей, где внешние и внутренние категории чередуются без визуальной группировки | Многоуровневые подписи категорий |
| Один ряд вырождается в линию | Два ряда различаются на порядки (продажи в миллионах, рост в процентах), но используют одну ось значений | Меньший ряд сжимается почти до нуля, и его вариация не видна | Вспомогательная ось |
Ни то, ни другое не является проблемой оформления. Обе связаны с тем, что ось не знает того, что ей нужно знать, — что категории имеют уровни или что значения имеют несовместимые шкалы. Два раздела ниже рассматривают их по очереди, причём второй опирается на первый, так что итоговая диаграмма содержит оба исправления.
Предварительные требования
Вам понадобится проект React с установленным Spire.XLS for JavaScript и инициализированным модулем WebAssembly, доступным по адресу window.wasmModule.spirexls. Пример загружает шрифт и заранее подготовленный файл данных в VFS перед созданием диаграммы; оба файла берутся из папки public проекта.
Данные, лежащие в основе многоуровневых подписей
Многоуровневые подписи не создаются одним лишь свойством — они считываются из данных. Ось категорий отрисовывает столько уровней подписей, сколько столбцов содержит диапазон, на который указывает CategoryLabels. Поэтому лист должен быть организован так, чтобы иерархия располагалась по столбцам:
| Столбец A (внешний) | Столбец B (внутренний) | Столбец C (значения) |
|---|---|---|
| North | Jan | 120,000 |
| North | Feb | 135,000 |
| South | Jan | 98,000 |
| South | Feb | 110,000 |
Внешние подписи в столбце A объединены по строкам, которые они охватывают, — "North" охватывает две строки для Jan и Feb. Именно это объединение приводит к тому, что при отрисовке диаграммы уровень визуально сворачивается в одну подпись на группу. Без него ось всё равно показывает два уровня, но внешний уровень повторяет подпись в каждой строке вместо группировки.
Это вопрос компоновки данных, а не API диаграмм. Коду диаграммы достаточно указать CategoryLabels на оба столбца; объединены ли внешние ячейки — решается в книге, а не в объекте диаграммы.
Создание диаграммы с многоуровневыми подписями категорий
Когда данные разложены, код диаграммы делает две вещи: указывает CategoryLabels на диапазон, охватывающий и внешний, и внутренний столбец, и включает MultiLevelLable, чтобы ось развернула эти столбцы в расположенные друг над другом строки. Шаги таковы:
- Загрузите шрифт и файл тестовых данных в VFS.
- Загрузите книгу и получите лист.
- Добавьте гистограмму и добавьте именованный ряд продаж.
- Укажите подписи категорий на столбец региона и на столбец месяца.
- Включите многоуровневые подписи для оси категорий и сохраните книгу.
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;
Диаграмма с многоуровневыми подписями категорий, каждый уровень в своей строке

Диапазон A2:B7 — это то, что заставляет ось показывать два уровня. Привязка одностолбцового диапазона, например B2:B7, всё равно даст один уровень, даже если для MultiLevelLable задано значение true, — свойство управляет тем, разворачиваются ли несколько уровней в строки, а не тем, существует ли уровень данных для разворачивания.
Почему ряд роста исчезает
Добавьте второй ряд для годового роста — значения в районе 12–13, проценты — и постройте его на той же оси значений, что и продажи. Столбцы продаж достигают 120 000; темп роста достигает 12. На оси с диапазоном от 0 до 140 000 число 12 неотличимо от нуля. Ряд присутствует, данные верны, а диаграмма показывает линию, плоскую у базовой линии.
Это не ошибка ни в данных, ни в диаграмме. Это ось значений делает свою работу — отображает диапазон, охватывающий наибольший ряд, — за счёт наименьшего. Единственный способ ясно увидеть оба ряда — дать каждому свою шкалу, и именно это делает вспомогательная ось.
Перемещение ряда на вспомогательную ось
Ряд роста добавляется как линия, а не как столбец. Линия не занимает ширину столбца, поэтому она чётко читается на фоне ряда столбцов, использующего те же категории. Чтобы убрать её с основной оси, достаточно одного свойства: UsePrimaryAxis = false. Шаги таковы:
- Загрузите шрифт и файл тестовых данных в VFS.
- Загрузите книгу и получите лист.
- Добавьте гистограмму и добавьте именованный ряд продаж.
- Добавьте ряд роста как линию.
- Переместите ряд роста на вспомогательную ось и сохраните книгу.
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 влияет только на тот ряд, для которого оно задано; все остальные ряды остаются на основной оси. Диаграмма приобретает вторую пару осей — значений и категорий, — что даёт ей два отдельных диапазона шкал. Series.Add принимает имя ряда одновременно, поэтому в легенде отображается переданное имя, а не автоматически созданное "Series 1".
Настройка шкалы вспомогательной оси
Как только ряд перемещается на вспомогательную ось, эта ось вычисляет собственную шкалу — независимо от основной. Два диапазона ничего не знают друг о друге, поэтому вспомогательная ось может выбрать границы, которые плохо согласуются с данными.
PrimaryValueAxis.MinValue, MaxValue и MajorUnit управляют только основной осью. Чтобы задать шкалу вспомогательной оси, используйте 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;
Задавайте шкалу после того, как ряд был перемещён на вспомогательную ось. Пока ни один ряд не использует вспомогательную ось, присваивание принимается, но никогда не записывается в файл — оси не существует в выходных данных, пока на неё не построен ряд.
Типичные проблемы
Ось категорий показывает только один уровень подписей.
CategoryLabels указывает на одностолбцовый диапазон. Число уровней определяется тем, сколько столбцов охватывает диапазон, а не свойством MultiLevelLable. Укажите многоколоночный диапазон, например A2:B7, и убедитесь, что внешние ячейки подписей объединены в данных.
Шкала вспомогательной оси выглядит неправильно.
Основная и вспомогательная оси значений вычисляют свои шкалы независимо. Установка MinValue или MaxValue в PrimaryValueAxis не влияет на вспомогательную ось. Используйте chart.SecondaryValueAxis, чтобы задать её шкалу напрямую, и делайте это после перемещения ряда на неё.
Ряд роста по-прежнему выглядит плоским после добавления вспомогательной оси.
Проверьте, что UsePrimaryAxis = false задано для ряда роста, а не для ряда продаж. Свойство действует на отдельный ряд — если задать его не тому ряду, на вспомогательную ось переместится не тот ряд.
В легенде отображается "Series 1" вместо имени ряда.
Имя не было передано в Series.Add. Используйте chart.Series.Add({ name: "Sales", ... }), чтобы легенда взяла нужное вам имя, а не автоматически созданную подпись.
Часто задаваемые вопросы
Может ли быть больше двух уровней подписей категорий?
Да. Число уровней определяется количеством столбцов, которые охватывает диапазон CategoryLabels. Диапазон из трёх столбцов даёт три уровня — например, год, квартал и месяц. Внешние ячейки подписей должны быть объединены в данных, чтобы каждый уровень группировался правильно.
Работает ли вспомогательная ось с типами диаграмм, отличными от гистограммы и линейчатой?
Да. Вспомогательная ось не привязана к конкретному типу диаграммы. Распространённый вариант — столбцы плюс линия (линия не занимает ширину столбца и чётко читается на фоне столбцов), но любой ряд можно переместить на вспомогательную ось, задав UsePrimaryAxis = false.
Нужен ли установленный Excel для создания таких диаграмм?
Нет. Движок электронных таблиц поставляется вместе с пакетом и работает как WebAssembly в браузере. Книга полностью создаётся, снабжается диаграммой и сохраняется на стороне клиента.
Можно ли управлять вспомогательной осью категорий отдельно?
Когда ряд перемещается на вспомогательную ось, диаграмма приобретает вспомогательную ось категорий в дополнение к вспомогательной оси значений. По умолчанию обе оси категорий используют одни и те же подписи категорий, поэтому многоуровневые подписи применяются к обеим.
Совместим ли выходной файл с Excel?
Да. Книга сохраняется в формате .xlsx, а диаграмма — включая многоуровневые подписи и вспомогательную ось — записывается в виде стандартного XML диаграмм, который Excel читает изначально.