diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialArcPath.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialArcPath.tsx
index a6278294029..b1a068c93a6 100644
--- a/packages/grafana-ui/src/components/RadialGauge/RadialArcPath.tsx
+++ b/packages/grafana-ui/src/components/RadialGauge/RadialArcPath.tsx
@@ -1,11 +1,18 @@
+import { useId } from 'react';
+
+import { colorManipulator } from '@grafana/data';
+
+import { RadialColorDefs } from './RadialColorDefs';
import { GaugeDimensions, toRad } from './utils';
export interface RadialArcPathPropsBase {
startAngle: number;
dimensions: GaugeDimensions;
- color: string;
- glowFilter?: string;
+ colorDef: RadialColorDefs;
arcLengthDeg: number;
+ color?: string;
+ gradient?: string;
+ glowFilter?: string;
roundedBars?: boolean;
showGuideDots?: boolean;
guideDotStartColor?: string;
@@ -22,17 +29,17 @@ type RadialArcPathProps = RadialArcPathPropsBase | RadialArcPathPropsWithGuideDo
const MAX_DOT_RADIUS = 8;
-export function RadialArcPath({
- startAngle: angle,
- dimensions,
- color,
- glowFilter,
+function drawRadialArcPath({
+ angle,
arcLengthDeg,
+ dimensions,
roundedBars,
- showGuideDots,
- guideDotStartColor,
- guideDotEndColor,
-}: RadialArcPathProps) {
+}: {
+ angle: number;
+ dimensions: GaugeDimensions;
+ arcLengthDeg: number;
+ roundedBars?: boolean;
+}): string {
const { radius, centerX, centerY, barWidth } = dimensions;
if (arcLengthDeg === 360) {
@@ -43,29 +50,111 @@ export function RadialArcPath({
const startRadians = toRad(angle);
const endRadians = toRad(angle + arcLengthDeg);
+ const largeArc = arcLengthDeg > 180 ? 1 : 0;
+
+ const outerR = radius + barWidth / 2;
+ const innerR = Math.max(0, radius - barWidth / 2);
+
+ const ox1 = centerX + outerR * Math.cos(startRadians);
+ const oy1 = centerY + outerR * Math.sin(startRadians);
+ const ox2 = centerX + outerR * Math.cos(endRadians);
+ const oy2 = centerY + outerR * Math.sin(endRadians);
+
+ const ix1 = centerX + innerR * Math.cos(startRadians);
+ const iy1 = centerY + innerR * Math.sin(startRadians);
+ const ix2 = centerX + innerR * Math.cos(endRadians);
+ const iy2 = centerY + innerR * Math.sin(endRadians);
+
+ const capR = barWidth / 2;
+
+ const pathParts = [
+ // start at outer start
+ 'M',
+ ox1,
+ oy1,
+ // outer arc from start to end (clockwise)
+ 'A',
+ outerR,
+ outerR,
+ 0,
+ largeArc,
+ 1,
+ ox2,
+ oy2,
+ ];
+
+ if (roundedBars) {
+ // rounded end cap: small arc connecting outer end to inner end
+ pathParts.push('A', capR, capR, 0, 0, 1, ix2, iy2);
+ } else {
+ // straight line to inner end
+ pathParts.push('L', ix2, iy2);
+ }
+
+ if (innerR <= 0) {
+ // if inner radius collapsed to center, line to center and close
+ pathParts.push('L', centerX, centerY, 'Z');
+ } else {
+ // inner arc from end back to start (counter-clockwise)
+ pathParts.push('A', innerR, innerR, 0, largeArc, 0, ix1, iy1);
+
+ if (roundedBars) {
+ // rounded start cap: small arc connecting inner start back to outer start
+ pathParts.push('A', capR, capR, 0, 0, 1, ox1, oy1);
+ } else {
+ // straight line back to outer start
+ pathParts.push('L', ox1, oy1);
+ }
+
+ pathParts.push('Z');
+ }
+
+ return pathParts.join(' ');
+}
+
+export function RadialArcPath({
+ startAngle: angle,
+ dimensions,
+ color,
+ colorDef,
+ gradient,
+ arcLengthDeg,
+ roundedBars,
+ showGuideDots,
+ guideDotStartColor,
+ guideDotEndColor,
+}: RadialArcPathProps) {
+ const id = useId();
+ const { radius, centerX, centerY, barWidth } = dimensions;
+
+ const startRadians = toRad(angle);
+ const endRadians = toRad(angle + arcLengthDeg);
+ const gradientList = colorDef?.getGradient();
+
+ const path = drawRadialArcPath({ angle, arcLengthDeg, dimensions, roundedBars });
let x1 = centerX + radius * Math.cos(startRadians);
let y1 = centerY + radius * Math.sin(startRadians);
let x2 = centerX + radius * Math.cos(endRadians);
let y2 = centerY + radius * Math.sin(endRadians);
- const largeArc = arcLengthDeg > 180 ? 1 : 0;
-
- const path = ['M', x1, y1, 'A', radius, radius, 0, largeArc, 1, x2, y2].join(' ');
const dotRadius = Math.min((barWidth / 2) * 0.4, MAX_DOT_RADIUS);
return (
<>
-
+
+
+
+
+
+
+
+ {roundedBars && gradientList && (
+ <>
+
+ {/* this would actually need to be the color determined by the display */}
+
+ >
+ )}
{showGuideDots && (
<>
{arcLengthDeg > 5 && }
diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialBar.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialBar.tsx
index c5552ad5bab..bb0dac60d45 100644
--- a/packages/grafana-ui/src/components/RadialGauge/RadialBar.tsx
+++ b/packages/grafana-ui/src/components/RadialGauge/RadialBar.tsx
@@ -41,7 +41,7 @@ export function RadialBar({
dimensions={dimensions}
startAngle={startAngle}
arcLengthDeg={angle}
- color={colorDefs.getMainBarColor()}
+ gradient={colorDefs.getGradientDef()}
roundedBars={roundedBars}
glowFilter={glowFilter}
showGuideDots={roundedBars}
diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialColorDefs.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialColorDefs.tsx
index 9e42d7b1bc2..97b24b99054 100644
--- a/packages/grafana-ui/src/components/RadialGauge/RadialColorDefs.tsx
+++ b/packages/grafana-ui/src/components/RadialGauge/RadialColorDefs.tsx
@@ -1,5 +1,3 @@
-import tinycolor from 'tinycolor2';
-
import {
colorManipulator,
DisplayProcessor,
@@ -10,6 +8,7 @@ import {
} from '@grafana/data';
import { RadialGradientMode, RadialShape } from './RadialGauge';
+import { buildGradientColors } from './colors';
import { GaugeDimensions } from './utils';
export interface RadialColorDefsOptions {
@@ -124,50 +123,15 @@ export class RadialColorDefs {
getGradient(baseColor = this.getFieldBaseColor(), forSegment?: boolean): Array<{ color: string; percent: number }> {
const { gradient, fieldDisplay, theme } = this.options;
- if (gradient === 'none') {
- return [
- { color: baseColor, percent: 0 },
- { color: baseColor, percent: 1 },
- ];
- }
+ return buildGradientColors(gradient, baseColor, theme, fieldDisplay.field.color?.mode, forSegment);
+ }
- const colorModeId = fieldDisplay.field.color?.mode;
- const colorMode = getFieldColorMode(colorModeId);
-
- // Handle continusous color modes first
- if (colorMode.isContinuous && colorMode.getColors && !forSegment) {
- const colors = colorMode.getColors(theme);
- return colors.map((color, idx) => ({ color, percent: idx / (colors.length - 1) }));
- } else if (colorMode.isByValue) {
- // For value based colors we want to stay more true to the specific color
- // So a radial gradient that adds a bit of light and shade works best
- const darkerColor = tinycolor(baseColor).darken(5);
- const lighterColor = tinycolor(baseColor).spin(20).lighten(10);
-
- const color1 = theme.isDark ? lighterColor : darkerColor;
- const color2 = theme.isDark ? darkerColor : lighterColor;
-
- return [
- { color: color1.toString(), percent: 0 },
- { color: color2.toString(), percent: 0.6 },
- { color: color2.toString(), percent: 1 },
- ];
- }
-
- // For value based colors we want to stay more true to the specific color
- // So a radial gradient that adds a bit of light and shade works best
- // we set the highest contrast color second based on the theme.
- const darkerColor = tinycolor(baseColor).spin(-20).darken(5);
- const lighterColor = tinycolor(baseColor).saturate(20).spin(20).brighten(10);
- return theme.isDark
- ? [
- { color: darkerColor.darken(10).toString(), percent: 0 },
- { color: lighterColor.lighten(10).toString(), percent: 1 },
- ]
- : [
- { color: lighterColor.lighten(10).toString(), percent: 0 },
- { color: darkerColor.toString(), percent: 1 },
- ];
+ getGradientDef(): string {
+ const gradientStops = this.getGradient();
+ const colorStrings = gradientStops.map((stop) => `${stop.color} ${(stop.percent * 100).toFixed(2)}%`);
+ return this.options.shape === 'circle'
+ ? `conic-gradient(from -10deg, ${colorStrings.join(', ')})`
+ : 'linear-gradient(90deg, ' + colorStrings.join(', ') + ')';
}
getGuideDotColors(): [string, string] {
diff --git a/packages/grafana-ui/src/components/RadialGauge/colors.ts b/packages/grafana-ui/src/components/RadialGauge/colors.ts
new file mode 100644
index 00000000000..62a31470ed3
--- /dev/null
+++ b/packages/grafana-ui/src/components/RadialGauge/colors.ts
@@ -0,0 +1,59 @@
+import tinycolor from 'tinycolor2';
+
+import { FieldColorModeId, getFieldColorMode, GrafanaTheme2 } from '@grafana/data';
+
+import { RadialGradientMode } from './RadialGauge';
+
+export function buildGradientColors(
+ gradientMode: RadialGradientMode,
+ baseColor: string,
+ theme: GrafanaTheme2,
+ colorModeId?: FieldColorModeId | string,
+ forSegment?: boolean
+): Array<{ color: string; percent: number }> {
+ if (gradientMode === 'none') {
+ return [
+ { color: baseColor, percent: 0 },
+ { color: baseColor, percent: 1 },
+ ];
+ }
+
+ const colorMode = getFieldColorMode(colorModeId);
+
+ // TODO we need to return thresholded values here. those will have breakpoints
+ // which map to exact percentages, and we should show that correctly.
+ // Handle continuous color modes first
+ if (colorMode.isContinuous && colorMode.getColors && !forSegment) {
+ const colors = colorMode.getColors(theme);
+ return colors.map((color, idx) => ({ color, percent: idx / (colors.length - 1) }));
+ } else if (colorMode.isByValue) {
+ // For value based colors we want to stay more true to the specific color
+ // So a radial gradient that adds a bit of light and shade works best
+ const darkerColor = tinycolor(baseColor).darken(5);
+ const lighterColor = tinycolor(baseColor).spin(20).lighten(10);
+
+ const color1 = theme.isDark ? lighterColor : darkerColor;
+ const color2 = theme.isDark ? darkerColor : lighterColor;
+
+ return [
+ { color: color1.toString(), percent: 0 },
+ { color: color2.toString(), percent: 0.6 },
+ { color: color2.toString(), percent: 1 },
+ ];
+ }
+
+ // For value based colors we want to stay more true to the specific color
+ // So a radial gradient that adds a bit of light and shade works best
+ // we set the highest contrast color second based on the theme.
+ const darkerColor = tinycolor(baseColor).spin(-20).darken(5);
+ const lighterColor = tinycolor(baseColor).saturate(20).spin(20).brighten(10);
+ return theme.isDark
+ ? [
+ { color: darkerColor.darken(10).toString(), percent: 0 },
+ { color: lighterColor.lighten(10).toString(), percent: 1 },
+ ]
+ : [
+ { color: lighterColor.lighten(10).toString(), percent: 0 },
+ { color: darkerColor.toString(), percent: 1 },
+ ];
+}