diff --git a/packages/grafana-schema/src/common/common.gen.ts b/packages/grafana-schema/src/common/common.gen.ts
index e95004ac43a..9dcdcdcb5d2 100644
--- a/packages/grafana-schema/src/common/common.gen.ts
+++ b/packages/grafana-schema/src/common/common.gen.ts
@@ -792,6 +792,7 @@ export interface TableSparklineCellOptions extends GraphFieldConfig {
* Colored background cell options
*/
export interface TableColoredBackgroundCellOptions {
+ applyToRow?: boolean;
mode?: TableCellBackgroundDisplayMode;
type: TableCellDisplayMode.ColorBackground;
}
diff --git a/packages/grafana-schema/src/common/table.cue b/packages/grafana-schema/src/common/table.cue
index c6aa05df53e..5c6dce2a5de 100644
--- a/packages/grafana-schema/src/common/table.cue
+++ b/packages/grafana-schema/src/common/table.cue
@@ -71,6 +71,7 @@ TableSparklineCellOptions: {
TableColoredBackgroundCellOptions: {
type: TableCellDisplayMode & "color-background"
mode?: TableCellBackgroundDisplayMode
+ applyToRow?: bool
} @cuetsy(kind="interface")
// Height of a table cell
diff --git a/packages/grafana-ui/src/components/Table/CellActions.tsx b/packages/grafana-ui/src/components/Table/CellActions.tsx
index bb8f4be9010..b8a2dc62d63 100644
--- a/packages/grafana-ui/src/components/Table/CellActions.tsx
+++ b/packages/grafana-ui/src/components/Table/CellActions.tsx
@@ -48,7 +48,7 @@ export function CellActions({ field, cell, previewMode, showFilters, onCellFilte
return (
<>
-
+
{inspectEnabled && (
{
- const { field, cell, tableStyles, row, cellProps, frame } = props;
+ const { field, cell, tableStyles, row, cellProps, frame, rowStyled } = props;
const inspectEnabled = Boolean(field.config.custom?.inspect);
const displayValue = field.display!(cell.value);
@@ -53,7 +52,15 @@ export const DefaultCell = (props: TableCellProps) => {
// Text should wrap when the content length is less than or equal to the length of an OG tweet and it contains whitespace
const textShouldWrap = displayValue.text.length <= OG_TWEET_LENGTH && /\s/.test(displayValue.text);
- const cellStyle = getCellStyle(tableStyles, cellOptions, displayValue, inspectEnabled, isStringValue, textShouldWrap);
+ const cellStyle = getCellStyle(
+ tableStyles,
+ cellOptions,
+ displayValue,
+ inspectEnabled,
+ isStringValue,
+ textShouldWrap,
+ rowStyled
+ );
if (isStringValue) {
let justifyContent = cellProps.style?.justifyContent;
@@ -104,31 +111,17 @@ function getCellStyle(
displayValue: DisplayValue,
disableOverflowOnHover = false,
isStringValue = false,
- shouldWrapText = false
+ shouldWrapText = false,
+ rowStyled = false
) {
- // How much to darken elements depends upon if we're in dark mode
- const darkeningFactor = tableStyles.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!, tableStyles.theme.isDark);
- bgColor = tinycolor(displayValue.color).toRgbString();
- } else if (mode === TableCellBackgroundDisplayMode.Gradient) {
- const bgColor2 = tinycolor(displayValue.color)
- .darken(10 * darkeningFactor)
- .spin(5);
- textColor = getTextColorForAlphaBackground(displayValue.color!, tableStyles.theme.isDark);
- bgColor = `linear-gradient(120deg, ${bgColor2.toRgbString()}, ${displayValue.color})`;
- }
- }
+ // Get colors
+ const colors = getCellColors(tableStyles, cellOptions, displayValue);
+ textColor = colors.textColor;
+ bgColor = colors.bgColor;
// If we have definied colors return those styles
// Otherwise we return default styles
@@ -137,7 +130,8 @@ function getCellStyle(
bgColor,
!disableOverflowOnHover,
isStringValue,
- shouldWrapText
+ shouldWrapText,
+ rowStyled
);
}
diff --git a/packages/grafana-ui/src/components/Table/RowsList.tsx b/packages/grafana-ui/src/components/Table/RowsList.tsx
index 6b16f4430a2..5194d9d693d 100644
--- a/packages/grafana-ui/src/components/Table/RowsList.tsx
+++ b/packages/grafana-ui/src/components/Table/RowsList.tsx
@@ -13,7 +13,7 @@ import {
TimeRange,
hasTimeField,
} from '@grafana/data';
-import { TableCellHeight } from '@grafana/schema';
+import { TableCellDisplayMode, TableCellHeight } from '@grafana/schema';
import { useTheme2 } from '../../themes';
import CustomScrollbar from '../CustomScrollbar/CustomScrollbar';
@@ -22,8 +22,8 @@ import { usePanelContext } from '../PanelChrome';
import { ExpandedRow, getExpandedRowHeight } from './ExpandedRow';
import { TableCell } from './TableCell';
import { TableStyles } from './styles';
-import { TableFilterActionCallback } from './types';
-import { calculateAroundPointThreshold, isPointTimeValAroundTableTimeVal } from './utils';
+import { CellColors, TableFieldOptions, TableFilterActionCallback } from './types';
+import { calculateAroundPointThreshold, getCellColors, isPointTimeValAroundTableTimeVal } from './utils';
interface RowsListProps {
data: DataFrame;
@@ -201,12 +201,30 @@ export const RowsList = (props: RowsListProps) => {
[tableState.pageIndex, tableState.pageSize]
);
+ let rowBg: Function | undefined = undefined;
+ for (const field of data.fields) {
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
+ const fieldOptions = field.config.custom as TableFieldOptions;
+
+ if (
+ fieldOptions !== undefined &&
+ fieldOptions.cellOptions !== undefined &&
+ fieldOptions.cellOptions.type === TableCellDisplayMode.ColorBackground &&
+ fieldOptions.cellOptions.applyToRow
+ ) {
+ rowBg = (rowIndex: number): CellColors => {
+ const display = field.display!(field.values.get(rowIndex));
+ const colors = getCellColors(tableStyles, fieldOptions.cellOptions, display);
+ return colors;
+ };
+ }
+ }
+
const RenderRow = useCallback(
({ index, style, rowHighlightIndex }: { index: number; style: CSSProperties; rowHighlightIndex?: number }) => {
const indexForPagination = rowIndexForPagination(index);
const row = rows[indexForPagination];
let additionalProps: React.HTMLAttributes = {};
-
prepareRow(row);
const expandedRowStyle = tableState.expanded[row.id] ? css({ '&:hover': { background: 'inherit' } }) : {};
@@ -217,6 +235,13 @@ export const RowsList = (props: RowsListProps) => {
'aria-selected': 'true',
};
}
+
+ if (rowBg) {
+ const { bgColor, textColor } = rowBg(row.index);
+ style.background = bgColor;
+ style.color = textColor;
+ }
+
return (
{
columnCount={row.cells.length}
timeRange={timeRange}
frame={data}
+ rowStyled={rowBg !== undefined}
/>
))}
@@ -264,6 +290,7 @@ export const RowsList = (props: RowsListProps) => {
theme.components.table.rowHoverBackground,
timeRange,
width,
+ rowBg,
]
);
diff --git a/packages/grafana-ui/src/components/Table/TableCell.tsx b/packages/grafana-ui/src/components/Table/TableCell.tsx
index 8b0452a8550..4419560aa93 100644
--- a/packages/grafana-ui/src/components/Table/TableCell.tsx
+++ b/packages/grafana-ui/src/components/Table/TableCell.tsx
@@ -15,9 +15,10 @@ export interface Props {
timeRange?: TimeRange;
userProps?: object;
frame: DataFrame;
+ rowStyled?: boolean;
}
-export const TableCell = ({ cell, tableStyles, onCellFilterAdded, timeRange, userProps, frame }: Props) => {
+export const TableCell = ({ cell, tableStyles, onCellFilterAdded, timeRange, userProps, frame, rowStyled }: Props) => {
const cellProps = cell.getCellProps();
const field = (cell.column as unknown as GrafanaTableColumn).field;
@@ -43,6 +44,7 @@ export const TableCell = ({ cell, tableStyles, onCellFilterAdded, timeRange, use
timeRange,
userProps,
frame,
+ rowStyled,
})}
>
);
diff --git a/packages/grafana-ui/src/components/Table/styles.ts b/packages/grafana-ui/src/components/Table/styles.ts
index 787e2ee5b51..0385f11d9d3 100644
--- a/packages/grafana-ui/src/components/Table/styles.ts
+++ b/packages/grafana-ui/src/components/Table/styles.ts
@@ -16,7 +16,8 @@ export function useTableStyles(theme: GrafanaTheme2, cellHeightOption: TableCell
background?: string,
overflowOnHover?: boolean,
asCellText?: boolean,
- textShouldWrap?: boolean
+ textShouldWrap?: boolean,
+ rowStyled?: boolean
) => {
return css({
label: overflowOnHover ? 'cellContainerOverflow' : 'cellContainerNoOverflow',
@@ -39,8 +40,8 @@ export function useTableStyles(theme: GrafanaTheme2, cellHeightOption: TableCell
alignItems: 'center',
borderRight: `1px solid ${borderColor}`,
- color: color ?? undefined,
- background: background ?? undefined,
+ color: rowStyled ? 'inherit' : color ?? undefined,
+ background: rowStyled ? undefined : background ?? undefined,
backgroundClip: 'padding-box',
'&:last-child:not(:only-child)': {
@@ -55,12 +56,14 @@ export function useTableStyles(theme: GrafanaTheme2, cellHeightOption: TableCell
wordBreak: textShouldWrap ? 'break-word' : undefined,
whiteSpace: textShouldWrap && overflowOnHover ? 'normal' : 'nowrap',
boxShadow: overflowOnHover ? `0 0 2px ${theme.colors.primary.main}` : undefined,
- background: overflowOnHover ? background ?? theme.components.table.rowHoverBackground : undefined,
+ background: rowStyled ? 'inherit' : background ?? undefined,
zIndex: 1,
'.cellActions': {
+ color: '#FFF',
visibility: 'visible',
opacity: 1,
width: 'auto',
+ background: 'rgba(0, 0, 0, 0.6)',
},
},
@@ -71,7 +74,7 @@ export function useTableStyles(theme: GrafanaTheme2, cellHeightOption: TableCell
'.cellActions': {
display: 'flex',
position: overflowOnHover ? undefined : 'absolute',
- top: overflowOnHover ? undefined : 0,
+ top: overflowOnHover ? undefined : '1px',
right: overflowOnHover ? undefined : 0,
margin: overflowOnHover ? theme.spacing(0, -0.5, 0, 0.5) : 'auto',
visibility: 'hidden',
@@ -79,8 +82,8 @@ export function useTableStyles(theme: GrafanaTheme2, cellHeightOption: TableCell
width: 0,
alignItems: 'center',
height: '100%',
- padding: theme.spacing(1, 0.5, 1, 0.5),
- background: background ? 'none' : theme.colors.emphasize(theme.colors.background.primary, 0.03),
+ padding: theme.spacing(1, 0.5, 1, 1),
+ background: background ? 'none' : 'rgba(0, 0, 0, 0.5)',
svg: {
color,
diff --git a/packages/grafana-ui/src/components/Table/types.ts b/packages/grafana-ui/src/components/Table/types.ts
index 563a0d9ee48..160edbc2742 100644
--- a/packages/grafana-ui/src/components/Table/types.ts
+++ b/packages/grafana-ui/src/components/Table/types.ts
@@ -140,3 +140,10 @@ export type TableFieldOptions = Omit &
cellOptions: TableCellOptions;
headerComponent?: React.ComponentType;
};
+
+// Cell background and text colors
+// Can also be used for table rows
+export interface CellColors {
+ textColor?: string;
+ bgColor?: string;
+}
diff --git a/packages/grafana-ui/src/components/Table/utils.ts b/packages/grafana-ui/src/components/Table/utils.ts
index 34a8efe5aeb..1274dc053d2 100644
--- a/packages/grafana-ui/src/components/Table/utils.ts
+++ b/packages/grafana-ui/src/components/Table/utils.ts
@@ -2,6 +2,7 @@ import { Property } from 'csstype';
import { clone } from 'lodash';
import memoize from 'micro-memoize';
import { Row } from 'react-table';
+import tinycolor from 'tinycolor2';
import {
DataFrame,
@@ -27,6 +28,8 @@ import {
TableCellDisplayMode,
} from '@grafana/schema';
+import { getTextColorForAlphaBackground } from '../../utils';
+
import { BarGaugeCell } from './BarGaugeCell';
import { DataLinksCell } from './DataLinksCell';
import { DefaultCell } from './DefaultCell';
@@ -36,6 +39,7 @@ import { ImageCell } from './ImageCell';
import { JSONViewCell } from './JSONViewCell';
import { RowExpander } from './RowExpander';
import { SparklineCell } from './SparklineCell';
+import { TableStyles } from './styles';
import {
CellComponent,
TableCellOptions,
@@ -43,6 +47,7 @@ import {
FooterItem,
GrafanaTableColumn,
TableFooterCalc,
+ CellColors,
} from './types';
export const EXPANDER_WIDTH = 50;
@@ -581,3 +586,46 @@ export function calculateAroundPointThreshold(timeField: Field): number {
return (max - min) / timeField.values.length;
}
+
+/**
+ * Retrieve colors for a table cell (or table row).
+ *
+ * @param tableStyles
+ * Styles for the table
+ * @param cellOptions
+ * Table cell configuration options
+ * @param displayValue
+ * The value that will be displayed
+ * @returns CellColors
+ */
+export function getCellColors(
+ tableStyles: TableStyles,
+ cellOptions: TableCellOptions,
+ displayValue: DisplayValue
+): CellColors {
+ // How much to darken elements depends upon if we're in dark mode
+ const darkeningFactor = tableStyles.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!, tableStyles.theme.isDark);
+ bgColor = tinycolor(displayValue.color).setAlpha(1).toRgbString();
+ } else if (mode === TableCellBackgroundDisplayMode.Gradient) {
+ const bgColor2 = tinycolor(displayValue.color)
+ .darken(10 * darkeningFactor)
+ .spin(5);
+ textColor = getTextColorForAlphaBackground(displayValue.color!, tableStyles.theme.isDark);
+ bgColor = `linear-gradient(120deg, ${bgColor2.toRgbString()}, ${displayValue.color})`;
+ }
+ }
+
+ return { textColor, bgColor };
+}
diff --git a/public/app/plugins/panel/table/cells/ColorBackgroundCellOptionsEditor.tsx b/public/app/plugins/panel/table/cells/ColorBackgroundCellOptionsEditor.tsx
index 26bb955edf6..76cdf277e5d 100644
--- a/public/app/plugins/panel/table/cells/ColorBackgroundCellOptionsEditor.tsx
+++ b/public/app/plugins/panel/table/cells/ColorBackgroundCellOptionsEditor.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import { SelectableValue } from '@grafana/data';
import { TableCellBackgroundDisplayMode, TableColoredBackgroundCellOptions } from '@grafana/schema';
-import { Field, RadioButtonGroup } from '@grafana/ui';
+import { Field, RadioButtonGroup, Switch } from '@grafana/ui';
import { TableCellEditorProps } from '../TableCellOptionEditor';
@@ -21,13 +21,27 @@ export const ColorBackgroundCellOptionsEditor = ({
onChange(cellOptions);
};
+ // Handle row coloring changes
+ const onColorRowChange = () => {
+ cellOptions.applyToRow = !cellOptions.applyToRow;
+ onChange(cellOptions);
+ };
+
return (
-
-
-
+ <>
+
+
+
+
+
+
+ >
);
};
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index 75b56554755..e3f83ca3384 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -369,7 +369,6 @@
"title-label": "Title"
},
"json-editor": {
- "apply-button": "Apply changes",
"save-button": "Save changes",
"subtitle": "The JSON model below is the data structure that defines the dashboard. This includes dashboard settings, panel settings, layout, queries, and so on.",
"title": "JSON Model"
diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json
index 548641ea6fe..688aa9d31b9 100644
--- a/public/locales/pseudo-LOCALE/grafana.json
+++ b/public/locales/pseudo-LOCALE/grafana.json
@@ -369,7 +369,6 @@
"title-label": "Ŧįŧľę"
},
"json-editor": {
- "apply-button": "Åppľy čĥäʼnģęş",
"save-button": "Ŝävę čĥäʼnģęş",
"subtitle": "Ŧĥę ĴŜØŃ mőđęľ þęľőŵ įş ŧĥę đäŧä şŧřūčŧūřę ŧĥäŧ đęƒįʼnęş ŧĥę đäşĥþőäřđ. Ŧĥįş įʼnčľūđęş đäşĥþőäřđ şęŧŧįʼnģş, päʼnęľ şęŧŧįʼnģş, ľäyőūŧ, qūęřįęş, äʼnđ şő őʼn.",
"title": "ĴŜØŃ Mőđęľ"