mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Piechart: Implements series override -> hide in area for the legend or tooltip (#51297)
* piechart: support series override for hiding legend item * piechart: add support for hiding values in multi tooltip * tidy up variable names * piechart: typing for hideFrom variable * betterer fixes
This commit is contained in:
@@ -314,14 +314,19 @@ function getTooltipData(
|
||||
tooltipOptions: VizTooltipOptions
|
||||
) {
|
||||
if (tooltipOptions.mode === 'multi') {
|
||||
return pie.arcs.map((pieArc) => {
|
||||
return {
|
||||
color: pieArc.data.display.color ?? FALLBACK_COLOR,
|
||||
label: pieArc.data.display.title,
|
||||
value: formattedValueToString(pieArc.data.display),
|
||||
isActive: pieArc.index === arc.index,
|
||||
};
|
||||
});
|
||||
return pie.arcs
|
||||
.filter((pa) => {
|
||||
const field = pa.data.field;
|
||||
return field && !field.custom?.hideFrom?.tooltip && !field.custom?.hideFrom?.viz;
|
||||
})
|
||||
.map((pieArc) => {
|
||||
return {
|
||||
color: pieArc.data.display.color ?? FALLBACK_COLOR,
|
||||
label: pieArc.data.display.title,
|
||||
value: formattedValueToString(pieArc.data.display),
|
||||
isActive: pieArc.index === arc.index,
|
||||
};
|
||||
});
|
||||
}
|
||||
return [
|
||||
{
|
||||
|
||||
206
public/app/plugins/panel/piechart/PieChartPanel.test.tsx
Normal file
206
public/app/plugins/panel/piechart/PieChartPanel.test.tsx
Normal file
@@ -0,0 +1,206 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React, { ComponentProps } from 'react';
|
||||
|
||||
import {
|
||||
FieldConfigSource,
|
||||
toDataFrame,
|
||||
FieldType,
|
||||
VizOrientation,
|
||||
LoadingState,
|
||||
getDefaultTimeRange,
|
||||
EventBusSrv,
|
||||
} from '@grafana/data';
|
||||
import { LegendDisplayMode, SortOrder, TooltipDisplayMode } from '@grafana/schema';
|
||||
|
||||
import { PieChartPanel } from './PieChartPanel';
|
||||
import { PieChartOptions, PieChartType, PieChartLegendValues } from './types';
|
||||
|
||||
type PieChartPanelProps = ComponentProps<typeof PieChartPanel>;
|
||||
|
||||
describe('PieChartPanel', () => {
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(global, 'ResizeObserver', {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation(() => ({
|
||||
observe: jest.fn(),
|
||||
unobserve: jest.fn(),
|
||||
disconnect: jest.fn(),
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
describe('series overrides - Hide in area', () => {
|
||||
const defaultConfig = {
|
||||
custom: {
|
||||
hideFrom: {
|
||||
legend: false,
|
||||
viz: false,
|
||||
tooltip: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('when no hiding', () => {
|
||||
const seriesWithNoOverrides = [
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'Chrome', config: defaultConfig, type: FieldType.number, values: [60] },
|
||||
{ name: 'Firefox', config: defaultConfig, type: FieldType.number, values: [20] },
|
||||
{ name: 'Safari', config: defaultConfig, type: FieldType.number, values: [20] },
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
it('should not filter out any slices or legend items', () => {
|
||||
setup({ data: { series: seriesWithNoOverrides } });
|
||||
|
||||
const slices = screen.queryAllByLabelText('Pie Chart Slice');
|
||||
expect(slices.length).toBe(3);
|
||||
expect(screen.queryByText(/Chrome/i)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Firefox/i)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Safari/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when series override to hide viz', () => {
|
||||
const hideVizConfig = {
|
||||
custom: {
|
||||
hideFrom: {
|
||||
legend: false,
|
||||
viz: true,
|
||||
tooltip: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const seriesWithFirefoxOverride = [
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'Chrome', config: defaultConfig, type: FieldType.number, values: [60] },
|
||||
{ name: 'Firefox', config: hideVizConfig, type: FieldType.number, values: [20] },
|
||||
{ name: 'Safari', config: defaultConfig, type: FieldType.number, values: [20] },
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
it('should filter out the Firefox pie chart slice but not the legend', () => {
|
||||
setup({ data: { series: seriesWithFirefoxOverride } });
|
||||
|
||||
const slices = screen.queryAllByLabelText('Pie Chart Slice');
|
||||
expect(slices.length).toBe(2);
|
||||
expect(screen.queryByText(/Firefox/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when series override to hide tooltip', () => {
|
||||
const hideTooltipConfig = {
|
||||
custom: {
|
||||
hideFrom: {
|
||||
legend: false,
|
||||
viz: false,
|
||||
tooltip: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const seriesWithFirefoxOverride = [
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'Chrome', config: defaultConfig, type: FieldType.number, values: [600] },
|
||||
{ name: 'Firefox', config: hideTooltipConfig, type: FieldType.number, values: [190] },
|
||||
{ name: 'Safari', config: defaultConfig, type: FieldType.number, values: [210] },
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
it('should filter out the Firefox series with value 190 from the multi tooltip', async () => {
|
||||
setup({ data: { series: seriesWithFirefoxOverride } });
|
||||
|
||||
await userEvent.hover(screen.getAllByLabelText('Pie Chart Slice')[0]);
|
||||
expect(screen.queryByText(/600/i)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/190/i)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/210/i)).toBeInTheDocument();
|
||||
|
||||
expect(screen.queryByText(/Firefox/i)).toBeInTheDocument();
|
||||
const slices = screen.queryAllByLabelText('Pie Chart Slice');
|
||||
expect(slices.length).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when series override to hide legend', () => {
|
||||
const hideLegendConfig = {
|
||||
custom: {
|
||||
hideFrom: {
|
||||
legend: true,
|
||||
viz: false,
|
||||
tooltip: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const seriesWithFirefoxOverride = [
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'Chrome', config: defaultConfig, type: FieldType.number, values: [60] },
|
||||
{ name: 'Firefox', config: hideLegendConfig, type: FieldType.number, values: [20] },
|
||||
{ name: 'Safari', config: defaultConfig, type: FieldType.number, values: [20] },
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
it('should filter out the series from the legend but not the slice', () => {
|
||||
setup({ data: { series: seriesWithFirefoxOverride } });
|
||||
|
||||
expect(screen.queryByText(/Firefox/i)).not.toBeInTheDocument();
|
||||
const slices = screen.queryAllByLabelText('Pie Chart Slice');
|
||||
expect(slices.length).toBe(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const setup = (propsOverrides?: {}) => {
|
||||
const fieldConfig: FieldConfigSource = {
|
||||
defaults: {},
|
||||
overrides: [],
|
||||
};
|
||||
|
||||
const options: PieChartOptions = {
|
||||
pieType: PieChartType.Pie,
|
||||
displayLabels: [],
|
||||
legend: {
|
||||
displayMode: LegendDisplayMode.List,
|
||||
placement: 'right',
|
||||
calcs: [],
|
||||
values: [PieChartLegendValues.Percent],
|
||||
},
|
||||
reduceOptions: {
|
||||
calcs: [],
|
||||
},
|
||||
orientation: VizOrientation.Auto,
|
||||
tooltip: { mode: TooltipDisplayMode.Multi, sort: SortOrder.Ascending },
|
||||
};
|
||||
|
||||
const props: PieChartPanelProps = {
|
||||
id: 1,
|
||||
data: { state: LoadingState.Done, timeRange: getDefaultTimeRange(), series: [] },
|
||||
timeZone: 'utc',
|
||||
options: options,
|
||||
fieldConfig: fieldConfig,
|
||||
width: 532,
|
||||
height: 250,
|
||||
renderCounter: 0,
|
||||
title: 'A pie chart',
|
||||
transparent: false,
|
||||
onFieldConfigChange: () => {},
|
||||
onOptionsChange: () => {},
|
||||
onChangeTimeRange: () => {},
|
||||
replaceVariables: (s: string) => s,
|
||||
eventBus: new EventBusSrv(),
|
||||
timeRange: getDefaultTimeRange(),
|
||||
...propsOverrides,
|
||||
};
|
||||
|
||||
return render(<PieChartPanel {...props} />);
|
||||
};
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
PanelProps,
|
||||
} from '@grafana/data';
|
||||
import { PanelDataErrorView } from '@grafana/runtime';
|
||||
import { LegendDisplayMode } from '@grafana/schema';
|
||||
import { HideSeriesConfig, LegendDisplayMode } from '@grafana/schema';
|
||||
import {
|
||||
SeriesVisibilityChangeBehavior,
|
||||
usePanelContext,
|
||||
@@ -82,7 +82,7 @@ function getLegend(props: Props, displayValues: FieldDisplay[]) {
|
||||
}
|
||||
const total = displayValues.filter(filterDisplayItems).reduce(sumDisplayItemsReducer, 0);
|
||||
|
||||
const legendItems = displayValues
|
||||
const legendItems: VizLegendItem[] = displayValues
|
||||
// Since the pie chart is always sorted, let's sort the legend as well.
|
||||
.sort((a, b) => {
|
||||
if (isNaN(a.display.numeric)) {
|
||||
@@ -93,14 +93,21 @@ function getLegend(props: Props, displayValues: FieldDisplay[]) {
|
||||
return b.display.numeric - a.display.numeric;
|
||||
}
|
||||
})
|
||||
.map<VizLegendItem>((value, idx) => {
|
||||
const hidden = value.field.custom.hideFrom.viz;
|
||||
.map<VizLegendItem | undefined>((value: FieldDisplay, idx: number) => {
|
||||
const hideFrom: HideSeriesConfig = value.field.custom?.hideFrom ?? {};
|
||||
|
||||
if (hideFrom.legend) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const hideFromViz = Boolean(hideFrom.viz);
|
||||
|
||||
const display = value.display;
|
||||
return {
|
||||
label: display.title ?? '',
|
||||
color: display.color ?? FALLBACK_COLOR,
|
||||
yAxis: 1,
|
||||
disabled: hidden,
|
||||
disabled: hideFromViz,
|
||||
getItemKey: () => (display.title ?? '') + idx,
|
||||
getDisplayValues: () => {
|
||||
const valuesToShow = legendOptions.values ?? [];
|
||||
@@ -111,14 +118,14 @@ function getLegend(props: Props, displayValues: FieldDisplay[]) {
|
||||
}
|
||||
|
||||
if (valuesToShow.includes(PieChartLegendValues.Percent)) {
|
||||
const fractionOfTotal = hidden ? 0 : display.numeric / total;
|
||||
const fractionOfTotal = hideFromViz ? 0 : display.numeric / total;
|
||||
const percentOfTotal = fractionOfTotal * 100;
|
||||
|
||||
displayValues.push({
|
||||
numeric: fractionOfTotal,
|
||||
percent: percentOfTotal,
|
||||
text:
|
||||
hidden || isNaN(fractionOfTotal)
|
||||
hideFromViz || isNaN(fractionOfTotal)
|
||||
? props.fieldConfig.defaults.noValue ?? '-'
|
||||
: percentOfTotal.toFixed(value.field.decimals ?? 0) + '%',
|
||||
title: valuesToShow.length > 1 ? 'Percent' : '',
|
||||
@@ -128,7 +135,8 @@ function getLegend(props: Props, displayValues: FieldDisplay[]) {
|
||||
return displayValues;
|
||||
},
|
||||
};
|
||||
});
|
||||
})
|
||||
.filter((i): i is VizLegendItem => !!i);
|
||||
|
||||
return (
|
||||
<VizLegend
|
||||
|
||||
Reference in New Issue
Block a user