diff --git a/devenv/dev-dashboards/panel-table/table_kitchen_sink.json b/devenv/dev-dashboards/panel-table/table_kitchen_sink.json
index 6e806785038..8ff35adb06d 100644
--- a/devenv/dev-dashboards/panel-table/table_kitchen_sink.json
+++ b/devenv/dev-dashboards/panel-table/table_kitchen_sink.json
@@ -434,9 +434,6 @@
"id": 1,
"options": {
"cellHeight": "sm",
- "frozenColumns": {
- "left": 1
- },
"footer": {
"countRows": false,
"enablePagination": false,
@@ -445,6 +442,9 @@
"show": true
},
"frameIndex": 0,
+ "frozenColumns": {
+ "left": 1
+ },
"showHeader": true,
"sortBy": [
{
@@ -797,6 +797,10 @@
"value": {
"type": "data-links"
}
+ },
+ {
+ "id": "custom.width",
+ "value": 140
}
]
}
@@ -804,7 +808,7 @@
},
"gridPos": {
"h": 7,
- "w": 24,
+ "w": 12,
"x": 0,
"y": 12
},
@@ -834,6 +838,166 @@
"title": "Colors and Links",
"type": "table"
},
+ {
+ "datasource": {
+ "type": "grafana-testdata-datasource"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "fixedColor": "transparent",
+ "mode": "fixed"
+ },
+ "custom": {
+ "align": "auto",
+ "cellOptions": {
+ "type": "auto"
+ },
+ "inspect": false,
+ "width": 100,
+ "wrapHeaderText": false
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": 0
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "highlight"
+ },
+ "properties": [
+ {
+ "id": "custom.cellOptions",
+ "value": {
+ "applyToRow": true,
+ "mode": "basic",
+ "type": "color-background"
+ }
+ },
+ {
+ "id": "mappings",
+ "value": [
+ {
+ "options": {
+ "1": {
+ "color": "#fff899",
+ "index": 0
+ }
+ },
+ "type": "value"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byRegexp",
+ "options": "/^color-*/"
+ },
+ "properties": [
+ {
+ "id": "mappings",
+ "value": [
+ {
+ "options": {
+ "1": {
+ "color": "green",
+ "index": 0
+ }
+ },
+ "type": "value"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "color-bg"
+ },
+ "properties": [
+ {
+ "id": "custom.cellOptions",
+ "value": {
+ "type": "color-background"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "color-text"
+ },
+ "properties": [
+ {
+ "id": "custom.cellOptions",
+ "value": {
+ "type": "color-text"
+ }
+ },
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "text",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "use-case"
+ },
+ "properties": [
+ {
+ "id": "custom.width"
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 12,
+ "x": 12,
+ "y": 12
+ },
+ "id": 10,
+ "options": {
+ "cellHeight": "sm",
+ "footer": {
+ "countRows": false,
+ "fields": "",
+ "reducer": ["sum"],
+ "show": false
+ },
+ "showHeader": true,
+ "sortBy": []
+ },
+ "pluginVersion": "12.2.0-pre",
+ "targets": [
+ {
+ "csvContent": "use-case,normal,color-text,color-bg,highlight\n\"color bg and apply to row\",1,0,1,1\n\"color text and apply to row\",1,1,0,1\n\"color text + color bg + apply to row\",1,1,1,1\n\"only apply to row\",1,0,0,1\n\"only color bg\",1,0,1,0\n\"only color text\",1,1,0,0\n\"color text + color bg\",1,1,1,0\n\"no colorization\",1,0,0,0",
+ "refId": "A",
+ "scenarioId": "csv_content"
+ }
+ ],
+ "title": "Apply to Row - mixed color cell types",
+ "type": "table"
+ },
{
"datasource": {
"type": "grafana-testdata-datasource"
@@ -1697,5 +1861,5 @@
"timezone": "",
"title": "Panel Tests - Table - Kitchen Sink",
"uid": "dcb9f5e9-8066-4397-889e-864b99555dbb",
- "version": 9
+ "version": 32
}
diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.test.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.test.tsx
index 57001a4176e..0b54c3ef717 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.test.tsx
+++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.test.tsx
@@ -2,6 +2,8 @@ import { render, RenderResult } from '@testing-library/react';
import { Field, FieldType, MappingType, createTheme } from '@grafana/data';
+import { getTextColorForBackground } from '../../../../utils/colors';
+
import { PillCell } from './PillCell';
describe('PillCell', () => {
@@ -26,49 +28,94 @@ describe('PillCell', () => {
describe('Color by hash (classic palette)', () => {
it('single value', () => {
expectHTML(
- render(),
- `value1`
+ render(
+
+ ),
+ `value1`
);
});
it('empty string', () => {
- expectHTML(render(), '');
+ expectHTML(
+ render(
+
+ ),
+ ''
+ );
});
it('null', () => {
- const { container } = render();
+ const { container } = render(
+
+ );
expect(container).toBeEmptyDOMElement();
});
it('CSV values', () => {
expectHTML(
- render(),
+ render(
+
+ ),
`
- value1
- value2
- value3
+ value1
+ value2
+ value3
`
);
});
it('JSON array values', () => {
expectHTML(
- render(),
+ render(
+
+ ),
`
- value1
- value2
- value3
+ value1
+ value2
+ value3
`
);
});
it('non-string values', () => {
expectHTML(
- render(),
+ render(
+
+ ),
`
- 100
- 200
- 300
+ 100
+ 200
+ 300
`
);
});
@@ -107,12 +154,14 @@ describe('PillCell', () => {
} satisfies Field;
expectHTML(
- render(),
+ render(
+
+ ),
`
- success
- error
- warning
- unknown
+ success
+ error
+ warning
+ unknown
`
);
});
diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.tsx
index 4ba20f36605..9ab5e732d8d 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.tsx
+++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.tsx
@@ -4,7 +4,6 @@ import { useMemo } from 'react';
import {
GrafanaTheme2,
classicColors,
- colorManipulator,
Field,
getColorByStringHash,
FALLBACK_COLOR,
@@ -14,12 +13,23 @@ import { FieldColorModeId } from '@grafana/schema';
import { PillCellProps, TableCellStyles, TableCellValue } from '../types';
-export function PillCell({ rowIdx, field, theme }: PillCellProps) {
+export function PillCell({ rowIdx, field, theme, getTextColorForBackground }: PillCellProps) {
const value = field.values[rowIdx];
const pills: Pill[] = useMemo(() => {
const pillValues = inferPills(value);
- return pillValues.length > 0 ? createPills(pillValues, field, theme) : [];
- }, [value, field, theme]);
+ return pillValues.length > 0
+ ? pillValues.map((pill, index) => {
+ const bgColor = getPillColor(pill, field, theme);
+ const textColor = getTextColorForBackground(bgColor);
+ return {
+ value: String(pill),
+ key: `${pill}-${index}`,
+ bgColor,
+ color: textColor,
+ };
+ })
+ : [];
+ }, [value, field, theme, getTextColorForBackground]);
if (pills.length === 0) {
return null;
@@ -49,19 +59,6 @@ interface Pill {
const SPLIT_RE = /\s*,\s*/;
const TRANSPARENT = 'rgba(0,0,0,0)';
-function createPills(pillValues: unknown[], field: Field, theme: GrafanaTheme2): Pill[] {
- return pillValues.map((pill, index) => {
- const bgColor = getPillColor(pill, field, theme);
- const textColor = colorManipulator.getContrastRatio('#FFFFFF', bgColor) >= 4.5 ? '#FFFFFF' : '#000000';
- return {
- value: String(pill),
- key: `${pill}-${index}`,
- bgColor,
- color: textColor,
- };
- });
-}
-
export function inferPills(rawValue: TableCellValue): unknown[] {
if (rawValue === '' || rawValue == null) {
return [];
diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.test.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.test.tsx
index 48b444839a6..da01b284216 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.test.tsx
+++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.test.tsx
@@ -91,6 +91,7 @@ describe('TableNG Cells renderers', () => {
getActions={jest.fn(() => [
{ title: 'Action', onClick: jest.fn(() => {}), confirmation: jest.fn(), style: {} },
])}
+ getTextColorForBackground={jest.fn(() => '#000000')}
/>
);
};
@@ -109,6 +110,7 @@ describe('TableNG Cells renderers', () => {
height={100}
width={100}
theme={createTheme()}
+ getTextColorForBackground={jest.fn(() => '#000000')}
cellOptions={cellOptions}
cellInspect={false}
showFilters={false}
diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx
index ba8df94ac5c..6079a4f63d0 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx
+++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx
@@ -110,7 +110,12 @@ const CELL_REGISTRY: Record = {
[TableCellDisplayMode.Pill]: {
// eslint-disable-next-line react/display-name
renderer: memo((props: TableCellRendererProps) => (
-
+
)),
getStyles: getPillStyles,
testField: (field: Field) => field.type === FieldType.string,
diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx
index bb3c3fea8fa..39979cabfb3 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx
+++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx
@@ -1,6 +1,7 @@
import 'react-data-grid/lib/styles.css';
import { clsx } from 'clsx';
+import memoize from 'micro-memoize';
import { CSSProperties, Key, ReactNode, useCallback, useMemo, useRef, useState } from 'react';
import {
Cell,
@@ -28,6 +29,7 @@ import { Trans } from '@grafana/i18n';
import { FieldColorModeId, TableCellTooltipPlacement } from '@grafana/schema';
import { useStyles2, useTheme2 } from '../../../themes/ThemeContext';
+import { getTextColorForBackground as _getTextColorForBackground } from '../../../utils/colors';
import { Pagination } from '../../Pagination/Pagination';
import { PanelContext, usePanelContext } from '../../PanelChrome';
import { DataLinksActionsTooltip } from '../DataLinksActionsTooltip';
@@ -80,7 +82,7 @@ import {
frameToRecords,
getAlignment,
getApplyToRowBgFn,
- getCellColorInlineStyles,
+ getCellColorInlineStylesFactory,
getCellLinks,
getCellOptions,
getDefaultRowHeight,
@@ -143,6 +145,7 @@ export function TableNG(props: TableNGProps) {
const rows = useMemo(() => frameToRecords(data), [data]);
const hasNestedFrames = useMemo(() => getIsNestedTable(data.fields), [data]);
+ const getTextColorForBackground = useMemo(() => memoize(_getTextColorForBackground, { maxSize: 1000 }), []);
const {
rows: filteredRows,
@@ -174,6 +177,11 @@ export function TableNG(props: TableNGProps) {
() => (hasNestedFrames ? width - COLUMN.EXPANDER_WIDTH : width) - scrollbarWidth,
[width, hasNestedFrames, scrollbarWidth]
);
+ const getCellColorInlineStyles = useMemo(() => getCellColorInlineStylesFactory(theme), [theme]);
+ const applyToRowBgFn = useMemo(
+ () => getApplyToRowBgFn(data.fields, getCellColorInlineStyles) ?? undefined,
+ [data.fields, getCellColorInlineStyles]
+ );
const typographyCtx = useMemo(
() =>
createTypographyContext(
@@ -226,7 +234,6 @@ export function TableNG(props: TableNGProps) {
footerOptions,
isCountRowsSet,
});
- const applyToRowBgFn = useMemo(() => getApplyToRowBgFn(data.fields, theme) ?? undefined, [data.fields, theme]);
// normalize the row height into a function which returns a number, so we avoid a bunch of conditionals during rendering.
const rowHeightFn = useMemo((): ((row: TableRow) => number) => {
@@ -438,14 +445,12 @@ export function TableNG(props: TableNGProps) {
}
}
- let style: CSSProperties | undefined;
-
- if (rowCellStyle.color != null || rowCellStyle.background != null) {
- style = rowCellStyle;
- } else if (canBeColorized) {
+ let style: CSSProperties = { ...rowCellStyle };
+ if (canBeColorized) {
const value = props.row[props.column.key];
const displayValue = field.display!(value); // this fires here to get colors, then again to get rendered value?
- style = getCellColorInlineStyles(theme, cellOptions, displayValue);
+ const cellColorStyles = getCellColorInlineStyles(cellOptions, displayValue, applyToRowBgFn != null);
+ Object.assign(style, cellColorStyles);
}
return (
@@ -486,6 +491,7 @@ export function TableNG(props: TableNGProps) {
showFilters={showFilters}
getActions={getCellActions}
disableSanitizeHtml={disableSanitizeHtml}
+ getTextColorForBackground={getTextColorForBackground}
/>
{showActions && (
): JSX.Element => {
// cached so we don't care about multiple calls.
const tooltipHeight = rowHeightFn(props.row);
- let tooltipStyle: CSSProperties | undefined;
+ let tooltipStyle: CSSProperties = { ...rowCellStyle };
if (tooltipCanBeColorized) {
- const tooltipDisplayValue = tooltipField.display!(props.row[tooltipDisplayName]); // this is yet another call to field.display() for the tooltip field
- tooltipStyle = getCellColorInlineStyles(theme, tooltipCellOptions, tooltipDisplayValue);
+ const tooltipDisplayValue = tooltipField.display!(props.row[tooltipDisplayName]);
+ const tooltipCellColorStyles = getCellColorInlineStyles(
+ tooltipCellOptions,
+ tooltipDisplayValue,
+ applyToRowBgFn != null
+ );
+ Object.assign(tooltipStyle, tooltipCellColorStyles);
}
return (
@@ -624,6 +636,8 @@ export function TableNG(props: TableNGProps) {
footerCalcs,
frozenColumns,
getCellActions,
+ getCellColorInlineStyles,
+ getTextColorForBackground,
isCountRowsSet,
numFrozenColsFullyInView,
onCellFilterAdded,
diff --git a/packages/grafana-ui/src/components/Table/TableNG/components/TableCellTooltip.tsx b/packages/grafana-ui/src/components/Table/TableNG/components/TableCellTooltip.tsx
index 156854bcfd9..232f3bb01a5 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/components/TableCellTooltip.tsx
+++ b/packages/grafana-ui/src/components/Table/TableNG/components/TableCellTooltip.tsx
@@ -19,6 +19,7 @@ export interface TableCellTooltipProps {
disableSanitizeHtml?: boolean;
field: Field;
getActions: (field: Field, rowIdx: number) => ActionModel[];
+ getTextColorForBackground: (bgColor: string) => string;
gridRef: RefObject;
height: number;
placement?: TableCellTooltipPlacement;
@@ -40,6 +41,7 @@ export const TableCellTooltip = memo(
disableSanitizeHtml,
field,
getActions,
+ getTextColorForBackground,
gridRef,
height,
placement,
@@ -100,6 +102,7 @@ export const TableCellTooltip = memo(
field,
frame: data,
getActions,
+ getTextColorForBackground,
height,
rowIdx,
showFilters: false,
@@ -107,7 +110,19 @@ export const TableCellTooltip = memo(
value: rawValue,
width,
}) satisfies TableCellRendererProps,
- [cellOptions, data, disableSanitizeHtml, field, getActions, height, rawValue, rowIdx, theme, width]
+ [
+ cellOptions,
+ data,
+ disableSanitizeHtml,
+ field,
+ getActions,
+ getTextColorForBackground,
+ height,
+ rawValue,
+ rowIdx,
+ theme,
+ width,
+ ]
);
const cellElement = tooltipCaretRef.current?.closest('.rdg-cell');
diff --git a/packages/grafana-ui/src/components/Table/TableNG/types.ts b/packages/grafana-ui/src/components/Table/TableNG/types.ts
index efdd9495336..123a8f39e7d 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/types.ts
+++ b/packages/grafana-ui/src/components/Table/TableNG/types.ts
@@ -167,6 +167,7 @@ export interface TableCellRendererProps {
showFilters: boolean;
getActions?: GetActionsFunctionLocal;
disableSanitizeHtml?: boolean;
+ getTextColorForBackground: (color: string) => string;
}
export type InspectCellProps = {
@@ -250,6 +251,7 @@ export interface PillCellProps {
theme: GrafanaTheme2;
field: Field;
rowIdx: number;
+ getTextColorForBackground: (color: string) => string;
}
export interface TableCellStyleOptions {
diff --git a/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts b/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts
index 8ca2ba7870f..bec5094ac77 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts
+++ b/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts
@@ -23,7 +23,7 @@ import {
extractPixelValue,
frameToRecords,
getAlignmentFactor,
- getCellColorInlineStyles,
+ getCellColorInlineStylesFactory,
getCellLinks,
getCellOptions,
getComparator,
@@ -107,28 +107,104 @@ describe('TableNG utils', () => {
},
} as unknown as GrafanaTheme2;
- it('should handle color background mode', () => {
- const field = { type: TableCellDisplayMode.ColorBackground as const, mode: TableCellBackgroundDisplayMode.Basic };
+ it('should handle color text cell type', () => {
+ const cellOptions = {
+ type: TableCellDisplayMode.ColorText as const,
+ };
const displayValue = { text: '100', numeric: 100, color: '#ff0000' };
- const colors = getCellColorInlineStyles(theme, field, displayValue);
- expect(colors.background).toBe('rgb(255, 0, 0)');
+ const getCellColorInlineStyles = getCellColorInlineStylesFactory(theme);
+ const colors = getCellColorInlineStyles(cellOptions, displayValue, false);
+ expect(colors.color).toBe('#ff0000');
+ expect(colors).not.toHaveProperty('background');
+ });
+
+ it('should pass thru color background cell type in basic mode', () => {
+ const cellOptions = {
+ type: TableCellDisplayMode.ColorBackground as const,
+ mode: TableCellBackgroundDisplayMode.Basic,
+ };
+
+ const displayValue = { text: '100', numeric: 100, color: '#ff0000' };
+
+ const getCellColorInlineStyles = getCellColorInlineStylesFactory(theme);
+ const colors = getCellColorInlineStyles(cellOptions, displayValue, false);
+ expect(colors.background).toBe('#ff0000');
expect(colors.color).toBe('rgb(247, 248, 250)');
});
- it('should handle color background gradient mode', () => {
- const field = {
+ it('should handle color background cell type in gradient mode', () => {
+ const cellOptions = {
type: TableCellDisplayMode.ColorBackground as const,
mode: TableCellBackgroundDisplayMode.Gradient,
};
const displayValue = { text: '100', numeric: 100, color: '#ff0000' };
- const colors = getCellColorInlineStyles(theme, field, displayValue);
+ const getCellColorInlineStyles = getCellColorInlineStylesFactory(theme);
+ const colors = getCellColorInlineStyles(cellOptions, displayValue, false);
expect(colors.background).toBe('linear-gradient(120deg, rgb(255, 54, 36), #ff0000)');
expect(colors.color).toBe('rgb(247, 248, 250)');
});
+
+ it('does not set CSSProperties for un-mapped cell types', () => {
+ const cellOptions = { type: TableCellDisplayMode.JSONView as const };
+
+ const displayValue = { text: '100', numeric: 100, color: '#ff0000' };
+
+ const getCellColorInlineStyles = getCellColorInlineStylesFactory(theme);
+ const colors = getCellColorInlineStyles(cellOptions, displayValue, false);
+
+ expect(colors).toEqual({});
+ });
+
+ describe('applyToRow', () => {
+ it.each([
+ ['hex', '#ffffff00'],
+ ['rgba', 'rgba(255,255,255,0)'],
+ ['hsla', 'hsla(0,100%,100%,0)'],
+ ])(
+ 'should not apply background color if the display value is transparent (%s) and applyToRow is on',
+ (_format, colorDisplayValue) => {
+ const cellOptions = {
+ type: TableCellDisplayMode.ColorBackground as const,
+ mode: TableCellBackgroundDisplayMode.Basic,
+ };
+
+ const displayValue = { text: '100', numeric: 100, color: colorDisplayValue };
+
+ const getCellColorInlineStyles = getCellColorInlineStylesFactory(theme);
+ const colors = getCellColorInlineStyles(cellOptions, displayValue, true);
+
+ expect(colors).toEqual({});
+ }
+ );
+
+ it.each([
+ ['hex', '#ffffff00'],
+ ['rgba', 'rgba(255,255,255,0)'],
+ ['hsla', 'hsla(0,100%,100%,0)'],
+ ])(
+ 'should apply background color if the display value is transparent (%s) and applyToRow is off',
+ (_format, colorDisplayValue) => {
+ const cellOptions = {
+ type: TableCellDisplayMode.ColorBackground as const,
+ mode: TableCellBackgroundDisplayMode.Basic,
+ };
+
+ const displayValue = { text: '100', numeric: 100, color: colorDisplayValue };
+
+ const getCellColorInlineStyles = getCellColorInlineStylesFactory(theme);
+ const colors = getCellColorInlineStyles(cellOptions, displayValue, false);
+
+ expect(colors).toEqual({
+ background: colorDisplayValue,
+ color: 'rgb(32, 34, 38)',
+ });
+ }
+ );
+ });
});
describe('frame to records conversion', () => {
diff --git a/packages/grafana-ui/src/components/Table/TableNG/utils.ts b/packages/grafana-ui/src/components/Table/TableNG/utils.ts
index 3eb89230a58..e6289ca0cba 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/utils.ts
+++ b/packages/grafana-ui/src/components/Table/TableNG/utils.ts
@@ -1,4 +1,5 @@
import { Property } from 'csstype';
+import memoize from 'micro-memoize';
import { CSSProperties } from 'react';
import { SortColumn } from 'react-data-grid';
import tinycolor from 'tinycolor2';
@@ -499,36 +500,58 @@ const CELL_GRADIENT_HUE_ROTATION_DEGREES = 5;
* @internal
* Returns the text and background colors for a table cell based on its options and display value.
*/
-export function getCellColorInlineStyles(
- theme: GrafanaTheme2,
- cellOptions: TableCellOptions,
- displayValue: DisplayValue
-): CSSProperties {
- // How much to darken elements depends upon if we're in dark mode
- const darkeningFactor = theme.isDark ? 1 : -0.7;
-
- // Setup color variables
- let textColor: string | undefined = undefined;
- let bgColor: string | undefined = undefined;
-
- if (cellOptions.type === TableCellDisplayMode.ColorText) {
- textColor = displayValue.color;
- } else if (cellOptions.type === TableCellDisplayMode.ColorBackground) {
- const mode = cellOptions.mode ?? TableCellBackgroundDisplayMode.Gradient;
-
- if (mode === TableCellBackgroundDisplayMode.Basic) {
- textColor = getTextColorForAlphaBackground(displayValue.color!, theme.isDark);
- bgColor = tinycolor(displayValue.color).toRgbString();
- } else if (mode === TableCellBackgroundDisplayMode.Gradient) {
- const bgColor2 = tinycolor(displayValue.color)
+export function getCellColorInlineStylesFactory(theme: GrafanaTheme2) {
+ const bgCellTextColor = memoize((color: string) => getTextColorForAlphaBackground(color, theme.isDark), {
+ maxSize: 1000,
+ });
+ const darkeningFactor = theme.isDark ? 1 : -0.7; // How much to darken elements depends upon if we're in dark mode
+ const gradientBg = memoize(
+ (color: string) =>
+ tinycolor(color)
.darken(CELL_COLOR_DARKENING_MULTIPLIER * darkeningFactor)
- .spin(CELL_GRADIENT_HUE_ROTATION_DEGREES);
- textColor = getTextColorForAlphaBackground(displayValue.color!, theme.isDark);
- bgColor = `linear-gradient(120deg, ${bgColor2.toRgbString()}, ${displayValue.color})`;
- }
- }
+ .spin(CELL_GRADIENT_HUE_ROTATION_DEGREES)
+ .toRgbString(),
+ { maxSize: 1000 }
+ );
+ const isTransparent = memoize(
+ (color: string) => {
+ // if hex, do the simple thing.
+ if (color[0] === '#') {
+ return color.length === 9 && color.endsWith('00');
+ }
+ // if not hex, just use tinycolor to avoid extra logic.
+ return tinycolor(color).getAlpha() === 0;
+ },
+ { maxSize: 1000 }
+ );
- return { color: textColor, background: bgColor };
+ return (cellOptions: TableCellOptions, displayValue: DisplayValue, hasApplyToRow: boolean): CSSProperties => {
+ const result: CSSProperties = {};
+ const displayValueColor = displayValue.color;
+
+ if (!displayValueColor) {
+ return result;
+ }
+
+ if (cellOptions.type === TableCellDisplayMode.ColorText) {
+ result.color = displayValueColor;
+ } else if (cellOptions.type === TableCellDisplayMode.ColorBackground) {
+ // return without setting anything if the bg is transparent. this allows
+ // the cell to inherit the row bg color if `applyToRow` is set.
+ if (hasApplyToRow && isTransparent(displayValueColor)) {
+ return result;
+ }
+
+ const mode = cellOptions.mode ?? TableCellBackgroundDisplayMode.Gradient;
+ result.color = bgCellTextColor(displayValueColor);
+ result.background =
+ mode === TableCellBackgroundDisplayMode.Gradient
+ ? `linear-gradient(120deg, ${gradientBg(displayValueColor)}, ${displayValueColor})`
+ : displayValueColor;
+ }
+
+ return result;
+ };
}
/**
@@ -881,7 +904,10 @@ export function computeColWidths(fields: Field[], availWidth: number) {
* @internal
* if applyToRow is true in any field, return a function that gets the row background color
*/
-export function getApplyToRowBgFn(fields: Field[], theme: GrafanaTheme2): ((rowIndex: number) => CSSProperties) | void {
+export function getApplyToRowBgFn(
+ fields: Field[],
+ getCellColorInlineStyles: ReturnType
+): ((rowIndex: number) => CSSProperties) | void {
for (const field of fields) {
const cellOptions = getCellOptions(field);
const fieldDisplay = field.display;
@@ -890,7 +916,7 @@ export function getApplyToRowBgFn(fields: Field[], theme: GrafanaTheme2): ((rowI
cellOptions.type === TableCellDisplayMode.ColorBackground &&
cellOptions.applyToRow === true
) {
- return (rowIndex: number) => getCellColorInlineStyles(theme, cellOptions, fieldDisplay(field.values[rowIndex]));
+ return (rowIndex: number) => getCellColorInlineStyles(cellOptions, fieldDisplay(field.values[rowIndex]), true);
}
}
}