Add project files.
This commit is contained in:
1 parent
fd9f318d6a
commit
0ce077ec11
98 files changed
+65707
No files matched your search
@@ -0,0 +1,175 @@
|
||||
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();
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user