/* ============================================================
Charts — hand-rolled SVG (no recharts dep)
============================================================ */
/* global React */
const { useMemo: useMemoChart } = React;
function LineChart20({ years, withSolar, withoutSolar, breakEvenYear, height = 160 }) {
// withSolar: array of cumulative net savings per year (capex offset)
// withoutSolar: cumulative cost paid to discom
const allVals = [...withSolar, ...withoutSolar];
const minY = Math.min(...allVals);
const maxY = Math.max(...allVals);
const W = 380, H = height;
const PAD = { l: 44, r: 12, t: 14, b: 22 };
const innerW = W - PAD.l - PAD.r, innerH = H - PAD.t - PAD.b;
const x = i => PAD.l + (i / (years - 1)) * innerW;
const y = v => PAD.t + innerH - ((v - minY) / (maxY - minY || 1)) * innerH;
const pathSolar = withSolar.map((v, i) => `${i === 0 ? "M" : "L"}${x(i)} ${y(v)}`).join(" ");
const pathNo = withoutSolar.map((v, i) => `${i === 0 ? "M" : "L"}${x(i)} ${y(v)}`).join(" ");
const yTicks = [minY, 0, maxY * 0.5, maxY];
const xTicks = [0, 5, 10, 15, 19];
return (
);
}
function BarChart({ data, height = 96, color = "#0d0d0c", highlightIdx = null }) {
// data: [{label, value}]
const W = 380, H = height;
const PAD = { l: 6, r: 6, t: 6, b: 18 };
const innerW = W - PAD.l - PAD.r, innerH = H - PAD.t - PAD.b;
const max = Math.max(...data.map(d => d.value));
const bw = innerW / data.length * 0.72;
const gap = innerW / data.length * 0.28;
return (
);
}
function Histogram({ buckets, height = 120, color = "#0d0d0c" }) {
// buckets: [{label, value}]
const W = 380, H = height;
const PAD = { l: 22, r: 6, t: 8, b: 22 };
const innerW = W - PAD.l - PAD.r, innerH = H - PAD.t - PAD.b;
const max = Math.max(...buckets.map(b => b.value));
const bw = innerW / buckets.length;
return (
);
}
function AzimuthRose({ buildings, height = 140 }) {
// 16 wedges representing azimuth bins
const W = 380, H = height;
const cx = W / 2, cy = H / 2 + 6;
const r = Math.min(W, H) / 2 - 18;
const bins = new Array(16).fill(0);
buildings.forEach(b => {
if (b.tilt === 0) return; // skip flat
const idx = Math.round(b.azimuth / 22.5) % 16;
bins[idx] += b.kWp;
});
const max = Math.max(...bins, 1);
const wedges = bins.map((v, i) => {
const a1 = (i - 0.5) * 22.5 * Math.PI / 180 - Math.PI / 2;
const a2 = (i + 0.5) * 22.5 * Math.PI / 180 - Math.PI / 2;
const len = (v / max) * r;
const x1 = cx + Math.cos(a1) * len, y1 = cy + Math.sin(a1) * len;
const x2 = cx + Math.cos(a2) * len, y2 = cy + Math.sin(a2) * len;
const fillT = v / max;
const fluxStops = [[26,17,71],[238,109,61],[247,167,34],[253,231,37]];
const c = (() => {
const n = fluxStops.length - 1;
const t = fillT * n;
const i0 = Math.floor(t);
const f = t - i0;
const a = fluxStops[i0], bb = fluxStops[Math.min(n, i0 + 1)];
return `rgb(${Math.round(a[0]+(bb[0]-a[0])*f)}, ${Math.round(a[1]+(bb[1]-a[1])*f)}, ${Math.round(a[2]+(bb[2]-a[2])*f)})`;
})();
return ;
});
const labels = [
{ l: "N", a: 0 }, { l: "E", a: 90 }, { l: "S", a: 180 }, { l: "W", a: 270 },
];
return (
);
}
function Sparkline({ data, height = 22, color = "#57574f" }) {
if (!data || data.length === 0) return null;
const W = 100, H = height;
const min = Math.min(...data), max = Math.max(...data);
const x = i => (i / (data.length - 1)) * W;
const y = v => H - ((v - min) / (max - min || 1)) * H;
const path = data.map((v, i) => `${i === 0 ? "M" : "L"}${x(i)} ${y(v)}`).join(" ");
return (
);
}
Object.assign(window, { LineChart20, BarChart, Histogram, AzimuthRose, Sparkline });