Графики и статистикаchartsvgzero-dependencyslopecomparisonbefore-after
Slope Chart
Сравнение «до/после» между двумя моментами времени с растущими и падающими линиями в цветах статуса.
Живое превью
Q1 vs Q2
View data
| Item | Q1 | Q2 | Change |
|---|---|---|---|
| Enterprise | 7.8% | 9.6% | +1.8 |
| Team | 6.1% | 6.9% | +0.8 |
| Pro | 5.4% | 4.6% | -0.8 |
| Starter | 3.2% | 4.1% | +0.9 |
| Free trial | 2.4% | 1.9% | -0.5 |
Использование
Скопируйте и вставьте этот код в ваш React-компонент:
tsx
import { SlopeChart } from 'react-component-kit-ui/charts';
<SlopeChart fromLabel="2025" toLabel="2026" items={[
{ label: 'Enterprise', from: 7.8, to: 9.6 },
{ label: 'Pro', from: 5.4, to: 4.6 },
]} suffix="%" />Ручная установка
Если вы не используете пакет npm, создайте файл и вставьте следующий код: src/components/SlopeChart.tsx + SlopeChart.css:
SlopeChart.tsx
'use client';
import ChartFrame from './ChartFrame';
import { scaleLinear, formatValue, POSITIVE, NEGATIVE } from './chart-utils';
/**
* SlopeChart — Before/after comparison of several items between two points in
* time. Rising lines use the positive status color, falling lines the negative.
*
* @param {string} fromLabel - Left column label.
* @param {string} toLabel - Right column label.
* @param {Array<{label: string, from: number, to: number}>} items - Items to compare.
* @param {string} highlight - Optional item label to emphasise (others dim).
* @param {string} prefix - Value prefix.
* @param {string} suffix - Value suffix.
* @param {string} variant - Card style (glass|solid|ghost).
* @param {string} size - Density (sm|md|lg).
*/
export interface SlopeChartProps {
title?: string;
subtitle?: string;
fromLabel?: string;
toLabel?: string;
items?: Array<{ label: string; from: number; to: number }>;
highlight?: string;
prefix?: string;
suffix?: string;
height?: number;
variant?: 'glass' | 'solid' | 'ghost';
size?: 'sm' | 'md' | 'lg';
className?: string;
}
export function SlopeChart({
title = 'Conversion rate by plan',
subtitle = 'Q1 vs Q2',
fromLabel = 'Q1',
toLabel = 'Q2',
items = [
{ label: 'Enterprise', from: 7.8, to: 9.6 },
{ label: 'Team', from: 6.1, to: 6.9 },
{ label: 'Pro', from: 5.4, to: 4.6 },
{ label: 'Starter', from: 3.2, to: 4.1 },
{ label: 'Free trial', from: 2.4, to: 1.9 },
],
highlight,
prefix = '',
suffix = '%',
height = 300,
variant = 'glass',
size = 'md',
className = '',
}: SlopeChartProps) {
const W = 640;
const H = height;
const m = { t: 34, b: 20 };
const x0 = 190;
const x1 = W - 190;
const vals = items.flatMap((i) => [i.from, i.to]);
const y = scaleLinear(Math.min(...vals), Math.max(...vals), H - m.b, m.t);
const fmt = (v: number) => formatValue(v, prefix, suffix);
return (
<>
<ChartFrame title={title} subtitle={subtitle} variant={variant} size={size} className={className} table={{
headers: ['Item', fromLabel, toLabel, 'Change'],
rows: items.map((it) => [it.label, fmt(it.from), fmt(it.to), `${it.to >= it.from ? '+' : ''}${(it.to - it.from).toFixed(1)}`]),
}}>
<svg viewBox={`0 0 ${W} ${H}`} role="img" aria-label={`${title}: slope chart from ${fromLabel} to ${toLabel}`}>
<line className="cf-axis" x1={x0} x2={x0} y1={m.t - 8} y2={H - m.b + 8} />
<line className="cf-axis" x1={x1} x2={x1} y1={m.t - 8} y2={H - m.b + 8} />
<text className="cf-label cf-label--strong" x={x0} y={14} textAnchor="middle">{fromLabel}</text>
<text className="cf-label cf-label--strong" x={x1} y={14} textAnchor="middle">{toLabel}</text>
{items.map((it) => {
const up = it.to >= it.from;
const c = up ? POSITIVE : NEGATIVE;
const dim = highlight && highlight !== it.label;
return (
<g className="cf-mark cf-dim" tabIndex={0} role="img" opacity={dim ? 0.35 : 1} aria-label={`${it.label}: ${fmt(it.from)} to ${fmt(it.to)} (${up ? 'up' : 'down'})`} data-tip-title={it.label} data-tip={`${fromLabel}: ${fmt(it.from)}|${toLabel}: ${fmt(it.to)}|${c}::${up ? 'Up' : 'Down'} ${Math.abs(it.to - it.from).toFixed(1)}`}>
<line x1={x0} y1={y(it.from)} x2={x1} y2={y(it.to)} stroke={c} strokeWidth="2" />
<line x1={x0} y1={y(it.from)} x2={x1} y2={y(it.to)} stroke="transparent" strokeWidth="14" />
<circle cx={x0} cy={y(it.from)} r="4.5" fill={c} stroke="var(--chart-surface, #0d0d0f)" strokeWidth="2" />
<circle cx={x1} cy={y(it.to)} r="4.5" fill={c} stroke="var(--chart-surface, #0d0d0f)" strokeWidth="2" />
<text className="cf-label" x={x0 - 12} y={y(it.from) + 4} textAnchor="end">{it.label} <tspan className="cf-label--strong">{fmt(it.from)}</tspan></text>
<text className="cf-label" x={x1 + 12} y={y(it.to) + 4}><tspan className="cf-label--strong">{fmt(it.to)}</tspan> {it.label}</text>
</g>
);
})}
</svg>
</ChartFrame>
</>
);
}
export default SlopeChart;Краткая информация
- Категория
- Графики и статистика
- Имя файла
SlopeChart.tsx- Зависимости
- Нет — чистый React + CSS
- Теги
- chartsvgzero-dependencyslopecomparisonbefore-after