Area Chart

Build stacked area charts with theme-aware SVG gradients.
alpha

Area charts emphasize magnitude as well as direction. They work best for totals, volume, and cumulative series where the filled region has meaning.

Stacked gradient

Use VisArea inside ChartAreaGradient when each stacked series needs a gradient derived from the ChartRoot palette. The slot exposes svgDefs for the Unovis container and a matching color accessor for the area.

Visitors
Signups
vue
<script setup lang="ts">
import { ref } from 'vue'
import { VisArea, VisAxis, VisLine, VisXYContainer } from '@unovis/vue'
import {
  ChartAreaGradient,
  ChartCrosshair,
  ChartLegend,
  ChartRoot,
  ChartTooltip,
  type ChartConfig,
} from '@typlog/ui/charts'

interface Point {
  month: string
  visitors: number
  signups: number
}

const data = ref<Point[]>([
  { month: 'Jan', visitors: 320, signups: 120 },
  { month: 'Feb', visitors: 410, signups: 160 },
  { month: 'Mar', visitors: 380, signups: 145 },
  { month: 'Apr', visitors: 520, signups: 210 },
  { month: 'May', visitors: 610, signups: 260 },
  { month: 'Jun', visitors: 720, signups: 305 },
])
const config = {
  month: { label: 'Month', role: 'x' },
  visitors: { label: 'Visitors' },
  signups: { label: 'Signups' },
} satisfies ChartConfig
const x = (_d: Point, index: number) => index
const yArea = [(d: Point) => d.visitors, (d: Point) => d.signups]
const yLine = [(d: Point) => d.visitors, (d: Point) => d.visitors + d.signups]
const xTickFormat = (value: number) => data.value[value]?.month ?? ''
</script>

<template>
  <ChartRoot v-slot="{ mounted }" :config="config">
    <ChartAreaGradient v-slot="{ color, svgDefs }">
      <VisXYContainer v-if="mounted" :data="data" :height="260" :svg-defs="svgDefs">
        <VisArea :x="x" :y="yArea" :color="color" :opacity="0.4" />
        <VisLine :x="x" :y="yLine" :line-width="1" />
        <VisAxis
          type="x"
          :tick-format="xTickFormat"
          :tick-values="data.map((_item, index) => index)"
          :tick-line="false"
          :domain-line="false"
          :grid-line="false"
        />
        <VisAxis
          type="y"
          :tick-line="false"
          :domain-line="false"
          :grid-line="true"
        />
        <ChartTooltip />
        <ChartCrosshair />
      </VisXYContainer>
    </ChartAreaGradient>
    <ChartLegend class="mt-3" align="center" />
  </ChartRoot>
</template>

Pass svgDefs to VisXYContainer; Unovis inserts the definitions into its internal SVG so area paths can resolve their gradient URLs. Keep the series order in VisArea.y aligned with ChartRoot.config.

Single series

A single gradient area is useful for request volume, traffic, or storage over time. Enabling the area line preserves a crisp boundary while the fill communicates magnitude.

vue
<script setup lang="ts">
import { VisArea, VisAxis, VisXYContainer } from '@unovis/vue'
import {
  ChartAreaGradient,
  ChartCrosshair,
  ChartRoot,
  ChartTooltip,
  type ChartConfig,
} from '@typlog/ui/charts'

interface Point {
  hour: string
  requests: number
}

const data: Point[] = [
  { hour: '00:00', requests: 18 },
  { hour: '04:00', requests: 12 },
  { hour: '08:00', requests: 38 },
  { hour: '12:00', requests: 64 },
  { hour: '16:00', requests: 52 },
  { hour: '20:00', requests: 31 },
]
const config = {
  hour: { label: 'Time', role: 'x' },
  requests: { label: 'Requests', color: 'var(--violet-9)' },
} satisfies ChartConfig
const x = (_item: Point, index: number) => index
const y = (item: Point) => item.requests
const xTickFormat = (value: number) => data[value]?.hour ?? ''
</script>

<template>
  <ChartRoot v-slot="{ mounted }" :config="config">
    <ChartAreaGradient v-slot="{ color, svgDefs }">
      <VisXYContainer v-if="mounted" :data="data" :height="220" :svg-defs="svgDefs">
        <VisArea
          :x="x"
          :y="y"
          :color="color"
          :opacity="0.4"
          :line="true"
          :line-width="1"
        />
        <VisAxis
          type="x"
          :tick-format="xTickFormat"
          :tick-values="data.map((_item, index) => index)"
          :tick-line="false"
          :domain-line="false"
          :grid-line="false"
        />
        <VisAxis type="y" :tick-format="() => ''" :tick-line="false" :domain-line="false" />
        <ChartTooltip />
        <ChartCrosshair />
      </VisXYContainer>
    </ChartAreaGradient>
  </ChartRoot>
</template>

Solid fill

Gradients are optional. Use palette colors directly for a quieter stacked area chart, and lower the opacity so overlapping grid lines and boundaries stay legible.

New users
Returning users
vue
<script setup lang="ts">
import { VisArea, VisAxis, VisXYContainer } from '@unovis/vue'
import {
  ChartCrosshair,
  ChartLegend,
  ChartRoot,
  ChartTooltip,
  type ChartConfig,
} from '@typlog/ui/charts'

interface Point {
  quarter: string
  newUsers: number
  returningUsers: number
}

const data: Point[] = [
  { quarter: 'Q1', newUsers: 36, returningUsers: 22 },
  { quarter: 'Q2', newUsers: 48, returningUsers: 29 },
  { quarter: 'Q3', newUsers: 55, returningUsers: 37 },
  { quarter: 'Q4', newUsers: 68, returningUsers: 43 },
]
const config = {
  quarter: { label: 'Quarter', role: 'x' },
  newUsers: { label: 'New users' },
  returningUsers: { label: 'Returning users' },
} satisfies ChartConfig
const x = (_item: Point, index: number) => index
const y = [(item: Point) => item.newUsers, (item: Point) => item.returningUsers]
const xTickFormat = (value: number) => data[value]?.quarter ?? ''
</script>

<template>
  <ChartRoot v-slot="{ mounted }" :config="config">
    <VisXYContainer v-if="mounted" :data="data" :height="220">
      <VisArea
        :x="x"
        :y="y"
        :opacity="0.22"
        :line="true"
        :line-width="2"
      />
      <VisAxis
        type="x"
        :tick-format="xTickFormat"
        :tick-values="data.map((_item, index) => index)"
        :tick-line="false"
        :domain-line="false"
        :grid-line="false"
      />
      <VisAxis type="y" :tick-format="() => ''" :tick-line="false" :domain-line="false" />
      <ChartTooltip />
      <ChartCrosshair />
    </VisXYContainer>
    <ChartLegend class="mt-3" align="center" />
  </ChartRoot>
</template>

Stacked areas communicate totals and composition, not precise comparison between individual series. Prefer grouped bars when readers need to compare exact values across categories.

Last updated Sep 6, 2026