Series data labels display the value of a data point directly on the chart. These are configured on the label property of each series.
Please see the API Reference for the full list of available options, which vary slightly between series types.
Styling Copy Link
Enable labels with label.enabled, then style them with the following options.
import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
AgBarSeriesOptions,
AgCartesianChartOptions,
BarSeriesModule,
CategoryAxisModule,
LegendModule,
ModuleRegistry,
NumberAxisModule,
} from "ag-charts-community";
import { DataType, data } from "./data";
function seriesLabel(): AgBarSeriesOptions<DataType>["label"] {
return {
enabled: true,
fontWeight: "bold",
placement: [
"outside-end",
"inside-center",
"beside-after-center",
"beside-before-center",
],
orientation: "horizontal",
border: { enabled: true, strokeWidth: 1 },
insideStyle: {
color: "white",
fill: "black",
fillOpacity: 0.6,
border: { stroke: "white" },
},
outsideStyle: {
color: "black",
fill: "white",
fillOpacity: 0.8,
border: { stroke: "black" },
},
};
}
ModuleRegistry.registerModules([
BarSeriesModule,
LegendModule,
CategoryAxisModule,
NumberAxisModule,
]);
const ChartExample = () => {
const [options, setOptions] = useState<AgCartesianChartOptions<DataType>>({
title: { text: "Quarterly Revenue by Product Line ($m)" },
data,
series: [
{
type: "bar",
xKey: "quarter",
yKey: "hardware",
yName: "Hardware",
stacked: true,
label: seriesLabel(),
},
{
type: "bar",
xKey: "quarter",
yKey: "services",
yName: "Services",
stacked: true,
label: seriesLabel(),
},
{
type: "bar",
xKey: "quarter",
yKey: "software",
yName: "Software",
stacked: true,
label: seriesLabel(),
},
],
axes: {
x: { type: "category" },
y: { type: "number", max: 90, title: { text: "Revenue ($m)" } },
},
});
return <AgCharts options={options} />;
};
const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);
export interface DataType {
quarter: string;
hardware: number;
software: number;
services: number;
}
export const data: DataType[] = [
{ quarter: "Q1", hardware: 42, software: 26, services: 3 },
{ quarter: "Q2", hardware: 35, software: 31, services: 9 },
{ quarter: "Q3", hardware: 29, software: 33, services: 2 },
{ quarter: "Q4", hardware: 46, software: 21, services: 6 },
];
{
series: [
{
// ...
label: {
enabled: true,
},
},
],
}In this example:
- The label text is styled with properties such as
colorandfontWeight. Other available options includefontSize,fontStyleandfontFamily. - The label itself has a fill and border, configured with properties such as
fillandborder. Other available options includefillOpacity,cornerRadiusandpadding. See Fills & Borders for more details. - The
insideStyleandoutsideStyleproperties override these text and box styles for when the resolved label placement sits inside or outside the series node â used here to swap between a dark-on-light and light-on-dark treatment. - Bar-family labels can additionally be rotated with
orientation. See Orientation for more details. - Providing
placementas an ordered array lets a label fall back to an alternative position. See Placement for more details.
Placement Copy Link
The available label positions are series-specific. These include 'inside-start' or 'outside-end' for a bar series, and 'top' or 'left' for a bubble series.
See the API Reference for the full list of placement values per series type.
import React, { useState, Fragment } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
AgBarSeriesOptions,
AgBubbleSeriesOptions,
AgCartesianChartOptions,
BarSeriesModule,
BubbleSeriesModule,
CategoryAxisModule,
LegendModule,
ModuleRegistry,
NumberAxisModule,
} from "ag-charts-community";
import {
AgBarSeriesLabelPlacement,
AgChartLabelCollisionPlacement,
} from "ag-charts-types";
import { BarDataType, BubbleDataType, barData, bubbleData } from "./data";
import clone from "clone";
import "./styles.css";
type SeriesType = "bubble" | "bar" | "bar-horizontal";
type Placement =
| AgChartLabelCollisionPlacement
| AgChartLabelCollisionPlacement[]
| AgBarSeriesLabelPlacement
| AgBarSeriesLabelPlacement[];
let spacing = 6;
function formatCurrency(value: number) {
const sign = value < 0 ? "-" : "";
return `${sign}$${Math.abs(value)}m`;
}
function parsePlacement(value: string): Placement {
const placements = value.split(/,\s*/g);
return (placements.length > 1 ? placements : placements[0]) as Placement;
}
ModuleRegistry.registerModules([
BubbleSeriesModule,
BarSeriesModule,
LegendModule,
CategoryAxisModule,
NumberAxisModule,
]);
const ChartExample = () => {
const [options, setOptions] = useState<
AgCartesianChartOptions<BubbleDataType | BarDataType>
>({
title: { text: "Weather Station Readings" },
data: bubbleData,
series: [
{
type: "bubble",
xKey: "temperature",
yKey: "humidity",
sizeKey: "windSpeed",
labelKey: "station",
maxSize: 60,
label: {
enabled: true,
placement: "top",
spacing,
},
},
],
axes: {
x: { type: "number", title: { text: "Temperature (°C)" } },
y: { type: "number", title: { text: "Humidity (%)" } },
},
});
const updateSpacingSlider = (placement: Placement) => {
const isCentred = placement === "inside" || placement === "inside-center";
(document.getElementById("spacingSlider") as HTMLInputElement).disabled =
isCentred;
};
const setSeriesType = (event: Event) => {
const nextOptions = clone(options);
const seriesType = (event.target as HTMLInputElement).value as SeriesType;
(
document.getElementById("bubblePlacementGroup") as HTMLFieldSetElement
).disabled = seriesType !== "bubble";
(
document.getElementById("barPlacementGroup") as HTMLFieldSetElement
).disabled = seriesType === "bubble";
const bubblePlacementSelect = document.getElementById(
"bubblePlacementSelect",
) as HTMLSelectElement;
const barPlacementSelect = document.getElementById(
"barPlacementSelect",
) as HTMLSelectElement;
let placement: Placement;
if (seriesType === "bubble") {
nextOptions.title = { text: "Weather Station Readings" };
nextOptions.data = bubbleData;
nextOptions.axes = {
x: { type: "number", title: { text: "Temperature (°C)" } },
y: { type: "number", title: { text: "Humidity (%)" } },
};
placement = parsePlacement(bubblePlacementSelect.value);
nextOptions.series = [
{
type: "bubble",
xKey: "temperature",
yKey: "humidity",
sizeKey: "windSpeed",
labelKey: "station",
maxSize: 60,
label: {
enabled: true,
placement: placement as
| AgChartLabelCollisionPlacement
| AgChartLabelCollisionPlacement[],
spacing,
},
},
];
} else {
nextOptions.title = { text: "Quarterly Profit Change ($m)" };
nextOptions.data = barData;
// direction: 'horizontal' swaps which axis carries the category vs the value
nextOptions.axes =
seriesType === "bar-horizontal"
? {
y: { type: "category" },
x: { type: "number", title: { text: "Profit Change ($m)" } },
}
: {
x: { type: "category" },
y: { type: "number", title: { text: "Profit Change ($m)" } },
};
placement = parsePlacement(barPlacementSelect.value);
nextOptions.series = [
{
type: "bar",
direction:
seriesType === "bar-horizontal" ? "horizontal" : "vertical",
xKey: "quarter",
yKey: "profitChange",
label: {
enabled: true,
placement: placement as
| AgBarSeriesLabelPlacement
| AgBarSeriesLabelPlacement[],
spacing,
truncate: false,
formatter: ({ value }) => formatCurrency(value),
},
tooltip: {
renderer: ({ datum }) => ({
data: [
{
label: "Profit Change",
value: formatCurrency((datum as BarDataType).profitChange),
},
],
}),
},
},
];
}
updateSpacingSlider(placement);
setOptions(nextOptions);
};
const setPlacement = (value: string) => {
const nextOptions = clone(options);
const placement = parsePlacement(value);
const series = nextOptions.series![0] as
| AgBubbleSeriesOptions<BubbleDataType>
| AgBarSeriesOptions<BarDataType>;
series.label!.placement = placement;
updateSpacingSlider(placement);
setOptions(nextOptions);
};
const setSpacing = (event: Event) => {
const nextOptions = clone(options);
spacing = Number((event.target as HTMLInputElement).value);
document.getElementById("spacingValue")!.textContent = String(spacing);
const series = nextOptions.series![0] as
| AgBubbleSeriesOptions<BubbleDataType>
| AgBarSeriesOptions<BarDataType>;
series.label!.spacing = spacing;
setOptions(nextOptions);
};
return (
<Fragment>
<div className="example-controls">
<div className="controls-row">
<span>Series:</span>
<div className="button-group" role="group" aria-label="Series">
<input
type="radio"
id="series-bubble"
name="series-type"
defaultValue="bubble"
defaultChecked
onChange={(event) => setSeriesType(event)}
/>
<label htmlFor="series-bubble">
<code>Bubble</code>
</label>
<input
type="radio"
id="series-bar"
name="series-type"
defaultValue="bar"
onChange={(event) => setSeriesType(event)}
/>
<label htmlFor="series-bar">
<code>Bar</code>
</label>
<input
type="radio"
id="series-bar-horizontal"
name="series-type"
defaultValue="bar-horizontal"
onChange={(event) => setSeriesType(event)}
/>
<label htmlFor="series-bar-horizontal">
<code>Horizontal Bar</code>
</label>
</div>
</div>
<div className="controls-row">
<fieldset id="bubblePlacementGroup" className="control-group">
<span>Bubble Placement:</span>
<select
id="bubblePlacementSelect"
className="gap-right"
onChange={(event) => setPlacement(event.target.value)}
>
<option value="top">Top</option>
<option value="top-right">Top Right</option>
<option value="right">Right</option>
<option value="bottom-right">Bottom Right</option>
<option value="bottom">Bottom</option>
<option value="bottom-left">Bottom Left</option>
<option value="left">Left</option>
<option value="top-left">Top Left</option>
<option value="inside">Centre (Inside)</option>
<hr />
<option value="top, bottom, left, right">
Top + Bottom + Left + Right fallback
</option>
<option value="left, right">Left + Right fallback</option>
</select>
</fieldset>
<fieldset
id="barPlacementGroup"
className="control-group"
disabled={true}
>
<span>Bar Placement:</span>
<select
id="barPlacementSelect"
className="gap-right"
onChange={(event) => setPlacement(event.target.value)}
>
<option value="outside-end">Outside End</option>
<option value="outside-start">Outside Start</option>
<option value="inside-end">Inside End</option>
<option value="inside-start">Inside Start</option>
<option value="inside-center">Centre (Inside)</option>
<option value="beside-before-start">Beside Before Start</option>
<option value="beside-before-center">Beside Before Centre</option>
<option value="beside-before-end">Beside Before End</option>
<option value="beside-after-start">Beside After Start</option>
<option value="beside-after-center">Beside After Centre</option>
<option value="beside-after-end">Beside After End</option>
<hr />
<option value="outside-end, inside-end">
Outside End + Inside End fallback
</option>
<option value="inside-center, beside-after-center">
Inside Centre + Beside After Centre fallback
</option>
</select>
</fieldset>
<label htmlFor="spacingSlider">Spacing:</label>
<input
type="range"
id="spacingSlider"
min="0"
max="20"
defaultValue="6"
onInput={(event) => setSpacing(event)}
onChange={(event) => setSpacing(event)}
/>
<span
id="spacingValue"
style={{
display: "inline-block",
minWidth: "3ch",
textAlign: "right",
}}
>
6
</span>
</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;
}
export interface BubbleDataType {
station: string;
temperature: number;
humidity: number;
windSpeed: number;
}
export const bubbleData: BubbleDataType[] = [
{ station: "Ashford", temperature: 19.2, humidity: 61, windSpeed: 12 },
{ station: "Bridgend", temperature: 19.8, humidity: 63, windSpeed: 9 },
{ station: "Camden", temperature: 20.1, humidity: 58, windSpeed: 14 },
{ station: "Dorking", temperature: 20.4, humidity: 65, windSpeed: 11 },
{ station: "Elgin", temperature: 19.5, humidity: 60, windSpeed: 16 },
{ station: "Frome", temperature: 20.7, humidity: 62, windSpeed: 8 },
{ station: "Goole", temperature: 19.9, humidity: 59, windSpeed: 13 },
{ station: "Hexham", temperature: 20.3, humidity: 64, windSpeed: 10 },
{ station: "Ilkley", temperature: 19.6, humidity: 61, windSpeed: 15 },
{ station: "Jarrow", temperature: 20.0, humidity: 57, windSpeed: 9 },
{ station: "Kendal", temperature: 20.6, humidity: 66, windSpeed: 12 },
{ station: "Looe", temperature: 19.3, humidity: 63, windSpeed: 11 },
{ station: "Marlow", temperature: 20.2, humidity: 59, windSpeed: 10 },
{ station: "Napton", temperature: 19.7, humidity: 62, windSpeed: 13 },
{ station: "Oundle", temperature: 20.5, humidity: 60, windSpeed: 9 },
{ station: "Pewsey", temperature: 19.4, humidity: 64, windSpeed: 14 },
{ station: "Quorn", temperature: 20.8, humidity: 61, windSpeed: 8 },
{ station: "Ripon", temperature: 19.1, humidity: 58, windSpeed: 16 },
{ station: "Settle", temperature: 20.0, humidity: 65, windSpeed: 12 },
{ station: "Thirsk", temperature: 19.9, humidity: 63, windSpeed: 10 },
{ station: "Ulverston", temperature: 20.4, humidity: 60, windSpeed: 11 },
{ station: "Verwood", temperature: 19.6, humidity: 62, windSpeed: 15 },
{ station: "Wetherby", temperature: 20.3, humidity: 59, windSpeed: 9 },
{ station: "Yeovil", temperature: 19.8, humidity: 61, windSpeed: 13 },
{ station: "Amersham", temperature: 19.5, humidity: 62, windSpeed: 11 },
{ station: "Bakewell", temperature: 20.1, humidity: 60, windSpeed: 9 },
{ station: "Chard", temperature: 19.7, humidity: 64, windSpeed: 13 },
{ station: "Devizes", temperature: 20.4, humidity: 59, windSpeed: 10 },
{ station: "Evesham", temperature: 19.3, humidity: 63, windSpeed: 15 },
{ station: "Fakenham", temperature: 20.6, humidity: 61, windSpeed: 8 },
{ station: "Grantham", temperature: 19.9, humidity: 58, windSpeed: 14 },
{ station: "Honiton", temperature: 20.2, humidity: 65, windSpeed: 11 },
{ station: "Ivybridge", temperature: 19.6, humidity: 60, windSpeed: 9 },
{ station: "Kington", temperature: 20.0, humidity: 62, windSpeed: 12 },
{ station: "Ludlow", temperature: 19.4, humidity: 64, windSpeed: 16 },
{ station: "Malmesbury", temperature: 20.5, humidity: 59, windSpeed: 8 },
{ station: "Newark", temperature: 19.8, humidity: 61, windSpeed: 13 },
{ station: "Oswestry", temperature: 20.3, humidity: 63, windSpeed: 10 },
{ station: "Presteigne", temperature: 19.2, humidity: 58, windSpeed: 15 },
{ station: "Ringwood", temperature: 20.7, humidity: 60, windSpeed: 9 },
];
export interface BarDataType {
quarter: string;
profitChange: number;
}
export const barData: BarDataType[] = [
{ quarter: "Q1", profitChange: 12 },
{ quarter: "Q2", profitChange: 8 },
{ quarter: "Q3", profitChange: -5 },
{ quarter: "Q4", profitChange: 1 },
{ quarter: "Q5", profitChange: -9 },
];
{
series: [
{
// ...
label: {
enabled: true,
placement: ['top', 'bottom', 'left', 'right'],
spacing: 6,
},
},
],
}In this example:
- Providing
placementas an ordered array allows the label to fallback to an alternative position if the first doesn't fit or collides with another item.- This is affected by Orientation and other Collision Avoidance options.
- Resize the example to see the fallback placements in action.
spacingsets the pixel distance between a label and its anchor and is ignored when the resolved placement is centred.
Orientation Copy Link
Bar-family series can rotate their labels using the label.orientation option. This accepts 'horizontal', 'vertical' or 'vertical-reversed', or an ordered array of fallback orientations.
import React, { useState, Fragment } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
AgBarSeriesOptions,
AgCartesianChartOptions,
BarSeriesModule,
CategoryAxisModule,
LegendModule,
ModuleRegistry,
NumberAxisModule,
} from "ag-charts-community";
import { AgChartLabelOrientation } from "ag-charts-types";
import { DataType, data } from "./data";
import clone from "clone";
import "./styles.css";
ModuleRegistry.registerModules([
BarSeriesModule,
LegendModule,
CategoryAxisModule,
NumberAxisModule,
]);
const ChartExample = () => {
const [options, setOptions] = useState<AgCartesianChartOptions<DataType>>({
title: { text: "Quarterly Profit Change ($m)" },
data,
series: [
{
type: "bar",
xKey: "quarter",
yKey: "profitChange",
label: {
enabled: true,
placement: "inside-end",
orientation: "horizontal",
wrapping: "never",
formatter: (params) =>
`$${params.value}m profit${params.datum.note ? ` (${params.datum.note})` : ""}`,
},
tooltip: {
renderer: ({ datum }) => ({
data: [
{ label: "Profit Change", value: `$${datum.profitChange}m` },
],
}),
},
},
],
axes: {
x: { type: "category" },
y: { type: "number", title: { text: "Profit Change ($m)" } },
},
});
const setOrientation = (orientation: string) => {
const nextOptions = clone(options);
const series = nextOptions.series![0] as AgBarSeriesOptions<DataType>;
const orientations = orientation.split(
/,\s*/g,
) as AgChartLabelOrientation[];
series.label!.orientation =
orientations.length > 1 ? orientations : orientations[0];
setOptions(nextOptions);
};
return (
<Fragment>
<div className="example-controls">
<div className="controls-row">
<span>Orientation:</span>
<select
className="gap-right"
onChange={(event) => setOrientation(event.target.value)}
>
<option value="horizontal">Horizontal</option>
<option value="vertical">Vertical</option>
<option value="vertical-reversed">Vertical Reversed</option>
<hr />
<option value="horizontal, vertical">
Horizontal, Vertical fallback
</option>
<option value="vertical, horizontal">
Vertical, Horizontal fallback
</option>
</select>
</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;
}
export interface DataType {
quarter: string;
profitChange: number;
note?: string;
}
export const data: DataType[] = [
{ quarter: "Q1 2024", profitChange: 12 },
{ quarter: "Q2 2024", profitChange: 8 },
{ quarter: "Q3 2024", profitChange: 15 },
{ quarter: "Q4 2024", profitChange: 2 },
{ quarter: "Q1 2025", profitChange: 11 },
{ quarter: "Q2 2025", profitChange: 9, note: "best quarter on record" },
{ quarter: "Q3 2025", profitChange: 14 },
{ quarter: "Q4 2025", profitChange: 7 },
];
{
series: [
{
type: 'bar',
// ...
label: {
enabled: true,
orientation: ['horizontal', 'vertical'],
wrapping: 'never',
},
},
],
}In this example:
- Providing
orientationas an ordered array allows the label to fallback to an alternative orientation if the first doesn't fit or collides with another item.- This is affected by Placement and other Collision Avoidance options.
- Resize the example to see the fallback placements in action.
Collision Avoidance Copy Link
Series label collision avoidance is separate from axis label collision avoidance, which is configured independently on each axis.
As well as using fallback placement and fallback orientation options, labels can also wrap, truncate, shrink to a smaller font size or be hidden when they collide with other elements or don't fit within provided maxWidth/maxHeight values.
import React, { useState, Fragment } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
AgBarSeriesOptions,
AgCartesianChartOptions,
BarSeriesModule,
CategoryAxisModule,
LegendModule,
ModuleRegistry,
NumberAxisModule,
} from "ag-charts-community";
import { TextWrap } from "ag-charts-types";
import { DataType, data } from "./data";
import clone from "clone";
import "./styles.css";
ModuleRegistry.registerModules([
BarSeriesModule,
LegendModule,
CategoryAxisModule,
NumberAxisModule,
]);
const ChartExample = () => {
const [options, setOptions] = useState<AgCartesianChartOptions<DataType>>({
title: { text: "Quarterly Revenue by Leading Division" },
data,
series: [
{
type: "bar",
xKey: "quarter",
yKey: "revenue",
label: {
enabled: true,
placement: "inside-end",
formatter: (params) => `$${params.value}m ${params.datum.division}`,
maxWidth: 70,
maxHeight: 54,
wrapping: "on-space",
truncate: true,
},
tooltip: {
renderer: ({ datum }) => ({
data: [{ label: "Revenue", value: `$${datum.revenue}m` }],
}),
},
},
],
axes: {
x: { type: "category" },
y: { type: "number", title: { text: "Revenue ($m)" } },
},
});
const setMaxWidth = (event: Event) => {
const nextOptions = clone(options);
const value = Number((event.target as HTMLInputElement).value);
document.getElementById("maxWidthValue")!.textContent = String(value);
(nextOptions.series![0] as AgBarSeriesOptions<DataType>).label!.maxWidth =
value;
setOptions(nextOptions);
};
const setMaxHeight = (event: Event) => {
const nextOptions = clone(options);
const value = Number((event.target as HTMLInputElement).value);
document.getElementById("maxHeightValue")!.textContent = String(value);
(nextOptions.series![0] as AgBarSeriesOptions<DataType>).label!.maxHeight =
value;
setOptions(nextOptions);
};
const setWrapping = (wrapping: string) => {
const nextOptions = clone(options);
(nextOptions.series![0] as AgBarSeriesOptions<DataType>).label!.wrapping =
wrapping as TextWrap;
setOptions(nextOptions);
};
const setMinimumFontSize = (value: string) => {
const nextOptions = clone(options);
(
nextOptions.series![0] as AgBarSeriesOptions<DataType>
).label!.minimumFontSize = value === "off" ? undefined : Number(value);
setOptions(nextOptions);
};
const setTruncate = (value: string) => {
const nextOptions = clone(options);
(nextOptions.series![0] as AgBarSeriesOptions<DataType>).label!.truncate =
value === "enabled";
setOptions(nextOptions);
};
return (
<Fragment>
<div className="example-controls">
<div className="controls-row">
<div className="gap-right">
<label htmlFor="maxWidthSlider">Max Width:</label>
<input
type="range"
id="maxWidthSlider"
min="20"
max="150"
defaultValue="70"
onInput={(event) => setMaxWidth(event)}
onChange={(event) => setMaxWidth(event)}
/>
<span
id="maxWidthValue"
style={{
display: "inline-block",
minWidth: "4ch",
textAlign: "right",
}}
>
70
</span>
</div>
<div>
<label htmlFor="maxHeightSlider">Max Height:</label>
<input
type="range"
id="maxHeightSlider"
min="10"
max="80"
defaultValue="54"
onInput={(event) => setMaxHeight(event)}
onChange={(event) => setMaxHeight(event)}
/>
<span
id="maxHeightValue"
style={{
display: "inline-block",
minWidth: "4ch",
textAlign: "right",
}}
>
54
</span>
</div>
</div>
<div className="controls-row">
<div className="gap-right">
<label htmlFor="wrap-select">Wrapping:</label>
<select
id="wrap-select"
onChange={(event) => setWrapping(event.target.value)}
>
<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 className="gap-right">
<label htmlFor="minFontSelect">Minimum Font Size:</label>
<select
id="minFontSelect"
onChange={(event) => setMinimumFontSize(event.target.value)}
>
<option value="off">Off</option>
<option value="10">10</option>
<option value="6">6</option>
</select>
</div>
<div>
<label htmlFor="truncate-select">Truncate:</label>
<select
id="truncate-select"
onChange={(event) => setTruncate(event.target.value)}
>
<option value="enabled">Enabled</option>
<option value="disabled">Disabled</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;
}
export interface DataType {
quarter: string;
revenue: number;
division: string;
}
export const data: DataType[] = [
{ quarter: "Q1 2024", revenue: 42, division: "Energy" },
{ quarter: "Q2 2024", revenue: 58, division: "Semiconductors" },
{ quarter: "Q3 2024", revenue: 51, division: "Infrastructure" },
{ quarter: "Q4 2024", revenue: 67, division: "Manufacturing" },
];
{
series: [
{
// ...
label: {
enabled: true,
placement: 'inside-end',
maxWidth: 70,
maxHeight: 54,
wrapping: 'on-space',
truncate: true,
minimumFontSize: 8,
},
},
],
}In this example:
maxWidthandmaxHeightspecify the maximum label size.- Supplying a
minimumFontSizelets the label shrink in conjunction with wrapping, attempting these methods before truncating or hiding. wrapping('on-space','always','hyphenate','never') controls how overflowing text wraps within the provided size or bar boundary.truncatetruncates whatever still doesn't fit, appending an ellipsis.
Pie and Donut
These series configure the same fitting options on calloutLabel and sectorLabel rather than on label.
import React, { useState, Fragment } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
AgPolarChartOptions,
DonutSeriesModule,
LegendModule,
ModuleRegistry,
PieSeriesModule,
} from "ag-charts-community";
import { TextWrap } from "ag-charts-types";
import { DataType, getData } from "./data";
import clone from "clone";
import "./styles.css";
let seriesType: "pie" | "donut" = "pie";
const fit = {
maxWidth: 70,
wrapping: "on-space" as TextWrap,
truncate: true,
minimumFontSize: undefined as number | undefined,
};
let calloutLabelEnabled = true;
let sectorLabelEnabled = true;
function buildSeries(): AgPolarChartOptions<DataType>["series"] {
const calloutLabel = { ...fit, enabled: calloutLabelEnabled };
const sectorLabel = {
...fit,
enabled: sectorLabelEnabled,
formatter: ({ value }: { value: number }) => `${value}% of total`,
};
if (seriesType === "donut") {
return [
{
type: "donut",
innerRadiusRatio: 0.5,
angleKey: "terawattHours",
calloutLabelKey: "source",
sectorLabelKey: "share",
calloutLabel,
sectorLabel,
},
];
}
return [
{
type: "pie",
angleKey: "terawattHours",
calloutLabelKey: "source",
sectorLabelKey: "share",
calloutLabel,
sectorLabel,
},
];
}
ModuleRegistry.registerModules([
DonutSeriesModule,
LegendModule,
PieSeriesModule,
]);
const ChartExample = () => {
const [options, setOptions] = useState<AgPolarChartOptions<DataType>>({
title: { text: "Global Electricity Generation by Source" },
data: getData(),
series: buildSeries(),
legend: { position: "right" },
});
const refresh = () => {
const nextOptions = clone(options);
nextOptions.series = buildSeries();
setOptions(nextOptions);
};
const setSeriesType = (type: string) => {
seriesType = type === "donut" ? "donut" : "pie";
refresh();
};
const setMaxWidth = (event: Event) => {
const value = Number((event.target as HTMLInputElement).value);
document.getElementById("maxWidthValue")!.textContent = String(value);
fit.maxWidth = value;
refresh();
};
const setWrapping = (wrapping: string) => {
fit.wrapping = wrapping as TextWrap;
refresh();
};
const setTruncate = (truncate: boolean) => {
fit.truncate = truncate;
refresh();
};
const setMinimumFontSize = (value: string) => {
fit.minimumFontSize = value === "off" ? undefined : Number(value);
refresh();
};
const toggleCalloutLabel = () => {
calloutLabelEnabled = !calloutLabelEnabled;
(
document.getElementById("calloutLabelToggle") as HTMLButtonElement
).setAttribute("aria-pressed", String(calloutLabelEnabled));
refresh();
};
const toggleSectorLabel = () => {
sectorLabelEnabled = !sectorLabelEnabled;
(
document.getElementById("sectorLabelToggle") as HTMLButtonElement
).setAttribute("aria-pressed", String(sectorLabelEnabled));
refresh();
};
return (
<Fragment>
<div className="example-controls">
<div className="controls-row">
<div>
<label htmlFor="seriesSelect">Series:</label>
<select
id="seriesSelect"
onChange={(event) => setSeriesType(event.target.value)}
>
<option value="pie">Pie</option>
<option value="donut">Donut</option>
</select>
</div>
<button
id="calloutLabelToggle"
aria-pressed="true"
onClick={toggleCalloutLabel}
>
Toggle Callout Labels
</button>
<button
id="sectorLabelToggle"
aria-pressed="true"
onClick={toggleSectorLabel}
>
Toggle Sector Labels
</button>
<div className="gap-left">
<label htmlFor="maxWidthSlider">Max Label Width:</label>
<input
type="range"
id="maxWidthSlider"
min="30"
max="180"
defaultValue="70"
onInput={(event) => setMaxWidth(event)}
onChange={(event) => setMaxWidth(event)}
/>
<span
id="maxWidthValue"
style={{
display: "inline-block",
minWidth: "4ch",
textAlign: "right",
}}
>
70
</span>
</div>
</div>
<div className="controls-row">
<div>
<label htmlFor="wrapSelect">Wrapping:</label>
<select
id="wrapSelect"
onChange={(event) => setWrapping(event.target.value)}
>
<option value="on-space">on-space</option>
<option value="always">always</option>
<option value="hyphenate">hyphenate</option>
<option value="never">never</option>
</select>
</div>
<div>
<label htmlFor="minFontSelect">Minimum Font Size:</label>
<select
id="minFontSelect"
onChange={(event) => setMinimumFontSize(event.target.value)}
>
<option value="off">Off</option>
<option value="10">10</option>
<option value="6">6</option>
</select>
</div>
<div>
<label htmlFor="truncateSelect">Truncate:</label>
<select
id="truncateSelect"
onChange={(event) =>
setTruncate(event.target.value === "enabled")
}
>
<option value="enabled">Enabled</option>
<option value="disabled">Disabled</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;
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;
}
export interface DataType {
source: string;
terawattHours: number;
share: number;
}
export function getData(): DataType[] {
return [
{ source: "Coal and Lignite", terawattHours: 10350, share: 35 },
{ source: "Natural Gas", terawattHours: 6650, share: 23 },
{ source: "Hydroelectric", terawattHours: 4300, share: 15 },
{ source: "Wind and Solar", terawattHours: 3550, share: 12 },
{ source: "Nuclear Fission", terawattHours: 2700, share: 9 },
{ source: "Biomass and Waste", terawattHours: 1750, share: 6 },
];
}
{
series: [
{
type: 'pie',
angleKey: 'terawattHours',
calloutLabelKey: 'source',
sectorLabelKey: 'share',
calloutLabel: {
maxWidth: 70,
wrapping: 'on-space',
truncate: true,
},
sectorLabel: {
wrapping: 'on-space',
truncate: true,
minimumFontSize: 8,
},
},
],
}In this example:
maxWidthandmaxHeightcap the label size in pixels. A sector label is capped by its wedge as well.- Use the controls and resize the container to see how the labels wrap, truncate or are hidden when they don't fit.
minimumFontSizelets the label shrink to fit before it is truncated or hidden. Switch it on to see the labels render in full at a smaller size.- When a
calloutLabeldoesn't fit around the chart, the pie or donut shrinks to make room for it, rather than fitting the label around a fixed radius.
Hiding Labels Copy Link
When any of these strategies are used but fail to find a satisfactory resolution, the label is hidden by default.
Use collision.alwaysShow: true to force the label to remain visible, or collision.alwaysShow: false to allow labels to be hidden even when no other strategies are enabled.
import React, { useState, Fragment } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
AgBubbleSeriesOptions,
AgCartesianChartOptions,
BubbleSeriesModule,
LegendModule,
ModuleRegistry,
NumberAxisModule,
} from "ag-charts-community";
import { DataType, data } from "./data";
import clone from "clone";
import "./styles.css";
ModuleRegistry.registerModules([
BubbleSeriesModule,
LegendModule,
NumberAxisModule,
]);
const ChartExample = () => {
const [options, setOptions] = useState<AgCartesianChartOptions<DataType>>({
title: { text: "Weather Station Readings" },
data,
series: [
{
type: "bubble",
xKey: "temperature",
yKey: "humidity",
sizeKey: "windSpeed",
labelKey: "station",
label: {
enabled: true,
border: {
enabled: true,
stroke: {
ref: "foregroundColor",
mix: 0.5,
onto: "backgroundColor",
},
strokeWidth: 2,
},
collision: {
threshold: 4,
alwaysShow: false,
},
},
},
],
axes: {
x: { type: "number", title: { text: "Temperature (°C)" } },
y: { type: "number", title: { text: "Humidity (%)" } },
},
});
const setThreshold = (event: Event) => {
const nextOptions = clone(options);
const value = Number((event.target as HTMLInputElement).value);
document.getElementById("thresholdValue")!.textContent = String(value);
(
nextOptions.series![0] as AgBubbleSeriesOptions<DataType>
).label!.collision!.threshold = value;
setOptions(nextOptions);
};
const setAlwaysShow = (value: string) => {
const nextOptions = clone(options);
const alwaysShow = value === "show";
(
nextOptions.series![0] as AgBubbleSeriesOptions<DataType>
).label!.collision!.alwaysShow = alwaysShow;
(
document.getElementById("thresholdGroup") as HTMLFieldSetElement
).disabled = alwaysShow;
setOptions(nextOptions);
};
return (
<Fragment>
<div className="example-controls">
<div className="controls-row">
<fieldset id="thresholdGroup" className="control-group">
<label htmlFor="thresholdSlider">Collision Threshold:</label>
<input
type="range"
id="thresholdSlider"
min="-10"
max="5"
defaultValue="4"
onInput={(event) => setThreshold(event)}
onChange={(event) => setThreshold(event)}
/>
<span
id="thresholdValue"
style={{
display: "inline-block",
minWidth: "4ch",
textAlign: "right",
}}
>
4
</span>
</fieldset>
</div>
<div className="controls-row">
<span>Colliding labels:</span>
<select onChange={(event) => setAlwaysShow(event.target.value)}>
<option value="hide">Hide</option>
<option value="show">Keep visible</option>
</select>
</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;
}
export interface DataType {
station: string;
temperature: number;
humidity: number;
windSpeed: number;
}
export const data: DataType[] = [
{ station: "Ashford", temperature: 19.2, humidity: 61, windSpeed: 12 },
{ station: "Bridgend", temperature: 19.4, humidity: 62, windSpeed: 9 },
{ station: "Camden", temperature: 19.6, humidity: 60, windSpeed: 14 },
{ station: "Dorking", temperature: 19.8, humidity: 63, windSpeed: 11 },
{ station: "Elgin", temperature: 20.0, humidity: 61, windSpeed: 16 },
{ station: "Frome", temperature: 19.5, humidity: 59, windSpeed: 8 },
{ station: "Goole", temperature: 19.9, humidity: 64, windSpeed: 13 },
{ station: "Hexham", temperature: 20.2, humidity: 62, windSpeed: 10 },
{ station: "Ilkley", temperature: 19.7, humidity: 60, windSpeed: 15 },
{ station: "Jarrow", temperature: 19.3, humidity: 63, windSpeed: 9 },
{ station: "Kendal", temperature: 20.055, humidity: 65, windSpeed: 12 },
{ station: "Looe", temperature: 19.6, humidity: 62, windSpeed: 10 },
{ station: "Marlow", temperature: 20.3, humidity: 59, windSpeed: 11 },
{ station: "Napton", temperature: 19.4, humidity: 63, windSpeed: 14 },
{ station: "Oundle", temperature: 19.9, humidity: 61, windSpeed: 9 },
{ station: "Pewsey", temperature: 20.0, humidity: 60, windSpeed: 13 },
{ station: "Quorn", temperature: 20.5, humidity: 62, windSpeed: 8 },
{ station: "Ripon", temperature: 19.1, humidity: 58, windSpeed: 16 },
{ station: "Settle", temperature: 19.8, humidity: 65, windSpeed: 12 },
{ station: "Thirsk", temperature: 20.4, humidity: 61, windSpeed: 10 },
{ station: "Buckden", temperature: 19.5, humidity: 62, windSpeed: 11 },
{ station: "Corsham", temperature: 20.1, humidity: 60, windSpeed: 9 },
{ station: "Diss", temperature: 19.7, humidity: 64, windSpeed: 13 },
{ station: "Egham", temperature: 20.4, humidity: 59, windSpeed: 10 },
{ station: "Fowey", temperature: 19.3, humidity: 63, windSpeed: 15 },
{ station: "Guisborough", temperature: 20.6, humidity: 61, windSpeed: 8 },
{ station: "Helmsley", temperature: 19.9, humidity: 58, windSpeed: 14 },
{ station: "Ingatestone", temperature: 20.2, humidity: 65, windSpeed: 11 },
{ station: "Knaresborough", temperature: 19.6, humidity: 60, windSpeed: 9 },
{ station: "Leominster", temperature: 20.0, humidity: 62, windSpeed: 12 },
{ station: "Mildenhall", temperature: 19.4, humidity: 64, windSpeed: 16 },
{ station: "Northallerton", temperature: 20.5, humidity: 59, windSpeed: 8 },
{ station: "Otley", temperature: 19.8, humidity: 61, windSpeed: 13 },
{ station: "Petworth", temperature: 20.3, humidity: 63, windSpeed: 10 },
{ station: "Ramsgate", temperature: 19.2, humidity: 58, windSpeed: 15 },
{ station: "Skipton", temperature: 20.7, humidity: 60, windSpeed: 9 },
{ station: "Thame", temperature: 19.5, humidity: 62, windSpeed: 12 },
{ station: "Uckfield", temperature: 20.1, humidity: 65, windSpeed: 10 },
{ station: "Wells", temperature: 19.9, humidity: 59, windSpeed: 13 },
{ station: "Yeovilton", temperature: 20.4, humidity: 63, windSpeed: 11 },
];
{
series: [
{
// ...
label: {
enabled: true,
collision: {
alwaysShow: true,
},
},
},
],
} Threshold Copy Link
Collisions are defined as the edge of one label hitting the edge of another element.
Use a collision.threshold value to ensure labels are a minimum pixel distance from obstacles, or a negative value to allow labels to overlap somewhat.
{
series: [
{
// ...
label: {
enabled: true,
collision: {
threshold: 4,
},
},
},
],
} API Reference Copy Link
Represents the configuration options for labels in an AgCharts. Labels are used to display textual information alongside data points in a chart.
- formatter
RichFormatter - A custom formatting function used to convert data values into text for display by labels.
- format
string - Format string used when rendering labels.
- itemStyler
Styler - Function used to style individual datum labels.
- enabled
boolean - Whether the associated elements and properties should be used in the chart.
- color
AgCssColorOrRef - The colour for text elements. A colour string, or a theme-colour reference object.
- fontSize
FontSize - The size of the font in pixels for text elements.
- fontFamily
FontFamily - The font family for text elements.
- fontStyle
FontStyle - The style to use for text elements.
- fontWeight
FontWeight - The font weight to use for text elements.
- border
BorderOptions - Stroke options for the box border.
- cornerRadius
PixelSize - Apply rounded corners to the label box.
- padding
Padding - Distance between the label text and the border. A number applies uniform padding; an object sets each side.
- fill
AgColorType - The colour for filling shapes. A colour string, or an object for a gradient, pattern, or image fill.
- fillOpacity
Opacity - The opacity of the fill colour.
Represents the configuration options for labels in an AgCharts. Labels are used to display textual information alongside data points in a chart.
- formatter
RichFormatter - A custom formatting function used to convert data values into text for display by labels.
- format
string - Format string used when rendering labels.
- itemStyler
Styler - Function used to style individual datum labels.
- enabled
boolean - Whether the associated elements and properties should be used in the chart.
- color
AgCssColorOrRef - The colour for text elements. A colour string, or a theme-colour reference object.
- fontSize
FontSize - The size of the font in pixels for text elements.
- fontFamily
FontFamily - The font family for text elements.
- fontStyle
FontStyle - The style to use for text elements.
- fontWeight
FontWeight - The font weight to use for text elements.
- border
BorderOptions - Stroke options for the box border.
- cornerRadius
PixelSize - Apply rounded corners to the label box.
- padding
Padding - Distance between the label text and the border. A number applies uniform padding; an object sets each side.
- fill
AgColorType - The colour for filling shapes. A colour string, or an object for a gradient, pattern, or image fill.
- fillOpacity
Opacity - The opacity of the fill colour.
Label-fit options extended with collision handling.
- collision
AgChartLabelCollisionOptions - Configuration controlling the spacing kept from obstacles and whether a label that cannot be placed clear of every obstacle is kept at its least-overflowing placement or hidden.
- maxWidth
PixelSize - Maximum width, in pixels, the label may occupy before it is wrapped or truncated to fit.
- maxHeight
PixelSize - Maximum height, in pixels, the label may occupy before it is wrapped or truncated to fit.
- wrapping
TextWrap - Text wrapping strategy applied when the label is constrained by `maxWidth` or `maxHeight`. - `'always'` will always wrap text to fit within the bounds. - `'hyphenate'` is similar to `'always'`, but inserts a hyphen (`-`) if forced to wrap in the middle of a word. - `'on-space'` will only wrap on white space. If there is no possibility to wrap a line on space and satisfy the bounds, the text will be truncated. - `'never'` disables text wrapping.
- truncate
boolean - Whether to truncate the label with an ellipsis when it does not fit within its bounds.
Label-fit options extended with collision handling.
- collision
AgChartLabelCollisionOptions - Configuration controlling the spacing kept from obstacles and whether a label that cannot be placed clear of every obstacle is kept at its least-overflowing placement or hidden.
- maxWidth
PixelSize - Maximum width, in pixels, the label may occupy before it is wrapped or truncated to fit.
- maxHeight
PixelSize - Maximum height, in pixels, the label may occupy before it is wrapped or truncated to fit.
- wrapping
TextWrap - Text wrapping strategy applied when the label is constrained by `maxWidth` or `maxHeight`. - `'always'` will always wrap text to fit within the bounds. - `'hyphenate'` is similar to `'always'`, but inserts a hyphen (`-`) if forced to wrap in the middle of a word. - `'on-space'` will only wrap on white space. If there is no possibility to wrap a line on space and satisfy the bounds, the text will be truncated. - `'never'` disables text wrapping.
- truncate
boolean - Whether to truncate the label with an ellipsis when it does not fit within its bounds.
Font reduction applied to a label that does not fit the region produced by its placement.
- minimumFontSize
FontSize - If the label does not fit within its bounds, setting this will allow the label to pick a font size between its normal `fontSize` and `minimumFontSize` to fit. The label is only truncated or hidden when it still does not fit at `minimumFontSize`.
Font reduction applied to a label that does not fit the region produced by its placement.
- minimumFontSize
FontSize - If the label does not fit within its bounds, setting this will allow the label to pick a font size between its normal `fontSize` and `minimumFontSize` to fit. The label is only truncated or hidden when it still does not fit at `minimumFontSize`.
Label style overrides applied according to the placement resolved at layout time.
- insideStyle
AgChartLabelPlacementStyleOptions - Styles applied when the label is placed inside the shape.
- outsideStyle
AgChartLabelPlacementStyleOptions - Styles applied when the label is placed outside the shape.
Label style overrides applied according to the placement resolved at layout time.
- insideStyle
AgChartLabelPlacementStyleOptions - Styles applied when the label is placed inside the shape.
- outsideStyle
AgChartLabelPlacementStyleOptions - Styles applied when the label is placed outside the shape.