Графики и статистика chartsvgzero-dependencytablecomparisondeltasparkline
Metric Delta Table
Сравнительная таблица со встроенными полосами, текущими и прошлыми значениями, дельтами и спарклайнами.
Живое превью
Signups this month vs last month
| Metric | This month | Last month | Change | Trend |
|---|---|---|---|---|
| Organic search | 4,820 | 4,310 | up 11.8% | |
| Direct | 3,120 | 3,290 | down 5.2% | |
| Newsletter | 1,840 | 1,220 | up 50.8% | |
| Paid social | 1,310 | 1,480 | down 11.5% | |
| Partners | 960 | 710 | up 35.2% |
Использование
Скопируйте и вставьте этот код на вашу страницу Astro:
astro
---
import { MetricDeltaTable } from 'astro-component-kit/charts';
---
<MetricDeltaTable
rows={[
{ label: 'Organic', current: 4820, previous: 4310, trend: [30, 34, 38, 44, 48] },
{ label: 'Paid', current: 1310, previous: 1480 },
]}
/> ---
import { MetricDeltaTable } from 'astro-component-kit/charts';
---
<MetricDeltaTable
rows={[
{ label: 'Organic', current: 4820, previous: 4310, trend: [30, 34, 38, 44, 48] },
{ label: 'Paid', current: 1310, previous: 1480 },
]}
/> Ручная установка
Если вы не используете пакет npm, создайте файл и вставьте следующий код: src/components/lib/MetricDeltaTable.astro:
astro
---
/**
* MetricDeltaTable — Comparison table with inline bars, current vs previous
* values and signed deltas with direction icons. A "table + chart" hybrid.
*
* @param {Array<{label: string, current: number, previous: number, invert?: boolean}>} rows - Metrics.
* @param {string} currentLabel - Header for current values.
* @param {string} previousLabel - Header for previous values.
* @param {string} prefix - Value prefix.
* @param {string} suffix - Value suffix.
* @param {string} color - Inline bar color.
* @param {string} variant - Card style (glass|solid|ghost).
* @param {string} size - Density (sm|md|lg).
*/
import ChartFrame from './ChartFrame.astro';
import SparklineChart from './SparklineChart.astro';
import { formatValue } from './chart-utils';
interface Row { label: string; current: number; previous: number; invert?: boolean; trend?: number[] }
interface Props {
title?: string;
subtitle?: string;
rows?: Row[];
currentLabel?: string;
previousLabel?: string;
prefix?: string;
suffix?: string;
color?: string;
variant?: 'glass' | 'solid' | 'ghost';
size?: 'sm' | 'md' | 'lg';
className?: string;
}
const {
title = 'Channel performance',
subtitle = 'Signups this month vs last month',
rows = [
{ label: 'Organic search', current: 4820, previous: 4310, trend: [30, 34, 33, 38, 41, 44, 48] },
{ label: 'Direct', current: 3120, previous: 3290, trend: [36, 35, 34, 33, 33, 32, 31] },
{ label: 'Newsletter', current: 1840, previous: 1220, trend: [10, 12, 13, 14, 16, 17, 18] },
{ label: 'Paid social', current: 1310, previous: 1480, trend: [16, 15, 15, 14, 14, 13, 13] },
{ label: 'Partners', current: 960, previous: 710, trend: [6, 7, 7, 8, 8, 9, 10] },
],
currentLabel = 'This month',
previousLabel = 'Last month',
prefix = '',
suffix = '',
color = 'var(--chart-1, #3987e5)',
variant = 'glass',
size = 'md',
className = '',
} = Astro.props;
const max = Math.max(...rows.map((r) => r.current)) || 1;
const fmt = (v: number) => formatValue(v, prefix, suffix, false);
---
<ChartFrame title={title} subtitle={subtitle} variant={variant} size={size} className={className}>
<div class="md">
<table class="md__table">
<thead>
<tr>
<th scope="col">Metric</th>
<th scope="col" class="md__num">{currentLabel}</th>
<th scope="col" class="md__num md__hide-sm">{previousLabel}</th>
<th scope="col" class="md__num">Change</th>
<th scope="col" class="md__hide-sm"><span class="sr-only">Trend</span></th>
</tr>
</thead>
<tbody>
{rows.map((r) => {
const d = r.previous ? ((r.current - r.previous) / r.previous) * 100 : 0;
const up = d >= 0;
const good = r.invert ? !up : up;
return (
<tr>
<th scope="row">
<span class="md__label">{r.label}</span>
<span class="md__bar" aria-hidden="true"><span style={`width:${(r.current / max) * 100}%; background:${color}`}></span></span>
</th>
<td class="md__num md__strong">{fmt(r.current)}</td>
<td class="md__num md__hide-sm">{fmt(r.previous)}</td>
<td class="md__num">
<span class={`md__delta ${good ? 'md__delta--good' : 'md__delta--bad'}`}>
<svg width="10" height="10" viewBox="0 0 10 10" aria-hidden="true"><path d={up ? 'M5 1 L9 7 H1 Z' : 'M5 9 L9 3 H1 Z'} fill="currentColor" /></svg>
<span class="sr-only">{up ? 'up' : 'down'}</span>
{Math.abs(d).toFixed(1)}%
</span>
</td>
<td class="md__hide-sm">{r.trend && <SparklineChart data={r.trend} size="sm" variant="line" label={`${r.label} trend`} color={good ? 'var(--chart-pos, #34c38f)' : 'var(--chart-neg, #f06a6a)'} />}</td>
</tr>
);
})}
</tbody>
</table>
</div>
</ChartFrame>
<style>
.md { overflow-x: auto; }
.md__table { width: 100%; border-collapse: collapse; font-size: 0.8125rem; font-variant-numeric: tabular-nums; }
.md__table th, .md__table td {
padding: 0.7rem 0.6rem;
border-bottom: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.06);
text-align: left;
vertical-align: middle;
}
.md__table thead th { font-size: 0.6875rem; font-weight: 600; color: var(--c-text-3, #8a8a8a); text-transform: uppercase; letter-spacing: 0.04em; }
.md__table tbody tr { transition: background 0.2s ease; }
.md__table tbody tr:hover { background: rgb(var(--ink-rgb, 255 255 255) / 0.03); }
.md__table tbody th { font-weight: 500; min-width: 9rem; }
.md__label { display: block; color: var(--c-text-1, #fff); margin-bottom: 0.35rem; }
.md__bar { display: block; height: 4px; border-radius: 2px; background: rgb(var(--ink-rgb, 255 255 255) / 0.05); overflow: hidden; }
.md__bar span { display: block; height: 100%; border-radius: 2px; }
.md__num { text-align: right !important; white-space: nowrap; color: var(--c-text-2, #a3a3a3); }
.md__strong { color: var(--c-text-1, #fff); font-weight: 650; }
.md__delta { display: inline-flex; align-items: center; gap: 0.3rem; color: var(--c-text-1, #fff); font-weight: 600; }
.md__delta--good svg { color: var(--chart-pos, #34c38f); }
.md__delta--bad svg { color: var(--chart-neg, #f06a6a); }
.sr-only {
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
@media (max-width: 560px) { .md__hide-sm { display: none; } }
</style> ---
/**
* MetricDeltaTable — Comparison table with inline bars, current vs previous
* values and signed deltas with direction icons. A "table + chart" hybrid.
*
* @param {Array<{label: string, current: number, previous: number, invert?: boolean}>} rows - Metrics.
* @param {string} currentLabel - Header for current values.
* @param {string} previousLabel - Header for previous values.
* @param {string} prefix - Value prefix.
* @param {string} suffix - Value suffix.
* @param {string} color - Inline bar color.
* @param {string} variant - Card style (glass|solid|ghost).
* @param {string} size - Density (sm|md|lg).
*/
import ChartFrame from './ChartFrame.astro';
import SparklineChart from './SparklineChart.astro';
import { formatValue } from './chart-utils';
interface Row { label: string; current: number; previous: number; invert?: boolean; trend?: number[] }
interface Props {
title?: string;
subtitle?: string;
rows?: Row[];
currentLabel?: string;
previousLabel?: string;
prefix?: string;
suffix?: string;
color?: string;
variant?: 'glass' | 'solid' | 'ghost';
size?: 'sm' | 'md' | 'lg';
className?: string;
}
const {
title = 'Channel performance',
subtitle = 'Signups this month vs last month',
rows = [
{ label: 'Organic search', current: 4820, previous: 4310, trend: [30, 34, 33, 38, 41, 44, 48] },
{ label: 'Direct', current: 3120, previous: 3290, trend: [36, 35, 34, 33, 33, 32, 31] },
{ label: 'Newsletter', current: 1840, previous: 1220, trend: [10, 12, 13, 14, 16, 17, 18] },
{ label: 'Paid social', current: 1310, previous: 1480, trend: [16, 15, 15, 14, 14, 13, 13] },
{ label: 'Partners', current: 960, previous: 710, trend: [6, 7, 7, 8, 8, 9, 10] },
],
currentLabel = 'This month',
previousLabel = 'Last month',
prefix = '',
suffix = '',
color = 'var(--chart-1, #3987e5)',
variant = 'glass',
size = 'md',
className = '',
} = Astro.props;
const max = Math.max(...rows.map((r) => r.current)) || 1;
const fmt = (v: number) => formatValue(v, prefix, suffix, false);
---
<ChartFrame title={title} subtitle={subtitle} variant={variant} size={size} className={className}>
<div class="md">
<table class="md__table">
<thead>
<tr>
<th scope="col">Metric</th>
<th scope="col" class="md__num">{currentLabel}</th>
<th scope="col" class="md__num md__hide-sm">{previousLabel}</th>
<th scope="col" class="md__num">Change</th>
<th scope="col" class="md__hide-sm"><span class="sr-only">Trend</span></th>
</tr>
</thead>
<tbody>
{rows.map((r) => {
const d = r.previous ? ((r.current - r.previous) / r.previous) * 100 : 0;
const up = d >= 0;
const good = r.invert ? !up : up;
return (
<tr>
<th scope="row">
<span class="md__label">{r.label}</span>
<span class="md__bar" aria-hidden="true"><span style={`width:${(r.current / max) * 100}%; background:${color}`}></span></span>
</th>
<td class="md__num md__strong">{fmt(r.current)}</td>
<td class="md__num md__hide-sm">{fmt(r.previous)}</td>
<td class="md__num">
<span class={`md__delta ${good ? 'md__delta--good' : 'md__delta--bad'}`}>
<svg width="10" height="10" viewBox="0 0 10 10" aria-hidden="true"><path d={up ? 'M5 1 L9 7 H1 Z' : 'M5 9 L9 3 H1 Z'} fill="currentColor" /></svg>
<span class="sr-only">{up ? 'up' : 'down'}</span>
{Math.abs(d).toFixed(1)}%
</span>
</td>
<td class="md__hide-sm">{r.trend && <SparklineChart data={r.trend} size="sm" variant="line" label={`${r.label} trend`} color={good ? 'var(--chart-pos, #34c38f)' : 'var(--chart-neg, #f06a6a)'} />}</td>
</tr>
);
})}
</tbody>
</table>
</div>
</ChartFrame>
<style>
.md { overflow-x: auto; }
.md__table { width: 100%; border-collapse: collapse; font-size: 0.8125rem; font-variant-numeric: tabular-nums; }
.md__table th, .md__table td {
padding: 0.7rem 0.6rem;
border-bottom: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.06);
text-align: left;
vertical-align: middle;
}
.md__table thead th { font-size: 0.6875rem; font-weight: 600; color: var(--c-text-3, #8a8a8a); text-transform: uppercase; letter-spacing: 0.04em; }
.md__table tbody tr { transition: background 0.2s ease; }
.md__table tbody tr:hover { background: rgb(var(--ink-rgb, 255 255 255) / 0.03); }
.md__table tbody th { font-weight: 500; min-width: 9rem; }
.md__label { display: block; color: var(--c-text-1, #fff); margin-bottom: 0.35rem; }
.md__bar { display: block; height: 4px; border-radius: 2px; background: rgb(var(--ink-rgb, 255 255 255) / 0.05); overflow: hidden; }
.md__bar span { display: block; height: 100%; border-radius: 2px; }
.md__num { text-align: right !important; white-space: nowrap; color: var(--c-text-2, #a3a3a3); }
.md__strong { color: var(--c-text-1, #fff); font-weight: 650; }
.md__delta { display: inline-flex; align-items: center; gap: 0.3rem; color: var(--c-text-1, #fff); font-weight: 600; }
.md__delta--good svg { color: var(--chart-pos, #34c38f); }
.md__delta--bad svg { color: var(--chart-neg, #f06a6a); }
.sr-only {
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
@media (max-width: 560px) { .md__hide-sm { display: none; } }
</style>
Краткая информация
- Категория
- Графики и статистика
- Имя файла
MetricDeltaTable.astro- Зависимости
- apexcharts (peer)
- Теги
- chartsvgzero-dependencytablecomparisondeltasparkline