mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Timeline: legend (#34340)
This commit is contained in:
parent
488529b99f
commit
9237348076
@ -15,6 +15,7 @@
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"id": 329,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
@ -53,6 +54,7 @@
|
||||
},
|
||||
"id": 8,
|
||||
"options": {
|
||||
"alignValue": "left",
|
||||
"colWidth": 0.9,
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
@ -230,6 +232,7 @@
|
||||
},
|
||||
"id": 9,
|
||||
"options": {
|
||||
"alignValue": "left",
|
||||
"colWidth": 0.9,
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
@ -268,7 +271,7 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"custom": {
|
||||
"fillOpacity": 70,
|
||||
@ -276,12 +279,16 @@
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"mode": "percentage",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "#EAB839",
|
||||
"value": 60
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
@ -364,5 +371,5 @@
|
||||
"timezone": "utc",
|
||||
"title": "Timeline Modes",
|
||||
"uid": "mIJjFy8Gz",
|
||||
"version": 21
|
||||
}
|
||||
"version": 12
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ export enum FieldColorModeId {
|
||||
Thresholds = 'thresholds',
|
||||
PaletteClassic = 'palette-classic',
|
||||
PaletteSaturated = 'palette-saturated',
|
||||
ContinuousGrYlRd = 'continuous-GrYlRd',
|
||||
Fixed = 'fixed',
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { PanelProps } from '@grafana/data';
|
||||
import { useTheme2, ZoomPlugin } from '@grafana/ui';
|
||||
import { TimelineMode, TimelineOptions } from './types';
|
||||
import { TimelineChart } from './TimelineChart';
|
||||
import { prepareTimelineFields } from './utils';
|
||||
import { prepareTimelineFields, prepareTimelineLegendItems } from './utils';
|
||||
|
||||
interface TimelinePanelProps extends PanelProps<TimelineOptions> {}
|
||||
|
||||
@ -26,6 +26,8 @@ export const StateTimelinePanel: React.FC<TimelinePanelProps> = ({
|
||||
options.mergeValues,
|
||||
]);
|
||||
|
||||
const legendItems = useMemo(() => prepareTimelineLegendItems(frames, options.legend), [frames, options.legend]);
|
||||
|
||||
if (!frames || warn) {
|
||||
return (
|
||||
<div className="panel-empty">
|
||||
@ -43,6 +45,7 @@ export const StateTimelinePanel: React.FC<TimelinePanelProps> = ({
|
||||
timeZone={timeZone}
|
||||
width={width}
|
||||
height={height}
|
||||
legendItems={legendItems}
|
||||
{...options}
|
||||
// hardcoded
|
||||
mode={TimelineMode.Changes}
|
||||
|
@ -1,5 +1,16 @@
|
||||
import React from 'react';
|
||||
import { PanelContext, PanelContextRoot, GraphNG, GraphNGProps, BarValueVisibility } from '@grafana/ui';
|
||||
import {
|
||||
PanelContext,
|
||||
PanelContextRoot,
|
||||
GraphNG,
|
||||
GraphNGProps,
|
||||
BarValueVisibility,
|
||||
LegendDisplayMode,
|
||||
UPlotConfigBuilder,
|
||||
VizLayout,
|
||||
VizLegend,
|
||||
VizLegendItem,
|
||||
} from '@grafana/ui';
|
||||
import { DataFrame, FieldType, TimeRange } from '@grafana/data';
|
||||
import { preparePlotConfigBuilder } from './utils';
|
||||
import { TimelineMode, TimelineValueAlignment } from './types';
|
||||
@ -13,6 +24,7 @@ export interface TimelineProps extends Omit<GraphNGProps, 'prepConfig' | 'propsT
|
||||
showValue: BarValueVisibility;
|
||||
alignValue?: TimelineValueAlignment;
|
||||
colWidth?: number;
|
||||
legendItems?: VizLegendItem[];
|
||||
}
|
||||
|
||||
const propsToDiff = ['rowHeight', 'colWidth', 'showValue', 'mergeValues', 'alignValue'];
|
||||
@ -33,7 +45,19 @@ export class TimelineChart extends React.Component<TimelineProps> {
|
||||
});
|
||||
};
|
||||
|
||||
renderLegend = () => null;
|
||||
renderLegend = (config: UPlotConfigBuilder) => {
|
||||
const { legend, legendItems } = this.props;
|
||||
|
||||
if (!config || !legendItems || !legend || legend.displayMode === LegendDisplayMode.Hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<VizLayout.Legend placement={legend.placement}>
|
||||
<VizLegend placement={legend.placement} items={legendItems} displayMode={legend.displayMode} />
|
||||
</VizLayout.Legend>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
@ -2,6 +2,7 @@ import { FieldColorModeId, FieldConfigProperty, PanelPlugin } from '@grafana/dat
|
||||
import { StateTimelinePanel } from './StateTimelinePanel';
|
||||
import { TimelineOptions, TimelineFieldConfig, defaultPanelOptions, defaultTimelineFieldConfig } from './types';
|
||||
import { BarValueVisibility } from '@grafana/ui';
|
||||
import { addLegendOptions } from '@grafana/ui/src/options/builder';
|
||||
|
||||
export const plugin = new PanelPlugin<TimelineOptions, TimelineFieldConfig>(StateTimelinePanel)
|
||||
.useFieldConfig({
|
||||
@ -11,7 +12,7 @@ export const plugin = new PanelPlugin<TimelineOptions, TimelineFieldConfig>(Stat
|
||||
byValueSupport: true,
|
||||
},
|
||||
defaultValue: {
|
||||
mode: FieldColorModeId.PaletteClassic,
|
||||
mode: FieldColorModeId.ContinuousGrYlRd,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -81,5 +82,5 @@ export const plugin = new PanelPlugin<TimelineOptions, TimelineFieldConfig>(Stat
|
||||
defaultValue: defaultPanelOptions.rowHeight,
|
||||
});
|
||||
|
||||
//addLegendOptions(builder);
|
||||
addLegendOptions(builder, false);
|
||||
});
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "State timeline",
|
||||
"id": "state-timeline",
|
||||
|
||||
"state": "alpha",
|
||||
"state": "beta",
|
||||
|
||||
"info": {
|
||||
"description": "State changes and durations",
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { VizLegendOptions, HideableFieldConfig, BarValueVisibility } from '@grafana/ui';
|
||||
import { HideableFieldConfig, BarValueVisibility, OptionsWithLegend } from '@grafana/ui';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface TimelineOptions {
|
||||
export interface TimelineOptions extends OptionsWithLegend {
|
||||
mode: TimelineMode; // not in the saved model!
|
||||
|
||||
legend: VizLegendOptions;
|
||||
showValue: BarValueVisibility;
|
||||
rowHeight: number;
|
||||
|
||||
|
@ -10,8 +10,18 @@ import {
|
||||
FALLBACK_COLOR,
|
||||
FieldType,
|
||||
ArrayVector,
|
||||
FieldColorModeId,
|
||||
getValueFormat,
|
||||
ThresholdsMode,
|
||||
} from '@grafana/data';
|
||||
import { UPlotConfigBuilder, FIXED_UNIT, SeriesVisibilityChangeMode, UPlotConfigPrepFn } from '@grafana/ui';
|
||||
import {
|
||||
UPlotConfigBuilder,
|
||||
FIXED_UNIT,
|
||||
SeriesVisibilityChangeMode,
|
||||
UPlotConfigPrepFn,
|
||||
VizLegendOptions,
|
||||
VizLegendItem,
|
||||
} from '@grafana/ui';
|
||||
import { TimelineCoreOptions, getConfig } from './timeline';
|
||||
import { AxisPlacement, ScaleDirection, ScaleOrientation } from '@grafana/ui/src/components/uPlot/config';
|
||||
import { measureText } from '@grafana/ui/src/utils/measureText';
|
||||
@ -291,3 +301,77 @@ export function prepareTimelineFields(
|
||||
}
|
||||
return { frames };
|
||||
}
|
||||
|
||||
export function prepareTimelineLegendItems(
|
||||
frames: DataFrame[] | undefined,
|
||||
options: VizLegendOptions
|
||||
): VizLegendItem[] | undefined {
|
||||
if (!frames || options.displayMode === 'hidden') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const fields = allNonTimeFields(frames);
|
||||
if (!fields.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const items: VizLegendItem[] = [];
|
||||
const first = fields[0].config;
|
||||
const colorMode = first.color?.mode ?? FieldColorModeId.Fixed;
|
||||
|
||||
// If thresholds are enabled show each step in the legend
|
||||
if (colorMode === FieldColorModeId.Thresholds && first.thresholds?.steps) {
|
||||
const steps = first.thresholds.steps;
|
||||
const disp = getValueFormat(
|
||||
first.thresholds.mode === ThresholdsMode.Percentage ? 'percent' : first.unit ?? 'fixed'
|
||||
);
|
||||
const fmt = (v: number) => formattedValueToString(disp(v));
|
||||
for (let i = 1; i <= steps.length; i++) {
|
||||
const step = steps[i - 1];
|
||||
items.push({
|
||||
label: i === 1 ? `< ${fmt(steps[i].value)}` : `${fmt(step.value)}+`,
|
||||
color: step.color,
|
||||
yAxis: 1,
|
||||
});
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
// If thresholds are enabled show each step in the legend
|
||||
if (colorMode.startsWith('continuous')) {
|
||||
return undefined; // eventually a color bar
|
||||
}
|
||||
|
||||
let stateColors: Map<string, string | undefined> = new Map();
|
||||
|
||||
fields.forEach((field) => {
|
||||
field.values.toArray().forEach((v) => {
|
||||
let state = field.display!(v);
|
||||
stateColors.set(state.text, state.color!);
|
||||
});
|
||||
});
|
||||
|
||||
stateColors.forEach((color, label) => {
|
||||
if (label.length > 0) {
|
||||
items.push({
|
||||
label: label!,
|
||||
color,
|
||||
yAxis: 1,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
function allNonTimeFields(frames: DataFrame[]): Field[] {
|
||||
const fields: Field[] = [];
|
||||
for (const frame of frames) {
|
||||
for (const field of frame.fields) {
|
||||
if (field.type !== FieldType.time) {
|
||||
fields.push(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { PanelProps } from '@grafana/data';
|
||||
import { useTheme2, ZoomPlugin } from '@grafana/ui';
|
||||
import { StatusPanelOptions } from './types';
|
||||
import { TimelineChart } from '../state-timeline/TimelineChart';
|
||||
import { TimelineMode } from '../state-timeline/types';
|
||||
import { prepareTimelineFields, prepareTimelineLegendItems } from '../state-timeline/utils';
|
||||
|
||||
interface TimelinePanelProps extends PanelProps<StatusPanelOptions> {}
|
||||
|
||||
@ -21,10 +22,14 @@ export const StatusGridPanel: React.FC<TimelinePanelProps> = ({
|
||||
}) => {
|
||||
const theme = useTheme2();
|
||||
|
||||
if (!data || !data.series?.length) {
|
||||
const { frames, warn } = useMemo(() => prepareTimelineFields(data?.series, false), [data]);
|
||||
|
||||
const legendItems = useMemo(() => prepareTimelineLegendItems(frames, options.legend), [frames, options.legend]);
|
||||
|
||||
if (!frames || warn) {
|
||||
return (
|
||||
<div className="panel-empty">
|
||||
<p>No data found in response</p>
|
||||
<p>{warn ?? 'No data found in response'}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -32,12 +37,13 @@ export const StatusGridPanel: React.FC<TimelinePanelProps> = ({
|
||||
return (
|
||||
<TimelineChart
|
||||
theme={theme}
|
||||
frames={data.series}
|
||||
frames={frames}
|
||||
structureRev={data.structureRev}
|
||||
timeRange={timeRange}
|
||||
timeZone={timeZone}
|
||||
width={width}
|
||||
height={height}
|
||||
legendItems={legendItems}
|
||||
{...options}
|
||||
// hardcoded
|
||||
mode={TimelineMode.Samples}
|
||||
|
@ -2,6 +2,7 @@ import { FieldColorModeId, FieldConfigProperty, PanelPlugin } from '@grafana/dat
|
||||
import { StatusGridPanel } from './StatusGridPanel';
|
||||
import { StatusPanelOptions, StatusFieldConfig, defaultStatusFieldConfig } from './types';
|
||||
import { BarValueVisibility } from '@grafana/ui';
|
||||
import { addLegendOptions } from '@grafana/ui/src/options/builder';
|
||||
|
||||
export const plugin = new PanelPlugin<StatusPanelOptions, StatusFieldConfig>(StatusGridPanel)
|
||||
.useFieldConfig({
|
||||
@ -11,7 +12,7 @@ export const plugin = new PanelPlugin<StatusPanelOptions, StatusFieldConfig>(Sta
|
||||
byValueSupport: true,
|
||||
},
|
||||
defaultValue: {
|
||||
mode: FieldColorModeId.PaletteClassic,
|
||||
mode: FieldColorModeId.Thresholds,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -74,5 +75,5 @@ export const plugin = new PanelPlugin<StatusPanelOptions, StatusFieldConfig>(Sta
|
||||
},
|
||||
});
|
||||
|
||||
//addLegendOptions(builder);
|
||||
addLegendOptions(builder, false);
|
||||
});
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Status grid",
|
||||
"id": "status-grid",
|
||||
|
||||
"state": "alpha",
|
||||
"state": "beta",
|
||||
|
||||
"info": {
|
||||
"description": "Periodic status history",
|
||||
|
Loading…
Reference in New Issue
Block a user