Histogram: enable client-side zoom (with bucket snapping) (#35220)

This commit is contained in:
Leon Sorokin
2021-06-03 20:05:47 -05:00
committed by GitHub
parent e1af571efd
commit 43d3d97562
2 changed files with 55 additions and 26 deletions

View File

@@ -1,5 +1,5 @@
import uPlot, { Cursor, Band, Hooks, Select } from 'uplot'; import uPlot, { Cursor, Band, Hooks, Select } from 'uplot';
import { defaultsDeep } from 'lodash'; import { merge } from 'lodash';
import { import {
DataFrame, DataFrame,
DefaultTimeZone, DefaultTimeZone,
@@ -17,6 +17,24 @@ import { AxisPlacement } from '../config';
import { pluginLog } from '../utils'; import { pluginLog } from '../utils';
import { getThresholdsDrawHook, UPlotThresholdOptions } from './UPlotThresholds'; import { getThresholdsDrawHook, UPlotThresholdOptions } from './UPlotThresholds';
const cursorDefaults: Cursor = {
// prevent client-side zoom from triggering at the end of a selection
drag: { setScale: false },
points: {
/*@ts-ignore*/
size: (u, seriesIdx) => u.series[seriesIdx].points.size * 2,
/*@ts-ignore*/
width: (u, seriesIdx, size) => size / 4,
/*@ts-ignore*/
stroke: (u, seriesIdx) => u.series[seriesIdx].points.stroke(u, seriesIdx) + '80',
/*@ts-ignore*/
fill: (u, seriesIdx) => u.series[seriesIdx].points.stroke(u, seriesIdx),
},
focus: {
prox: 30,
},
};
export class UPlotConfigBuilder { export class UPlotConfigBuilder {
private series: UPlotSeriesBuilder[] = []; private series: UPlotSeriesBuilder[] = [];
private axes: Record<string, UPlotAxisBuilder> = {}; private axes: Record<string, UPlotAxisBuilder> = {};
@@ -98,7 +116,7 @@ export class UPlotConfigBuilder {
} }
setCursor(cursor?: Cursor) { setCursor(cursor?: Cursor) {
this.cursor = { ...this.cursor, ...cursor }; this.cursor = merge({}, this.cursor, cursor);
} }
setSelect(select: Select) { setSelect(select: Select) {
@@ -153,11 +171,9 @@ export class UPlotConfigBuilder {
config.hooks = this.hooks; config.hooks = this.hooks;
/* @ts-ignore */
// uPlot types don't export the Select interface prior to 1.6.4
config.select = this.select; config.select = this.select;
config.cursor = this.cursor || {}; config.cursor = merge({}, cursorDefaults, this.cursor);
config.tzDate = this.tzDate; config.tzDate = this.tzDate;
@@ -181,26 +197,6 @@ export class UPlotConfigBuilder {
} }
} }
const cursorDefaults: Cursor = {
// prevent client-side zoom from triggering at the end of a selection
drag: { setScale: false },
points: {
/*@ts-ignore*/
size: (u, seriesIdx) => u.series[seriesIdx].points.size * 2,
/*@ts-ignore*/
width: (u, seriesIdx, size) => size / 4,
/*@ts-ignore*/
stroke: (u, seriesIdx) => u.series[seriesIdx].points.stroke(u, seriesIdx) + '80',
/*@ts-ignore*/
fill: (u, seriesIdx) => u.series[seriesIdx].points.stroke(u, seriesIdx),
},
focus: {
prox: 30,
},
};
defaultsDeep(config.cursor, cursorDefaults);
return config; return config;
} }

View File

@@ -27,6 +27,14 @@ import {
import { PanelOptions } from './models.gen'; import { PanelOptions } from './models.gen';
import { ScaleDistribution } from '@grafana/ui/src/components/uPlot/models.gen'; import { ScaleDistribution } from '@grafana/ui/src/components/uPlot/models.gen';
function incrRoundDn(num: number, incr: number) {
return Math.floor(num / incr) * incr;
}
function incrRoundUp(num: number, incr: number) {
return Math.ceil(num / incr) * incr;
}
export interface HistogramProps extends Themeable2 { export interface HistogramProps extends Themeable2 {
options: PanelOptions; // used for diff options: PanelOptions; // used for diff
alignedFrame: DataFrame; // This could take HistogramFields alignedFrame: DataFrame; // This could take HistogramFields
@@ -70,7 +78,24 @@ const prepConfig = (frame: DataFrame, theme: GrafanaTheme2) => {
distribution: ScaleDistribution.Linear, distribution: ScaleDistribution.Linear,
orientation: ScaleOrientation.Horizontal, orientation: ScaleOrientation.Horizontal,
direction: ScaleDirection.Right, direction: ScaleDirection.Right,
range: (u) => [u.data[0][0], u.data[0][u.data[0].length - 1] + bucketSize], range: (u, wantedMin, wantedMax) => {
let fullRangeMin = u.data[0][0];
let fullRangeMax = u.data[0][u.data[0].length - 1];
// snap to bucket divisors...
if (wantedMax === fullRangeMax) {
wantedMax += bucketSize;
} else {
wantedMax = incrRoundUp(wantedMax, bucketSize);
}
if (wantedMin > fullRangeMin) {
wantedMin = incrRoundDn(wantedMin, bucketSize);
}
return [wantedMin, wantedMax];
},
}); });
builder.addScale({ builder.addScale({
@@ -114,6 +139,14 @@ const prepConfig = (frame: DataFrame, theme: GrafanaTheme2) => {
theme, theme,
}); });
builder.setCursor({
drag: {
x: true,
y: false,
setScale: true,
},
});
let pathBuilder = uPlot.paths.bars!({ align: 1, size: [1, Infinity] }); let pathBuilder = uPlot.paths.bars!({ align: 1, size: [1, Infinity] });
let seriesIndex = 0; let seriesIndex = 0;