Stacked Bar
Basic Configuration
The Stacked Bar component has been designed to work together with XY Container. The minimal Stacked Bar configuration looks like:
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
import { VisXYContainer, VisStackedBar } from '@unovis/react'
function Component(props) {
const data: DataRecord[] = props.data
const x = (d: DataRecord) => d.x
const y = (d: DataRecord) => d.y
return (
<VisXYContainer data={data}>
<VisStackedBar x={x} y={y}/>
</VisXYContainer>
)
}
@Component({
templateUrl: 'template.html'
})
export class Component {
@Input data: DataRecord[];
x = (d: DataRecord) => d.x
y = (d: DataRecord) => d.y
}
<vis-xy-container [data]="data">
<vis-stacked-bar [x]="x" [y]="y"></vis-stacked-bar>
</vis-xy-container>
<script lang='ts'>
import { VisXYContainer, VisStackedBar } from '@unovis/svelte'
export let data: DataRecord[]
const x = (d: DataRecord) => d.x
const y = (d: DataRecord) => d.y
</script>
<VisXYContainer {data}>
<VisStackedBar {x} {y}/>
</VisXYContainer>
<script setup lang="ts">
import { VisXYContainer, VisStackedBar } from '@unovis/vue'
const props = defineProps<{ data: DataRecord[] }>()
const x = (d: DataRecord) => d.x
const y = (d: DataRecord) => d.y
</script>
<template>
<VisXYContainer :data="data">
<VisStackedBar :x="x" :y="y" />
</VisXYContainer>
</template>
import { VisXYContainer, VisStackedBar } from '@unovis/solid'
function Component(props) {
const data: DataRecord[] = () => props.data
const x = (d: DataRecord) => d.x
const y = (d: DataRecord) => d.y
return (
<VisXYContainer data={data()}>
<VisStackedBar x={x} y={y}/>
</VisXYContainer>
)
}
import { XYContainer, StackedBar } from '@unovis/ts'
import { data, DataRecord } from './data'
const container = new XYContainer<DataRecord>(node, {
components: [
new StackedBar<DataRecord>({
x: (d: DataRecord) => d.x,
y: (d: DataRecord) => d.y
})
]
}, data)
Multiple Stacked Bars
Stacked Bar can accept an array of y accessors to display values as a stack of bars:
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
import { VisXYContainer, VisStackedBar } from '@unovis/react'
function Component(props) {
const data: DataRecord[] = props.data
const x = (d: DataRecord) => d.x
const y = [
(d: DataRecord) => d.y,
(d: DataRecord) => d.y1,
(d: DataRecord) => d.y2
]
return (
<VisXYContainer data={data}>
<VisStackedBar x={x} y={y}/>
</VisXYContainer>
)
}
@Component({
templateUrl: 'template.html'
})
export class Component {
@Input data: DataRecord[];
x = (d: DataRecord) => d.x
y = [
(d: DataRecord) => d.y,
(d: DataRecord) => d.y1,
(d: DataRecord) => d.y2
]
}
<vis-xy-container [data]="data">
<vis-stacked-bar [x]="x" [y]="y"></vis-stacked-bar>
</vis-xy-container>
<script lang='ts'>
import { VisXYContainer, VisStackedBar } from '@unovis/svelte'
export let data: DataRecord[]
const x = (d: DataRecord) => d.x
const y = [
(d: DataRecord) => d.y,
(d: DataRecord) => d.y1,
(d: DataRecord) => d.y2
]
</script>
<VisXYContainer {data}>
<VisStackedBar {x} {y}/>
</VisXYContainer>
<script setup lang="ts">
import { VisXYContainer, VisStackedBar } from '@unovis/vue'
const props = defineProps<{ data: DataRecord[] }>()
const x = (d: DataRecord) => d.x
const y = [
(d: DataRecord) => d.y,
(d: DataRecord) => d.y1,
(d: DataRecord) => d.y2
]
</script>
<template>
<VisXYContainer :data="data">
<VisStackedBar :x="x" :y="y" />
</VisXYContainer>
</template>
import { VisXYContainer, VisStackedBar } from '@unovis/solid'
function Component(props) {
const data: DataRecord[] = () => props.data
const x = (d: DataRecord) => d.x
const y = [
(d: DataRecord) => d.y,
(d: DataRecord) => d.y1,
(d: DataRecord) => d.y2
]
return (
<VisXYContainer data={data()}>
<VisStackedBar x={x} y={y}/>
</VisXYContainer>
)
}
import { XYContainer, StackedBar } from '@unovis/ts'
import { data, DataRecord } from './data'
const container = new XYContainer<DataRecord>(node, {
components: [
new StackedBar<DataRecord>({
x: (d: DataRecord) => d.x,
y: [
(d: DataRecord) => d.y,
(d: DataRecord) => d.y1,
(d: DataRecord) => d.y2
]
})
]
}, data)
Baseline
By default every stack starts at zero. The baseline property moves that starting point per data record,
turning the bars into floating bars: each one spans from baseline to baseline + y. It accepts a constant
or an accessor function, and works the same way as the Area component's baseline.
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
function Component(props) {
const data: DataRecord[] = props.data
const x = (d: DataRecord) => d.x
const y = (d: DataRecord) => d.y
const baseline = (d: DataRecord) => d.y1
return (
<VisStackedBar x={x} y={y} baseline={baseline}/>
)
}
@Component({
template: `
<vis-stacked-bar
[x]="x"
[y]="y"
[baseline]="baseline"
></vis-stacked-bar>
`
})
export class Component {
@Input data: DataRecord[];
x = (d: DataRecord) => d.x
y = (d: DataRecord) => d.y
baseline = (d: DataRecord) => d.y1
}
<script lang='ts'>
import { VisXYContainer, VisStackedBar } from '@unovis/svelte'
export let data: DataRecord[]
const x = (d: DataRecord) => d.x
const y = (d: DataRecord) => d.y
const baseline = (d: DataRecord) => d.y1
</script>
<VisStackedBar {x} {y} {baseline}/>
<script setup lang="ts">
import { VisXYContainer, VisStackedBar } from '@unovis/vue'
const props = defineProps<{ data: DataRecord[] }>()
const x = (d: DataRecord) => d.x
const y = (d: DataRecord) => d.y
const baseline = (d: DataRecord) => d.y1
</script>
<template>
<VisStackedBar :x="x" :y="y" :baseline="baseline" />
</template>
function Component(props) {
const data: DataRecord[] = () => props.data
const x = (d: DataRecord) => d.x
const y = (d: DataRecord) => d.y
const baseline = (d: DataRecord) => d.y1
return (
<VisStackedBar x={x} y={y} baseline={baseline}/>
)
}
const stackedBar = new StackedBar<DataRecord>({
x: (d: DataRecord) => d.x,
y: (d: DataRecord) => d.y,
baseline: (d: DataRecord) => d.y1
})
With an array of y accessors the whole stack is offset: the first segment starts at baseline and the
rest stack on top of it as usual. Positive and negative values still stack away from the baseline in
opposite directions.
Waterfall Charts
A waterfall chart is a single-series Stacked Bar whose baseline carries the running total, so each bar
starts where the previous one ended. Unovis doesn't accumulate the values for you — you compute the running
total when preparing your data, which keeps you in control of ordering, subtotals and totals:
type Step = { label: string; value?: number; isTotal?: boolean }
function toWaterfall (steps: Step[]) {
let running = 0
return steps.map((step, index) => {
// Totals and subtotals are absolute, so they're drawn from zero up to the accumulated value
if (step.isTotal) {
running = step.value ?? running
return { index, label: step.label, value: running, start: 0, isTotal: true }
}
const start = running
running += step.value ?? 0
return { index, label: step.label, value: step.value ?? 0, start, isTotal: false }
})
}
Pass start to baseline and the signed value to y, then colour each bar by the sign of its value:
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
<VisXYContainer data={data}>
<VisAxis
type="x"
tickValues={[0,1,2,3,4,5]}
tickFormat={tickFormat}
/>
<VisAxis type="y"/>
<VisStackedBar
x={x}
y={y}
baseline={baseline}
color={color}
barPadding={0.3}
/>
</VisXYContainer>
<vis-xy-container [data]="data">
<vis-axis
type="x"
[tickValues]="[0,1,2,3,4,5]"
[tickFormat]="tickFormat"
></vis-axis>
<vis-axis type="y"></vis-axis>
<vis-stacked-bar
[x]="x"
[y]="y"
[baseline]="baseline"
[color]="color"
[barPadding]="0.3"
></vis-stacked-bar>
</vis-xy-container>
<VisXYContainer {data}>
<VisAxis type="x" tickValues={[0,1,2,3,4,5]} {tickFormat}/>
<VisAxis type="y"/>
<VisStackedBar {x} {y} {baseline} {color} barPadding={0.3}/>
</VisXYContainer>
<VisXYContainer :data="data">
<VisAxis
type="x"
:tickValues="[0,1,2,3,4,5]"
:tickFormat="tickFormat"
/>
<VisAxis type="y" />
<VisStackedBar
:x="x"
:y="y"
:baseline="baseline"
:color="color"
:barPadding="0.3"
/>
</VisXYContainer>
<VisXYContainer data={data}>
<VisAxis
type="x"
tickValues={[0,1,2,3,4,5]}
tickFormat={tickFormat}
/>
<VisAxis type="y"/>
<VisStackedBar
x={x}
y={y}
baseline={baseline}
color={color}
barPadding={0.3}
/>
</VisXYContainer>
const container = new XYContainer<DataRecord>(node, {
xAxis: new Axis({ type: "x", tickValues: [0,1,2,3,4,5], tickFormat }),
yAxis: new Axis({ type: "y" }),
components: [new StackedBar({ x, y, baseline, color, barPadding: 0.3 })]
}, data)
Steps whose value is exactly 0 produce a zero-height bar and aren't rendered. Set
barMinHeight1Px if you want those "no change" steps to stay visible as a 1 pixel line.
See the Waterfall Chart gallery example for a complete version with a tooltip.
Orientation
Stacked Bar supports horizontal and vertical orientation.
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
<VisStackedBar x={x} y={y} orientation="horizontal"/>
<vis-stacked-bar
[x]="x"
[y]="y"
orientation="horizontal"
></vis-stacked-bar>
<VisStackedBar {x} {y} orientation="horizontal"/>
<VisStackedBar :x="x" :y="y" orientation="horizontal" />
<VisStackedBar x={x} y={y} orientation="horizontal"/>
const stackedBar = new StackedBar<DataRecord>({ x, y, orientation: "horizontal" })
Rounded Corners
You can apply rounded corners to the top bar in your Stacked Bar component using the roundedCorners property, which accepts
either a number (in pixels) or boolean argument.
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
<VisStackedBar x={x} y={y} roundedCorners={true}/>
<vis-stacked-bar
[x]="x"
[y]="y"
[roundedCorners]="true"
></vis-stacked-bar>
<VisStackedBar {x} {y} roundedCorners={true}/>
<VisStackedBar :x="x" :y="y" :roundedCorners="true" />
<VisStackedBar x={x} y={y} roundedCorners={true}/>
const stackedBar = new StackedBar<DataRecord>({ x, y, roundedCorners: true })
Bar Color
Set the color of each bar by assigning the color property to a color string, a color accessor function or an array of color strings.
Stacked Bars
For stacked data, use an array of values or a single callback function.
// Either of these work
const colors = ['red', 'green', 'blue']
const color = (d: DataRecord, i: number) => ['red', 'green', 'blue'][i]
Non-Stacked Bars
If you want non-stacked data (i.e. single bars) to display variable colors, you can use a single color accessor. The following sets the color based on the value of y:
const color = (d: DataRecord) => d.y > 7 ? '#FF4F4E' : '#1acb9a'}
Custom Bar Styles
When color alone isn't enough, the barStyle accessor lets you style individual bars. It's called for
every bar with the datum and its stack index, and the returned object is applied to the bar as inline
styles. Property names can be camelCase or kebab-case, and CSS custom properties (--*) are supported.
A common use case is rendering projected or estimated values differently from actuals:
const barStyle = (d: DataRecord, i: number) => d.x > 5
? { strokeDasharray: '4 3', strokeWidth: 1, fillOpacity: 0.2 }
: undefined
A few things to keep in mind:
- The properties the component manages itself —
fill,opacity,cursorandmask— take precedence over their regular sources (like thecoloraccessor) and stay animated; all other properties are applied instantly. - Properties that are no longer returned for a bar are removed on the next render, so conditional styles clean up after themselves.
- Since inline styles bypass the CSS cascade, use CSS variable references (e.g.
stroke: 'var(--my-color)') for values that should respond to theming.
Bar Sizing
Bar Width
By default, the width of the bars is calculated automatically based on their count. But you can also strictly set the bar's width in pixels using the barWidth property:
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
<VisStackedBar x={x} y={y} barWidth={5}/>
<vis-stacked-bar [x]="x" [y]="y" [barWidth]="5"></vis-stacked-bar>
<VisStackedBar {x} {y} barWidth={5}/>
<VisStackedBar :x="x" :y="y" :barWidth="5" />
<VisStackedBar x={x} y={y} barWidth={5}/>
const stackedBar = new StackedBar<DataRecord>({ x, y, barWidth: 5 })
Limiting Dynamic Bar Width
When you don't know the number of bars in advance, and you're relying on automatic bar width calculation, you might want to limit the maximum bar width to prevent the bars from being too wide when there are just a few of them. That can be achieved by setting the barMaxWidth property.
Bar Padding
Another way to control the bar's width is by changing the barPadding property, which specifies how much of the available sector should be empty, in the range of [0,1).
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
<VisStackedBar x={x} y={y} barPadding={0.5}/>
<vis-stacked-bar [x]="x" [y]="y" [barPadding]="0.5"></vis-stacked-bar>
<VisStackedBar {x} {y} barPadding={0.5}/>
<VisStackedBar :x="x" :y="y" :barPadding="0.5" />
<VisStackedBar x={x} y={y} barPadding={0.5}/>
const stackedBar = new StackedBar<DataRecord>({ x, y, barPadding: 0.5 })
Minimum Bar Height
When you have highly scattered data with very low and high values, the bars corresponding to the lower values can be so small, so they become invisible.
If you want to prevent that you can set the minimum bar height to 1 pixel using the barMinHeight1Px boolean property.
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
<VisStackedBar x={x} y={y} barMinHeight1Px={undefined}/>
<vis-stacked-bar
[x]="x"
[y]="y"
[barMinHeight1Px]="undefined"
></vis-stacked-bar>
<VisStackedBar {x} {y} barMinHeight1Px={undefined}/>
<VisStackedBar :x="x" :y="y" :barMinHeight1Px="undefined" />
<VisStackedBar x={x} y={y} barMinHeight1Px={undefined}/>
const stackedBar = new StackedBar<DataRecord>({ x, y, barMinHeight1Px: undefined })
Preventing Overlaps with dataStep
When your data has gaps, it's impossible to do calculate of the bar width automatically. The visualization will still try to do that, but most likely the result will be wrong,
and you'll see wide overlapping bars. However, you can help the calculation by setting your data step implicitly using the dataStep property.
Consider the following example, with data mainly clumped in the domain 0 < x < 1:
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
<VisStackedBar x={x} y={y}/>
<vis-stacked-bar [x]="x" [y]="y"></vis-stacked-bar>
<VisStackedBar {x} {y}/>
<VisStackedBar :x="x" :y="y" />
<VisStackedBar x={x} y={y}/>
const stackedBar = new StackedBar<DataRecord>({ x, y })
<VisStackedBar x={x} y={y} dataStep={0.1}/>
<vis-stacked-bar [x]="x" [y]="y" [dataStep]="0.1"></vis-stacked-bar>
<VisStackedBar {x} {y} dataStep={0.1}/>
<VisStackedBar :x="x" :y="y" :dataStep="0.1" />
<VisStackedBar x={x} y={y} dataStep={0.1}/>
const stackedBar = new StackedBar<DataRecord>({ x, y, dataStep: 0.1 })
Ordinal Data
Read our guide about using ordinal/categorical values with XY Components here
Events
import { StackedBar } from '@unovis/ts'
...
events = {
[StackedBar.selectors.bar]: {
click: (d: DataRecord) => {},
...
},
[StackedBar.selectors.barGroup]: {
mouseover: (d: DataRecord[]) => {},
...
},
}
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
<VisStackedBar x={x} y={y} events={events}/>
<vis-stacked-bar [x]="x" [y]="y" [events]="events"></vis-stacked-bar>
<VisStackedBar {x} {y} {events}/>
<VisStackedBar :x="x" :y="y" :events="events" />
<VisStackedBar x={x} y={y} events={events}/>
const stackedBar = new StackedBar<DataRecord>({ x, y, events })
CSS Variables
The Stacked Bar component supports additional styling via CSS variables that you can define for your visualization container. For example:
.custom-stacked-bar {
--vis-stacked-bar-stroke-color: #000;
--vis-stacked-bar-stroke-width: 5px;
--vis-stacked-bar-hover-stroke-width: 10px;
--vis-stacked-bar-hover-stroke-color: #8777d9;
}
Supported CSS variables and their default values
--vis-stacked-bar-cursor: default;
--vis-stacked-bar-fill-color: var(--vis-color-main);
--vis-stacked-bar-stroke-color: none;
--vis-stacked-bar-stroke-width: 0px;
--vis-stacked-bar-hover-stroke-width: none;
--vis-stacked-bar-hover-stroke-color: none;
/* Dark Theme */
--vis-dark-stacked-bar-stroke-color: none;
Component Props
| Name | Type | Description |
|---|---|---|
| * required property |