From 32ed3e8009761cba74e36177a71077d22f567426 Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Tue, 26 Sep 2023 18:12:45 -0500 Subject: [PATCH] TimeSeries: Implement ad hoc y-zoom via Shift-drag (#75408) --- .../components/uPlot/plugins/ZoomPlugin.tsx | 91 ++++++++++++++----- .../panel/candlestick/CandlestickPanel.tsx | 2 +- .../panel/timeseries/TimeSeriesPanel.tsx | 2 +- 3 files changed, 70 insertions(+), 25 deletions(-) diff --git a/packages/grafana-ui/src/components/uPlot/plugins/ZoomPlugin.tsx b/packages/grafana-ui/src/components/uPlot/plugins/ZoomPlugin.tsx index d0697334379..415d000d303 100644 --- a/packages/grafana-ui/src/components/uPlot/plugins/ZoomPlugin.tsx +++ b/packages/grafana-ui/src/components/uPlot/plugins/ZoomPlugin.tsx @@ -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; }, diff --git a/public/app/plugins/panel/candlestick/CandlestickPanel.tsx b/public/app/plugins/panel/candlestick/CandlestickPanel.tsx index 2611c40d823..e0707a3820f 100644 --- a/public/app/plugins/panel/candlestick/CandlestickPanel.tsx +++ b/public/app/plugins/panel/candlestick/CandlestickPanel.tsx @@ -254,7 +254,7 @@ export const CandlestickPanel = ({ return ( <> - + - + {options.tooltip.mode === TooltipDisplayMode.None || (