UI: Add graph option to start the chart's Y axis at zero (#17565)

To reduce main UI clutter, I added a new settings submenu above the chart
itself for the new setting. So far it only has the one new axis setting, but it
could accommodate further settings in the future.

For now I'm only adding a boolean on/off setting to the UI to set the Y axis to
0 or not. However, the underlying stored URL field is already named
y_axis_min={number} and would support other Y axis minima, in case we want to
support custom values in the UI in the future - but then we'd probably also
want to add an axis maximum and possibly other settings.

Fixes https://github.com/prometheus/prometheus/issues/520

Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
Julius Volz 2025-11-20 14:28:18 +01:00 committed by GitHub
parent 5947cc1459
commit 36d054cb2e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 68 additions and 0 deletions

View File

@ -25,6 +25,7 @@ export interface GraphProps {
resolution: GraphResolution;
showExemplars: boolean;
displayMode: GraphDisplayMode;
yAxisMin: number | null;
retriggerIdx: number;
onSelectRange: (start: number, end: number) => void;
}
@ -37,6 +38,7 @@ const Graph: FC<GraphProps> = ({
resolution,
showExemplars,
displayMode,
yAxisMin,
retriggerIdx,
onSelectRange,
}) => {
@ -222,6 +224,7 @@ const Graph: FC<GraphProps> = ({
width={width}
showExemplars={showExemplars}
displayMode={displayMode}
yAxisMin={yAxisMin}
onSelectRange={onSelectRange}
/>
</Box>

View File

@ -7,8 +7,12 @@ import {
SegmentedControl,
Stack,
Skeleton,
ActionIcon,
Popover,
Checkbox,
} from "@mantine/core";
import {
IconAdjustmentsHorizontal,
IconChartAreaFilled,
IconChartLine,
IconGraph,
@ -37,6 +41,7 @@ import ErrorBoundary from "../../components/ErrorBoundary";
import ASTNode from "../../promql/ast";
import serializeNode from "../../promql/serialize";
import ExplainView from "./ExplainViews/ExplainView";
import { actionIconStyle } from "../../styles";
export interface PanelProps {
idx: number;
@ -290,6 +295,39 @@ const QueryPanel: FC<PanelProps> = ({ idx, metricNames }) => {
// },
]}
/>
<Popover position="bottom" withArrow shadow="md">
<Popover.Target>
<ActionIcon
variant="light"
color="gray"
size={32}
title="Graph settings"
>
<IconAdjustmentsHorizontal
style={actionIconStyle}
stroke={1.5}
/>
</ActionIcon>
</Popover.Target>
<Popover.Dropdown p="lg">
<Checkbox
size="xs"
checked={panel.visualizer.yAxisMin !== null}
label="Start Y axis at 0"
onChange={(event) =>
dispatch(
setVisualizer({
idx,
visualizer: {
...panel.visualizer,
yAxisMin: event.currentTarget.checked ? 0 : null,
},
})
)
}
/>
</Popover.Dropdown>
</Popover>
</Group>
</Group>
<Space h="lg" />
@ -301,6 +339,7 @@ const QueryPanel: FC<PanelProps> = ({ idx, metricNames }) => {
resolution={panel.visualizer.resolution}
showExemplars={panel.visualizer.showExemplars}
displayMode={panel.visualizer.displayMode}
yAxisMin={panel.visualizer.yAxisMin}
retriggerIdx={retriggerIdx}
onSelectRange={onSelectRange}
/>

View File

@ -24,6 +24,7 @@ export interface UPlotChartProps {
width: number;
showExemplars: boolean;
displayMode: GraphDisplayMode;
yAxisMin: number | null;
onSelectRange: (start: number, end: number) => void;
}
@ -34,6 +35,7 @@ const UPlotChart: FC<UPlotChartProps> = ({
range: { startTime, endTime, resolution },
width,
displayMode,
yAxisMin,
onSelectRange,
}) => {
const [options, setOptions] = useState<uPlot.Options | null>(null);
@ -60,6 +62,7 @@ const UPlotChart: FC<UPlotChartProps> = ({
width,
data,
useLocalTime,
yAxisMin,
theme === "light",
onSelectRange
);
@ -81,6 +84,7 @@ const UPlotChart: FC<UPlotChartProps> = ({
useLocalTime,
theme,
onSelectRange,
yAxisMin,
]);
if (options === null || processedData === null) {

View File

@ -289,6 +289,7 @@ export const getUPlotOptions = (
width: number,
result: RangeSamples[],
useLocalTime: boolean,
yAxisMin: number | null,
light: boolean,
onSelectRange: (_start: number, _end: number) => void
): uPlot.Options => ({
@ -330,6 +331,17 @@ export const getUPlotOptions = (
focus: {
alpha: 1,
},
scales:
yAxisMin !== null
? {
y: {
range: (_u, _min, max) => {
const minMax = uPlot.rangeNum(yAxisMin, max, 0.1, true);
return [yAxisMin, minMax[1]];
},
},
}
: undefined,
axes: [
// X axis (time).
{

View File

@ -63,6 +63,9 @@ export const decodePanelOptionsFromURLParams = (query: string): Panel[] => {
panel.visualizer.displayMode =
value === "1" ? GraphDisplayMode.Stacked : GraphDisplayMode.Lines;
});
decodeSetting("y_axis_min", (value) => {
panel.visualizer.yAxisMin = value === null ? null : parseFloat(value);
});
decodeSetting("show_exemplars", (value) => {
panel.visualizer.showExemplars = value === "1";
});
@ -171,6 +174,11 @@ export const encodePanelOptionsToURLParams = (
}
addParam(idx, "display_mode", p.visualizer.displayMode);
addParam(
idx,
"y_axis_min",
p.visualizer.yAxisMin === null ? "" : p.visualizer.yAxisMin.toString()
);
addParam(idx, "show_exemplars", p.visualizer.showExemplars ? "1" : "0");
});

View File

@ -58,6 +58,7 @@ export interface Visualizer {
resolution: GraphResolution;
displayMode: GraphDisplayMode;
showExemplars: boolean;
yAxisMin: number | null;
}
export type Panel = {
@ -86,6 +87,7 @@ export const newDefaultPanel = (): Panel => ({
resolution: { type: "auto", density: "medium" },
displayMode: GraphDisplayMode.Lines,
showExemplars: false,
yAxisMin: null,
},
});