Vitral 0.2
Dados

Chart

Gráficos em SVG sem dependências, mais de vinte tipos a partir de um objeto de opções, com tooltips, legendas, zoom e grupos sincronizados, legíveis pelo teclado.

Importação

main.ts
import { Chart } from '@vitral/vue';

Deciding which chart to use is a job for the eye, not for a list of names: every kind on one page, live, with what each one is for and the options behind it.

Lines

Dois eixos y, cada um medindo a sua série; uma segunda linha tracejada, com marcadores. Chegue à área de plotagem com Tab e use as setas.

Visitors and sign-ups. Gráfico de Linhas, 2 séries: Visitors, Sign-ups. Visitors, 12 pontos de 310 a 980. Sign-ups, 12 pontos de 42 a 140. de Jan a DecVisitors and sign-upsTwo axes, one per series
<script setup lang="ts">import { Chart, type ChartOptions, type ChartSeries } from '@vitral/vue'; const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; const traffic: ChartSeries = [    { name: 'Visitors', data: [310, 402, 385, 520, 610, 580, 720, 690, 810, 760, 905, 980] },    { name: 'Sign-ups', data: [42, 51, 48, 66, 80, 71, 95, 88, 110, 102, 126, 140] }];const lineOptions: ChartOptions = {    xaxis: { categories: months },    yaxis: [{ title: { text: 'Visitors' } }, { opposite: true, title: { text: 'Sign-ups' } }],    stroke: { curve: 'smooth', width: [3, 2], dashArray: [0, 5] },    markers: { size: [0, 4] },    title: { text: 'Visitors and sign-ups', align: 'left' },    subtitle: { text: 'Two axes, one per series' }};</script> <template>    <Chart type="line" :series="traffic" :options="lineOptions" style="width: 100%" /></template>

Stacked area

As áreas se empilham, com preenchimento em gradiente; os rótulos dos eixos usam o preset compacto, e o tooltip, um template próprio. Clique num item da legenda para ocultar uma série.

Gráfico de Área, 3 séries: API, Web, Jobs. API, 12 pontos de 1.200 requests a 3.300 requests. Web, 12 pontos de 800 requests a 2.200 requests. Jobs, 12 pontos de 280 requests a 600 requests. de Jan a Dec
<script setup lang="ts">import { Chart, type ChartOptions, type ChartSeries } from '@vitral/vue'; const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; const requests: ChartSeries = [    { name: 'API', data: [1200, 1500, 1400, 1800, 2100, 1900, 2400, 2600, 2500, 2900, 3100, 3300] },    { name: 'Web', data: [800, 900, 1100, 1000, 1300, 1500, 1400, 1600, 1800, 1700, 2000, 2200] },    { name: 'Jobs', data: [300, 280, 350, 400, 380, 420, 460, 500, 480, 530, 560, 600] }];const areaOptions: ChartOptions = {    chart: { stacked: true },    xaxis: { categories: months },    yaxis: { labels: { formatter: '{value|compact}' } },    stroke: { curve: 'monotoneCubic', width: 2 },    tooltip: { y: { formatter: '{value|integer} requests' } }};</script> <template>    <Chart type="area" :series="requests" :options="areaOptions" style="width: 100%" /></template>

Bars and columns

Os mesmos dados agrupados, empilhados (com totais), empilhados até 100% e na horizontal. Só a ponta que cresce de uma barra é arredondada.

Gráfico de Barras, 3 séries: North, South, East. North, 4 pontos de 41 a 67. South, 4 pontos de 8 a 23. East, 4 pontos de 11 a 17. de Q1 a Q4
<script setup lang="ts">import { Button, Chart, type ChartOptions, type ChartSeries, StackPanel } from '@vitral/vue';import { computed, ref } from 'vue'; const barKinds = ['grouped', 'stacked', '100%', 'horizontal'] as const;const barKind = ref<(typeof barKinds)[number]>('grouped');const regions: ChartSeries = [    { name: 'North', data: [44, 55, 41, 67] },    { name: 'South', data: [13, 23, 20, 8] },    { name: 'East', data: [11, 17, 15, 15] }];const barOptions = computed<ChartOptions>(() => ({    chart: { stacked: barKind.value === 'stacked' || barKind.value === '100%', stackType: barKind.value === '100%' ? '100%' : 'normal' },    plotOptions: {        bar: {            horizontal: barKind.value === 'horizontal',            borderRadius: 5,            borderRadiusWhenStacked: 'last',            columnWidth: '60%',            dataLabels: { total: { enabled: barKind.value === 'stacked' } }        }    },    dataLabels: { enabled: barKind.value !== 'grouped', formatter: '{value}' },    xaxis: { categories: ['Q1', 'Q2', 'Q3', 'Q4'] },    yaxis: { labels: { formatter: barKind.value === '100%' ? '{value|percent}' : '{value}' } },    legend: { position: 'top', horizontalAlign: 'right' }}));</script> <template>    <StackPanel spacing="1rem" style="width: 100%">        <div style="display: flex; gap: 0.5rem" role="group" aria-label="Layout">            <Button v-for="k in barKinds" :key="k" size="small" severity="secondary" variant="outlined" :aria-pressed="barKind === k" @click="barKind = k">{{ k }}</Button>        </div>        <Chart type="bar" :series="regions" :options="barOptions" style="width: 100%" />    </StackPanel></template>

Lollipop

Barras com a maior parte da tinta removida, para muitas categorias com pequenas diferenças.

Gráfico de Pirulito, 1 séries: Rating. Rating, 8 pontos de 3,2 a 4,6. de Lisboa a Leiria
<script setup lang="ts">import { Chart, type ChartOptions, type ChartSeries } from '@vitral/vue'; const quiet = { chart: { toolbar: { show: false } } } satisfies ChartOptions; const lollipop: ChartSeries = [{ name: 'Rating', data: [4.6, 4.4, 4.1, 3.9, 3.8, 3.6, 3.4, 3.2] }];const lollipopOptions: ChartOptions = {    ...quiet,    xaxis: { categories: ['Lisboa', 'Porto', 'Braga', 'Faro', 'Coimbra', 'Aveiro', 'Évora', 'Leiria'] },    plotOptions: { bar: { horizontal: true }, lollipop: { markerSize: 12, stemWidth: 2 } },    dataLabels: { enabled: true, formatter: '{value|fixed:1}', style: { color: 'var(--vt-text-color)' } },    grid: { xaxis: { lines: { show: true } }, yaxis: { lines: { show: false } } }};</script> <template>    <Chart type="lollipop" :series="lollipop" :options="lollipopOptions" style="width: 100%" /></template>

Scatter and bubbles

Um eixo x numérico, arrastar para dar zoom nos dois eixos (numa tela de toque, pare e depois arraste) e uma anotação em y. As bolhas mapeiam o valor para a área, não para o raio.

Gráfico de Dispersão, 2 séries: Team A, Team B. Team A, 28 pontos de 32,2 a 67. Team B, 28 pontos de 45,1 a 81,9
Gráfico de Bolhas, 2 séries: Europe, Americas. Europe, 3 pontos de 55 a 72. Americas, 3 pontos de 40 a 65
<script setup lang="ts">import { Chart, type ChartOptions, type ChartSeries } from '@vitral/vue'; const quiet = { chart: { toolbar: { show: false } } } satisfies ChartOptions;// A seeded random, so the page draws the same data twice.const seeded = (seed: number) => () => ((seed = (seed * 16807) % 2147483647) - 1) / 2147483646; const random = seeded(7);const scatter: ChartSeries = ['Team A', 'Team B'].map((name, t) => ({    name,    data: Array.from({ length: 28 }, () => [Math.round((20 + random() * 60 + t * 12) * 10) / 10, Math.round((30 + random() * 40 + t * 15) * 10) / 10] as [number, number])}));const scatterOptions: ChartOptions = {    xaxis: { title: { text: 'Hours of practice' } },    yaxis: { title: { text: 'Score' } },    markers: { shape: ['circle', 'diamond'], size: 8 },    annotations: { yaxis: [{ y: 70, label: { text: 'Pass mark' } }] }}; const bubbles: ChartSeries = [    { name: 'Europe', data: [{ x: 12, y: 60, z: 450 }, { x: 25, y: 72, z: 83 }, { x: 34, y: 55, z: 67 }] },    { name: 'Americas', data: [{ x: 18, y: 40, z: 330 }, { x: 42, y: 65, z: 210 }, { x: 55, y: 48, z: 39 }] }];const bubbleOptions: ChartOptions = { ...quiet, xaxis: { title: { text: 'Growth %' } }, tooltip: { z: { title: 'Population (M)' } }, dataLabels: { enabled: true, formatter: '{value}' } };</script> <template>    <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); gap: 1rem; width: 100%">        <Chart type="scatter" :series="scatter" :options="scatterOptions" />        <Chart type="bubble" :series="bubbles" :options="bubbleOptions" />    </div></template>

Heat map

Uma cor em cinco passos: “mais da mesma cor” dispensa legenda.

Gráfico de Mapa de calor, 7 séries: Mon, Tue, Wed, Thu, Fri, Sat, Sun. Mon, 9 pontos de 28 commits a 89 commits. Tue, 9 pontos de 28 commits a 90 commits. Wed, 9 pontos de 33 commits a 93 commits. Thu, 9 pontos de 44 commits a 95 commits. Fri, 9 pontos de 45 commits a 95 commits. Sat, 9 pontos de 14 commits a 66 commits. Sun, 9 pontos de 8 commits a 64 commits. de 06 a 22
<script setup lang="ts">import { Chart, type ChartOptions, type ChartSeries } from '@vitral/vue'; const quiet = { chart: { toolbar: { show: false } } } satisfies ChartOptions;// A seeded random, so the page draws the same data twice.const seeded = (seed: number) => () => ((seed = (seed * 16807) % 2147483647) - 1) / 2147483646; const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];const hours = ['06', '08', '10', '12', '14', '16', '18', '20', '22'];const busy = seeded(3);const heat: ChartSeries = days.map((d, di) => ({ name: d, data: hours.map((x, hi) => ({ x, y: Math.round((di > 4 ? 20 : 50) + Math.sin(hi / 2) * 30 + busy() * 20) })) }));const heatOptions: ChartOptions = { ...quiet, colors: ['var(--vt-chart-1)'], plotOptions: { heatmap: { shadeSteps: 5, radius: 3 } }, tooltip: { y: { formatter: '{value} commits' } } };</script> <template>    <Chart type="heatmap" :series="heat" :options="heatOptions" height="280" style="width: 100%" /></template>

Waterfall

Barras que flutuam: cada uma começa onde o total acumulado parou, então o gráfico mostra como um número foi de um total a outro. `plotOptions.waterfall.totals` indica as colunas que voltam a zero.

Gráfico de Cascata, 1 séries: Cash. Cash, 7 pontos de -260 a 1,6 mil. de Opening a Closing
<script setup lang="ts">import { Chart, type ChartOptions, type ChartSeries } from '@vitral/vue'; const quiet = { chart: { toolbar: { show: false } } } satisfies ChartOptions; const cash: ChartSeries = [{ name: 'Cash', data: [1200, 420, -180, 310, -260, 140, null] }];const cashOptions: ChartOptions = {    ...quiet,    xaxis: { categories: ['Opening', 'Sales', 'Refunds', 'Services', 'Costs', 'Other', 'Closing'] },    // The last column is a total: it is drawn from zero to the running sum    // rather than as another step.    plotOptions: { waterfall: { totals: [6] } },    yaxis: { labels: { formatter: '{value|compact}' } },    dataLabels: { enabled: true, formatter: '{value|compact}' }};</script> <template>    <Chart type="waterfall" :series="cash" :options="cashOptions" height="300" style="width: 100%" /></template>

Range bar and range area

Um ponto `[low, high]` em vez de um único número: a barra vai de um ao outro, e a área preenche o espaço entre eles. Uma linha ao lado da faixa é uma segunda série.

Gráfico de Barras de intervalo, 1 séries: On call. On call, 4 pontos de 8:00 a 24:00. de Ana a Dan
Gráfico de Área de intervalo, 2 séries: Likely range, Forecast. Likely range, 12 pontos de 78 a 155. Forecast, 12 pontos de 59 a 119,5. de Jan a Dec
<script setup lang="ts">import { Chart, type ChartOptions, type ChartSeries } from '@vitral/vue'; const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];const quiet = { chart: { toolbar: { show: false } } } satisfies ChartOptions; const shifts: ChartSeries = [{ name: 'On call', data: [{ x: 'Ana', y: [8, 16] }, { x: 'Bruno', y: [12, 20] }, { x: 'Célia', y: [16, 24] }, { x: 'Dan', y: [0, 8] }] }];const shiftOptions: ChartOptions = {    ...quiet,    plotOptions: { bar: { horizontal: true } },    // Turned on its side the value axis is the y one, so the title goes there.    yaxis: { title: { text: 'Hour of the day' } },    tooltip: { y: { formatter: '{value}:00' } }}; const forecast: ChartSeries = [    { name: 'Likely range', type: 'rangeArea', data: months.map((x, i) => ({ x, y: [40 + i * 4, 78 + i * 7] as [number, number] })) },    { name: 'Forecast', type: 'line', data: months.map((_, i) => 59 + i * 5.5) }];const forecastOptions: ChartOptions = { ...quiet, xaxis: { categories: months }, stroke: { curve: 'smooth', width: [1, 3] }, yaxis: { title: { text: 'Orders' } } };</script> <template>    <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); gap: 1rem; width: 100%">        <Chart type="rangeBar" :series="shifts" :options="shiftOptions" height="280" />        <Chart type="rangeArea" :series="forecast" :options="forecastOptions" height="280" />    </div></template>

Histogram and box plot

Duas maneiras de mostrar uma dispersão. O histograma recebe as leituras brutas e as agrupa em faixas por conta própria; o box plot recebe os cinco números — mínimo, quartis, mediana, máximo — por categoria.

Gráfico de Histograma, 1 séries: Response time. Response time, 13 pontos de 1 a 68. de 40–50 a 160–170
Gráfico de Diagrama de caixa, 1 séries: Response time. Response time, 4 pontos de 22 a 68. de API a Search
<script setup lang="ts">import { Chart, type ChartOptions, type ChartSeries } from '@vitral/vue'; const quiet = { chart: { toolbar: { show: false } } } satisfies ChartOptions;// A seeded random, so the page draws the same data twice.const seeded = (seed: number) => () => ((seed = (seed * 16807) % 2147483647) - 1) / 2147483646; // Raw readings: the chart counts them into bins itself.const noise = seeded(17);const latencies = Array.from({ length: 400 }, () => Math.round(40 + Math.abs(noise() + noise() + noise() - 1.5) * 90));const latencySeries: ChartSeries = [{ name: 'Response time', data: latencies }];const latencyOptions: ChartOptions = {    ...quiet,    plotOptions: { histogram: { bins: 16 } },    xaxis: { title: { text: 'Milliseconds' } },    yaxis: { title: { text: 'Requests' } },    colors: ['var(--vt-chart-5)']}; const spread: ChartSeries = [    {        name: 'Response time',        data: [            { x: 'API', y: [12, 28, 41, 63, 140] as [number, number, number, number, number] },            { x: 'Web', y: [30, 52, 68, 90, 180] as [number, number, number, number, number] },            { x: 'Jobs', y: [8, 15, 22, 34, 70] as [number, number, number, number, number] },            { x: 'Search', y: [18, 34, 49, 71, 155] as [number, number, number, number, number] }        ]    }];const spreadOptions: ChartOptions = { ...quiet, yaxis: { title: { text: 'Milliseconds' } }, colors: ['var(--vt-chart-2)'] };</script> <template>    <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); gap: 1rem; width: 100%">        <Chart type="histogram" :series="latencySeries" :options="latencyOptions" height="280" />        <Chart type="boxPlot" :series="spread" :options="spreadOptions" height="280" />    </div></template>

Funnel

Etapas de um processo, cada uma tão larga quanto a sua fatia da primeira, para que a queda se leia como uma rampa. `{percent}` está no formatador do rótulo porque é a fatia que se procura num funil.

Gráfico de Funil, 1 séries: Signups. Signups, 5 pontos de Renewed: 610 (13%) a Visited: 4,8 mil (100%). de Visited a Renewed
<script setup lang="ts">import { Chart, type ChartOptions, type ChartSeries } from '@vitral/vue'; const quiet = { chart: { toolbar: { show: false } } } satisfies ChartOptions; const signups: ChartSeries = [{ name: 'Signups', data: [4820, 3100, 1740, 980, 610] }];const funnelOptions: ChartOptions = {    ...quiet,    labels: ['Visited', 'Signed up', 'Activated', 'Subscribed', 'Renewed'],    plotOptions: { funnel: { neck: '30%', gap: 4 } },    dataLabels: { enabled: true, formatter: '{seriesName}: {value|compact} ({percent|percent:0})' }};</script> <template>    <Chart type="funnel" :series="signups" :options="funnelOptions" height="320" style="width: 100%" /></template>

Stream

Uma área empilhada flutuando sobre uma linha de base própria: as camadas dividem o movimento, em vez de a de baixo carregá-lo sozinha. `plotOptions.stream.offset` escolhe como.

Gráfico de Fluxo, 5 séries: Search, Direct, Social, Mail, Referral. Search, 12 pontos de 30 a 66. Direct, 12 pontos de 27 a 70. Social, 12 pontos de 36 a 89. Mail, 12 pontos de 33 a 77. Referral, 12 pontos de 36 a 89. de Jan a Dec
<script setup lang="ts">import { Chart, type ChartOptions, type ChartSeries } from '@vitral/vue'; const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];const quiet = { chart: { toolbar: { show: false } } } satisfies ChartOptions; const traffic: ChartSeries = ['Search', 'Direct', 'Social', 'Mail', 'Referral'].map((name, row) => ({    name,    data: months.map((_, i) => Math.round(30 + Math.sin((i + row * 2) / 1.7) * 18 + row * 6 + i * (row % 2 ? 1.5 : 3)))}));const streamOptions: ChartOptions = { ...quiet, xaxis: { categories: months }, tooltip: { shared: true } };</script> <template>    <Chart type="stream" :series="traffic" :options="streamOptions" height="300" style="width: 100%" /></template>

Bullet

Uma medida, a sua meta e as faixas em que ela cai — o trabalho de um medidor numa tira que cabe numa linha de tabela. O traço é a meta que o ponto carrega.

Gráfico de Marcador, 1 séries: Against target. Against target, 4 pontos de 41% a 112%. de Revenue a Margin
<script setup lang="ts">import { Chart, type ChartOptions, type ChartSeries } from '@vitral/vue'; const quiet = { chart: { toolbar: { show: false } } } satisfies ChartOptions; const against: ChartSeries = [    {        name: 'Against target',        data: [            { x: 'Revenue', y: 78, target: 90 },            { x: 'Signups', y: 112, target: 100 },            { x: 'Retention', y: 64, target: 75 },            { x: 'Margin', y: 41, target: 50 }        ]    }];const bulletOptions: ChartOptions = {    ...quiet,    // The bands are the scale a bullet is read against: poor, fair, good.    plotOptions: { bullet: { ranges: [{ from: 0, to: 130 }, { from: 0, to: 90 }, { from: 0, to: 55 }] } },    tooltip: { y: { formatter: '{value}%' } }};</script> <template>    <Chart type="bullet" :series="against" :options="bulletOptions" height="260" style="width: 100%" /></template>

Treemap and sunburst

Duas maneiras de desenhar uma hierarquia. O treemap a encaixa em caixas cujas áreas são os valores; o sunburst a dispõe em anéis, um nível por anel, para que cada fatia fique junto da sua família.

Gráfico de Mapa de árvore, 3 séries: Europe, Americas, Asia. Europe, 4 pontos de 12 a 84. Americas, 3 pontos de 39 a 132. Asia, 3 pontos de 14 a 61. de United States a Portugal
Gráfico de Explosão solar, 3 séries: Europe, Americas, Asia. Europe, 5 pontos de 12 a 211. Americas, 4 pontos de 39 a 243. Asia, 4 pontos de 14 a 130. de Europe a Singapore
<script setup lang="ts">import { Chart, type ChartOptions, type ChartSeries } from '@vitral/vue'; const quiet = { chart: { toolbar: { show: false } } } satisfies ChartOptions; const regions: ChartSeries = [    { name: 'Europe', data: [{ x: 'Germany', y: 84 }, { x: 'France', y: 68 }, { x: 'Spain', y: 47 }, { x: 'Portugal', y: 12 }] },    { name: 'Americas', data: [{ x: 'United States', y: 132 }, { x: 'Brazil', y: 72 }, { x: 'Mexico', y: 39 }] },    { name: 'Asia', data: [{ x: 'Japan', y: 61 }, { x: 'India', y: 55 }, { x: 'Singapore', y: 14 }] }];const treemapOptions: ChartOptions = { ...quiet, legend: { show: true, position: 'bottom' } };</script> <template>    <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); gap: 1rem; width: 100%">        <Chart type="treemap" :series="regions" :options="treemapOptions" height="300" />        <Chart type="sunburst" :series="regions" :options="quiet" height="300" />    </div></template>

Uma árvore de qualquer profundidade

Um ponto que indica um `parent` monta a árvore por conta própria, em vez dos dois níveis que uma lista de séries dá.

Gráfico de Explosão solar, 1 séries: Headcount. Headcount, 9 pontos de 4 a 41. de Engineering a Research
<script setup lang="ts">import { Chart, type ChartOptions, type ChartSeries } from '@vitral/vue'; const quiet = { chart: { toolbar: { show: false } } } satisfies ChartOptions; // A parent a point names builds a tree of any depth, rather than the two// levels a list of series gives.const roles: ChartSeries = [    {        name: 'Headcount',        data: [            { x: 'Engineering', y: 0 },            { x: 'Product', y: 0 },            { x: 'Platform', y: 18, parent: 'Engineering' },            { x: 'Web', y: 14, parent: 'Engineering' },            { x: 'Mobile', y: 9, parent: 'Engineering' },            { x: 'Design', y: 7, parent: 'Product' },            { x: 'Research', y: 4, parent: 'Product' },            { x: 'iOS', y: 5, parent: 'Mobile' },            { x: 'Android', y: 4, parent: 'Mobile' }        ]    }];</script> <template>    <Chart type="sunburst" :series="roles" :options="quiet" height="340" style="width: 100%" /></template>

Radial bars and a gauge

Anéis lidos contra as próprias trilhas, e um número contra o seu máximo, com a leitura no meio. `plotOptions.radialBar.min` e `max` definem o que significa um anel completo.

Gráfico de Barras radiais, 3 séries: Storage, Memory, CPU. Storage, 1 pontos de 72 a 72. Memory, 1 pontos de 48 a 48. CPU, 1 pontos de 35 a 35. de Storage a CPU
Gráfico de Medidor, 1 séries: Uptime. Uptime, 1 pontos de 99,4 a 99,4
<script setup lang="ts">import { Chart, type ChartOptions, type ChartSeries } from '@vitral/vue'; const quiet = { chart: { toolbar: { show: false } } } satisfies ChartOptions; const usage: ChartSeries = [    { name: 'Storage', data: [72] },    { name: 'Memory', data: [48] },    { name: 'CPU', data: [35] }];const radialOptions: ChartOptions = { ...quiet, legend: { show: true, position: 'bottom' } };const gaugeOptions: ChartOptions = { ...quiet, colors: ['var(--vt-chart-3)'], plotOptions: { radialBar: { min: 90, max: 100 } }, dataLabels: { formatter: '{value}%' } };</script> <template>    <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap: 1rem; width: 100%">        <Chart type="radialBar" :series="usage" :options="radialOptions" height="300" />        <Chart type="gauge" :series="[{ name: 'Uptime', data: [99.4] }]" :options="gaugeOptions" height="300" />    </div></template>

Calendar

Um ano de dias como uma grade de semanas. Uma linha de 365 pontos mostra a tendência; isto mostra o dia.

Gráfico de Calendário, 1 séries: Commits. Commits, 290 pontos de 1 commits a 14 commits. de 2 de jan. de 2026 a 30 de dez. de 2026
<script setup lang="ts">import { Chart, type ChartOptions, type ChartSeries } from '@vitral/vue'; const quiet = { chart: { toolbar: { show: false } } } satisfies ChartOptions;const seeded = (seed: number) => () => ((seed = (seed * 16807) % 2147483647) - 1) / 2147483646; // A year of daily activity, from a fixed seed so the page is the same twice.const daily = seeded(23);const commits: ChartSeries = [    {        name: 'Commits',        data: Array.from({ length: 365 }, (_, i) => {            const date = new Date(2026, 0, 1 + i);            const weekend = date.getDay() === 0 || date.getDay() === 6;            const value = Math.round(daily() * (weekend ? 4 : 14) * (daily() > 0.18 ? 1 : 0));            return { x: date.getTime(), y: value };        }).filter((d) => d.y > 0)    }];const calendarOptions: ChartOptions = { ...quiet, plotOptions: { calendar: { weekStart: 1 } }, colors: ['var(--vt-chart-3)'], tooltip: { y: { formatter: '{value} commits' } } };</script> <template>    <Chart type="calendar" :series="commits" :options="calendarOptions" height="200" style="width: 100%" /></template>

Gráficos sincronizados, trading

Um preço e o seu volume num só grupo: compartilham a mira, o tooltip e o zoom. Arraste sobre qualquer um para dar zoom nos dois; Shift e arrastar desloca. Inicie o feed e o último candle passa a ser o que ainda está sendo negociado — o fechamento se move a cada tick, a máxima e a mínima só se alargam —, e as três linhas da sessão, lidas desse candle, andam com ele. Como os dados são interpolados em vez de substituídos, um tick é um movimento, e não um salto.

Last $199.41 high · $196.13 low · prev close $196.13
Gráfico de Candlestick, 1 séries: VTRL. VTRL, 45 pontos de US$ 176,37 a US$ 200,29. de seg, 1 jun 2026 a qua, 15 jul 2026
Gráfico de Barras, 1 séries: Volume. Volume, 45 pontos de 2,1 mil a 8 mil. de seg, 1 jun 2026 a qua, 15 jul 2026
<script setup lang="ts">import { Button, Chart, type ChartOptions, type ChartSeries, StackPanel } from '@vitral/vue';import { computed, onBeforeUnmount, ref } from 'vue'; const seeded = (seed: number) => () => ((seed = (seed * 16807) % 2147483647) - 1) / 2147483646; // A candlestick over its volume: one group.const start = new Date(2026, 5, 1).getTime();const walk = seeded(11);let close = 180;const candles = Array.from({ length: 45 }, (_, i) => {    const open = close;    close = Math.round((open + (walk() - 0.48) * 6) * 100) / 100;    const high = Math.max(open, close) + Math.round(walk() * 300) / 100;    const low = Math.min(open, close) - Math.round(walk() * 300) / 100;    return { x: start + i * 86400000, y: [open, high, low, close], volume: Math.round(2000 + walk() * 6000) };});// The last candle is the one still being traded: a tick moves its close, and// its high and low only ever widen. That is what makes the session lines worth// drawing — they are read off the session, so they move as it does.const live = ref(false);const price = ref<ChartSeries>([{ name: 'VTRL', data: candles.map((c) => ({ x: c.x, y: c.y })) }]);const volume = ref<ChartSeries>([{ name: 'Volume', data: candles.map((c) => ({ x: c.x, y: c.volume })) }]);const session = ref({ prevClose: candles[candles.length - 2]!.y[3], high: candles[candles.length - 1]!.y[1], low: candles[candles.length - 2]!.y[3] }); const tickWalk = seeded(23);let ticker = 0; function tick() {    const bars = candles.map((c) => ({ ...c, y: [...c.y] as [number, number, number, number] }));    const last = bars[bars.length - 1]!;    const previous = bars[bars.length - 2]!;    const moved = Math.round((last.y[3] + (tickWalk() - 0.5) * 3) * 100) / 100;    last.y[3] = moved;    last.y[1] = Math.max(last.y[1], moved);    last.y[2] = Math.min(last.y[2], moved);    last.volume = Math.round(last.volume + tickWalk() * 400);    candles[candles.length - 1] = last;    price.value = [{ name: 'VTRL', data: bars.map((c) => ({ x: c.x, y: c.y })) }];    volume.value = [{ name: 'Volume', data: bars.map((c) => ({ x: c.x, y: c.volume })) }];    session.value = { prevClose: previous.y[3], high: last.y[1], low: last.y[2] };} function toggleLive() {    live.value = !live.value;    if (live.value) ticker = window.setInterval(tick, 1200);    else window.clearInterval(ticker);}onBeforeUnmount(() => window.clearInterval(ticker)); const money = (n: number) => `$${n.toFixed(2)}`;const priceOptions = computed<ChartOptions>(() => ({    chart: { group: 'market', id: 'price', height: 260, animations: { dynamicAnimation: { speed: 900 } } },    xaxis: { type: 'datetime', labels: { show: false }, axisTicks: { show: false } },    yaxis: { labels: { formatter: '{value|currency:USD}' }, forceNiceScale: true },    tooltip: { x: { format: 'EEE, d MMM yyyy' } },    legend: { show: false },    // Three lines read straight off the session, so they travel with it.    annotations: {        yaxis: [            // Dashed, so a line read off the session is never mistaken for one            // the data drew; the chips take each line's own colour.            { y: session.value.prevClose, borderColor: 'var(--vt-chart-8)', strokeDashArray: 4, label: { text: `Prev close ${money(session.value.prevClose)}`, position: 'left' } },            { y: session.value.high, borderColor: 'var(--vt-chart-3)', strokeDashArray: 4, label: { text: `High ${money(session.value.high)}` } },            { y: session.value.low, borderColor: 'var(--vt-chart-6)', strokeDashArray: 4, label: { text: `Low ${money(session.value.low)}`, position: 'left' } }        ]    }}));const volumeOptions: ChartOptions = {    chart: { group: 'market', id: 'volume', height: 140, toolbar: { show: false }, animations: { dynamicAnimation: { speed: 900 } } },    xaxis: { type: 'datetime' },    yaxis: { labels: { formatter: '{value|compact}' }, tickAmount: 2 },    colors: ['var(--vt-chart-8)'],    plotOptions: { bar: { columnWidth: '70%', borderRadius: 1 } },    tooltip: { x: { format: 'EEE, d MMM yyyy' } }};</script> <template>    <StackPanel spacing="0.25rem" style="width: 100%">        <div style="display: flex; align-items: center; gap: 0.75rem">            <Button size="small" severity="secondary" variant="outlined" :aria-pressed="live" @click="toggleLive">{{ live ? 'Stop the feed' : 'Start the feed' }}</Button>            <small style="color: var(--vt-text-muted-color)">Last {{ money(session.high) }} high · {{ money(session.low) }} low · prev close {{ money(session.prevClose) }}</small>        </div>        <Chart type="candlestick" :series="price" :options="priceOptions" />        <Chart type="bar" :series="volume" :options="volumeOptions" />    </StackPanel></template>

Brush

A faixa de baixo é um brush: arraste a janela dele, ou arraste uma nova, e o gráfico de cima acompanha, reescalando o eixo y ao que mostra.

Gráfico de Linhas, 1 séries: Temperature. Temperature, 240 pontos de 8 °C a 56 °C. de 3 jun 2026 12:00 a 6 jun 2026 00:00
Gráfico de Área, 1 séries: Temperature. Temperature, 240 pontos de 8 a 56. de 1 jun 2026 00:00 a 10 jun 2026 23:00
<script setup lang="ts">import { Chart, type ChartOptions, type ChartSeries, StackPanel } from '@vitral/vue'; const seeded = (seed: number) => () => ((seed = (seed * 16807) % 2147483647) - 1) / 2147483646; const start = new Date(2026, 5, 1).getTime();const readings = seeded(5);let level = 50;const series1 = Array.from({ length: 240 }, (_, i) => [start + i * 3600000, (level = Math.max(5, Math.round(level + (readings() - 0.5) * 8)))] as [number, number]);const sensor: ChartSeries = [{ name: 'Temperature', data: series1 }];const targetOptions: ChartOptions = { chart: { id: 'sensor', height: 240, toolbar: { tools: { download: true } } }, xaxis: { type: 'datetime' }, stroke: { width: 2, curve: 'straight' }, yaxis: { labels: { formatter: '{value} °C' } }, legend: { show: false } };const brushOptions: ChartOptions = {    chart: { height: 110, brush: { enabled: true, target: 'sensor' }, selection: { enabled: true, xaxis: { min: series1[60]![0], max: series1[120]![0] } } },    xaxis: { type: 'datetime' },    yaxis: { show: false },    colors: ['var(--vt-chart-4)'],    legend: { show: false },    tooltip: { enabled: false }};</script> <template>    <StackPanel spacing="1rem" style="width: 100%">        <Chart type="line" :series="sensor" :options="targetOptions" />        <Chart type="area" :series="sensor" :options="brushOptions" />    </StackPanel></template>

Pie and donut

O centro mostra o total, ou a fatia em foco; um clique (ou Enter) destaca uma fatia.

Gráfico de Rosca, 1 séries: Housing, Food, Transport, Leisure, Other. Housing: 48%, Food: 18%, Transport: 15%, Leisure: 12%, Other: 6%
Click or press Enter on a slice.
<script setup lang="ts">import { Button, Chart, type ChartKind, type ChartOptions, type ChartSeries, StackPanel } from '@vitral/vue';import { ref } from 'vue'; const budget: ChartSeries = [1200, 450, 380, 290, 160];const pieKind = ref<ChartKind>('donut');const pieOptions: ChartOptions = {    labels: ['Housing', 'Food', 'Transport', 'Leisure', 'Other'],    plotOptions: { pie: { donut: { labels: { total: { label: 'Monthly', formatter: '{value|currency:EUR}' }, value: { formatter: '{value|currency:EUR}' } } } } },    tooltip: { y: { formatter: '{value|currency:EUR}' } }};const picked = ref('Click or press Enter on a slice.');</script> <template>    <StackPanel spacing="1rem" style="width: 100%">        <div style="display: flex; gap: 0.5rem" role="group" aria-label="Kind">            <Button v-for="k in ['donut', 'pie'] as const" :key="k" size="small" severity="secondary" variant="outlined" :aria-pressed="pieKind === k" @click="pieKind = k">{{ k }}</Button>        </div>        <Chart :type="pieKind" :series="budget" :options="pieOptions" height="300" @data-point-selection="(e) => (picked = `${pieOptions.labels![e.seriesIndex]}: ${e.selected ? 'selected' : 'released'}`)" />        <small style="color: var(--vt-text-muted-color)">{{ picked }}</small>    </StackPanel></template>

Radar

Uma teia de linhas finas para comparar dois perfis, e faixas sombreadas para um só.

Gráfico de Radar, 2 séries: Ana, Rui. Ana, 6 pontos de 20 a 100. Rui, 6 pontos de 20 a 80. de Design a Support
Gráfico de Radar, 1 séries: Ana. Ana, 6 pontos de 20 a 100. de Design a Support
<script setup lang="ts">import { Chart, type ChartOptions, type ChartSeries } from '@vitral/vue'; const quiet = { chart: { toolbar: { show: false } } } satisfies ChartOptions; const skills: ChartSeries = [    { name: 'Ana', data: [80, 50, 30, 40, 100, 20] },    { name: 'Rui', data: [20, 30, 40, 80, 20, 80] }];const radarOptions = (grid: 'web' | 'polygon'): ChartOptions => ({    ...quiet,    xaxis: { categories: ['Design', 'Code', 'Ops', 'Data', 'Writing', 'Support'] },    plotOptions: { radar: { grid } },    fill: { opacity: grid === 'polygon' ? 0.35 : 0.15 },    legend: { show: grid === 'web' }});</script> <template>    <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); gap: 1rem; width: 100%">        <Chart type="radar" :series="skills" :options="radarOptions('web')" height="320" />        <Chart type="radar" :series="[skills[0]!]" :options="radarOptions('polygon')" height="320" />    </div></template>

Sparklines

`chart.sparkline.enabled`: só as marcas, para um bloco de estatística.

Revenue$48.2K
Gráfico de Área, 1 séries: Revenue. Revenue, 10 pontos de 12 a 27. de 1 a 10
Churn2.4%
Gráfico de Linhas, 1 séries: Churn. Churn, 10 pontos de 2,4 a 3,2. de 1 a 10
Deploys126
Gráfico de Barras, 1 séries: Deploys. Deploys, 10 pontos de 8 a 18. de 1 a 10
<script setup lang="ts">import { Chart, type ChartOptions } from '@vitral/vue'; const kpis = [    { label: 'Revenue', value: '$48.2K', type: 'area' as const, data: [12, 14, 13, 17, 19, 18, 22, 24, 23, 27], color: 'var(--vt-chart-3)' },    { label: 'Churn', value: '2.4%', type: 'line' as const, data: [3.1, 3.0, 2.9, 3.2, 2.8, 2.7, 2.6, 2.5, 2.5, 2.4], color: 'var(--vt-chart-6)' },    { label: 'Deploys', value: '126', type: 'bar' as const, data: [8, 12, 9, 14, 11, 16, 13, 15, 12, 18], color: 'var(--vt-chart-4)' }];const spark = (color: string): ChartOptions => ({ chart: { sparkline: { enabled: true }, height: 48 }, colors: [color], stroke: { width: 2 }, tooltip: { x: { show: false } } });</script> <template>    <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: 1rem; width: 100%">        <div v-for="k in kpis" :key="k.label" style="display: flex; flex-direction: column; gap: 0.25rem; padding: 0.75rem; border: 1px solid var(--vt-content-border-color); border-radius: var(--vt-content-border-radius)">            <small style="color: var(--vt-text-muted-color)">{{ k.label }}</small>            <strong style="font-size: 1.25rem">{{ k.value }}</strong>            <Chart :type="k.type" :series="[{ name: k.label, data: k.data }]" :options="spark(k.color)" />        </div>    </div></template>

Opções são dados

Edite o JSON: cada opção aqui é um dado simples, que é o que um editor visual vai escrever. Estreite a janela para menos de 520px e a entrada responsiva deita as barras.

Gráfico de Barras, 1 séries: Orders. Orders, 5 pontos de 9,8 mil a 21 mil. de Mon a Fri
<script setup lang="ts">import { Chart, Tag, type ChartOptions, type ChartSeries } from '@vitral/vue';import { computed, ref } from 'vue'; const editable = ref(    JSON.stringify(        {            chart: { type: 'bar', height: 260 },            xaxis: { categories: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'] },            yaxis: { labels: { formatter: '{value|compact}' } },            plotOptions: { bar: { borderRadius: 6, columnWidth: '50%', distributed: true } },            dataLabels: { enabled: true, formatter: '{value|compact}', style: { color: '#ffffff' } },            legend: { show: false },            responsive: [{ breakpoint: 520, options: { plotOptions: { bar: { horizontal: true } } } }]        },        null,        2    ));const parsed = computed<{ options?: ChartOptions; error?: string }>(() => {    try {        return { options: JSON.parse(editable.value) };    } catch (error) {        return { error: (error as Error).message };    }});const jsonSeries: ChartSeries = [{ name: 'Orders', data: [12400, 18300, 9800, 21000, 16700] }];</script> <template>    <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); gap: 1rem; width: 100%">        <textarea v-model="editable" aria-label="Chart options as JSON" spellcheck="false" rows="18" style="font-family: var(--vt-font-family-mono); font-size: 0.75rem; padding: 0.5rem; border-radius: var(--vt-content-border-radius); border: 1px solid var(--vt-content-border-color); background: var(--vt-content-background); color: var(--vt-text-color)" />        <div>            <Chart v-if="parsed.options" :series="jsonSeries" :options="parsed.options" />            <Tag v-else severity="danger" :value="parsed.error" />        </div>    </div></template>

Tooltip personalizado e sem dados

O slot do tooltip substitui a leitura; um gráfico sem nada para desenhar diz isso.

Gráfico de Linhas, 1 séries: Visitors. Visitors, 12 pontos de 310 a 980. de Jan a Dec
Dados de Gráfico
CategoriaVisitors
Jan310
Feb402
Mar385
Apr520
May610
Jun580
Jul720
Aug690
Sep810
Oct760
Nov905
Dec980
Sem dados para exibir
Sem dados para exibir
<script setup lang="ts">import { Chart, type ChartOptions, type ChartSeries } from '@vitral/vue'; const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];const quiet = { chart: { toolbar: { show: false } } } satisfies ChartOptions; const visitors: ChartSeries = [{ name: 'Visitors', data: [310, 402, 385, 520, 610, 580, 720, 690, 810, 760, 905, 980] }];</script> <template>    <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); gap: 1rem; width: 100%">        <Chart type="line" :series="visitors" :options="{ xaxis: { categories: months }, ...quiet, chart: { ...quiet.chart, accessibility: { dataTable: true } } }" height="240">            <template #tooltip="{ title, rows }">                <strong>{{ title }}</strong>                <span>{{ rows[0]?.value }} visitors</span>            </template>        </Chart>        <Chart type="bar" :series="[]" height="240" />    </div></template>

Números novos chegam se movendo

Aperte o botão. As barras viajam até as novas alturas em vez de simplesmente aparecerem nelas, então dá para ver para que lado cada uma foi — os valores são interpolados e a imagem é redesenhada a cada quadro, e é por isso que linhas, áreas e fatias também se movem. `chart.animations.dynamicAnimation` define o ritmo ou desliga a animação, e uma mudança que adiciona ou remove uma série simplesmente desenha, já que não há por onde se mover.

Gráfico de Barras, 2 séries: Europe, Americas. Europe, 4 pontos de 41 a 67. Americas, 4 pontos de 8 a 23. de Q1 a Q4
<script setup lang="ts">import { Button, Chart, type ChartOptions, type ChartSeries, StackPanel } from '@vitral/vue';import { ref } from 'vue'; const quiet = { chart: { toolbar: { show: false } } } satisfies ChartOptions; const quarters = ['Q1', 'Q2', 'Q3', 'Q4'];let reading = 0;const quarterly = [    [        { name: 'Europe', data: [44, 55, 41, 67] },        { name: 'Americas', data: [13, 23, 20, 8] }    ],    [        { name: 'Europe', data: [61, 38, 72, 49] },        { name: 'Americas', data: [28, 45, 12, 33] }    ],    [        { name: 'Europe', data: [30, 70, 55, 35] },        { name: 'Americas', data: [40, 18, 38, 22] }    ]] satisfies ChartSeries[];const moving = ref<ChartSeries>(quarterly[0]!);const nextReading = () => {    reading = (reading + 1) % quarterly.length;    moving.value = quarterly[reading]!;};const movingOptions: ChartOptions = {    ...quiet,    xaxis: { categories: quarters },    plotOptions: { bar: { borderRadius: 4, columnWidth: '55%' } },    legend: { position: 'top', horizontalAlign: 'left', value: { show: true } }};</script> <template>    <StackPanel spacing="1rem" style="width: 100%">        <div><Button size="small" severity="secondary" variant="outlined" @click="nextReading">New numbers</Button></div>        <Chart type="bar" :series="moving" :options="movingOptions" height="260" style="width: 100%" />    </StackPanel></template>

Como um gráfico se move

`chart.animations` define a forma do movimento e se as séries chegam juntas ou uma atrás da outra. A mesma curva de easing conduz o primeiro desenho e todas as mudanças depois dele, então um gráfico se move de um jeito só. Nada disso roda para quem tem o sistema pedindo movimento reduzido.

Gráfico de Barras, 2 séries: Europe, Americas. Europe, 4 pontos de 41 a 67. Americas, 4 pontos de 8 a 23. de Q1 a Q4
<script setup lang="ts">import { Button, Chart, type ChartOptions, type ChartSeries, StackPanel } from '@vitral/vue';import { computed, ref } from 'vue'; const quiet = { chart: { toolbar: { show: false } } } satisfies ChartOptions; const quarters = ['Q1', 'Q2', 'Q3', 'Q4'];const sales: ChartSeries = [    { name: 'Europe', data: [44, 55, 41, 67] },    { name: 'Americas', data: [13, 23, 20, 8] }]; const easings = ['linear', 'easein', 'easeout', 'easeinout'] as const;const easing = ref<(typeof easings)[number]>('easeout');const gradually = ref(true);const replay = ref(0);const animationOptions = computed<ChartOptions>(() => ({    ...quiet,    xaxis: { categories: quarters },    plotOptions: { bar: { borderRadius: 4, columnWidth: '55%' } },    legend: { position: 'top', horizontalAlign: 'left' },    chart: { ...quiet.chart, animations: { enabled: true, speed: 900, easing: easing.value, animateGradually: { enabled: gradually.value, delay: 220 } } }}));</script> <template>    <StackPanel spacing="1rem" style="width: 100%">        <div style="display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem">            <div style="display: flex; gap: 0.5rem" role="group" aria-label="Easing">                <Button v-for="e in easings" :key="e" size="small" severity="secondary" variant="outlined" :aria-pressed="easing === e" @click="((easing = e), replay++)">{{ e }}</Button>            </div>            <Button size="small" severity="secondary" variant="outlined" :aria-pressed="gradually" @click="((gradually = !gradually), replay++)">one at a time</Button>            <Button size="small" severity="secondary" variant="outlined" @click="replay++">replay</Button>        </div>        <Chart :key="replay" type="bar" :series="sales" :options="animationOptions" height="240" style="width: 100%" />    </StackPanel></template>

A legenda como leitura

`legend.value` põe um número ao lado do nome de cada série — o total, onde terminou, o maior ou o menor valor —, então a pergunta que costuma vir ao lado de uma legenda é respondida nela. Esta mostra a última leitura de cada linha.

Gráfico de Linhas, 2 séries: Visitors, Sign-ups. Visitors, 12 pontos de 310 a 980. Sign-ups, 12 pontos de 42 a 140. de Jan a Dec
<script setup lang="ts">import { Chart, type ChartOptions, type ChartSeries } from '@vitral/vue'; const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];const quiet = { chart: { toolbar: { show: false } } } satisfies ChartOptions; const traffic: ChartSeries = [    { name: 'Visitors', data: [310, 402, 385, 520, 610, 580, 720, 690, 810, 760, 905, 980] },    { name: 'Sign-ups', data: [42, 51, 48, 66, 80, 71, 95, 88, 110, 102, 126, 140] }];const legendValueOptions: ChartOptions = {    ...quiet,    xaxis: { categories: months },    stroke: { curve: 'smooth', width: 2 },    legend: { position: 'top', horizontalAlign: 'left', value: { show: true, source: 'last', formatter: '{value}' } }};</script> <template>    <Chart type="line" :series="traffic" :options="legendValueOptions" height="260" style="width: 100%" /></template>

Faixas atrás das marcas

Anotações com `fillColor` sombreiam um intervalo em vez de desenhar uma linha: duas faixas em y dizendo o que conta como calmo e como movimentado, e uma faixa em x sobre os meses em que uma campanha rodou. `position: 'back'` as coloca por baixo dos dados, onde fica um fundo.

Gráfico de Linhas, 1 séries: Visitors. Visitors, 12 pontos de 310 a 980. de Jan a Dec
<script setup lang="ts">import { Chart, type ChartOptions, type ChartSeries } from '@vitral/vue'; const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];const quiet = { chart: { toolbar: { show: false } } } satisfies ChartOptions; const visitors: ChartSeries = [{ name: 'Visitors', data: [310, 402, 385, 520, 610, 580, 720, 690, 810, 760, 905, 980] }];const bandedOptions: ChartOptions = {    ...quiet,    xaxis: { categories: months },    stroke: { curve: 'smooth', width: 3 },    legend: { show: false },    annotations: {        position: 'back',        yaxis: [            { y: 200, y2: 400, fillColor: 'var(--vt-chart-3)', opacity: 0.1, label: { text: 'Quiet', position: 'left' } },            { y: 700, y2: 1000, fillColor: 'var(--vt-chart-6)', opacity: 0.1, label: { text: 'Busy', position: 'left' } }        ],        xaxis: [{ x: 'Jul', x2: 'Sep', fillColor: 'var(--vt-chart-5)', opacity: 0.12, label: { text: 'Campaign' } }]    }};</script> <template>    <Chart type="line" :series="visitors" :options="bandedOptions" height="260" style="width: 100%" /></template>

A coluna inteira, ou só o que está sob o ponteiro

À esquerda, o tooltip é compartilhado: lista todas as séries na categoria e destaca a que está sendo apontada, para que o painel responda tanto “como estas se comparam” quanto “onde estou”. À direita, `tooltip.intersect` pede uma única leitura, que aparece quando o ponteiro está de fato sobre uma barra.

Gráfico de Barras, 2 séries: Europe, Americas. Europe, 4 pontos de 41 a 67. Americas, 4 pontos de 8 a 23. de Q1 a Q4
Gráfico de Barras, 2 séries: Europe, Americas. Europe, 4 pontos de 41 a 67. Americas, 4 pontos de 8 a 23. de Q1 a Q4
<script setup lang="ts">import { Chart, type ChartOptions, type ChartSeries } from '@vitral/vue'; const quiet = { chart: { toolbar: { show: false } } } satisfies ChartOptions; const quarters = ['Q1', 'Q2', 'Q3', 'Q4'];const sales: ChartSeries = [    { name: 'Europe', data: [44, 55, 41, 67] },    { name: 'Americas', data: [13, 23, 20, 8] }];const sharedTooltip: ChartOptions = { ...quiet, xaxis: { categories: quarters }, legend: { position: 'top', horizontalAlign: 'left' } };const singleTooltip: ChartOptions = { ...sharedTooltip, tooltip: { shared: false, intersect: true } };</script> <template>    <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); gap: 1rem; width: 100%">        <Chart type="bar" :series="sales" :options="sharedTooltip" height="240" />        <Chart type="bar" :series="sales" :options="singleTooltip" height="240" />    </div></template>

Usando o gráfico sem framework

O gráfico é o @vitral/chart, que não tem framework nenhum dentro; <Chart> é uma camada fina em volta dele. createChart(element, { type, series, options }) desenha em qualquer elemento e devolve um handle: update, on, zoomX, exportSvg, destroy. Este aqui é montado numa <div> simples pelo módulo abaixo.

Bar chart, 2 series: Online, Stores. Online, 4 points from 42k to 73k. Stores, 4 points from 31k to 38k. from Q1 to Q4
Click a bar, or Tab to the plot and press Enter.
<script setup lang="ts">// No framework here: `@vitral/chart` draws into any element, and the same// options object the <Chart> component takes configures it.import { createChart, type ChartHandle } from '@vitral/chart';import { Button, StackPanel } from '@vitral/vue';import { onBeforeUnmount, onMounted, ref } from 'vue'; const host = ref<HTMLElement | null>(null);const kinds = ['bar', 'line', 'area'] as const;const kind = ref<(typeof kinds)[number]>('bar');const pick = ref('Click a bar, or Tab to the plot and press Enter.');const quarters = ['Q1', 'Q2', 'Q3', 'Q4'];let chart: ChartHandle | undefined; onMounted(() => {    chart = createChart(host.value!, {        type: 'bar',        series: [            { name: 'Online', data: [42, 58, 51, 73] },            { name: 'Stores', data: [35, 31, 38, 36] }        ],        options: {            chart: { height: 260, toolbar: { show: false } },            xaxis: { categories: quarters },            yaxis: { labels: { formatter: '{value}k' } },            plotOptions: { bar: { borderRadius: 4, columnWidth: '55%' } },            legend: { position: 'top', horizontalAlign: 'right' }        }    });    chart.on('dataPointSelection', ({ seriesIndex, dataPointIndex, value, selected }) => {        const series = seriesIndex === 0 ? 'Online' : 'Stores';        pick.value = selected ? `${series}, ${quarters[dataPointIndex]}: ${value}k` : 'Nothing selected';    });});// And when the element goes away: chart.destroy();onBeforeUnmount(() => chart?.destroy()); // Later: new inputs are compared by value, and only what changed is redrawn.function pickKind(next: (typeof kinds)[number]) {    kind.value = next;    chart?.update({ type: next });}</script> <template>    <StackPanel spacing="1rem" style="width: 100%">        <div style="display: flex; gap: 0.5rem" role="group" aria-label="Kind">            <Button v-for="k in kinds" :key="k" size="small" severity="secondary" variant="outlined" :aria-pressed="kind === k" @click="pickKind(k)">{{ k }}</Button>        </div>        <div ref="host" />        <small style="color: var(--vt-text-muted-color)">{{ pick }}</small>    </StackPanel></template>

API

Lido de packages/vue/src/components/Chart/types.ts, então diz o que o componente aceita de fato.

Props

NomeTipoDescrição
typeChartKindO tipo de gráfico; sobrepõe `options.chart.type`. O padrão é `'line'`.
seriesSeriesOs dados, em qualquer formato que o ApexCharts aceite: `[{ name, data: [...] }]`, ou números simples para uma pizza.
optionsOptionsOpções no formato do ApexCharts. Tudo o que é visual é dado simples, então o objeto sobrevive ao JSON.
heightnumber | stringSobrepõe `options.chart.height`: pixels, ou um comprimento CSS.
widthnumber | stringSobrepõe `options.chart.width`.

Mais pt, dt e unstyled de BaseProps; veja pass-through e modo sem estilo.

Emits

EventoPayloadDescrição
dataPointSelectionevent: ChartPointEventUm ponto de dados foi escolhido por clique, Enter ou Espaço.
dataPointMouseEnterevent: ChartPointEvent—
dataPointMouseLeaveevent: ChartPointEvent—
legendClickevent: { seriesIndex: number; seriesName: string; hidden: boolean }Uma entrada da legenda foi pressionada; `hidden` é o novo estado da série.
zoomedevent: { min: number; max: number } | nullA janela do eixo x mudou; null quando o zoom está todo afastado.
selectionevent: { min: number; max: number }Um intervalo foi escolhido no modo de seleção (ou num brush).
clickevent: { seriesIndex: number; dataPointIndex: number; column: number; originalEvent: MouseEvent }Qualquer clique na área do gráfico.

Slots

NomeProps do slotDescrição
default—As partes, escritas como filhos: `<Chart.Tooltip>`, `<Chart.Legend>`…
tooltip(props: ChartTooltipSlotProps)Substitui o conteúdo do tooltip.
noData—Mostrado no lugar do gráfico quando não há dados.
legend(props: { name: string; seriesIndex: number; hidden: boolean; color: string })Substitui o texto de uma entrada da legenda.
center(props: { name: string; value: string; total: string })Substitui o texto central de uma rosca.