/** * Komponent wizualizujący rozkład emocji w artykułach w formie wykresu kołowego. * * Odpowiada za: * - normalizację i agregację danych sentymentu, * - tłumaczenie etykiet w zależności od języka interfejsu, * - prezentację danych z wykorzystaniem biblioteki Recharts, * - obsługę tooltipów i etykiet procentowych. */ "use client"; import { useMemo } from "react"; import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, Legend } from "recharts"; import { useLanguage } from "../app/hooks/useLanguage"; import locales from "../lib/locales"; interface EmotionalPieChartProps { data: { name: string; value: number }[]; title?: string; } type Canonical = "POSITIVE" | "NEGATIVE" | "NEUTRAL"; const EMOTION_COLORS = { POSITIVE: "#22c55e", // green NEGATIVE: "#ef4444", // red NEUTRAL: "#3b82f6", // blue } as const; const EMOTION_LABELS = { pl: { POSITIVE: "Pozytywne", NEGATIVE: "Negatywne", NEUTRAL: "Neutralne", }, en: { POSITIVE: "Positive", NEGATIVE: "Negative", NEUTRAL: "Neutral", }, no: { POSITIVE: "Positivt", NEGATIVE: "Negativt", NEUTRAL: "Nøytralt", }, } as const; export default function EmotionalPieChart({ data, title }: EmotionalPieChartProps) { const { language } = useLanguage(); const t = locales[language]; const toCanonical = (raw: string): Canonical | null => { const u = (raw || "").trim().toUpperCase(); if (u === "POSITIVE" || u === "POZYTYWNE") return "POSITIVE"; if (u === "NEGATIVE" || u === "NEGATYWNE") return "NEGATIVE"; if (u === "NEUTRAL" || u === "NEUTRALNE") return "NEUTRAL"; return null; }; const translatedData = useMemo(() => { const initialData: Record = { POSITIVE: 0, NEGATIVE: 0, NEUTRAL: 0 }; for (const item of data) { const c = toCanonical(item.name); if (!c) continue; initialData[c] = (initialData[c] ?? 0) + (item.value ?? 0); } return (["POSITIVE", "NEUTRAL", "NEGATIVE"] as Canonical[]) .map(canonical => ({ canonical, name: EMOTION_LABELS[language][canonical], value: initialData[canonical], color: EMOTION_COLORS[canonical] })) .filter(item => item.value > 0); }, [data, language]); const totalValue = useMemo(() => translatedData.reduce((sum, item) => sum + item.value, 0), [translatedData] ); const RAD = Math.PI / 180; const renderLabel = ({ cx, cy, midAngle, outerRadius, name, value, payload }: any) => { const r = (outerRadius ?? 0) + 14; const x = cx + r * Math.cos(-midAngle * RAD); const y = cy + r * Math.sin(-midAngle * RAD); const anchor: "start" | "end" = x > cx ? "start" : "end"; const fill = EMOTION_COLORS[(payload?.canonical as Canonical) ?? "NEUTRAL"]; return ( {`${name}: ${value}`} ); }; const CustomTooltip = ({ active, payload }: any) => { if (active && payload && payload.length) { const data = payload[0].payload; const percentage = totalValue > 0 ? (data.value / totalValue) * 100 : 0; return (

{data.name}

{language === "pl" ? "Liczba artykułów: " : language === "no" ? "Antall artikler: " : "Number of articles: "} {data.value}

{language === "pl" ? "Procent: " : language === "no" ? "Prosent: " : "Percentage: "} {percentage.toFixed(1)}%

); } return null; }; if (translatedData.length === 0) { return (

{title || t.emotionsInArticles}

{language === "pl" ? "Brak danych do wyświetlenia" : language === "no" ? "Ingen data å vise" : "No data to display"}

); } return (

{title || t.emotionsInArticles}

{translatedData.map((item, idx) => ( ))} } /> ( {value} )} wrapperStyle={{ paddingTop: '10px' }} />

{language === "pl" ? "Wykres pokazuje rozkład emocji w analizowanych artykułach." : language === "no" ? "Diagrammet viser fordelingen av følelser i analyserte artikler." : "This chart shows the distribution of detected emotions in articles."}

); }