StateTimeline / StatusHistory: Add axis visibility and width controls (#98548)

* Allow setting the y axis width

* Add to docs

* Add to status history as well

* Add to status history docs and schema

* Change config to come from generic axis builder

* keep axis

* Change overridden label

* Update docs/sources/panels-visualizations/visualizations/status-history/index.md

Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com>

* Update docs/sources/panels-visualizations/visualizations/state-timeline/index.md

Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com>

* Remove the category/label override

* Move axis to its own section in docs as well

* clean

* rename to addAxisWidth

* Apply suggestions from code review

Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com>

* Move sections to match UI order

* Update docs/sources/shared/visualizations/axis-options-all.md

Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com>

* Change other axis options doc to be consistent.

* Fix linter

* add AxisPlacement

* Add new placement option to docs

* change some wording

* Apply suggestions from code review

Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com>

---------

Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com>
This commit is contained in:
Kristina 2025-01-23 12:21:24 -06:00 committed by GitHub
parent d24e7c126d
commit e9d9b15295
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 149 additions and 81 deletions

View File

@ -149,6 +149,10 @@ The **Page size** option lets you paginate the state timeline visualization to l
{{< docs/shared lookup="visualizations/tooltip-options-1.md" source="grafana" version="<GRAFANA_VERSION>" leveloffset="+1" >}} {{< docs/shared lookup="visualizations/tooltip-options-1.md" source="grafana" version="<GRAFANA_VERSION>" leveloffset="+1" >}}
### Axis options
{{< docs/shared lookup="visualizations/axis-options-state-status.md" source="grafana" version="<GRAFANA_VERSION>" leveloffset="+1" >}}
### Standard options ### Standard options
{{< docs/shared lookup="visualizations/standard-options.md" source="grafana" version="<GRAFANA_VERSION>" >}} {{< docs/shared lookup="visualizations/standard-options.md" source="grafana" version="<GRAFANA_VERSION>" >}}

View File

@ -131,6 +131,10 @@ Controls the opacity of state regions.
{{< docs/shared lookup="visualizations/tooltip-options-1.md" source="grafana" version="<GRAFANA_VERSION>" >}} {{< docs/shared lookup="visualizations/tooltip-options-1.md" source="grafana" version="<GRAFANA_VERSION>" >}}
## Axis options
{{< docs/shared lookup="visualizations/axis-options-state-status.md" source="grafana" version="<GRAFANA_VERSION>" leveloffset="+1" >}}
## Standard options ## Standard options
{{< docs/shared lookup="visualizations/standard-options.md" source="grafana" version="<GRAFANA_VERSION>" >}} {{< docs/shared lookup="visualizations/standard-options.md" source="grafana" version="<GRAFANA_VERSION>" >}}

View File

@ -209,21 +209,7 @@ The following example shows three series: Min, Max, and Value. The Min and Max s
### Axis options ### Axis options
Options under the **Axis** section control how the x- and y-axes are rendered. Some options don't take effect until you click outside of the field option box you're editing. You can also press `Enter`. {{< docs/shared lookup="visualizations/axis-options-all.md" source="grafana" version="<GRAFANA_VERSION>" leveloffset="+1" >}}
| Option | Description |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Time zone | Set the desired time zones to display along the x-axis. |
| [Placement](#placement) | Select the placement of the y-axis. |
| Label | Set a y-axis text label. If you have more than one y-axis, then you can assign different labels using an override. |
| Width | Set a fixed width of the axis. By default, Grafana dynamically calculates the width of an axis. By setting the width of the axis, data with different axes types can share the same display proportions. This setting makes it easier for you to compare more than one graphs worth of data because the axes aren't shifted or stretched within visual proximity to each other. |
| Show grid lines | Set the axis grid line visibility.<br> |
| Color | Set the color of the axis. |
| Show border | Set the axis border visibility. |
| Scale | Set the y-axis values scale.<br> |
| Centered zero | Set the y-axis so it's centered on zero. |
| [Soft min](#soft-min-and-soft-max) | Set a soft min to better control the y-axis limits. zero. |
| [Soft max](#soft-min-and-soft-max) | Set a soft max to better control the y-axis limits. zero. |
#### Placement #### Placement

View File

@ -0,0 +1,21 @@
---
title: Axis options
comments: |
This file is used in the following visualizations: time series.
---
Options under the **Axis** section control how the x- and y-axes are rendered. Some options don't take effect until you click outside of the field option box you're editing. You can also press `Enter`.
| Option | Description |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| Time zone | Set the desired time zones to display along the x-axis. |
| [Placement](#placement) | Select the placement of the y-axis. |
| Label | Set a y-axis text label. If you have more than one y-axis, then you can assign different labels using an override. |
| Width | Set a fixed width for the axis. By default, Grafana dynamically calculates the width of an axis. |
| Show grid lines | Set the axis grid line visibility.<br> |
| Color | Set the color of the axis. |
| Show border | Set the axis border visibility. |
| Scale | Set the y-axis values scale.<br> |
| Centered zero | Set the y-axis so it's centered on zero. |
| [Soft min](#soft-min-and-soft-max) | Set a soft min to better control the y-axis limits. zero. |
| [Soft max](#soft-min-and-soft-max) | Set a soft max to better control the y-axis limits. zero. |

View File

@ -0,0 +1,10 @@
---
title: Axis options
comments: |
This file is used in the following visualizations: state timeline, status history.
---
| Option | Description |
| --------- | ------------------------------------------------------------------------------------------------ |
| Placement | Control the visibility of series names along the y-axis or time values along the x-axis. |
| Width | Set a fixed width for the axis. By default, Grafana dynamically calculates the width of an axis. |

View File

@ -43,7 +43,7 @@ export const defaultOptions: Partial<Options> = {
showValue: ui.VisibilityMode.Auto, showValue: ui.VisibilityMode.Auto,
}; };
export interface FieldConfig extends ui.HideableFieldConfig { export interface FieldConfig extends ui.AxisConfig, ui.HideableFieldConfig {
fillOpacity?: number; fillOpacity?: number;
lineWidth?: number; lineWidth?: number;
} }

View File

@ -33,7 +33,7 @@ export const defaultOptions: Partial<Options> = {
showValue: ui.VisibilityMode.Auto, showValue: ui.VisibilityMode.Auto,
}; };
export interface FieldConfig extends ui.HideableFieldConfig { export interface FieldConfig extends ui.AxisConfig, ui.HideableFieldConfig {
fillOpacity?: number; fillOpacity?: number;
lineWidth?: number; lineWidth?: number;
} }

View File

@ -14,28 +14,16 @@ import { Stack } from '../../components/Layout/Stack/Stack';
import { Select } from '../../components/Select/Select'; import { Select } from '../../components/Select/Select';
import { graphFieldOptions } from '../../components/uPlot/config'; import { graphFieldOptions } from '../../components/uPlot/config';
const category = ['Axis'];
/** /**
* @alpha * @alpha
*/ */
export function addAxisConfig( export function addAxisConfig(builder: FieldConfigEditorBuilder<AxisConfig>, defaultConfig: AxisConfig) {
builder: FieldConfigEditorBuilder<AxisConfig>,
defaultConfig: AxisConfig,
hideScale?: boolean
) {
const category = ['Axis'];
// options for axis appearance // options for axis appearance
builder addAxisPlacement(builder);
.addRadio({
path: 'axisPlacement', builder.addTextInput({
name: 'Placement',
category,
defaultValue: graphFieldOptions.axisPlacement[0].value,
settings: {
options: graphFieldOptions.axisPlacement,
},
})
.addTextInput({
path: 'axisLabel', path: 'axisLabel',
name: 'Label', name: 'Label',
category, category,
@ -47,16 +35,11 @@ export function addAxisConfig(
showIf: (c) => c.axisPlacement !== AxisPlacement.Hidden, showIf: (c) => c.axisPlacement !== AxisPlacement.Hidden,
// Do not apply default settings to time and string fields which are used as x-axis fields in Time series and Bar chart panels // Do not apply default settings to time and string fields which are used as x-axis fields in Time series and Bar chart panels
shouldApply: (f) => f.type !== FieldType.time && f.type !== FieldType.string, shouldApply: (f) => f.type !== FieldType.time && f.type !== FieldType.string,
}) });
.addNumberInput({
path: 'axisWidth', addAxisWidth(builder);
name: 'Width',
category, builder
settings: {
placeholder: 'Auto',
},
showIf: (c) => c.axisPlacement !== AxisPlacement.Hidden,
})
.addRadio({ .addRadio({
path: 'axisGridShow', path: 'axisGridShow',
name: 'Show grid lines', name: 'Show grid lines',
@ -209,3 +192,32 @@ export const ScaleDistributionEditor = ({ value, onChange }: StandardEditorProps
</Stack> </Stack>
); );
}; };
/** @internal */
export function addAxisWidth(builder: FieldConfigEditorBuilder<AxisConfig>) {
builder.addNumberInput({
path: 'axisWidth',
name: 'Width',
category,
settings: {
placeholder: 'Auto',
},
showIf: (c) => c.axisPlacement !== AxisPlacement.Hidden,
});
}
/** @internal */
export function addAxisPlacement(
builder: FieldConfigEditorBuilder<AxisConfig>,
optionsFilter = (placement: AxisPlacement) => true
) {
builder.addRadio({
path: 'axisPlacement',
name: 'Placement',
category,
defaultValue: graphFieldOptions.axisPlacement[0].value,
settings: {
options: graphFieldOptions.axisPlacement.filter((placement) => optionsFilter(placement.value!)),
},
});
}

View File

@ -51,6 +51,7 @@ interface UPlotConfigOptions {
mergeValues?: boolean; mergeValues?: boolean;
getValueColor: (frameIdx: number, fieldIdx: number, value: unknown) => string; getValueColor: (frameIdx: number, fieldIdx: number, value: unknown) => string;
hoverMulti: boolean; hoverMulti: boolean;
axisWidth?: number;
} }
/** /**
@ -161,25 +162,32 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<UPlotConfigOptions> = (
range: coreConfig.yRange, range: coreConfig.yRange,
}); });
const xAxisHidden = frame.fields[0].config.custom.axisPlacement === AxisPlacement.Hidden;
builder.addAxis({ builder.addAxis({
show: !xAxisHidden,
scaleKey: xScaleKey, scaleKey: xScaleKey,
isTime: true, isTime: true,
splits: coreConfig.xSplits!, splits: coreConfig.xSplits!,
placement: AxisPlacement.Bottom, placement: AxisPlacement.Bottom,
timeZone: timeZones[0], timeZone: timeZones[0],
theme, theme,
grid: { show: true },
}); });
const yCustomConfig = frame.fields[1].config.custom;
const yAxisWidth = yCustomConfig.axisWidth;
const yAxisHidden = yCustomConfig.axisPlacement === AxisPlacement.Hidden;
builder.addAxis({ builder.addAxis({
scaleKey: FIXED_UNIT, // y scaleKey: FIXED_UNIT, // y
isTime: false, isTime: false,
placement: AxisPlacement.Left, placement: AxisPlacement.Left,
splits: coreConfig.ySplits, splits: coreConfig.ySplits,
values: coreConfig.yValues, values: yAxisHidden ? (u, splits) => splits.map((v) => null) : coreConfig.yValues,
grid: { show: false }, grid: { show: false },
ticks: { show: false }, ticks: { show: false },
gap: 16, gap: yAxisHidden ? 0 : 16,
size: yAxisHidden ? 0 : yAxisWidth,
theme, theme,
}); });

View File

@ -103,7 +103,7 @@ export const plugin = new PanelPlugin<Options, FieldConfig>(BarChartPanel)
shouldApply: () => true, shouldApply: () => true,
}); });
commonOptionsBuilder.addAxisConfig(builder, cfg, false); commonOptionsBuilder.addAxisConfig(builder, cfg);
commonOptionsBuilder.addHideFrom(builder); commonOptionsBuilder.addHideFrom(builder);
}, },
}) })

View File

@ -4,6 +4,7 @@ import { useMeasure } from 'react-use';
import { DashboardCursorSync, DataFrame, PanelProps } from '@grafana/data'; import { DashboardCursorSync, DataFrame, PanelProps } from '@grafana/data';
import { import {
AxisPlacement,
EventBusPlugin, EventBusPlugin,
Pagination, Pagination,
TooltipDisplayMode, TooltipDisplayMode,
@ -209,7 +210,7 @@ export const StateTimelinePanel = ({
maxWidth={options.tooltip.maxWidth} maxWidth={options.tooltip.maxWidth}
/> />
)} )}
{/* Renders annotations */} {alignedFrame.fields[0].config.custom?.axisPlacement !== AxisPlacement.Hidden && (
<AnnotationsPlugin2 <AnnotationsPlugin2
annotations={data.annotations ?? []} annotations={data.annotations ?? []}
config={builder} config={builder}
@ -218,6 +219,7 @@ export const StateTimelinePanel = ({
setNewRange={setNewAnnotationRange} setNewRange={setNewAnnotationRange}
canvasRegionRendering={false} canvasRegionRendering={false}
/> />
)}
<OutsideRangePlugin config={builder} onChangeTimeRange={onChangeTimeRange} /> <OutsideRangePlugin config={builder} onChangeTimeRange={onChangeTimeRange} />
</> </>
); );

View File

@ -5,7 +5,7 @@ import {
identityOverrideProcessor, identityOverrideProcessor,
PanelPlugin, PanelPlugin,
} from '@grafana/data'; } from '@grafana/data';
import { VisibilityMode } from '@grafana/schema'; import { AxisPlacement, VisibilityMode } from '@grafana/schema';
import { commonOptionsBuilder } from '@grafana/ui'; import { commonOptionsBuilder } from '@grafana/ui';
import { InsertNullsEditor } from '../timeseries/InsertNullsEditor'; import { InsertNullsEditor } from '../timeseries/InsertNullsEditor';
@ -76,6 +76,11 @@ export const plugin = new PanelPlugin<Options, FieldConfig>(StateTimelinePanel)
}); });
commonOptionsBuilder.addHideFrom(builder); commonOptionsBuilder.addHideFrom(builder);
commonOptionsBuilder.addAxisPlacement(
builder,
(placement) => placement === AxisPlacement.Auto || placement === AxisPlacement.Hidden
);
commonOptionsBuilder.addAxisWidth(builder);
}, },
}) })
.setPanelOptions((builder) => { .setPanelOptions((builder) => {

View File

@ -41,6 +41,7 @@ composableKinds: PanelCfg: {
perPage?: number & >=1 | *20 perPage?: number & >=1 | *20
} @cuetsy(kind="interface") } @cuetsy(kind="interface")
FieldConfig: { FieldConfig: {
ui.AxisConfig
ui.HideableFieldConfig ui.HideableFieldConfig
lineWidth?: uint32 & <=10 | *0 lineWidth?: uint32 & <=10 | *0
fillOpacity?: uint32 & <=100 | *70 fillOpacity?: uint32 & <=100 | *70

View File

@ -41,7 +41,7 @@ export const defaultOptions: Partial<Options> = {
showValue: ui.VisibilityMode.Auto, showValue: ui.VisibilityMode.Auto,
}; };
export interface FieldConfig extends ui.HideableFieldConfig { export interface FieldConfig extends ui.AxisConfig, ui.HideableFieldConfig {
fillOpacity?: number; fillOpacity?: number;
lineWidth?: number; lineWidth?: number;
} }

View File

@ -1,7 +1,14 @@
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { DashboardCursorSync, PanelProps } from '@grafana/data'; import { DashboardCursorSync, PanelProps } from '@grafana/data';
import { EventBusPlugin, TooltipDisplayMode, TooltipPlugin2, usePanelContext, useTheme2 } from '@grafana/ui'; import {
AxisPlacement,
EventBusPlugin,
TooltipDisplayMode,
TooltipPlugin2,
usePanelContext,
useTheme2,
} from '@grafana/ui';
import { TimeRange2, TooltipHoverMode } from '@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2'; import { TimeRange2, TooltipHoverMode } from '@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2';
import { TimelineChart } from 'app/core/components/TimelineChart/TimelineChart'; import { TimelineChart } from 'app/core/components/TimelineChart/TimelineChart';
import { import {
@ -141,6 +148,7 @@ export const StatusHistoryPanel = ({
maxWidth={options.tooltip.maxWidth} maxWidth={options.tooltip.maxWidth}
/> />
)} )}
{alignedFrame.fields[0].config.custom?.axisPlacement !== AxisPlacement.Hidden && (
<AnnotationsPlugin2 <AnnotationsPlugin2
annotations={data.annotations ?? []} annotations={data.annotations ?? []}
config={builder} config={builder}
@ -149,6 +157,7 @@ export const StatusHistoryPanel = ({
setNewRange={setNewAnnotationRange} setNewRange={setNewAnnotationRange}
canvasRegionRendering={false} canvasRegionRendering={false}
/> />
)}
<OutsideRangePlugin config={builder} onChangeTimeRange={onChangeTimeRange} /> <OutsideRangePlugin config={builder} onChangeTimeRange={onChangeTimeRange} />
</> </>
); );

View File

@ -1,5 +1,5 @@
import { FieldColorModeId, FieldConfigProperty, PanelPlugin } from '@grafana/data'; import { FieldColorModeId, FieldConfigProperty, PanelPlugin } from '@grafana/data';
import { VisibilityMode } from '@grafana/schema'; import { AxisPlacement, VisibilityMode } from '@grafana/schema';
import { commonOptionsBuilder } from '@grafana/ui'; import { commonOptionsBuilder } from '@grafana/ui';
import { StatusHistoryPanel } from './StatusHistoryPanel'; import { StatusHistoryPanel } from './StatusHistoryPanel';
@ -42,6 +42,11 @@ export const plugin = new PanelPlugin<Options, FieldConfig>(StatusHistoryPanel)
}); });
commonOptionsBuilder.addHideFrom(builder); commonOptionsBuilder.addHideFrom(builder);
commonOptionsBuilder.addAxisPlacement(
builder,
(placement) => placement === AxisPlacement.Auto || placement === AxisPlacement.Hidden
);
commonOptionsBuilder.addAxisWidth(builder);
}, },
}) })
.setPanelOptions((builder) => { .setPanelOptions((builder) => {

View File

@ -37,6 +37,7 @@ composableKinds: PanelCfg: {
colWidth?: float & <=1 | *0.9 colWidth?: float & <=1 | *0.9
} @cuetsy(kind="interface") } @cuetsy(kind="interface")
FieldConfig: { FieldConfig: {
ui.AxisConfig
ui.HideableFieldConfig ui.HideableFieldConfig
lineWidth?: uint32 & <=10 | *1 lineWidth?: uint32 & <=10 | *1
fillOpacity?: uint32 & <=100 | *70 fillOpacity?: uint32 & <=100 | *70

View File

@ -31,7 +31,7 @@ export const defaultOptions: Partial<Options> = {
showValue: ui.VisibilityMode.Auto, showValue: ui.VisibilityMode.Auto,
}; };
export interface FieldConfig extends ui.HideableFieldConfig { export interface FieldConfig extends ui.AxisConfig, ui.HideableFieldConfig {
fillOpacity?: number; fillOpacity?: number;
lineWidth?: number; lineWidth?: number;
} }