window.chartInterop = { chartInstances: {}, transactionLabels: {}, createOrUpdateChart: function (canvasId, labels, datasets, dayTransactionLabels, highlightIndex, currencySymbol) { const canvas = document.getElementById(canvasId); if (!canvas) return; if (this.chartInstances[canvasId]) { this.chartInstances[canvasId].destroy(); } this.transactionLabels[canvasId] = dayTransactionLabels || []; const sym = currencySymbol || '£'; const chartDatasets = datasets.map(ds => ({ label: ds.label, data: ds.data, borderColor: ds.color, backgroundColor: ds.color + '20', fill: true, tension: 0.3, pointRadius: 1, pointHoverRadius: 5, pointHoverBorderWidth: 2, pointHoverBackgroundColor: '#fff', borderWidth: 2, currencySymbol: ds.currencySymbol || sym })); // Vertical highlight line plugin (transaction selection) const highlightPlugin = { id: 'highlightLine', afterDraw: (chart) => { const idx = chart.config._highlightIndex; if (idx == null || idx < 0) return; const meta = chart.getDatasetMeta(0); if (!meta || !meta.data[idx]) return; const x = meta.data[idx].x; const ctx = chart.ctx; const top = chart.chartArea.top; const bottom = chart.chartArea.bottom; ctx.save(); ctx.beginPath(); ctx.moveTo(x, top); ctx.lineTo(x, bottom); ctx.lineWidth = 2; ctx.strokeStyle = 'rgba(255, 99, 132, 0.8)'; ctx.setLineDash([6, 4]); ctx.stroke(); ctx.restore(); // Draw label const label = labels[idx]; if (label) { ctx.save(); ctx.font = 'bold 11px sans-serif'; ctx.fillStyle = 'rgba(255, 99, 132, 1)'; ctx.textAlign = 'center'; ctx.fillText('\u25BC ' + label, x, top - 4); ctx.restore(); } } }; // Crosshair plugin — vertical line at hovered index const crosshairPlugin = { id: 'crosshair', afterDraw: (chart) => { const tooltip = chart.tooltip; if (!tooltip || !tooltip.getActiveElements().length) return; const activePoint = tooltip.getActiveElements()[0]; const meta = chart.getDatasetMeta(activePoint.datasetIndex); if (!meta || !meta.data[activePoint.index]) return; const x = meta.data[activePoint.index].x; const ctx = chart.ctx; const top = chart.chartArea.top; const bottom = chart.chartArea.bottom; ctx.save(); ctx.beginPath(); ctx.moveTo(x, top); ctx.lineTo(x, bottom); ctx.lineWidth = 1; ctx.strokeStyle = 'rgba(0, 0, 0, 0.15)'; ctx.stroke(); ctx.restore(); } }; const storedLabels = this.transactionLabels[canvasId]; const chartInstance = new Chart(canvas, { type: 'line', data: { labels: labels, datasets: chartDatasets }, options: { responsive: true, maintainAspectRatio: false, interaction: { intersect: false, mode: 'index', axis: 'x' }, plugins: { legend: { position: 'top' }, tooltip: { position: 'nearest', caretPadding: 8, callbacks: { label: function (context) { const dsSymbol = context.dataset.currencySymbol || sym; return context.dataset.label + ': ' + dsSymbol + context.parsed.y.toFixed(2); }, afterBody: function (contexts) { const idx = contexts[0]?.dataIndex; if (idx == null || !storedLabels[idx] || storedLabels[idx].length === 0) return []; const lines = ['', 'Transactions:']; storedLabels[idx].forEach(l => lines.push(' ' + l)); return lines; } } } }, scales: { x: { ticks: { maxTicksLimit: 10 } }, y: { ticks: { callback: function (value) { return sym + value.toFixed(0); } } } } }, plugins: [highlightPlugin, crosshairPlugin] }); chartInstance.config._highlightIndex = (highlightIndex != null && highlightIndex >= 0) ? highlightIndex : null; if (highlightIndex != null && highlightIndex >= 0) { chartInstance.update(); } this.chartInstances[canvasId] = chartInstance; }, highlightDate: function (canvasId, index) { const chart = this.chartInstances[canvasId]; if (!chart) return; chart.config._highlightIndex = (index != null && index >= 0) ? index : null; chart.update(); }, destroyChart: function (canvasId) { if (this.chartInstances[canvasId]) { this.chartInstances[canvasId].destroy(); delete this.chartInstances[canvasId]; delete this.transactionLabels[canvasId]; } } }; // Auto-select contents of numeric inputs on focus document.addEventListener('focusin', function (e) { if (e.target && e.target.matches('input[type="number"], input.numeric-input')) { e.target.select(); } });