fix(trace-charts): drop glitchy custom wheel/pan, restore uPlot default mouse control
The hand-rolled wheel-zoom + drag-pan (wheelZoomPlugin) was hakelig/glitchy — uPlot is not built to be driven that way. Remove the plugin and the cursor.drag override so uPlot's own defaults govern the mouse: drag = box-zoom on x, double-click = reset to full range, with the legend value-readout and crosshair unchanged. Everything else from the Tier-1 pass stays: the ordinal (gap-collapsed) x-axis default + continuous toggle, the ordinal-label honesty (ticks/legend report the real xs[i]), and panel cursor-sync (cursor.sync key retained; only the drag override and the plugin are dropped). The x-mode guard is flipped to pin the revert — overlay carries no custom plugin and no cursor.drag override, panels carry no plugin — and stays green alongside the other three chart guards. refs #103
This commit is contained in:
@@ -6,49 +6,14 @@
|
||||
(function () {
|
||||
var PALETTE = ["#89b4fa", "#f38ba8", "#a6e3a1", "#f9e2af", "#cba6f7", "#94e2d5"];
|
||||
|
||||
// wheelZoomPlugin: a pure factory (safe to construct headless — its hooks run only in
|
||||
// a browser). On init it wires wheel-zoom (x, cursor-anchored), drag-pan, and
|
||||
// dblclick-reset onto u.over. uPlot's default drag-box-zoom is disabled in buildCharts
|
||||
// (cursor.drag off) so drag pans instead.
|
||||
function wheelZoomPlugin() {
|
||||
var ZOOM = 0.9; // one wheel notch shrinks/grows the visible x-range by this factor
|
||||
return { hooks: { init: function (u) {
|
||||
var over = u.over;
|
||||
over.addEventListener("wheel", function (e) {
|
||||
e.preventDefault();
|
||||
var rect = over.getBoundingClientRect();
|
||||
var xVal = u.posToVal(e.clientX - rect.left, "x");
|
||||
var lo = u.scales.x.min, hi = u.scales.x.max;
|
||||
var f = e.deltaY < 0 ? ZOOM : 1 / ZOOM; // wheel up = zoom in
|
||||
u.setScale("x", { min: xVal - (xVal - lo) * f, max: xVal + (hi - xVal) * f });
|
||||
}, { passive: false });
|
||||
over.addEventListener("mousedown", function (d) {
|
||||
if (d.button !== 0) return;
|
||||
var x0 = d.clientX, lo = u.scales.x.min, hi = u.scales.x.max;
|
||||
var unitsPerPx = (hi - lo) / over.clientWidth;
|
||||
function move(m) {
|
||||
var dx = (m.clientX - x0) * unitsPerPx;
|
||||
u.setScale("x", { min: lo - dx, max: hi - dx });
|
||||
}
|
||||
function up() {
|
||||
document.removeEventListener("mousemove", move);
|
||||
document.removeEventListener("mouseup", up);
|
||||
}
|
||||
document.addEventListener("mousemove", move);
|
||||
document.addEventListener("mouseup", up);
|
||||
});
|
||||
over.addEventListener("dblclick", function () {
|
||||
u.setScale("x", { min: u.data[0][0], max: u.data[0][u.data[0].length - 1] });
|
||||
});
|
||||
} } };
|
||||
}
|
||||
|
||||
// {xs, series:[{name, y_scale_id, points}]} + mode + xMode -> array of {opts, data}.
|
||||
// xMode in {"ordinal","continuous"}, default "ordinal". ordinal: data[0] is the index
|
||||
// spine [0..n-1] (gaps collapse) and the x-axis/legend map an index back to the real
|
||||
// xs[i]; continuous: data[0] is xs. series points (data[1..]) are untouched either way
|
||||
// (null gaps preserved verbatim). overlay -> one chart; panels -> one chart per series,
|
||||
// sharing a cursor.sync key so the crosshair lines up.
|
||||
// sharing a cursor.sync key so the crosshair lines up. Mouse interaction is uPlot's
|
||||
// default (drag = box-zoom on x, double-click = reset); we add no custom wheel/pan
|
||||
// (the hand-rolled version was glitchy — uPlot is not built for it).
|
||||
function buildCharts(traces, mode, xMode) {
|
||||
xMode = xMode || "ordinal";
|
||||
var xs = traces.xs;
|
||||
@@ -77,8 +42,7 @@
|
||||
scales: { x: { time: false }, [s.y_scale_id]: {} },
|
||||
series: [xSeriesCfg(), { label: s.name, scale: s.y_scale_id, stroke: PALETTE[i % PALETTE.length] }],
|
||||
axes: [xAxisCfg(), { scale: s.y_scale_id }],
|
||||
cursor: { sync: { key: SYNC }, drag: { x: false, y: false } },
|
||||
plugins: [wheelZoomPlugin()],
|
||||
cursor: { sync: { key: SYNC } },
|
||||
},
|
||||
data: [xData, s.points],
|
||||
};
|
||||
@@ -97,7 +61,7 @@
|
||||
if (i < 2) axes.push({ scale: s.y_scale_id, side: i === 0 ? 3 : 1 });
|
||||
});
|
||||
return [{
|
||||
opts: { scales: scales, series: uSeries, axes: axes, cursor: { drag: { x: false, y: false } }, plugins: [wheelZoomPlugin()] },
|
||||
opts: { scales: scales, series: uSeries, axes: axes },
|
||||
data: [xData].concat(traces.series.map(function (s) { return s.points; })),
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
// Properties: (1) the DEFAULT x spine is ORDINAL — data[0] is [0..n-1], not the raw
|
||||
// timestamps, so non-trading gaps collapse; the x-axis carries an index->label values
|
||||
// formatter. (2) "continuous" mode restores data[0] === xs verbatim. (3) panels carry a
|
||||
// shared cursor.sync.key so the crosshair lines up. (4) every config attaches an
|
||||
// interaction plugin with an init hook. Drives the pure buildCharts; no DOM/uPlot.
|
||||
// shared cursor.sync.key so the crosshair lines up. (4) no custom interaction plugin and
|
||||
// no cursor.drag override — uPlot's default drag-box-zoom / dblclick-reset governs the
|
||||
// mouse (the hand-rolled wheel/pan was glitchy). Drives the pure buildCharts; no DOM/uPlot.
|
||||
import { createRequire } from "node:module";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
@@ -29,9 +30,10 @@ const ov = buildCharts(traces, "overlay");
|
||||
sameArr(ov[0].data[0], idx, "default overlay x is the ordinal index spine [0..n-1]");
|
||||
if (typeof ov[0].opts.axes[0].values !== "function") fail("ordinal overlay x-axis lacks a values formatter");
|
||||
|
||||
// (4) interaction wired: a plugin with an init hook.
|
||||
if (!Array.isArray(ov[0].opts.plugins) || ov[0].opts.plugins.length < 1) fail("no interaction plugin attached");
|
||||
if (typeof ov[0].opts.plugins[0].hooks.init !== "function") fail("interaction plugin has no init hook");
|
||||
// (4) interaction is uPlot's default: overlay carries no custom plugin and no
|
||||
// cursor.drag override (so drag = box-zoom, dblclick = reset, both uPlot built-ins).
|
||||
if (ov[0].opts.plugins && ov[0].opts.plugins.length) fail("overlay should carry no custom interaction plugin (uPlot defaults)");
|
||||
if (ov[0].opts.cursor && ov[0].opts.cursor.drag) fail("overlay should not override cursor.drag (uPlot default box-zoom)");
|
||||
|
||||
// (2) continuous mode: data[0] is xs verbatim.
|
||||
const ovc = buildCharts(traces, "overlay", "continuous");
|
||||
@@ -42,7 +44,8 @@ const panels = buildCharts(traces, "panels");
|
||||
const keys = panels.map((p) => p.opts.cursor && p.opts.cursor.sync && p.opts.cursor.sync.key);
|
||||
if (keys.some((k) => !k)) fail("a panel lacks cursor.sync.key");
|
||||
if (new Set(keys).size !== 1) fail("panels do not share one cursor.sync key");
|
||||
if (panels.some((p) => p.opts.plugins && p.opts.plugins.length)) fail("panels should carry no custom interaction plugin (uPlot defaults)");
|
||||
sameArr(panels[0].data[0], idx, "default panel x is the ordinal index spine");
|
||||
|
||||
console.log("OK — ordinal default + continuous toggle + panel cursor-sync + interaction plugin.");
|
||||
console.log("OK — ordinal default + continuous toggle + panel cursor-sync + uPlot-default controls.");
|
||||
process.exit(0);
|
||||
|
||||
Reference in New Issue
Block a user