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:
Torkel Ödegaard
2020-10-19 10:25:27 +02:00
committed by GitHub
parent 95a1993443
commit 566cd2c6af
19 changed files with 494 additions and 38 deletions

View File

@@ -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:'));

View File

@@ -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', () => {

View File

@@ -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'],
}),
];
});

View File

@@ -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);

View File

@@ -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);

View File

@@ -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];

View File

@@ -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],

View File

@@ -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 = () => {

View 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;
}

View File

@@ -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;

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);