From 9b7c68c9940f1fb600500cae764deab8274a8697 Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Fri, 24 Oct 2025 11:35:52 -0500 Subject: [PATCH] TimeSeries: Allow custom time units on x-axis (#112913) * more panels * fix --- .../src/components/uPlot/config/UPlotAxisBuilder.ts | 4 ++-- public/app/core/components/TimeSeries/utils.ts | 3 +++ public/app/core/components/TimelineChart/utils.ts | 7 ++++++- public/app/plugins/panel/heatmap/module.tsx | 7 ++++++- public/app/plugins/panel/heatmap/utils.ts | 12 ++++++++++++ 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts index 3d8d5972fe1..675d6536184 100644 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts @@ -163,8 +163,6 @@ export class UPlotAxisBuilder extends PlotConfigBuilder { if (values) { config.values = values; - } else if (isTime) { - config.values = formatTime; } else if (formatValue) { config.values = (u: uPlot, splits, axisIdx, tickSpace, tickIncr) => { let decimals = guessDecimals(roundDecimals(tickIncr, 6)); @@ -176,6 +174,8 @@ export class UPlotAxisBuilder extends PlotConfigBuilder { } }); }; + } else if (isTime) { + config.values = formatTime; } // store timezone diff --git a/public/app/core/components/TimeSeries/utils.ts b/public/app/core/components/TimeSeries/utils.ts index 1bf0c5a18c0..f029388e602 100644 --- a/public/app/core/components/TimeSeries/utils.ts +++ b/public/app/core/components/TimeSeries/utils.ts @@ -156,6 +156,9 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ({ theme, grid: { show: i === 0 && xField.config.custom?.axisGridShow }, filter: filterTicks, + formatValue: xField.config.unit?.startsWith('time:') + ? (v, decimals) => xField.display!(v, decimals).text + : undefined, }); } diff --git a/public/app/core/components/TimelineChart/utils.ts b/public/app/core/components/TimelineChart/utils.ts index 1b233d4a3ad..78f7cd4ef69 100644 --- a/public/app/core/components/TimelineChart/utils.ts +++ b/public/app/core/components/TimelineChart/utils.ts @@ -166,7 +166,9 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ( range: coreConfig.yRange, }); - const xAxisHidden = frame.fields[0].config.custom.axisPlacement === AxisPlacement.Hidden; + const xField = frame.fields[0]; + + const xAxisHidden = xField.config.custom.axisPlacement === AxisPlacement.Hidden; builder.addAxis({ show: !xAxisHidden, @@ -176,6 +178,9 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ( placement: AxisPlacement.Bottom, timeZone: timeZones[0], theme, + formatValue: xField.config.unit?.startsWith('time:') + ? (v, decimals) => xField.display!(v, decimals).text + : undefined, }); const yCustomConfig = frame.fields[1].config.custom; diff --git a/public/app/plugins/panel/heatmap/module.tsx b/public/app/plugins/panel/heatmap/module.tsx index 6375f7e012b..897fdbda461 100644 --- a/public/app/plugins/panel/heatmap/module.tsx +++ b/public/app/plugins/panel/heatmap/module.tsx @@ -23,13 +23,18 @@ import { Options, defaultOptions, HeatmapColorMode, HeatmapColorScale } from './ export const plugin = new PanelPlugin(HeatmapPanel) .useFieldConfig({ - disableStandardOptions: Object.values(FieldConfigProperty).filter((v) => v !== FieldConfigProperty.Links), + disableStandardOptions: Object.values(FieldConfigProperty).filter( + (v) => v !== FieldConfigProperty.Links && v !== FieldConfigProperty.Unit + ), standardOptions: { [FieldConfigProperty.Links]: { settings: { showOneClick: true, }, }, + [FieldConfigProperty.Unit]: { + hideFromDefaults: true, + }, }, useCustomConfig: (builder) => { const category = [t('heatmap.category-heatmap', 'Heatmap')]; diff --git a/public/app/plugins/panel/heatmap/utils.ts b/public/app/plugins/panel/heatmap/utils.ts index 3618dcdef2d..11a1c03b7c7 100644 --- a/public/app/plugins/panel/heatmap/utils.ts +++ b/public/app/plugins/panel/heatmap/utils.ts @@ -10,6 +10,7 @@ import { incrRoundUp, TimeRange, FieldType, + getDisplayProcessor, } from '@grafana/data'; import { AxisPlacement, ScaleDirection, ScaleDistribution, ScaleOrientation, HeatmapCellLayout } from '@grafana/schema'; import { UPlotConfigBuilder } from '@grafana/ui'; @@ -163,6 +164,13 @@ export function prepConfig(opts: PrepConfigOpts) { } } + let xField = dataRef.current?.heatmap?.fields[0]!; + xField.display ??= getDisplayProcessor({ + field: xField, + theme, + timeZone, + }); + builder.addAxis({ scaleKey: xScaleKey, placement: AxisPlacement.Bottom, @@ -170,6 +178,10 @@ export function prepConfig(opts: PrepConfigOpts) { isTime, theme: theme, timeZone, + formatValue: + isTime && xField.config.unit?.startsWith('time:') + ? (v, decimals) => xField.display!(v, decimals).text + : undefined, }); const yField = dataRef.current?.heatmap?.fields[1]!;