Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelogs/fragments/10755.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
feat:
- Update new color scheme for heatmap ([#10755](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/10755))
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
import { createLabelLayer, getDataBound, addTransform, enhanceStyle } from './heatmap_chart_utils';
import {
createLabelLayer,
getDataBound,
addTransform,
enhanceStyle,
generateSchemeList,
getColorRange,
} from './heatmap_chart_utils';
import { AggregationType, VisFieldType, ColorSchemas, ScaleType, VisColumn } from '../types';
import { DEFAULT_GREY } from '../theme/default_colors';
import { defaultHeatmapChartStyles, HeatmapLabels, HeatmapChartStyle } from './heatmap_vis_config';
Expand Down Expand Up @@ -328,3 +335,72 @@ describe('enhanceStyle', () => {
expect(markLayer).toEqual(baseMarkLayer);
});
});

describe('generateSchemeList', () => {
it('should generate default 11 colors with center color matching target', () => {
const result = generateSchemeList('#ff0000');
expect(result).toHaveLength(11);
expect(result[5]).toBe('#ff0000');
});

it('should create lighter colors on left side', () => {
const result = generateSchemeList('#808080');
expect(result[0]).toBe('#e4e4e4');
expect(result[1]).toBe('#d0d0d0');
expect(result[5]).toBe('#808080');
});

it('should create darker colors on right side', () => {
const result = generateSchemeList('#808080');
expect(result[5]).toBe('#808080');
expect(result[6]).toBe('#6c6c6c');
expect(result[7]).toBe('#585858');
});
});

describe('getColorRange', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('should return blues color scheme', () => {
const result = getColorRange(ColorSchemas.BLUES);
expect(result).toHaveLength(11);
expect(result[5]).toBe('#6092c0');
});

it('should return purples color scheme', () => {
const result = getColorRange(ColorSchemas.PURPLES);
expect(result).toHaveLength(11);
expect(result[5]).toBe('#9170b8');
});

it('should return oranges color scheme', () => {
const result = getColorRange(ColorSchemas.ORANGES);
expect(result).toHaveLength(11);
expect(result[5]).toBe('#e7664c');
});

it('should return yellows color scheme', () => {
const result = getColorRange(ColorSchemas.YELLOWS);
expect(result).toHaveLength(11);
expect(result[5]).toBe('#d6bf57');
});

it('should return greens color scheme', () => {
const result = getColorRange(ColorSchemas.GREENS);
expect(result).toHaveLength(11);
expect(result[5]).toBe('#54b399');
});

it('should return reds color scheme', () => {
const result = getColorRange(ColorSchemas.REDS);
expect(result).toHaveLength(11);
expect(result[5]).toBe('#d36086');
});

it('should return undefined for unknown color schema', () => {
const result = getColorRange('UNKNOWN' as ColorSchemas);
expect(result).toBeUndefined();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import type { Encoding } from 'vega-lite/build/src/encoding';
import { AggregationType, VisColumn } from '../types';
import { AggregationType, VisColumn, ColorSchemas } from '../types';
import { HeatmapChartStyle } from './heatmap_vis_config';

import { getColors, DEFAULT_GREY } from '../theme/default_colors';
Expand Down Expand Up @@ -117,3 +117,75 @@ export const enhanceStyle = (
markLayer.encoding.color.scale.range = [DEFAULT_GREY, ...colorRange];
}
};

export function generateSchemeList(targetHex: string, n = 11, step = 20) {
function hexToRgb(hex: string) {
hex = hex.replace('#', '');
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
return {
r: parseInt(hex.slice(0, 2), 16),
g: parseInt(hex.slice(2, 4), 16),
b: parseInt(hex.slice(4, 6), 16),
};
}

function rgbToHex({ r, g, b }: { r: number; g: number; b: number }) {
return (
'#' +
r.toString(16).padStart(2, '0') +
g.toString(16).padStart(2, '0') +
b.toString(16).padStart(2, '0')
);
}

const half = Math.floor(n / 2);
const target = hexToRgb(targetHex);
const colors = [];

// Left side (lighter)
for (let i = half; i > 0; i--) {
colors.push(
rgbToHex({
r: Math.min(255, target.r + i * step),
g: Math.min(255, target.g + i * step),
b: Math.min(255, target.b + i * step),
})
);
}

// Center
colors.push(rgbToHex(target));

// Right side (darker)
for (let i = 1; i <= half; i++) {
colors.push(
rgbToHex({
r: Math.max(0, target.r - i * step),
g: Math.max(0, target.g - i * step),
b: Math.max(0, target.b - i * step),
})
);
}
return colors;
}

export const getColorRange = (colorSchema: ColorSchemas) => {
switch (colorSchema) {
case ColorSchemas.BLUES:
return generateSchemeList(getColors().categories[0]);
case ColorSchemas.PURPLES:
return generateSchemeList(getColors().categories[1]);
case ColorSchemas.ORANGES:
return generateSchemeList(getColors().categories[2]);
case ColorSchemas.YELLOWS:
return generateSchemeList(getColors().categories[3]);
case ColorSchemas.GREENS:
return generateSchemeList(getColors().categories[5]);
case ColorSchemas.REDS:
return generateSchemeList(getColors().categories[6]);
default:
return undefined;
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ jest.mock('./heatmap_chart_utils', () => ({
enhanceStyle: jest.fn(),
addTransform: jest.fn(() => []),
createLabelLayer: jest.fn(() => null),
getColorRange: jest.fn(() => undefined),
}));

jest.mock('../utils/utils', () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { HeatmapChartStyle } from './heatmap_vis_config';
import { VisColumn, VEGASCHEMA, AxisColumnMappings } from '../types';
import { applyAxisStyling, getSwappedAxisRole, getSchemaByAxis } from '../utils/utils';
import { createLabelLayer, enhanceStyle, addTransform } from './heatmap_chart_utils';
import { createLabelLayer, enhanceStyle, addTransform, getColorRange } from './heatmap_chart_utils';

export const createHeatmapWithBin = (
transformedData: Array<Record<string, any>>,
Expand Down Expand Up @@ -103,6 +103,10 @@ export const createRegularHeatmap = (
const colorField = colorFieldColumn?.column;
const colorName = colorFieldColumn?.name;

const colorScale = getColorRange(styles.exclusive?.colorSchema)
? { range: getColorRange(styles.exclusive?.colorSchema) }
: { scheme: styles.exclusive?.colorSchema };

const markLayer: any = {
mark: {
type: 'rect',
Expand Down Expand Up @@ -131,7 +135,7 @@ export const createRegularHeatmap = (
: false,
scale: {
type: styles.exclusive?.colorScaleType,
scheme: styles.exclusive?.colorSchema,
...colorScale,
reverse: styles.exclusive?.reverseSchema,
},
legend: styles.addLegend
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export const getColors = () => {
// been implemented accordingly.
const isExperimental =
localStorage.getItem('__DEVELOPMENT__.discover.vis.theme') === 'experimental';

const euiPaletteColorBlindColors = euiPaletteColorBlind();

if (isExperimental) {
if (darkMode) {
return {
Expand Down Expand Up @@ -69,7 +72,18 @@ export const getColors = () => {
text: euiThemeVars.euiTextColor,
grid: euiThemeVars.euiColorChartLines,
backgroundShade: darkMode ? '#27252C' : '#f1f1f1ff',
categories: euiPaletteColorBlind(),
categories: [
euiPaletteColorBlindColors[1],
euiPaletteColorBlindColors[3],
euiPaletteColorBlindColors[9],
euiPaletteColorBlindColors[5],
euiPaletteColorBlindColors[1],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this duplicate color intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, in the new theme categories, we’ve defined two types of blue: purple blue and regular blue. But euiPaletteColorBlindColors only includes one blue, I duplicated it to cover both cases.

euiPaletteColorBlindColors[0],
euiPaletteColorBlindColors[2],
euiPaletteColorBlindColors[8],
euiPaletteColorBlindColors[0],
euiPaletteColorBlindColors[4],
],
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,12 @@ export enum PointShape {

export enum ColorSchemas {
BLUES = 'blues',
PURPLES = 'purples',
ORANGES = 'oranges',
YELLOWS = 'yellows',
GREENS = 'greens',
GREYS = 'greys',
REDS = 'reds',
GREYS = 'greys',
YELLOWORANGE = 'yelloworangered',
GREENBLUE = 'greenblue',
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,35 @@ export const getColorSchemas = () => [
value: ColorSchemas.GREENS,
},
{
text: i18n.translate('explore.vis.colorSchemas.greys', {
defaultMessage: 'Greys',
text: i18n.translate('explore.vis.colorSchemas.purples', {
defaultMessage: 'Purples',
}),
value: ColorSchemas.GREYS,
value: ColorSchemas.PURPLES,
},
{
text: i18n.translate('explore.vis.colorSchemas.reds', {
defaultMessage: 'Reds',
}),
value: ColorSchemas.REDS,
},
{
text: i18n.translate('explore.vis.colorSchemas.greys', {
defaultMessage: 'Greys',
}),
value: ColorSchemas.GREYS,
},
{
text: i18n.translate('explore.vis.colorSchemas.yellows', {
defaultMessage: 'Yellows',
}),
value: ColorSchemas.YELLOWS,
},
{
text: i18n.translate('explore.vis.colorSchemas.oranges', {
defaultMessage: 'Oranges',
}),
value: ColorSchemas.ORANGES,
},
{
text: i18n.translate('explore.vis.colorSchemas.greenToBlue', {
defaultMessage: 'Green to Blue',
Expand Down
Loading