Реализация графиков и диаграмм: Chart.js, D3.js, Recharts
Визуализация данных требует выбора инструмента под задачу: Chart.js для стандартных графиков, Recharts для React-экосистемы, D3.js для нестандартных визуализаций.
Chart.js: линейный и столбчатый графики
import { Chart, registerables } from 'chart.js';
import 'chartjs-adapter-date-fns';
Chart.register(...registerables);
function RevenueChart({ data }: { data: { date: string; revenue: number }[] }) {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const ctx = canvasRef.current?.getContext('2d');
if (!ctx) return;
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: data.map(d => d.date),
datasets: [{
label: 'Выручка',
data: data.map(d => d.revenue),
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
fill: true,
tension: 0.4,
}],
},
options: {
responsive: true,
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: ctx => `${ctx.parsed.y.toLocaleString('ru-RU')} ₽`,
},
},
},
scales: {
x: { type: 'time', time: { unit: 'day' } },
y: {
ticks: { callback: val => `${Number(val).toLocaleString('ru-RU')} ₽` },
},
},
},
});
return () => chart.destroy();
}, [data]);
return <canvas ref={canvasRef} />;
}
Recharts: React-нативные графики
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from 'recharts';
// Area Chart
function TrafficChart({ data }: { data: { date: string; sessions: number; users: number }[] }) {
return (
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis />
<Tooltip formatter={(val) => val.toLocaleString('ru-RU')} />
<Area type="monotone" dataKey="sessions" stroke="#3b82f6" fill="#dbeafe" name="Сессии" />
<Area type="monotone" dataKey="users" stroke="#10b981" fill="#d1fae5" name="Пользователи" />
</AreaChart>
</ResponsiveContainer>
);
}
// Pie Chart с легендой
const COLORS = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6'];
function CategoryPieChart({ data }: { data: { name: string; value: number }[] }) {
return (
<ResponsiveContainer width="100%" height={300}>
<PieChart>
<Pie
data={data}
cx="50%"
cy="50%"
outerRadius={100}
dataKey="value"
label={({ name, percent }) => `${name}: ${(percent * 100).toFixed(0)}%`}
>
{data.map((_, index) => (
<Cell key={index} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<Tooltip />
</PieChart>
</ResponsiveContainer>
);
}
D3.js: кастомная heatmap
import * as d3 from 'd3';
function ActivityHeatmap({ data }: { data: { date: string; count: number }[] }) {
const svgRef = useRef<SVGSVGElement>(null);
useEffect(() => {
const svg = d3.select(svgRef.current);
svg.selectAll('*').remove();
const cellSize = 14;
const weeks = d3.timeWeek.range(
d3.timeYear.floor(new Date(data[0]?.date)),
new Date()
);
const colorScale = d3.scaleSequential()
.domain([0, d3.max(data, d => d.count) || 10])
.interpolator(d3.interpolateGreens);
const dataMap = new Map(data.map(d => [d.date, d.count]));
svg.selectAll('rect')
.data(weeks.flatMap(w => d3.timeDays(w, d3.timeWeek.offset(w, 1))))
.join('rect')
.attr('width', cellSize - 1)
.attr('height', cellSize - 1)
.attr('x', d => d3.timeWeek.count(d3.timeYear(d), d) * cellSize)
.attr('y', d => d.getDay() * cellSize)
.attr('rx', 2)
.attr('fill', d => {
const count = dataMap.get(d.toISOString().split('T')[0]) || 0;
return count === 0 ? '#f3f4f6' : colorScale(count);
})
.append('title')
.text(d => `${d.toLocaleDateString('ru-RU')}: ${dataMap.get(d.toISOString().split('T')[0]) || 0}`);
}, [data]);
return <svg ref={svgRef} width="100%" height={7 * 15} />;
}
Выбор инструмента
| Инструмент | Когда использовать |
|---|---|
| Chart.js | Стандартные типы (line, bar, pie, radar), без React |
| Recharts | React-приложения, декларативный подход |
| Victory | React Native + Web |
| D3.js | Нестандартные визуализации, интерактивные карты, treemap, sankey |
| ECharts | Большие датасеты, карты, сложные интерактивные дашборды |
| Plotly | Научные графики, 3D, статистика |
API для данных графиков
// Агрегированные данные для frontend
Route::get('/api/analytics/revenue', function (Request $request) {
return DB::table('orders')
->selectRaw("DATE_TRUNC('day', created_at) as date, SUM(total) as revenue, COUNT(*) as orders")
->where('status', 'completed')
->whereBetween('created_at', [$request->date('from'), $request->date('to')])
->groupByRaw("DATE_TRUNC('day', created_at)")
->orderBy('date')
->get();
});
Срок реализации
Recharts или Chart.js дашборд с 3–5 графиками и API: 2–3 дня. Кастомная D3.js визуализация (heatmap, force graph, treemap): 3–5 дней.







