mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
GraphNG: stats in legend (#30251)
* StatsPickerEditor - add more config to the options ui * Show calcs in the legend * Refactor the way legend items are created * Progress on refresh * Migration update * Use human-readable names in the legend stats * Disable pointer cursor in table header
This commit is contained in:
@@ -132,3 +132,14 @@ export interface FieldColorConfigSettings {
|
||||
*/
|
||||
preferThresholdsMode?: boolean;
|
||||
}
|
||||
|
||||
export interface StatsPickerConfigSettings {
|
||||
/**
|
||||
* Enable multi-selection in the stats picker
|
||||
*/
|
||||
allowMultiple: boolean;
|
||||
/**
|
||||
* Default stats to be use in the stats picker
|
||||
*/
|
||||
defaultStat?: string;
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ export const GraphWithLegend: React.FunctionComponent<GraphWithLegendProps> = (p
|
||||
color: s.color || '',
|
||||
disabled: !s.isVisible,
|
||||
yAxis: s.yAxis.index,
|
||||
displayValues: s.info || [],
|
||||
getDisplayValues: () => s.info || [],
|
||||
},
|
||||
]);
|
||||
}, []);
|
||||
|
||||
@@ -63,8 +63,8 @@ export const Lines: React.FC = () => {
|
||||
to: dateTime(1546380000000),
|
||||
},
|
||||
}}
|
||||
legend={{ displayMode: LegendDisplayMode.List, placement: legendPlacement }}
|
||||
legend={{ displayMode: LegendDisplayMode.List, placement: legendPlacement, calcs: [] }}
|
||||
timeZone="browser"
|
||||
></GraphNG>
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,19 +2,22 @@ import React, { useCallback, useLayoutEffect, useMemo, useRef } from 'react';
|
||||
import {
|
||||
compareDataFrameStructures,
|
||||
DataFrame,
|
||||
DisplayValue,
|
||||
FieldConfig,
|
||||
FieldMatcher,
|
||||
fieldReducers,
|
||||
FieldType,
|
||||
formattedValueToString,
|
||||
getFieldColorModeForField,
|
||||
getFieldDisplayName,
|
||||
reduceField,
|
||||
TimeRange,
|
||||
} from '@grafana/data';
|
||||
import { alignDataFrames } from './utils';
|
||||
import { useTheme } from '../../themes';
|
||||
import { UPlotChart } from '../uPlot/Plot';
|
||||
import { PlotProps } from '../uPlot/types';
|
||||
import { AxisPlacement, DrawStyle, GraphFieldConfig, PointVisibility } from '../uPlot/config';
|
||||
import { useTheme } from '../../themes';
|
||||
import { VizLayout } from '../VizLayout/VizLayout';
|
||||
import { LegendDisplayMode, VizLegendItem, VizLegendOptions } from '../VizLegend/types';
|
||||
import { VizLegend } from '../VizLegend/VizLegend';
|
||||
@@ -30,7 +33,7 @@ export interface XYFieldMatchers {
|
||||
}
|
||||
export interface GraphNGProps extends Omit<PlotProps, 'data' | 'config'> {
|
||||
data: DataFrame[];
|
||||
legend?: VizLegendOptions;
|
||||
legend: VizLegendOptions;
|
||||
fields?: XYFieldMatchers; // default will assume timeseries data
|
||||
onLegendClick?: (event: GraphNGLegendEvent) => void;
|
||||
onSeriesColorChange?: (label: string, color: string) => void;
|
||||
@@ -55,10 +58,10 @@ export const GraphNG: React.FC<GraphNGProps> = ({
|
||||
onSeriesColorChange,
|
||||
...plotProps
|
||||
}) => {
|
||||
const alignedFrameWithGapTest = useMemo(() => alignDataFrames(data, fields), [data, fields]);
|
||||
const theme = useTheme();
|
||||
const legendItemsRef = useRef<VizLegendItem[]>([]);
|
||||
const hasLegend = useRef(legend && legend.displayMode !== LegendDisplayMode.Hidden);
|
||||
|
||||
const alignedFrameWithGapTest = useMemo(() => alignDataFrames(data, fields), [data, fields]);
|
||||
const alignedFrame = alignedFrameWithGapTest?.frame;
|
||||
const getDataFrameFieldIndex = alignedFrameWithGapTest?.getDataFrameFieldIndex;
|
||||
|
||||
@@ -87,6 +90,7 @@ export const GraphNG: React.FC<GraphNGProps> = ({
|
||||
|
||||
// reference change will not triger re-render
|
||||
const currentTimeRange = useRef<TimeRange>(timeRange);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
currentTimeRange.current = timeRange;
|
||||
}, [timeRange]);
|
||||
@@ -130,8 +134,6 @@ export const GraphNG: React.FC<GraphNGProps> = ({
|
||||
});
|
||||
}
|
||||
|
||||
const legendItems: VizLegendItem[] = [];
|
||||
|
||||
for (let i = 0; i < alignedFrame.fields.length; i++) {
|
||||
const field = alignedFrame.fields[i];
|
||||
const config = field.config as FieldConfig<GraphFieldConfig>;
|
||||
@@ -170,6 +172,7 @@ export const GraphNG: React.FC<GraphNGProps> = ({
|
||||
}
|
||||
|
||||
const showPoints = customConfig.drawStyle === DrawStyle.Points ? PointVisibility.Always : customConfig.showPoints;
|
||||
const dataFrameFieldIndex = getDataFrameFieldIndex ? getDataFrameFieldIndex(i) : undefined;
|
||||
|
||||
builder.addSeries({
|
||||
scaleKey,
|
||||
@@ -185,24 +188,13 @@ export const GraphNG: React.FC<GraphNGProps> = ({
|
||||
spanNulls: customConfig.spanNulls || false,
|
||||
show: !customConfig.hideFrom?.graph,
|
||||
fillGradient: customConfig.fillGradient,
|
||||
|
||||
// The following properties are not used in the uPlot config, but are utilized as transport for legend config
|
||||
dataFrameFieldIndex,
|
||||
fieldName: getFieldDisplayName(field, alignedFrame),
|
||||
hideInLegend: customConfig.hideFrom?.legend,
|
||||
});
|
||||
|
||||
if (hasLegend.current && !customConfig.hideFrom?.legend) {
|
||||
const axisPlacement = builder.getAxisPlacement(scaleKey);
|
||||
// we need to add this as dep or move it to be done outside.
|
||||
const dataFrameFieldIndex = getDataFrameFieldIndex ? getDataFrameFieldIndex(i) : undefined;
|
||||
|
||||
legendItems.push({
|
||||
disabled: field.config.custom?.hideFrom?.graph ?? false,
|
||||
fieldIndex: dataFrameFieldIndex,
|
||||
color: seriesColor,
|
||||
label: getFieldDisplayName(field, alignedFrame),
|
||||
yAxis: axisPlacement === AxisPlacement.Left ? 1 : 2,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
legendItemsRef.current = legendItems;
|
||||
return builder;
|
||||
}, [configRev, timeZone]);
|
||||
|
||||
@@ -214,16 +206,58 @@ export const GraphNG: React.FC<GraphNGProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
const legendItems = configBuilder
|
||||
.getSeries()
|
||||
.map<VizLegendItem | undefined>(s => {
|
||||
const seriesConfig = s.props;
|
||||
const fieldIndex = seriesConfig.dataFrameFieldIndex;
|
||||
const axisPlacement = configBuilder.getAxisPlacement(s.props.scaleKey);
|
||||
|
||||
if (seriesConfig.hideInLegend || !fieldIndex) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const field = data[fieldIndex.frameIndex]?.fields[fieldIndex.fieldIndex];
|
||||
|
||||
// Hackish: when the data prop and config builder are not in sync yet
|
||||
if (!field) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
disabled: !seriesConfig.show ?? false,
|
||||
fieldIndex,
|
||||
color: seriesConfig.lineColor!,
|
||||
label: seriesConfig.fieldName,
|
||||
yAxis: axisPlacement === AxisPlacement.Left ? 1 : 2,
|
||||
getDisplayValues: () => {
|
||||
const fmt = field.display ?? defaultFormatter;
|
||||
const fieldCalcs = reduceField({
|
||||
field,
|
||||
reducers: legend.calcs,
|
||||
});
|
||||
|
||||
return legend.calcs.map<DisplayValue>(reducer => {
|
||||
return {
|
||||
...fmt(fieldCalcs[reducer]),
|
||||
title: fieldReducers.get(reducer).name,
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
})
|
||||
.filter(i => i !== undefined) as VizLegendItem[];
|
||||
|
||||
let legendElement: React.ReactElement | undefined;
|
||||
|
||||
if (hasLegend && legendItemsRef.current.length > 0) {
|
||||
if (hasLegend && legendItems.length > 0) {
|
||||
legendElement = (
|
||||
<VizLayout.Legend position={legend!.placement} maxHeight="35%" maxWidth="60%">
|
||||
<VizLayout.Legend position={legend.placement} maxHeight="35%" maxWidth="60%">
|
||||
<VizLegend
|
||||
onLabelClick={onLabelClick}
|
||||
placement={legend!.placement}
|
||||
items={legendItemsRef.current}
|
||||
displayMode={legend!.displayMode}
|
||||
placement={legend.placement}
|
||||
items={legendItems}
|
||||
displayMode={legend.displayMode}
|
||||
onSeriesColorChange={onSeriesColorChange}
|
||||
/>
|
||||
</VizLayout.Legend>
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
import React from 'react';
|
||||
import { FieldConfigEditorProps, ReducerID } from '@grafana/data';
|
||||
import { FieldConfigEditorProps, StatsPickerConfigSettings } from '@grafana/data';
|
||||
import { StatsPicker } from '../StatsPicker/StatsPicker';
|
||||
|
||||
export const StatsPickerEditor: React.FC<FieldConfigEditorProps<string[], any>> = ({ value, onChange }) => {
|
||||
return <StatsPicker stats={value} onChange={onChange} allowMultiple={false} defaultStat={ReducerID.mean} />;
|
||||
export const StatsPickerEditor: React.FC<FieldConfigEditorProps<string[], StatsPickerConfigSettings>> = ({
|
||||
value,
|
||||
onChange,
|
||||
item,
|
||||
}) => {
|
||||
return (
|
||||
<StatsPicker
|
||||
stats={value}
|
||||
onChange={onChange}
|
||||
allowMultiple={!!item.settings?.allowMultiple}
|
||||
defaultStat={item.settings?.defaultStat}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
FieldType,
|
||||
getFieldColorModeForField,
|
||||
FieldConfig,
|
||||
getFieldDisplayName,
|
||||
} from '@grafana/data';
|
||||
import { AxisPlacement, DrawStyle, GraphFieldConfig, PointVisibility } from '../uPlot/config';
|
||||
import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder';
|
||||
@@ -136,6 +137,7 @@ export class Sparkline extends PureComponent<Props, State> {
|
||||
|
||||
builder.addSeries({
|
||||
scaleKey,
|
||||
fieldName: getFieldDisplayName(field, data),
|
||||
drawStyle: customConfig.drawStyle!,
|
||||
lineColor: customConfig.lineColor ?? seriesColor,
|
||||
lineWidth: customConfig.lineWidth,
|
||||
|
||||
@@ -40,7 +40,7 @@ export const VizLegendListItem: React.FunctionComponent<Props> = ({ item, onSeri
|
||||
{item.label}
|
||||
</div>
|
||||
|
||||
{item.displayValues && <VizLegendStatsList stats={item.displayValues} />}
|
||||
{item.getDisplayValues && <VizLegendStatsList stats={item.getDisplayValues()} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,8 +22,8 @@ export const VizLegendTable: FC<VizLegendTableProps> = ({
|
||||
|
||||
const columns = items
|
||||
.map(item => {
|
||||
if (item.displayValues) {
|
||||
return item.displayValues.map(i => i.title);
|
||||
if (item.getDisplayValues) {
|
||||
return item.getDisplayValues().map(i => i.title);
|
||||
}
|
||||
return [];
|
||||
})
|
||||
@@ -39,8 +39,8 @@ export const VizLegendTable: FC<VizLegendTableProps> = ({
|
||||
|
||||
const sortedItems = sortKey
|
||||
? sortBy(items, item => {
|
||||
if (item.displayValues) {
|
||||
const stat = item.displayValues.filter(stat => stat.title === sortKey)[0];
|
||||
if (item.getDisplayValues) {
|
||||
const stat = item.getDisplayValues().filter(stat => stat.title === sortKey)[0];
|
||||
return stat && stat.numeric;
|
||||
}
|
||||
return undefined;
|
||||
@@ -67,7 +67,7 @@ export const VizLegendTable: FC<VizLegendTableProps> = ({
|
||||
return (
|
||||
<th
|
||||
key={columnHeader}
|
||||
className={styles.header}
|
||||
className={cx(styles.header, onToggleSort && styles.headerSortable)}
|
||||
onClick={() => {
|
||||
if (onToggleSort) {
|
||||
onToggleSort(columnHeader);
|
||||
@@ -99,6 +99,8 @@ const getStyles = (theme: GrafanaTheme) => ({
|
||||
border-bottom: 1px solid ${theme.colors.border1};
|
||||
padding: ${theme.spacing.xxs} ${theme.spacing.sm};
|
||||
text-align: right;
|
||||
`,
|
||||
headerSortable: css`
|
||||
cursor: pointer;
|
||||
`,
|
||||
sortIcon: css`
|
||||
|
||||
@@ -49,8 +49,8 @@ export const LegendTableItem: React.FunctionComponent<Props> = ({
|
||||
</div>
|
||||
</span>
|
||||
</td>
|
||||
{item.displayValues &&
|
||||
item.displayValues.map((stat, index) => {
|
||||
{item.getDisplayValues &&
|
||||
item.getDisplayValues().map((stat, index) => {
|
||||
return (
|
||||
<td className={styles.value} key={`${stat.title}-${index}`}>
|
||||
{formattedValueToString(stat)}
|
||||
|
||||
@@ -24,7 +24,8 @@ export interface VizLegendItem {
|
||||
color: string;
|
||||
yAxis: number;
|
||||
disabled?: boolean;
|
||||
displayValues?: DisplayValue[];
|
||||
// displayValues?: DisplayValue[];
|
||||
getDisplayValues?: () => DisplayValue[];
|
||||
fieldIndex?: DataFrameFieldIndex;
|
||||
}
|
||||
|
||||
@@ -39,6 +40,7 @@ export type LegendPlacement = 'bottom' | 'right';
|
||||
export interface VizLegendOptions {
|
||||
displayMode: LegendDisplayMode;
|
||||
placement: LegendPlacement;
|
||||
calcs: string[];
|
||||
}
|
||||
|
||||
export type SeriesOptionChangeHandler<TOption> = (label: string, option: TOption) => void;
|
||||
|
||||
@@ -325,6 +325,7 @@ describe('UPlotConfigBuilder', () => {
|
||||
builder.addSeries({
|
||||
drawStyle: DrawStyle.Line,
|
||||
scaleKey: 'scale-x',
|
||||
fieldName: 'A-series',
|
||||
lineColor: '#0000ff',
|
||||
});
|
||||
|
||||
@@ -336,6 +337,7 @@ describe('UPlotConfigBuilder', () => {
|
||||
builder.addSeries({
|
||||
drawStyle: DrawStyle.Line,
|
||||
scaleKey: 'scale-x',
|
||||
fieldName: 'A-series',
|
||||
lineColor: '#FFAABB',
|
||||
fillOpacity: 50,
|
||||
});
|
||||
@@ -348,6 +350,7 @@ describe('UPlotConfigBuilder', () => {
|
||||
builder.addSeries({
|
||||
drawStyle: DrawStyle.Line,
|
||||
scaleKey: 'scale-x',
|
||||
fieldName: 'A-series',
|
||||
lineColor: '#FFAABB',
|
||||
fillOpacity: 50,
|
||||
fillColor: '#FF0000',
|
||||
@@ -361,6 +364,7 @@ describe('UPlotConfigBuilder', () => {
|
||||
builder.addSeries({
|
||||
drawStyle: DrawStyle.Line,
|
||||
scaleKey: 'scale-x',
|
||||
fieldName: 'A-series',
|
||||
lineColor: '#FFAABB',
|
||||
fillOpacity: 50,
|
||||
fillGradient: FillGradientMode.Opacity,
|
||||
@@ -374,6 +378,7 @@ describe('UPlotConfigBuilder', () => {
|
||||
builder.addSeries({
|
||||
drawStyle: DrawStyle.Line,
|
||||
scaleKey: 'scale-x',
|
||||
fieldName: 'A-series',
|
||||
fillOpacity: 50,
|
||||
fillGradient: FillGradientMode.Opacity,
|
||||
showPoints: PointVisibility.Auto,
|
||||
|
||||
@@ -57,6 +57,10 @@ export class UPlotConfigBuilder {
|
||||
this.series.push(new UPlotSeriesBuilder(props));
|
||||
}
|
||||
|
||||
getSeries() {
|
||||
return this.series;
|
||||
}
|
||||
|
||||
/** Add or update the scale with the scale key */
|
||||
addScale(props: ScaleProps) {
|
||||
const current = this.scales.find(v => v.props.scaleKey === props.scaleKey);
|
||||
|
||||
@@ -11,11 +11,15 @@ import {
|
||||
FillGradientMode,
|
||||
} from '../config';
|
||||
import { PlotConfigBuilder } from '../types';
|
||||
import { DataFrameFieldIndex } from '@grafana/data';
|
||||
|
||||
export interface SeriesProps extends LineConfig, FillConfig, PointsConfig {
|
||||
drawStyle: DrawStyle;
|
||||
scaleKey: string;
|
||||
fieldName: string;
|
||||
drawStyle: DrawStyle;
|
||||
show?: boolean;
|
||||
dataFrameFieldIndex?: DataFrameFieldIndex;
|
||||
hideInLegend?: boolean;
|
||||
}
|
||||
|
||||
export class UPlotSeriesBuilder extends PlotConfigBuilder<SeriesProps, Series> {
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
TimeZone,
|
||||
FieldColor,
|
||||
FieldColorConfigSettings,
|
||||
StatsPickerConfigSettings,
|
||||
} from '@grafana/data';
|
||||
|
||||
import { Switch } from '../components/Switch/Switch';
|
||||
@@ -323,7 +324,7 @@ export const getStandardOptionEditors = () => {
|
||||
editor: DataLinksValueEditor as any,
|
||||
};
|
||||
|
||||
const statsPicker: StandardEditorsRegistryItem<string[]> = {
|
||||
const statsPicker: StandardEditorsRegistryItem<string[], StatsPickerConfigSettings> = {
|
||||
id: 'stats-picker',
|
||||
name: 'Stats Picker',
|
||||
editor: StatsPickerEditor as any,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ApplyFieldOverrideOptions, DataTransformerConfig, dateMath, FieldColorModeId, PanelData } from '@grafana/data';
|
||||
import { GraphNG, Table } from '@grafana/ui';
|
||||
import { GraphNG, LegendDisplayMode, Table } from '@grafana/ui';
|
||||
import { config } from 'app/core/config';
|
||||
import React, { FC, useMemo, useState } from 'react';
|
||||
import { useObservable } from 'react-use';
|
||||
@@ -54,7 +54,14 @@ export const TestStuffPage: FC = () => {
|
||||
|
||||
{data && (
|
||||
<div style={{ padding: '16px' }}>
|
||||
<GraphNG width={1200} height={300} data={data.series} timeRange={data.timeRange} timeZone="browser" />
|
||||
<GraphNG
|
||||
width={1200}
|
||||
height={300}
|
||||
data={data.series}
|
||||
legend={{ displayMode: LegendDisplayMode.List, placement: 'bottom', calcs: [] }}
|
||||
timeRange={data.timeRange}
|
||||
timeZone="browser"
|
||||
/>
|
||||
<hr></hr>
|
||||
<Table data={data.series[0]} width={1200} height={300} />
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,60 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Graph Migrations legend 1`] = `
|
||||
Object {
|
||||
"fieldConfig": Object {
|
||||
"defaults": Object {
|
||||
"custom": Object {
|
||||
"axisPlacement": "auto",
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 50,
|
||||
"lineInterpolation": "stepAfter",
|
||||
"lineWidth": 5,
|
||||
"pointSize": 6,
|
||||
"showPoints": "never",
|
||||
"spanNulls": true,
|
||||
},
|
||||
"nullValueMode": "null",
|
||||
"unit": "short",
|
||||
},
|
||||
"overrides": Array [
|
||||
Object {
|
||||
"matcher": Object {
|
||||
"id": "byName",
|
||||
"options": "A-series",
|
||||
},
|
||||
"properties": Array [
|
||||
Object {
|
||||
"id": "color",
|
||||
"value": Object {
|
||||
"fixedColor": "red",
|
||||
"mode": "fixed",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
"options": Object {
|
||||
"graph": Object {},
|
||||
"legend": Object {
|
||||
"calcs": Array [
|
||||
"mean",
|
||||
"lastNotNull",
|
||||
"max",
|
||||
"min",
|
||||
"sum",
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
},
|
||||
"tooltipOptions": Object {
|
||||
"mode": "single",
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Graph Migrations simple bars 1`] = `
|
||||
Object {
|
||||
"fieldConfig": Object {
|
||||
@@ -16,6 +71,7 @@ Object {
|
||||
"options": Object {
|
||||
"graph": Object {},
|
||||
"legend": Object {
|
||||
"calcs": Array [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
},
|
||||
@@ -50,7 +106,14 @@ Object {
|
||||
"options": Object {
|
||||
"graph": Object {},
|
||||
"legend": Object {
|
||||
"displayMode": "list",
|
||||
"calcs": Array [
|
||||
"mean",
|
||||
"lastNotNull",
|
||||
"max",
|
||||
"min",
|
||||
"sum",
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
},
|
||||
"tooltipOptions": Object {
|
||||
@@ -98,6 +161,7 @@ Object {
|
||||
"options": Object {
|
||||
"graph": Object {},
|
||||
"legend": Object {
|
||||
"calcs": Array [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
},
|
||||
@@ -179,6 +243,7 @@ Object {
|
||||
"options": Object {
|
||||
"graph": Object {},
|
||||
"legend": Object {
|
||||
"calcs": Array [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
},
|
||||
|
||||
@@ -39,6 +39,15 @@ describe('Graph Migrations', () => {
|
||||
panel.options = graphPanelChangedHandler(panel, 'graph', old);
|
||||
expect(panel).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('legend', () => {
|
||||
const old: any = {
|
||||
angular: legend,
|
||||
};
|
||||
const panel = {} as PanelModel;
|
||||
panel.options = graphPanelChangedHandler(panel, 'graph', old);
|
||||
expect(panel).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
const stairscase = {
|
||||
@@ -306,3 +315,97 @@ const stepedColordLine = {
|
||||
timeShift: null,
|
||||
datasource: null,
|
||||
};
|
||||
|
||||
const legend = {
|
||||
aliasColors: {
|
||||
'A-series': 'red',
|
||||
},
|
||||
dashLength: 10,
|
||||
fieldConfig: {
|
||||
defaults: {
|
||||
custom: {},
|
||||
},
|
||||
overrides: [],
|
||||
},
|
||||
fill: 5,
|
||||
gridPos: {
|
||||
h: 9,
|
||||
w: 12,
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
id: 2,
|
||||
legend: {
|
||||
avg: true,
|
||||
current: true,
|
||||
max: false,
|
||||
min: false,
|
||||
show: true,
|
||||
total: true,
|
||||
values: true,
|
||||
alignAsTable: true,
|
||||
},
|
||||
lines: true,
|
||||
linewidth: 5,
|
||||
maxDataPoints: 20,
|
||||
nullPointMode: 'null',
|
||||
options: {
|
||||
alertThreshold: true,
|
||||
},
|
||||
pluginVersion: '7.4.0-pre',
|
||||
pointradius: 2,
|
||||
renderer: 'flot',
|
||||
seriesOverrides: [],
|
||||
spaceLength: 10,
|
||||
steppedLine: true,
|
||||
thresholds: [],
|
||||
timeRegions: [],
|
||||
title: 'Panel Title',
|
||||
tooltip: {
|
||||
shared: true,
|
||||
sort: 0,
|
||||
value_type: 'individual',
|
||||
},
|
||||
type: 'graph',
|
||||
xaxis: {
|
||||
buckets: null,
|
||||
mode: 'time',
|
||||
name: null,
|
||||
show: true,
|
||||
values: [],
|
||||
},
|
||||
yaxes: [
|
||||
{
|
||||
$$hashKey: 'object:38',
|
||||
format: 'short',
|
||||
label: null,
|
||||
logBase: 1,
|
||||
max: null,
|
||||
min: null,
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
$$hashKey: 'object:39',
|
||||
format: 'short',
|
||||
label: null,
|
||||
logBase: 1,
|
||||
max: null,
|
||||
min: null,
|
||||
show: true,
|
||||
},
|
||||
],
|
||||
yaxis: {
|
||||
align: false,
|
||||
alignLevel: null,
|
||||
},
|
||||
bars: false,
|
||||
dashes: false,
|
||||
fillGradient: 0,
|
||||
hiddenSeries: false,
|
||||
percentage: false,
|
||||
points: false,
|
||||
stack: false,
|
||||
timeFrom: null,
|
||||
timeShift: null,
|
||||
datasource: null,
|
||||
};
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import {
|
||||
ConfigOverrideRule,
|
||||
DynamicConfigValue,
|
||||
FieldColorModeId,
|
||||
FieldConfig,
|
||||
FieldConfigProperty,
|
||||
FieldConfigSource,
|
||||
FieldMatcherID,
|
||||
fieldReducers,
|
||||
NullValueMode,
|
||||
PanelModel,
|
||||
fieldReducers,
|
||||
ConfigOverrideRule,
|
||||
FieldMatcherID,
|
||||
DynamicConfigValue,
|
||||
FieldConfigProperty,
|
||||
FieldColorModeId,
|
||||
} from '@grafana/data';
|
||||
import { GraphFieldConfig, LegendDisplayMode } from '@grafana/ui';
|
||||
import {
|
||||
FillGradientMode,
|
||||
AxisPlacement,
|
||||
DrawStyle,
|
||||
FillGradientMode,
|
||||
LineInterpolation,
|
||||
LineStyle,
|
||||
PointVisibility,
|
||||
@@ -256,15 +256,29 @@ export function flotToGraphOptions(angular: any): { fieldConfig: FieldConfigSour
|
||||
legend: {
|
||||
displayMode: LegendDisplayMode.List,
|
||||
placement: 'bottom',
|
||||
calcs: [],
|
||||
},
|
||||
tooltipOptions: {
|
||||
mode: 'single',
|
||||
},
|
||||
};
|
||||
|
||||
if (angular.legend?.values) {
|
||||
const show = getReducersFromLegend(angular.legend?.values);
|
||||
console.log('Migrate Legend', show);
|
||||
// Legend config migration
|
||||
const legendConfig = angular.legend;
|
||||
if (legendConfig) {
|
||||
if (legendConfig.show) {
|
||||
options.legend.displayMode = legendConfig.alignAsTable ? LegendDisplayMode.Table : LegendDisplayMode.List;
|
||||
} else {
|
||||
options.legend.displayMode = LegendDisplayMode.Hidden;
|
||||
}
|
||||
|
||||
if (legendConfig.rightSide) {
|
||||
options.legend.placement = 'right';
|
||||
}
|
||||
|
||||
if (angular.legend.values) {
|
||||
options.legend.calcs = getReducersFromLegend(angular.legend);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { PanelPlugin } from '@grafana/data';
|
||||
import { PanelPlugin, standardEditorsRegistry, StatsPickerConfigSettings } from '@grafana/data';
|
||||
import { GraphFieldConfig, LegendDisplayMode } from '@grafana/ui';
|
||||
import { TimeSeriesPanel } from './TimeSeriesPanel';
|
||||
import { graphPanelChangedHandler } from './migrations';
|
||||
import { Options } from './types';
|
||||
import { getGraphFieldConfig, defaultGraphConfig } from './config';
|
||||
import { defaultGraphConfig, getGraphFieldConfig } from './config';
|
||||
|
||||
export const plugin = new PanelPlugin<Options, GraphFieldConfig>(TimeSeriesPanel)
|
||||
.setPanelChangeHandler(graphPanelChangedHandler)
|
||||
@@ -48,5 +48,17 @@ export const plugin = new PanelPlugin<Options, GraphFieldConfig>(TimeSeriesPanel
|
||||
],
|
||||
},
|
||||
showIf: c => c.legend.displayMode !== LegendDisplayMode.Hidden,
|
||||
})
|
||||
.addCustomEditor<StatsPickerConfigSettings, string[]>({
|
||||
id: 'legend.calcs',
|
||||
path: 'legend.calcs',
|
||||
name: 'Legend calculations',
|
||||
description: 'Choose a reducer functions / calculations to include in legend',
|
||||
editor: standardEditorsRegistry.get('stats-picker').editor as any,
|
||||
defaultValue: [],
|
||||
settings: {
|
||||
allowMultiple: true,
|
||||
},
|
||||
showIf: currentConfig => currentConfig.legend.displayMode !== LegendDisplayMode.Hidden,
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user