DOM Studio

Data Visualisation

Charts & reporting

Turn application data into clear charts. Consistent colours, useful defaults, and examples you can own and adapt.

Live example

A complete reporting surface

Choose a date range to update the traffic chart and its summary metrics. The lower cards show separate full-month reports.

Traffic overview

Example workspace · September 2026

Reporting period

September 2026

MonTueWedThuFriSatSun

September 2026

MonTueWedThuFriSatSun

Visitors

14,114

Visitors trend

Organic share

67%

Days in view

30

Audience growth

2026-09-01 to 2026-09-30 · Daily visitors

View data for Audience growth
Audience growth. Missing measurements are shown as a dash.
dateOrganic searchDirect
1 Sept210142
2 Sept259133
3 Sept288109
4 Sept28790
5 Sept26093
6 Sept222118
7 Sept195148
8 Sept195163
9 Sept225153
10 Sept274129
11 Sept323111
12 Sept351115
13 Sept350140
14 Sept322170
15 Sept284184
16 Sept258174
17 Sept258150
18 Sept289132
19 Sept338136
20 Sept387161
21 Sept414191
22 Sept412205
23 Sept384195
24 Sept346170
25 Sept320152
26 Sept322157
27 Sept353183
28 Sept402213
29 Sept450226
30 Sept478215
Revenue by plan

Full September · GBP

View data for Revenue by plan
Revenue by plan. Missing measurements are shown as a dash.
planNew accountsRecurring
Starter£4,800£2,200
Growth£7,400£9,800
Scale£6,200£15,400
Acquisition channels

Full September · All channels

View data for Acquisition channels
Acquisition channels. Missing values are shown as a dash.
CategoryValue
8,240
3,620
2,180
1,460
Install
npm install @getdom/studio
vue
<script setup>
import '@getdom/studio/style.css';
import { computed, ref } from 'vue';
import { DomAreaChart, DomBarChart, DomCard, DomDateRangePicker, DomDonutChart, DomMetricCard } from '@getdom/studio';

const period = ref({ start: '2026-09-01', end: '2026-09-30' });
const comparison = ref('none');
// Deterministic sample data keeps the example identical during SSR and hydration.
const dailyRows = Array.from({ length: 30 }, (_, index) => ({
	date: `2026-09-${String(index + 1).padStart(2, '0')}`,
	organic: Math.round(210 + index * 7 + Math.sin(index * 0.7) * 65),
	direct: Math.round(110 + index * 3 + Math.cos(index * 0.9) * 32),
}));
const sources = [{ label: 'Organic search', value: 8240, color: 'chart-1' }, { label: 'Direct', value: 3620, color: 'chart-2' }, { label: 'Referral', value: 2180, color: 'chart-3' }, { label: 'Email', value: 1460, color: 'chart-4' }];
const plans = [{ plan: 'Starter', new: 4800, recurring: 2200 }, { plan: 'Growth', new: 7400, recurring: 9800 }, { plan: 'Scale', new: 6200, recurring: 15400 }];
const trafficSeries = [{ key: 'organic', label: 'Organic search', color: 'chart-1' }, { key: 'direct', label: 'Direct', color: 'chart-2' }];
const revenueSeries = [{ key: 'new', label: 'New accounts', color: 'chart-1' }, { key: 'recurring', label: 'Recurring', color: 'chart-2' }];
const selectedRows = computed(getSelectedRows);
const visitorRows = computed(() => selectedRows.value.map((row) => ({ date: row.date, value: row.organic + row.direct })));
const visitors = computed(() => selectedRows.value.reduce((total, row) => total + row.organic + row.direct, 0));
const organic = computed(() => selectedRows.value.reduce((total, row) => total + row.organic, 0));
const description = computed(() => `${period.value.start} to ${period.value.end} · Daily visitors`);

/** @returns {Array<object>} Filters application records using inclusive reporting dates. */
function getSelectedRows() { return dailyRows.filter((row) => row.date >= period.value.start && row.date <= period.value.end); }
</script>

<template>
	<div class="w-full space-y-5">
		<div class="flex flex-wrap items-center justify-between gap-4">
			<div><p class="text-sm font-semibold">Traffic overview</p><p class="mt-1 text-xs text-muted-fg">Example workspace · September 2026</p></div>
			<DomDateRangePicker v-model="period" v-model:comparison="comparison" :show-comparison="false" anchor-date="2026-09-30" min="2026-09-01" max="2026-09-30" />
		</div>
		<div class="grid gap-4 sm:grid-cols-3">
			<DomMetricCard label="Visitors" :value="visitors" :rows="visitorRows" color="chart-1" />
			<DomMetricCard label="Organic share" :value="visitors ? organic / visitors : null" format="percent" />
			<DomMetricCard label="Days in view" :value="selectedRows.length" />
		</div>
		<DomCard padding="sm"><DomAreaChart title="Audience growth" :description="description" :rows="selectedRows" x-key="date" x-type="time" :series="trafficSeries" stacked /></DomCard>
		<div class="grid gap-4 xl:grid-cols-2">
			<DomCard padding="sm"><DomBarChart title="Revenue by plan" description="Full September · GBP" :rows="plans" x-key="plan" :series="revenueSeries" format="currency" :height="240" /></DomCard>
			<DomCard padding="sm"><DomDonutChart title="Acquisition channels" description="Full September · All channels" :rows="sources" :height="240" total-label="Visitors" /></DomCard>
		</div>
	</div>
</template>

Explore

Charts and reporting components

Application examples

Finance

Finance & cashflow

Investment portfolios, bank money in and out, cash projections, and a simulated transaction feed that updates the reports and charts.

Working with data

One chart language

Your records, directly

Pass rows, an xKey, and series definitions. Applications own fetching, aggregation, timezone policy, and comparisons. Missing measurements remain distinct from zero.

Styled with your theme

Chart colours use --chart-1 through --chart-5. Axes, grid lines, tooltips, and legends use the surrounding theme. Assign explicit colours to coordinate multiple charts.

More than a picture

Use the keyboard to inspect points, toggle legends, and open the original data table. Line, area, and bar charts emit the original record when a point is selected.

Custom compositions

Built on Unovis

The chart family uses Unovis 1.7. DOM Studio supplies the record mapping, theme integration, shared states, and reporting controls. Import DomChartContainer, DomChartLegend, and DomChartTooltip when composing a custom visualisation with Unovis. The standard charts load their drawing engine after mount; headings and data tables are available during server rendering.

Unovis documentation