/* 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 ( {/* ===== shared event bands (panels 1 & 2) ===== */} {EVENT_BANDS.map(b => { const bx = sx(b.from), bw = sx(b.to) - sx(b.from); return ( ); })} {/* ===== PANEL 1: price vs trend ===== */} Panel 1 · Mean House Price vs HP Trend {[100, 200, 300, 400, 500].map(v => ( RM {v}K ))} {/* panel-1 event labels */} {/* legend */} Mean House Price HP Trend (λ = 1600) {/* ===== PANEL 2: cyclical component ===== */} Panel 2 · Cyclical Component (Mean Price − HP Trend) {[-10, -5, 0, 5, 10, 15].map(v => ( {v > 0 ? '+' : ''}{v}K ))} {/* panel-2 callouts */} {/* fill legend */} Above trend (cycle_pos = 1) Below trend (cycle_pos = 0) {/* ===== PANEL 3: cycle_pos strip ===== */} Panel 3 · Dependent Variable — cycle_pos {DECOMP.map((p, i) => ( ))} 1 ▲ 0 ▼ {/* ===== shared x-axis ===== */} {xTicks.map(yr => ( {yr} ))} ); }; Object.assign(window, { CyclicalChart });