Legend: Sort by name (#69490)

This commit is contained in:
Adela Almasan 2023-06-06 15:16:16 -05:00 committed by GitHub
parent c0a1fc2cbd
commit 76b05b80a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 22 additions and 60 deletions

View File

@ -69,10 +69,8 @@ By default, Grafana specifies the color of your series data, which you can chang
You can change legend mode to **Table** and choose [calculations]({{< relref "../../calculation-types/" >}}) to be displayed in the legend. Click the calculation name header in the legend table to sort the values in the table in ascending or descending order.
The sort order affects the positions of the bars in the Bar chart panel as well as the order of stacked series in the Time series and Bar chart panels.
{{% admonition type="note" %}}
This feature is only supported in these panels: Bar chart, Histogram, Time series, XY Chart.
This feature is only supported in these panels: Bar chart, Histogram, Time series.
{{% /admonition %}}
![Sort legend series](/static/img/docs/legend/legend-series-sort-8-3.png).

View File

@ -25,6 +25,7 @@ export function VizLegend<T>({
className,
itemRenderer,
readonly,
isSortable,
}: LegendProps<T>) {
const { eventBus, onToggleSeriesVisibility, onToggleLegendSort } = usePanelContext();
@ -96,6 +97,7 @@ export function VizLegend<T>({
onLabelMouseOut={onMouseOut}
itemRenderer={itemRenderer}
readonly={readonly}
isSortable={isSortable}
/>
);
case LegendDisplayMode.List:

View File

@ -24,9 +24,16 @@ export const VizLegendTable = <T extends unknown>({
onLabelMouseOver,
onLabelMouseOut,
readonly,
isSortable,
}: VizLegendTableProps<T>): JSX.Element => {
const styles = useStyles2(getStyles);
const stats: Record<string, DisplayValue> = {};
const nameSortKey = 'Name';
if (isSortable) {
// placeholder displayValue for Name
stats[nameSortKey] = { description: 'name', numeric: 0, text: '' };
}
for (const item of items) {
if (item.getDisplayValues) {
@ -40,6 +47,10 @@ export const VizLegendTable = <T extends unknown>({
? orderBy(
items,
(item) => {
if (sortKey === nameSortKey) {
return item.label;
}
if (item.getDisplayValues) {
const stat = item.getDisplayValues().filter((stat) => stat.title === sortKey)[0];
return stat && stat.numeric;
@ -68,14 +79,14 @@ export const VizLegendTable = <T extends unknown>({
<table className={cx(styles.table, className)}>
<thead>
<tr>
<th></th>
{!isSortable && <th></th>}
{Object.keys(stats).map((columnTitle) => {
const displayValue = stats[columnTitle];
return (
<th
title={displayValue.description}
key={columnTitle}
className={cx(styles.header, onToggleSort && styles.headerSortable, {
className={cx(styles.header, onToggleSort && styles.headerSortable, isSortable && styles.nameHeader, {
[styles.withIcon]: sortKey === columnTitle,
})}
onClick={() => {
@ -113,6 +124,10 @@ const getStyles = (theme: GrafanaTheme2) => ({
text-align: right;
white-space: nowrap;
`,
nameHeader: css`
text-align: left;
padding-left: 30px;
`,
// This needs to be padding-right - icon size(xs==12) to avoid jumping
withIcon: css`
padding-right: 4px;

View File

@ -30,6 +30,7 @@ export interface VizLegendTableProps<T> extends VizLegendBaseProps<T> {
sortBy?: string;
sortDesc?: boolean;
onToggleSort?: (sortBy: string) => void;
isSortable?: boolean;
}
export interface LegendProps<T = any> extends VizLegendBaseProps<T>, VizLegendTableProps<T> {

View File

@ -150,6 +150,7 @@ export const PlotLegend = React.memo(
displayMode={displayMode}
sortBy={vizLayoutLegendProps.sortBy}
sortDesc={vizLayoutLegendProps.sortDesc}
isSortable={true}
/>
</VizLayout.Legend>
);

View File

@ -226,47 +226,6 @@ describe('BarChart utils', () => {
`);
});
it('should sort fields when legend sortBy and sortDesc are set', () => {
const frame = new MutableDataFrame({
fields: [
{ name: 'string', type: FieldType.string, values: ['a', 'b', 'c'] },
{ name: 'a', values: [-10, 20, 10], state: { calcs: { min: -10 } } },
{ name: 'b', values: [20, 20, 20], state: { calcs: { min: 20 } } },
{ name: 'c', values: [10, 10, 10], state: { calcs: { min: 10 } } },
],
});
const resultAsc = prepareBarChartDisplayValues([frame], createTheme(), {
legend: { sortBy: 'Min', sortDesc: false },
} as Options);
const displayValuesAsc = assertIsDefined('viz' in resultAsc ? resultAsc : null).viz[0];
expect(displayValuesAsc.fields[0].type).toBe(FieldType.string);
expect(displayValuesAsc.fields[1].name).toBe('a');
expect(displayValuesAsc.fields[2].name).toBe('c');
expect(displayValuesAsc.fields[3].name).toBe('b');
const displayLegendValuesAsc = assertIsDefined('legend' in resultAsc ? resultAsc : null).legend;
expect(displayLegendValuesAsc.fields[0].type).toBe(FieldType.string);
expect(displayLegendValuesAsc.fields[1].name).toBe('a');
expect(displayLegendValuesAsc.fields[2].name).toBe('c');
expect(displayLegendValuesAsc.fields[3].name).toBe('b');
const resultDesc = prepareBarChartDisplayValues([frame], createTheme(), {
legend: { sortBy: 'Min', sortDesc: true },
} as Options);
const displayValuesDesc = assertIsDefined('viz' in resultDesc ? resultDesc : null).viz[0];
expect(displayValuesDesc.fields[0].type).toBe(FieldType.string);
expect(displayValuesDesc.fields[1].name).toBe('b');
expect(displayValuesDesc.fields[2].name).toBe('c');
expect(displayValuesDesc.fields[3].name).toBe('a');
const displayLegendValuesDesc = assertIsDefined('legend' in resultDesc ? resultDesc : null).legend;
expect(displayLegendValuesDesc.fields[0].type).toBe(FieldType.string);
expect(displayLegendValuesDesc.fields[1].name).toBe('b');
expect(displayLegendValuesDesc.fields[2].name).toBe('c');
expect(displayLegendValuesDesc.fields[3].name).toBe('a');
});
it('should remove unit from legend values when stacking is percent', () => {
const frame = new MutableDataFrame({
fields: [

View File

@ -1,4 +1,3 @@
import { orderBy } from 'lodash';
import uPlot, { Padding } from 'uplot';
import {
@ -11,7 +10,6 @@ import {
getFieldSeriesColor,
GrafanaTheme2,
outerJoinDataFrames,
reduceField,
TimeZone,
VizOrientation,
} from '@grafana/data';
@ -474,18 +472,6 @@ export function prepareBarChartDisplayValues(
}
}
if (isLegendOrdered(options.legend)) {
const sortKey = options.legend.sortBy!.toLowerCase();
const reducers = options.legend.calcs ?? [sortKey];
fields = orderBy(
fields,
(field) => {
return reduceField({ field, reducers })[sortKey];
},
options.legend.sortDesc ? 'desc' : 'asc'
);
}
let legendFields: Field[] = fields;
if (options.stacking === StackingMode.Percent) {
legendFields = fields.map((field) => {