// A global store of active Chart.js instances let chartInstances = {}; /** * Mapping from raw JSON metric keys to user-friendly names. * (Unchanged from your existing code) */ const metricNameMap = { gdp_growth: "GDP Growth (%)", unemployment_rate: "Unemployment Rate (%)", ppiaco: "Producer Price Index (All Commodities)", ism_pmi: "ISM Purchasing Managers’ Index", outlook: "NAM Outlook Survey", factory_orders: "Factory Orders", durable_goods: "Durable Goods Orders", quarterly_profits: "Quarterly Profits (Manufacturing)", manemp: "Manufacturing Employment", jts3000jol: "Manufacturing Job Openings (JOLTS)", vapgdpma: "Mfg Value-Added Share of GDP", bopgtb: "Goods Trade Balance", bopgexp: "Goods Exports", bopgimp: "Goods Imports", iron_ore: "Iron Ore Price", "HG1:COM": "Copper Futures (HG1)", "LMAHDS03:COM": "Aluminum (LME 3-Month)", "XAUUSD:CUR": "Gold Spot Price", "SPX:IND": "S&P 500 Index", "DXY:CUR": "U.S. Dollar Index", "SCO:COM": "SCO A 2× short (leveraged) ETF on crude oil prices" }; /** * Mapping from metric keys (exactly matching the JSON) to a group name * that corresponds to
in dashboard.php. */ const metricGroupMap = { // ----- NATIONAL ----- "gdp_growth": "national", "unemployment_rate": "national", "ppiaco": "national", // ----- SENTIMENT ----- "ism_pmi": "sentiment", "outlook": "sentiment", // ----- MANUFACTURERS ----- "factory_orders": "manufacturers", "durable_goods": "manufacturers", "quarterly_profits": "manufacturers", "manemp": "manufacturers", "jts3000jol": "manufacturers", "vapgdpma": "manufacturers", // ----- TRADE ----- "bopgtb": "trade", "bopgexp": "trade", "bopgimp": "trade", // ----- commodities ----- "iron_ore": "commodities", "HG1:COM": "commodities", "LMAHDS03:COM": "commodities", "XAUUSD:CUR": "commodities", "SCO:COM": "commodities", // ----- MARKETS ----- "SPX:IND": "markets", "DXY:CUR": "markets" }; // Fetch the JSON data from the URL provided by WordPress (econData.json_url) fetch(econData.json_url) .then(res => res.json()) .then(data => { // 1. Build tiles in the dashboard Object.entries(data).forEach(([metric, series]) => { if (!Array.isArray(series) || series.length < 1) return; const latest = series[series.length - 1]; const prev = series.length > 1 ? series[series.length - 2] : null; const value = latest.value; const delta = latest.yoy_percent_change ?? latest.change_vs_previous ?? 0; const isUp = delta > 0; const groupKey = metricGroupMap[metric] || 'other'; const container = document.querySelector(`.dashboard-group[data-group="${groupKey}"]`); if (!container) return; // Use a friendly name if available, else fallback const displayName = metricNameMap[metric] || formatTitle(metric); // Create the tile const tile = document.createElement('div'); tile.className = 'metric-tile'; tile.dataset.metric = metric; tile.innerHTML = `

${displayName}

${formatValue(value)} ${isUp ? '▲' : '▼'} ${Math.abs(delta).toFixed(1)}%
`; container.appendChild(tile); }); // 2. Attach click events for toggling each chart document.querySelectorAll('.tile-header').forEach(header => { header.addEventListener('click', () => { const parent = header.parentElement; const chartBox = parent.querySelector('.tile-chart'); const canvas = chartBox.querySelector('canvas'); const metric = parent.dataset.metric; // Toggle the hidden class if (chartBox.classList.contains('hidden')) { chartBox.classList.remove('hidden'); if (!chartInstances[metric]) { // Build the dataset with global no points const datasetOptions = { label: displayNameOrFallback(metric), data: data[metric].map(d => d.value), borderWidth: 2, borderColor: '#333', fill: false, pointRadius: 0, pointHoverRadius: 0 }; chartInstances[metric] = new Chart(canvas, { type: 'line', data: { labels: data[metric].map(d => d.date), datasets: [ datasetOptions ] }, options: { responsive: true, plugins: { legend: { display: false }, tooltip: { mode: 'index', intersect: false } }, scales: { y: { title: { display: true, text: displayNameOrFallback(metric) } }, x: { ticks: { autoSkip: true, maxTicksLimit: 12 } } } } }); } } else { chartBox.classList.add('hidden'); } }); }); // 3. Collapse chart button document.querySelectorAll('.collapse-chart').forEach(btn => { btn.addEventListener('click', e => { e.stopPropagation(); const chartBox = btn.closest('.tile-chart'); chartBox.classList.add('hidden'); }); }); // 4. Press 'e' to export all open charts as PNG document.addEventListener('keydown', e => { if (e.key === 'e') { const openCharts = Object.values(chartInstances).filter(c => { const tileChart = c.canvas.closest('.tile-chart'); return tileChart && !tileChart.classList.contains('hidden'); }); openCharts.forEach(c => { const link = document.createElement('a'); link.download = `${c.canvas.closest('.metric-tile').dataset.metric}-chart.png`; link.href = c.toBase64Image(); link.click(); }); } }); // 5. Build the scrolling ticker AFTER tiles are created updateTicker(); /** * Helper function for charts: * If metricNameMap doesn't have a display name, use formatTitle */ function displayNameOrFallback(metricKey) { return metricNameMap[metricKey] || formatTitle(metricKey); } }); // UTILITY FUNCTION: Format numeric values function formatValue(val) { if (typeof val === 'number') { if (val > 1000) return '$' + (val / 1000).toFixed(1) + 'b'; if (val % 1 === 0) return val; return val.toFixed(1); } return val; } // UTILITY FUNCTION: Turn e.g. "XAUUSD:CUR" into "Xauusd: Cur" function formatTitle(key) { return key .replace(/_/g, ' ') .replace(/\b\w/g, l => l.toUpperCase()); } /* * NEW FUNCTION: Build the Ticker * - Grabs all .metric-tile elements * - For each tile, extracts (title, value, delta) * - Creates a .ticker-item that scrolls across the ticker * - Clicking on the ticker item scrolls to that tile & opens its chart */ function updateTicker() { const tickerInner = document.getElementById('ticker-inner'); if (!tickerInner) return; // Clear any existing ticker content. tickerInner.innerHTML = ''; // Collect all dashboard metric tiles. const tiles = document.querySelectorAll('.metric-tile'); tiles.forEach(tile => { const metric = tile.dataset.metric; const titleElem = tile.querySelector('.tile-header h3'); const valueElem = tile.querySelector('.tile-value'); const deltaElem = tile.querySelector('.tile-delta'); // Proceed only if we have all necessary elements. if (!titleElem || !valueElem || !deltaElem) return; const titleText = titleElem.textContent.trim(); // Assume the first text node in .tile-value is the numeric value. const valueText = valueElem.childNodes[0].textContent.trim(); const deltaText = deltaElem.textContent.trim(); const upOrDown = deltaElem.classList.contains('up') ? 'up' : 'down'; // Create the ticker item container. const item = document.createElement('div'); item.className = 'ticker-item'; item.innerHTML = ` ${titleText} ${valueText} ${deltaText} `; // Click event: scroll to the corresponding tile and open its chart. item.addEventListener('click', () => { const targetTile = document.querySelector(`.metric-tile[data-metric="${metric}"]`); if (!targetTile) return; // Open the chart if hidden. const chartBox = targetTile.querySelector('.tile-chart.hidden'); if (chartBox) { targetTile.querySelector('.tile-header').click(); } // Smoothly scroll the tile into view. targetTile.scrollIntoView({ behavior: 'smooth', block: 'start' }); }); tickerInner.appendChild(item); }); // After building ticker items, add the 'animate' class to start the animation. tickerInner.classList.add('animate'); } updateTicker();