Theme: New visualization colors model (#33973)

* Initial design for new viz colors

* added unit tests

* Progress

* Updated selected color

* Use old named color names and colors to begin with

* Updates

* Fixing tests

* Progress

* Updates

* updating

* fixing tests

* Using some named colors

* renames && fixes

* remove plural

* fixed tests
This commit is contained in:
Torkel Ödegaard
2021-05-13 07:32:13 +02:00
committed by GitHub
parent 17750d3cdb
commit 567bbdf6e6
26 changed files with 673 additions and 374 deletions

View File

@@ -11,7 +11,6 @@ import { KeyValue, TimeZone } from '../types';
import { getScaleCalculator, ScaleCalculator } from './scale';
import { GrafanaTheme2 } from '../themes/types';
import { anyToNumber } from '../utils/anyToNumber';
import { classicColors, getColorForTheme } from '../utils/namedColorsPalette';
interface DisplayProcessorOptions {
field: Partial<Field>;
@@ -82,7 +81,7 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP
}
if (mappingResult.color != null) {
color = getColorForTheme(mappingResult.color, options.theme.v1);
color = options.theme.visualization.getColorByName(mappingResult.color);
}
shouldFormat = false;
@@ -143,7 +142,7 @@ function getDefaultColorFunc(field: Field, scaleFunc: ScaleCalculator, theme: Gr
const hc = strHashCode(value as string);
return {
color: classicColors[Math.floor(hc % classicColors.length)],
color: theme.visualization.palette[Math.floor(hc % theme.visualization.palette.length)],
percent: 0,
};
};

View File

@@ -37,12 +37,12 @@ describe('fieldColorModeRegistry', () => {
it('Palette classic with series index 0', () => {
const calcFn = getCalculator({ mode: FieldColorModeId.PaletteClassic, seriesIndex: 0 });
expect(calcFn(70, 0, undefined)).toEqual('#7EB26D');
expect(calcFn(70, 0, undefined)).toEqual('#73BF69');
});
it('Palette classic with series index 1', () => {
const calcFn = getCalculator({ mode: FieldColorModeId.PaletteClassic, seriesIndex: 1 });
expect(calcFn(70, 0, undefined)).toEqual('#EAB839');
expect(calcFn(70, 0, undefined)).toEqual('#F2CC0C');
});
it('When color.seriesBy is set to last use that instead of v', () => {

View File

@@ -1,5 +1,5 @@
import { FALLBACK_COLOR, Field, FieldColorModeId, Threshold } from '../types';
import { classicColors, getColorForTheme, RegistryItem } from '../utils';
import { RegistryItem } from '../utils';
import { Registry } from '../utils/Registry';
import { interpolateRgbBasis } from 'd3-interpolate';
import { fallBackTreshold } from './thresholds';
@@ -13,7 +13,7 @@ export type FieldValueColorCalculator = (value: number, percent: number, Thresho
/** @beta */
export interface FieldColorMode extends RegistryItem {
getCalculator: (field: Field, theme: GrafanaTheme2) => FieldValueColorCalculator;
colors?: string[];
getColors?: (theme: GrafanaTheme2) => string[];
isContinuous?: boolean;
isByValue?: boolean;
}
@@ -35,106 +35,88 @@ export const fieldColorModeRegistry = new Registry<FieldColorMode>(() => {
getCalculator: (_field, theme) => {
return (_value, _percent, threshold) => {
const thresholdSafe = threshold ?? fallBackTreshold;
return getColorForTheme(thresholdSafe.color, theme.v1);
return theme.visualization.getColorByName(thresholdSafe.color);
};
},
},
// new FieldColorSchemeMode({
// id: FieldColorModeId.PaletteSaturated,
// name: 'Saturated palette',
// //description: 'Assigns color based on series or field index',
// isContinuous: false,
// isByValue: false,
// colors: [
// 'blue',
// 'red',
// 'green',
// 'orange',
// 'purple',
// 'orange',
// 'dark-blue',
// 'dark-red',
// 'dark-yellow',
// 'dark-purple',
// 'dark-orange',
// ],
// }),
new FieldColorSchemeMode({
id: FieldColorModeId.PaletteClassic,
name: 'Classic palette',
isContinuous: false,
isByValue: false,
colors: classicColors,
getColors: (theme: GrafanaTheme2) => {
return theme.visualization.palette;
},
}),
new FieldColorSchemeMode({
id: 'continuous-GrYlRd',
name: 'Green-Yellow-Red',
isContinuous: true,
isByValue: true,
colors: ['green', 'yellow', 'red'],
getColors: (theme: GrafanaTheme2) => ['green', 'yellow', 'red'],
}),
new FieldColorSchemeMode({
id: 'continuous-RdYlGr',
name: 'Red-Yellow-Green',
isContinuous: true,
isByValue: true,
colors: ['red', 'yellow', 'green'],
getColors: (theme: GrafanaTheme2) => ['red', 'yellow', 'green'],
}),
new FieldColorSchemeMode({
id: 'continuous-BlYlRd',
name: 'Blue-Yellow-Red',
isContinuous: true,
isByValue: true,
colors: ['dark-blue', 'super-light-yellow', 'dark-red'],
getColors: (theme: GrafanaTheme2) => ['dark-blue', 'super-light-yellow', 'dark-red'],
}),
new FieldColorSchemeMode({
id: 'continuous-YlRd',
name: 'Yellow-Red',
isContinuous: true,
isByValue: true,
colors: ['super-light-yellow', 'dark-red'],
getColors: (theme: GrafanaTheme2) => ['super-light-yellow', 'dark-red'],
}),
new FieldColorSchemeMode({
id: 'continuous-BlPu',
name: 'Blue-Purple',
isContinuous: true,
isByValue: true,
colors: ['blue', 'purple'],
getColors: (theme: GrafanaTheme2) => ['blue', 'purple'],
}),
new FieldColorSchemeMode({
id: 'continuous-YlBl',
name: 'Yellow-Blue',
isContinuous: true,
isByValue: true,
colors: ['super-light-yellow', 'dark-blue'],
getColors: (theme: GrafanaTheme2) => ['super-light-yellow', 'dark-blue'],
}),
new FieldColorSchemeMode({
id: 'continuous-blues',
name: 'Blues',
isContinuous: true,
isByValue: true,
colors: ['panel-bg', 'dark-blue'],
getColors: (theme: GrafanaTheme2) => ['panel-bg', 'dark-blue'],
}),
new FieldColorSchemeMode({
id: 'continuous-reds',
name: 'Reds',
isContinuous: true,
isByValue: true,
colors: ['panel-bg', 'dark-red'],
getColors: (theme: GrafanaTheme2) => ['panel-bg', 'dark-red'],
}),
new FieldColorSchemeMode({
id: 'continuous-greens',
name: 'Greens',
isContinuous: true,
isByValue: true,
colors: ['panel-bg', 'dark-green'],
getColors: (theme: GrafanaTheme2) => ['panel-bg', 'dark-green'],
}),
new FieldColorSchemeMode({
id: 'continuous-purples',
name: 'Purples',
isContinuous: true,
isByValue: true,
colors: ['panel-bg', 'dark-purple'],
getColors: (theme: GrafanaTheme2) => ['panel-bg', 'dark-purple'],
}),
];
});
@@ -143,7 +125,7 @@ interface FieldColorSchemeModeOptions {
id: string;
name: string;
description?: string;
colors: string[];
getColors: (theme: GrafanaTheme2) => string[];
isContinuous: boolean;
isByValue: boolean;
}
@@ -152,27 +134,34 @@ export class FieldColorSchemeMode implements FieldColorMode {
id: string;
name: string;
description?: string;
colors: string[];
isContinuous: boolean;
isByValue: boolean;
colorCache?: string[];
colorCacheTheme?: GrafanaTheme2;
interpolator?: (value: number) => string;
getNamedColors?: (theme: GrafanaTheme2) => string[];
constructor(options: FieldColorSchemeModeOptions) {
this.id = options.id;
this.name = options.name;
this.description = options.description;
this.colors = options.colors;
this.getNamedColors = options.getColors;
this.isContinuous = options.isContinuous;
this.isByValue = options.isByValue;
}
private getColors(theme: GrafanaTheme2) {
if (this.colorCache) {
getColors(theme: GrafanaTheme2): string[] {
if (!this.getNamedColors) {
return [];
}
if (this.colorCache && this.colorCacheTheme === theme) {
return this.colorCache;
}
this.colorCache = this.colors.map((c) => getColorForTheme(c, theme.v1));
this.colorCache = this.getNamedColors(theme).map(theme.visualization.getColorByName);
this.colorCacheTheme = theme;
return this.colorCache;
}
@@ -243,6 +232,6 @@ export function getFieldSeriesColor(field: Field, theme: GrafanaTheme2): ColorSc
function getFixedColor(field: Field, theme: GrafanaTheme2) {
return () => {
return getColorForTheme(field.config.color?.fixedColor ?? FALLBACK_COLOR, theme.v1);
return theme.visualization.getColorByName(field.config.color?.fixedColor ?? FALLBACK_COLOR);
};
}

View File

@@ -2,7 +2,6 @@ import { isNumber } from 'lodash';
import { GrafanaTheme2 } from '../themes/types';
import { reduceField, ReducerID } from '../transformations/fieldReducer';
import { Field, FieldConfig, FieldType, NumericRange, Threshold } from '../types';
import { getColorForTheme } from '../utils';
import { getFieldColorModeForField } from './fieldColor';
import { getActiveThresholdForValue } from './thresholds';
@@ -42,21 +41,22 @@ export function getScaleCalculator(field: Field, theme: GrafanaTheme2): ScaleCal
function getBooleanScaleCalculator(field: Field, theme: GrafanaTheme2): ScaleCalculator {
const trueValue: ColorScaleValue = {
color: getColorForTheme('green', theme.v1),
color: theme.visualization.getColorByName('green'),
percent: 1,
threshold: (undefined as unknown) as Threshold,
};
const falseValue: ColorScaleValue = {
color: getColorForTheme('red', theme.v1),
color: theme.visualization.getColorByName('red'),
percent: 0,
threshold: (undefined as unknown) as Threshold,
};
const mode = getFieldColorModeForField(field);
if (mode.isContinuous && mode.colors) {
trueValue.color = getColorForTheme(mode.colors[mode.colors.length - 1], theme.v1);
falseValue.color = getColorForTheme(mode.colors[0], theme.v1);
if (mode.isContinuous && mode.getColors) {
const colors = mode.getColors(theme);
trueValue.color = colors[colors.length - 1];
falseValue.color = colors[0];
}
return (value: number) => {

View File

@@ -9,6 +9,7 @@ import { createTypography, ThemeTypographyInput } from './createTypography';
import { createV1Theme } from './createV1Theme';
import { GrafanaTheme2 } from './types';
import { zIndex } from './zIndex';
import { createVisualizationColors } from './createVisualizationColors';
/** @internal */
export interface NewThemeOptions {
@@ -37,6 +38,7 @@ export function createTheme(options: NewThemeOptions = {}): GrafanaTheme2 {
const shadows = createShadows(colors);
const transitions = createTransitions();
const components = createComponents(colors, shadows);
const visualization = createVisualizationColors(colors);
const theme = {
name,
@@ -50,6 +52,7 @@ export function createTheme(options: NewThemeOptions = {}): GrafanaTheme2 {
typography,
shadows,
transitions,
visualization,
zIndex: {
...zIndex,
},

View File

@@ -229,6 +229,7 @@ export function createV1Theme(theme: Omit<GrafanaTheme2, 'v1'>): GrafanaTheme {
shadows: {
listItem: 'none',
},
visualization: theme.visualization,
};
}

View File

@@ -0,0 +1,32 @@
import { createColors } from './createColors';
import { createVisualizationColors } from './createVisualizationColors';
describe('createVizColors', () => {
const darkThemeColors = createColors({});
const vizColors = createVisualizationColors(darkThemeColors);
it('Can map named colors to real color', () => {
expect(vizColors.getColorByName('green')).toBe('#73BF69');
});
it('Can map named colors using old aliases to real color', () => {
expect(vizColors.getColorByName('dark-green')).toBe('#37872D');
});
it('Can get color from palette', () => {
expect(vizColors.palette[0]).not.toBeUndefined();
});
it('returns color if specified as hex or rgb/a', () => {
expect(vizColors.getColorByName('#ff0000')).toBe('#ff0000');
expect(vizColors.getColorByName('#ff0000')).toBe('#ff0000');
expect(vizColors.getColorByName('#FF0000')).toBe('#FF0000');
expect(vizColors.getColorByName('#CCC')).toBe('#CCC');
expect(vizColors.getColorByName('rgb(0,0,0)')).toBe('rgb(0,0,0)');
expect(vizColors.getColorByName('rgba(0,0,0,1)')).toBe('rgba(0,0,0,1)');
});
it('returns hex for named color that is not a part of named colors palette', () => {
expect(vizColors.getColorByName('lime')).toBe('#00ff00');
});
});

View File

@@ -0,0 +1,521 @@
import { FALLBACK_COLOR } from '../types';
import { ThemeColors } from './createColors';
/**
* @alpha
*/
export interface ThemeVisualizationColors {
/** Only for internal use by color schemes */
palette: string[];
/** Lookup the real color given the name */
getColorByName: (color: string) => string;
/** Colors organized by hue */
hues: ThemeVizHue[];
}
/**
* @alpha
*/
export interface ThemeVizColor {
color: string;
name: string;
aliases?: string[];
primary?: boolean;
}
/**
* @alpha
*/
export interface ThemeVizHue {
name: string;
shades: ThemeVizColor[];
}
/**
* @internal
*/
export function createVisualizationColors(colors: ThemeColors): ThemeVisualizationColors {
let hues: ThemeVizHue[] = [];
if (colors.mode === 'dark') {
hues = getDarkHues();
} else if (colors.mode === 'light') {
hues = getLightHues();
}
const byNameIndex: Record<string, string> = {};
for (const hue of hues) {
for (const shade of hue.shades) {
byNameIndex[shade.name] = shade.color;
if (shade.aliases) {
for (const alias of shade.aliases) {
byNameIndex[alias] = shade.color;
}
}
}
}
// special colors
byNameIndex['transparent'] = 'rgba(0,0,0,0)';
byNameIndex['panel-bg'] = colors.background.primary;
byNameIndex['text'] = colors.text.primary;
const getColorByName = (colorName: string) => {
if (!colorName) {
return FALLBACK_COLOR;
}
const realColor = byNameIndex[colorName];
if (realColor) {
return realColor;
}
if (colorName[0] === '#') {
return colorName;
}
if (colorName.indexOf('rgb') > -1) {
return colorName;
}
const nativeColor = nativeColorNames[colorName.toLowerCase()];
if (nativeColor) {
byNameIndex[colorName] = nativeColor;
return nativeColor;
}
return colorName;
};
const palette = getClassicPalette();
return {
hues,
palette,
getColorByName,
};
}
function getDarkHues(): ThemeVizHue[] {
return [
{
name: 'red',
shades: [
{ color: '#FFA6B0', name: 'super-light-red' },
{ color: '#FF7383', name: 'light-red' },
{ color: '#F2495C', name: 'red', primary: true },
{ color: '#E02F44', name: 'semi-dark-red' },
{ color: '#C4162A', name: 'dark-red' },
],
},
{
name: 'orange',
shades: [
{ color: '#FFCB7D', name: 'super-light-orange', aliases: [] },
{ color: '#FFB357', name: 'light-orange', aliases: [] },
{ color: '#FF9830', name: 'orange', aliases: [], primary: true },
{ color: '#FF780A', name: 'semi-dark-orange', aliases: [] },
{ color: '#FA6400', name: 'dark-orange', aliases: [] },
],
},
{
name: 'yellow',
shades: [
{ color: '#FFF899', name: 'super-light-yellow', aliases: [] },
{ color: '#FFEE52', name: 'light-yellow', aliases: [] },
{ color: '#FADE2A', name: 'yellow', aliases: [], primary: true },
{ color: '#F2CC0C', name: 'semi-dark-yellow', aliases: [] },
{ color: '#E0B400', name: 'dark-yellow', aliases: [] },
],
},
{
name: 'green',
shades: [
{ color: '#C8F2C2', name: 'super-light-green', aliases: [] },
{ color: '#96D98D', name: 'light-green', aliases: [] },
{ color: '#73BF69', name: 'green', aliases: [], primary: true },
{ color: '#56A64B', name: 'semi-dark-green', aliases: [] },
{ color: '#37872D', name: 'dark-green', aliases: [] },
],
},
{
name: 'blue',
shades: [
{ color: '#C0D8FF', name: 'super-light-blue', aliases: [] },
{ color: '#8AB8FF', name: 'light-blue', aliases: [] },
{ color: '#5794F2', name: 'blue', aliases: [], primary: true },
{ color: '#3274D9', name: 'semi-dark-blue', aliases: [] },
{ color: '#1F60C4', name: 'dark-blue', aliases: [] },
],
},
{
name: 'purple',
shades: [
{ color: '#DEB6F2', name: 'super-light-purple', aliases: [] },
{ color: '#CA95E5', name: 'light-purple', aliases: [] },
{ color: '#B877D9', name: 'purple', aliases: [], primary: true },
{ color: '#A352CC', name: 'semi-dark-purple', aliases: [] },
{ color: '#8F3BB8', name: 'dark-purple', aliases: [] },
],
},
];
}
function getLightHues(): ThemeVizHue[] {
return [
{
name: 'red',
shades: [
{ color: '#FF7383', name: 'super-light-red' },
{ color: '#F2495C', name: 'light-red' },
{ color: '#E02F44', name: 'red', primary: true },
{ color: '#C4162A', name: 'semi-dark-red' },
{ color: '#AD0317', name: 'dark-red' },
],
},
{
name: 'orange',
shades: [
{ color: '#FFB357', name: 'super-light-orange', aliases: [] },
{ color: '#FF9830', name: 'light-orange', aliases: [] },
{ color: '#FF780A', name: 'orange', aliases: [], primary: true },
{ color: '#FA6400', name: 'semi-dark-orange', aliases: [] },
{ color: '#E55400', name: 'dark-orange', aliases: [] },
],
},
{
name: 'yellow',
shades: [
{ color: '#FFEE52', name: 'super-light-yellow', aliases: [] },
{ color: '#FADE2A', name: 'light-yellow', aliases: [] },
{ color: '#F2CC0C', name: 'yellow', aliases: [], primary: true },
{ color: '#E0B400', name: 'semi-dark-yellow', aliases: [] },
{ color: '#CC9D00', name: 'dark-yellow', aliases: [] },
],
},
{
name: 'green',
shades: [
{ color: '#96D98D', name: 'super-light-green', aliases: [] },
{ color: '#73BF69', name: 'light-green', aliases: [] },
{ color: '#56A64B', name: 'green', aliases: [], primary: true },
{ color: '#37872D', name: 'semi-dark-green', aliases: [] },
{ color: '#19730E', name: 'dark-green', aliases: [] },
],
},
{
name: 'blue',
shades: [
{ color: '#8AB8FF', name: 'super-light-blue', aliases: [] },
{ color: '#5794F2', name: 'light-blue', aliases: [] },
{ color: '#3274D9', name: 'blue', aliases: [], primary: true },
{ color: '#1F60C4', name: 'semi-dark-blue', aliases: [] },
{ color: '#1250B0', name: 'dark-blue', aliases: [] },
],
},
{
name: 'purple',
shades: [
{ color: '#CA95E5', name: 'super-light-purple', aliases: [] },
{ color: '#B877D9', name: 'light-purple', aliases: [] },
{ color: '#A352CC', name: 'purple', aliases: [], primary: true },
{ color: '#8F3BB8', name: 'semi-dark-purple', aliases: [] },
{ color: '#7C2EA3', name: 'dark-purple', aliases: [] },
],
},
];
}
function getClassicPalette() {
// Todo replace these with named colors (as many as possible)
return [
'green', // '#7EB26D', // 0: pale green
'semi-dark-yellow', // '#EAB839', // 1: mustard
'light-blue', // #6ED0E0', // 2: light blue
'semi-dark-orange', // '#EF843C', // 3: orange
'red', // '#E24D42', // 4: red
'blue', // #1F78C1', // 5: ocean
'purple', // '#BA43A9', // 6: purple
'#705DA0', // 7: violet
'dark-green', // '#508642', // 8: dark green
'yellow', //'#CCA300', // 9: dark sand
'#447EBC',
'#C15C17',
'#890F02',
'#0A437C',
'#6D1F62',
'#584477',
'#B7DBAB',
'#F4D598',
'#70DBED',
'#F9BA8F',
'#F29191',
'#82B5D8',
'#E5A8E2',
'#AEA2E0',
'#629E51',
'#E5AC0E',
'#64B0C8',
'#E0752D',
'#BF1B00',
'#0A50A1',
'#962D82',
'#614D93',
'#9AC48A',
'#F2C96D',
'#65C5DB',
'#F9934E',
'#EA6460',
'#5195CE',
'#D683CE',
'#806EB7',
'#3F6833',
'#967302',
'#2F575E',
'#99440A',
'#58140C',
'#052B51',
'#511749',
'#3F2B5B',
'#E0F9D7',
'#FCEACA',
'#CFFAFF',
'#F9E2D2',
'#FCE2DE',
'#BADFF4',
'#F9D9F9',
'#DEDAF7',
];
}
// Old hues
// function getDarkHues(): ThemeVizHue[] {
// return [
// {
// name: 'red',
// shades: [
// { name: 'red1', color: '#FFC2D4', aliases: ['super-light-red'] },
// { name: 'red2', color: '#FFA8C2', aliases: ['light-red'] },
// { name: 'red3', color: '#FF85A9', aliases: ['red'], primary: true },
// { name: 'red4', color: '#FF5286', aliases: ['semi-dark-red'] },
// { name: 'red5', color: '#E0226E', aliases: ['dark-red'] },
// ],
// },
// {
// name: 'orange',
// shades: [
// { name: 'orange1', color: '#FFC0AD', aliases: ['super-light-orange'] },
// { name: 'orange2', color: '#FFA98F', aliases: ['light-orange'] },
// { name: 'orange3', color: '#FF825C', aliases: ['orange'], primary: true },
// { name: 'orange4', color: '#FF5F2E', aliases: ['semi-dark-orange'] },
// { name: 'orange5', color: '#E73903', aliases: ['dark-orange'] },
// ],
// },
// {
// name: 'yellow',
// shades: [
// { name: 'yellow1', color: '#FFE68F', aliases: ['super-light-yellow'] },
// { name: 'yellow2', color: '#FAD34A', aliases: ['light-yellow'] },
// { name: 'yellow3', color: '#ECBB09', aliases: ['yellow'], primary: true },
// { name: 'yellow4', color: '#CFA302', aliases: ['semi-dark-yellow'] },
// { name: 'yellow5', color: '#AD8800', aliases: ['dark-yellow'] },
// ],
// },
// {
// name: 'green',
// shades: [
// { name: 'green1', color: '#93ECCB', aliases: ['super-light-green'] },
// { name: 'green2', color: '#65DCB1', aliases: ['light-green'] },
// { name: 'green3', color: '#2DC88F', aliases: ['green'], primary: true },
// { name: 'green4', color: '#25A777', aliases: ['semi-dark-green'] },
// { name: 'green5', color: '#1B855E', aliases: ['dark-green'] },
// ],
// },
// {
// name: 'teal',
// shades: [
// { name: 'teal1', color: '#73E7F7' },
// { name: 'teal2', color: '#2BD6EE' },
// { name: 'teal3', color: '#11BDD4', primary: true },
// { name: 'teal4', color: '#0EA0B4' },
// { name: 'teal5', color: '#077D8D' },
// ],
// },
// {
// name: 'blue',
// shades: [
// { name: 'blue1', color: '#C2D7FF', aliases: ['super-light-blue'] },
// { name: 'blue2', color: '#A3C2FF', aliases: ['light-blue'] },
// { name: 'blue3', color: '#83ACFC', aliases: ['blue'], primary: true },
// { name: 'blue4', color: '#5D8FEF', aliases: ['semi-dark-blue'] },
// { name: 'blue5', color: '#3871DC', aliases: ['dark-blue'] },
// ],
// },
// {
// name: 'violet',
// shades: [
// { name: 'violet1', color: '#DACCFF' },
// { name: 'violet2', color: '#C7B2FF' },
// { name: 'violet3', color: '#B094FF', primary: true },
// { name: 'violet4', color: '#9271EF' },
// { name: 'violet5', color: '#7E63CA' },
// ],
// },
// {
// name: 'purple',
// shades: [
// { name: 'purple1', color: '#FFBDFF', aliases: ['super-light-purple'] },
// { name: 'purple2', color: '#F5A3F5', aliases: ['light-purple'] },
// { name: 'purple3', color: '#E48BE4', aliases: ['purple'], primary: true },
// { name: 'purple4', color: '#CA68CA', aliases: ['semi-dark-purple'] },
// { name: 'purple5', color: '#B545B5', aliases: ['dark-purple'] },
// ],
// },
// ];
// }
const nativeColorNames: Record<string, string> = {
aliceblue: '#f0f8ff',
antiquewhite: '#faebd7',
aqua: '#00ffff',
aquamarine: '#7fffd4',
azure: '#f0ffff',
beige: '#f5f5dc',
bisque: '#ffe4c4',
black: '#000000',
blanchedalmond: '#ffebcd',
blue: '#0000ff',
blueviolet: '#8a2be2',
brown: '#a52a2a',
burlywood: '#deb887',
cadetblue: '#5f9ea0',
chartreuse: '#7fff00',
chocolate: '#d2691e',
coral: '#ff7f50',
cornflowerblue: '#6495ed',
cornsilk: '#fff8dc',
crimson: '#dc143c',
cyan: '#00ffff',
darkblue: '#00008b',
darkcyan: '#008b8b',
darkgoldenrod: '#b8860b',
darkgray: '#a9a9a9',
darkgreen: '#006400',
darkkhaki: '#bdb76b',
darkmagenta: '#8b008b',
darkolivegreen: '#556b2f',
darkorange: '#ff8c00',
darkorchid: '#9932cc',
darkred: '#8b0000',
darksalmon: '#e9967a',
darkseagreen: '#8fbc8f',
darkslateblue: '#483d8b',
darkslategray: '#2f4f4f',
darkturquoise: '#00ced1',
darkviolet: '#9400d3',
deeppink: '#ff1493',
deepskyblue: '#00bfff',
dimgray: '#696969',
dodgerblue: '#1e90ff',
firebrick: '#b22222',
floralwhite: '#fffaf0',
forestgreen: '#228b22',
fuchsia: '#ff00ff',
gainsboro: '#dcdcdc',
ghostwhite: '#f8f8ff',
gold: '#ffd700',
goldenrod: '#daa520',
gray: '#808080',
green: '#008000',
greenyellow: '#adff2f',
honeydew: '#f0fff0',
hotpink: '#ff69b4',
'indianred ': '#cd5c5c',
indigo: '#4b0082',
ivory: '#fffff0',
khaki: '#f0e68c',
lavender: '#e6e6fa',
lavenderblush: '#fff0f5',
lawngreen: '#7cfc00',
lemonchiffon: '#fffacd',
lightblue: '#add8e6',
lightcoral: '#f08080',
lightcyan: '#e0ffff',
lightgoldenrodyellow: '#fafad2',
lightgrey: '#d3d3d3',
lightgreen: '#90ee90',
lightpink: '#ffb6c1',
lightsalmon: '#ffa07a',
lightseagreen: '#20b2aa',
lightskyblue: '#87cefa',
lightslategray: '#778899',
lightsteelblue: '#b0c4de',
lightyellow: '#ffffe0',
lime: '#00ff00',
limegreen: '#32cd32',
linen: '#faf0e6',
magenta: '#ff00ff',
maroon: '#800000',
mediumaquamarine: '#66cdaa',
mediumblue: '#0000cd',
mediumorchid: '#ba55d3',
mediumpurple: '#9370d8',
mediumseagreen: '#3cb371',
mediumslateblue: '#7b68ee',
mediumspringgreen: '#00fa9a',
mediumturquoise: '#48d1cc',
mediumvioletred: '#c71585',
midnightblue: '#191970',
mintcream: '#f5fffa',
mistyrose: '#ffe4e1',
moccasin: '#ffe4b5',
navajowhite: '#ffdead',
navy: '#000080',
oldlace: '#fdf5e6',
olive: '#808000',
olivedrab: '#6b8e23',
orange: '#ffa500',
orangered: '#ff4500',
orchid: '#da70d6',
palegoldenrod: '#eee8aa',
palegreen: '#98fb98',
paleturquoise: '#afeeee',
palevioletred: '#d87093',
papayawhip: '#ffefd5',
peachpuff: '#ffdab9',
peru: '#cd853f',
pink: '#ffc0cb',
plum: '#dda0dd',
powderblue: '#b0e0e6',
purple: '#800080',
rebeccapurple: '#663399',
red: '#ff0000',
rosybrown: '#bc8f8f',
royalblue: '#4169e1',
saddlebrown: '#8b4513',
salmon: '#fa8072',
sandybrown: '#f4a460',
seagreen: '#2e8b57',
seashell: '#fff5ee',
sienna: '#a0522d',
silver: '#c0c0c0',
skyblue: '#87ceeb',
slateblue: '#6a5acd',
slategray: '#708090',
snow: '#fffafa',
springgreen: '#00ff7f',
steelblue: '#4682b4',
tan: '#d2b48c',
teal: '#008080',
thistle: '#d8bfd8',
tomato: '#ff6347',
turquoise: '#40e0d0',
violet: '#ee82ee',
wheat: '#f5deb3',
white: '#ffffff',
whitesmoke: '#f5f5f5',
yellow: '#ffff00',
yellowgreen: '#9acd32',
};

View File

@@ -8,7 +8,7 @@ export { ThemeTypography, ThemeTypographyVariant } from './createTypography';
export { ThemeTransitions } from './createTransitions';
export { ThemeSpacing } from './createSpacing';
export { ThemeZIndices } from './zIndex';
export { palette } from './palette';
export { ThemeVisualizationColors, ThemeVizColor, ThemeVizHue } from './createVisualizationColors';
/** Exporting the module like this to be able to generate docs properly. */
import * as colorManipulator from './colorManipulator';

View File

@@ -8,6 +8,7 @@ import { ThemeSpacing } from './createSpacing';
import { ThemeTransitions } from './createTransitions';
import { ThemeTypography } from './createTypography';
import { ThemeZIndices } from './zIndex';
import { ThemeVisualizationColors } from './createVisualizationColors';
/**
* @beta
@@ -25,6 +26,7 @@ export interface GrafanaTheme2 {
typography: ThemeTypography;
zIndex: ThemeZIndices;
shadows: ThemeShadows;
visualization: ThemeVisualizationColors;
transitions: ThemeTransitions;
v1: GrafanaTheme;
}

View File

@@ -1,3 +1,5 @@
import { ThemeVisualizationColors } from '../themes';
export enum GrafanaThemeType {
Light = 'light',
Dark = 'dark',
@@ -238,4 +240,5 @@ export interface GrafanaTheme extends GrafanaThemeCommons {
shadows: {
listItem: string;
};
visualization: ThemeVisualizationColors;
}

View File

@@ -1,33 +1,16 @@
import { getColorFromHexRgbOrName, getColorDefinitionByName } from './namedColorsPalette';
import { GrafanaThemeType } from '../types/theme';
import { getColorForTheme } from './namedColorsPalette';
import { createTheme } from '../themes';
describe('colors', () => {
const SemiDarkBlue = getColorDefinitionByName('semi-dark-blue');
const theme = createTheme();
describe('getColorFromHexRgbOrName', () => {
it('returns black for unknown color', () => {
expect(getColorFromHexRgbOrName('aruba-sunshine')).toBe('#000000');
expect(getColorForTheme('aruba-sunshine', theme.v1)).toBe('aruba-sunshine');
});
it('returns dark hex variant for known color if theme not specified', () => {
expect(getColorFromHexRgbOrName(SemiDarkBlue.name)).toBe(SemiDarkBlue.variants.dark);
});
it("returns correct variant's hex for known color if theme specified", () => {
expect(getColorFromHexRgbOrName(SemiDarkBlue.name, GrafanaThemeType.Light)).toBe(SemiDarkBlue.variants.light);
});
it('returns color if specified as hex or rgb/a', () => {
expect(getColorFromHexRgbOrName('ff0000')).toBe('#ff0000');
expect(getColorFromHexRgbOrName('#ff0000')).toBe('#ff0000');
expect(getColorFromHexRgbOrName('#FF0000')).toBe('#FF0000');
expect(getColorFromHexRgbOrName('#CCC')).toBe('#CCC');
expect(getColorFromHexRgbOrName('rgb(0,0,0)')).toBe('rgb(0,0,0)');
expect(getColorFromHexRgbOrName('rgba(0,0,0,1)')).toBe('rgba(0,0,0,1)');
});
it('returns hex for named color that is not a part of named colors palette', () => {
expect(getColorFromHexRgbOrName('lime')).toBe('#00ff00');
expect(getColorForTheme('semi-dark-blue', theme.v1)).toBe('#3274D9');
});
});
});

View File

@@ -1,216 +1,19 @@
import { flatten } from 'lodash';
import tinycolor from 'tinycolor2';
import { GrafanaTheme, GrafanaThemeType } from '../types/theme';
type Hue = 'green' | 'yellow' | 'red' | 'blue' | 'orange' | 'purple';
export type Color =
| 'green'
| 'dark-green'
| 'semi-dark-green'
| 'light-green'
| 'super-light-green'
| 'yellow'
| 'dark-yellow'
| 'semi-dark-yellow'
| 'light-yellow'
| 'super-light-yellow'
| 'red'
| 'dark-red'
| 'semi-dark-red'
| 'light-red'
| 'super-light-red'
| 'blue'
| 'dark-blue'
| 'semi-dark-blue'
| 'light-blue'
| 'super-light-blue'
| 'orange'
| 'dark-orange'
| 'semi-dark-orange'
| 'light-orange'
| 'super-light-orange'
| 'purple'
| 'dark-purple'
| 'semi-dark-purple'
| 'light-purple'
| 'super-light-purple'
| 'panel-bg'
| 'transparent'
| 'text';
type ThemeVariants = {
dark: string;
light: string;
};
export type ColorDefinition = {
hue: Hue;
isPrimary?: boolean;
name: Color;
variants: ThemeVariants;
};
let colorsPaletteInstance: Map<Hue, ColorDefinition[]>;
let colorsMap: Record<Color, string> | undefined;
let colorsMapTheme: GrafanaTheme | undefined;
const buildColorDefinition = (
hue: Hue,
name: Color,
[light, dark]: string[],
isPrimary?: boolean
): ColorDefinition => ({
hue,
name,
variants: {
light,
dark,
},
isPrimary: !!isPrimary,
});
export function getColorDefinitionByName(name: Color): ColorDefinition {
return flatten(Array.from(getNamedColorPalette().values())).filter((definition) => definition.name === name)[0];
}
export function buildColorsMapForTheme(theme: GrafanaTheme): Record<Color, string> {
theme = theme ?? GrafanaThemeType.Dark;
colorsMap = {} as Record<Color, string>;
for (const def of getNamedColorPalette().values()) {
for (const c of def) {
colorsMap[c.name] = c.variants[theme.type];
}
}
colorsMap['panel-bg'] = theme.colors.panelBg;
colorsMap['transparent'] = 'rgba(0,0,0,0)';
colorsMap['text'] = theme.colors.text;
return colorsMap;
}
/**
* @deprecated use theme.vizColors.getByName
*/
export function getColorForTheme(color: string, theme: GrafanaTheme): string {
if (!color) {
return 'gray';
}
// check if we need to rebuild cache
if (!colorsMap || colorsMapTheme !== theme) {
colorsMap = buildColorsMapForTheme(theme);
colorsMapTheme = theme;
}
let realColor = colorsMap[color as Color];
if (realColor) {
return realColor;
}
if (color[0] === '#') {
return (colorsMap[color as Color] = color);
}
if (color.indexOf('rgb') > -1) {
return (colorsMap[color as Color] = color);
}
return (colorsMap[color as Color] = tinycolor(color).toHexString());
return theme.visualization.getColorByName(color);
}
/**
* @deprecated use getColorForTheme
*/
export function getColorFromHexRgbOrName(color: string, type?: GrafanaThemeType): string {
const themeType = type ?? GrafanaThemeType.Dark;
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);
return 'gray';
}
const buildNamedColorsPalette = () => {
const palette = new Map<Hue, ColorDefinition[]>();
const BasicGreen = buildColorDefinition('green', 'green', ['#56A64B', '#73BF69'], true);
const DarkGreen = buildColorDefinition('green', 'dark-green', ['#19730E', '#37872D']);
const SemiDarkGreen = buildColorDefinition('green', 'semi-dark-green', ['#37872D', '#56A64B']);
const LightGreen = buildColorDefinition('green', 'light-green', ['#73BF69', '#96D98D']);
const SuperLightGreen = buildColorDefinition('green', 'super-light-green', ['#96D98D', '#C8F2C2']);
const BasicYellow = buildColorDefinition('yellow', 'yellow', ['#F2CC0C', '#FADE2A'], true);
const DarkYellow = buildColorDefinition('yellow', 'dark-yellow', ['#CC9D00', '#E0B400']);
const SemiDarkYellow = buildColorDefinition('yellow', 'semi-dark-yellow', ['#E0B400', '#F2CC0C']);
const LightYellow = buildColorDefinition('yellow', 'light-yellow', ['#FADE2A', '#FFEE52']);
const SuperLightYellow = buildColorDefinition('yellow', 'super-light-yellow', ['#FFEE52', '#FFF899']);
const BasicRed = buildColorDefinition('red', 'red', ['#E02F44', '#F2495C'], true);
const DarkRed = buildColorDefinition('red', 'dark-red', ['#AD0317', '#C4162A']);
const SemiDarkRed = buildColorDefinition('red', 'semi-dark-red', ['#C4162A', '#E02F44']);
const LightRed = buildColorDefinition('red', 'light-red', ['#F2495C', '#FF7383']);
const SuperLightRed = buildColorDefinition('red', 'super-light-red', ['#FF7383', '#FFA6B0']);
const BasicBlue = buildColorDefinition('blue', 'blue', ['#3274D9', '#5794F2'], true);
const DarkBlue = buildColorDefinition('blue', 'dark-blue', ['#1250B0', '#1F60C4']);
const SemiDarkBlue = buildColorDefinition('blue', 'semi-dark-blue', ['#1F60C4', '#3274D9']);
const LightBlue = buildColorDefinition('blue', 'light-blue', ['#5794F2', '#8AB8FF']);
const SuperLightBlue = buildColorDefinition('blue', 'super-light-blue', ['#8AB8FF', '#C0D8FF']);
const BasicOrange = buildColorDefinition('orange', 'orange', ['#FF780A', '#FF9830'], true);
const DarkOrange = buildColorDefinition('orange', 'dark-orange', ['#E55400', '#FA6400']);
const SemiDarkOrange = buildColorDefinition('orange', 'semi-dark-orange', ['#FA6400', '#FF780A']);
const LightOrange = buildColorDefinition('orange', 'light-orange', ['#FF9830', '#FFB357']);
const SuperLightOrange = buildColorDefinition('orange', 'super-light-orange', ['#FFB357', '#FFCB7D']);
const BasicPurple = buildColorDefinition('purple', 'purple', ['#A352CC', '#B877D9'], true);
const DarkPurple = buildColorDefinition('purple', 'dark-purple', ['#7C2EA3', '#8F3BB8']);
const SemiDarkPurple = buildColorDefinition('purple', 'semi-dark-purple', ['#8F3BB8', '#A352CC']);
const LightPurple = buildColorDefinition('purple', 'light-purple', ['#B877D9', '#CA95E5']);
const SuperLightPurple = buildColorDefinition('purple', 'super-light-purple', ['#CA95E5', '#DEB6F2']);
const greens = [BasicGreen, DarkGreen, SemiDarkGreen, LightGreen, SuperLightGreen];
const yellows = [BasicYellow, DarkYellow, SemiDarkYellow, LightYellow, SuperLightYellow];
const reds = [BasicRed, DarkRed, SemiDarkRed, LightRed, SuperLightRed];
const blues = [BasicBlue, DarkBlue, SemiDarkBlue, LightBlue, SuperLightBlue];
const oranges = [BasicOrange, DarkOrange, SemiDarkOrange, LightOrange, SuperLightOrange];
const purples = [BasicPurple, DarkPurple, SemiDarkPurple, LightPurple, SuperLightPurple];
palette.set('green', greens);
palette.set('yellow', yellows);
palette.set('red', reds);
palette.set('blue', blues);
palette.set('orange', oranges);
palette.set('purple', purples);
return palette;
};
export const getNamedColorPalette = () => {
if (colorsPaletteInstance) {
return colorsPaletteInstance;
}
colorsPaletteInstance = buildNamedColorsPalette();
return colorsPaletteInstance;
};
export const classicColors = [
'#7EB26D', // 0: pale green
'#EAB839', // 1: mustard

View File

@@ -12,7 +12,6 @@ import {
FieldConfig,
FieldColorModeId,
getFieldColorMode,
getColorForTheme,
FALLBACK_COLOR,
TextDisplayOptions,
VizOrientation,
@@ -536,7 +535,7 @@ export function getBarGradient(props: Props, maxSize: number): string {
for (let i = 0; i < thresholds.steps.length; i++) {
const threshold = thresholds.steps[i];
const color = getColorForTheme(threshold.color, props.theme.v1);
const color = props.theme.visualization.getColorByName(threshold.color);
const valuePercent =
thresholds.mode === ThresholdsMode.Percentage
? threshold.value / 100
@@ -560,8 +559,9 @@ export function getBarGradient(props: Props, maxSize: number): string {
return gradient + ')';
}
if (mode.isContinuous && mode.colors) {
const scheme = mode.colors.map((item) => getColorForTheme(item, theme.v1));
if (mode.isContinuous && mode.getColors) {
const scheme = mode.getColors(theme);
for (let i = 0; i < scheme.length; i++) {
const color = scheme[i];

View File

@@ -12,6 +12,7 @@ function getProps(propOverrides?: Partial<Props>): Props {
value: {
text: '25',
numeric: 25,
color: 'red',
},
theme: createTheme(),
};

View File

@@ -3,7 +3,7 @@ import React, { CSSProperties } from 'react';
import tinycolor from 'tinycolor2';
// Utils
import { formattedValueToString, DisplayValue, getColorForTheme, FieldConfig } from '@grafana/data';
import { formattedValueToString, DisplayValue, FieldConfig } from '@grafana/data';
import { calculateFontSize } from '../../utils/measureText';
// Types
@@ -30,9 +30,9 @@ export abstract class BigValueLayout {
textValues: BigValueTextValues;
constructor(private props: Props) {
const { width, height, value, theme, text } = props;
const { width, height, value, text } = props;
this.valueColor = getColorForTheme(value.color || 'green', theme.v1);
this.valueColor = value.color ?? 'gray';
this.panelPadding = height > 100 ? 12 : 8;
this.textValues = getTextValues(props);
this.justifyCenter = shouldJustifyCenter(props.justifyMode, this.textValues.title);

View File

@@ -5,7 +5,7 @@ exports[`BigValue Render with basic options should render 1`] = `
style={
Object {
"alignItems": "center",
"background": "linear-gradient(120deg, rgb(66, 154, 67), rgb(111, 183, 87))",
"background": "linear-gradient(120deg, rgb(179, 24, 0), rgb(230, 0, 31))",
"borderRadius": "3px",
"display": "flex",
"flexDirection": "row",
@@ -41,6 +41,7 @@ exports[`BigValue Render with basic options should render 1`] = `
}
value={
Object {
"color": "red",
"numeric": 25,
"text": "25",
"titleToAlignTo": undefined,

View File

@@ -2,7 +2,7 @@ import React, { Component, createRef } from 'react';
import { PopoverController } from '../Tooltip/PopoverController';
import { Popover } from '../Tooltip/Popover';
import { ColorPickerPopover, ColorPickerProps, ColorPickerChangeHandler } from './ColorPickerPopover';
import { getColorForTheme, GrafanaTheme2 } from '@grafana/data';
import { GrafanaTheme2 } from '@grafana/data';
import { SeriesColorPickerPopover } from './SeriesColorPickerPopover';
import { css } from '@emotion/css';
@@ -75,7 +75,7 @@ export const colorPickerFactory = <T extends ColorPickerProps>(
ref={this.pickerTriggerRef}
onClick={showPopper}
onMouseLeave={hidePopper}
color={getColorForTheme(this.props.color || '#000000', theme.v1)}
color={theme.visualization.getColorByName(this.props.color || '#000000')}
/>
)}
</>

View File

@@ -1,13 +1,12 @@
import React from 'react';
import { mount, ReactWrapper } from 'enzyme';
import { ColorPickerPopover } from './ColorPickerPopover';
import { flatten } from 'lodash';
import { getNamedColorPalette, getColorFromHexRgbOrName } from '@grafana/data';
import { ColorSwatch } from './ColorSwatch';
const allColors = flatten(Array.from(getNamedColorPalette().values()));
import { createTheme, getColorForTheme } from '@grafana/data';
describe('ColorPickerPopover', () => {
const theme = createTheme();
describe('rendering', () => {
it('should render provided color as selected if color provided by name', () => {
const wrapper = mount(<ColorPickerPopover color={'green'} onChange={() => {}} />);
@@ -15,18 +14,7 @@ describe('ColorPickerPopover', () => {
const notSelectedSwatches = wrapper.find(ColorSwatch).filterWhere((node) => node.prop('isSelected') === false);
expect(selectedSwatch.length).toBe(1);
expect(notSelectedSwatches.length).toBe(allColors.length + 1);
expect(selectedSwatch.prop('isSelected')).toBe(true);
});
it('should render provided color as selected if color provided by hex', () => {
const wrapper = mount(<ColorPickerPopover color={'green'} onChange={() => {}} />);
const selectedSwatch = wrapper.find(ColorSwatch).findWhere((node) => node.key() === 'green');
const notSelectedSwatches = wrapper.find(ColorSwatch).filterWhere((node) => node.prop('isSelected') === false);
expect(selectedSwatch.length).toBe(1);
expect(notSelectedSwatches.length).toBe(allColors.length + 1);
expect(notSelectedSwatches.length).toBe(31);
expect(selectedSwatch.prop('isSelected')).toBe(true);
});
});
@@ -47,7 +35,7 @@ describe('ColorPickerPopover', () => {
basicBlueSwatch.simulate('click');
expect(onChangeSpy).toBeCalledTimes(1);
expect(onChangeSpy).toBeCalledWith(getColorFromHexRgbOrName('green'));
expect(onChangeSpy).toBeCalledWith(getColorForTheme('green', theme.v1));
});
it('should pass color name to onChange prop when named colors enabled', () => {

View File

@@ -5,7 +5,7 @@ import SpectrumPalette from './SpectrumPalette';
import { Themeable2 } from '../../types/theme';
import { warnAboutColorPickerPropsDeprecation } from './warnAboutColorPickerPropsDeprecation';
import { css } from '@emotion/css';
import { GrafanaTheme2, getColorForTheme } from '@grafana/data';
import { GrafanaTheme2 } from '@grafana/data';
import { stylesFactory, withTheme2 } from '../../themes';
export type ColorPickerChangeHandler = (color: string) => void;
@@ -59,7 +59,7 @@ class UnThemedColorPickerPopover<T extends CustomPickersDescriptor> extends Reac
if (enableNamedColors) {
return changeHandler(color);
}
changeHandler(getColorForTheme(color, theme.v1));
changeHandler(theme.visualization.getColorByName(color));
};
onTabChange = (tab: PickerType | keyof T) => {

View File

@@ -1,38 +1,34 @@
import React, { FunctionComponent } from 'react';
import { ColorDefinition } from '@grafana/data';
import { ThemeVizHue } from '@grafana/data';
import { Color } from 'csstype';
import { upperFirst, find } from 'lodash';
import { ColorSwatch, ColorSwatchVariant } from './ColorSwatch';
import { useTheme2 } from '../../themes/ThemeContext';
type ColorChangeHandler = (color: ColorDefinition) => void;
import { upperFirst } from 'lodash';
interface NamedColorsGroupProps {
colors: ColorDefinition[];
hue: ThemeVizHue;
selectedColor?: Color;
onColorSelect: ColorChangeHandler;
onColorSelect: (colorName: string) => void;
key?: string;
}
const NamedColorsGroup: FunctionComponent<NamedColorsGroupProps> = ({
colors,
hue,
selectedColor,
onColorSelect,
...otherProps
}) => {
const theme = useTheme2();
const primaryColor = find(colors, (color) => !!color.isPrimary);
const primaryShade = hue.shades.find((shade) => shade.primary)!;
return (
<div {...otherProps} style={{ display: 'flex', flexDirection: 'column' }}>
{primaryColor && (
{primaryShade && (
<ColorSwatch
key={primaryColor.name}
isSelected={primaryColor.name === selectedColor}
key={primaryShade.name}
isSelected={primaryShade.name === selectedColor}
variant={ColorSwatchVariant.Large}
color={primaryColor.variants[theme.colors.mode]}
label={upperFirst(primaryColor.hue)}
onClick={() => onColorSelect(primaryColor)}
color={primaryShade.color}
label={upperFirst(hue.name)}
onClick={() => onColorSelect(primaryShade.name)}
/>
)}
<div
@@ -41,15 +37,15 @@ const NamedColorsGroup: FunctionComponent<NamedColorsGroupProps> = ({
marginTop: '8px',
}}
>
{colors.map(
(color) =>
!color.isPrimary && (
<div key={color.name} style={{ marginRight: '4px' }}>
{hue.shades.map(
(shade) =>
!shade.primary && (
<div key={shade.name} style={{ marginRight: '4px' }}>
<ColorSwatch
key={color.name}
isSelected={color.name === selectedColor}
color={color.variants[theme.colors.mode]}
onClick={() => onColorSelect(color)}
key={shade.name}
isSelected={shade.name === selectedColor}
color={shade.color}
onClick={() => onColorSelect(shade.name)}
/>
</div>
)

View File

@@ -1,12 +1,13 @@
import React from 'react';
import { mount, ReactWrapper } from 'enzyme';
import { NamedColorsPalette } from './NamedColorsPalette';
import { createTheme, getColorDefinitionByName } from '@grafana/data';
import { createTheme } from '@grafana/data';
import { ColorSwatch } from './ColorSwatch';
import { ThemeContext } from '../../themes';
describe('NamedColorsPalette', () => {
const BasicGreen = getColorDefinitionByName('green');
const theme = createTheme();
const greenHue = theme.visualization.hues.find((x) => x.name === 'green')!;
const selectedShade = greenHue.shades[2];
describe('theme support for named colors', () => {
let wrapper: ReactWrapper, selectedSwatch;
@@ -16,21 +17,9 @@ describe('NamedColorsPalette', () => {
});
it('should render provided color variant specific for theme', () => {
wrapper = mount(<NamedColorsPalette color={BasicGreen.name} onChange={() => {}} />);
selectedSwatch = wrapper.find(ColorSwatch).findWhere((node) => node.key() === BasicGreen.name);
expect(selectedSwatch.prop('color')).toBe(BasicGreen.variants.dark);
wrapper.unmount();
const withLightTheme = (
<ThemeContext.Provider value={createTheme({ colors: { mode: 'light' } })}>
<NamedColorsPalette color={BasicGreen.name} onChange={() => {}} />
</ThemeContext.Provider>
);
wrapper = mount(withLightTheme);
selectedSwatch = wrapper.find(ColorSwatch).findWhere((node) => node.key() === BasicGreen.name);
expect(selectedSwatch.prop('color')).toBe(BasicGreen.variants.light);
wrapper = mount(<NamedColorsPalette color={selectedShade.name} onChange={() => {}} />);
selectedSwatch = wrapper.find(ColorSwatch).findWhere((node) => node.key() === selectedShade.name);
expect(selectedSwatch.prop('color')).toBe(selectedShade.color);
});
});
});

View File

@@ -1,5 +1,4 @@
import React from 'react';
import { getNamedColorPalette } from '@grafana/data';
import NamedColorsGroup from './NamedColorsGroup';
import { VerticalGroup } from '../Layout/Layout';
import { ColorSwatch } from './ColorSwatch';
@@ -14,18 +13,9 @@ export const NamedColorsPalette = ({ color, onChange }: NamedColorsPaletteProps)
const theme = useTheme2();
const swatches: JSX.Element[] = [];
getNamedColorPalette().forEach((colors, hue) => {
swatches.push(
<NamedColorsGroup
key={hue}
selectedColor={color}
colors={colors}
onColorSelect={(color) => {
onChange(color.name);
}}
/>
);
});
for (const hue of theme.visualization.hues) {
swatches.push(<NamedColorsGroup key={hue.name} selectedColor={color} hue={hue} onColorSelect={onChange} />);
}
return (
<VerticalGroup spacing="md">
@@ -39,6 +29,7 @@ export const NamedColorsPalette = ({ color, onChange }: NamedColorsPaletteProps)
}}
>
{swatches}
<div />
<ColorSwatch
isSelected={color === 'transparent'}
color={'rgba(0,0,0,0)'}

View File

@@ -6,15 +6,14 @@ import {
FieldColor,
fieldColorModeRegistry,
FieldColorMode,
GrafanaTheme,
getColorForTheme,
GrafanaTheme2,
FieldColorConfigSettings,
FieldColorSeriesByMode,
getFieldColorMode,
} from '@grafana/data';
import { Select } from '../Select/Select';
import { ColorValueEditor } from './color';
import { useStyles, useTheme } from '../../themes/ThemeContext';
import { useStyles2, useTheme2 } from '../../themes/ThemeContext';
import { css } from '@emotion/css';
import { Field } from '../Forms/Field';
import { RadioButtonGroup } from '../Forms/RadioButtonGroup/RadioButtonGroup';
@@ -24,8 +23,8 @@ export const FieldColorEditor: React.FC<FieldConfigEditorProps<FieldColor | unde
onChange,
item,
}) => {
const theme = useTheme();
const styles = useStyles(getStyles);
const theme = useTheme2();
const styles = useStyles2(getStyles);
const colorMode = getFieldColorMode(value?.mode);
const availableOptions = item.settings?.byValueSupport
@@ -90,7 +89,7 @@ export const FieldColorEditor: React.FC<FieldConfigEditorProps<FieldColor | unde
return (
<>
<div style={{ marginBottom: theme.spacing.formInputMargin }}>
<div style={{ marginBottom: theme.spacing(2) }}>
<Select minMenuHeight={200} options={options} value={mode} onChange={onModeChange} />
</div>
<Field label="Color series by">
@@ -105,15 +104,15 @@ export const FieldColorEditor: React.FC<FieldConfigEditorProps<FieldColor | unde
interface ModeProps {
mode: FieldColorMode;
theme: GrafanaTheme;
theme: GrafanaTheme2;
}
const FieldColorModeViz: FC<ModeProps> = ({ mode, theme }) => {
if (!mode.colors) {
if (!mode.getColors) {
return null;
}
const colors = mode.colors.map((item) => getColorForTheme(item, theme));
const colors = mode.getColors(theme).map(theme.visualization.getColorByName);
const style: CSSProperties = {
height: '8px',
width: '100%',
@@ -145,7 +144,7 @@ const FieldColorModeViz: FC<ModeProps> = ({ mode, theme }) => {
return <div style={style} />;
};
const getStyles = (theme: GrafanaTheme) => {
const getStyles = (theme: GrafanaTheme2) => {
return {
group: css`
display: flex;

View File

@@ -1,5 +1,4 @@
import React from 'react';
import { config } from '@grafana/runtime';
import { renderHook } from '@testing-library/react-hooks';
import { css } from '@emotion/css';
import { mockThemeContext, useStyles } from './ThemeContext';
@@ -36,8 +35,6 @@ describe('useStyles', () => {
it('passes in theme and returns style object', (done) => {
const Dummy: React.FC = function () {
const styles = useStyles((theme) => {
expect(theme).toEqual(config.theme);
return {
someStyle: css`
color: ${theme.palette.critical};

View File

@@ -289,6 +289,7 @@ function PieLabel({ arc, outerRadius, innerRadius, displayLabels, total, color,
y={labelY}
dy=".33em"
fontSize={labelFontSize}
fontWeight={500}
textAnchor="middle"
pointerEvents="none"
>