(null);
const data = parseChartData(content);
useEffect(() => {
if (!data || !canvasRef.current) return;
if (data.type === "pie" || data.type === "doughnut") return;
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const COLORS = ["#4f8ef7","#7c4dff","#22c55e","#f59e0b","#ef4444","#06b6d4","#ec4899"];
const w = canvas.width, h = canvas.height;
const pad = { top: 20, right: 20, bottom: 35, left: 40 };
const chartW = w - pad.left - pad.right;
const chartH = h - pad.top - pad.bottom;
ctx.clearRect(0, 0, w, h);
const allVals = data.datasets.flatMap(d => d.data);
const maxVal = Math.max(...allVals, 1);
const minVal = Math.min(0, ...allVals);
// Grid
ctx.strokeStyle = "#1e1e2e";
ctx.lineWidth = 1;
const ySteps = 4;
for (let i = 0; i <= ySteps; i++) {
const y = pad.top + (i / ySteps) * chartH;
ctx.beginPath(); ctx.moveTo(pad.left, y); ctx.lineTo(pad.left + chartW, y); ctx.stroke();
const val = maxVal - (i / ySteps) * (maxVal - minVal);
ctx.fillStyle = "#5a5a72"; ctx.font = "9px sans-serif"; ctx.textAlign = "right";
ctx.fillText(val.toFixed(1), pad.left - 4, y + 3);
}
const barGroupW = chartW / data.labels.length;
const barCount = data.datasets.length;
const barW = Math.min(barGroupW * 0.7 / barCount, 40);
const yZero = pad.top + chartH * (maxVal / (maxVal - minVal));
data.datasets.forEach((ds, di) => {
ctx.fillStyle = ds.color || COLORS[di % COLORS.length];
ctx.strokeStyle = ds.color || COLORS[di % COLORS.length];
ctx.lineWidth = 2;
if (data.type === "line") {
ctx.beginPath();
ds.data.forEach((val, xi) => {
const x = pad.left + barGroupW * (xi + 0.5);
const y = pad.top + chartH * (1 - (val - minVal) / (maxVal - minVal));
if (xi === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
});
ctx.stroke();
ds.data.forEach((val, xi) => {
const x = pad.left + barGroupW * (xi + 0.5);
const y = pad.top + chartH * (1 - (val - minVal) / (maxVal - minVal));
ctx.beginPath(); ctx.arc(x, y, 3, 0, Math.PI * 2); ctx.fill();
});
} else {
ds.data.forEach((val, xi) => {
const x = pad.left + barGroupW * xi + (barGroupW - barCount * barW) / 2 + di * barW;
const barH = Math.abs((val / (maxVal - minVal)) * chartH);
const y = val >= 0 ? yZero - barH : yZero;
ctx.fillRect(x, y, barW - 2, barH);
});
}
});
// X labels
ctx.fillStyle = "#5a5a72"; ctx.font = "9px sans-serif"; ctx.textAlign = "center";
data.labels.forEach((l, i) => {
const x = pad.left + barGroupW * (i + 0.5);
ctx.fillText(l.slice(0, 10), x, pad.top + chartH + 14);
});
}, [data]);
if (!data) {
return (
Formato grafico non valido. Usa JSON con: labels, datasets[].data, type (bar|line|pie).
);
}
const isPie = data.type === "pie" || data.type === "doughnut";
return (
{data.title && (
{data.title}
)}
{error &&
{error}
}
);
}
);
export default ChartBlock;