Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(color): color consistency enhancements #21507

Merged
merged 15 commits into from
Oct 17, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class CategoricalColorScale extends ExtensibleFunction {
* @param {*} parentForcedColors optional parameter that comes from parent
* (usually CategoricalColorNamespace) and supersede this.forcedColors
*/
constructor(colors: string[], parentForcedColors?: ColorsLookup) {
constructor(colors: string[], parentForcedColors: ColorsLookup = {}) {
super((value: string, sliceId?: number) => this.getColor(value, sliceId));

this.originColors = colors;
Expand All @@ -67,30 +67,25 @@ class CategoricalColorScale extends ExtensibleFunction {
const cleanedValue = stringifyAndTrim(value);
const sharedLabelColor = getSharedLabelColor();

const parentColor = this.parentForcedColors?.[cleanedValue];
if (parentColor) {
sharedLabelColor.addSlice(cleanedValue, parentColor, sliceId);
return parentColor;
}

const forcedColor = this.forcedColors[cleanedValue];
if (forcedColor) {
sharedLabelColor.addSlice(cleanedValue, forcedColor, sliceId);
return forcedColor;
}

if (isFeatureEnabled(FeatureFlag.USE_ANALAGOUS_COLORS)) {
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));
// priority: parentForcedColors > forcedColors > labelColors
let color =
this.parentForcedColors?.[cleanedValue] ||
this.forcedColors?.[cleanedValue] ||
sharedLabelColor.getColorMap().get(cleanedValue);

if (!color) {
if (isFeatureEnabled(FeatureFlag.USE_ANALAGOUS_COLORS)) {
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));
}
}
color = this.scale(cleanedValue);
}

const color = this.scale(cleanedValue);
sharedLabelColor.addSlice(cleanedValue, color, sliceId);

return color;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,113 +18,79 @@
*/

import { CategoricalColorNamespace } from '.';
import { FeatureFlag, isFeatureEnabled, makeSingleton } from '../utils';
import { getAnalogousColors } from './utils';
import { makeSingleton } from '../utils';

export enum SharedLabelColorSource {
dashboard,
explore,
}
export class SharedLabelColor {
sliceLabelColorMap: Record<number, Record<string, string | undefined>>;
sliceLabelMap: Map<number, string[]>;

constructor() {
// { sliceId1: { label1: color1 }, sliceId2: { label2: color2 } }
this.sliceLabelColorMap = {};
}
colorMap: Map<string, string>;

getColorMap(
colorNamespace?: string,
colorScheme?: string,
updateColorScheme?: boolean,
) {
if (colorScheme) {
const categoricalNamespace =
CategoricalColorNamespace.getNamespace(colorNamespace);
const sharedLabels = this.getSharedLabels();
let generatedColors: string[] = [];
let sharedLabelMap;
source: SharedLabelColorSource;

if (sharedLabels.length) {
const colorScale = categoricalNamespace.getScale(colorScheme);
const colors = colorScale.range();
if (isFeatureEnabled(FeatureFlag.USE_ANALAGOUS_COLORS)) {
const multiple = Math.ceil(sharedLabels.length / colors.length);
generatedColors = getAnalogousColors(colors, multiple);
sharedLabelMap = sharedLabels.reduce(
(res, label, index) => ({
...res,
[label.toString()]: generatedColors[index],
}),
{},
);
} else {
// reverse colors to reduce color conflicts
colorScale.range(colors.reverse());
sharedLabelMap = sharedLabels.reduce(
(res, label) => ({
...res,
[label.toString()]: colorScale(label),
}),
{},
);
}
}
constructor() {
// { sliceId1: [label1, label2, ...], sliceId2: [label1, label2, ...] }
this.sliceLabelMap = new Map();
this.colorMap = new Map();
this.source = SharedLabelColorSource.dashboard;
}

const labelMap = Object.keys(this.sliceLabelColorMap).reduce(
(res, sliceId) => {
// get new color scale instance
const colorScale = categoricalNamespace.getScale(colorScheme);
return {
...res,
...Object.keys(this.sliceLabelColorMap[sliceId]).reduce(
(res, label) => ({
...res,
[label]: updateColorScheme
? colorScale(label)
: this.sliceLabelColorMap[sliceId][label],
}),
{},
),
};
},
{},
);
updateColorMap(colorNamespace?: string, colorScheme?: string) {
const categoricalNamespace =
CategoricalColorNamespace.getNamespace(colorNamespace);
const newColorMap = new Map();
this.colorMap.clear();
this.sliceLabelMap.forEach(labels => {
const colorScale = categoricalNamespace.getScale(colorScheme);
labels.forEach(label => {
const newColor = colorScale(label);
newColorMap.set(label, newColor);
});
});
this.colorMap = newColorMap;
}

return {
...labelMap,
...sharedLabelMap,
};
}
return undefined;
getColorMap() {
return this.colorMap;
}

addSlice(label: string, color: string, sliceId?: number) {
if (!sliceId) return;
this.sliceLabelColorMap[sliceId] = {
...this.sliceLabelColorMap[sliceId],
[label]: color,
};
if (
this.source !== SharedLabelColorSource.dashboard ||
sliceId === undefined
)
return;
const labels = this.sliceLabelMap.get(sliceId) || [];
labels.push(label);
this.sliceLabelMap.set(sliceId, labels);
this.colorMap.set(label, color);
}

removeSlice(sliceId: number) {
delete this.sliceLabelColorMap[sliceId];
if (this.source !== SharedLabelColorSource.dashboard) return;
this.sliceLabelMap.delete(sliceId);
const newColorMap = new Map();
this.sliceLabelMap.forEach(labels => {
labels.forEach(label => {
newColorMap.set(label, this.colorMap.get(label));
});
});
this.colorMap = newColorMap;
}

clear() {
this.sliceLabelColorMap = {};
reset() {
const copyColorMap = new Map(this.colorMap);
copyColorMap.forEach((_, label) => {
this.colorMap.set(label, '');
});
}

getSharedLabels() {
const tempLabels = new Set<string>();
const result = new Set<string>();
Object.keys(this.sliceLabelColorMap).forEach(sliceId => {
const colorMap = this.sliceLabelColorMap[sliceId];
Object.keys(colorMap).forEach(label => {
if (tempLabels.has(label) && !result.has(label)) {
result.add(label);
} else {
tempLabels.add(label);
}
});
});
return [...result];
clear() {
this.sliceLabelMap.clear();
this.colorMap.clear();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export * from './utils';
export {
default as getSharedLabelColor,
SharedLabelColor,
SharedLabelColorSource,
} from './SharedLabelColorSingleton';

export const BRAND_COLOR = '#00A699';
Loading