mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
TimeSeries/BarChart: Add support for sorting series in the tooltip (#43615)
* TimeSeries panel: Add support for sorting series in the tooltip * Fix cue tests * Make sortValues work with string values * Sort values in DatHoverView and remove sort index from TooltipPlugin * Rename sortOrder prop to sort * DataHoverView - use raw values for sorting
This commit is contained in:
@@ -118,7 +118,14 @@ export const BarChartPanel: React.FunctionComponent<Props> = ({ data, options, w
|
||||
seriesIdx = info.aligned.fields.findIndex((f) => disp === getFieldDisplayName(f, info.aligned));
|
||||
}
|
||||
|
||||
return <DataHoverView data={info.aligned} rowIndex={datapointIdx} columnIndex={seriesIdx} />;
|
||||
return (
|
||||
<DataHoverView
|
||||
data={info.aligned}
|
||||
rowIndex={datapointIdx}
|
||||
columnIndex={seriesIdx}
|
||||
sortOrder={options.tooltip.sort}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderLegend = (config: UPlotConfigBuilder) => {
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
VisibilityMode,
|
||||
GraphGradientMode,
|
||||
StackingMode,
|
||||
SortOrder,
|
||||
} from '@grafana/schema';
|
||||
import {
|
||||
createTheme,
|
||||
@@ -92,6 +93,7 @@ describe('BarChart utils', () => {
|
||||
stacking: StackingMode.None,
|
||||
tooltip: {
|
||||
mode: TooltipDisplayMode.None,
|
||||
sort: SortOrder.None,
|
||||
},
|
||||
text: {
|
||||
valueSize: 10,
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { PureComponent } from 'react';
|
||||
import { stylesFactory } from '@grafana/ui';
|
||||
import {
|
||||
ArrayDataFrame,
|
||||
arrayUtils,
|
||||
DataFrame,
|
||||
Field,
|
||||
formattedValueToString,
|
||||
@@ -11,19 +12,21 @@ import {
|
||||
import { css } from '@emotion/css';
|
||||
import { config } from 'app/core/config';
|
||||
import { FeatureLike } from 'ol/Feature';
|
||||
import { SortOrder } from '@grafana/schema';
|
||||
|
||||
export interface Props {
|
||||
data?: DataFrame; // source data
|
||||
feature?: FeatureLike;
|
||||
rowIndex?: number | null; // the hover row
|
||||
columnIndex?: number | null; // the hover column
|
||||
sortOrder?: SortOrder;
|
||||
}
|
||||
|
||||
export class DataHoverView extends PureComponent<Props> {
|
||||
style = getStyles(config.theme2);
|
||||
|
||||
render() {
|
||||
const { feature, columnIndex } = this.props;
|
||||
const { feature, columnIndex, sortOrder } = this.props;
|
||||
let { data, rowIndex } = this.props;
|
||||
if (feature) {
|
||||
const { geometry, ...properties } = feature.getProperties();
|
||||
@@ -35,17 +38,33 @@ export class DataHoverView extends PureComponent<Props> {
|
||||
return null;
|
||||
}
|
||||
|
||||
const displayValues: Array<[string, any, string]> = [];
|
||||
const visibleFields = data.fields.filter((f) => !Boolean(f.config.custom?.hideFrom?.tooltip));
|
||||
|
||||
if (visibleFields.length === 0) {
|
||||
return null;
|
||||
}
|
||||
for (let i = 0; i < visibleFields.length; i++) {
|
||||
displayValues.push([
|
||||
getFieldDisplayName(visibleFields[i], data),
|
||||
visibleFields[i].values.get(rowIndex!),
|
||||
fmt(visibleFields[i], rowIndex),
|
||||
]);
|
||||
}
|
||||
|
||||
if (sortOrder && sortOrder !== SortOrder.None) {
|
||||
displayValues.sort((a, b) => arrayUtils.sortValues(sortOrder)(a[1], b[1]));
|
||||
}
|
||||
|
||||
return (
|
||||
<table className={this.style.infoWrap}>
|
||||
<tbody>
|
||||
{data.fields
|
||||
.filter((f) => !Boolean(f.config.custom?.hideFrom?.tooltip))
|
||||
.map((f, i) => (
|
||||
<tr key={`${i}/${rowIndex}`} className={i === columnIndex ? this.style.highlight : ''}>
|
||||
<th>{getFieldDisplayName(f, data)}:</th>
|
||||
<td>{fmt(f, rowIndex!)}</td>
|
||||
</tr>
|
||||
))}
|
||||
{displayValues.map((v, i) => (
|
||||
<tr key={`${i}/${rowIndex}`} className={i === columnIndex ? this.style.highlight : ''}>
|
||||
<th>{v[0]}:</th>
|
||||
<td>{v[2]}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
TooltipDisplayMode,
|
||||
GraphGradientMode,
|
||||
HideableFieldConfig,
|
||||
SortOrder,
|
||||
} from '@grafana/schema';
|
||||
|
||||
export const modelVersion = Object.freeze([1, 0]);
|
||||
@@ -29,6 +30,7 @@ export const defaultPanelOptions: PanelOptions = {
|
||||
},
|
||||
tooltip: {
|
||||
mode: TooltipDisplayMode.Multi,
|
||||
sort: SortOrder.None,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
|
||||
data={alignedDataFrame}
|
||||
config={config}
|
||||
mode={options.tooltip.mode}
|
||||
sortOrder={options.tooltip.sort}
|
||||
sync={sync}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
|
||||
@@ -46,7 +46,8 @@ Object {
|
||||
"placement": "bottom",
|
||||
},
|
||||
"tooltip": Object {
|
||||
"mode": "single",
|
||||
"mode": "multi",
|
||||
"sort": "none",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -74,6 +75,7 @@ Object {
|
||||
},
|
||||
"tooltip": Object {
|
||||
"mode": "single",
|
||||
"sort": "none",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -99,6 +101,7 @@ Object {
|
||||
},
|
||||
"tooltip": Object {
|
||||
"mode": "single",
|
||||
"sort": "none",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -153,7 +156,8 @@ Object {
|
||||
"placement": "bottom",
|
||||
},
|
||||
"tooltip": Object {
|
||||
"mode": "single",
|
||||
"mode": "multi",
|
||||
"sort": "none",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -208,7 +212,8 @@ Object {
|
||||
"placement": "bottom",
|
||||
},
|
||||
"tooltip": Object {
|
||||
"mode": "single",
|
||||
"mode": "multi",
|
||||
"sort": "none",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -236,6 +241,7 @@ Object {
|
||||
},
|
||||
"tooltip": Object {
|
||||
"mode": "single",
|
||||
"sort": "none",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -321,7 +327,8 @@ Object {
|
||||
"placement": "bottom",
|
||||
},
|
||||
"tooltip": Object {
|
||||
"mode": "single",
|
||||
"mode": "multi",
|
||||
"sort": "none",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -377,7 +384,8 @@ Object {
|
||||
"placement": "bottom",
|
||||
},
|
||||
"tooltip": Object {
|
||||
"mode": "single",
|
||||
"mode": "multi",
|
||||
"sort": "none",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -417,7 +425,8 @@ Object {
|
||||
"placement": "bottom",
|
||||
},
|
||||
"tooltip": Object {
|
||||
"mode": "single",
|
||||
"mode": "multi",
|
||||
"sort": "none",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -465,7 +474,8 @@ Object {
|
||||
"placement": "bottom",
|
||||
},
|
||||
"tooltip": Object {
|
||||
"mode": "single",
|
||||
"mode": "multi",
|
||||
"sort": "none",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -550,7 +560,8 @@ Object {
|
||||
"placement": "bottom",
|
||||
},
|
||||
"tooltip": Object {
|
||||
"mode": "single",
|
||||
"mode": "multi",
|
||||
"sort": "none",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { PanelModel, FieldConfigSource } from '@grafana/data';
|
||||
import { graphPanelChangedHandler } from './migrations';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { TooltipDisplayMode, SortOrder } from '@grafana/schema';
|
||||
|
||||
describe('Graph Migrations', () => {
|
||||
let prevFieldConfig: FieldConfigSource;
|
||||
@@ -308,6 +309,87 @@ describe('Graph Migrations', () => {
|
||||
expect(panel.fieldConfig.overrides[0].properties[0].value).toEqual({ viz: true, legend: false, tooltip: false });
|
||||
});
|
||||
});
|
||||
|
||||
describe('tooltip', () => {
|
||||
test('tooltip mode', () => {
|
||||
const single: any = {
|
||||
angular: {
|
||||
tooltip: {
|
||||
shared: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
const multi: any = {
|
||||
angular: {
|
||||
tooltip: {
|
||||
shared: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const panel1 = {} as PanelModel;
|
||||
const panel2 = {} as PanelModel;
|
||||
|
||||
panel1.options = graphPanelChangedHandler(panel1, 'graph', single, prevFieldConfig);
|
||||
panel2.options = graphPanelChangedHandler(panel2, 'graph', multi, prevFieldConfig);
|
||||
|
||||
expect(panel1.options.tooltip.mode).toBe(TooltipDisplayMode.Single);
|
||||
expect(panel2.options.tooltip.mode).toBe(TooltipDisplayMode.Multi);
|
||||
});
|
||||
|
||||
test('sort order', () => {
|
||||
const none: any = {
|
||||
angular: {
|
||||
tooltip: {
|
||||
shared: true,
|
||||
sort: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const asc: any = {
|
||||
angular: {
|
||||
tooltip: {
|
||||
shared: true,
|
||||
sort: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const desc: any = {
|
||||
angular: {
|
||||
tooltip: {
|
||||
shared: true,
|
||||
sort: 2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const singleModeWithUnnecessaryOption: any = {
|
||||
angular: {
|
||||
tooltip: {
|
||||
shared: false,
|
||||
sort: 2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const panel1 = {} as PanelModel;
|
||||
const panel2 = {} as PanelModel;
|
||||
const panel3 = {} as PanelModel;
|
||||
const panel4 = {} as PanelModel;
|
||||
|
||||
panel1.options = graphPanelChangedHandler(panel1, 'graph', none, prevFieldConfig);
|
||||
panel2.options = graphPanelChangedHandler(panel2, 'graph', asc, prevFieldConfig);
|
||||
panel3.options = graphPanelChangedHandler(panel3, 'graph', desc, prevFieldConfig);
|
||||
panel4.options = graphPanelChangedHandler(panel4, 'graph', singleModeWithUnnecessaryOption, prevFieldConfig);
|
||||
|
||||
expect(panel1.options.tooltip.sort).toBe(SortOrder.None);
|
||||
expect(panel2.options.tooltip.sort).toBe(SortOrder.Ascending);
|
||||
expect(panel3.options.tooltip.sort).toBe(SortOrder.Descending);
|
||||
expect(panel4.options.tooltip.sort).toBe(SortOrder.None);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const customColor = {
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
VisibilityMode,
|
||||
ScaleDistribution,
|
||||
StackingMode,
|
||||
SortOrder,
|
||||
} from '@grafana/schema';
|
||||
import { TimeSeriesOptions } from './types';
|
||||
import { omitBy, pickBy, isNil, isNumber, isString } from 'lodash';
|
||||
@@ -313,6 +314,7 @@ export function flotToGraphOptions(angular: any): { fieldConfig: FieldConfigSour
|
||||
},
|
||||
tooltip: {
|
||||
mode: TooltipDisplayMode.Single,
|
||||
sort: SortOrder.None,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -335,6 +337,26 @@ export function flotToGraphOptions(angular: any): { fieldConfig: FieldConfigSour
|
||||
}
|
||||
}
|
||||
|
||||
const tooltipConfig = angular.tooltip;
|
||||
if (tooltipConfig) {
|
||||
if (tooltipConfig.shared !== undefined) {
|
||||
options.tooltip.mode = tooltipConfig.shared ? TooltipDisplayMode.Multi : TooltipDisplayMode.Single;
|
||||
}
|
||||
|
||||
if (tooltipConfig.sort !== undefined && tooltipConfig.shared) {
|
||||
switch (tooltipConfig.sort) {
|
||||
case 1:
|
||||
options.tooltip.sort = SortOrder.Ascending;
|
||||
break;
|
||||
case 2:
|
||||
options.tooltip.sort = SortOrder.Descending;
|
||||
break;
|
||||
default:
|
||||
options.tooltip.sort = SortOrder.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (angular.thresholds && angular.thresholds.length > 0) {
|
||||
let steps: Threshold[] = [];
|
||||
let area = false;
|
||||
|
||||
Reference in New Issue
Block a user