import { useState } from "react"; import { ChartTooltip, type TooltipState } from "./ChartTooltip"; import { categoricalColor } from "../../theme/palette"; export interface ShareDatum { label: string; count: number; } // Part-to-whole как один горизонтальный 100%-stacked bar вместо pie chart — // dataviz: "part-to-whole rides on the stacked bar chart; donut stays deprioritized". // Легенда обязательна (>=2 сегментов), т.к. на самих сегментах подписи не всегда влезают. // Токен-потолок категориальной палитры — 8 слотов (см. dataviz choosing-a-form: // "series 7-8: token ceiling; past it, fold the tail into Other"). Данные уже // отсортированы бэкендом по убыванию — берём топ-7 и сворачиваем остаток. const MAX_SLOTS = 7; const OTHER_COLOR_LIGHT = "#898781"; const OTHER_COLOR_DARK = "#a7b2c0"; function foldTail(data: ShareDatum[]): ShareDatum[] { if (data.length <= MAX_SLOTS + 1) return data; const head = data.slice(0, MAX_SLOTS); const tail = data.slice(MAX_SLOTS); const otherCount = tail.reduce((s, d) => s + d.count, 0); return [...head, { label: `Другое (${tail.length})`, count: otherCount }]; } export function PartToWholeBar({ data, dark }: { data: ShareDatum[]; dark: boolean }) { const [tooltip, setTooltip] = useState(null); if (data.length === 0) { return
Нет данных
; } const folded = foldTail(data); const wasFolded = folded.length < data.length; const isOther = (i: number) => wasFolded && i === folded.length - 1; const total = folded.reduce((s, d) => s + d.count, 0) || 1; return (
{folded.map((d, i) => { const pct = (d.count / total) * 100; const color = isOther(i) ? (dark ? OTHER_COLOR_DARK : OTHER_COLOR_LIGHT) : categoricalColor(i, dark); return (
{ const rect = e.currentTarget.getBoundingClientRect(); const parent = e.currentTarget.parentElement!.parentElement!.getBoundingClientRect(); setTooltip({ x: rect.left - parent.left + rect.width / 2, y: rect.top - parent.top, label: d.label, value: `${d.count} (${pct.toFixed(1)}%)`, swatch: color, }); }} onMouseLeave={() => setTooltip(null)} onFocus={() => setTooltip({ x: 0, y: 0, label: d.label, value: `${d.count} (${pct.toFixed(1)}%)`, swatch: color }) } onBlur={() => setTooltip(null)} /> ); })}
{folded.map((d, i) => { const pct = (d.count / total) * 100; const color = isOther(i) ? (dark ? OTHER_COLOR_DARK : OTHER_COLOR_LIGHT) : categoricalColor(i, dark); return ( {d.label} — {d.count} ({pct.toFixed(1)}%) ); })}
); }