From c7af2be682e295673d5ff50336f50a203685a99b Mon Sep 17 00:00:00 2001 From: Paul Marbach Date: Wed, 17 Dec 2025 17:42:14 -0500 Subject: [PATCH] more testing and cleanup --- .../src/components/RadialGauge/colors.test.ts | 13 +++++++++ .../src/components/RadialGauge/colors.ts | 29 +++++++++---------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/packages/grafana-ui/src/components/RadialGauge/colors.test.ts b/packages/grafana-ui/src/components/RadialGauge/colors.test.ts index b3206a267b8..9eb787ce150 100644 --- a/packages/grafana-ui/src/components/RadialGauge/colors.test.ts +++ b/packages/grafana-ui/src/components/RadialGauge/colors.test.ts @@ -151,6 +151,19 @@ describe('RadialGauge color utils', () => { expect(colorAtGradientPercent(gradient, 1).toHexString()).toBe('#0000ff'); }); + it('will still work if unsorted', () => { + const gradient = [ + { color: '#0000ff', percent: 1 }, + { color: '#00ff00', percent: 0.5 }, + { color: '#ff0000', percent: 0 }, + ]; + expect(colorAtGradientPercent(gradient, 0).toHexString()).toBe('#ff0000'); + expect(colorAtGradientPercent(gradient, 0.25).toHexString()).toBe('#808000'); + expect(colorAtGradientPercent(gradient, 0.5).toHexString()).toBe('#00ff00'); + expect(colorAtGradientPercent(gradient, 0.75).toHexString()).toBe('#008080'); + expect(colorAtGradientPercent(gradient, 1).toHexString()).toBe('#0000ff'); + }); + it('should not throw an error when percent is outside 0-1 range', () => { const gradient = [ { color: '#ff0000', percent: 0 }, diff --git a/packages/grafana-ui/src/components/RadialGauge/colors.ts b/packages/grafana-ui/src/components/RadialGauge/colors.ts index 4cf5d7ca84e..1f667804b7d 100644 --- a/packages/grafana-ui/src/components/RadialGauge/colors.ts +++ b/packages/grafana-ui/src/components/RadialGauge/colors.ts @@ -102,7 +102,7 @@ export function colorAtGradientPercent(stops: GradientStop[], percent: number): throw new Error('colorAtGradientPercent requires at least two color stops'); } - // normalize and sort stops by percent + // normalize and sort stops by percent. TODO: is this necessary? is gradientstops always sorted? const sorted = stops .map((s) => ({ color: s.color, percent: clamp(s.percent, 0, 1) })) .sort((a, b) => a.percent - b.percent); @@ -115,25 +115,24 @@ export function colorAtGradientPercent(stops: GradientStop[], percent: number): return tinycolor(sorted[sorted.length - 1].color); } - // find surrounding stops - let left = sorted[0]; - let right = sorted[sorted.length - 1]; - for (let i = 1; i < sorted.length; i++) { - if (percent <= sorted[i].percent) { - left = sorted[i - 1]; - right = sorted[i]; - break; + // find surrounding stops using binary search + let lo = 0; + let hi = sorted.length - 1; + while (lo + 1 < hi) { + const mid = (lo + hi) >> 1; + if (percent <= sorted[mid].percent) { + hi = mid; + } else { + lo = mid; } } + const left = sorted[lo]; + const right = sorted[hi]; + const range = right.percent - left.percent; const t = range === 0 ? 0 : (percent - left.percent) / range; // 0..1 - - // tinycolor.mix expects amount as percentage of the second color - const mixed = tinycolor.mix(left.color, right.color, t * 100); - - // return hex6 if opaque, hex8 if has alpha - return mixed; + return tinycolor.mix(left.color, right.color, t * 100); } export function getBarEndcapColors(gradientStops: GradientStop[], percent = 1): [string, string] {