TimeSeries: Implement ad hoc y-zoom via Shift-drag (#75408)

This commit is contained in:
Leon Sorokin 2023-09-26 18:12:45 -05:00 committed by GitHub
parent bb9e66e671
commit 32ed3e8009
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 70 additions and 25 deletions

View File

@ -1,10 +1,10 @@
import { useLayoutEffect } from 'react';
import { UPlotConfigBuilder } from '../config/UPlotConfigBuilder';
import { pluginLog } from '../utils';
interface ZoomPluginProps {
onZoom: (range: { from: number; to: number }) => void;
withZoomY?: boolean;
config: UPlotConfigBuilder;
}
@ -14,41 +14,86 @@ const MIN_ZOOM_DIST = 5;
/**
* @alpha
*/
export const ZoomPlugin = ({ onZoom, config }: ZoomPluginProps) => {
export const ZoomPlugin = ({ onZoom, config, withZoomY = false }: ZoomPluginProps) => {
useLayoutEffect(() => {
config.addHook('setSelect', (u) => {
const min = u.posToVal(u.select.left, 'x');
const max = u.posToVal(u.select.left + u.select.width, 'x');
let yZoomed = false;
let yDrag = false;
if (u.select.width >= MIN_ZOOM_DIST) {
pluginLog('ZoomPlugin', false, 'selected', {
min,
max,
bbox: {
left: u.bbox.left / window.devicePixelRatio + u.select.left,
top: u.bbox.top / window.devicePixelRatio,
height: u.bbox.height / window.devicePixelRatio,
width: u.select.width,
if (withZoomY) {
config.addHook('init', (u) => {
u.root!.addEventListener(
'mousedown',
(e) => {
if (e.button === 0 && e.shiftKey) {
yDrag = true;
u.cursor!.drag!.x = false;
u.cursor!.drag!.y = true;
let onUp = (e: MouseEvent) => {
u.cursor!.drag!.x = true;
u.cursor!.drag!.y = false;
document.removeEventListener('mouseup', onUp, true);
};
document.addEventListener('mouseup', onUp, true);
}
},
});
onZoom({ from: min, to: max });
true
);
});
}
config.addHook('setSelect', (u) => {
if (withZoomY && yDrag) {
if (u.select.height >= MIN_ZOOM_DIST) {
for (let key in u.scales!) {
if (key !== 'x') {
const maxY = u.posToVal(u.select.top, key);
const minY = u.posToVal(u.select.top + u.select.height, key);
u.setScale(key, { min: minY, max: maxY });
}
}
yZoomed = true;
}
yDrag = false;
} else {
if (u.select.width >= MIN_ZOOM_DIST) {
const minX = u.posToVal(u.select.left, 'x');
const maxX = u.posToVal(u.select.left + u.select.width, 'x');
onZoom({ from: minX, to: maxX });
}
}
// manually hide selected region (since cursor.drag.setScale = false)
/* @ts-ignore */
u.setSelect({ left: 0, width: 0 }, false);
u.setSelect({ left: 0, width: 0, top: 0, height: 0 }, false);
});
config.setCursor({
bind: {
dblclick: (u) => () => {
let xScale = u.scales.x;
if (withZoomY && yZoomed) {
for (let key in u.scales!) {
if (key !== 'x') {
// @ts-ignore (this is not typed correctly in uPlot, assigning nulls means auto-scale / reset)
u.setScale(key, { min: null, max: null });
}
}
const frTs = xScale.min!;
const toTs = xScale.max!;
const pad = (toTs - frTs) / 2;
yZoomed = false;
} else {
let xScale = u.scales.x;
onZoom({ from: frTs - pad, to: toTs + pad });
const frTs = xScale.min!;
const toTs = xScale.max!;
const pad = (toTs - frTs) / 2;
onZoom({ from: frTs - pad, to: toTs + pad });
}
return null;
},

View File

@ -254,7 +254,7 @@ export const CandlestickPanel = ({
return (
<>
<ZoomPlugin config={config} onZoom={onChangeTimeRange} />
<ZoomPlugin config={config} onZoom={onChangeTimeRange} withZoomY={true} />
<TooltipPlugin
data={alignedDataFrame}
config={config}

View File

@ -87,7 +87,7 @@ export const TimeSeriesPanel = ({
return (
<>
<KeyboardPlugin config={config} />
<ZoomPlugin config={config} onZoom={onChangeTimeRange} />
<ZoomPlugin config={config} onZoom={onChangeTimeRange} withZoomY={true} />
{options.tooltip.mode === TooltipDisplayMode.None || (
<TooltipPlugin
frames={frames}