mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
ColorSchemes: Adds more color schemes and text colors that depend on the background (#28305)
* Adding more color modes and text colors that depend on the background color * Updates * Updated * Another big value fix * Fixing unit tests * Updated * Updated test * Update * Updated * Updated * Updated * Updated * Added new demo dashboard * Updated * updated * Updated * Updateed * added beta notice * Fixed e2e test
This commit is contained in:
@@ -3,13 +3,14 @@ import _ from 'lodash';
|
||||
|
||||
// Types
|
||||
import { Field, FieldType } from '../types/dataFrame';
|
||||
import { GrafanaTheme, GrafanaThemeType } from '../types/theme';
|
||||
import { GrafanaTheme } from '../types/theme';
|
||||
import { DecimalCount, DecimalInfo, DisplayProcessor, DisplayValue } from '../types/displayValue';
|
||||
import { getValueFormat } from '../valueFormats/valueFormats';
|
||||
import { getMappedValue } from '../utils/valueMappings';
|
||||
import { dateTime } from '../datetime';
|
||||
import { KeyValue, TimeZone } from '../types';
|
||||
import { getScaleCalculator } from './scale';
|
||||
import { getTestTheme } from '../utils/testdata/testTheme';
|
||||
|
||||
interface DisplayProcessorOptions {
|
||||
field: Partial<Field>;
|
||||
@@ -41,7 +42,7 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP
|
||||
const config = field.config ?? {};
|
||||
|
||||
// Theme should be required or we need access to default theme instance from here
|
||||
const theme = options.theme ?? ({ type: GrafanaThemeType.Dark } as GrafanaTheme);
|
||||
const theme = options.theme ?? getTestTheme();
|
||||
|
||||
let unit = config.unit;
|
||||
let hasDateUnit = unit && (timeFormats[unit] || unit.startsWith('time:'));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Field, GrafanaThemeType, GrafanaTheme, FieldColorModeId } from '../types';
|
||||
import { Field, FieldColorModeId } from '../types';
|
||||
import { getTestTheme } from '../utils/testdata/testTheme';
|
||||
import { fieldColorModeRegistry, FieldValueColorCalculator } from './fieldColor';
|
||||
|
||||
describe('fieldColorModeRegistry', () => {
|
||||
@@ -9,10 +10,7 @@ describe('fieldColorModeRegistry', () => {
|
||||
|
||||
function getCalculator(options: GetCalcOptions): FieldValueColorCalculator {
|
||||
const mode = fieldColorModeRegistry.get(options.mode);
|
||||
return mode.getCalculator(
|
||||
{ state: { seriesIndex: options.seriesIndex } } as Field,
|
||||
{ type: GrafanaThemeType.Dark } as GrafanaTheme
|
||||
);
|
||||
return mode.getCalculator({ state: { seriesIndex: options.seriesIndex } } as Field, getTestTheme());
|
||||
}
|
||||
|
||||
it('Schemes should interpolate', () => {
|
||||
|
||||
@@ -54,20 +54,81 @@ export const fieldColorModeRegistry = new Registry<FieldColorMode>(() => {
|
||||
// }),
|
||||
new FieldColorSchemeMode({
|
||||
id: FieldColorModeId.PaletteClassic,
|
||||
name: 'By series / Classic palette',
|
||||
//description: 'Assigns color based on series or field index',
|
||||
name: 'Classic palette',
|
||||
isContinuous: false,
|
||||
isByValue: false,
|
||||
colors: classicColors,
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-GrYlRd',
|
||||
name: 'By value / Green Yellow Red (gradient)',
|
||||
//description: 'Interpolated colors based value, min and max',
|
||||
name: 'Green-Yellow-Red',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['green', 'yellow', 'red'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-BlYlRd',
|
||||
name: 'Blue-Yellow-Red',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['dark-blue', 'super-light-yellow', 'dark-red'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-RdYlBl',
|
||||
name: 'Red-Yellow-Blue',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['dark-red', 'super-light-yellow', 'dark-blue'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-YlRd',
|
||||
name: 'Yellow-Red',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['super-light-yellow', 'dark-red'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-BlPu',
|
||||
name: 'Blue-Purple',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['blue', 'purple'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-YlBl',
|
||||
name: 'Yellow-Blue',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['super-light-yellow', 'dark-blue'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-blues',
|
||||
name: 'Blues',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['panel-bg', 'dark-blue'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-reds',
|
||||
name: 'Reds',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['panel-bg', 'dark-red'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-greens',
|
||||
name: 'Greens',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['panel-bg', 'dark-green'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-purples',
|
||||
name: 'Purples',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['panel-bg', 'dark-purple'],
|
||||
}),
|
||||
];
|
||||
});
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ import merge from 'lodash/merge';
|
||||
import { getFieldDisplayValues, GetFieldDisplayValuesOptions } from './fieldDisplay';
|
||||
import { toDataFrame } from '../dataframe/processDataFrame';
|
||||
import { ReducerID } from '../transformations/fieldReducer';
|
||||
import { GrafanaTheme } from '../types/theme';
|
||||
import { MappingType } from '../types';
|
||||
import { standardFieldConfigEditorRegistry } from './standardFieldConfigEditorRegistry';
|
||||
import { getTestTheme } from '../utils/testdata/testTheme';
|
||||
|
||||
describe('FieldDisplay', () => {
|
||||
beforeAll(() => {
|
||||
@@ -241,7 +241,7 @@ function createDisplayOptions(extend: Partial<GetFieldDisplayValuesOptions> = {}
|
||||
overrides: [],
|
||||
defaults: {},
|
||||
},
|
||||
theme: {} as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
};
|
||||
|
||||
return merge<GetFieldDisplayValuesOptions, any>(options, extend);
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
FieldConfigPropertyItem,
|
||||
FieldConfigSource,
|
||||
FieldType,
|
||||
GrafanaTheme,
|
||||
InterpolateFunction,
|
||||
ThresholdsMode,
|
||||
FieldColorModeId,
|
||||
@@ -28,6 +27,7 @@ import { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry';
|
||||
import { getFieldDisplayName } from './fieldState';
|
||||
import { ArrayVector } from '../vector';
|
||||
import { getDisplayProcessor } from './displayProcessor';
|
||||
import { getTestTheme } from '../utils/testdata/testTheme';
|
||||
|
||||
const property1: any = {
|
||||
id: 'custom.property1', // Match field properties
|
||||
@@ -136,7 +136,7 @@ describe('applyFieldOverrides', () => {
|
||||
},
|
||||
replaceVariables: (value: any) => value,
|
||||
getDataSourceSettingsByUid: undefined as any,
|
||||
theme: {} as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
fieldConfigRegistry: new FieldConfigOptionsRegistry(),
|
||||
});
|
||||
|
||||
@@ -199,7 +199,7 @@ describe('applyFieldOverrides', () => {
|
||||
fieldConfigRegistry: customFieldRegistry,
|
||||
getDataSourceSettingsByUid: undefined as any,
|
||||
replaceVariables: v => v,
|
||||
theme: {} as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
})[0];
|
||||
|
||||
const outField = processed.fields[0];
|
||||
@@ -216,7 +216,7 @@ describe('applyFieldOverrides', () => {
|
||||
fieldConfig: src as FieldConfigSource, // defaults + overrides
|
||||
replaceVariables: (undefined as any) as InterpolateFunction,
|
||||
getDataSourceSettingsByUid: undefined as any,
|
||||
theme: (undefined as any) as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
fieldConfigRegistry: customFieldRegistry,
|
||||
})[0];
|
||||
const valueColumn = data.fields[1];
|
||||
@@ -244,7 +244,7 @@ describe('applyFieldOverrides', () => {
|
||||
fieldConfig: src as FieldConfigSource, // defaults + overrides
|
||||
replaceVariables: (undefined as any) as InterpolateFunction,
|
||||
getDataSourceSettingsByUid: undefined as any,
|
||||
theme: (undefined as any) as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
autoMinMax: true,
|
||||
})[0];
|
||||
const valueColumn = data.fields[1];
|
||||
@@ -268,7 +268,7 @@ describe('applyFieldOverrides', () => {
|
||||
return value;
|
||||
}) as InterpolateFunction,
|
||||
getDataSourceSettingsByUid: undefined as any,
|
||||
theme: (undefined as any) as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
autoMinMax: true,
|
||||
fieldConfigRegistry: customFieldRegistry,
|
||||
})[0];
|
||||
@@ -521,7 +521,7 @@ describe('getLinksSupplier', () => {
|
||||
// this is used only for internal links so isn't needed here
|
||||
() => ({} as any),
|
||||
{
|
||||
theme: {} as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
}
|
||||
);
|
||||
supplier({});
|
||||
@@ -568,7 +568,7 @@ describe('getLinksSupplier', () => {
|
||||
// We do not need to interpolate anything for this test
|
||||
(value, vars, format) => value,
|
||||
uid => ({ name: 'testDS' } as any),
|
||||
{ theme: {} as GrafanaTheme }
|
||||
{ theme: getTestTheme() }
|
||||
);
|
||||
const links = supplier({ valueRowIndex: 0 });
|
||||
expect(links.length).toBe(1);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { getFieldDisplayValuesProxy } from './getFieldDisplayValuesProxy';
|
||||
import { applyFieldOverrides } from './fieldOverrides';
|
||||
import { toDataFrame } from '../dataframe';
|
||||
import { GrafanaTheme } from '../types';
|
||||
import { getTestTheme } from '../utils/testdata/testTheme';
|
||||
|
||||
describe('getFieldDisplayValuesProxy', () => {
|
||||
const data = applyFieldOverrides({
|
||||
@@ -30,7 +31,7 @@ describe('getFieldDisplayValuesProxy', () => {
|
||||
replaceVariables: (val: string) => val,
|
||||
getDataSourceSettingsByUid: (val: string) => ({} as any),
|
||||
timeZone: 'utc',
|
||||
theme: {} as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
autoMinMax: true,
|
||||
})[0];
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ThresholdsMode, Field, FieldType, GrafanaThemeType, GrafanaTheme } from '../types';
|
||||
import { ThresholdsMode, Field, FieldType } from '../types';
|
||||
import { sortThresholds } from './thresholds';
|
||||
import { ArrayVector } from '../vector/ArrayVector';
|
||||
import { getScaleCalculator } from './scale';
|
||||
import { getTestTheme } from '../utils/testdata/testTheme';
|
||||
|
||||
describe('getScaleCalculator', () => {
|
||||
it('should return percent, threshold and color', () => {
|
||||
@@ -18,7 +19,7 @@ describe('getScaleCalculator', () => {
|
||||
values: new ArrayVector([0, 50, 100]),
|
||||
};
|
||||
|
||||
const calc = getScaleCalculator(field, { type: GrafanaThemeType.Dark } as GrafanaTheme);
|
||||
const calc = getScaleCalculator(field, getTestTheme());
|
||||
expect(calc(70)).toEqual({
|
||||
percent: 0.7,
|
||||
threshold: thresholds[1],
|
||||
|
||||
@@ -34,7 +34,8 @@ export type Color =
|
||||
| 'dark-purple'
|
||||
| 'semi-dark-purple'
|
||||
| 'light-purple'
|
||||
| 'super-light-purple';
|
||||
| 'super-light-purple'
|
||||
| 'panel-bg';
|
||||
|
||||
type ThemeVariants = {
|
||||
dark: string;
|
||||
@@ -82,6 +83,8 @@ export function buildColorsMapForTheme(theme: GrafanaTheme): Record<Color, strin
|
||||
}
|
||||
}
|
||||
|
||||
colorsMap['panel-bg'] = theme.colors.panelBg;
|
||||
|
||||
return colorsMap;
|
||||
}
|
||||
|
||||
@@ -118,7 +121,25 @@ export function getColorForTheme(color: string, theme: GrafanaTheme): string {
|
||||
export function getColorFromHexRgbOrName(color: string, type?: GrafanaThemeType): string {
|
||||
const themeType = type ?? GrafanaThemeType.Dark;
|
||||
|
||||
return getColorForTheme(color, ({ type: themeType } as unknown) as GrafanaTheme);
|
||||
if (themeType === GrafanaThemeType.Dark) {
|
||||
const darkTheme = ({
|
||||
type: themeType,
|
||||
colors: {
|
||||
panelBg: '#141619',
|
||||
},
|
||||
} as unknown) as GrafanaTheme;
|
||||
|
||||
return getColorForTheme(color, darkTheme);
|
||||
}
|
||||
|
||||
const lightTheme = ({
|
||||
type: themeType,
|
||||
colors: {
|
||||
panelBg: '#000000',
|
||||
},
|
||||
} as unknown) as GrafanaTheme;
|
||||
|
||||
return getColorForTheme(color, lightTheme);
|
||||
}
|
||||
|
||||
const buildNamedColorsPalette = () => {
|
||||
|
||||
12
packages/grafana-data/src/utils/testdata/testTheme.ts
vendored
Normal file
12
packages/grafana-data/src/utils/testdata/testTheme.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import { GrafanaTheme, GrafanaThemeType } from '../../types/theme';
|
||||
|
||||
export function getTestTheme(type: GrafanaThemeType = GrafanaThemeType.Dark): GrafanaTheme {
|
||||
return ({
|
||||
type,
|
||||
isDark: type === GrafanaThemeType.Dark,
|
||||
isLight: type === GrafanaThemeType.Light,
|
||||
colors: {
|
||||
panelBg: 'white',
|
||||
},
|
||||
} as unknown) as GrafanaTheme;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { calculateFontSize } from '../../utils/measureText';
|
||||
|
||||
// Types
|
||||
import { BigValueColorMode, Props, BigValueJustifyMode, BigValueTextMode } from './BigValue';
|
||||
import { getTextColorForBackground } from '../../utils';
|
||||
|
||||
const LINE_HEIGHT = 1.2;
|
||||
const MAX_TITLE_SIZE = 30;
|
||||
@@ -51,7 +52,7 @@ export abstract class BigValueLayout {
|
||||
};
|
||||
|
||||
if (this.props.colorMode === BigValueColorMode.Background) {
|
||||
styles.color = 'white';
|
||||
styles.color = getTextColorForBackground(this.valueColor);
|
||||
}
|
||||
|
||||
return styles;
|
||||
@@ -69,7 +70,7 @@ export abstract class BigValueLayout {
|
||||
styles.color = this.valueColor;
|
||||
break;
|
||||
case BigValueColorMode.Background:
|
||||
styles.color = 'white';
|
||||
styles.color = getTextColorForBackground(this.valueColor);
|
||||
}
|
||||
|
||||
return styles;
|
||||
|
||||
@@ -30,7 +30,7 @@ exports[`BigValue Render with basic options should render 1`] = `
|
||||
<FormattedDisplayValue
|
||||
style={
|
||||
Object {
|
||||
"color": "white",
|
||||
"color": "#202226",
|
||||
"fontSize": 230,
|
||||
"fontWeight": 500,
|
||||
"lineHeight": 1.2,
|
||||
|
||||
@@ -23,9 +23,11 @@ export const FieldColorEditor: React.FC<FieldConfigEditorProps<FieldColor | unde
|
||||
const styles = useStyles(getStyles);
|
||||
|
||||
const options = fieldColorModeRegistry.list().map(mode => {
|
||||
let suffix = mode.isByValue ? ' (by value)' : '';
|
||||
|
||||
return {
|
||||
value: mode.id,
|
||||
label: mode.name,
|
||||
label: `${mode.name}${suffix}`,
|
||||
description: mode.description,
|
||||
isContinuous: mode.isContinuous,
|
||||
isByValue: mode.isByValue,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { TableCellDisplayMode, TableCellProps } from './types';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { TableStyles } from './styles';
|
||||
import { FilterActions } from './FilterActions';
|
||||
import { getTextColorForBackground } from '../../utils';
|
||||
|
||||
export const DefaultCell: FC<TableCellProps> = props => {
|
||||
const { field, cell, tableStyles, row, cellProps } = props;
|
||||
@@ -65,7 +66,12 @@ function getCellStyle(tableStyles: TableStyles, field: Field, displayValue: Disp
|
||||
.spin(5)
|
||||
.toRgbString();
|
||||
|
||||
return tableStyles.buildCellContainerStyle('white', `linear-gradient(120deg, ${bgColor2}, ${displayValue.color})`);
|
||||
const textColor = getTextColorForBackground(displayValue.color!);
|
||||
|
||||
return tableStyles.buildCellContainerStyle(
|
||||
textColor,
|
||||
`linear-gradient(120deg, ${bgColor2}, ${displayValue.color})`
|
||||
);
|
||||
}
|
||||
|
||||
return tableStyles.cellContainer;
|
||||
|
||||
@@ -4,6 +4,8 @@ import flattenDeep from 'lodash/flattenDeep';
|
||||
import chunk from 'lodash/chunk';
|
||||
import zip from 'lodash/zip';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import lightTheme from '../themes/light';
|
||||
import darkTheme from '../themes/dark';
|
||||
|
||||
export const PALETTE_ROWS = 4;
|
||||
export const PALETTE_COLUMNS = 14;
|
||||
@@ -93,4 +95,9 @@ function hslToHex(color: any) {
|
||||
return tinycolor(color).toHexString();
|
||||
}
|
||||
|
||||
export function getTextColorForBackground(color: string) {
|
||||
const b = tinycolor(color).getBrightness();
|
||||
return b > 150 ? lightTheme.colors.textStrong : darkTheme.colors.textStrong;
|
||||
}
|
||||
|
||||
export let sortedColors = sortColorsByHue(colors);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
let canvas: HTMLCanvasElement | null = null;
|
||||
const cache: Record<string, TextMetrics> = {};
|
||||
|
||||
/**
|
||||
* @beta
|
||||
*/
|
||||
export function measureText(text: string, fontSize: number): TextMetrics {
|
||||
const fontStyle = `${fontSize}px 'Roboto'`;
|
||||
const cacheKey = text + fontStyle;
|
||||
@@ -26,6 +29,9 @@ export function measureText(text: string, fontSize: number): TextMetrics {
|
||||
return metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* @beta
|
||||
*/
|
||||
export function calculateFontSize(text: string, width: number, height: number, lineHeight: number, maxSize?: number) {
|
||||
// calculate width in 14px
|
||||
const textSize = measureText(text, 14);
|
||||
|
||||
Reference in New Issue
Block a user