Skip to content

Commit 4687466

Browse files
committed
feat(litmus): add onZoom callback to RangeSelector
onZoom(range, instance) fires after a zoom is applied on mouse release and on double-click reset. `range` is { x, y, reset? } where x/y are [min, max] for the axes that changed (null otherwise) and reset is true for a double-click reset. The example shows the applied range in a status readout.
1 parent de0d0f8 commit 4687466

1 file changed

Lines changed: 34 additions & 7 deletions

File tree

litmus/features/charts/line-graph/TimeLineGraph.js

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ import { getTopLevelBoundingClientRect } from "cx/util";
1515
// - bind `yMin`/`yMax` -> the selector zooms the y-axis
1616
// - bind both -> rectangular (x + y) zoom
1717
//
18+
// An optional `onZoom(range, instance)` callback fires after a zoom is applied
19+
// (and on double-click reset). `range` is `{ x, y, reset? }`, where `x`/`y` are
20+
// `[min, max]` for the axes that changed or `null` otherwise; `reset` is `true`
21+
// for a double-click reset.
22+
//
1823
// It relies on the axis calculators (available to any chart child via
1924
// `context.axes[...]`) to convert pixel <-> value:
2025
// - trackValue(px, offset, constrain) : pixel -> value
@@ -171,21 +176,27 @@ class RangeSelector extends BoundedObject {
171176
window.removeEventListener("mousemove", move, true);
172177
window.removeEventListener("mouseup", up, true);
173178

179+
let range = { x: null, y: null };
180+
174181
instance.store.batch(() => {
175182
instance.set("x1", null);
176183
instance.set("x2", null);
177184
instance.set("y1", null);
178185
instance.set("y2", null);
179186

180187
if (useX && ax && Math.abs(lastPxX - startPxX) >= 3) {
181-
instance.set("xMin", Math.min(startX, lastX));
182-
instance.set("xMax", Math.max(startX, lastX));
188+
range.x = [Math.min(startX, lastX), Math.max(startX, lastX)];
189+
instance.set("xMin", range.x[0]);
190+
instance.set("xMax", range.x[1]);
183191
}
184192
if (useY && ay && Math.abs(lastPxY - startPxY) >= 3) {
185-
instance.set("yMin", Math.min(startY, lastY));
186-
instance.set("yMax", Math.max(startY, lastY));
193+
range.y = [Math.min(startY, lastY), Math.max(startY, lastY)];
194+
instance.set("yMin", range.y[0]);
195+
instance.set("yMax", range.y[1]);
187196
}
188197
});
198+
199+
if ((range.x || range.y) && this.onZoom) instance.invoke("onZoom", range, instance);
189200
};
190201

191202
window.addEventListener("mousemove", move, true);
@@ -203,6 +214,10 @@ class RangeSelector extends BoundedObject {
203214
instance.set("yMax", null);
204215
}
205216
});
217+
218+
// reset is signalled with null ranges for the axes this selector controls
219+
if (this.onZoom)
220+
instance.invoke("onZoom", { x: null, y: null, reset: true }, instance);
206221
}
207222
}
208223

@@ -226,10 +241,19 @@ class PageController extends Controller {
226241
}
227242
}
228243

244+
function describeZoom(range, label) {
245+
if (range.reset) return `${label}: reset`;
246+
let d = (ms) => new Date(ms).toLocaleDateString();
247+
let parts = [];
248+
if (range.x) parts.push(`x ${d(range.x[0])}${d(range.x[1])}`);
249+
if (range.y) parts.push(`y ${range.y[0].toFixed(1)}${range.y[1].toFixed(1)}`);
250+
return `${label}: ${parts.join(", ")}`;
251+
}
252+
229253
// A single chart bound to the shared x-range ($page.range) and its own y-range.
230254
// `zoomY` decides whether the y-axis bindings are wired to the RangeSelector;
231255
// when they are omitted, the selector zooms the x-axis only.
232-
const ZoomChart = ({ lineStyle, yField, yRange, sel, zoomY }) => (
256+
const ZoomChart = ({ label, lineStyle, yField, yRange, sel, zoomY }) => (
233257
<cx>
234258
<Svg style="width:800px; height:260px;">
235259
<Chart
@@ -260,6 +284,7 @@ const ZoomChart = ({ lineStyle, yField, yRange, sel, zoomY }) => (
260284
y2={zoomY ? { bind: `${sel}.y2` } : undefined}
261285
yMin={zoomY ? { bind: `${yRange}.from` } : undefined}
262286
yMax={zoomY ? { bind: `${yRange}.to` } : undefined}
287+
onZoom={(range, inst) => inst.store.set("$page.status", describeZoom(range, label))}
263288
/>
264289
</Chart>
265290
</Svg>
@@ -282,13 +307,15 @@ export default (
282307
store.set("$page.range", null);
283308
store.set("$page.y1", null);
284309
store.set("$page.y2", null);
310+
store.set("$page.status", null);
285311
}}
286312
>
287313
Reset zoom
288314
</Button>
315+
<div style="color:#555; font-family:monospace" text-tpl="onZoom → {$page.status:s;—}" />
289316
</FlexRow>
290-
<ZoomChart lineStyle="stroke: #555; stroke-width: 1.5" yField="value" yRange="$page.y1" sel="$page.sel1" />
291-
<ZoomChart lineStyle="stroke: #888; stroke-width: 1.5" yField="value2" yRange="$page.y2" sel="$page.sel2" zoomY />
317+
<ZoomChart label="Top" lineStyle="stroke: #555; stroke-width: 1.5" yField="value" yRange="$page.y1" sel="$page.sel1" />
318+
<ZoomChart label="Bottom" lineStyle="stroke: #888; stroke-width: 1.5" yField="value2" yRange="$page.y2" sel="$page.sel2" zoomY />
292319
</div>
293320
</cx>
294321
);

0 commit comments

Comments
 (0)