Gauge: Add overflow scrolling support for vertical and horizontal orientations (#71690)

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
Nathan Marrs 2023-08-23 02:08:11 +02:00 committed by GitHub
parent 69267cd28b
commit 65df5a0d7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 102 additions and 11 deletions

View File

@ -28,6 +28,8 @@ It extends [SingleStatBaseOptions](#singlestatbaseoptions).
| Property | Type | Required | Default | Description |
|------------------------|-------------------------------------------------|----------|---------|---------------------------------------------------------------------------------------------------------------------------------------------|
| `minVizHeight` | uint32 | **Yes** | `75` | |
| `minVizWidth` | uint32 | **Yes** | `75` | |
| `showThresholdLabels` | boolean | **Yes** | `false` | |
| `showThresholdMarkers` | boolean | **Yes** | `true` | |
| `orientation` | string | No | | *(Inherited from [SingleStatBaseOptions](#singlestatbaseoptions))*<br/>TODO docs<br/>Possible values are: `auto`, `vertical`, `horizontal`. |

View File

@ -14,11 +14,15 @@ import * as common from '@grafana/schema';
export const pluginVersion = "10.2.0-pre";
export interface Options extends common.SingleStatBaseOptions {
minVizHeight: number;
minVizWidth: number;
showThresholdLabels: boolean;
showThresholdMarkers: boolean;
}
export const defaultOptions: Partial<Options> = {
minVizHeight: 75,
minVizWidth: 75,
showThresholdLabels: false,
showThresholdMarkers: true,
};

View File

@ -10,7 +10,7 @@ import {
GAUGE_DEFAULT_MINIMUM,
GrafanaTheme2,
} from '@grafana/data';
import { VizTextDisplayOptions } from '@grafana/schema';
import { VizTextDisplayOptions, VizOrientation } from '@grafana/schema';
import { calculateFontSize } from '../../utils/measureText';
import { clearButtonStyles } from '../Button';
@ -28,6 +28,7 @@ export interface Props {
onClick?: React.MouseEventHandler<HTMLElement>;
className?: string;
theme: GrafanaTheme2;
orientation?: VizOrientation;
}
export class Gauge extends PureComponent<Props> {
@ -52,17 +53,19 @@ export class Gauge extends PureComponent<Props> {
}
draw() {
const { field, showThresholdLabels, showThresholdMarkers, width, height, theme, value } = this.props;
const { field, showThresholdLabels, showThresholdMarkers, width, height, theme, value, orientation } = this.props;
const autoProps = calculateGaugeAutoProps(width, height, value.title);
const dimension = Math.min(width, autoProps.gaugeHeight);
// If the gauge is in vertical layout, we need to set the width of the gauge to the height of the gauge
const calculatedGaugeWidth = orientation === VizOrientation.Vertical ? autoProps.gaugeHeight : width;
const dimension = Math.min(calculatedGaugeWidth, autoProps.gaugeHeight);
const backgroundColor = theme.colors.background.secondary;
const gaugeWidthReduceRatio = showThresholdLabels ? 1.5 : 1;
const gaugeWidth = Math.min(dimension / 5.5, 40) / gaugeWidthReduceRatio;
const thresholdMarkersWidth = gaugeWidth / 5;
const text = formattedValueToString(value);
// This not 100% accurate as I am unsure of flot's calculations here
const valueWidthBase = Math.min(width, dimension * 1.3) * 0.9;
const valueWidthBase = Math.min(calculatedGaugeWidth, dimension * 1.3) * 0.9;
// remove gauge & marker width (on left and right side)
// and 10px is some padding that flot adds to the outer canvas
const valueWidth =
@ -145,11 +148,15 @@ export class Gauge extends PureComponent<Props> {
}
renderVisualization = () => {
const { width, value, height, onClick, text, theme } = this.props;
const autoProps = calculateGaugeAutoProps(width, height, value.title);
const { width, value, height, onClick, text, theme, orientation } = this.props;
const autoProps = calculateGaugeAutoProps(width, height, value.title, orientation);
// If the gauge is in vertical layout, we need to set the width of the gauge to the height of the gauge
const gaugeWidth = orientation === VizOrientation.Vertical ? `${autoProps.gaugeHeight}px` : '100%';
const gaugeElement = (
<div
style={{ height: `${autoProps.gaugeHeight}px`, width: '100%' }}
style={{ height: `${autoProps.gaugeHeight}px`, width: gaugeWidth }}
ref={(element) => (this.canvasElement = element)}
/>
);
@ -172,7 +179,7 @@ export class Gauge extends PureComponent<Props> {
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
position: 'relative',
width: '100%',
width: gaugeWidth,
top: '-4px',
cursor: 'default',
}}

View File

@ -1,8 +1,9 @@
import { FieldColorModeId, FieldConfig, ThresholdsMode } from '@grafana/data';
import { VizOrientation } from '@grafana/schema';
import { getTheme } from '../../themes';
import { getFormattedThresholds } from './utils';
import { calculateGaugeAutoProps, getFormattedThresholds } from './utils';
describe('getFormattedThresholds', () => {
const value = {
@ -53,3 +54,49 @@ describe('getFormattedThresholds', () => {
]);
});
});
describe('calculateGaugeAutoProps', () => {
it('should calculate gauge properties correctly when title is undefined', () => {
const width = 200;
const height = 300;
const orientation = VizOrientation.Horizontal;
const title = undefined;
const result = calculateGaugeAutoProps(width, height, title, orientation);
expect(result.showLabel).toBe(false);
expect(result.gaugeHeight).toBe(200);
expect(result.titleFontSize).toBe(20);
});
it('should calculate gauge properties correctly when title is defined', () => {
const width = 200;
const height = 300;
const orientation = VizOrientation.Vertical;
const title = 'My Gauge';
const result = calculateGaugeAutoProps(width, height, title, orientation);
expect(result.showLabel).toBe(true);
expect(result.gaugeHeight).toBe(200);
expect(result.titleFontSize).toBe(20);
});
it('should calculate gauge properties correctly for vertical and horizontal orientations', () => {
const width = 100;
const height = 150;
const title = 'My Gauge';
// Test for vertical orientation
const verticalResult = calculateGaugeAutoProps(width, height, title, VizOrientation.Vertical);
expect(verticalResult.showLabel).toBe(true);
expect(verticalResult.gaugeHeight).toBe(100);
expect(verticalResult.titleFontSize).toBe(15);
// Test for horizontal orientation
const horizontalResult = calculateGaugeAutoProps(width, height, title, VizOrientation.Horizontal);
expect(horizontalResult.showLabel).toBe(true);
expect(horizontalResult.gaugeHeight).toBe(100);
expect(horizontalResult.titleFontSize).toBe(10);
});
});

View File

@ -12,6 +12,7 @@ import {
ThresholdsConfig,
ThresholdsMode,
} from '@grafana/data';
import { VizOrientation } from '@grafana/schema';
interface GaugeAutoProps {
titleFontSize: number;
@ -27,9 +28,15 @@ export const DEFAULT_THRESHOLDS: ThresholdsConfig = {
],
};
export function calculateGaugeAutoProps(width: number, height: number, title: string | undefined): GaugeAutoProps {
export function calculateGaugeAutoProps(
width: number,
height: number,
title: string | undefined,
orientation?: VizOrientation
): GaugeAutoProps {
const showLabel = title !== null && title !== undefined;
const titleFontSize = Math.min((width * 0.15) / 1.5, 20); // 20% of height * line-height, max 40px
const titleFontSizeDimension = orientation === VizOrientation.Vertical ? height : width;
const titleFontSize = Math.min((titleFontSizeDimension * 0.15) / 1.5, 20); // 20% of height * line-height, max 40px
const titleHeight = titleFontSize * 1.5;
const availableHeight = showLabel ? height - titleHeight : height;
const gaugeHeight = Math.min(availableHeight, width);

View File

@ -31,6 +31,7 @@ export class GaugePanel extends PureComponent<PanelProps<Options>> {
theme={config.theme2}
onClick={openMenu}
className={targetClassName}
orientation={options.orientation}
/>
);
};
@ -90,6 +91,8 @@ export class GaugePanel extends PureComponent<PanelProps<Options>> {
autoGrid={true}
renderCounter={renderCounter}
orientation={options.orientation}
minVizHeight={options.minVizHeight}
minVizWidth={options.minVizWidth}
/>
);
}

View File

@ -1,4 +1,5 @@
import { PanelPlugin } from '@grafana/data';
import { VizOrientation } from '@grafana/schema';
import { commonOptionsBuilder } from '@grafana/ui';
import { addOrientationOption, addStandardDataReduceOptions } from '../stat/common';
@ -37,6 +38,20 @@ export const plugin = new PanelPlugin<Options>(GaugePanel)
name: 'Show threshold markers',
description: 'Renders the thresholds as an outer bar',
defaultValue: defaultOptions.showThresholdMarkers,
})
.addNumberInput({
path: 'minVizWidth',
name: 'Min width',
description: 'Minimum column width',
defaultValue: defaultOptions.minVizWidth,
showIf: (options: Options) => options.orientation === VizOrientation.Vertical,
})
.addNumberInput({
path: 'minVizHeight',
name: 'Min height',
description: 'Minimum row height',
defaultValue: defaultOptions.minVizHeight,
showIf: (options: Options) => options.orientation === VizOrientation.Horizontal,
});
commonOptionsBuilder.addTextSizeOptions(builder);

View File

@ -29,6 +29,8 @@ composableKinds: PanelCfg: {
common.SingleStatBaseOptions
showThresholdLabels: bool | *false
showThresholdMarkers: bool | *true
minVizWidth: uint32 | *75
minVizHeight: uint32 | *75
} @cuetsy(kind="interface")
}
}]

View File

@ -11,11 +11,15 @@
import * as common from '@grafana/schema';
export interface Options extends common.SingleStatBaseOptions {
minVizHeight: number;
minVizWidth: number;
showThresholdLabels: boolean;
showThresholdMarkers: boolean;
}
export const defaultOptions: Partial<Options> = {
minVizHeight: 75,
minVizWidth: 75,
showThresholdLabels: false,
showThresholdMarkers: true,
};