Charts

Data visualization with Unovis — a framework-agnostic charting library.

Why Unovis

Haze Dashboard uses Unovis for all charts and data visualizations. Unovis is framework-agnostic — the same data models and accessor functions work across @unovis/react, @unovis/svelte, and @unovis/angular. This makes your chart code portable if you ever switch frameworks.

Both @unovis/ts (core) and

SSR: ClientOnly Wrapper

Unovis renders to SVG/Canvas and requires the DOM. Since Nuxt renders pages on the server first (SSR), you must wrap all charts in a <ClientOnly> component to prevent server-side rendering errors:

vue
<template>
  <ClientOnly>
    <VisXYContainer :data="data" :height="300">
      <VisLine :x="(d) => d.month" :y="(d) => d.revenue" />
      <VisAxis type="x" />
      <VisAxis type="y" />
    </VisXYContainer>

    <template #fallback>
      <div class="h-[300px] animate-pulse rounded-lg bg-[var(--haze-surface-hover)]" />
    </template>
  </ClientOnly>
</template>

The #fallback slot provides a placeholder skeleton while the chart loads on the client. This prevents layout shifts and gives users immediate visual feedback.

Available Chart Types

TypeComponentsUse Case
LineVisXYContainer + VisLineTrends over time (revenue, traffic)
AreaVisXYContainer + VisAreaFilled trend lines (growth, cumulative)
BarVisXYContainer + VisGroupedBarCategory comparison (sales by region)
Stacked BarVisXYContainer + VisStackedBarPart-to-whole by category
DonutVisSingleContainer + VisDonutProportions (device breakdown, category split)
ScatterVisXYContainer + VisScatterCorrelation analysis

Example: Line Chart

A complete line chart with X and Y axes. The :x and :y props accept accessor functions that map your data shape to chart coordinates:

vue
<template>
  <ClientOnly>
    <VisXYContainer :data="data" :height="300">
      <VisLine
        :x="(d, i) => i"
        :y="(d) => d.revenue"
        color="var(--haze-primary)"
        :curveType="'monotoneX'"
      />
      <VisAxis type="x" :tickFormat="(i) => months[i]" />
      <VisAxis type="y" :tickFormat="(v) => '$' + v" />
    </VisXYContainer>
  </ClientOnly>
</template>

<script setup lang="ts">

const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']

const data = [
  { month: 'Jan', revenue: 4200 },
  { month: 'Feb', revenue: 5800 },
  { month: 'Mar', revenue: 7200 },
  { month: 'Apr', revenue: 6100 },
  { month: 'May', revenue: 8400 },
  { month: 'Jun', revenue: 9200 },
]
</script>

Example: Donut Chart

Donut charts use VisSingleContainer instead of VisXYContainer:

vue
<template>
  <ClientOnly>
    <VisSingleContainer :data="data" :height="250">
      <VisDonut
        :value="(d) => d.value"
        :arcWidth="40"
        :padAngle="0.02"
        :color="(d, i) => colors[i]"
      />
    </VisSingleContainer>
  </ClientOnly>
</template>

<script setup lang="ts">

const colors = ['#14b8a6', '#3b82f6', '#f97316', '#8b5cf6']

const data = [
  { label: 'Desktop', value: 62 },
  { label: 'Mobile', value: 28 },
  { label: 'Tablet', value: 7 },
  { label: 'Other', value: 3 },
]
</script>

Example: Area Chart

Area charts work exactly like line charts but fill the area below the line. You can combine multiple area layers for comparison:

vue
<template>
  <ClientOnly>
    <VisXYContainer :data="data" :height="250">
      <VisArea
        :x="(d, i) => i"
        :y="(d) => d.users"
        color="var(--haze-primary)"
        :opacity="0.3"
        :curveType="'monotoneX'"
      />
      <VisLine
        :x="(d, i) => i"
        :y="(d) => d.users"
        color="var(--haze-primary)"
      />
      <VisAxis type="x" />
      <VisAxis type="y" />
    </VisXYContainer>
  </ClientOnly>
</template>

<script setup lang="ts">

const data = [
  { users: 120 },
  { users: 180 },
  { users: 240 },
  { users: 310 },
  { users: 280 },
  { users: 390 },
]
</script>

Accessor Functions

Unovis uses accessor functions to decouple chart components from your data shape. Instead of requiring specific property names, you provide a function that extracts the value:

typescript
// Accessor functions extract values from your data
const xAccessor = (d: DataPoint, i: number) => i    // Use index as x
const yAccessor = (d: DataPoint) => d.revenue        // Use revenue as y
const colorAccessor = (d: DataPoint) => d.color       // Per-item color

// These are passed as props:
// <VisLine :x="xAccessor" :y="yAccessor" :color="colorAccessor" />

This pattern means you can feed any data structure to the chart — just update the accessor functions.

Styling Charts

Use the design system's CSS custom properties for consistent chart colors. Unovis accepts color values as props or via CSS:

vue
<!-- Use CSS variables for consistent theming -->
<VisLine
  :x="(d, i) => i"
  :y="(d) => d.value"
  color="var(--haze-primary)"
/>

<!-- Multiple series with different colors -->
<VisLine :x="xAccessor" :y="(d) => d.revenue" color="#14b8a6" />
<VisLine :x="xAccessor" :y="(d) => d.expenses" color="#f97316" />

Charts automatically resize to fit their container. Ensure the parent element has a defined height (either via the container's :height prop or a CSS class).

Adding a Chart to a Dashboard Page

Follow these steps to add a new chart:

  1. Prepare your data array (from an API, store, or static data)
  2. Define accessor functions for the x and y axes
  3. Wrap the chart in <ClientOnly>
  4. Set a height on the container
  5. Add axes with VisAxis if needed

Tip

Unovis configs are portable — the same data structures and accessor functions work identically in @unovis/react, @unovis/svelte, and @unovis/angular. Only the template syntax changes.

Next Steps

Learn about multi-language support in the Internationalization guide.