/* ============================================================ CHARTS — visualizaciones SVG (radar, barras, dona, heatmap…) Exporta a window: Glyph, Radar, DistBars, Donut, Heatmap, EvolBar, HistLines, ScoreBar, RingStat ============================================================ */ const IHX = window.IH; const ARCH = IHX.archetypes; const COL = Object.fromEntries(ARCH.map(a => [a.id, a.hex])); /* —— Glifo geométrico abstracto por arquetipo —— */ function Glyph({ id, size = 28, stroke = 2, color }) { const c = color || COL[id] || '#888'; const s = size, m = s / 2; const props = { fill: 'none', stroke: c, strokeWidth: stroke, strokeLinecap: 'round', strokeLinejoin: 'round' }; let body = null; switch (id) { case 'huerfano': // anillo abierto body = ; break; case 'vagabundo': // zigzag body = ; break; case 'martir': // diamante partido body = ; break; case 'guerrero': // triángulo ascendente body = ; break; case 'sabio': // concéntrico body = ; break; case 'mago': // octograma (dos cuadrados) body = ; break; default: body = ; } return {body}; } /* —— RADAR —— series: [{scores, color, fillOpacity, label, dash}] —— */ function Radar({ series = [], size = 360, max = 100, showColoredAxes = true, labels = true, levels = 4 }) { const cx = size / 2, cy = size / 2; const pad = labels ? 58 : 14; const R = size / 2 - pad; const ids = IHX.order; const N = ids.length; const angle = i => (-Math.PI / 2) + (i * 2 * Math.PI / N); const pt = (i, r) => [cx + r * Math.cos(angle(i)), cy + r * Math.sin(angle(i))]; const gridPolys = []; for (let l = 1; l <= levels; l++) { const r = R * l / levels; const pts = ids.map((_, i) => pt(i, r).join(',')).join(' '); gridPolys.push(); } const spokes = ids.map((_, i) => { const [x, y] = pt(i, R); return ; }); return ( {gridPolys}{spokes} {/* vértices de color */} {showColoredAxes && ids.map((id, i) => { const [x, y] = pt(i, R); return ; })} {/* series */} {series.map((s, si) => { const pts = ids.map((id, i) => pt(i, R * Math.max(0, Math.min(max, s.scores[id])) / max)); const poly = pts.map(p => p.join(',')).join(' '); return ( {(s.dots ?? true) && pts.map((p, i) => ( ))} ); })} {/* etiquetas */} {labels && ids.map((id, i) => { const [x, y] = pt(i, R + 30); const a = ARCH.find(z => z.id === id); const anchor = Math.abs(x - cx) < 6 ? 'middle' : (x > cx ? 'start' : 'end'); return ( {a.name} ); })} ); } /* —— Etiqueta de valor en el eje (leyenda compacta) —— */ function AxisLegend({ scores }) { return (
{IHX.order.map(id => { const a = IHX.byId[id]; return (
{a.name} {scores[id]}%
); })}
); } /* —— Barra de puntaje horizontal —— */ function ScoreBar({ id, value, max = 100, height = 9, showGlyph = false, animated = true }) { const a = IHX.byId[id]; return (
{showGlyph && }
{a.name} {value}%
); } /* —— Distribución: cuántos colaboradores por arquetipo dominante —— */ function DistBars({ dist, total }) { const maxV = Math.max(1, ...Object.values(dist)); return (
{IHX.order.map(id => { const a = IHX.byId[id]; const v = dist[id] || 0; const pct = Math.round((v / (total || 1)) * 100); return (
{a.name}
{v} · {pct}%
); })}
); } /* —— Dona de distribución —— */ function Donut({ dist, total, size = 200, thickness = 26 }) { const cx = size / 2, cy = size / 2, r = (size - thickness) / 2; const circ = 2 * Math.PI * r; let acc = 0; const segs = IHX.order.map(id => { const v = dist[id] || 0; const frac = total ? v / total : 0; const seg = { id, frac, off: acc }; acc += frac; return seg; }); return ( {segs.map(s => ( ))} {total} EVALUADOS ); } /* —— Mapa de calor organizacional (área × arquetipo) —— */ function Heatmap({ areaMap }) { const areas = Object.keys(areaMap); const ids = IHX.order; const cell = (v) => { // 0..100 → opacidad return Math.max(0.06, Math.min(1, v / 100)); }; return (
{ids.map(id => ( ))} {areas.map(ar => ( {ids.map(id => { const v = areaMap[ar].scores[id]; const op = cell(v); const light = op > 0.62; return ( ); })} ))}
Área
{IHX.byId[id].name}
{ar} n={areaMap[ar].n}
{v}
); } /* —— Ranking de evolución por área —— */ function EvolRanking({ rows }) { const maxV = 100; return (
{rows.map((r, i) => (
{String(i + 1).padStart(2, '0')} {r.area}
{r.evol}
))}
); } /* —— Comparación histórica (líneas/área del índice + arquetipos) —— */ function HistLines({ hist, width = 520, height = 200 }) { const pad = { t: 16, r: 16, b: 30, l: 30 }; const W = width - pad.l - pad.r, H = height - pad.t - pad.b; const xs = hist.map((_, i) => pad.l + (hist.length === 1 ? W / 2 : i * W / (hist.length - 1))); const y = v => pad.t + H - (v / 100) * H; const ids = IHX.order; return ( {[0, 25, 50, 75, 100].map(g => ( {g} ))} {ids.map(id => { const d = hist.map((h, i) => `${xs[i]},${y(h.scores[id])}`).join(' '); return ; })} {/* índice de evolución resaltado */} `${xs[i]},${y(h.evol)}`).join(' ')} fill="none" stroke="var(--gold)" strokeWidth="3" /> {hist.map((h, i) => )} {hist.map((h, i) => ( {h.periodo.replace(' anterior', ' ant.')} ))} ); } /* —— Anillo estadístico (índice de evolución) —— */ function RingStat({ value, label, size = 132, color = 'var(--gold)' }) { const r = (size - 14) / 2, circ = 2 * Math.PI * r, cx = size / 2; return (
{value} / 100 {label && {label}}
); } Object.assign(window, { Glyph, Radar, AxisLegend, ScoreBar, DistBars, Donut, Heatmap, EvolRanking, HistLines, RingStat, COL });