longevity: il referto del cliente, e il radar che dice cosa non sa
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
// Il radar a sette assi del referto: isola React perché recharts ha bisogno del DOM.
|
||||
// Due serie sovrapposte sugli stessi sette assi (recharts vuole un solo `data` condiviso,
|
||||
// non può disegnare un poligono che copre solo alcuni assi):
|
||||
// - "noto": l'area piena, coi valori congelati che la sessione ha davvero. Dove il dato
|
||||
// manca vale 0 — non si inventa un centro pieno per una misura che non c'è, si lascia
|
||||
// che l'area si ritiri, onestamente, verso il centro;
|
||||
// - "potenziale": il perimetro tratteggiato. Dove il dato c'è coincide col bordo di
|
||||
// "noto" (stesso valore, la linea tratteggiata sparisce dietro quella piena); dove
|
||||
// manca si allarga fino al massimo (100) — è la parte che CHIUDE il perimetro, il gesto
|
||||
// su cui si regge l'interfaccia: non un errore, un invito a completare quell'asse.
|
||||
// Sotto il grafico, l'elenco leggibile di cosa completerebbe ogni asse ancora tratteggiato,
|
||||
// letto da `mancano` (calcolato in vista.ts, mai qui: questo componente non tocca il db).
|
||||
import {
|
||||
ResponsiveContainer, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, Radar,
|
||||
} from 'recharts';
|
||||
import type { AsseVista } from '../../lib/longevity/vista';
|
||||
|
||||
interface Props {
|
||||
assi: AsseVista[];
|
||||
}
|
||||
|
||||
// Stessi colori del sito (come in DataChart.tsx, che per lo stesso motivo — attributi SVG,
|
||||
// non proprietà CSS — non passa var(--...) a recharts): l'accento e il tratteggio di
|
||||
// longevity.css, non un colore nuovo.
|
||||
const ACCENTO = '#9c8b70';
|
||||
const TRATTEGGIO = '#d8d2c8';
|
||||
const LINEA = '#e4dfd7';
|
||||
const TESTO_CHIARO = '#8a8a8a';
|
||||
|
||||
export default function RadarAssi({ assi }: Props) {
|
||||
const dati = assi.map((a) => ({
|
||||
nome: a.nome,
|
||||
noto: a.valore ?? 0,
|
||||
potenziale: a.valore ?? 100,
|
||||
}));
|
||||
const incompleti = assi.filter((a) => a.mancano.length > 0);
|
||||
|
||||
return (
|
||||
<div className="lg-radar">
|
||||
<div className="lg-radar__grafico">
|
||||
<ResponsiveContainer width="100%" height={360}>
|
||||
<RadarChart data={dati} outerRadius="72%">
|
||||
<PolarGrid stroke={LINEA} />
|
||||
<PolarAngleAxis dataKey="nome" tick={{ fill: TESTO_CHIARO, fontSize: 11 }} />
|
||||
<PolarRadiusAxis domain={[0, 100]} tick={false} axisLine={false} />
|
||||
{/* il perimetro tratteggiato si disegna per primo: quello pieno gli va sopra */}
|
||||
<Radar
|
||||
name="Perimetro completo"
|
||||
dataKey="potenziale"
|
||||
stroke={TRATTEGGIO}
|
||||
strokeDasharray="4 3"
|
||||
strokeWidth={1.5}
|
||||
fill="none"
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
<Radar
|
||||
name="Quello che sappiamo"
|
||||
dataKey="noto"
|
||||
stroke={ACCENTO}
|
||||
fill={ACCENTO}
|
||||
fillOpacity={0.32}
|
||||
strokeWidth={2}
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
</RadarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
{incompleti.length > 0 && (
|
||||
<div className="lg-radar__mancano">
|
||||
<p className="lg-radar__intro">
|
||||
L'area piena è quello che sappiamo davvero. Il perimetro tratteggiato si completa così:
|
||||
</p>
|
||||
{incompleti.map((a) => (
|
||||
<div className="lg-radar__voce" key={a.nome}>
|
||||
<p className="lg-radar__voce-nome">{a.nome}</p>
|
||||
<ul className="lg-radar__lista">
|
||||
{a.mancano.map((m) => <li key={m}>{m}</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user