---
product: "AG Charts"
title: "Series Labels"
description: "React Charts support styling series data labels, controlling their position and orientation, and avoiding collisions between labels, markers and other series geometry."
framework: react
version: "14.2.0"
related:
    - title: "Cross Lines"
      url: "https://www.ag-grid.com/charts/archive/14.2.0/react/axes-cross-lines/"
    - title: "Legend"
      url: "https://www.ag-grid.com/charts/archive/14.2.0/react/legend/"
    - title: "Formatters"
      url: "https://www.ag-grid.com/charts/archive/14.2.0/react/formatters/"
    - title: "Stylers"
      url: "https://www.ag-grid.com/charts/archive/14.2.0/react/stylers/"
    - title: "Series Bars"
      url: "https://www.ag-grid.com/charts/archive/14.2.0/react/bars/"
    - title: "Series Fills"
      url: "https://www.ag-grid.com/charts/archive/14.2.0/react/fills/"
    - title: "Series Markers"
      url: "https://www.ag-grid.com/charts/archive/14.2.0/react/markers/"
    - title: "Style Segments"
      url: "https://www.ag-grid.com/charts/archive/14.2.0/react/style-segments/"
    - title: "Annotations"
      url: "https://www.ag-grid.com/charts/archive/14.2.0/react/annotations/"
    - title: "Background Regions"
      url: "https://www.ag-grid.com/charts/archive/14.2.0/react/background-regions/"
    - title: "Colour Scale"
      url: "https://www.ag-grid.com/charts/archive/14.2.0/react/colour-scale/"
    - title: "Error Bars"
      url: "https://www.ag-grid.com/charts/archive/14.2.0/react/error-bars/"
llms: "https://www.ag-grid.com/charts/archive/14.2.0/llms.txt"
---

# Series Labels

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](https://www.ag-grid.com/charts/archive/14.2.0/react/series-labels/#api-reference) for the full list of available options, which vary slightly between series types.

## Styling

Enable labels with `label.enabled`, then style them with the following options.

#### Label Styling

```tsx
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 />);
```

[Live example: Label Styling](https://www.ag-grid.com/charts/archive/14.2.0/reactFunctionalTs/series-labels/examples/label-showcase/)

```js
{
    series: [
        {
            // ...
            label: {
                enabled: true,
            },
        },
    ],
}
```

In this example:

- The label text is styled with properties such as `color` and `fontWeight`. Other available options include `fontSize`, `fontStyle` and `fontFamily`.
- The label itself has a fill and border, configured with properties such as `fill` and `border`. Other available options include `fillOpacity`, `cornerRadius` and `padding`. See [Fills & Borders](https://www.ag-grid.com/charts/archive/14.2.0/react/fills-borders/) for more details.
- The `insideStyle` and `outsideStyle` properties 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](https://www.ag-grid.com/charts/archive/14.2.0/react/series-labels/#orientation) for more details.
- Providing `placement` as an ordered array lets a label fall back to an alternative position. See [Placement](https://www.ag-grid.com/charts/archive/14.2.0/react/series-labels/#placement) for more details.

## Placement

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](https://www.ag-grid.com/charts/archive/14.2.0/options/) for the full list of placement values per series type.

#### Label Placement

```tsx
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 />);
```

[Live example: Label Placement](https://www.ag-grid.com/charts/archive/14.2.0/reactFunctionalTs/series-labels/examples/label-position/)

```js
{
    series: [
        {
            // ...
            label: {
                enabled: true,
                placement: ['top', 'bottom', 'left', 'right'],
                spacing: 6,
            },
        },
    ],
}
```

In this example:

- Providing `placement` as 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](https://www.ag-grid.com/charts/archive/14.2.0/react/series-labels/#orientation) and other [Collision Avoidance](https://www.ag-grid.com/charts/archive/14.2.0/react/series-labels/#collision-avoidance) options.
  - Resize the example to see the fallback placements in action.
- `spacing` sets the pixel distance between a label and its anchor and is ignored when the resolved placement is centred.

## Orientation

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.

#### Label Orientation

```tsx
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 />);
```

[Live example: Label Orientation](https://www.ag-grid.com/charts/archive/14.2.0/reactFunctionalTs/series-labels/examples/label-orientation/)

```js
{
    series: [
        {
            type: 'bar',
            // ...
            label: {
                enabled: true,
                orientation: ['horizontal', 'vertical'],
                wrapping: 'never',
            },
        },
    ],
}
```

In this example:

- Providing `orientation` as 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](https://www.ag-grid.com/charts/archive/14.2.0/react/series-labels/#placement) and other [Collision Avoidance](https://www.ag-grid.com/charts/archive/14.2.0/react/series-labels/#collision-avoidance) options.
  - Resize the example to see the fallback placements in action.

## Collision Avoidance

> **Note**
>
> Series label collision avoidance is separate from [axis label collision avoidance](https://www.ag-grid.com/charts/archive/14.2.0/react/axes-labels/#collision-avoidance), which is configured independently on each axis.

As well as using [fallback placement](https://www.ag-grid.com/charts/archive/14.2.0/react/series-labels/#placement) and [fallback orientation](https://www.ag-grid.com/charts/archive/14.2.0/react/series-labels/#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.

#### Label Fitting

```tsx
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 />);
```

[Live example: Label Fitting](https://www.ag-grid.com/charts/archive/14.2.0/reactFunctionalTs/series-labels/examples/label-fitting/)

```js
{
    series: [
        {
            // ...
            label: {
                enabled: true,
                placement: 'inside-end',
                maxWidth: 70,
                maxHeight: 54,
                wrapping: 'on-space',
                truncate: true,
                minimumFontSize: 8,
            },
        },
    ],
}
```

In this example:

- `maxWidth` and `maxHeight` specify the maximum label size.
- Supplying a `minimumFontSize` lets 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.
- `truncate` truncates 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`.

#### Pie Chart with Fitted Labels

```tsx
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 />);
```

[Live example: Pie Chart with Fitted Labels](https://www.ag-grid.com/charts/archive/14.2.0/reactFunctionalTs/series-labels/examples/pie-label-fitting/)

```js
{
    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:

- `maxWidth` and `maxHeight` cap 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.
- `minimumFontSize` lets 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 `calloutLabel` doesn'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

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.

#### Label Collision Threshold

```tsx
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 />);
```

[Live example: Label Collision Threshold](https://www.ag-grid.com/charts/archive/14.2.0/reactFunctionalTs/series-labels/examples/label-threshold/)

```js
{
    series: [
        {
            // ...
            label: {
                enabled: true,
                collision: {
                    alwaysShow: true,
                },
            },
        },
    ],
}
```

### Threshold

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.

```js
{
    series: [
        {
            // ...
            label: {
                enabled: true,
                collision: {
                    threshold: 4,
                },
            },
        },
    ],
}
```

## API Reference

#### Label Options

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| 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 | CssColor \| AgColorRef \| AgColorRefMixOnto \| AgColorRefMixOntoColor |  | 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. |
| border.enabled | boolean |  | Whether the associated elements and properties should be used in the chart. |
| border.stroke | CssColor \| AgColorRef \| AgColorRefMixOnto \| AgColorRefMixOntoColor |  | The colour for the stroke. |
| border.strokeWidth | PixelSize |  | The width of the stroke in pixels. |
| border.strokeOpacity | Opacity |  | The opacity of the stroke colour. |
| cornerRadius | PixelSize |  | Apply rounded corners to the label box. |
| padding | PixelSize \| PaddingOptions |  | Distance between the label text and the border. A number applies uniform padding; an object sets each side. |
| fill | CssColor \| AgColorRef \| AgColorRefMixOnto \| AgColorRefMixOntoColor \| AgGradientColor \| AgPatternColor \| AgImageFill |  | 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. |

#### Collision Avoidance

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| 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. |
| collision.threshold | PixelSize |  | Collision threshold in pixels. A positive value triggers avoidance strategies when labels are further away, a negative value allows labels to overlap without triggering avoidance. |
| collision.alwaysShow | boolean |  | Whether to keep a colliding label visible when a collision remains after every avoidance strategy has been applied. When `true` the label stays at the best available position; when `false` it is hidden instead. |
| 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 | 'never' \| 'always' \| 'hyphenate' \| 'on-space' |  | 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

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| 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`. |

#### Placement Styles

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| insideStyle | AgChartLabelPlacementStyleOptions |  | Styles applied when the label is placed inside the shape. |
| insideStyle.cornerRadius | PixelSize |  | Rounded corners of the label box. |
| insideStyle.padding | PixelSize \| PaddingOptions |  | Padding between the label text and the box edge. |
| insideStyle.border | BorderOptions |  | Border applied to the label box for this placement. |
| insideStyle.border.enabled | boolean |  | Whether the associated elements and properties should be used in the chart. |
| insideStyle.border.stroke | CssColor \| AgColorRef \| AgColorRefMixOnto \| AgColorRefMixOntoColor |  | The colour for the stroke. |
| insideStyle.border.strokeWidth | PixelSize |  | The width of the stroke in pixels. |
| insideStyle.border.strokeOpacity | Opacity |  | The opacity of the stroke colour. |
| insideStyle.color | CssColor \| AgColorRef \| AgColorRefMixOnto \| AgColorRefMixOntoColor |  | The colour for text elements. A colour string, or a theme-colour reference object. |
| insideStyle.fill | CssColor \| AgColorRef \| AgColorRefMixOnto \| AgColorRefMixOntoColor \| AgGradientColor \| AgPatternColor \| AgImageFill |  | The colour for filling shapes. A colour string, or an object for a gradient, pattern, or image fill. |
| insideStyle.fillOpacity | Opacity |  | The opacity of the fill colour. |
| outsideStyle | AgChartLabelPlacementStyleOptions |  | Styles applied when the label is placed outside the shape. |
| outsideStyle.cornerRadius | PixelSize |  | Rounded corners of the label box. |
| outsideStyle.padding | PixelSize \| PaddingOptions |  | Padding between the label text and the box edge. |
| outsideStyle.border | BorderOptions |  | Border applied to the label box for this placement. |
| outsideStyle.border.enabled | boolean |  | Whether the associated elements and properties should be used in the chart. |
| outsideStyle.border.stroke | CssColor \| AgColorRef \| AgColorRefMixOnto \| AgColorRefMixOntoColor |  | The colour for the stroke. |
| outsideStyle.border.strokeWidth | PixelSize |  | The width of the stroke in pixels. |
| outsideStyle.border.strokeOpacity | Opacity |  | The opacity of the stroke colour. |
| outsideStyle.color | CssColor \| AgColorRef \| AgColorRefMixOnto \| AgColorRefMixOntoColor |  | The colour for text elements. A colour string, or a theme-colour reference object. |
| outsideStyle.fill | CssColor \| AgColorRef \| AgColorRefMixOnto \| AgColorRefMixOntoColor \| AgGradientColor \| AgPatternColor \| AgImageFill |  | The colour for filling shapes. A colour string, or an object for a gradient, pattern, or image fill. |
| outsideStyle.fillOpacity | Opacity |  | The opacity of the fill colour. |
