DataFrame: Add explicit histogram frame type (panel & transforms) (#61195)

This commit is contained in:
Leon Sorokin 2023-01-10 17:42:45 -06:00 committed by GitHub
parent 86b5fbbf60
commit be1c5e13d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 865 additions and 467 deletions

File diff suppressed because it is too large Load Diff

View File

@ -35,7 +35,7 @@ describe('histogram frames frames', () => {
"config": {
"unit": "mph",
},
"name": "BucketMin",
"name": "xMin",
"values": [
1,
2,
@ -52,7 +52,7 @@ describe('histogram frames frames', () => {
"config": {
"unit": "mph",
},
"name": "BucketMax",
"name": "xMax",
"values": [
2,
3,
@ -145,7 +145,7 @@ describe('histogram frames frames', () => {
).toMatchInlineSnapshot(`
[
{
"name": "BucketMin",
"name": "xMin",
"values": [
1,
2,
@ -159,7 +159,7 @@ describe('histogram frames frames', () => {
],
},
{
"name": "BucketMax",
"name": "xMax",
"values": [
2,
3,
@ -173,7 +173,7 @@ describe('histogram frames frames', () => {
],
},
{
"name": "Count",
"name": "count",
"values": [
1,
1,

View File

@ -2,8 +2,9 @@ import { map } from 'rxjs/operators';
import { getDisplayProcessor } from '../../field';
import { createTheme, GrafanaTheme2 } from '../../themes';
import { SynchronousDataTransformerInfo } from '../../types';
import { DataFrameType, SynchronousDataTransformerInfo } from '../../types';
import { DataFrame, Field, FieldConfig, FieldType } from '../../types/dataFrame';
import { roundDecimals } from '../../utils';
import { ArrayVector } from '../../vector/ArrayVector';
import { DataTransformerID } from './ids';
@ -100,19 +101,33 @@ export const histogramTransformer: SynchronousDataTransformerInfo<HistogramTrans
/**
* @internal
*/
export const histogramFrameBucketMinFieldName = 'BucketMin';
export const histogramFrameBucketMinFieldName = 'xMin';
/**
* @internal
*/
export const histogramFrameBucketMaxFieldName = 'BucketMax';
export function isHistogramFrameBucketMinFieldName(v: string) {
return v === histogramFrameBucketMinFieldName || v === 'BucketMin'; // REMOVE 'BuckentMin/Max'
}
/**
* @internal
*/
export const histogramFrameBucketMaxFieldName = 'xMax';
/**
* @internal
*/
export function isHistogramFrameBucketMaxFieldName(v: string) {
return v === histogramFrameBucketMaxFieldName || v === 'BucketMax'; // REMOVE 'BuckentMin/Max'
}
/**
* @alpha
*/
export interface HistogramFields {
bucketMin: Field;
bucketMax: Field;
xMin: Field;
xMax: Field;
counts: Field[]; // frequency
}
@ -122,22 +137,46 @@ export interface HistogramFields {
* @alpha
*/
export function getHistogramFields(frame: DataFrame): HistogramFields | undefined {
let bucketMin: Field | undefined = undefined;
let bucketMax: Field | undefined = undefined;
let xMin: Field | undefined = undefined;
let xMax: Field | undefined = undefined;
const counts: Field[] = [];
for (const field of frame.fields) {
if (field.name === histogramFrameBucketMinFieldName) {
bucketMin = field;
} else if (field.name === histogramFrameBucketMaxFieldName) {
bucketMax = field;
if (isHistogramFrameBucketMinFieldName(field.name)) {
xMin = field;
} else if (isHistogramFrameBucketMaxFieldName(field.name)) {
xMax = field;
} else if (field.type === FieldType.number) {
counts.push(field);
}
}
if (bucketMin && bucketMax && counts.length) {
// guess bucket size from single explicit bucket field
if (!xMax && xMin && xMin.values.length > 1) {
let vals = xMin.values.toArray();
let bucketSize = roundDecimals(vals[1] - vals[0], 6);
xMax = {
...xMin,
name: histogramFrameBucketMaxFieldName,
values: new ArrayVector(vals.map((v) => v + bucketSize)),
};
}
if (!xMin && xMax && xMax?.values.length > 1) {
let vals = xMax.values.toArray();
let bucketSize = roundDecimals(vals[1] - vals[0], 6);
xMin = {
...xMax,
name: histogramFrameBucketMinFieldName,
values: new ArrayVector(vals.map((v) => v - bucketSize)),
};
}
if (xMin && xMax && counts.length) {
return {
bucketMin,
bucketMax,
xMin,
xMax,
counts,
};
}
@ -250,7 +289,7 @@ export function buildHistogram(frames: DataFrame[], options?: HistogramTransform
}
}
const bucketMin: Field = {
const xMin: Field = {
name: histogramFrameBucketMinFieldName,
values: new ArrayVector(joinedHists[0]),
type: FieldType.number,
@ -263,8 +302,8 @@ export function buildHistogram(frames: DataFrame[], options?: HistogramTransform
decimals: bucketDecimals,
},
};
const bucketMax = {
...bucketMin,
const xMax = {
...xMin,
name: histogramFrameBucketMaxFieldName,
values: new ArrayVector(joinedHists[0].map((v) => v + bucketSize!)),
};
@ -279,7 +318,7 @@ export function buildHistogram(frames: DataFrame[], options?: HistogramTransform
counts = [
{
...counts[0],
name: 'Count',
name: 'count',
values: new ArrayVector(vals),
type: FieldType.number,
state: undefined,
@ -292,8 +331,8 @@ export function buildHistogram(frames: DataFrame[], options?: HistogramTransform
}
return {
bucketMin,
bucketMax,
xMin,
xMax,
counts,
};
}
@ -364,13 +403,13 @@ function histogram(
* @internal
*/
export function histogramFieldsToFrame(info: HistogramFields, theme?: GrafanaTheme2): DataFrame {
if (!info.bucketMin.display) {
if (!info.xMin.display) {
const display = getDisplayProcessor({
field: info.bucketMin,
field: info.xMin,
theme: theme ?? createTheme(),
});
info.bucketMin.display = display;
info.bucketMax.display = display;
info.xMin.display = display;
info.xMax.display = display;
}
// ensure updated units are reflected on the count field used for y axis formatting
@ -380,7 +419,10 @@ export function histogramFieldsToFrame(info: HistogramFields, theme?: GrafanaThe
});
return {
fields: [info.bucketMin, info.bucketMax, ...info.counts],
length: info.bucketMin.values.length,
length: info.xMin.values.length,
meta: {
type: DataFrameType.Histogram,
},
fields: [info.xMin, info.xMax, ...info.counts],
};
}

View File

@ -30,4 +30,10 @@ export enum DataFrameType {
* If the y value is actually ordinal, use `meta.custom` to specify the bucket lookup values
*/
HeatmapCells = 'heatmap-cells',
/**
* Explicit fields for:
* xMin, xMax, count
*/
Histogram = 'histogram',
}

View File

@ -175,7 +175,7 @@ const prepConfig = (frame: DataFrame, theme: GrafanaTheme2) => {
let seriesIndex = 0;
// assumes BucketMax is [1]
// assumes xMin is [0], xMax is [1]
for (let i = 2; i < frame.fields.length; i++) {
const field = frame.fields[i];
@ -209,10 +209,7 @@ const prepConfig = (frame: DataFrame, theme: GrafanaTheme2) => {
softMax: customConfig.axisSoftMax,
// The following properties are not used in the uPlot config, but are utilized as transport for legend config
dataFrameFieldIndex: {
fieldIndex: 1,
frameIndex: i - 2,
},
dataFrameFieldIndex: field.state.origin,
});
}
@ -272,11 +269,12 @@ export class Histogram extends React.Component<HistogramProps, State> {
renderLegend(config: UPlotConfigBuilder) {
const { legend } = this.props;
if (!config || legend.showLegend === false || !this.props.rawSeries) {
if (!config || legend.showLegend === false) {
return null;
}
return <PlotLegend data={this.props.rawSeries} config={config} maxHeight="35%" maxWidth="60%" {...legend} />;
return <PlotLegend data={this.props.rawSeries!} config={config} maxHeight="35%" maxWidth="60%" {...legend} />;
}
componentDidUpdate(prevProps: HistogramProps) {

View File

@ -16,6 +16,20 @@ export const HistogramPanel = ({ data, options, width, height }: Props) => {
if (!data?.series?.length) {
return undefined;
}
// stamp origins for legend's calcs (from raw values)
data.series.forEach((frame, frameIndex) => {
frame.fields.forEach((field, fieldIndex) => {
field.state = {
...field.state,
origin: {
frameIndex,
fieldIndex,
},
};
});
});
if (data.series.length === 1) {
const info = getHistogramFields(data.series[0]);
if (info) {

View File

@ -1,7 +1,7 @@
import { DataFrame, FieldType } from '@grafana/data';
import {
histogramFrameBucketMinFieldName,
histogramFrameBucketMaxFieldName,
isHistogramFrameBucketMinFieldName,
isHistogramFrameBucketMaxFieldName,
} from '@grafana/data/src/transformations/transformers/histogram';
export function originalDataHasHistogram(frames?: DataFrame[]): boolean {
@ -14,8 +14,8 @@ export function originalDataHasHistogram(frames?: DataFrame[]): boolean {
}
if (
frame.fields[0].name !== histogramFrameBucketMinFieldName ||
frame.fields[1].name !== histogramFrameBucketMaxFieldName
!isHistogramFrameBucketMinFieldName(frame.fields[0].name) ||
!isHistogramFrameBucketMaxFieldName(frame.fields[1].name)
) {
return false;
}