From f78452be30189e0ca275c5c92315ead245ee4522 Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Fri, 23 Jul 2021 20:50:47 -0500 Subject: [PATCH] Color: fix alpha calculation < 16/255 and State-timeline threshold alpha (#35911) --- .../grafana-data/src/field/fieldOverrides.ts | 16 ++++++++++++++++ .../src/themes/colorManipulator.test.ts | 4 ++++ .../grafana-data/src/themes/colorManipulator.ts | 7 ++++++- .../app/plugins/panel/state-timeline/timeline.ts | 6 ++++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/grafana-data/src/field/fieldOverrides.ts b/packages/grafana-data/src/field/fieldOverrides.ts index 048cd4b4051..424c03712bc 100644 --- a/packages/grafana-data/src/field/fieldOverrides.ts +++ b/packages/grafana-data/src/field/fieldOverrides.ts @@ -32,6 +32,7 @@ import { getFrameDisplayName } from './fieldState'; import { getTimeField } from '../dataframe/processDataFrame'; import { mapInternalLinkToExplore } from '../utils/dataLinks'; import { getTemplateProxyForField } from './templateProxies'; +import tinycolor from 'tinycolor2'; interface OverrideProps { match: FieldMatcher; @@ -204,11 +205,17 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra }); } +// this is a significant optimization for streaming, where we currently re-process all values in the buffer on ech update +// via field.display(value). this can potentially be removed once we... +// 1. process data packets incrementally and/if cache the results in the streaming datafame (maybe by buffer index) +// 2. have the ability to selectively get display color or text (but not always both, which are each quite expensive) +// 3. sufficently optimize text formating and threshold color determinitation function cachingDisplayProcessor(disp: DisplayProcessor, maxCacheSize = 2500): DisplayProcessor { const cache = new Map(); return (value: any) => { let v = cache.get(value); + if (!v) { // Don't grow too big if (cache.size === maxCacheSize) { @@ -216,8 +223,17 @@ function cachingDisplayProcessor(disp: DisplayProcessor, maxCacheSize = 2500): D } v = disp(value); + + // convert to hex6 or hex8 so downstream we can cheaply test for alpha (and set new alpha) + // via a simple length check (in colorManipulator) rather using slow parsing via tinycolor + if (v.color && v.color[0] !== '#') { + let color = tinycolor(v.color); + v.color = color.getAlpha() < 1 ? color.toHex8String() : color.toHexString(); + } + cache.set(value, v); } + return v; }; } diff --git a/packages/grafana-data/src/themes/colorManipulator.test.ts b/packages/grafana-data/src/themes/colorManipulator.test.ts index b591658b092..16a25e78a68 100644 --- a/packages/grafana-data/src/themes/colorManipulator.test.ts +++ b/packages/grafana-data/src/themes/colorManipulator.test.ts @@ -279,6 +279,10 @@ describe('utils/colorManipulator', () => { expect(alpha('hsla(0, 100%, 50%, 0.2)', 0.5)).toEqual('hsla(0, 100%, 50%, 0.5)'); }); + it('converts an rgb hex color with the alpha value provided', () => { + expect(alpha('#FFFFFF', 0)).toEqual('#FFFFFF00'); + }); + it('throw on invalid colors', () => { expect(() => { alpha('white', 0.4); diff --git a/packages/grafana-data/src/themes/colorManipulator.ts b/packages/grafana-data/src/themes/colorManipulator.ts index f04b6e4faee..a8bd5934a9f 100644 --- a/packages/grafana-data/src/themes/colorManipulator.ts +++ b/packages/grafana-data/src/themes/colorManipulator.ts @@ -248,7 +248,12 @@ export function alpha(color: string, value: number) { color = color.substring(0, 7); } - return color + Math.round(value * 255).toString(16); + return ( + color + + Math.round(value * 255) + .toString(16) + .padStart(2, '0') + ); } // rgb(, hsl( else if (color[3] === '(') { diff --git a/public/app/plugins/panel/state-timeline/timeline.ts b/public/app/plugins/panel/state-timeline/timeline.ts index 78fd73b8c1b..975d485f084 100644 --- a/public/app/plugins/panel/state-timeline/timeline.ts +++ b/public/app/plugins/panel/state-timeline/timeline.ts @@ -547,6 +547,12 @@ export function getConfig(opts: TimelineCoreOptions) { } function getFillColor(fieldConfig: TimelineFieldConfig, color: string) { + // if #rgba with pre-existing alpha. ignore fieldConfig.fillOpacity + // e.g. thresholds with opacity + if (color[0] === '#' && color.length === 9) { + return color; + } + const opacityPercent = (fieldConfig.fillOpacity ?? 100) / 100; return alpha(color, opacityPercent); }