> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Bar Charts

> Create vertical, horizontal, grouped, and stacked bar charts using Anvil2 themes with amCharts 5.

> [!WARNING]
>
> Charts (amCharts) is deprecated. Use [Charts - React](https://anvil.servicetitan.com/docs/charts/web)
> or [Charts - React Native](https://anvil.servicetitan.com/docs/charts/react-native) for any new chart
> experiences.

Bar charts use the amCharts 5 `XYChart` and `ColumnSeries` classes. All three Anvil2 themes apply design-token-based colors and chart styling automatically.

> [!NOTE]
>
> For design guidelines on bar charts, see [Bar Charts design guidelines](https://anvil.servicetitan.com/docs/data-visualization/bar-charts.md).

## Vertical bar chart

A basic vertical bar chart uses a `CategoryAxis` on the x-axis and a `ValueAxis` on the y-axis.

ext-charts-bar-vertical

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ext-charts-bar-vertical?file=src/App.tsx)

```tsx lines expandable theme={null}
import { useLayoutEffect } from "react";
import * as am5 from "@amcharts/amcharts5";
import am5themes_Animated from "@amcharts/amcharts5/themes/Animated";
import * as am5xy from "@amcharts/amcharts5/xy";
import { ThemeMonochrome } from "@servicetitan/anvil2-ext-charts/am5";

function App() {
  useLayoutEffect(() => {
    const data = [
      { country: "USA", value: 2025 },
      { country: "China", value: 1882 },
      { country: "Japan", value: 1809 },
      { country: "Germany", value: 1322 },
      { country: "UK", value: 1122 },
    ];

    const root = am5.Root.new("chartdiv");
    root.setThemes([am5themes_Animated.new(root), ThemeMonochrome.new(root)]);

    const chart = root.container.children.push(
      am5xy.XYChart.new(root, {
        panX: true,
        panY: true,
        wheelX: "panX",
        wheelY: "zoomX",
        paddingLeft: 0,
        paddingRight: 1,
      }),
    );

    // X axis (categories)
    const xRenderer = am5xy.AxisRendererX.new(root, {
      minGridDistance: 30,
      minorGridEnabled: true,
    });
    xRenderer.grid.template.setAll({ location: 1 });

    const xAxis = chart.xAxes.push(
      am5xy.CategoryAxis.new(root, {
        maxDeviation: 0.3,
        categoryField: "country",
        renderer: xRenderer,
        tooltip: am5.Tooltip.new(root, {}),
      }),
    );
    xAxis.data.setAll(data);

    // Y axis (values)
    const yAxis = chart.yAxes.push(
      am5xy.ValueAxis.new(root, {
        maxDeviation: 0.3,
        renderer: am5xy.AxisRendererY.new(root, { strokeOpacity: 0.1 }),
      }),
    );

    // Series
    const series = chart.series.push(
      am5xy.ColumnSeries.new(root, {
        name: "Series 1",
        xAxis: xAxis,
        yAxis: yAxis,
        valueYField: "value",
        sequencedInterpolation: true,
        categoryXField: "country",
      }),
    );

    series.columns.template.setAll({
      tooltipText: "{categoryX}: {valueY}",
      tooltipY: am5.percent(10),
    });

    series.data.setAll(data);

    return () => root.dispose();
  }, []);

  return <div id="chartdiv" style={{ minWidth: "55rem", height: "500px" }} />;
}

export default App;
```

## Horizontal bar chart

To create a horizontal bar chart, swap the axes: use a `CategoryAxis` on the y-axis and a `ValueAxis` on the x-axis. Set `categoryYField` and `valueXField` on the series instead of `categoryXField` and `valueYField`.

ext-charts-bar-horizontal

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ext-charts-bar-horizontal?file=src/App.tsx)

```tsx lines expandable theme={null}
import { useLayoutEffect } from "react";
import * as am5 from "@amcharts/amcharts5";
import am5themes_Animated from "@amcharts/amcharts5/themes/Animated";
import * as am5xy from "@amcharts/amcharts5/xy";
import { ThemeMonochrome } from "@servicetitan/anvil2-ext-charts/am5";

function App() {
  useLayoutEffect(() => {
    // Reverse data so highest values appear at the top
    const data = [
      { country: "USA", value: 2025 },
      { country: "China", value: 1882 },
      { country: "Japan", value: 1809 },
      { country: "Germany", value: 1322 },
      { country: "UK", value: 1122 },
    ].reverse();

    const root = am5.Root.new("chartdiv");
    root.setThemes([am5themes_Animated.new(root), ThemeMonochrome.new(root)]);

    const chart = root.container.children.push(
      am5xy.XYChart.new(root, {
        panX: true,
        panY: true,
        wheelX: "panY",
        wheelY: "zoomY",
        paddingLeft: 0,
        paddingRight: 1,
      }),
    );

    // Y axis (categories — swapped from vertical)
    const yRenderer = am5xy.AxisRendererY.new(root, {
      minGridDistance: 30,
      minorGridEnabled: true,
    });
    yRenderer.grid.template.setAll({ location: 1 });

    const yAxis = chart.yAxes.push(
      am5xy.CategoryAxis.new(root, {
        maxDeviation: 0.3,
        categoryField: "country",
        renderer: yRenderer,
        tooltip: am5.Tooltip.new(root, {}),
      }),
    );
    yAxis.data.setAll(data);

    // X axis (values — swapped from vertical)
    const xAxis = chart.xAxes.push(
      am5xy.ValueAxis.new(root, {
        maxDeviation: 0.3,
        renderer: am5xy.AxisRendererX.new(root, { strokeOpacity: 0.1 }),
      }),
    );

    // Series — uses categoryYField and valueXField for horizontal orientation
    const series = chart.series.push(
      am5xy.ColumnSeries.new(root, {
        name: "Series 1",
        xAxis: xAxis,
        yAxis: yAxis,
        valueXField: "value",
        sequencedInterpolation: true,
        categoryYField: "country",
      }),
    );

    series.columns.template.setAll({
      tooltipText: "{categoryY}: {valueX}",
      tooltipY: am5.percent(90),
    });

    series.data.setAll(data);

    return () => root.dispose();
  }, []);

  return <div id="chartdiv" style={{ minWidth: "55rem", height: "500px" }} />;
}

export default App;
```

The theme detects horizontal orientation when `categoryYField` or `valueXField` is set on the series.

## Grouped bar chart

Grouped bar charts display multiple series side by side for each category. Create a separate `ColumnSeries` for each data group.

ext-charts-bar-grouped

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ext-charts-bar-grouped?file=src/App.tsx)

```tsx lines expandable theme={null}
import { useLayoutEffect } from "react";
import * as am5 from "@amcharts/amcharts5";
import am5themes_Animated from "@amcharts/amcharts5/themes/Animated";
import * as am5xy from "@amcharts/amcharts5/xy";
import { ThemeCategorical } from "@servicetitan/anvil2-ext-charts/am5";

function App() {
  useLayoutEffect(() => {
    const data = [
      { year: "2021", europe: 2.5, namerica: 2.5, asia: 2.1, lamerica: 1.0 },
      { year: "2022", europe: 2.6, namerica: 2.7, asia: 2.2, lamerica: 0.5 },
      { year: "2023", europe: 2.8, namerica: 2.9, asia: 2.4, lamerica: 0.3 },
    ];

    const root = am5.Root.new("chartdiv");
    root.setThemes([am5themes_Animated.new(root), ThemeCategorical.new(root)]);

    const chart = root.container.children.push(
      am5xy.XYChart.new(root, {
        panX: false,
        panY: false,
        paddingLeft: 0,
        wheelX: "panX",
        wheelY: "zoomX",
        layout: root.verticalLayout,
      }),
    );

    // Legend
    const legend = chart.children.push(
      am5.Legend.new(root, {
        centerX: am5.p50,
        x: am5.p50,
      }),
    );

    // X axis with cell padding for grouped columns
    const xRenderer = am5xy.AxisRendererX.new(root, {
      cellStartLocation: 0.1,
      cellEndLocation: 0.9,
      minorGridEnabled: true,
    });
    xRenderer.grid.template.setAll({ location: 1 });

    const xAxis = chart.xAxes.push(
      am5xy.CategoryAxis.new(root, {
        categoryField: "year",
        renderer: xRenderer,
        tooltip: am5.Tooltip.new(root, {}),
      }),
    );
    xAxis.data.setAll(data);

    const yAxis = chart.yAxes.push(
      am5xy.ValueAxis.new(root, {
        renderer: am5xy.AxisRendererY.new(root, { strokeOpacity: 0.1 }),
      }),
    );

    // Create one series per data group
    function makeSeries(name: string, fieldName: string) {
      const series = chart.series.push(
        am5xy.ColumnSeries.new(root, {
          name: name,
          xAxis: xAxis,
          yAxis: yAxis,
          valueYField: fieldName,
          categoryXField: "year",
        }),
      );

      series.columns.template.setAll({
        tooltipText: "{name}, {categoryX}: {valueY}",
        width: am5.percent(90),
        tooltipY: 0,
      });

      series.data.setAll(data);
      legend.data.push(series);
    }

    makeSeries("Europe", "europe");
    makeSeries("North America", "namerica");
    makeSeries("Asia", "asia");
    makeSeries("Latin America", "lamerica");

    return () => root.dispose();
  }, []);

  return <div id="chartdiv" style={{ minWidth: "55rem", height: "500px" }} />;
}

export default App;
```

## Stacked bar chart

Stacked bar charts show how parts contribute to a whole. Set `stacked: true` on each `ColumnSeries`.

ext-charts-bar-stacked

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ext-charts-bar-stacked?file=src/App.tsx)

```tsx lines expandable theme={null}
import { useLayoutEffect } from "react";
import * as am5 from "@amcharts/amcharts5";
import am5themes_Animated from "@amcharts/amcharts5/themes/Animated";
import * as am5xy from "@amcharts/amcharts5/xy";
import { ThemeMonochrome } from "@servicetitan/anvil2-ext-charts/am5";

function App() {
  useLayoutEffect(() => {
    const data = [
      { year: "2021", europe: 2.5, namerica: 2.5, asia: 2.1, lamerica: 1.0 },
      { year: "2022", europe: 2.6, namerica: 2.7, asia: 2.2, lamerica: 0.5 },
      { year: "2023", europe: 2.8, namerica: 2.9, asia: 2.4, lamerica: 0.3 },
    ];

    const root = am5.Root.new("chartdiv");
    root.setThemes([am5themes_Animated.new(root), ThemeMonochrome.new(root)]);

    const chart = root.container.children.push(
      am5xy.XYChart.new(root, {
        panX: false,
        panY: false,
        wheelX: "panX",
        wheelY: "zoomX",
        paddingLeft: 0,
        layout: root.verticalLayout,
      }),
    );

    const legend = chart.children.push(
      am5.Legend.new(root, {
        centerX: am5.p50,
        x: am5.p50,
      }),
    );

    const xRenderer = am5xy.AxisRendererX.new(root, {
      cellStartLocation: 0.1,
      cellEndLocation: 0.9,
      minorGridEnabled: true,
    });
    xRenderer.grid.template.setAll({ location: 1 });

    const xAxis = chart.xAxes.push(
      am5xy.CategoryAxis.new(root, {
        categoryField: "year",
        renderer: xRenderer,
        tooltip: am5.Tooltip.new(root, {}),
      }),
    );
    xAxis.data.setAll(data);

    const yAxis = chart.yAxes.push(
      am5xy.ValueAxis.new(root, {
        min: 0,
        renderer: am5xy.AxisRendererY.new(root, { strokeOpacity: 0.1 }),
      }),
    );

    // Create stacked series — set stacked: true on each ColumnSeries
    function makeSeries(name: string, fieldName: string) {
      const series = chart.series.push(
        am5xy.ColumnSeries.new(root, {
          stacked: true,
          name: name,
          xAxis: xAxis,
          yAxis: yAxis,
          valueYField: fieldName,
          categoryXField: "year",
        }),
      );

      series.columns.template.setAll({
        tooltipText: "{name}, {categoryX}: {valueY}",
        tooltipY: am5.percent(10),
      });

      series.data.setAll(data);
      legend.data.push(series);
    }

    makeSeries("Europe", "europe");
    makeSeries("North America", "namerica");
    makeSeries("Asia", "asia");
    makeSeries("Latin America", "lamerica");

    return () => root.dispose();
  }, []);

  return <div id="chartdiv" style={{ minWidth: "55rem", height: "500px" }} />;
}

export default App;
```

## Using different themes

Change the theme import to apply a different color palette. The chart structure stays the same.

### Monochrome theme

Use `ThemeMonochrome` for data with a natural order or progression, or when displaying 4 or fewer variables. This is the default theme used in the examples above.

```tsx theme={null}
import { ThemeMonochrome } from "@servicetitan/anvil2-ext-charts/am5";

root.setThemes([
  am5themes_Animated.new(root),
  ThemeMonochrome.new(root),
]);
```

### Categorical theme

Use `ThemeCategorical` when displaying 5 or more distinct series that need maximum color differentiation.

```tsx theme={null}
import { ThemeCategorical } from "@servicetitan/anvil2-ext-charts/am5";

root.setThemes([
  am5themes_Animated.new(root),
  ThemeCategorical.new(root),
]);
```

> [!WARNING]
>
> The categorical palette colors do not all meet 3:1 contrast against each other. When using `ThemeCategorical`, include direct labeling on bars to meet [accessibility requirements](https://anvil.servicetitan.com/docs/data-visualization/accessibility.md).

### Semantic theme

Use `ThemeSemantic` when data represents status values. Colors follow the order: Success, Neutral, Warning, Danger.

```tsx theme={null}
import { ThemeSemantic } from "@servicetitan/anvil2-ext-charts/am5";

root.setThemes([
  am5themes_Animated.new(root),
  ThemeSemantic.new(root),
]);
```

## Tooltips

The theme applies tooltips that match the Anvil2 [Tooltip](https://anvil.servicetitan.com/docs/components/tooltip/design) styling across all chart types.

Customize the tooltip text format on the series:

```tsx theme={null}
series.columns.template.set("tooltipText", "{categoryX}: {valueY}");
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.