Skip to content

@vibe-labs/design-vue-charts

Vue 3 components for lightweight CSS-driven charts. No D3 dependency — for data-dense interactive charting, see @vibe-labs/design-vue-graphs.

Installation

ts
import { VibeChartHeatmap, VibeChartLegend, VibeChartLegendGradient, useHeatmapIntensity } from "@vibe-labs/design-vue-charts";

Requires the CSS layer from @vibe-labs/design-components-charts.

Components

VibeChartHeatmap

Renders a 2D heatmap grid from numeric data. Automatically maps values to 5 intensity levels (0–4).

Usage

vue
<VibeChartHeatmap
  :rows="[
    [0, 2, 5, 8, 3],
    [1, 4, 7, 2, 6],
    [3, 0, 1, 9, 4],
  ]"
  :x-labels="['Mon', 'Tue', 'Wed', 'Thu', 'Fri']"
  :y-labels="['Week 1', 'Week 2', 'Week 3']"
  size="md"
  color-scale="accent"
  :rounded="true"
  :interactive="true"
  aria-label="Weekly activity heatmap"
  @cell-click="onCellClick"
>
  <template #footer>
    <VibeChartLegendGradient min-label="None" max-label="High" />
  </template>
</VibeChartHeatmap>

Rich Data

Pass HeatmapCell objects instead of raw numbers for per-cell labels:

vue
<VibeChartHeatmap
  :rows="[
    [
      { value: 5, label: '5 commits' },
      { value: 0, label: 'No activity' },
    ],
    [
      { value: 12, label: '12 commits' },
      { value: 3, label: '3 commits' },
    ],
  ]"
/>

Props

PropTypeDefaultDescription
rows(number | HeatmapCell)[][]required2D data grid
xLabelsstring[]Column header labels
yLabelsstring[]Row header labels
minnumberautoOverride minimum value for intensity mapping
maxnumberautoOverride maximum value for intensity mapping
size"sm" | "md" | "lg""md"Cell size + gap
colorScale"accent" | "success" | "warning" | "danger" | "info""accent"Color theme
roundedbooleanfalseRound cell corners
seamlessbooleanfalseRemove gap between cells
interactivebooleanfalseEnable hover/focus/click on cells
ariaLabelstringAccessible description

Events

EventPayloadDescription
cellClickHeatmapCellEventCell clicked (when interactive)
cellHoverHeatmapCellEventCell hovered (when interactive)

Slots

SlotScoped PropsDescription
cell{ row, col, value, label, intensity }Custom cell content
footerBelow the grid (legend, controls, etc.)

VibeChartLegend

Categorical legend with color swatches.

Usage

vue
<VibeChartLegend
  layout="horizontal"
  :items="[
    { label: 'Revenue', color: 'var(--color-accent)' },
    { label: 'Costs', color: 'var(--color-danger)' },
  ]"
/>

Props

PropTypeDefaultDescription
itemsVibeChartLegendItem[]requiredArray of { label, color }
layout"horizontal" | "vertical""horizontal"Layout direction

VibeChartLegendGradient

Continuous gradient legend for heatmap intensity scales.

Usage

vue
<VibeChartLegendGradient min-label="Low" max-label="High" />

Props

PropTypeDefaultDescription
minLabelstring"Less"Low-end label
maxLabelstring"More"High-end label

Composables

useHeatmapIntensity

Maps raw numeric data to intensity buckets (0–4). Used internally by VibeChartHeatmap but exported for custom implementations.

ts
import { useHeatmapIntensity } from "@vibe-labs/design-vue-charts";

const { grid, colCount, min, max } = useHeatmapIntensity({
  rows: () => data.value,
  min: () => 0,
  max: () => 100,
});

Returns

PropertyTypeDescription
gridComputedRef<{ row, col, value, label?, intensity }[][]>Mapped 2D grid
colCountComputedRef<number>Number of columns
minComputedRef<number>Resolved minimum
maxComputedRef<number>Resolved maximum

Dependencies

  • @vibe-labs/design-components-charts — CSS + types
  • @vibe-labs/core — shared utilities
  • vue (peer) — ^3.5.18

Build

bash
npm run build

Built with Vite + vite-plugin-dts. Outputs ES module with TypeScript declarations.


Usage Guide

Setup

ts
import { VibeChartHeatmap, VibeChartLegend, VibeChartLegendGradient } from "@vibe-labs/design-vue-charts";
import "@vibe-labs/design-components-charts";

VibeChartHeatmap

Weekly activity heatmap

vue
<script setup lang="ts">
import { VibeChartHeatmap, VibeChartLegendGradient } from "@vibe-labs/design-vue-charts";
import { ref } from "vue";

// 4 weeks × 7 days of commit counts
const activityData = ref([
  [0, 2, 5, 8, 3, 0, 1],
  [1, 4, 7, 2, 6, 3, 2],
  [3, 0, 1, 9, 4, 5, 0],
  [2, 6, 3, 1, 8, 4, 7],
]);

const xLabels = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
const yLabels = ["Week 1", "Week 2", "Week 3", "Week 4"];
</script>

<template>
  <VibeChartHeatmap
    :rows="activityData"
    :x-labels="xLabels"
    :y-labels="yLabels"
    color-scale="accent"
    :rounded="true"
    :interactive="true"
    aria-label="Monthly git activity"
  >
    <template #footer>
      <VibeChartLegendGradient min-label="No activity" max-label="High activity" />
    </template>
  </VibeChartHeatmap>
</template>

Heatmap with rich cell data and click handler

vue
<script setup lang="ts">
import { VibeChartHeatmap } from "@vibe-labs/design-vue-charts";
import type { HeatmapCellEvent } from "@vibe-labs/design-vue-charts";

const rows = [
  [
    { value: 12, label: "12 plays" },
    { value: 45, label: "45 plays" },
    { value: 3, label: "3 plays" },
  ],
  [
    { value: 28, label: "28 plays" },
    { value: 0, label: "No plays" },
    { value: 67, label: "67 plays" },
  ],
];

function onCellClick(e: HeatmapCellEvent) {
  console.log(`Row ${e.row}, Col ${e.col}: ${e.label ?? e.value}`);
}
</script>

<template>
  <VibeChartHeatmap
    :rows="rows"
    :x-labels="['Mon', 'Wed', 'Fri']"
    :y-labels="['This week', 'Last week']"
    color-scale="success"
    size="lg"
    :interactive="true"
    :rounded="true"
    @cell-click="onCellClick"
  />
</template>

Categorical legend

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

const legendItems = [
  { label: "Revenue", color: "var(--color-accent)" },
  { label: "Costs", color: "var(--color-danger)" },
  { label: "Profit", color: "var(--color-success)" },
];
</script>

<template>
  <VibeChartLegend :items="legendItems" layout="horizontal" />
</template>

Composables

useHeatmapIntensity for custom rendering

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

const rawData = ref([[5, 20, 0, 15], [10, 3, 8, 25]]);

const { grid, min, max } = useHeatmapIntensity({
  rows: () => rawData.value,
});
// grid.value is a 2D array of { row, col, value, intensity (0-4) }
</script>

Common Patterns

Heatmap with custom cell slot

vue
<template>
  <VibeChartHeatmap :rows="rows" :interactive="true">
    <template #cell="{ value, intensity }">
      <span :title="`${value} events`" :data-intensity="intensity" />
    </template>
  </VibeChartHeatmap>
</template>

Vibe