diff --git a/projects/common/src/color/color-palette.test.ts b/projects/common/src/color/color-palette.test.ts index 37d713152..3d4d40db5 100644 --- a/projects/common/src/color/color-palette.test.ts +++ b/projects/common/src/color/color-palette.test.ts @@ -31,4 +31,30 @@ describe('Color palette', () => { expect(() => new ColorPalette(['black'])).toThrow(); expect(() => new ColorPalette(['white', 'black'])).not.toThrow(); }); + + test('should return color combinations correctly', () => { + const palette = new ColorPalette(['#fffbeb', '#140300']); + expect(palette.getColorCombinations(2)).toEqual([ + { + background: 'rgb(255, 251, 235)', + foreground: '#080909' + }, + { + background: 'rgb(20, 3, 0)', + foreground: '#FFFFFF' + } + ]); + }); + + test('should generate color for a string as expected from a limited set', () => { + const palette = new ColorPalette(['#fffbeb', '#140300', '#789ab7']); + expect(palette.getColorCombinationForId('test', 2)).toEqual({ + background: 'rgb(255, 251, 235)', + foreground: '#080909' + }); + expect(palette.getColorCombinationForId('test')).toEqual({ + background: 'rgb(20, 3, 0)', + foreground: '#FFFFFF' + }); + }); }); diff --git a/projects/common/src/color/color-palette.ts b/projects/common/src/color/color-palette.ts index 41c832c71..2d1789984 100644 --- a/projects/common/src/color/color-palette.ts +++ b/projects/common/src/color/color-palette.ts @@ -1,5 +1,7 @@ import { rgb } from 'd3-color'; import { interpolateRgbBasis, quantize } from 'd3-interpolate'; +import { hashCode } from '../utilities/math/math-utilities'; +import { Color, ColorCombination } from './color'; export class ColorPalette { private readonly basisColors: string[]; @@ -10,6 +12,25 @@ export class ColorPalette { this.basisColors = basisColors.map(color => rgb(color).toString()); } + public getColorCombinations(count: number): ColorCombination[] { + return this.forNColors(count).map(color => ({ background: color, foreground: this.getContrast(color) })); + } + + public getColorCombinationForId(id: string, colorSetSize: number = this.basisColors.length): ColorCombination { + return this.getColorCombinations(colorSetSize)[Math.abs(hashCode(id)) % colorSetSize]; + } + + private getContrast(rgbColorString: string): string { + // Convert to RGB value + const rgbColor = rgb(rgbColorString); + + // Get YIQ ratio + const yiq = (rgbColor.r * 299 + rgbColor.g * 587 + rgbColor.b * 114) / 1000; + + // Check contrast + return yiq >= 128 ? Color.Gray9 : Color.White; + } + public forNColors(count: number): string[] { if (count === this.basisColors.length) { // Use as is if palette size matches, don't interpolate diff --git a/projects/common/src/color/color.ts b/projects/common/src/color/color.ts index bcf86245d..d5d8b3afd 100644 --- a/projects/common/src/color/color.ts +++ b/projects/common/src/color/color.ts @@ -1,3 +1,5 @@ +import { hashCode } from '../utilities/math/math-utilities'; + export const enum Color { Blue1 = '#f0f6ff', Blue2 = '#b8d3ff', @@ -58,3 +60,20 @@ export const enum Color { Yellow8 = '#6d5b00', Yellow9 = '#181400' } + +export interface ColorCombination { + background: string; + foreground: string; +} + +export const getHexColorForString = (id: string): string => { + const hash = hashCode(id); + let rgb = '#'; + for (let i = 0; i < 3; i++) { + // tslint:disable-next-line: no-bitwise + const value = (hash >> (i * 8)) & 0xff; + rgb += `00${value.toString(16)}`.substr(-2); + } + + return rgb; +}; diff --git a/projects/common/src/public-api.ts b/projects/common/src/public-api.ts index 0ea74530b..ebd7dd733 100644 --- a/projects/common/src/public-api.ts +++ b/projects/common/src/public-api.ts @@ -124,3 +124,6 @@ export * from './time/page-time-range-preference.service'; // Validators export * from './utilities/validators'; + +// Color Palette +export * from './color/color-palette'; diff --git a/projects/common/src/utilities/math/math-utilities.ts b/projects/common/src/utilities/math/math-utilities.ts index 9a0963d04..2a0f3cbbc 100644 --- a/projects/common/src/utilities/math/math-utilities.ts +++ b/projects/common/src/utilities/math/math-utilities.ts @@ -110,6 +110,11 @@ export const getPercentage = (numerator: number | undefined, denominator: number return (numerator / denominator) * 100; }; +// Trying to recreate Java hashcode +export const hashCode = (str: string): number => + // tslint:disable-next-line: no-bitwise + Array.from(str).reduce((s, c) => (Math.imul(31, s) + c.charCodeAt(0)) | 0, 0); + export interface Point { x: number; y: number;