Skip to content

NbBarChart compares numeric values across discrete categories. It supports single and multiple series; multi-series data renders as grouped bars.

vue
<template>
  <NbBarChart title="Sign-ups per channel" :series="series" />
</template>

<script setup lang="ts">
const series = [
  {
    name: 'Sign-ups',
    data: [
      { x: 'Organic', y: 320 },
      { x: 'Referral', y: 240 },
      { x: 'Paid', y: 180 },
      { x: 'Direct', y: 150 },
    ],
  },
]
</script>

Multiple series ​

When more than one series shares a category, bars are grouped side-by-side.

vue
<template>
  <NbBarChart
    title="Quarterly revenue by region"
    subtitle="In thousands of USD"
    :series="series"
  />
</template>

Negative values ​

The Y axis automatically extends below zero when the data requires it.

Custom palette ​

vue
<template>
  <NbBarChart
    :series="series"
    :colors="[
      'var(--nb-c-success)',
      'var(--nb-c-warning)',
      'var(--nb-c-danger)',
    ]"
  />
</template>

Horizontal orientation ​

orientation="horizontal" puts the categories on the vertical axis and the values on the horizontal one. Reach for it when the category labels are words rather than short codes: vertically each label gets one band of width to fit in and they collide, horizontally each gets its own row and the full panel width.

vue
<template>
  <NbBarChart
    title="Open issues by area"
    orientation="horizontal"
    :series="series"
  />
</template>

The same data vertically, for comparison. On a wide desktop panel the labels just fit; give the chart a dashboard tile's width instead, and each category has around 60px to spend on a label that needs three times that. Horizontal is the fix, because the category axis then has the whole panel width for text:

The left gutter sizes itself to the longest label, up to 40% of the chart width. Past that the chart keeps its plot and the labels are the part that gives, so one unusually long category cannot crush the bars it belongs to.

Stacked series ​

stacked puts the series of a category on top of each other rather than side by side, so the bar length reads as the category total. It works in both orientations, and negative values stack downward (or leftward) from the baseline rather than cancelling the positive ones out.

vue
<template>
  <NbBarChart title="Quarterly revenue by region" stacked :series="series" />
</template>

Reacting to a click ​

Bind @select to make the bars a way into the data behind them, typically a filtered list view.

vue
<template>
  <NbBarChart :series="series" orientation="horizontal" @select="onSelect" />
</template>

<script setup lang="ts">
import { useRouter } from 'vue-router'
import type { IChartSeriesSelection } from '@nubisco/ui'

const router = useRouter()

const onSelect = (selection: IChartSeriesSelection) => {
  // `x` is the value from your data, not the rendered label, so this keeps
  // working when the axis is formatted or translated.
  router.push({ name: 'issues', query: { area: String(selection.x) } })
}
</script>

The payload identifies the datum rather than describing the pixel that was clicked:

FieldTypeDescription
kind'series'Discriminator shared across the chart family
xTChartScalarThe category value, as supplied in the data
ynumberThe value of the selected bar
seriesNamestringName of the series the bar belongs to
seriesIndexnumberIndex of that series
indexnumberIndex of the category along the shared axis
pointIChartPointThe datum itself, so z and label come back

The clickable target is the whole category band, not the exact rectangle, so a near miss still selects. In a grouped chart the position within the band picks the series; in a stacked one the position along the value axis picks the segment.

Interactivity is opt-in. With no @select listener bound, the chart is a picture: no pointer cursor, no focus ring, no button semantics. Bind one and each bar becomes a real focusable button, reachable by Tab and activated with Enter or Space, labelled with its category and value. The <svg> also stops presenting itself as a single image, so assistive technology can reach the controls inside it.

Without legend or tooltip ​

For dense dashboard tiles, you can suppress the chrome.

Props ​

PropTypeDefaultDescription
seriesIChartSeries[][]One or more named series of { x, y } data points
titlestring-Chart title
subtitlestring-Secondary descriptive line
heightnumber | string280Container height
showLegendbooleantrueRender a legend below the chart
showTooltipbooleantrueShow hover tooltip
showGridbooleantrueRender Y-axis gridlines
colorsstring[]default palettePer-series colors, recycled if shorter than series
orientationTBarOrientation'vertical''vertical' or 'horizontal'
stackedbooleanfalseStack series within a category instead of grouping

Events ​

EventPayloadDescription
selectIChartSeriesSelectionA bar was clicked, or activated by key

Data shape ​

ts
interface IChartPoint {
  x: number | string | Date
  y: number
}

interface IChartSeries {
  name: string
  data: IChartPoint[]
  color?: string
}

The X values of the first series determine the category set; subsequent series should expose the same X values in the same order.