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:
Daniel Lee
2022-06-27 15:13:19 +02:00
committed by GitHub
parent b2b0be7b93
commit 292b24e30d
4 changed files with 236 additions and 17 deletions

View File

@@ -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 [
{

View 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} />);
};

View File

@@ -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