TooltipPlugin2: Absorb ZoomPlugin (#78160)

This commit is contained in:
Leon Sorokin 2023-11-15 17:23:54 -06:00 committed by GitHub
parent 1ebdf390e6
commit 768fde02aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -23,6 +23,12 @@ export const enum TooltipHoverMode {
interface TooltipPlugin2Props { interface TooltipPlugin2Props {
config: UPlotConfigBuilder; config: UPlotConfigBuilder;
hoverMode: TooltipHoverMode; hoverMode: TooltipHoverMode;
// x only
queryZoom?: (range: { from: number; to: number }) => void;
// y-only, via shiftKey
clientZoom?: boolean;
render: ( render: (
u: uPlot, u: uPlot,
dataIdxs: Array<number | null>, dataIdxs: Array<number | null>,
@ -67,10 +73,15 @@ const INITIAL_STATE: TooltipContainerState = {
dismiss: () => {}, dismiss: () => {},
}; };
// min px width that triggers zoom
const MIN_ZOOM_DIST = 5;
const maybeZoomAction = (e?: MouseEvent | null) => e != null && !e.ctrlKey && !e.metaKey;
/** /**
* @alpha * @alpha
*/ */
export const TooltipPlugin2 = ({ config, hoverMode, render }: TooltipPlugin2Props) => { export const TooltipPlugin2 = ({ config, hoverMode, render, clientZoom = false, queryZoom }: TooltipPlugin2Props) => {
const domRef = useRef<HTMLDivElement>(null); const domRef = useRef<HTMLDivElement>(null);
const [{ plot, isHovering, isPinned, contents, style, dismiss }, setState] = useReducer(mergeState, INITIAL_STATE); const [{ plot, isHovering, isPinned, contents, style, dismiss }, setState] = useReducer(mergeState, INITIAL_STATE);
@ -101,6 +112,9 @@ export const TooltipPlugin2 = ({ config, hoverMode, render }: TooltipPlugin2Prop
}), }),
}; };
let yZoomed = false;
let yDrag = false;
let _plot = plot; let _plot = plot;
let _isHovering = isHovering; let _isHovering = isHovering;
let _isPinned = isPinned; let _isPinned = isPinned;
@ -195,6 +209,34 @@ export const TooltipPlugin2 = ({ config, hoverMode, render }: TooltipPlugin2Prop
config.addHook('init', (u) => { config.addHook('init', (u) => {
setState({ plot: (_plot = u) }); setState({ plot: (_plot = u) });
// detect shiftKey and mutate drag mode from x-only to y-only
if (clientZoom) {
u.over.addEventListener(
'mousedown',
(e) => {
if (!maybeZoomAction(e)) {
return;
}
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);
}
},
true
);
}
// this handles pinning // this handles pinning
u.over.addEventListener('click', (e) => { u.over.addEventListener('click', (e) => {
// only pinnable tooltip is visible *and* is within proximity to series/point // only pinnable tooltip is visible *and* is within proximity to series/point
@ -205,6 +247,79 @@ export const TooltipPlugin2 = ({ config, hoverMode, render }: TooltipPlugin2Prop
}); });
}); });
config.addHook('setSelect', (u) => {
if (clientZoom || queryZoom != null) {
if (maybeZoomAction(u.cursor!.event)) {
if (clientZoom && 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 (queryZoom != null) {
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');
queryZoom({ from: minX, to: maxX });
yZoomed = false;
}
}
}
}
// manually hide selected region (since cursor.drag.setScale = false)
u.setSelect({ left: 0, width: 0, top: 0, height: 0 }, false);
});
if (clientZoom || queryZoom != null) {
config.setCursor({
bind: {
dblclick: (u) => () => {
if (!maybeZoomAction(u.cursor!.event)) {
return null;
}
if (clientZoom && 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 });
}
}
yZoomed = false;
} else if (queryZoom != null) {
let xScale = u.scales.x;
const frTs = xScale.min!;
const toTs = xScale.max!;
const pad = (toTs - frTs) / 2;
queryZoom({ from: frTs - pad, to: toTs + pad });
}
return null;
},
},
});
}
config.addHook('setData', (u) => {
yZoomed = false;
yDrag = false;
});
// fires on data value hovers/unhovers (before setSeries) // fires on data value hovers/unhovers (before setSeries)
config.addHook('setLegend', (u) => { config.addHook('setLegend', (u) => {
let hoveredSeriesIdx = _plot!.cursor.idxs!.findIndex((v, i) => i > 0 && v != null); let hoveredSeriesIdx = _plot!.cursor.idxs!.findIndex((v, i) => i > 0 && v != null);