---
title: "Series Bars"
framework: react
version: "14.1.0"
---

# Series Bars

Data points can be represented by vertical or horizontal bars in many series types, such as [Bar](https://www.ag-grid.com/charts/react/bar-series/), [Range Bar](https://www.ag-grid.com/charts/react/range-bar-series/), [Waterfall](https://www.ag-grid.com/charts/react/waterfall-series/) and [Box Plot](https://www.ag-grid.com/charts/react/box-plot-series/).

Styling and customisation options such as `fill`, `stroke` and `cornerRadius` are configurable within each series. See [API Reference](#api-reference) for details.

## Fixed Width

Use the `width` option to set a fixed pixel width for each bar.

#### Fixed Width

```tsx
import React, { useState, Fragment } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
  AgCartesianChartOptions,
  BarSeriesModule,
  CategoryAxisModule,
  LegendModule,
  ModuleRegistry,
  NumberAxisModule,
  ScrollbarModule,
} from "ag-charts-enterprise";
import { getData } from "./data";
import clone from "clone";

ModuleRegistry.registerModules([
  BarSeriesModule,
  CategoryAxisModule,
  LegendModule,
  NumberAxisModule,
  ScrollbarModule,
]);

const ChartExample = () => {
  const [options, setOptions] = useState<AgCartesianChartOptions>({
    data: getData(),
    title: {
      text: "Quarterly Revenue by Product Line",
    },
    scrollbar: { enabled: true },
    series: [
      {
        type: "bar",
        xKey: "quarter",
        yKey: "software",
        yName: "Software",
        width: 30,
      },
      {
        type: "bar",
        xKey: "quarter",
        yKey: "hardware",
        yName: "Hardware",
        width: 30,
      },
      {
        type: "bar",
        xKey: "quarter",
        yKey: "services",
        yName: "Services",
        width: 30,
      },
    ],
    axes: {
      x: {
        type: "category",
      },
      y: {
        type: "number",
        label: {
          formatter: ({ value }) => `$${(value / 1000).toFixed(1)}B`,
        },
      },
    },
  });

  const toggleFixedWidth = () => {
    const nextOptions = clone(options);

    for (const series of nextOptions.series ?? []) {
      if (!("width" in series)) continue;
      series.width =
        series.width == null
          ? Number(document.getElementById("fixedWidthSliderValue")!.innerHTML)
          : undefined;
    }

    setOptions(nextOptions);
  };

  const updateFixedWidth = (event: any) => {
    const nextOptions = clone(options);

    const value = Number(event.target?.value);
    for (const series of nextOptions.series ?? []) {
      if (!("width" in series)) continue;
      series.width = value;
    }

    document.getElementById("fixedWidthSliderValue")!.innerHTML = String(value);

    setOptions(nextOptions);
  };

  return (
    <Fragment>
      <div className="example-controls">
        <div className="controls-row">
          <button onClick={toggleFixedWidth}>Toggle Fixed Width</button>
          <label htmlFor="fixedWidthSliderInput">Fixed Width:</label>
          <input
            type="range"
            id="fixedWidthSliderInput"
            min="5"
            max="60"
            defaultValue="30"
            step="1"
            onInput={(event) => updateFixedWidth(event)}
            onChange={(event) => updateFixedWidth(event)}
          />
          <span id="fixedWidthSliderValue">30</span>
        </div>
      </div>
      <AgCharts options={options} />
    </Fragment>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);
```

[Live example: Fixed Width](https://www.ag-grid.com/charts/reactFunctionalTs/bars/examples/fixed-width-clipping)

```js
{
    series: [
        {
            type: 'bar',
            width: 30,
        },
    ],
}
```

In this example:

- Each bar in the series has a fixed width of 30 pixels.
- Toggle the fixed width off to let bars automatically size to fit the series area.
- Use the slider to change the width in pixels.

When using fixed width bars:

- Resizing the chart does not affect the width of the bars.
- The bars will be clipped if the fixed width exceeds the available space in the series area.
- Clipped bars can be viewed using the [Scrollbar](https://www.ag-grid.com/charts/react/scrollbar/), [Navigator](https://www.ag-grid.com/charts/react/navigator/) or [Zoom](https://www.ag-grid.com/charts/react/zoom/) controls.

### Band Alignment

Use the `bandAlignment` option on a [Category](https://www.ag-grid.com/charts/react/axes-types/#category), [Unit Time](https://www.ag-grid.com/charts/react/axes-time/#unit-time) or [Ordinal Time](https://www.ag-grid.com/charts/react/axes-time/#ordinal-time) axis to align fixed width bars.

#### Band Alignment

```tsx
import React, { useState, Fragment } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
  AgBandAlignment,
  AgCartesianChartOptions,
  AgCategoryAxisOptions,
  BarSeriesModule,
  CategoryAxisModule,
  ModuleRegistry,
  NumberAxisModule,
} from "ag-charts-enterprise";
import { DataType, getData } from "./data";
import clone from "clone";

ModuleRegistry.registerModules([
  BarSeriesModule,
  CategoryAxisModule,
  NumberAxisModule,
]);

const ChartExample = () => {
  const [options, setOptions] = useState<AgCartesianChartOptions<DataType>>({
    data: getData(),
    title: {
      text: "Total Visitors to Museums and Galleries",
    },
    footnote: {
      text: "Source: Department for Digital, Culture, Media & Sport",
    },
    series: [
      {
        type: "bar",
        xKey: "quarter",
        yKey: "museums",
        yName: "Museums",
        width: 10,
      },
      {
        type: "bar",
        xKey: "quarter",
        yKey: "galleries",
        yName: "Galleries",
        width: 10,
      },
      {
        type: "bar",
        xKey: "quarter",
        yKey: "heritage",
        yName: "Heritage Sites",
        width: 10,
      },
    ],
    axes: {
      x: {
        type: "category",
        bandAlignment: "start",
      },
      y: {
        type: "number",
        title: {
          text: "Total Visitors (Millions)",
        },
      },
    },
    formatter: {
      y(params) {
        const value = params.value as number;
        const millions = value / 1000000;
        const accuracy = ["series-label", "axis-label"].includes(params.source)
          ? 0
          : 1;
        return `${millions.toFixed(accuracy)}M`;
      },
    },
  });

  const changeBandAlignment = (alignment: AgBandAlignment) => {
    const nextOptions = clone(options);

    (nextOptions.axes!.x! as AgCategoryAxisOptions).bandAlignment = alignment;

    setOptions(nextOptions);
  };

  return (
    <Fragment>
      <div className="example-controls">
        <div className="controls-row">
          <button onClick={() => changeBandAlignment("justify")}>
            Justify
          </button>
          <button onClick={() => changeBandAlignment("start")}>Start</button>
          <button onClick={() => changeBandAlignment("center")}>Center</button>
          <button onClick={() => changeBandAlignment("end")}>End</button>
        </div>
      </div>
      <AgCharts options={options} />
    </Fragment>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);
```

[Live example: Band Alignment](https://www.ag-grid.com/charts/reactFunctionalTs/bars/examples/band-alignment)

```js
{
    axes: {
        x: {
            type: 'category',
            bandAlignment: 'start',
        },
    },
}
```

In this example:

- The category axis has an initial band alignment of `start`.
- Use the buttons to compare other band alignment options.
  - `justify` - bands are sized to fill the chart width, with the bars centred within each band.
  - `start` - bands are sized to fit the bar width and aligned to the start of the axis.
  - `center` - bands are sized to fit the bar width and centred within the chart width.
  - `end` - bands are sized to fit the bar width and aligned to the end of the axis.

## Width Ratio

Use the `widthRatio` option to set the bar width as a proportion of the default width.

#### Width Ratio

```tsx
import React, { useState, Fragment } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
  AgCartesianChartOptions,
  CrosshairModule,
  LegendModule,
  ModuleRegistry,
  NumberAxisModule,
  RangeBarSeriesModule,
  UnitTimeAxisModule,
} from "ag-charts-enterprise";
import { getData } from "./data";
import clone from "clone";

const data = getData();
ModuleRegistry.registerModules([
  CrosshairModule,
  LegendModule,
  NumberAxisModule,
  RangeBarSeriesModule,
  UnitTimeAxisModule,
]);

const ChartExample = () => {
  const [options, setOptions] = useState<AgCartesianChartOptions>({
    title: {
      text: "Australia vs Global Temperature Patterns",
    },
    subtitle: {
      text: "Monthly temperature ranges (2020) showing seasonal variations across regions",
    },
    footnote: {
      text: "Data: World Meteorological Organization. Ranges show typical monthly lows and highs.",
      fontStyle: "italic",
    },
    series: [
      {
        data: data.World,
        type: "range-bar",
        xKey: "month",
        yName: "World",
        yLowKey: "lowTemperature",
        yHighKey: "highTemperature",
        yLowName: "Min Temp",
        yHighName: "Max Temp",
        cornerRadius: 5,
        fill: "transparent",
        strokeWidth: 2,
        strokeOpacity: 0.6,
        highlight: { enabled: false },
      },
      {
        data: data.Australia,
        type: "range-bar",
        xKey: "month",
        yName: "Australia",
        grouped: false,
        widthRatio: 0.4,
        yLowKey: "lowTemperature",
        yHighKey: "highTemperature",
        yLowName: "Min Temp",
        yHighName: "Max Temp",
        cornerRadius: 5,
      },
    ],
    axes: {
      x: {
        type: "unit-time",
        label: {
          formatter: ({ value }) => {
            const date = new Date(value);
            return date.toLocaleDateString("en-US", { month: "short" });
          },
        },
      },
      y: {
        label: {
          formatter: ({ value }) => `${value}°C`,
        },
      },
    },
  });

  const updateWidthRatio = (event: any) => {
    const nextOptions = clone(options);

    const value = Number(event.target?.value);
    (nextOptions.series![1] as any).widthRatio = value;

    document.getElementById("widthRatioSliderValue")!.innerHTML = String(value);

    setOptions(nextOptions);
  };

  return (
    <Fragment>
      <div className="example-controls">
        <div className="controls-row">
          <label htmlFor="widthRatioSliderInput">Width Ratio:</label>
          <input
            type="range"
            id="widthRatioSliderInput"
            min="0.1"
            max="1.0"
            defaultValue="0.4"
            step="0.1"
            onInput={(event) => updateWidthRatio(event)}
            onChange={(event) => updateWidthRatio(event)}
          />
          <span id="widthRatioSliderValue">0.4</span>
        </div>
      </div>
      <AgCharts options={options} />
    </Fragment>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);
```

[Live example: Width Ratio](https://www.ag-grid.com/charts/reactFunctionalTs/bars/examples/width-ratio)

```js
{
    series: [
        {
            type: 'range-bar',
            grouped: false,
            widthRatio: 0.4,
        },
    ],
}
```

In this example:

- The World series uses the default width ratio of 1.
- The Australia series has an initial width ratio of 0.4.
- Use the slider to change the width ratio.
- This gives an [Actual vs Target](#actual-vs-target-bars) style visualisation, with the "World" series as a background reference.

## Actual vs Target Bars

Bars can be layered to create actual vs target comparisons by using `grouped: false` to overlay series.

#### Actual vs Target

```tsx
import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
  AgCartesianChartOptions,
  BarSeriesModule,
  CategoryAxisModule,
  LegendModule,
  ModuleRegistry,
  NumberAxisModule,
} from "ag-charts-community";
import { getData } from "./data";

ModuleRegistry.registerModules([
  BarSeriesModule,
  CategoryAxisModule,
  LegendModule,
  NumberAxisModule,
]);

const ChartExample = () => {
  const [options, setOptions] = useState<AgCartesianChartOptions>({
    data: getData(),
    title: {
      text: "Quarterly Sales vs Target",
    },
    series: [
      {
        type: "bar",
        direction: "horizontal",
        xKey: "quarter",
        yKey: "target",
        yName: "Target",
        grouped: false,
        fillOpacity: 0.3,
        cornerRadius: 3,
        highlight: {
          enabled: false,
        },
      },
      {
        type: "bar",
        direction: "horizontal",
        xKey: "quarter",
        yKey: "actual",
        yName: "Actual",
        grouped: false,
        widthRatio: 0.5,
        cornerRadius: 6,
      },
    ],
  });

  return <AgCharts options={options} />;
};

const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);
```

[Live example: Actual vs Target](https://www.ag-grid.com/charts/reactFunctionalTs/bars/examples/actual-target)

```js
{
    series: [
        {
            type: 'bar',
            yKey: 'target',
            grouped: false,
            fillOpacity: 0.3,
        },
        {
            type: 'bar',
            yKey: 'actual',
            grouped: false,
            widthRatio: 0.5,
        },
    ],
}
```

In this example:

- The Target series uses `grouped: false` to span the full category width as a background bar.
- The Actual series also uses `grouped: false` with a `widthRatio` of 0.5 to appear narrower in front.
- The Target series is specified first in the `series` array so that it appears behind the Actual series.
- The target has reduced `fillOpacity` and highlighting disabled.

### Multiple Metrics

Multiple grouped series can be displayed over a single ungrouped target bar.

#### Multiple Metrics vs Target

```tsx
import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
  AgCartesianChartOptions,
  BarSeriesModule,
  CategoryAxisModule,
  LegendModule,
  ModuleRegistry,
  NumberAxisModule,
} from "ag-charts-community";
import { getData } from "./data";

ModuleRegistry.registerModules([
  BarSeriesModule,
  CategoryAxisModule,
  LegendModule,
  NumberAxisModule,
]);

const ChartExample = () => {
  const [options, setOptions] = useState<AgCartesianChartOptions>({
    data: getData(),
    title: {
      text: "Regional Sales vs Target",
    },
    series: [
      {
        type: "bar",
        xKey: "quarter",
        yKey: "target",
        yName: "Target",
        grouped: false,
        fillOpacity: 0.3,
        highlight: {
          enabled: false,
        },
      },
      {
        type: "bar",
        xKey: "quarter",
        yKey: "europe",
        yName: "Europe",
        widthRatio: 0.8,
      },
      {
        type: "bar",
        xKey: "quarter",
        yKey: "asia",
        yName: "Asia",
        widthRatio: 0.8,
      },
    ],
  });

  return <AgCharts options={options} />;
};

const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);
```

[Live example: Multiple Metrics vs Target](https://www.ag-grid.com/charts/reactFunctionalTs/bars/examples/targets-comparisons)

In this example:

- The "Target" series is ungrouped and spans the full category width as a background reference.
- The "Europe" and "Asia" series are grouped by default, sharing their portion of the category width.
- When a series has `grouped: false`, its `widthRatio` is relative to the full category width.
- When `grouped: true` (the default), `widthRatio` is relative to the automatically calculated width allocated to each series within group.

## Skip Null Bars

Use the `skipNullBars` option on a [Category](https://www.ag-grid.com/charts/react/axes-types/#category), [Unit Time](https://www.ag-grid.com/charts/react/axes-time/#unit-time) or [Ordinal Time](https://www.ag-grid.com/charts/react/axes-time/#ordinal-time) axis to prevent bars with `null`, `undefined` or missing values from taking up space within each category band. This also closes the gap when a series supplies its own `data` array and a category is absent from it.

#### Skip Null Bars

```tsx
import React, { useState, Fragment } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
  AgCartesianChartOptions,
  AgCategoryAxisOptions,
  BarSeriesModule,
  CategoryAxisModule,
  LegendModule,
  ModuleRegistry,
  NumberAxisModule,
} from "ag-charts-enterprise";
import { getData } from "./data";
import clone from "clone";

ModuleRegistry.registerModules([
  BarSeriesModule,
  CategoryAxisModule,
  LegendModule,
  NumberAxisModule,
]);

const ChartExample = () => {
  const [options, setOptions] = useState<AgCartesianChartOptions>({
    data: getData(),
    title: {
      text: "Quarterly Revenue",
    },
    series: [
      {
        type: "bar",
        xKey: "quarter",
        yKey: "software",
        yName: "Software",
      },
      {
        type: "bar",
        xKey: "quarter",
        yKey: "hardware",
        yName: "Hardware",
      },
      {
        type: "bar",
        xKey: "quarter",
        yKey: "services",
        yName: "Services",
      },
      {
        type: "bar",
        xKey: "quarter",
        yKey: "investments",
        yName: "Investments",
      },
    ],
    axes: {
      x: {
        type: "category",
        skipNullBars: true,
      },
      y: {
        type: "number",
        label: {
          formatter: ({ value }) => `$${(value / 1000).toFixed(1)}B`,
        },
      },
    },
  });

  const skipNullBars = () => {
    const nextOptions = clone(options);

    (nextOptions.axes!.x as AgCategoryAxisOptions).skipNullBars = true;

    setOptions(nextOptions);
  };

  const showNullBars = () => {
    const nextOptions = clone(options);

    (nextOptions.axes!.x as AgCategoryAxisOptions).skipNullBars = false;

    setOptions(nextOptions);
  };

  return (
    <Fragment>
      <div className="example-controls">
        <div className="controls-row">
          <button onClick={skipNullBars}>Skip Null Bars</button>
          <button onClick={showNullBars}>Show Null Bars</button>
        </div>
      </div>
      <AgCharts options={options} />
    </Fragment>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);
```

[Live example: Skip Null Bars](https://www.ag-grid.com/charts/reactFunctionalTs/bars/examples/skip-null-bars)

```js
{
    axes: {
        x: {
            type: 'category',
            skipNullBars: true,
        },
    },
}
```

In this example:

- Various values in the data are set to `null`, `undefined` or missing.
- When an axis has `skipNullBars: true`, bars with `null`, `undefined` or missing values are not represented on the chart.
- Toggle between "Skip Null Bars" and "Show Null Bars" to compare the difference.

## API Reference

#### Bar Options

These properties are common to [Bar](https://www.ag-grid.com/charts/react/bar-series/), [Range Bar](https://www.ag-grid.com/charts/react/range-bar-series/), [Waterfall](https://www.ag-grid.com/charts/react/waterfall-series/) and [Box Plot](https://www.ag-grid.com/charts/react/box-plot-series/) series types.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| width | PixelSize |  | Fixed width of each bar in the series. |
| widthRatio | Ratio |  | Ratio of the bandwidth (or specified width) to use for the width for each bar in the series. |
| cornerRadius | PixelSize |  | Apply rounded corners to each bar. |
| 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. |
| stroke | CssColor \| AgColorRef \| AgColorRefMixOnto \| AgColorRefMixOntoColor |  | The colour for the stroke. |
| strokeWidth | PixelSize |  | The width of the stroke in pixels. |
| strokeOpacity | Opacity |  | The opacity of the stroke colour. |
| lineDash | PixelSize[] |  | An array specifying the length in pixels of alternating dashes and gaps. |
| lineDashOffset | PixelSize |  | The initial offset of the dashed line in pixels. |

#### Band Alignment

This property is available on [Category](https://www.ag-grid.com/charts/react/axes-types/#category), [Ordinal Time](https://www.ag-grid.com/charts/react/axes-time/#ordinal-time) and [Unit Time](https://www.ag-grid.com/charts/react/axes-time/#unit-time) axes.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| bandAlignment | 'justify' \| 'start' \| 'center' \| 'end' | 'justify' | The alignment of bands when used with bar-like series with fixed widths. |

#### Skip Null Bars

This property is available on [Category](https://www.ag-grid.com/charts/react/axes-types/#category), [Ordinal Time](https://www.ag-grid.com/charts/react/axes-time/#ordinal-time) and [Unit Time](https://www.ag-grid.com/charts/react/axes-time/#unit-time) axes.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| skipNullBars | boolean | false | Set to `true` to prevent bars with `null`, `undefined` or missing values from taking up space in each category. |
