Appearance
@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
| Prop | Type | Default | Description |
|---|---|---|---|
series | GraphSeries[] | required | Data series |
horizontal | boolean | false | Horizontal bars |
stacked | boolean | false | Stacked mode |
barRadius | number | — | Bar 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
| Prop | Type | Default | Description |
|---|---|---|---|
series | GraphSeries[] | required | Data series |
curve | "linear" | "monotoneX" | "step" | "natural" | "cardinal" | "monotoneX" | Line interpolation |
points | boolean | true | Show data point markers |
area | boolean | false | Fill 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
| Prop | Type | Default | Description |
|---|---|---|---|
data | GraphSlice[] | required | Pie slices |
innerRadius | number | 0 | 0 = pie, 0.6 = donut |
padAngle | number | 1 | Degrees between slices |
cornerRadius | number | 2 | Arc corner radius |
labels | boolean | false | Show 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
| Prop | Type | Default | Description |
|---|---|---|---|
axes | RadarAxis[] | required | Axis definitions (min 3) |
series | RadarSeries[] | required | Data profiles to overlay |
gridStyle | RadarGridStyle | "polygon" | Concentric grid shape: polygon or circle |
gridLevels | number | 5 | Number of concentric rings |
max | number | — | Scale max (auto-calculated if not set) |
points | boolean | true | Show vertex dots |
fill | boolean | true | Fill 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
| Prop | Type | Default | Description |
|---|---|---|---|
data | number[] | required | Flat array of values |
width | string | "100%" | SVG width |
height | string | "2rem" | SVG height |
color | string | "1" | Series index, status name, or CSS color |
area | boolean | false | Fill area under line |
curve | "linear" | "monotoneX" | "natural" | "monotoneX" | Interpolation |
showExtremes | boolean | false | Min/max dot markers |
animate | boolean | true | Draw 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:
| Prop | Type | Default | Description |
|---|---|---|---|
width | string | "auto" | Chart width |
height | string | "auto" | Chart height |
ratio | GraphRatio | — | Aspect ratio (16x9, 4x3, 1x1, 21x9) |
legend | boolean | true | Show legend |
legendPosition | "top" | "bottom" | "left" | "right" | "bottom" | Legend position |
tooltip | boolean | true | Enable tooltips |
animate | boolean | true | Mount animation |
animationDuration | number | 600 | Animation ms |
emptyText | string | "No data available" | Empty state message |
ariaLabel | string | — | Chart ARIA label |
Axis Props (GraphAxisProps)
Bar and line charts additionally support:
| Prop | Type | Description |
|---|---|---|
xLabel / yLabel | string | Axis labels |
grid | boolean | Grid lines (default: true) |
yMin / yMax | number | Explicit axis bounds |
yFormat / xFormat | (v) => string | Tick label formatters |
annotations | GraphAnnotation[] | Reference/threshold lines |
Events
All charts emit:
| Event | Payload | Description |
|---|---|---|
hover | GraphHoverEvent | Datum hovered |
leave | — | Mouse left datum |
click | GraphClickEvent | Datum 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
| Property | Type | Description |
|---|---|---|
resolvedMax | ComputedRef<number> | Effective max value across all axes |
axisScales | ComputedRef<ScaleLinear[]> | Per-axis d3 linear scales |
spokes | ComputedRef<RadarSpoke[]> | Axis lines (index, key, angle, x, y) |
gridRings | ComputedRef<RadarGridRing[]> | Concentric rings with polygon points |
polygons | ComputedRef<RadarPolygon[]> | Data area polygons with vertices |
labels | ComputedRef<RadarLabel[]> | Perimeter labels with anchor/baseline |
gridLabels | ComputedRef<{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-isundefined→ auto-assigned from palette by index
Dependencies
| Package | Purpose |
|---|---|
d3-scale | Linear, band, and point scales |
d3-shape | Line, area, arc, and pie generators |
@vibe-labs/design-components-graphs | CSS tokens + generated styles |
Build
bash
npm run buildBuilt 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-shapeVibeGraphBar
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>