# BarChart
A bar chart component for comparing values across categories. Supports horizontal and vertical orientations, stacked bars, and grouped series.
## Import
```tsx
import { BarChart } from '@coinbase/cds-web-visualization'
```
## Examples
### Basic Example
Bar charts are a useful component for comparing discrete categories of data.
They are helpful for highlighting trends to users or allowing them to compare proportions at a glance.
To start, pass in a series of data to the chart.
```jsx live
```
### Multiple Series
You can also provide multiple series of data to the chart. Series will have their bars for each data point rendered side by side.
```jsx live
function MonthlyGainsByAsset() {
const ThinSolidLine = memo((props: SolidLineProps) => );
const tickFormatter = useCallback(
(amount) =>
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(amount),
[],
);
return (
);
}
```
### Series Stacking
You can also configure stacking for your chart using the `stacked` prop.
```jsx live
function MonthlyGainsByAsset() {
const ThinSolidLine = memo((props: SolidLineProps) => );
const tickFormatter = useCallback(
(amount) =>
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(amount),
[],
);
return (
);
}
```
You can also configure multiple stacks by setting the `stackId` prop on each series.
```jsx live
function MonthlyGainsMultipleStacks() {
const ThinSolidLine = memo((props: SolidLineProps) => );
const tickFormatter = useCallback(
(amount) =>
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(amount),
[],
);
return (
);
}
```
#### Stack Gap
```jsx live
function MonthlyGainsByAsset() {
const ThinSolidLine = memo((props: SolidLineProps) => );
const tickFormatter = useCallback(
(amount) =>
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(amount),
[],
);
return (
);
}
```
### Border Radius
Bars have a default border radius of `100`. You can change this by setting the `borderRadius` prop on the chart.
Stacks will only round the top corners of touching bars.
```jsx live
{
if (value === 'D') {
return {value};
}
return value;
},
}}
style={{ margin: '0 auto' }}
/>
```
#### Round Baseline
You can also round the baseline of the bars by setting the `roundBaseline` prop on the chart.
```jsx live
{
if (value === 'D') {
return {value};
}
return value;
},
}}
style={{ margin: '0 auto' }}
/>
```
### Negative Data
```jsx live
function PositiveAndNegativeCashFlow() {
const ThinSolidLine = memo((props: SolidLineProps) => );
const categories = Array.from({ length: 31 }, (_, i) => `3/${i + 1}`);
const gains = [
5, 0, 6, 18, 0, 5, 12, 0, 12, 22, 28, 18, 0, 12, 6, 0, 0, 24, 0, 0, 4, 0, 18, 0, 0, 14, 10, 16,
0, 0, 0,
];
const losses = [
-4, 0, -8, -12, -6, 0, 0, 0, -18, 0, -12, 0, -9, -6, 0, 0, 0, 0, -22, -8, 0, 0, -10, -14, 0, 0,
0, 0, 0, -12, -10,
];
const series = [
{ id: 'gains', data: gains, color: 'var(--color-fgPositive)' },
{ id: 'losses', data: losses, color: 'var(--color-fgNegative)' },
];
return (
`$${value}M`,
}}
/>
);
}
```
### Missing Bars
You can pass in `null` or `0` values to not render a bar for that data point.
```jsx live
`$${value}k`,
showGrid: true,
showTickMarks: true,
showLine: true,
tickMarkSize: 1.5,
domain: { max: 50 },
}}
/>
```
You can also use the `BarStackComponent` prop to render an empty circle for zero values.
```jsx live
function MonthlyRewards() {
const months = ['J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D'];
const currentMonth = 7;
const purple = [null, 6, 8, 10, 7, 6, 6, 8, null, null, null, null];
const blue = [null, 10, 12, 11, 10, 9, 10, 11, null, null, null, null];
const cyan = [null, 7, 10, 12, 11, 10, 8, 11, null, null, null, null];
const green = [10, null, null, null, 1, null, null, 6, null, null, null, null];
const series = [
{ id: 'purple', data: purple, color: '#b399ff' },
{ id: 'blue', data: blue, color: '#4f7cff' },
{ id: 'cyan', data: cyan, color: '#00c2df' },
{ id: 'green', data: green, color: '#33c481' },
];
const CustomBarStackComponent = ({ children, ...props }: BarStackComponentProps) => {
if (props.height === 0) {
const diameter = props.width;
return (
);
}
return {children};
};
return (
{
if (index == currentMonth) {
return {months[index]};
}
return months[index];
},
categoryPadding: 0.27,
}}
/>
);
};
```
### Customization
#### Bar Spacing
There are two ways to control the spacing between bars. You can set the `barPadding` prop to control the spacing between bars within a series. You can also set the `categoryPadding` prop to control the spacing between stacks of bars.
```jsx live
```
#### Minimum Size
To better emphasize small values, you can set the `stackMinSize` or `barMinSize` prop to control the minimum size for entire stacks or individual bar.
It is recommended to only use `stackMinSize` for stacked charts and `barMinSize` for non-stacked charts.
##### Minimum Stack Size
You can set the `stackMinSize` prop to control the minimum size for entire stacks. This will only apply to stacks that have a value that is not `null` or `0`. It will proportionally scale the values of each bar in the stack to reach the minimum size.
```jsx live
```
##### Minimum Bar Size
You can also set the `barMinSize` prop to control the minimum size for individual bars. This will only apply to bars that have a value that is not `null` or `0`.
```jsx live
`$${value}k`,
showGrid: true,
showTickMarks: true,
showLine: true,
tickMarkSize: 1.5,
domain: { max: 50 },
}}
barMinSize={4}
/>
```
#### Multiple Y Axes
You can render bars from separate y axes in one `BarPlot`, however they aren't able to be stack.
```jsx live
`$${value}k`}
/>
`${value}%`}
/>
Revenue ($)Profit Margin (%)
```
#### Custom Components
##### Candlesticks
You can set the `BarComponent` prop to render a custom component for bars.
```jsx live
function Candlesticks() {
const infoTextId = useId();
const infoTextRef = React.useRef(null);
const selectedIndexRef = React.useRef(null);
const stockData = btcCandles.slice(0, 90)
.reverse();
const min = Math.min(...stockData.map((data) => parseFloat(data.low)));
const ThinSolidLine = memo((props: SolidLineProps) => );
// Custom line component that renders a rect to highlight the entire bandwidth
const BandwidthHighlight = memo(({ d, stroke }) => {
const { getXScale, drawingArea, getXAxis } = useCartesianChartContext();
const { scrubberPosition } = useScrubberContext();
const xScale = getXScale();
const xAxis = getXAxis();
if (!xScale || scrubberPosition === undefined) return
const xPos = xScale(scrubberPosition);
if (xPos === undefined) return
return (
);
});
const candlesData = stockData.map((data) => [parseFloat(data.low), parseFloat(data.high)]) as [
number,
number,
][];
const CandlestickBarComponent = memo(
({ x, y, width, height, originY, dataX, ...props }) => {
const { getYScale } = useCartesianChartContext();
const yScale = getYScale();
const wickX = x + width / 2;
const timePeriodValue = stockData[dataX as number];
const open = parseFloat(timePeriodValue.open);
const close = parseFloat(timePeriodValue.close);
const bullish = open < close;
const color = bullish ? 'var(--color-fgPositive)' : 'var(--color-fgNegative)';
const openY = yScale?.(open) ?? 0;
const closeY = yScale?.(close) ?? 0;
const bodyHeight = Math.abs(openY - closeY);
const bodyY = openY < closeY ? openY : closeY;
return (
);
},
);
const formatPrice = React.useCallback((price: string) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(parseFloat(price));
}, []);
const formatThousandsPrice = React.useCallback((price: string) => {
const formattedPrice = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}).format(parseFloat(price) / 1000);
return `${formattedPrice}k`;
}, []);
const formatVolume = React.useCallback((volume: string) => {
const volumeInThousands = parseFloat(volume) / 1000;
return (
new Intl.NumberFormat('en-US', {
style: 'decimal',
minimumFractionDigits: 0,
maximumFractionDigits: 2,
}).format(volumeInThousands) + 'k'
);
}, []);
const formatTime = React.useCallback(
(index) => {
if (index === null || index === undefined || index >= stockData.length) return '';
const ts = parseInt(stockData[index].start);
return new Date(ts * 1000).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
});
},
[stockData],
);
const updateInfoText = React.useCallback(
(index) => {
if (!infoTextRef.current) return;
const text =
index !== null && index !== undefined
? `Open: ${formatThousandsPrice(stockData[index].open)}, Close: ${formatThousandsPrice(
stockData[index].close,
)}, Volume: ${(parseFloat(stockData[index].volume) / 1000).toFixed(2)}k`
: formatPrice(stockData[stockData.length - 1].close);
infoTextRef.current.textContent = text;
selectedIndexRef.current = index;
},
[stockData, formatPrice, formatVolume],
);
const initialInfo = formatPrice(stockData[stockData.length - 1].close);
return (
{initialInfo}{children}}
animate={false}
borderRadius={0}
height={{ base: 150, tablet: 200, desktop: 250 }}
inset={{ top: 8, bottom: 8, left: 0, right: 0 }}
onScrubberPositionChange={updateInfoText}
series={[
{
id: 'stock-prices',
data: candlesData,
},
]}
xAxis={{
tickLabelFormatter: formatTime,
}}
yAxis={{
domain: { min },
tickLabelFormatter: formatThousandsPrice,
width: 40,
showGrid: true,
GridLineComponent: ThinSolidLine,
}}
aria-labelledby={infoTextId}
>
);
};
```
##### Outlined Stacks
You can set the `BarStackComponent` prop to render a custom component for stacks.
```jsx live
function MonthlyRewards() {
const CustomBarStackComponent = ({ children, ...props }: BarStackComponentProps) => {
return (
<>
{children}
>
);
};
return (
{
if (value === 'D') {
return {value};
}
return value;
},
}}
yAxis={{ range: ({ min, max }) => ({ min, max: max - 4 }) }}
style={{ margin: '0 auto' }}
/>
);
}
```
### Custom Transitions
You can customize the transition animations for your bar chart using the `transition` prop.
This allows you to control enter, update, and exit animations separately.
```jsx live
function UpdatingChartValues() {
const [data, setData] = React.useState([45, 80, 120, 95, 150, 110, 85]);
const [nullIndex, setNullIndex] = React.useState(null);
const displayData = React.useMemo(() => {
if (nullIndex === null) return data;
return data.map((d, i) => (i === nullIndex ? null : d));
}, [data, nullIndex]);
return (
Default AnimationsCustom Update Animations
);
}
```
## Props
| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `BarComponent` | `BarComponent` | No | `-` | Component to render the bar. |
| `BarStackComponent` | `BarStackComponent` | No | `DefaultBarStack` | Custom component to render the stack container. Can be used to add clip paths, outlines, or other custom styling. |
| `alignContent` | `ResponsiveProp