From 36925ef5216e0de7f2c09c4f9540442095e0bf5e Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Fri, 4 Jun 2021 20:15:08 -0500 Subject: [PATCH] Histogram: reduce default bucket size, fix rendering (#35294) --- .../transformations/transformers/histogram.ts | 72 ++++++++++++++----- packages/grafana-ui/package.json | 2 +- .../app/plugins/panel/histogram/Histogram.tsx | 11 ++- .../panel/histogram/HistogramPanel.tsx | 5 +- yarn.lock | 8 +-- 5 files changed, 71 insertions(+), 27 deletions(-) diff --git a/packages/grafana-data/src/transformations/transformers/histogram.ts b/packages/grafana-data/src/transformations/transformers/histogram.ts index 04379474040..a28aeaf88f9 100644 --- a/packages/grafana-data/src/transformations/transformers/histogram.ts +++ b/packages/grafana-data/src/transformations/transformers/histogram.ts @@ -8,19 +8,31 @@ import { AlignedData, join } from './joinDataFrames'; import { getDisplayProcessor } from '../../field'; import { createTheme, GrafanaTheme2 } from '../../themes'; -/* eslint-disable */ -// prettier-ignore /** * @internal */ +/* eslint-disable */ +// prettier-ignore export const histogramBucketSizes = [ - .001, .002, .0025, .005, - .01, .02, .025, .05, - .1, .2, .25, .5, - 1, 2, 4, 5, - 10, 20, 25, 50, - 100, 200, 250, 500, - 1000, 2000, 2500, 5000, + 1e-9, 2e-9, 2.5e-9, 4e-9, 5e-9, + 1e-8, 2e-8, 2.5e-8, 4e-8, 5e-8, + 1e-7, 2e-7, 2.5e-7, 4e-7, 5e-7, + 1e-6, 2e-6, 2.5e-6, 4e-6, 5e-6, + 1e-5, 2e-5, 2.5e-5, 4e-5, 5e-5, + 1e-4, 2e-4, 2.5e-4, 4e-4, 5e-4, + 1e-3, 2e-3, 2.5e-3, 4e-3, 5e-3, + 1e-2, 2e-2, 2.5e-2, 4e-2, 5e-2, + 1e-1, 2e-1, 2.5e-1, 4e-1, 5e-1, + 1, 2, 4, 5, + 1e+1, 2e+1, 2.5e+1, 4e+1, 5e+1, + 1e+2, 2e+2, 2.5e+2, 4e+2, 5e+2, + 1e+3, 2e+3, 2.5e+3, 4e+3, 5e+3, + 1e+4, 2e+4, 2.5e+4, 4e+4, 5e+4, + 1e+5, 2e+5, 2.5e+5, 4e+5, 5e+5, + 1e+6, 2e+6, 2.5e+6, 4e+6, 5e+6, + 1e+7, 2e+7, 2.5e+7, 4e+7, 5e+7, + 1e+8, 2e+8, 2.5e+8, 4e+8, 5e+8, + 1e+9, 2e+9, 2.5e+9, 4e+9, 5e+9, ]; /* eslint-enable */ @@ -135,6 +147,8 @@ export function getHistogramFields(frame: DataFrame): HistogramFields | undefine return undefined; } +const APPROX_BUCKETS = 20; + /** * @alpha */ @@ -144,27 +158,47 @@ export function buildHistogram(frames: DataFrame[], options?: HistogramTransform // if bucket size is auto, try to calc from all numeric fields if (!bucketSize) { - let min = Infinity, - max = -Infinity; + let allValues: number[] = []; // TODO: include field configs! for (const frame of frames) { for (const field of frame.fields) { if (field.type === FieldType.number) { - for (const value of field.values.toArray()) { - min = Math.min(min, value); - max = Math.max(max, value); - } + allValues = allValues.concat(field.values.toArray()); } } } - let range = Math.abs(max - min); + allValues.sort((a, b) => a - b); + + let smallestDelta = Infinity; + + // TODO: case of 1 value needs work + if (allValues.length === 1) { + smallestDelta = 1; + } else { + for (let i = 1; i < allValues.length; i++) { + let delta = allValues[i] - allValues[i - 1]; + + if (delta !== 0) { + smallestDelta = Math.min(smallestDelta, delta); + } + } + } + + let min = allValues[0]; + let max = allValues[allValues.length - 1]; + + let range = max - min; + + const targetSize = range / APPROX_BUCKETS; // choose bucket - for (const size of histogramBucketSizes) { - if (range / 10 < size) { - bucketSize = size; + for (let i = 0; i < histogramBucketSizes.length; i++) { + let _bucketSize = histogramBucketSizes[i]; + + if (targetSize < _bucketSize && _bucketSize >= smallestDelta) { + bucketSize = _bucketSize; break; } } diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 05215d2c450..2641f3451c2 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -72,7 +72,7 @@ "react-transition-group": "4.4.1", "slate": "0.47.8", "tinycolor2": "1.4.1", - "uplot": "1.6.11" + "uplot": "1.6.12" }, "devDependencies": { "@rollup/plugin-commonjs": "16.0.0", diff --git a/public/app/plugins/panel/histogram/Histogram.tsx b/public/app/plugins/panel/histogram/Histogram.tsx index 2895301c217..8f3d24decec 100644 --- a/public/app/plugins/panel/histogram/Histogram.tsx +++ b/public/app/plugins/panel/histogram/Histogram.tsx @@ -38,6 +38,7 @@ function incrRoundUp(num: number, incr: number) { export interface HistogramProps extends Themeable2 { options: PanelOptions; // used for diff alignedFrame: DataFrame; // This could take HistogramFields + bucketSize: number; width: number; height: number; structureRev?: number; // a number that will change when the frames[] structure changes @@ -45,13 +46,18 @@ export interface HistogramProps extends Themeable2 { children?: (builder: UPlotConfigBuilder, frame: DataFrame) => React.ReactNode; } +export function getBucketSize(frame: DataFrame) { + // assumes BucketMin is fields[0] and BucktMax is fields[1] + return frame.fields[1].values.get(0) - frame.fields[0].values.get(0); +} + const prepConfig = (frame: DataFrame, theme: GrafanaTheme2) => { // todo: scan all values in BucketMin and BucketMax fields to assert if uniform bucketSize let builder = new UPlotConfigBuilder(); // assumes BucketMin is fields[0] and BucktMax is fields[1] - let bucketSize = frame.fields[1].values.get(0) - frame.fields[0].values.get(0); + let bucketSize = getBucketSize(frame); // splits shifter, to ensure splits always start at first bucket let xSplits: uPlot.Axis.Splits = (u, axisIdx, scaleMin, scaleMax, foundIncr, foundSpace) => { @@ -251,13 +257,14 @@ export class Histogram extends React.Component { } componentDidUpdate(prevProps: HistogramProps) { - const { structureRev, alignedFrame } = this.props; + const { structureRev, alignedFrame, bucketSize } = this.props; if (alignedFrame !== prevProps.alignedFrame) { let newState = this.prepState(this.props, false); if (newState) { const shouldReconfig = + bucketSize !== prevProps.bucketSize || this.props.options !== prevProps.options || this.state.config === undefined || structureRev !== prevProps.structureRev || diff --git a/public/app/plugins/panel/histogram/HistogramPanel.tsx b/public/app/plugins/panel/histogram/HistogramPanel.tsx index 4ed32e7b813..71b5170dc15 100644 --- a/public/app/plugins/panel/histogram/HistogramPanel.tsx +++ b/public/app/plugins/panel/histogram/HistogramPanel.tsx @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; import { PanelProps, buildHistogram, getHistogramFields } from '@grafana/data'; -import { Histogram } from './Histogram'; +import { Histogram, getBucketSize } from './Histogram'; import { PanelOptions } from './models.gen'; import { useTheme2 } from '@grafana/ui'; @@ -38,6 +38,8 @@ export const HistogramPanel: React.FC = ({ data, options, width, height } ); } + const bucketSize = getBucketSize(histogram); + return ( = ({ data, options, width, height } width={width} height={height} alignedFrame={histogram} + bucketSize={bucketSize} > {(config, alignedFrame) => { return null; // ; diff --git a/yarn.lock b/yarn.lock index b416f0a69c3..4c4f02dfe08 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22043,10 +22043,10 @@ update-notifier@^2.5.0: semver-diff "^2.0.0" xdg-basedir "^3.0.0" -uplot@1.6.11: - version "1.6.11" - resolved "https://registry.yarnpkg.com/uplot/-/uplot-1.6.11.tgz#2ade33071e4eb30c6504ad7c68eb4c05d9dd47eb" - integrity sha512-DTA/wLLFSccSghZKM02hLgHoe8zDoCGFEkbbszXwkNRFgkkRKYc0gPNaJG76JFrwDlZNc7+u9noUHxt9fLu+lg== +uplot@1.6.12: + version "1.6.12" + resolved "https://registry.yarnpkg.com/uplot/-/uplot-1.6.12.tgz#a2741f224c3d3b4f4f25a53797c1687ddfef2989" + integrity sha512-afBjwy/9SM0E7w29Gen+wQ+UAe6ipiA2zE/s1dMCidMXQYvYkCpEgxLRkVA179/8eRJz+og2r7ZFS1HIzT75fQ== upper-case@^1.1.1: version "1.1.3"