mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
GraphNG: stack by % (#37127)
This commit is contained in:
@@ -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(() => {
|
||||
|
@@ -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,
|
||||
};
|
||||
}
|
||||
|
@@ -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(`
|
||||
|
@@ -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 });
|
||||
|
@@ -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({
|
||||
|
@@ -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:
|
||||
|
Reference in New Issue
Block a user