GraphNG: stack by % (#37127)

This commit is contained in:
Leon Sorokin
2021-07-28 20:31:07 -05:00
committed by GitHub
parent 4fa17c8040
commit 8b80d2256d
14 changed files with 121 additions and 30 deletions

View File

@@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import { PanelProps, TimeRange, VizOrientation } from '@grafana/data';
import { StackingMode, TooltipDisplayMode, TooltipPlugin } from '@grafana/ui';
import { StackingMode, TooltipDisplayMode, TooltipPlugin, useTheme2 } from '@grafana/ui';
import { BarChartOptions } from './types';
import { BarChart } from './BarChart';
import { prepareGraphableFrames } from './utils';
@@ -11,8 +11,11 @@ interface Props extends PanelProps<BarChartOptions> {}
* @alpha
*/
export const BarChartPanel: React.FunctionComponent<Props> = ({ data, options, width, height, timeZone }) => {
const { frames, warn } = useMemo(() => prepareGraphableFrames(data?.series, options.stacking), [
const theme = useTheme2();
const { frames, warn } = useMemo(() => prepareGraphableFrames(data?.series, theme, options.stacking), [
data,
theme,
options.stacking,
]);
const orientation = useMemo(() => {

View File

@@ -1,7 +1,7 @@
import uPlot, { Axis } from 'uplot';
import uPlot, { Axis, AlignedData } from 'uplot';
import { pointWithin, Quadtree, Rect } from './quadtree';
import { distribute, SPACE_BETWEEN } from './distribute';
import { GrafanaTheme2 } from '@grafana/data';
import { DataFrame, GrafanaTheme2 } from '@grafana/data';
import {
calculateFontSize,
PlotTooltipInterpolator,
@@ -11,6 +11,7 @@ import {
ScaleDirection,
ScaleOrientation,
} from '@grafana/ui';
import { preparePlotData } from '../../../../../packages/grafana-ui/src/components/uPlot/utils';
const groupDistr = SPACE_BETWEEN;
const barDistr = SPACE_BETWEEN;
@@ -52,10 +53,17 @@ export interface BarsOptions {
* @internal
*/
export function getConfig(opts: BarsOptions, theme: GrafanaTheme2) {
const { xOri, xDir: dir, groupWidth, barWidth, rawValue, formatValue, showValue } = opts;
const { xOri, xDir: dir, rawValue, formatValue, showValue } = opts;
const isXHorizontal = xOri === ScaleOrientation.Horizontal;
const hasAutoValueSize = !Boolean(opts.text?.valueSize);
const isStacked = opts.stacking !== StackingMode.None;
const pctStacked = opts.stacking === StackingMode.Percent;
let { groupWidth, barWidth } = opts;
if (isStacked) {
[groupWidth, barWidth] = [barWidth, groupWidth];
}
let qt: Quadtree;
let hovered: Rect | undefined = undefined;
@@ -200,7 +208,7 @@ export function getConfig(opts: BarsOptions, theme: GrafanaTheme2) {
let labelOffset = LABEL_OFFSET_MAX;
barRects.forEach((r, i) => {
texts[i] = formatValue(r.sidx, rawValue(r.sidx, r.didx));
texts[i] = formatValue(r.sidx, rawValue(r.sidx, r.didx)! / (pctStacked ? alignedTotals![r.sidx][r.didx]! : 1));
labelOffset = Math.min(labelOffset, Math.round(LABEL_OFFSET_FACTOR * (isXHorizontal ? r.w : r.h)));
});
@@ -302,6 +310,16 @@ export function getConfig(opts: BarsOptions, theme: GrafanaTheme2) {
};
};
let alignedTotals: AlignedData | null = null;
function prepData(alignedFrame: DataFrame) {
alignedTotals = null;
return preparePlotData(alignedFrame, ({ totals }) => {
alignedTotals = totals;
});
}
return {
cursor: {
x: false,
@@ -318,5 +336,6 @@ export function getConfig(opts: BarsOptions, theme: GrafanaTheme2) {
drawClear,
draw,
interpolateTooltip,
prepData,
};
}

View File

@@ -143,7 +143,7 @@ describe('BarChart utils', () => {
describe('prepareGraphableFrames', () => {
it('will warn when there is no data in the response', () => {
const result = prepareGraphableFrames([], StackingMode.None);
const result = prepareGraphableFrames([], createTheme(), StackingMode.None);
expect(result.warn).toEqual('No data in response');
});
@@ -154,7 +154,7 @@ describe('BarChart utils', () => {
{ name: 'value', values: [1, 2, 3, 4, 5] },
],
});
const result = prepareGraphableFrames([df], StackingMode.None);
const result = prepareGraphableFrames([df], createTheme(), StackingMode.None);
expect(result.warn).toEqual('Bar charts requires a string field');
expect(result.frames).toBeUndefined();
});
@@ -166,7 +166,7 @@ describe('BarChart utils', () => {
{ name: 'value', type: FieldType.boolean, values: [true, true, true, true, true] },
],
});
const result = prepareGraphableFrames([df], StackingMode.None);
const result = prepareGraphableFrames([df], createTheme(), StackingMode.None);
expect(result.warn).toEqual('No numeric fields found');
expect(result.frames).toBeUndefined();
});
@@ -178,7 +178,7 @@ describe('BarChart utils', () => {
{ name: 'value', values: [-10, NaN, 10, -Infinity, +Infinity] },
],
});
const result = prepareGraphableFrames([df], StackingMode.None);
const result = prepareGraphableFrames([df], createTheme(), StackingMode.None);
const field = result.frames![0].fields[1];
expect(field!.values.toArray()).toMatchInlineSnapshot(`

View File

@@ -4,8 +4,10 @@ import {
Field,
FieldType,
formattedValueToString,
getDisplayProcessor,
getFieldColorModeForField,
getFieldSeriesColor,
GrafanaTheme2,
MutableDataFrame,
VizOrientation,
} from '@grafana/data';
@@ -89,6 +91,8 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<BarChartOptions> = ({
builder.setTooltipInterpolator(config.interpolateTooltip);
builder.setPrepData(config.prepData);
builder.addScale({
scaleKey: 'x',
isTime: false,
@@ -222,6 +226,7 @@ export function preparePlotFrame(data: DataFrame[]) {
/** @internal */
export function prepareGraphableFrames(
series: DataFrame[],
theme: GrafanaTheme2,
stacking: StackingMode
): { frames?: DataFrame[]; warn?: string } {
if (!series?.length) {
@@ -268,6 +273,12 @@ export function prepareGraphableFrames(
})
),
};
if (stacking === StackingMode.Percent) {
copy.config.unit = 'percentunit';
copy.display = getDisplayProcessor({ field: copy, theme });
}
fields.push(copy);
} else {
fields.push({ ...field });

View File

@@ -27,6 +27,7 @@ import { getConfig, TimelineCoreOptions } from './timeline';
import { AxisPlacement, ScaleDirection, ScaleOrientation } from '@grafana/ui/src/components/uPlot/config';
import { TimelineFieldConfig, TimelineOptions } from './types';
import { PlotTooltipInterpolator } from '@grafana/ui/src/components/uPlot/types';
import { preparePlotData } from '../../../../../packages/grafana-ui/src/components/uPlot/utils';
const defaultConfig: TimelineFieldConfig = {
lineWidth: 0,
@@ -140,6 +141,8 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<TimelineOptions> = ({
builder.setTooltipInterpolator(interpolateTooltip);
builder.setPrepData(preparePlotData);
builder.setCursor(coreConfig.cursor);
builder.addScale({

View File

@@ -7,7 +7,7 @@ import {
GrafanaTheme2,
isBooleanUnit,
} from '@grafana/data';
import { GraphFieldConfig, LineInterpolation } from '@grafana/ui';
import { GraphFieldConfig, LineInterpolation, StackingMode } from '@grafana/ui';
// This will return a set of frames with only graphable values included
export function prepareGraphableFields(
@@ -46,6 +46,12 @@ export function prepareGraphableFields(
})
),
};
if (copy.config.custom?.stacking?.mode === StackingMode.Percent) {
copy.config.unit = 'percentunit';
copy.display = getDisplayProcessor({ field: copy, theme });
}
fields.push(copy);
break; // ok
case FieldType.boolean: