/* eslint-disable no-undef */
/* CyclicalChart.jsx — three stacked panels sharing one x-axis (1988–2025):
Panel 1 Mean House Price vs HP Trend
Panel 2 Cyclical Component (price − trend), green above / red below
Panel 3 cycle_pos dependent variable (1 = above trend, 0 = below)
Recoloured into the dashboard's warm palette. */
function _hex(c, a) {
const r = parseInt(c.slice(1, 3), 16), g = parseInt(c.slice(3, 5), 16), b = parseInt(c.slice(5, 7), 16);
return `rgba(${r},${g},${b},${a})`;
}
/* small boxed annotation label with an optional connector */
const Callout = ({ x, y, text, color, anchor = 'middle', line }) => {
const lines = text.split('\n');
const wBox = Math.max(...lines.map(l => l.length)) * 5.6 + 14;
const hBox = lines.length * 12 + 8;
const bx = anchor === 'middle' ? x - wBox / 2 : anchor === 'end' ? x - wBox : x;
return (
{line && }
{line && }
{lines.map((l, i) => (
{l}
))}
);
};
const CyclicalChart = () => {
const W = 1000, H = 730;
const xL = 78, xR = W - 30;
const yearMin = 1988, yearMax = 2026;
const sx = (yr) => xL + ((yr - yearMin) / (yearMax - yearMin)) * (xR - xL);
// panel vertical extents
const P1 = { t: 50, b: 286 }, P2 = { t: 350, b: 580 }, P3 = { t: 626, b: 668 };
// panel 1 scale: price RM '000
const pMin = 50, pMax = 515;
const syP = (v) => P1.b - ((v - pMin) / (pMax - pMin)) * (P1.b - P1.t);
// panel 2 scale: cyclical RM '000
const cMin = -15, cMax = 17.5;
const syC = (v) => P2.b - ((v - cMin) / (cMax - cMin)) * (P2.b - P2.t);
const zeroC = syC(0);
const priceLine = DECOMP.map((p, i) => `${i ? 'L' : 'M'}${sx(p.year).toFixed(1)} ${syP(p.price).toFixed(1)}`).join(' ');
const trendLine = DECOMP.map((p, i) => `${i ? 'L' : 'M'}${sx(p.year).toFixed(1)} ${syP(p.trend).toFixed(1)}`).join(' ');
const cycFill = `M${sx(DECOMP[0].year).toFixed(1)} ${zeroC.toFixed(1)} `
+ DECOMP.map(p => `L${sx(p.year).toFixed(1)} ${syC(p.cyc).toFixed(1)}`).join(' ')
+ ` L${sx(DECOMP[DECOMP.length - 1].year).toFixed(1)} ${zeroC.toFixed(1)} Z`;
const cycLine = DECOMP.map((p, i) => `${i ? 'L' : 'M'}${sx(p.year).toFixed(1)} ${syC(p.cyc).toFixed(1)}`).join(' ');
const xTicks = [1988, 1992, 1996, 2000, 2004, 2008, 2012, 2016, 2020, 2024];
const qW = (sx(DECOMP[1].year) - sx(DECOMP[0].year));
return (
);
};
Object.assign(window, { CyclicalChart });