Skip to content
ABCrimson edited this page Mar 4, 2026 · 1 revision

Charts & Visualizations

modern-xlsx supports creating, reading, and roundtripping Excel charts with full ECMA-376 DrawingML compliance. 10 chart types with a fluent builder API.

Quick Start

import { initWasm, Workbook, ChartBuilder } from 'modern-xlsx';

await initWasm();

const wb = new Workbook();
const ws = wb.addSheet('Sheet1');

// Add data
ws.cell('A1').value = 'Product';
ws.cell('B1').value = 'Sales';
ws.cell('A2').value = 'Widget A';
ws.cell('B2').value = 1200;
ws.cell('A3').value = 'Widget B';
ws.cell('B3').value = 850;
ws.cell('A4').value = 'Widget C';
ws.cell('B4').value = 2100;

// Create a bar chart
ws.addChart('bar', (b) => {
  b.title('Product Sales')
   .addSeries({
     name: 'Sales',
     categories: 'Sheet1!$A$2:$A$4',
     values: 'Sheet1!$B$2:$B$4',
   })
   .catAxis({ title: 'Product' })
   .valAxis({ title: 'Revenue ($)' })
   .legend('bottom');
});

await wb.toFile('charts.xlsx');

Chart Types

Type Description Groupings
bar Horizontal bars clustered, stacked, percentStacked
col Vertical columns clustered, stacked, percentStacked
line Line chart standard, stacked, percentStacked
pie Pie chart (single series)
doughnut Donut chart (configurable hole size)
scatter XY scatter plot lineMarker, smoothMarker, line, smooth
area Area chart standard, stacked, percentStacked
radar Radar/spider chart radar, filled
bubble Bubble chart (bubble size series)
stock Stock OHLC chart (open-high-low-close)

ChartBuilder API

The ChartBuilder provides a fluent interface for constructing chart data models.

Creating a ChartBuilder

import { ChartBuilder } from 'modern-xlsx';

const chart = new ChartBuilder('bar')
  .title('My Chart')
  .addSeries({ name: 'Series 1', values: 'Sheet1!$B$2:$B$5' })
  .build();

Adding to a Worksheet

Two approaches:

// 1. Callback-based (recommended)
ws.addChart('bar', (builder) => {
  builder
    .title('Sales')
    .addSeries({ name: 'Revenue', values: 'Sheet1!$B$2:$B$5' });
});

// 2. Pre-built chart data
const chartData = new ChartBuilder('line').title('Trend').build();
ws.addChartData(chartData);

Builder Methods

title(text)

Set the chart title.

builder.title('Quarterly Revenue');

addSeries(options)

Add a data series. Call multiple times for multiple series.

builder.addSeries({
  name: 'Revenue',           // Series name (displayed in legend)
  categories: 'Sheet1!$A$2:$A$5', // Category labels (optional)
  values: 'Sheet1!$B$2:$B$5',     // Data values (required)
  marker: 'circle',          // Marker style (optional)
  trendline: { type: 'linear' },   // Trendline (optional)
  errorBars: { type: 'percentage', value: 5 }, // Error bars (optional)
});

AddSeriesOptions:

Option Type Description
name string Series name for legend
categories string Cell range for category axis labels
values string Cell range for data values (required)
bubbleSizes string Cell range for bubble sizes (bubble charts)
marker string Marker style: circle, square, diamond, triangle, etc.
trendline object Trendline configuration
errorBars object Error bar configuration

catAxis(options) / valAxis(options)

Configure the category and value axes.

builder
  .catAxis({ title: 'Month', position: 'b' })
  .valAxis({ title: 'Revenue', position: 'l', min: 0, max: 10000 });

AxisOptions:

Option Type Description
title string Axis title text
position 'b' | 't' | 'l' | 'r' Axis position
min number Minimum axis value
max number Maximum axis value
majorTickMark string Major tick mark style
minorTickMark string Minor tick mark style
tickLblPos string Tick label position

legend(position)

Set legend position.

builder.legend('bottom'); // 'bottom' | 'top' | 'left' | 'right' | 'topRight'

dataLabels(options)

Show data labels on chart.

builder.dataLabels({
  showVal: true,
  showCatName: false,
  showSerName: false,
});

grouping(type)

Set chart grouping (bar, column, line, area).

builder.grouping('stacked'); // 'clustered' | 'stacked' | 'percentStacked' | 'standard'

scatterStyle(style)

Set scatter chart style.

builder.scatterStyle('smoothMarker'); // 'lineMarker' | 'smoothMarker' | 'line' | 'smooth'

radarStyle(style)

Set radar chart style.

builder.radarStyle('filled'); // 'radar' | 'filled'

holeSize(percent)

Set doughnut chart hole size (10-90).

builder.holeSize(50);

barDirection(dir)

Set bar chart direction.

builder.barDirection('bar'); // 'bar' (horizontal) | 'col' (vertical)

style(id)

Apply a chart style preset.

builder.style('colorful1'); // See Chart Style Presets below

anchor(config)

Set chart position within the worksheet.

builder.anchor({
  fromCol: 3, fromRow: 1,
  toCol: 10, toRow: 15,
});

view3d(config)

Enable 3D rotation.

builder.view3d({
  rotX: 15,
  rotY: 20,
  perspective: 30,
  rAngAx: true,
});

showDataTable()

Show a data table below the chart.

builder.showDataTable();

plotLayout(layout)

Set manual plot area positioning (0-1 normalized coordinates).

builder.plotLayout({ x: 0.1, y: 0.1, w: 0.8, h: 0.7 });

Trendlines

Add trendlines to any series:

builder.addSeries({
  name: 'Sales',
  values: 'Sheet1!$B$2:$B$13',
  trendline: {
    type: 'linear',       // linear | exponential | logarithmic | polynomial | power | movingAverage
    order: 2,             // Polynomial order (2-6, for polynomial type)
    period: 3,            // Moving average period (for movingAverage type)
    forward: 2,           // Forecast forward periods
    backward: 1,          // Forecast backward periods
    displayEquation: true,
    displayRSquared: true,
    name: 'Linear Trend',
  },
});

Trendline Types

Type Use Case
linear Straight line best fit
exponential Exponential growth/decay
logarithmic Logarithmic curve
polynomial Polynomial fit (order 2-6)
power Power law relationship
movingAverage Smoothing with N-period window

Error Bars

Add error bars to series:

builder.addSeries({
  name: 'Measurements',
  values: 'Sheet1!$B$2:$B$10',
  errorBars: {
    type: 'percentage',   // fixed | percentage | stdDev | stdErr | custom
    direction: 'both',    // both | plus | minus
    value: 10,            // Error value (for fixed, percentage, stdDev)
    plusRef: 'Sheet1!$C$2:$C$10',  // Custom plus values
    minusRef: 'Sheet1!$D$2:$D$10', // Custom minus values
  },
});

Combo Charts

Create charts with two chart types sharing the same plot area:

const chart = new ChartBuilder('col')
  .title('Revenue vs Growth Rate')
  .addSeries({
    name: 'Revenue',
    values: 'Sheet1!$B$2:$B$5',
  })
  .build();

// Add secondary chart on secondary axis
chart.secondaryChart = {
  type: 'line',
  series: [{
    name: 'Growth %',
    values: { ref: 'Sheet1!$C$2:$C$5' },
  }],
};
chart.secondaryValAxis = {
  position: 'r',
  title: { text: 'Growth Rate (%)' },
};

ws.addChartData(chart);

Chart Style Presets

modern-xlsx includes 8 color palettes:

import { getChartStylePalette, CHART_STYLE_PALETTES } from 'modern-xlsx';

// Get a palette by name
const palette = getChartStylePalette('colorful1');
// Returns: ['4472C4', 'ED7D31', 'A5A5A5', 'FFC000', '5B9BD5', '70AD47']

// Apply to chart
builder.style('colorful1');

Available Palettes:

ID Description
colorful1 Default Office colors
colorful2 Warm tones
colorful3 Cool tones
colorful4 Earth tones
monochrome1 Blue monochrome
monochrome2 Orange monochrome
monochrome3 Green monochrome
monochrome4 Purple monochrome

Reading Charts

Charts are automatically parsed when reading XLSX files:

import { readFile } from 'modern-xlsx';

const wb = await readFile('charts.xlsx');
const ws = wb.getSheet('Sheet1');

// Access all charts on the sheet
const charts = ws.charts;
console.log(`Found ${charts.length} chart(s)`);

for (const chart of charts) {
  console.log('Type:', chart.chart.type);
  console.log('Title:', chart.chart.title?.text);
  console.log('Series:', chart.chart.series.length);

  for (const s of chart.chart.series) {
    console.log(`  - ${s.name}: ${s.values?.ref}`);
  }
}

Chart Data Structure

interface WorksheetChartData {
  chart: ChartDataModel;
  anchor: ChartAnchorData;
}

interface ChartDataModel {
  type: ChartType;
  title?: ChartTitleData;
  series: ChartSeriesData[];
  catAxis?: ChartAxisData;
  valAxis?: ChartAxisData;
  legend?: ChartLegendData;
  dataLabels?: DataLabelsData;
  grouping?: ChartGrouping;
  scatterStyle?: ScatterStyle;
  radarStyle?: RadarStyle;
  holeSize?: number;
  barDir?: string;
  style?: string;
  plotLayout?: ManualLayoutData;
  secondaryChart?: { type: ChartType; series: ChartSeriesData[] };
  secondaryValAxis?: ChartAxisData;
  showDataTable?: boolean;
  view3d?: View3DData;
}

Removing Charts

const ws = wb.getSheet('Sheet1');

// Remove by index
ws.removeChart(0);

Roundtrip Fidelity

Charts written by modern-xlsx survive a write-read roundtrip with full fidelity:

// Write
const wb1 = new Workbook();
const ws1 = wb1.addSheet('Sheet1');
ws1.addChart('bar', (b) => {
  b.title('Test').addSeries({ name: 'S1', values: 'Sheet1!$A$1:$A$5' });
});
const buf = await wb1.toBuffer();

// Read back
const wb2 = await readBuffer(buf);
const ws2 = wb2.getSheet('Sheet1');
console.log(ws2.charts[0].chart.title.text); // 'Test'
console.log(ws2.charts[0].chart.series[0].name); // 'S1'

All chart properties are preserved: type, title, series, categories, values, axes, legend, trendlines, error bars, 3D rotation, data labels, combo charts, and style.

Examples

Multi-Series Column Chart

ws.addChart('col', (b) => {
  b.title('Quarterly Revenue by Region')
   .addSeries({
     name: 'North',
     categories: 'Sheet1!$A$2:$A$5',
     values: 'Sheet1!$B$2:$B$5',
   })
   .addSeries({
     name: 'South',
     categories: 'Sheet1!$A$2:$A$5',
     values: 'Sheet1!$C$2:$C$5',
   })
   .addSeries({
     name: 'West',
     categories: 'Sheet1!$A$2:$A$5',
     values: 'Sheet1!$D$2:$D$5',
   })
   .grouping('clustered')
   .legend('bottom');
});

Pie Chart

ws.addChart('pie', (b) => {
  b.title('Market Share')
   .addSeries({
     name: 'Share',
     categories: 'Sheet1!$A$2:$A$5',
     values: 'Sheet1!$B$2:$B$5',
   })
   .dataLabels({ showPercent: true, showCatName: true });
});

Scatter with Trendline

ws.addChart('scatter', (b) => {
  b.title('Correlation Analysis')
   .addSeries({
     name: 'Data Points',
     categories: 'Sheet1!$A$2:$A$20',
     values: 'Sheet1!$B$2:$B$20',
     marker: 'circle',
     trendline: {
       type: 'linear',
       displayEquation: true,
       displayRSquared: true,
     },
   })
   .scatterStyle('lineMarker');
});

Stacked Area Chart

ws.addChart('area', (b) => {
  b.title('Cumulative Sales')
   .addSeries({ name: 'Online', values: 'Sheet1!$B$2:$B$13' })
   .addSeries({ name: 'Retail', values: 'Sheet1!$C$2:$C$13' })
   .grouping('stacked')
   .legend('right');
});

Doughnut Chart

ws.addChart('doughnut', (b) => {
  b.title('Budget Allocation')
   .addSeries({
     name: 'Budget',
     categories: 'Sheet1!$A$2:$A$6',
     values: 'Sheet1!$B$2:$B$6',
   })
   .holeSize(60)
   .dataLabels({ showPercent: true });
});

3D Column Chart

ws.addChart('col', (b) => {
  b.title('3D Sales View')
   .addSeries({ name: 'Sales', values: 'Sheet1!$B$2:$B$5' })
   .view3d({ rotX: 15, rotY: 20, perspective: 30 });
});

Clone this wiki locally