From 35bbbadc10f8c29c845c9d8c2fb47dda1198686c Mon Sep 17 00:00:00 2001 From: stephenLYZ <750188453@qq.com> Date: Wed, 23 Mar 2022 18:27:43 +0800 Subject: [PATCH 1/4] feat(color): support analogous colors --- .../src/color/CategoricalColorScale.ts | 9 +++++++++ .../src/color/SharedLabelColorSingleton.ts | 19 ++++-------------- .../superset-ui-core/src/color/utils.ts | 20 +++++++++++++++++++ 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/superset-frontend/packages/superset-ui-core/src/color/CategoricalColorScale.ts b/superset-frontend/packages/superset-ui-core/src/color/CategoricalColorScale.ts index d34960dac097..f69569420fe4 100644 --- a/superset-frontend/packages/superset-ui-core/src/color/CategoricalColorScale.ts +++ b/superset-frontend/packages/superset-ui-core/src/color/CategoricalColorScale.ts @@ -23,6 +23,7 @@ import { ExtensibleFunction } from '../models'; import { ColorsLookup } from './types'; import stringifyAndTrim from './stringifyAndTrim'; import getSharedLabelColor from './SharedLabelColorSingleton'; +import { getAnalogousColors } from './utils'; // Use type augmentation to correct the fact that // an instance of CategoricalScale is also a function @@ -72,6 +73,14 @@ class CategoricalColorScale extends ExtensibleFunction { return forcedColor; } + const domain = this.domain(); + const range = this.range(); + const multiple = Math.floor(domain.length / range.length); + if (multiple >= 1) { + const newRange = getAnalogousColors(range, multiple); + this.range(range.concat(newRange)); + } + const color = this.scale(cleanedValue); sharedLabelColor.addSlice(cleanedValue, color, sliceId); diff --git a/superset-frontend/packages/superset-ui-core/src/color/SharedLabelColorSingleton.ts b/superset-frontend/packages/superset-ui-core/src/color/SharedLabelColorSingleton.ts index 227b565276a9..d2a59ac7c292 100644 --- a/superset-frontend/packages/superset-ui-core/src/color/SharedLabelColorSingleton.ts +++ b/superset-frontend/packages/superset-ui-core/src/color/SharedLabelColorSingleton.ts @@ -17,9 +17,9 @@ * under the License. */ -import tinycolor from 'tinycolor2'; import { CategoricalColorNamespace } from '.'; import makeSingleton from '../utils/makeSingleton'; +import { getAnalogousColors } from './utils'; export class SharedLabelColor { sliceLabelColorMap: Record>; @@ -39,27 +39,16 @@ export class SharedLabelColor { CategoricalColorNamespace.getNamespace(colorNamespace); const colors = categoricalNamespace.getScale(colorScheme).range(); const sharedLabels = this.getSharedLabels(); - const generatedColors: tinycolor.Instance[] = []; + let generatedColors: string[] = []; let sharedLabelMap; if (sharedLabels.length) { const multiple = Math.ceil(sharedLabels.length / colors.length); - const ext = 5; - const analogousColors = colors.map(color => { - const result = tinycolor(color).analogous(multiple + ext); - return result.slice(ext); - }); - - // [[A, AA, AAA], [B, BB, BBB]] => [A, B, AA, BB, AAA, BBB] - while (analogousColors[analogousColors.length - 1]?.length) { - analogousColors.forEach(colors => - generatedColors.push(colors.shift() as tinycolor.Instance), - ); - } + generatedColors = getAnalogousColors(colors, multiple); sharedLabelMap = sharedLabels.reduce( (res, label, index) => ({ ...res, - [label.toString()]: generatedColors[index]?.toHexString(), + [label.toString()]: generatedColors[index], }), {}, ); diff --git a/superset-frontend/packages/superset-ui-core/src/color/utils.ts b/superset-frontend/packages/superset-ui-core/src/color/utils.ts index 47a936aaa618..9ed8ee5d35e7 100644 --- a/superset-frontend/packages/superset-ui-core/src/color/utils.ts +++ b/superset-frontend/packages/superset-ui-core/src/color/utils.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import tinycolor from 'tinycolor2'; const rgbRegex = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/; export function getContrastingColor(color: string, thresholds = 186) { @@ -51,3 +52,22 @@ export function getContrastingColor(color: string, thresholds = 186) { return r * 0.299 + g * 0.587 + b * 0.114 > thresholds ? '#000' : '#FFF'; } + +export function getAnalogousColors(colors: string[], results: number) { + const generatedColors: string[] = []; + const ext = 5; + const analogousColors = colors.map(color => { + const result = tinycolor(color).analogous(results + ext); + return result.slice(ext); + }); + + // [[A, AA, AAA], [B, BB, BBB]] => [A, B, AA, BB, AAA, BBB] + while (analogousColors[analogousColors.length - 1]?.length) { + analogousColors.forEach(colors => { + const color = colors.shift() as tinycolor.Instance; + generatedColors.push(color.toHexString()); + }); + } + + return generatedColors; +} From 96b0bb944a2c175a1b494155a539c5ff3f3040e4 Mon Sep 17 00:00:00 2001 From: stephenLYZ <750188453@qq.com> Date: Wed, 23 Mar 2022 20:57:10 +0800 Subject: [PATCH 2/4] fix test --- .../test/color/CategoricalColorScale.test.ts | 29 +++++-------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/superset-frontend/packages/superset-ui-core/test/color/CategoricalColorScale.test.ts b/superset-frontend/packages/superset-ui-core/test/color/CategoricalColorScale.test.ts index f080b6fc84e5..1d47cf760e32 100644 --- a/superset-frontend/packages/superset-ui-core/test/color/CategoricalColorScale.test.ts +++ b/superset-frontend/packages/superset-ui-core/test/color/CategoricalColorScale.test.ts @@ -62,28 +62,15 @@ describe('CategoricalColorScale', () => { expect(c2).not.toBe(c3); expect(c3).not.toBe(c1); }); - it('recycles colors when number of items exceed available colors', () => { - const colorSet: { [key: string]: number } = {}; + it('get analogous colors when number of items exceed available colors', () => { const scale = new CategoricalColorScale(['blue', 'red', 'green']); - const colors = [ - scale.getColor('pig'), - scale.getColor('horse'), - scale.getColor('cat'), - scale.getColor('cow'), - scale.getColor('donkey'), - scale.getColor('goat'), - ]; - colors.forEach(color => { - if (colorSet[color]) { - colorSet[color] += 1; - } else { - colorSet[color] = 1; - } - }); - expect(Object.keys(colorSet)).toHaveLength(3); - ['blue', 'red', 'green'].forEach(color => { - expect(colorSet[color]).toBe(2); - }); + scale.getColor('pig'); + scale.getColor('horse'); + scale.getColor('cat'); + scale.getColor('cow'); + scale.getColor('donkey'); + scale.getColor('goat'); + expect(scale.range()).toHaveLength(6); }); }); describe('.setColor(value, forcedColor)', () => { From 1b7a8401cefadfd79189e34414cc9aa7800a0066 Mon Sep 17 00:00:00 2001 From: stephenLYZ <750188453@qq.com> Date: Thu, 24 Mar 2022 17:22:02 +0800 Subject: [PATCH 3/4] fix range --- .../src/color/CategoricalColorScale.ts | 19 +++++++++++++------ .../superset-ui-core/src/color/utils.ts | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/superset-frontend/packages/superset-ui-core/src/color/CategoricalColorScale.ts b/superset-frontend/packages/superset-ui-core/src/color/CategoricalColorScale.ts index f69569420fe4..c6f37e4ff771 100644 --- a/superset-frontend/packages/superset-ui-core/src/color/CategoricalColorScale.ts +++ b/superset-frontend/packages/superset-ui-core/src/color/CategoricalColorScale.ts @@ -32,6 +32,8 @@ interface CategoricalColorScale { } class CategoricalColorScale extends ExtensibleFunction { + originColors: string[]; + colors: string[]; scale: ScaleOrdinal<{ toString(): string }, string>; @@ -40,6 +42,8 @@ class CategoricalColorScale extends ExtensibleFunction { forcedColors: ColorsLookup; + multiple: number; + /** * Constructor * @param {*} colors an array of colors @@ -49,11 +53,13 @@ class CategoricalColorScale extends ExtensibleFunction { constructor(colors: string[], parentForcedColors?: ColorsLookup) { super((value: string, sliceId?: number) => this.getColor(value, sliceId)); + this.originColors = colors; this.colors = colors; this.scale = scaleOrdinal<{ toString(): string }, string>(); this.scale.range(colors); this.parentForcedColors = parentForcedColors; this.forcedColors = {}; + this.multiple = 0; } getColor(value?: string, sliceId?: number) { @@ -73,12 +79,13 @@ class CategoricalColorScale extends ExtensibleFunction { return forcedColor; } - const domain = this.domain(); - const range = this.range(); - const multiple = Math.floor(domain.length / range.length); - if (multiple >= 1) { - const newRange = getAnalogousColors(range, multiple); - this.range(range.concat(newRange)); + const multiple = Math.floor( + this.domain().length / this.originColors.length, + ); + if (multiple > this.multiple) { + this.multiple = multiple; + const newRange = getAnalogousColors(this.originColors, multiple); + this.range(this.originColors.concat(newRange)); } const color = this.scale(cleanedValue); diff --git a/superset-frontend/packages/superset-ui-core/src/color/utils.ts b/superset-frontend/packages/superset-ui-core/src/color/utils.ts index 9ed8ee5d35e7..8ea80d4bec89 100644 --- a/superset-frontend/packages/superset-ui-core/src/color/utils.ts +++ b/superset-frontend/packages/superset-ui-core/src/color/utils.ts @@ -55,7 +55,7 @@ export function getContrastingColor(color: string, thresholds = 186) { export function getAnalogousColors(colors: string[], results: number) { const generatedColors: string[] = []; - const ext = 5; + const ext = 3; const analogousColors = colors.map(color => { const result = tinycolor(color).analogous(results + ext); return result.slice(ext); From 5eb792c49d65472a512a909f223ad209ce0be828 Mon Sep 17 00:00:00 2001 From: stephenLYZ <750188453@qq.com> Date: Thu, 31 Mar 2022 08:41:45 +0800 Subject: [PATCH 4/4] add some comment --- superset-frontend/packages/superset-ui-core/src/color/utils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/superset-frontend/packages/superset-ui-core/src/color/utils.ts b/superset-frontend/packages/superset-ui-core/src/color/utils.ts index 8ea80d4bec89..0ce64d049012 100644 --- a/superset-frontend/packages/superset-ui-core/src/color/utils.ts +++ b/superset-frontend/packages/superset-ui-core/src/color/utils.ts @@ -55,6 +55,8 @@ export function getContrastingColor(color: string, thresholds = 186) { export function getAnalogousColors(colors: string[], results: number) { const generatedColors: string[] = []; + // This is to solve the problem that the first three values generated by tinycolor.analogous + // may have the same or very close colors. const ext = 3; const analogousColors = colors.map(color => { const result = tinycolor(color).analogous(results + ext);