(cherry picked from commit 4c3e197e26)
Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
co-authored by
Leon Sorokin
parent
a8f5445d47
commit
0a08cf10e5
@@ -69,6 +69,7 @@ Object {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
"cursor": Object {
|
"cursor": Object {
|
||||||
|
"dataIdx": [Function],
|
||||||
"drag": Object {
|
"drag": Object {
|
||||||
"setScale": false,
|
"setScale": false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ function applySpanNullsThresholds(frame: DataFrame) {
|
|||||||
let spanNulls = field.config.custom?.spanNulls;
|
let spanNulls = field.config.custom?.spanNulls;
|
||||||
|
|
||||||
if (typeof spanNulls === 'number') {
|
if (typeof spanNulls === 'number') {
|
||||||
field.values = new ArrayVector(nullToUndefThreshold(refValues, field.values.toArray(), spanNulls));
|
if (spanNulls !== -1) {
|
||||||
|
field.values = new ArrayVector(nullToUndefThreshold(refValues, field.values.toArray(), spanNulls));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -262,6 +262,58 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{ sync: DashboardCursor
|
|||||||
|
|
||||||
builder.scaleKeys = [xScaleKey, yScaleKey];
|
builder.scaleKeys = [xScaleKey, yScaleKey];
|
||||||
|
|
||||||
|
// if hovered value is null, how far we may scan left/right to hover nearest non-null
|
||||||
|
const hoverProximityPx = 15;
|
||||||
|
|
||||||
|
let cursor: Partial<uPlot.Cursor> = {
|
||||||
|
// this scans left and right from cursor position to find nearest data index with value != null
|
||||||
|
// TODO: do we want to only scan past undefined values, but halt at explicit null values?
|
||||||
|
dataIdx: (self, seriesIdx, hoveredIdx, cursorXVal) => {
|
||||||
|
let seriesData = self.data[seriesIdx];
|
||||||
|
|
||||||
|
if (seriesData[hoveredIdx] == null) {
|
||||||
|
let nonNullLft = hoveredIdx,
|
||||||
|
nonNullRgt = hoveredIdx,
|
||||||
|
i;
|
||||||
|
|
||||||
|
i = hoveredIdx;
|
||||||
|
while (nonNullLft === hoveredIdx && i-- > 0) {
|
||||||
|
if (seriesData[i] != null) {
|
||||||
|
nonNullLft = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i = hoveredIdx;
|
||||||
|
while (nonNullRgt === hoveredIdx && i++ < seriesData.length) {
|
||||||
|
if (seriesData[i] != null) {
|
||||||
|
nonNullRgt = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let xVals = self.data[0];
|
||||||
|
|
||||||
|
let curPos = self.valToPos(cursorXVal, 'x');
|
||||||
|
let rgtPos = self.valToPos(xVals[nonNullRgt], 'x');
|
||||||
|
let lftPos = self.valToPos(xVals[nonNullLft], 'x');
|
||||||
|
|
||||||
|
let lftDelta = curPos - lftPos;
|
||||||
|
let rgtDelta = rgtPos - curPos;
|
||||||
|
|
||||||
|
if (lftDelta <= rgtDelta) {
|
||||||
|
if (lftDelta <= hoverProximityPx) {
|
||||||
|
hoveredIdx = nonNullLft;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (rgtDelta <= hoverProximityPx) {
|
||||||
|
hoveredIdx = nonNullRgt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hoveredIdx;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
if (sync !== DashboardCursorSync.Off) {
|
if (sync !== DashboardCursorSync.Off) {
|
||||||
const payload: DataHoverPayload = {
|
const payload: DataHoverPayload = {
|
||||||
point: {
|
point: {
|
||||||
@@ -271,34 +323,34 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{ sync: DashboardCursor
|
|||||||
data: frame,
|
data: frame,
|
||||||
};
|
};
|
||||||
const hoverEvent = new DataHoverEvent(payload);
|
const hoverEvent = new DataHoverEvent(payload);
|
||||||
builder.setSync();
|
cursor.sync = {
|
||||||
builder.setCursor({
|
key: '__global_',
|
||||||
sync: {
|
filters: {
|
||||||
key: '__global_',
|
pub: (type: string, src: uPlot, x: number, y: number, w: number, h: number, dataIdx: number) => {
|
||||||
filters: {
|
payload.columnIndex = dataIdx;
|
||||||
pub: (type: string, src: uPlot, x: number, y: number, w: number, h: number, dataIdx: number) => {
|
if (x < 0 && y < 0) {
|
||||||
payload.columnIndex = dataIdx;
|
payload.point[xScaleUnit] = null;
|
||||||
if (x < 0 && y < 0) {
|
payload.point[yScaleKey] = null;
|
||||||
payload.point[xScaleUnit] = null;
|
eventBus.publish(new DataHoverClearEvent(payload));
|
||||||
payload.point[yScaleKey] = null;
|
} else {
|
||||||
eventBus.publish(new DataHoverClearEvent(payload));
|
// convert the points
|
||||||
} else {
|
payload.point[xScaleUnit] = src.posToVal(x, xScaleKey);
|
||||||
// convert the points
|
payload.point[yScaleKey] = src.posToVal(y, yScaleKey);
|
||||||
payload.point[xScaleUnit] = src.posToVal(x, xScaleKey);
|
eventBus.publish(hoverEvent);
|
||||||
payload.point[yScaleKey] = src.posToVal(y, yScaleKey);
|
hoverEvent.payload.down = undefined;
|
||||||
eventBus.publish(hoverEvent);
|
}
|
||||||
hoverEvent.payload.down = undefined;
|
return true;
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
// ??? setSeries: syncMode === DashboardCursorSync.Tooltip,
|
|
||||||
scales: builder.scaleKeys,
|
|
||||||
match: [() => true, () => true],
|
|
||||||
},
|
},
|
||||||
});
|
// ??? setSeries: syncMode === DashboardCursorSync.Tooltip,
|
||||||
|
scales: builder.scaleKeys,
|
||||||
|
match: [() => true, () => true],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
builder.setSync();
|
||||||
|
builder.setCursor(cursor);
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
|||||||
const plotCtx = usePlotContext();
|
const plotCtx = usePlotContext();
|
||||||
const [focusedSeriesIdx, setFocusedSeriesIdx] = useState<number | null>(null);
|
const [focusedSeriesIdx, setFocusedSeriesIdx] = useState<number | null>(null);
|
||||||
const [focusedPointIdx, setFocusedPointIdx] = useState<number | null>(null);
|
const [focusedPointIdx, setFocusedPointIdx] = useState<number | null>(null);
|
||||||
|
const [focusedPointIdxs, setFocusedPointIdxs] = useState<Array<number | null>>([]);
|
||||||
const [coords, setCoords] = useState<CartesianCoords2D | null>(null);
|
const [coords, setCoords] = useState<CartesianCoords2D | null>(null);
|
||||||
const plotInstance = plotCtx.plot;
|
const plotInstance = plotCtx.plot;
|
||||||
|
|
||||||
@@ -93,10 +94,13 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
|||||||
})(u);
|
})(u);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
config.addHook('setLegend', (u) => {
|
||||||
|
setFocusedPointIdx(u.cursor.idx!);
|
||||||
|
setFocusedPointIdxs(u.cursor.idxs!.slice());
|
||||||
|
});
|
||||||
|
|
||||||
// default series/datapoint idx retireval
|
// default series/datapoint idx retireval
|
||||||
config.addHook('setCursor', (u) => {
|
config.addHook('setCursor', (u) => {
|
||||||
setFocusedPointIdx(u.cursor.idx === undefined ? u.posToIdx(u.cursor.left || 0) : u.cursor.idx);
|
|
||||||
|
|
||||||
const bbox = plotCtx.getCanvasBoundingBox();
|
const bbox = plotCtx.getCanvasBoundingBox();
|
||||||
if (!bbox) {
|
if (!bbox) {
|
||||||
return;
|
return;
|
||||||
@@ -174,7 +178,7 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const display = field.display!(otherProps.data.fields[i].values.get(focusedPointIdx));
|
const display = field.display!(otherProps.data.fields[i].values.get(focusedPointIdxs[i]!));
|
||||||
|
|
||||||
series.push({
|
series.push({
|
||||||
color: display.color || FALLBACK_COLOR,
|
color: display.color || FALLBACK_COLOR,
|
||||||
|
|||||||
Reference in New Issue
Block a user