diff --git a/packages/grafana-schema/src/raw/composable/newgauge/panelcfg/x/NewGaugePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/newgauge/panelcfg/x/NewGaugePanelCfg_types.gen.ts index 3c9853535e8..7c29eb7b463 100644 --- a/packages/grafana-schema/src/raw/composable/newgauge/panelcfg/x/NewGaugePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/newgauge/panelcfg/x/NewGaugePanelCfg_types.gen.ts @@ -28,6 +28,7 @@ export interface Options extends common.SingleStatBaseOptions { barShape: ('flat' | 'rounded'); barWidthFactor: number; effects: GaugePanelEffects; + endpointMarker?: ('point' | 'glow' | 'none'); segmentCount: number; segmentSpacing: number; shape: ('circle' | 'gauge'); @@ -40,6 +41,7 @@ export const defaultOptions: Partial = { barShape: 'flat', barWidthFactor: 0.5, effects: {}, + endpointMarker: 'point', segmentCount: 1, segmentSpacing: 0.3, shape: 'gauge', diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialArcPath.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialArcPath.tsx index 29841113167..4a0eabb7866 100644 --- a/packages/grafana-ui/src/components/RadialGauge/RadialArcPath.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/RadialArcPath.tsx @@ -1,11 +1,11 @@ -import { useId, memo, HTMLAttributes } from 'react'; +import { useId, memo, HTMLAttributes, ReactElement } from 'react'; import { FieldDisplay } from '@grafana/data'; import { useTheme2 } from '../../themes/ThemeContext'; -import { buildGradientColors, getEndpointColors, getGradientCss, getGuideDotColors } from './colors'; -import { RadialGradientMode, RadialShape, RadialGaugeDimensions } from './types'; +import { buildGradientColors, getBarEndcapColors, getGradientCss, getEndpointMarkerColors } from './colors'; +import { RadialShape, RadialGaugeDimensions } from './types'; import { drawRadialArcPath, toRad } from './utils'; export interface RadialArcPathPropsBase { @@ -13,12 +13,13 @@ export interface RadialArcPathPropsBase { color?: string; dimensions: RadialGaugeDimensions; fieldDisplay: FieldDisplay; - glowFilter?: string; - gradientMode: RadialGradientMode; + gradient?: boolean; roundedBars?: boolean; shape: RadialShape; - showGuideDots?: boolean; + endpointMarker?: 'point' | 'glow'; startAngle: number; + glowFilter?: string; + endpointMarkerGlowFilter?: string; } interface RadialArcPathPropsWithGuideDot extends RadialArcPathPropsBase { @@ -40,27 +41,18 @@ export const RadialArcPath = memo( color, dimensions, fieldDisplay, - glowFilter, - gradientMode, + gradient, roundedBars, shape, - showGuideDots, + endpointMarker, startAngle: angle, + glowFilter, + endpointMarkerGlowFilter, }: RadialArcPathProps) => { const theme = useTheme2(); const id = useId(); - const gradientStops = buildGradientColors(gradientMode, theme, fieldDisplay, fieldDisplay.display.color); - - let guideDotColors: [string, string] | undefined; - let endpointColors: [string, string] | undefined; - - if (showGuideDots && gradientStops.length > 0) { - guideDotColors = getGuideDotColors(gradientStops, fieldDisplay.display.percent); - if (shape === 'circle') { - endpointColors = getEndpointColors(gradientStops, fieldDisplay.display.percent); - } - } + const gradientStops = buildGradientColors(gradient, theme, fieldDisplay, fieldDisplay.display.color); const bgDivStyle: HTMLAttributes['style'] = { width: '100%', height: '100%' }; if (color) { @@ -76,12 +68,65 @@ export const RadialArcPath = memo( const startRadians = toRad(angle); const endRadians = toRad(angle + arcLengthDeg); - const x1 = centerX + radius * Math.cos(startRadians); - const y1 = centerY + radius * Math.sin(startRadians); - const x2 = centerX + radius * Math.cos(endRadians); - const y2 = centerY + radius * Math.sin(endRadians); + const xStart = centerX + radius * Math.cos(startRadians); + const yStart = centerY + radius * Math.sin(startRadians); + const xEnd = centerX + radius * Math.cos(endRadians); + const yEnd = centerY + radius * Math.sin(endRadians); - const dotRadius = Math.min((barWidth / 2) * DOT_RADIUS_FACTOR, MAX_DOT_RADIUS); + const dotRadius = + endpointMarker === 'point' ? Math.min((barWidth / 2) * DOT_RADIUS_FACTOR, MAX_DOT_RADIUS) : barWidth / 2; + + let barEndcapColors: [string | undefined, string | undefined] | undefined; + const endpointMarks: ReactElement[] = []; + if (endpointMarker && gradientStops.length > 0) { + switch (endpointMarker) { + case 'point': + const [pointColorStart, pointColorEnd] = getEndpointMarkerColors(gradientStops, fieldDisplay.display.percent); + if (arcLengthDeg > DOT_START_MIN_ANGLE_DEG) { + endpointMarks.push( + + ); + } + endpointMarks.push( + + ); + break; + case 'glow': + const xStartMark = centerX + radius * Math.cos(endRadians - 0.2); + const yStartMark = centerY + radius * Math.sin(endRadians - 0.2); + endpointMarks.push( + + ); + break; + default: + break; + } + + if (shape === 'circle') { + barEndcapColors = getBarEndcapColors(gradientStops, fieldDisplay.display.percent); + } + } return ( <> @@ -100,14 +145,13 @@ export const RadialArcPath = memo( >
- {endpointColors && } - {endpointColors && } + {barEndcapColors?.[0] && } + {barEndcapColors?.[1] && ( + + )} - {guideDotColors && arcLengthDeg > DOT_START_MIN_ANGLE_DEG && ( - - )} - {guideDotColors && } + {endpointMarks} ); } diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialBar.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialBar.tsx index cf2a50b55cb..45107751f67 100644 --- a/packages/grafana-ui/src/components/RadialGauge/RadialBar.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/RadialBar.tsx @@ -3,29 +3,33 @@ import { FieldDisplay } from '@grafana/data'; import { useTheme2 } from '../../themes/ThemeContext'; import { RadialArcPath } from './RadialArcPath'; -import { RadialGradientMode, RadialShape, RadialGaugeDimensions } from './types'; +import { RadialShape, RadialGaugeDimensions } from './types'; export interface RadialBarProps { angle: number; angleRange: number; dimensions: RadialGaugeDimensions; fieldDisplay: FieldDisplay; - glowFilter?: string; - gradientMode: RadialGradientMode; + gradient?: boolean; roundedBars?: boolean; + endpointMarker?: 'point' | 'glow'; shape: RadialShape; startAngle: number; + glowFilter?: string; + endpointMarkerGlowFilter?: string; } export function RadialBar({ angle, angleRange, dimensions, fieldDisplay, - glowFilter, - gradientMode, + gradient, roundedBars, + endpointMarker, shape, startAngle, + glowFilter, + endpointMarkerGlowFilter, }: RadialBarProps) { const theme = useTheme2(); return ( @@ -36,7 +40,6 @@ export function RadialBar({ fieldDisplay={fieldDisplay} color={theme.colors.action.hover} dimensions={dimensions} - gradientMode="none" roundedBars={roundedBars} shape={shape} startAngle={startAngle + angle} @@ -44,15 +47,16 @@ export function RadialBar({ {/** The colored bar */} ); diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialBarSegmented.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialBarSegmented.tsx index d86baa5d1b5..5995d65dcbd 100644 --- a/packages/grafana-ui/src/components/RadialGauge/RadialBarSegmented.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/RadialBarSegmented.tsx @@ -5,7 +5,7 @@ import { FALLBACK_COLOR, FieldDisplay } from '@grafana/data'; import { useTheme2 } from '../../themes/ThemeContext'; import { RadialArcPath } from './RadialArcPath'; -import { RadialGradientMode, RadialShape, RadialGaugeDimensions } from './types'; +import { RadialShape, RadialGaugeDimensions } from './types'; import { getAngleBetweenSegments, getFieldConfigMinMax, @@ -22,7 +22,7 @@ export interface RadialBarSegmentedProps { segmentCount: number; segmentSpacing: number; shape: RadialShape; - gradientMode: RadialGradientMode; + gradient?: boolean; } export const RadialBarSegmented = memo( @@ -32,10 +32,10 @@ export const RadialBarSegmented = memo( startAngle, angleRange, glowFilter, + gradient, segmentCount, segmentSpacing, shape, - gradientMode, }: RadialBarSegmentedProps) => { const theme = useTheme2(); @@ -53,21 +53,21 @@ export const RadialBarSegmented = memo( let segmentColor: string | undefined; if (angleValue >= value) { segmentColor = theme.colors.action.hover; - } else if (gradientMode === 'none') { + } else if (!gradient) { segmentColor = displayProcessor(angleValue).color ?? FALLBACK_COLOR; } segments.push( ); } diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialGauge.story.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialGauge.story.tsx index 0bd1e4abf09..e6d6da7366c 100644 --- a/packages/grafana-ui/src/components/RadialGauge/RadialGauge.story.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/RadialGauge.story.tsx @@ -14,7 +14,7 @@ import { useTheme2 } from '../../themes/ThemeContext'; import { Stack } from '../Layout/Stack/Stack'; import { RadialGauge, RadialGaugeProps } from './RadialGauge'; -import { RadialGradientMode, RadialShape, RadialTextMode } from './types'; +import { RadialShape, RadialTextMode } from './types'; interface StoryProps extends RadialGaugeProps { value: number; @@ -60,7 +60,7 @@ const meta: Meta = { width: 200, height: 200, shape: 'circle', - gradient: 'none', + gradient: false, seriesCount: 1, segmentCount: 0, segmentSpacing: 0.2, @@ -77,7 +77,7 @@ const meta: Meta = { roundedBars: { control: 'boolean' }, sparkline: { control: 'boolean' }, thresholdsBar: { control: 'boolean' }, - gradient: { control: { type: 'radio' } }, + gradient: { control: { type: 'boolean' } }, seriesCount: { control: { type: 'range', min: 1, max: 20 } }, segmentCount: { control: { type: 'range', min: 0, max: 100 } }, segmentSpacing: { control: { type: 'range', min: 0, max: 1, step: 0.01 } }, @@ -119,41 +119,17 @@ export const Examples: StoryFn = (args) => {
Bar width
- - - - + + + +
Effects
- - - - + + + +
Shape: Gauge & color scale
@@ -161,14 +137,14 @@ export const Examples: StoryFn = (args) => { value={40} shape="gauge" width={250} - gradient="auto" + gradient colorScheme={FieldColorModeId.ContinuousGrYlRd} glowCenter={true} barWidthFactor={0.6} /> = (args) => { value={args.value ?? 70} color="blue" shape="gauge" - gradient="auto" + gradient sparkline={true} glowBar={true} glowCenter={true} @@ -194,7 +170,7 @@ export const Examples: StoryFn = (args) => { value={args.value ?? 30} color="green" shape="gauge" - gradient="auto" + gradient sparkline={true} glowBar={true} glowCenter={true} @@ -205,7 +181,7 @@ export const Examples: StoryFn = (args) => { color="red" shape="gauge" width={250} - gradient="auto" + gradient sparkline={true} glowBar={true} glowCenter={true} @@ -216,7 +192,7 @@ export const Examples: StoryFn = (args) => { color="red" width={250} shape="gauge" - gradient="auto" + gradient sparkline={true} glowBar={true} glowCenter={true} @@ -228,7 +204,7 @@ export const Examples: StoryFn = (args) => { = (args) => { = (args) => { = (args) => { width={250} colorScheme={FieldColorModeId.ContinuousGrYlRd} shape="gauge" - gradient="auto" + gradient glowBar={true} glowCenter={true} segmentCount={40} @@ -280,7 +256,7 @@ export const Examples: StoryFn = (args) => { = (args) => { value={args.value ?? 70} width={250} colorScheme={FieldColorModeId.Thresholds} - gradient="auto" + gradient glowCenter={true} thresholdsBar={true} roundedBars={false} @@ -301,7 +277,7 @@ export const Examples: StoryFn = (args) => { value={args.value ?? 70} width={250} colorScheme={FieldColorModeId.Thresholds} - gradient="auto" + gradient glowCenter={true} thresholdsBar={true} roundedBars={false} @@ -347,7 +323,6 @@ export const Temp: StoryFn = (args) => { }; interface ExampleProps { - gradient?: RadialGradientMode; color?: string; seriesName?: string; value?: number; @@ -356,6 +331,7 @@ interface ExampleProps { max?: number; width?: number; height?: number; + gradient?: boolean; glowBar?: boolean; glowCenter?: boolean; barWidthFactor?: number; @@ -373,7 +349,6 @@ interface ExampleProps { } export function RadialGaugeExample({ - gradient = 'none', color, seriesName = 'Server A', value = 70, @@ -382,6 +357,7 @@ export function RadialGaugeExample({ max = 100, width = 200, height = 200, + gradient = false, glowBar = false, glowCenter = false, barWidthFactor = 0.4, diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialGauge.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialGauge.tsx index 7cf4ab3fa76..9cb6e55584f 100644 --- a/packages/grafana-ui/src/components/RadialGauge/RadialGauge.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/RadialGauge.tsx @@ -13,8 +13,8 @@ import { RadialScaleLabels } from './RadialScaleLabels'; import { RadialSparkline } from './RadialSparkline'; import { RadialText } from './RadialText'; import { ThresholdsBar } from './ThresholdsBar'; -import { GlowGradient, MiddleCircleGlow } from './effects'; -import { RadialGradientMode, RadialShape, RadialTextMode } from './types'; +import { GlowGradient, MiddleCircleGlow, SpotlightGradient } from './effects'; +import { RadialShape, RadialTextMode } from './types'; import { calculateDimensions, getValueAngleForValue } from './utils'; export interface RadialGaugeProps { @@ -25,7 +25,7 @@ export interface RadialGaugeProps { * Circle or gauge (partial circle) */ shape?: RadialShape; - gradient?: RadialGradientMode; + gradient?: boolean; /** * Bar width is always relative to size of the gauge. * But this gives you control over the width relative to size. @@ -37,6 +37,10 @@ export interface RadialGaugeProps { glowCenter?: boolean; roundedBars?: boolean; thresholdsBar?: boolean; + /** + * Specify if an endpoint marker should be shown at the end of the bar + */ + endpointMarker?: 'point' | 'glow'; /** * Number of segments depends on size of gauge but this * factor 1-10 gives you relative control @@ -74,7 +78,7 @@ export function RadialGauge(props: RadialGaugeProps) { width = 256, height = 256, shape = 'circle', - gradient = 'none', + gradient = false, barWidthFactor = 0.4, glowBar = false, glowCenter = false, @@ -85,6 +89,7 @@ export function RadialGauge(props: RadialGaugeProps) { roundedBars = true, thresholdsBar = false, showScaleLabels = false, + endpointMarker, onClick, values, } = props; @@ -120,8 +125,24 @@ export function RadialGauge(props: RadialGaugeProps) { showScaleLabels ); + // FIXME: I want to move the ids for these filters into a context which the children + // can reference via a hook, rather than passing them down as props + const spotlightGradientId = `spotlight-${barIndex}-${gaugeId}`; const glowFilterId = `glow-${gaugeId}`; + if (endpointMarker === 'glow') { + defs.push( + + ); + } + if (segmentCount > 1) { graphics.push( ); } else { @@ -147,9 +168,11 @@ export function RadialGauge(props: RadialGaugeProps) { startAngle={startAngle} roundedBars={roundedBars} glowFilter={`url(#${glowFilterId})`} + endpointMarkerGlowFilter={`url(#${spotlightGradientId})`} shape={shape} - gradientMode={gradient} + gradient={gradient} fieldDisplay={displayValue} + endpointMarker={endpointMarker} /> ); } @@ -210,7 +233,7 @@ export function RadialGauge(props: RadialGaugeProps) { roundedBars={roundedBars} glowFilter={`url(#${glowFilterId})`} shape={shape} - gradientMode={gradient} + gradient={gradient} /> ); } diff --git a/packages/grafana-ui/src/components/RadialGauge/ThresholdsBar.tsx b/packages/grafana-ui/src/components/RadialGauge/ThresholdsBar.tsx index 5db86596dbe..e9137d68ae1 100644 --- a/packages/grafana-ui/src/components/RadialGauge/ThresholdsBar.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/ThresholdsBar.tsx @@ -1,7 +1,7 @@ import { FieldDisplay, Threshold } from '@grafana/data'; import { RadialArcPath } from './RadialArcPath'; -import { RadialGaugeDimensions, RadialGradientMode, RadialShape } from './types'; +import { RadialGaugeDimensions, RadialShape } from './types'; import { getFieldConfigMinMax } from './utils'; interface ThresholdsBarProps { @@ -14,7 +14,7 @@ interface ThresholdsBarProps { roundedBars?: boolean; glowFilter?: string; thresholds: Threshold[]; - gradientMode: RadialGradientMode; + gradient?: boolean; } export function ThresholdsBar({ @@ -26,7 +26,7 @@ export function ThresholdsBar({ glowFilter, thresholds, shape, - gradientMode, + gradient, }: ThresholdsBarProps) { const thresholdDimensions = { ...dimensions, @@ -50,7 +50,7 @@ export function ThresholdsBar({ } const lengthDeg = valueDeg - currentStart + startAngle; - const color = gradientMode === 'none' ? threshold.color : undefined; + const color = gradient ? undefined : threshold.color; paths.push( = { @@ -49,9 +49,16 @@ describe('RadialGauge color utils', () => { }, }); - it('should return the baseColor if gradientMode is none', () => { + it('should return the baseColor if gradient is false-y', () => { expect( - buildGradientColors('none', createTheme(), buildFieldDisplay(createField(FieldColorModeId.Fixed)), '#FF0000') + buildGradientColors(false, createTheme(), buildFieldDisplay(createField(FieldColorModeId.Fixed)), '#FF0000') + ).toEqual([ + { color: '#FF0000', percent: 0 }, + { color: '#FF0000', percent: 1 }, + ]); + + expect( + buildGradientColors(undefined, createTheme(), buildFieldDisplay(createField(FieldColorModeId.Fixed)), '#FF0000') ).toEqual([ { color: '#FF0000', percent: 0 }, { color: '#FF0000', percent: 1 }, @@ -59,18 +66,18 @@ describe('RadialGauge color utils', () => { }); it('uses the fallback color if no baseColor is set', () => { - expect( - buildGradientColors('none', createTheme(), buildFieldDisplay(createField(FieldColorModeId.Fixed))) - ).toEqual([ - { color: FALLBACK_COLOR, percent: 0 }, - { color: FALLBACK_COLOR, percent: 1 }, - ]); + expect(buildGradientColors(false, createTheme(), buildFieldDisplay(createField(FieldColorModeId.Fixed)))).toEqual( + [ + { color: FALLBACK_COLOR, percent: 0 }, + { color: FALLBACK_COLOR, percent: 1 }, + ] + ); }); it('should map threshold colors correctly (with baseColor if displayProcessor does not return colors)', () => { expect( buildGradientColors( - 'auto', + true, createTheme(), buildFieldDisplay(createField(FieldColorModeId.Thresholds), { view: { getFieldDisplayProcessor: jest.fn(() => jest.fn(() => ({ color: '#444444' }))) }, @@ -81,19 +88,14 @@ describe('RadialGauge color utils', () => { it('should map threshold colors correctly (with baseColor if displayProcessor does not return colors)', () => { expect( - buildGradientColors( - 'auto', - createTheme(), - buildFieldDisplay(createField(FieldColorModeId.Thresholds)), - '#FF0000' - ) + buildGradientColors(true, createTheme(), buildFieldDisplay(createField(FieldColorModeId.Thresholds)), '#FF0000') ).toMatchSnapshot(); }); it('should return gradient colors for continuous color modes', () => { expect( buildGradientColors( - 'auto', + true, createTheme(), buildFieldDisplay(createField(FieldColorModeId.ContinuousCividis)), '#00FF00' @@ -104,7 +106,7 @@ describe('RadialGauge color utils', () => { it.each(['dark', 'light'] as const)('should return gradient colors for by-value color mode in %s theme', (mode) => { expect( buildGradientColors( - 'auto', + true, createTheme({ colors: { mode } }), buildFieldDisplay(createField(FieldColorModeId.ContinuousBlues)) ) @@ -114,7 +116,7 @@ describe('RadialGauge color utils', () => { it.each(['dark', 'light'] as const)('should return gradient colors for fixed color mode in %s theme', (mode) => { expect( buildGradientColors( - 'auto', + true, createTheme({ colors: { mode } }), buildFieldDisplay(createField(FieldColorModeId.Fixed)), '#442299' @@ -168,14 +170,14 @@ describe('RadialGauge color utils', () => { }); }); - describe('getEndpointColors', () => { + describe('getBarEndcapColors', () => { it('should return the first and last colors in the gradient', () => { const gradient = [ { color: '#ff0000', percent: 0 }, { color: '#00ff00', percent: 0.5 }, { color: '#0000ff', percent: 1 }, ]; - const [startColor, endColor] = getEndpointColors(gradient); + const [startColor, endColor] = getBarEndcapColors(gradient); expect(startColor).toBe('#ff0000'); expect(endColor).toBe('#0000ff'); }); @@ -186,22 +188,22 @@ describe('RadialGauge color utils', () => { { color: '#00ff00', percent: 0.5 }, { color: '#0000ff', percent: 1 }, ]; - const [startColor, endColor] = getEndpointColors(gradient, 0.25); + const [startColor, endColor] = getBarEndcapColors(gradient, 0.25); expect(startColor).toBe('#ff0000'); expect(endColor).toBe('#808000'); }); it('should handle gradients with only one colors', () => { const gradient = [{ color: '#ff0000', percent: 0 }]; - const [startColor, endColor] = getEndpointColors(gradient); + const [startColor, endColor] = getBarEndcapColors(gradient); expect(startColor).toBe('#ff0000'); expect(endColor).toBe('#ff0000'); }); it('should throw an error when no colors are provided', () => { expect(() => { - getEndpointColors([]); - }).toThrow('getEndpointColors requires at least one color stop'); + getBarEndcapColors([]); + }).toThrow('getBarEndcapColors requires at least one color stop'); }); }); @@ -227,14 +229,14 @@ describe('RadialGauge color utils', () => { }); }); - describe('getGuideDotColors', () => { + describe('getEndpointMarkerColors', () => { it('should return contrasting guide dot colors based on the gradient endpoints and percent', () => { const gradient = [ { color: '#000000', percent: 0 }, { color: '#ffffff', percent: 0.5 }, { color: '#ffffff', percent: 1 }, ]; - const [startDotColor, endDotColor] = getGuideDotColors(gradient, 0.35); + const [startDotColor, endDotColor] = getEndpointMarkerColors(gradient, 0.35); expect(startDotColor).toBe('#fbfbfb'); expect(endDotColor).toBe('#111217'); }); diff --git a/packages/grafana-ui/src/components/RadialGauge/colors.ts b/packages/grafana-ui/src/components/RadialGauge/colors.ts index 58053d6cbe7..4cf5d7ca84e 100644 --- a/packages/grafana-ui/src/components/RadialGauge/colors.ts +++ b/packages/grafana-ui/src/components/RadialGauge/colors.ts @@ -3,16 +3,16 @@ import tinycolor from 'tinycolor2'; import { colorManipulator, FALLBACK_COLOR, FieldDisplay, getFieldColorMode, GrafanaTheme2 } from '@grafana/data'; import { FieldColorModeId } from '@grafana/schema'; -import { GradientStop, RadialGradientMode, RadialShape } from './types'; +import { GradientStop, RadialShape } from './types'; import { getFieldConfigMinMax, getFieldDisplayProcessor } from './utils'; export function buildGradientColors( - gradientMode: RadialGradientMode, + gradient = false, theme: GrafanaTheme2, fieldDisplay: FieldDisplay, baseColor = FALLBACK_COLOR ): GradientStop[] { - if (gradientMode === 'none') { + if (!gradient) { return [ { color: baseColor, percent: 0 }, { color: baseColor, percent: 1 }, @@ -67,16 +67,14 @@ export function buildGradientColors( // for fixed colors and other modes, we create a simple two-color gradient // we set the highest contrast color second based on the theme. - const darkerColor = tinycolor(baseColor).spin(5).darken(35).saturate(20); + const darkerColor = tinycolor(baseColor).spin(5).darken(20).saturate(25); const lighterColor = tinycolor(baseColor) .spin(-5) .brighten(theme.isDark ? 30 : 15) - .lighten(theme.isDark ? 35 : 15) - .desaturate(10); + .lighten(theme.isDark ? 35 : 15); return [ { color: theme.isDark ? darkerColor.toString() : lighterColor.toString(), percent: 0 }, - { color: baseColor, percent: 0.45 }, - { color: baseColor, percent: 0.55 }, + { color: baseColor, percent: 0.33 }, { color: theme.isDark ? lighterColor.toString() : darkerColor.toString(), percent: 1 }, ]; } @@ -138,9 +136,9 @@ export function colorAtGradientPercent(stops: GradientStop[], percent: number): return mixed; } -export function getEndpointColors(gradientStops: GradientStop[], percent = 1): [string, string] { +export function getBarEndcapColors(gradientStops: GradientStop[], percent = 1): [string, string] { if (gradientStops.length === 0) { - throw new Error('getEndpointColors requires at least one color stop'); + throw new Error('getBarEndcapColors requires at least one color stop'); } const startColor = gradientStops[0].color; @@ -173,7 +171,7 @@ const getGuideDotColor = (color: string): string => { return colorManipulator.getContrastRatio(darkColor, color) >= CONTRAST_THRESHOLD_MAX ? darkColor : lightColor; }; -export function getGuideDotColors(gradientStops: GradientStop[], percent = 1): [string, string] { - const [startColor, endColor] = getEndpointColors(gradientStops, percent); +export function getEndpointMarkerColors(gradientStops: GradientStop[], percent = 1): [string, string] { + const [startColor, endColor] = getBarEndcapColors(gradientStops, percent); return [getGuideDotColor(startColor), getGuideDotColor(endColor)]; } diff --git a/packages/grafana-ui/src/components/RadialGauge/effects.tsx b/packages/grafana-ui/src/components/RadialGauge/effects.tsx index c8f7afb542f..c48307f177e 100644 --- a/packages/grafana-ui/src/components/RadialGauge/effects.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/effects.tsx @@ -1,3 +1,5 @@ +import { GrafanaTheme2 } from '@grafana/data'; + import { RadialGaugeDimensions } from './types'; export interface GlowGradientProps { @@ -57,3 +59,36 @@ export function MiddleCircleGlow({ dimensions, gaugeId, color }: CenterGlowProps ); } + +export function SpotlightGradient({ + id, + dimensions, + roundedBars, + angle, + theme, +}: { + id: string; + dimensions: RadialGaugeDimensions; + angle: number; + roundedBars: boolean; + theme: GrafanaTheme2; +}) { + if (theme.isLight) { + return null; + } + + const angleRadian = ((angle - 90) * Math.PI) / 180; + + let x1 = dimensions.centerX + dimensions.radius * Math.cos(angleRadian - 0.2); + let y1 = dimensions.centerY + dimensions.radius * Math.sin(angleRadian - 0.2); + let x2 = dimensions.centerX + dimensions.radius * Math.cos(angleRadian); + let y2 = dimensions.centerY + dimensions.radius * Math.sin(angleRadian); + + return ( + + + + {roundedBars && } + + ); +} diff --git a/packages/grafana-ui/src/components/RadialGauge/types.ts b/packages/grafana-ui/src/components/RadialGauge/types.ts index 67084a3fb63..cc233dd524c 100644 --- a/packages/grafana-ui/src/components/RadialGauge/types.ts +++ b/packages/grafana-ui/src/components/RadialGauge/types.ts @@ -1,4 +1,3 @@ -export type RadialGradientMode = 'none' | 'auto'; export type RadialTextMode = 'auto' | 'value_and_name' | 'value' | 'name' | 'none'; export type RadialShape = 'circle' | 'gauge'; diff --git a/public/app/plugins/panel/radialbar/RadialBarPanel.tsx b/public/app/plugins/panel/radialbar/RadialBarPanel.tsx index 7c2a0cc9081..03232406463 100644 --- a/public/app/plugins/panel/radialbar/RadialBarPanel.tsx +++ b/public/app/plugins/panel/radialbar/RadialBarPanel.tsx @@ -37,7 +37,7 @@ export function RadialBarPanel({ width={width} height={height} barWidthFactor={options.barWidthFactor} - gradient={options.effects?.gradient ? 'auto' : 'none'} + gradient={options.effects?.gradient} glowBar={options.effects?.barGlow} glowCenter={options.effects?.centerGlow} roundedBars={options.barShape === 'rounded'} @@ -50,6 +50,7 @@ export function RadialBarPanel({ alignmentFactors={valueProps.alignmentFactors} valueManualFontSize={options.text?.valueSize} nameManualFontSize={options.text?.titleSize} + endpointMarker={options.endpointMarker !== 'none' ? options.endpointMarker : undefined} onClick={menuProps.openMenu} /> ); diff --git a/public/app/plugins/panel/radialbar/module.tsx b/public/app/plugins/panel/radialbar/module.tsx index f7cd5fb043f..e1719847632 100644 --- a/public/app/plugins/panel/radialbar/module.tsx +++ b/public/app/plugins/panel/radialbar/module.tsx @@ -71,6 +71,22 @@ export const plugin = new PanelPlugin(RadialBarPanel) showIf: (options) => options.segmentCount === 1, }); + builder.addRadio({ + path: 'endpointMarker', + name: t('radialbar.config.endpoint-marker', 'Endpoint marker'), + description: t('radialbar.config.endpoint-marker-description', 'Glow is only supported in dark mode.'), + category, + defaultValue: defaultOptions.endpointMarker, + settings: { + options: [ + { value: 'point', label: t('radialbar.config.endpoint-marker-point', 'Point') }, + { value: 'glow', label: t('radialbar.config.endpoint-marker-glow', 'Glow') }, + { value: 'none', label: t('radialbar.config.endpoint-marker-none', 'None') }, + ], + }, + showIf: (options) => options.barShape === 'rounded' && options.segmentCount === 1, + }); + builder.addSliderInput({ path: 'barWidthFactor', name: t('radialbar.config.bar-width', 'Bar width'), diff --git a/public/app/plugins/panel/radialbar/panelcfg.cue b/public/app/plugins/panel/radialbar/panelcfg.cue index 89df6eddc26..c959512c0b8 100644 --- a/public/app/plugins/panel/radialbar/panelcfg.cue +++ b/public/app/plugins/panel/radialbar/panelcfg.cue @@ -28,7 +28,7 @@ composableKinds: PanelCfg: { GaugePanelEffects: { barGlow?: bool | *false centerGlow?: bool | *false - gradient?: bool | *true + gradient?: bool | *true } @cuetsy(kind="interface") Options: { @@ -40,7 +40,8 @@ composableKinds: PanelCfg: { sparkline?: bool | *true shape: "circle" | *"gauge" barWidthFactor: number | *0.5 - barShape: "flat" | "rounded" | *"flat" + barShape: "flat" | "rounded" | *"flat" + endpointMarker?: "point" | "glow" | "none" | *"point" effects: GaugePanelEffects | *{} } @cuetsy(kind="interface") } diff --git a/public/app/plugins/panel/radialbar/panelcfg.gen.ts b/public/app/plugins/panel/radialbar/panelcfg.gen.ts index ddb7b522365..e050a044f77 100644 --- a/public/app/plugins/panel/radialbar/panelcfg.gen.ts +++ b/public/app/plugins/panel/radialbar/panelcfg.gen.ts @@ -26,6 +26,7 @@ export interface Options extends common.SingleStatBaseOptions { barShape: ('flat' | 'rounded'); barWidthFactor: number; effects: GaugePanelEffects; + endpointMarker?: ('point' | 'glow' | 'none'); segmentCount: number; segmentSpacing: number; shape: ('circle' | 'gauge'); @@ -38,6 +39,7 @@ export const defaultOptions: Partial = { barShape: 'flat', barWidthFactor: 0.5, effects: {}, + endpointMarker: 'point', segmentCount: 1, segmentSpacing: 0.3, shape: 'gauge',