Skip to content

@vibe-labs/design-vue-graphs

Vue 3 charting components for the Vibe Design System. Built on D3 scale/shape primitives with reactive dimensions, token-based series colors, and full tooltip/legend support.

Installation

ts
import { VibeGraphBar, VibeGraphLine, VibeGraphPie, VibeGraphSparkline, VibeGraphRadar } from "@vibe-labs/design-vue-graphs";

Requires the CSS layer from @vibe-labs/design-components-graphs and peer dependencies d3-scale and d3-shape.

Components

VibeGraphBar

Multi-series bar chart with grouped and stacked modes, horizontal/vertical orientation, and annotations.

Usage

vue
<VibeGraphBar
  :series="[
    {
      name: 'Revenue',
      data: [
        { label: 'Q1', value: 120 },
        { label: 'Q2', value: 180 },
      ],
    },
    {
      name: 'Costs',
      data: [
        { label: 'Q1', value: 80 },
        { label: 'Q2', value: 95 },
      ],
    },
  ]"
  :annotations="[{ value: 150, label: 'Target', variant: 'positive' }]"
  y-label="Amount ($)"
/>

<!-- Stacked -->
<VibeGraphBar :series="data" stacked />

<!-- Horizontal -->
<VibeGraphBar :series="data" horizontal />

<!-- Custom tooltip -->
<VibeGraphBar :series="data">
  <template #tooltip="{ datum, series }">
    <strong>{{ series }}</strong>: {{ datum.value }}
  </template>
</VibeGraphBar>

Props

PropTypeDefaultDescription
seriesGraphSeries[]requiredData series
horizontalbooleanfalseHorizontal bars
stackedbooleanfalseStacked mode
barRadiusnumberBar corner radius
+ all GraphBaseProps and GraphAxisProps

VibeGraphLine

Multi-series line chart with configurable curves, area fills, data point markers, and draw animation.

Usage

vue
<VibeGraphLine
  :series="[
    { name: 'Streams', data: monthlyData, color: '1' },
    { name: 'Downloads', data: downloadData, color: '2' },
  ]"
  curve="monotoneX"
  area
/>

<!-- With annotations -->
<VibeGraphLine :series="data" :annotations="[{ value: 1000, label: 'Goal' }]" />

<!-- Minimal -->
<VibeGraphLine :series="data" :legend="false" :grid="false" :points="false" />

Props

PropTypeDefaultDescription
seriesGraphSeries[]requiredData series
curve"linear" | "monotoneX" | "step" | "natural" | "cardinal""monotoneX"Line interpolation
pointsbooleantrueShow data point markers
areabooleanfalseFill area under lines
+ all GraphBaseProps and GraphAxisProps

VibeGraphPie

Pie and donut chart with slice labels, configurable padding, and corner radius.

Usage

vue
<!-- Pie -->
<VibeGraphPie
  :data="[
    { label: 'Rock', value: 45 },
    { label: 'Pop', value: 30 },
    { label: 'Jazz', value: 15 },
    { label: 'Other', value: 10 },
  ]"
  labels
/>

<!-- Donut -->
<VibeGraphPie :data="genreData" :inner-radius="0.6" />

<!-- Custom slice colors -->
<VibeGraphPie
  :data="[
    { label: 'Active', value: 80, color: '#22c55e' },
    { label: 'Inactive', value: 20, color: '#ef4444' },
  ]"
/>

Props

PropTypeDefaultDescription
dataGraphSlice[]requiredPie slices
innerRadiusnumber00 = pie, 0.6 = donut
padAnglenumber1Degrees between slices
cornerRadiusnumber2Arc corner radius
labelsbooleanfalseShow percentage labels
+ all GraphBaseProps

VibeGraphRadar

Radar (spider) chart for multi-dimensional data profiles. Supports overlaid series for comparison, polygon or circle grid styles, and per-axis max overrides.

Usage

vue
<VibeGraphRadar
  :axes="[
    { key: 'energy', label: 'Energy' },
    { key: 'underground', label: 'Underground' },
    { key: 'nostalgia', label: 'Nostalgia' },
    { key: 'sophistication', label: 'Sophistication' },
    { key: 'familiarity', label: 'Familiarity' },
    { key: 'discovery', label: 'Discovery' },
  ]"
  :series="[
    {
      name: 'Brand Profile',
      values: { energy: 70, underground: 40, nostalgia: 60, sophistication: 85, familiarity: 50, discovery: 65 },
      color: '1',
    },
  ]"
/>

<!-- Overlay comparison -->
<VibeGraphRadar
  :axes="axes"
  :series="[
    { name: 'Venue Profile', values: venueProfile, color: '1' },
    { name: 'Track Match', values: trackProfile, color: '3' },
  ]"
/>

Props

PropTypeDefaultDescription
axesRadarAxis[]requiredAxis definitions (min 3)
seriesRadarSeries[]requiredData profiles to overlay
gridStyleRadarGridStyle"polygon"Concentric grid shape: polygon or circle
gridLevelsnumber5Number of concentric rings
maxnumberScale max (auto-calculated if not set)
pointsbooleantrueShow vertex dots
fillbooleantrueFill the polygon area
+ all GraphBaseProps

VibeGraphSparkline

Minimal inline chart for dashboards, tables, and compact displays.

Usage

vue
<!-- Basic -->
<VibeGraphSparkline :data="[5, 12, 8, 20, 15, 25]" />

<!-- With area fill -->
<VibeGraphSparkline :data="values" area color="2" />

<!-- Show min/max markers -->
<VibeGraphSparkline :data="values" show-extremes />

<!-- Custom size -->
<VibeGraphSparkline :data="values" width="120px" height="2.5rem" />

Props

PropTypeDefaultDescription
datanumber[]requiredFlat array of values
widthstring"100%"SVG width
heightstring"2rem"SVG height
colorstring"1"Series index, status name, or CSS color
areabooleanfalseFill area under line
curve"linear" | "monotoneX" | "natural""monotoneX"Interpolation
showExtremesbooleanfalseMin/max dot markers
animatebooleantrueDraw animation on mount

Shared Components

VibeGraphLegend

Interactive legend with color swatches. Supports block and line swatch styles.

vue
<VibeGraphLegend :items="legendItems" @toggle="handleToggle" />

VibeGraphTooltip

Positioned tooltip that auto-clamps within the chart container. Supports custom content via slot.

VibeGraphEmpty

Empty state placeholder shown when charts have no data.


Common Props (GraphBaseProps)

All chart types (except sparkline) share:

PropTypeDefaultDescription
widthstring"auto"Chart width
heightstring"auto"Chart height
ratioGraphRatioAspect ratio (16x9, 4x3, 1x1, 21x9)
legendbooleantrueShow legend
legendPosition"top" | "bottom" | "left" | "right""bottom"Legend position
tooltipbooleantrueEnable tooltips
animatebooleantrueMount animation
animationDurationnumber600Animation ms
emptyTextstring"No data available"Empty state message
ariaLabelstringChart ARIA label

Axis Props (GraphAxisProps)

Bar and line charts additionally support:

PropTypeDescription
xLabel / yLabelstringAxis labels
gridbooleanGrid lines (default: true)
yMin / yMaxnumberExplicit axis bounds
yFormat / xFormat(v) => stringTick label formatters
annotationsGraphAnnotation[]Reference/threshold lines

Events

All charts emit:

EventPayloadDescription
hoverGraphHoverEventDatum hovered
leaveMouse left datum
clickGraphClickEventDatum clicked

Composables

useSeriesColors(items)

Returns a computed array of resolved CSS colors from series/slice color props. Handles index mapping ("1"var(--graph-series-1)), status names ("positive"var(--graph-positive)), and passthrough CSS colors.

resolveSeriesColor(color, fallbackIndex)

Standalone resolution function — use outside reactive contexts.

useGraphDimensions(containerEl, margins?)

ResizeObserver-based reactive dimensions. Returns outer/inner dimensions with configurable margins.

useTooltip(containerEl)

Tooltip state management with smart clamping (stays within container bounds, flips above/below as needed).

useRadarGeometry(options)

Pure geometry composable for radar/spider charts. Accepts axes, series, radius, and centre coordinates as refs.

ts
import { useRadarGeometry } from "@vibe-labs/design-vue-graphs";

const { spokes, gridRings, polygons, labels, resolvedMax } = useRadarGeometry({
  axes: computed(() => myAxes),
  series: computed(() => mySeries),
  radius: computed(() => 150),
  cx: computed(() => 200),
  cy: computed(() => 200),
  gridLevels: 5,
  max: computed(() => 100),
});

Returns

PropertyTypeDescription
resolvedMaxComputedRef<number>Effective max value across all axes
axisScalesComputedRef<ScaleLinear[]>Per-axis d3 linear scales
spokesComputedRef<RadarSpoke[]>Axis lines (index, key, angle, x, y)
gridRingsComputedRef<RadarGridRing[]>Concentric rings with polygon points
polygonsComputedRef<RadarPolygon[]>Data area polygons with vertices
labelsComputedRef<RadarLabel[]>Perimeter labels with anchor/baseline
gridLabelsComputedRef<{value, x, y}[]>Scale tick labels along top spoke
polarToCartesian(r, angle) => {x, y}Utility for custom rendering

Data Types

ts
interface GraphDatum {
  label: string;
  value: number;
  meta?: Record<string, unknown>;
}
interface GraphSlice extends GraphDatum {
  color?: string;
}
interface GraphSeries {
  name: string;
  data: GraphDatum[];
  color?: string;
}
interface GraphAnnotation {
  value: number;
  label?: string;
  variant?: GraphAnnotationVariant;
}

/* Radar-specific */
interface RadarAxis {
  key: string;
  label: string;
  max?: number;
}
interface RadarSeries {
  name: string;
  values: Record<string, number>;
  color?: string;
}

Series Colors

The color prop on series/slices resolves through a cascade:

  • "1" through "8"var(--graph-series-N) (tenant-themeable)
  • "positive" / "negative" / "warning" / "neutral" → status tokens
  • "#ff6b6b" → used as-is
  • undefined → auto-assigned from palette by index

Dependencies

PackagePurpose
d3-scaleLinear, band, and point scales
d3-shapeLine, area, arc, and pie generators
@vibe-labs/design-components-graphsCSS tokens + generated styles

Build

bash
npm run build

Built with Vite + vite-plugin-dts. D3 modules are externalized. Outputs ES module with TypeScript declarations.


Usage Guide

Setup

ts
import { VibeGraphBar, VibeGraphLine, VibeGraphPie, VibeGraphSparkline, VibeGraphRadar } from "@vibe-labs/design-vue-graphs";
import "@vibe-labs/design-components-graphs";
// Peer deps: d3-scale, d3-shape

VibeGraphBar

Multi-series bar chart

vue
<script setup lang="ts">
import { VibeGraphBar } from "@vibe-labs/design-vue-graphs";
import type { GraphSeries } from "@vibe-labs/design-vue-graphs";

const series: GraphSeries[] = [
  {
    name: "Revenue",
    data: [
      { label: "Q1", value: 120000 },
      { label: "Q2", value: 185000 },
      { label: "Q3", value: 162000 },
      { label: "Q4", value: 210000 },
    ],
  },
  {
    name: "Costs",
    data: [
      { label: "Q1", value: 80000 },
      { label: "Q2", value: 95000 },
      { label: "Q3", value: 88000 },
      { label: "Q4", value: 105000 },
    ],
  },
];
</script>

<template>
  <VibeGraphBar
    :series="series"
    y-label="Amount (£)"
    :annotations="[{ value: 150000, label: 'Target', variant: 'positive' }]"
    ratio="16x9"
  />
</template>

VibeGraphLine

Trend line with area fill

vue
<script setup lang="ts">
import { VibeGraphLine } from "@vibe-labs/design-vue-graphs";

const monthlyStreams = [
  { label: "Jan", value: 12400 },
  { label: "Feb", value: 15200 },
  { label: "Mar", value: 18900 },
  { label: "Apr", value: 22100 },
  { label: "May", value: 19800 },
  { label: "Jun", value: 25600 },
];
</script>

<template>
  <VibeGraphLine
    :series="[{ name: 'Monthly Streams', data: monthlyStreams, color: '1' }]"
    curve="monotoneX"
    area
    y-label="Streams"
  />
</template>

VibeGraphPie

Donut chart for genre distribution

vue
<script setup lang="ts">
import { VibeGraphPie } from "@vibe-labs/design-vue-graphs";

const genres = [
  { label: "Electronic", value: 42 },
  { label: "Hip Hop", value: 28 },
  { label: "Rock", value: 18 },
  { label: "Other", value: 12 },
];
</script>

<template>
  <VibeGraphPie
    :data="genres"
    :inner-radius="0.6"
    labels
    aria-label="Genre distribution"
    ratio="1x1"
  />
</template>

VibeGraphRadar

Brand vs track profile comparison

vue
<script setup lang="ts">
import { VibeGraphRadar } from "@vibe-labs/design-vue-graphs";

const axes = [
  { key: "energy", label: "Energy" },
  { key: "underground", label: "Underground" },
  { key: "sophistication", label: "Sophistication" },
  { key: "familiarity", label: "Familiarity" },
  { key: "discovery", label: "Discovery" },
];

const series = [
  {
    name: "Venue Profile",
    values: { energy: 70, underground: 40, sophistication: 85, familiarity: 50, discovery: 65 },
    color: "1",
  },
  {
    name: "Track Match",
    values: { energy: 80, underground: 55, sophistication: 70, familiarity: 45, discovery: 72 },
    color: "3",
  },
];
</script>

<template>
  <VibeGraphRadar :axes="axes" :series="series" :max="100" />
</template>

VibeGraphSparkline

Inline trend in a table cell

vue
<script setup lang="ts">
import { VibeGraphSparkline } from "@vibe-labs/design-vue-graphs";

defineProps<{ weeklyPlays: number[] }>();
</script>

<template>
  <td>
    <VibeGraphSparkline :data="weeklyPlays" area color="1" width="80px" height="2rem" />
  </td>
</template>

Common Patterns

Custom tooltip slot

vue
<template>
  <VibeGraphBar :series="series">
    <template #tooltip="{ datum, series }">
      <div>
        <strong>{{ series }}</strong>
        <span>{{ datum.label }}: £{{ datum.value.toLocaleString() }}</span>
      </div>
    </template>
  </VibeGraphBar>
</template>

Formatted axis ticks

vue
<template>
  <VibeGraphLine
    :series="series"
    :y-format="(v) => `£${(v / 1000).toFixed(0)}k`"
    :x-format="(label) => label.slice(0, 3)"
  />
</template>

Vibe