Axis labels provide clear identification and context for the data represented on each axis in a chart.
Collision Avoidance Copy Link
AG Charts has a number of methods to avoid axis label collisions, which occur when two labels overlap or when a label exceeds the axis's maxThicknessRatio.
import React, { useState, Fragment } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
AgBarSeriesOptions,
AgCartesianChartOptions,
AgCategoryAxisOptions,
AgNumberAxisOptions,
BarSeriesModule,
CategoryAxisModule,
LegendModule,
ModuleRegistry,
NumberAxisModule,
TextWrap,
} from "ag-charts-community";
import { getData } from "./data";
import clone from "clone";
import "./styles.css";
ModuleRegistry.registerModules([
BarSeriesModule,
CategoryAxisModule,
LegendModule,
NumberAxisModule,
]);
const ChartExample = () => {
const [options, setOptions] = useState<AgCartesianChartOptions>({
data: getData(),
series: [
{
type: "bar",
xKey: "year",
yKey: "value",
},
],
axes: {
x: {
type: "category",
label: {},
},
y: {
type: "number",
label: {},
},
},
});
const reset = () => {
const nextOptions = clone(options);
const categoryAxis = nextOptions.axes!.x! as AgCategoryAxisOptions;
const numberAxis = nextOptions.axes!.y! as AgNumberAxisOptions;
delete categoryAxis.label!.rotation;
delete categoryAxis.label!.autoRotate;
delete categoryAxis.label!.avoidCollisions;
delete categoryAxis.label!.truncate;
delete numberAxis.label!.rotation;
delete numberAxis.label!.autoRotate;
delete numberAxis.label!.avoidCollisions;
delete numberAxis.label!.truncate;
(nextOptions.series![0] as AgBarSeriesOptions).xKey = "year";
setOptions(nextOptions);
};
const rotationChange = (e: Event) => {
const nextOptions = clone(options);
const categoryAxis = nextOptions.axes!.x! as AgCategoryAxisOptions;
const numberAxis = nextOptions.axes!.y! as AgNumberAxisOptions;
delete categoryAxis.label!.rotation;
delete numberAxis.label!.rotation;
const value = (e.target as HTMLInputElement).value;
switch (value) {
case "auto":
categoryAxis.label!.autoRotate = true;
numberAxis.label!.autoRotate = true;
break;
case "fixed":
categoryAxis.label!.rotation = 45;
numberAxis.label!.rotation = 45;
categoryAxis.label!.autoRotate = false;
numberAxis.label!.autoRotate = false;
break;
case "disabled":
categoryAxis.label!.autoRotate = false;
numberAxis.label!.autoRotate = false;
break;
}
setOptions(nextOptions);
};
const labelChange = (e: Event) => {
const nextOptions = clone(options);
const value = (e.target as HTMLInputElement).value;
(nextOptions.series![0] as AgBarSeriesOptions).xKey = value;
setOptions(nextOptions);
};
const truncationChange = (e: Event) => {
const nextOptions = clone(options);
const categoryAxis = nextOptions.axes!.x! as AgCategoryAxisOptions;
const numberAxis = nextOptions.axes!.y! as AgNumberAxisOptions;
delete categoryAxis.label!.rotation;
delete numberAxis.label!.rotation;
const value = (e.target as HTMLInputElement).value;
const enabled = value === "enabled";
categoryAxis.label!.truncate = enabled;
numberAxis.label!.truncate = enabled;
setOptions(nextOptions);
};
const avoidanceChange = (e: Event) => {
const nextOptions = clone(options);
const categoryAxis = nextOptions.axes!.x! as AgCategoryAxisOptions;
const numberAxis = nextOptions.axes!.y! as AgNumberAxisOptions;
delete categoryAxis.label!.rotation;
delete numberAxis.label!.rotation;
const value = (e.target as HTMLInputElement).value;
const enabled = value === "enabled";
categoryAxis.label!.avoidCollisions = enabled;
numberAxis.label!.avoidCollisions = enabled;
setOptions(nextOptions);
};
const wrapChange = (e: Event) => {
const nextOptions = clone(options);
const categoryAxis = nextOptions.axes!.x! as AgCategoryAxisOptions;
const numberAxis = nextOptions.axes!.y! as AgNumberAxisOptions;
const value = (e.target as HTMLInputElement).value as TextWrap;
categoryAxis.label!.wrapping = value;
numberAxis.label!.wrapping = value;
setOptions(nextOptions);
};
return (
<Fragment>
<div className="example-controls">
<div className="controls-row">
<div className="gap-right">
<label htmlFor="avoidance-select">Avoid Collisions:</label>
<select
id="avoidance-select"
onChange={(event) => avoidanceChange(event)}
>
<option value="enabled">Enabled (default)</option>
<option value="disabled">Disabled</option>
</select>
</div>
<div>
<label htmlFor="label-select">Labels: </label>
<select id="label-select" onChange={(event) => labelChange(event)}>
<option value="year">Short</option>
<option value="sportStar">Long</option>
</select>
</div>
<button className="push-right" onClick={reset}>
Reset
</button>
</div>
<div className="controls-row">
<div className="gap-right">
<label htmlFor="truncation-select">Truncation: </label>
<select
id="truncation-select"
onChange={(event) => truncationChange(event)}
>
<option value="disabled">Disabled (default)</option>
<option value="enabled">Enabled</option>
</select>
</div>
<div className="gap-right">
<label htmlFor="rotation-select">Rotation: </label>
<select
id="rotation-select"
onChange={(event) => rotationChange(event)}
>
<option value="auto">Auto rotation (default)</option>
<option value="disabled">No rotation</option>
<option value="fixed">Fixed rotation</option>
</select>
</div>
<div>
<label htmlFor="wrap-select">Wrapping: </label>
<select id="wrap-select" onChange={(event) => wrapChange(event)}>
<option value="on-space">on-space (default)</option>
<option value="always">always</option>
<option value="hyphenate">hyphenate</option>
<option value="never">never</option>
</select>
</div>
</div>
</div>
<div className="resizable-container">
<AgCharts options={options} className="resizable" />
</div>
</Fragment>
);
};
const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);
.resizable-container {
height: 100%;
padding: 4px;
height: 100%;
border-radius: 8px;
background-color: color-mix(in srgb, var(--chart-bg), var(--chart-border) 10%);
border: 1px solid var(--chart-border);
overflow: hidden;
}
.resizable {
width: 100%;
max-width: 100%;
max-height: 100%;
overflow: hidden;
resize: both;
}
function sfc32(a: number, b: number, c: number, d: number) {
return function () {
a >>>= 0;
b >>>= 0;
c >>>= 0;
d >>>= 0;
let t = (a + b) | 0;
a = b ^ (b >>> 9);
b = (c + (c << 3)) | 0;
c = (c << 21) | (c >>> 11);
d = (d + 1) | 0;
t = (t + d) | 0;
c = (c + t) | 0;
return (t >>> 0) / 4294967296;
};
}
function seedRandom(seed = 1337): () => number {
const realSeed = seed ^ 0xdeadbeef;
return sfc32(0x9e3779b9, 0x243f6a88, 0xb7e15162, realSeed);
}
const years: number[] = [];
for (let year = 2012; year <= 2022; year++) {
years.push(year);
}
const sportStars = [
"Usain Bolt",
"Serena Williams",
"Michael Phelps",
"Simone Biles",
"Lionel Messi",
"Roger Federer",
"Katie Ledecky",
"Cristiano Ronaldo",
"Naomi Osaka",
"LeBron James",
"Max Verstappen",
];
export function getData(): any[] {
const random = seedRandom(67890);
return years.map((year, idx) => ({
year,
sportStar: sportStars[idx],
value: Math.round(random() * 1000),
}));
}
In this example:
- Use the grab handle in the bottom right of the example to resize the chart. Observe how the labels behave with the different options applied.
- The first dropdown allows enabling and disabling collision avoidance altogether.
- The second dropdown buttons allows changing the length of the labels. This more easily shows the wrapping and skipping behaviour.
- The Rotation, Wrapping and Truncation controls enable exploration of their behaviour with all the available options.
- If enabled, labels try wrapping, then truncation, rotation, and finally skipping if needed.
Collision avoidance is enabled by default, to disable set label.avoidCollisions to false.
{
label: {
avoidCollisions: false,
},
} Wrapping Copy Link
Text wrapping allows labels to fit within constrained space by breaking across multiple lines.
Wrapping behaviour is set with the label.wrapping property:
'on-space'- Wraps only on whitespace. If no suitable space is found, the next avoidance strategy is used.'always'- Forces wrapping to fit within the available space, including breaking words if needed.'hyphenate'- Similar to'always', but inserts a hyphen (-) when wrapping in the middle of a word.'never'- Disables wrapping. Labels will remain on a single line, with the next avoidance strategy being used.
{
label: {
wrapping: 'hyphenate',
},
}This defaults to on-space for Category Axes, and to never on all other axes types.
Truncation Copy Link
Set truncate: true on a label to truncate the labelâs text when there isn't enough space to display it fully. This ensures as much as possible of each label remains readable without reducing the number of ticks.
When truncation occurs, hovering over the truncated text will display a tooltip with the full label text.
Truncation takes precedence over auto-rotation. When truncation is enabled, auto-rotation will not apply.
Rotation Copy Link
Rotating axis labels allows fitting more labels into a smaller area, at the expense of readability.
Three rotation options are available:
- No rotation.
- Fixed rotation - labels are always rotated by the amount specified in the
rotationproperty. - Automatic rotation - labels are rotated if any label will be wider than the gap between ticks.
Automatic rotation can be enabled or disabled using the autoRotate property. It is also possible to specify a rotation angle for the automatic rotation via the autoRotateAngle property.
Category axes have autoRotate enabled by default with a default autoRotateAngle of 335.
Skipping Copy Link
Label skipping is performed automatically when there is a high likelihood of collisions between labels. Labels that would collide are not displayed.
A collision is defined as labels coming within 10 pixels of each other. This minimum gap allowed between the axis labels before skipping can be configured using the label.minSpacing property.
{
label: {
minSpacing: 20,
},
}If autoRotate is enabled, rotation will be attempted before label skipping applies.
Alignment Copy Link
Axis labels are aligned within the axis thickness and bands, with a default alignment derived from the axis position and label rotation. Both directions can be set explicitly.
Text Alignment Copy Link
Use label.textAlign to control the horizontal alignment of labels. This is most useful on a vertical axis containing labels of differing width.
import React, { useState, Fragment } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
AgCartesianChartOptions,
AgNumberAxisOptions,
BarSeriesModule,
CategoryAxisModule,
ModuleRegistry,
NumberAxisModule,
} from "ag-charts-community";
import { getData } from "./data";
import clone from "clone";
type TextAlign = "left" | "center" | "right";
const initialXTextAlign: TextAlign = "center";
// 'left' is what a right-positioned vertical axis derives, so the labels start unchanged.
const initialYTextAlign: TextAlign = "left";
ModuleRegistry.registerModules([
BarSeriesModule,
CategoryAxisModule,
NumberAxisModule,
]);
const ChartExample = () => {
const [options, setOptions] = useState<AgCartesianChartOptions>({
data: getData(),
series: [
{
type: "bar",
xKey: "quarter",
yKey: "revenue",
},
],
axes: {
x: {
type: "category",
label: {
textAlign: initialXTextAlign,
},
},
y: {
type: "number",
position: "right",
label: {
textAlign: initialYTextAlign,
formatter: ({ value }) => `$${value.toLocaleString()}`,
},
} as AgNumberAxisOptions,
},
});
const setXTextAlign = (event: Event) => {
const nextOptions = clone(options);
nextOptions.axes!.x!.label!.textAlign = (event.target as HTMLInputElement)
.value as TextAlign;
setOptions(nextOptions);
};
const setYTextAlign = (event: Event) => {
const nextOptions = clone(options);
nextOptions.axes!.y!.label!.textAlign = (event.target as HTMLInputElement)
.value as TextAlign;
setOptions(nextOptions);
};
return (
<Fragment>
<div className="example-controls">
<div className="controls-row">
<span>X Axis:</span>
<div
className="button-group"
role="group"
aria-label="X Axis Text Alignment"
>
<input
type="radio"
id="x-text-align-left"
name="xTextAlign"
defaultValue="left"
onChange={(event) => setXTextAlign(event)}
/>
<label htmlFor="x-text-align-left">
<code>left</code>
</label>
<input
type="radio"
id="x-text-align-center"
name="xTextAlign"
defaultValue="center"
defaultChecked
onChange={(event) => setXTextAlign(event)}
/>
<label htmlFor="x-text-align-center">
<code>center</code>
</label>
<input
type="radio"
id="x-text-align-right"
name="xTextAlign"
defaultValue="right"
onChange={(event) => setXTextAlign(event)}
/>
<label htmlFor="x-text-align-right">
<code>right</code>
</label>
</div>
<span className="gap-left">Y Axis:</span>
<div
className="button-group"
role="group"
aria-label="Y Axis Text Alignment"
>
<input
type="radio"
id="y-text-align-left"
name="yTextAlign"
defaultValue="left"
defaultChecked
onChange={(event) => setYTextAlign(event)}
/>
<label htmlFor="y-text-align-left">
<code>left</code>
</label>
<input
type="radio"
id="y-text-align-center"
name="yTextAlign"
defaultValue="center"
onChange={(event) => setYTextAlign(event)}
/>
<label htmlFor="y-text-align-center">
<code>center</code>
</label>
<input
type="radio"
id="y-text-align-right"
name="yTextAlign"
defaultValue="right"
onChange={(event) => setYTextAlign(event)}
/>
<label htmlFor="y-text-align-right">
<code>right</code>
</label>
</div>
</div>
</div>
<AgCharts options={options} />
</Fragment>
);
};
const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);
export function getData(): { quarter: string; revenue: number }[] {
return [
{ quarter: "Q1 2024", revenue: 90000 },
{ quarter: "Q2 2024", revenue: 450000 },
{ quarter: "Q3 2024", revenue: 1200000 },
{ quarter: "Q4 2024", revenue: 750000 },
{ quarter: "Q1 2025", revenue: 1500000 },
{ quarter: "Q2 2025", revenue: 980000 },
];
}
{
axes: {
x: {
label: {
textAlign: 'center',
},
},
y: {
label: {
textAlign: 'left',
},
},
},
}In this example:
- Use the buttons to see the
textAlignoptions for each axis. - On the vertical number axis, labels are horizontally aligned within the axis thickness.
- On the horizontal category axis, labels are horizontally aligned within each category band.
Vertical Alignment Copy Link
Use label.verticalAlign to control the vertical alignment of labels. This is most useful on a horizontal axis containing labels of differing height.
import React, { useState, Fragment } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
AgCartesianChartOptions,
AgCategoryAxisOptions,
BarSeriesModule,
CategoryAxisModule,
ModuleRegistry,
NumberAxisModule,
} from "ag-charts-community";
import { getData } from "./data";
import clone from "clone";
type VerticalAlign = "top" | "middle" | "bottom";
// The alignment each axis derives from its position, so the labels start unchanged.
const initialXVerticalAlign: VerticalAlign = "top";
const initialYVerticalAlign: VerticalAlign = "middle";
ModuleRegistry.registerModules([
BarSeriesModule,
CategoryAxisModule,
NumberAxisModule,
]);
const ChartExample = () => {
const [options, setOptions] = useState<AgCartesianChartOptions>({
data: getData(),
series: [
{
type: "bar",
xKey: "region",
yKey: "revenue",
},
],
axes: {
x: {
type: "category",
label: {
// Region names wrap onto a differing number of lines, so alignment matters here.
wrapping: "always",
verticalAlign: initialXVerticalAlign,
},
} as AgCategoryAxisOptions,
y: {
type: "number",
label: {
verticalAlign: initialYVerticalAlign,
formatter: ({ value }) => `$${value.toLocaleString()}`,
},
},
},
});
const setXVerticalAlign = (event: Event) => {
const nextOptions = clone(options);
nextOptions.axes!.x!.label!.verticalAlign = (
event.target as HTMLInputElement
).value as VerticalAlign;
setOptions(nextOptions);
};
const setYVerticalAlign = (event: Event) => {
const nextOptions = clone(options);
nextOptions.axes!.y!.label!.verticalAlign = (
event.target as HTMLInputElement
).value as VerticalAlign;
setOptions(nextOptions);
};
return (
<Fragment>
<div className="example-controls">
<div className="controls-row">
<span>X Axis:</span>
<div
className="button-group"
role="group"
aria-label="X Axis Vertical Alignment"
>
<input
type="radio"
id="x-vertical-align-top"
name="xVerticalAlign"
defaultValue="top"
defaultChecked
onChange={(event) => setXVerticalAlign(event)}
/>
<label htmlFor="x-vertical-align-top">
<code>top</code>
</label>
<input
type="radio"
id="x-vertical-align-middle"
name="xVerticalAlign"
defaultValue="middle"
onChange={(event) => setXVerticalAlign(event)}
/>
<label htmlFor="x-vertical-align-middle">
<code>middle</code>
</label>
<input
type="radio"
id="x-vertical-align-bottom"
name="xVerticalAlign"
defaultValue="bottom"
onChange={(event) => setXVerticalAlign(event)}
/>
<label htmlFor="x-vertical-align-bottom">
<code>bottom</code>
</label>
</div>
<span className="gap-left">Y Axis:</span>
<div
className="button-group"
role="group"
aria-label="Y Axis Vertical Alignment"
>
<input
type="radio"
id="y-vertical-align-top"
name="yVerticalAlign"
defaultValue="top"
onChange={(event) => setYVerticalAlign(event)}
/>
<label htmlFor="y-vertical-align-top">
<code>top</code>
</label>
<input
type="radio"
id="y-vertical-align-middle"
name="yVerticalAlign"
defaultValue="middle"
defaultChecked
onChange={(event) => setYVerticalAlign(event)}
/>
<label htmlFor="y-vertical-align-middle">
<code>middle</code>
</label>
<input
type="radio"
id="y-vertical-align-bottom"
name="yVerticalAlign"
defaultValue="bottom"
onChange={(event) => setYVerticalAlign(event)}
/>
<label htmlFor="y-vertical-align-bottom">
<code>bottom</code>
</label>
</div>
</div>
</div>
<AgCharts options={options} />
</Fragment>
);
};
const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);
export function getData(): { region: string; revenue: number }[] {
return [
{ region: "UK", revenue: 430000 },
{ region: "EU", revenue: 620000 },
{ region: "North America", revenue: 900000 },
{ region: "Latin America", revenue: 380000 },
{ region: "Asia Pacific", revenue: 1150000 },
{ region: "Middle East", revenue: 290000 },
{ region: "Sub-Saharan Africa", revenue: 210000 },
{ region: "Eastern Europe and Central Asia", revenue: 340000 },
{ region: "South East Asia and the Pacific Islands", revenue: 460000 },
];
}
{
axes: {
x: {
label: {
verticalAlign: 'top',
},
},
y: {
label: {
verticalAlign: 'middle',
},
},
},
}In this example:
- Use the buttons to see the
verticalAlignoptions for each axis. - On the horizontal category axis, labels are vertically aligned within the axis thickness.
- On the vertical number axis, labels are vertically aligned around the tick they label.
Label Text Formatting Copy Link
The label text is taken directly from the data.
To format how this is displayed, either use a Global Formatter, or specify an axis label level Formatter or Format as explained below.
This section discusses formatting the label text. See the Axis Intervals section to learn how to configure which labels are shown.
Formatter Copy Link
The label.formatter callback allows maximum flexibility for controlling what appears in the labels.
import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
AgChartOptions,
BarSeriesModule,
CategoryAxisModule,
LegendModule,
ModuleRegistry,
NumberAxisModule,
} from "ag-charts-community";
import { DataType, getData } from "./data";
ModuleRegistry.registerModules([
BarSeriesModule,
CategoryAxisModule,
LegendModule,
NumberAxisModule,
]);
const ChartExample = () => {
const [options, setOptions] = useState<AgChartOptions<DataType>>({
data: getData(),
title: {
text: "Desktop Operating Systems",
},
series: [
{
type: "bar",
xKey: "os",
yKey: "share",
},
],
axes: {
x: {
type: "category",
label: {
formatter: ({ value }) =>
value === "Windows" ? "== Windows ==" : value,
},
},
y: {
type: "number",
label: {
formatter: ({ value }) => `${value * 100}%`,
},
},
},
});
return <AgCharts options={options} />;
};
const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);
export interface DataType {
os: string;
share: number;
}
export function getData(): DataType[] {
return [
{ os: "Windows", share: 0.88 },
{ os: "macOS", share: 0.094 },
{ os: "Linux", share: 0.187 },
];
}
{
label: {
formatter: function (params) {
return params.value * 100 + '%';
},
},
}In the above example:
- The number axis uses a formatter to multiply by 100 and append
'%'to all values. - The category axis uses a formatter to add
'=='around the 'Windows' label only.
The formatter function receives a single params object which contains:
- The raw
valueof the label (without any default formatting applied). - The
indexof the label in the data array. - The number of
fractionDigits, if the value is a number.
It is called for each label and should return a string.
Format Copy Link
The label.format property takes a static string representing a time or number format. This has less flexibility than the formatter, but can be serialised. For all time axes, an object format mapping a unit of time to a format string can also be used.
import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
AgChartOptions,
LegendModule,
LineSeriesModule,
ModuleRegistry,
NumberAxisModule,
UnitTimeAxisModule,
} from "ag-charts-community";
ModuleRegistry.registerModules([
LegendModule,
LineSeriesModule,
NumberAxisModule,
UnitTimeAxisModule,
]);
const ChartExample = () => {
const [options, setOptions] = useState<AgChartOptions>({
series: [
{
type: "line",
xKey: "date",
yKey: "temp",
},
],
axes: {
x: {
type: "unit-time",
interval: { step: "month" },
label: {
format: "%b %Y",
},
},
y: {
type: "number",
label: {
format: "$#{0>6.2f}",
},
},
},
data: [
{ date: new Date("2019-01-01"), temp: 82.0 },
{ date: new Date("2019-02-01"), temp: 75.0 },
{ date: new Date("2019-03-01"), temp: 62.0 },
{ date: new Date("2019-04-01"), temp: 99.0 },
{ date: new Date("2019-05-01"), temp: 82.0 },
],
});
return <AgCharts options={options} />;
};
const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);
{
label: {
format: '%b %Y',
},
}{
label: {
format: '$#{0>6.2f}',
},
}In the above example:
- The time axis uses a format to display the short month name and full year for all values.
- The number axis uses a format to prepend a
'$'and use 2 decimal places for all values. Shorter numbers are padding with'0'.
The syntax used in the format strings depends on the axis type.
See the Formatters page for a full list of the available formats.
Customisation Copy Link
See Fills & Borders for details on customising axis labels.