The spectrum chart was missing xMin/xMax persistence and data boundary clamping in enablePan(). Align with the background/cps chart patterns. Bump cache versions to force browser refresh. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
90 lines
2.7 KiB
JavaScript
90 lines
2.7 KiB
JavaScript
/**
|
|
* Manual pan handler for Chart.js charts with zoom.
|
|
* Enables click-and-drag to pan on the X axis, clamped to data boundaries.
|
|
* Call enablePan(chart, chartId, dataMin, dataMax) after chart creation.
|
|
*/
|
|
function enablePan(chart, resetBtnId, dataMin, dataMax) {
|
|
const canvas = chart.canvas;
|
|
let isPanning = false;
|
|
let startClientX = 0;
|
|
let startMin = 0;
|
|
let startMax = 0;
|
|
let xScaleWidth = 0;
|
|
|
|
canvas.addEventListener('mousedown', (e) => {
|
|
if (e.button !== 0) return;
|
|
isPanning = true;
|
|
startClientX = e.clientX;
|
|
const xScale = chart.scales.x;
|
|
startMin = xScale.min;
|
|
startMax = xScale.max;
|
|
xScaleWidth = xScale.width;
|
|
canvas.style.cursor = 'grabbing';
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
});
|
|
|
|
const onMove = (clientX) => {
|
|
if (!isPanning) return;
|
|
const xScale = chart.scales.x;
|
|
const pixelDiff = clientX - startClientX;
|
|
const valuePerPixel = (startMax - startMin) / xScaleWidth;
|
|
const shift = -pixelDiff * valuePerPixel;
|
|
|
|
let newMin = startMin + shift;
|
|
let newMax = startMax + shift;
|
|
|
|
// Clamp to data boundaries
|
|
if (newMin < dataMin) {
|
|
newMin = dataMin;
|
|
newMax = newMin + (startMax - startMin);
|
|
}
|
|
if (newMax > dataMax) {
|
|
newMax = dataMax;
|
|
newMin = newMax - (startMax - startMin);
|
|
}
|
|
|
|
// Set via options to persist through update
|
|
// Set via chart.options to persist, and store on chart for next update
|
|
chart.options.scales.x.min = newMin;
|
|
chart.options.scales.x.max = newMax;
|
|
chart._panRange = [newMin, newMax];
|
|
chart.update('none');
|
|
};
|
|
|
|
canvas.addEventListener('mousemove', (e) => {
|
|
onMove(e.clientX);
|
|
});
|
|
|
|
canvas.addEventListener('mouseup', () => {
|
|
if (isPanning) {
|
|
isPanning = false;
|
|
canvas.style.cursor = '';
|
|
const btn = document.getElementById(resetBtnId);
|
|
if (btn) btn.style.display = 'inline-block';
|
|
}
|
|
});
|
|
|
|
canvas.addEventListener('mouseleave', () => {
|
|
if (isPanning) {
|
|
isPanning = false;
|
|
canvas.style.cursor = '';
|
|
}
|
|
});
|
|
|
|
// Document-level listeners for drag-outside-canvas
|
|
const docUp = () => {
|
|
if (isPanning) {
|
|
isPanning = false;
|
|
canvas.style.cursor = '';
|
|
const btn = document.getElementById(resetBtnId);
|
|
if (btn) btn.style.display = 'inline-block';
|
|
}
|
|
};
|
|
const docMove = (e) => {
|
|
if (isPanning) onMove(e.clientX);
|
|
};
|
|
document.addEventListener('mouseup', docUp);
|
|
document.addEventListener('mousemove', docMove);
|
|
}
|