Skip to content

Commit

Permalink
Migrate @superset-ui/color to TypeScript (#69)
Browse files Browse the repository at this point in the history
* Refactor: Convert color to TS
  • Loading branch information
kristw authored and zhaoyongjie committed Nov 17, 2021
1 parent ead7392 commit 9cdbcad
Show file tree
Hide file tree
Showing 28 changed files with 95 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"build": "yarn run build:cjs && yarn run build:esm && yarn run type:dts",
"build:cjs": "NODE_ENV=production beemo babel --extensions=\".js,.jsx,.ts,.tsx\" ./src --out-dir lib/ --minify --workspaces=\"@superset-ui/!(demo|generator-superset)\"",
"build:esm": "NODE_ENV=production beemo babel --extensions=\".js,.jsx,.ts,.tsx\" ./src --out-dir esm/ --esm --minify --workspaces=\"@superset-ui/!(demo|generator-superset)\"",
"type": "NODE_ENV=production beemo typescript --workspaces=\"@superset-ui/(connection|core|chart)\" --noEmit",
"type:dts": "NODE_ENV=production beemo typescript --workspaces=\"@superset-ui/(connection|core|chart)\" --emitDeclarationOnly",
"type": "NODE_ENV=production beemo typescript --workspaces=\"@superset-ui/(connection|core|color|chart)\" --noEmit",
"type:dts": "NODE_ENV=production beemo typescript --workspaces=\"@superset-ui/(connection|core|color|chart)\" --emitDeclarationOnly",
"lint": "beemo create-config prettier && beemo eslint \"./packages/*/{src,test,storybook}/**/*.{js,jsx,ts,tsx}\"",
"lint:fix": "beemo create-config prettier && beemo eslint --fix \"./packages/*/{src,test,storybook}/**/*.{js,jsx,ts,tsx}\"",
"jest": "beemo jest --color --coverage --react",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
},
"dependencies": {
"@superset-ui/core": "^0.7.0",
"@types/d3-scale": "^2.0.2",
"d3-scale": "^2.1.2"
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
import CategoricalColorScale from './CategoricalColorScale';
import { ColorsLookup } from './types';
import getCategoricalSchemeRegistry from './CategoricalSchemeRegistrySingleton';
import stringifyAndTrim from './stringifyAndTrim';

export default class CategoricalColorNamespace {
constructor(name) {
name: string;
forcedItems: ColorsLookup;
scales: {
[key: string]: CategoricalColorScale;
};

constructor(name: string) {
this.name = name;
this.scales = {};
this.forcedItems = {};
}

getScale(schemeId) {
const id = schemeId || getCategoricalSchemeRegistry().getDefaultKey();
getScale(schemeId?: string) {
const id = schemeId || getCategoricalSchemeRegistry().getDefaultKey() || '';
const scale = this.scales[id];
if (scale) {
return scale;
}
const newScale = new CategoricalColorScale(
getCategoricalSchemeRegistry().get(id).colors,
this.forcedItems,
);
const scheme = getCategoricalSchemeRegistry().get(id);

const newScale = new CategoricalColorScale((scheme && scheme.colors) || [], this.forcedItems);
this.scales[id] = newScale;

return newScale;
Expand All @@ -31,17 +37,20 @@ export default class CategoricalColorNamespace {
* @param {*} value value
* @param {*} forcedColor color
*/
setColor(value, forcedColor) {
setColor(value: string, forcedColor: string) {
this.forcedItems[stringifyAndTrim(value)] = forcedColor;

return this;
}
}

const namespaces = {};
const namespaces: {
[key: string]: CategoricalColorNamespace;
} = {};

export const DEFAULT_NAMESPACE = 'GLOBAL';

export function getNamespace(name = DEFAULT_NAMESPACE) {
export function getNamespace(name: string = DEFAULT_NAMESPACE) {
const instance = namespaces[name];
if (instance) {
return instance;
Expand All @@ -52,12 +61,12 @@ export function getNamespace(name = DEFAULT_NAMESPACE) {
return newInstance;
}

export function getColor(value, schemeId, namespace) {
export function getColor(value?: string, schemeId?: string, namespace?: string) {
return getNamespace(namespace)
.getScale(schemeId)
.getColor(value);
}

export function getScale(scheme, namespace) {
export function getScale(scheme?: string, namespace?: string) {
return getNamespace(namespace).getScale(scheme);
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
import { ExtensibleFunction } from '@superset-ui/core';
import { ColorsLookup } from './types';
import stringifyAndTrim from './stringifyAndTrim';

export default class CategoricalColorScale extends ExtensibleFunction {
colors: string[];
parentForcedColors?: ColorsLookup;
forcedColors: ColorsLookup;
seen: { [key: string]: number };

/**
* Constructor
* @param {*} colors an array of colors
* @param {*} parentForcedColors optional parameter that comes from parent
* (usually CategoricalColorNamespace) and supersede this.forcedColors
*/
constructor(colors, parentForcedColors) {
super((...args) => this.getColor(...args));
constructor(colors: string[], parentForcedColors?: ColorsLookup) {
super((value: string) => this.getColor(value));
this.colors = colors;
this.parentForcedColors = parentForcedColors;
this.forcedColors = {};
this.seen = {};
}

getColor(value) {
getColor(value?: string) {
const cleanedValue = stringifyAndTrim(value);

const parentColor = this.parentForcedColors && this.parentForcedColors[cleanedValue];
Expand Down Expand Up @@ -46,7 +52,7 @@ export default class CategoricalColorScale extends ExtensibleFunction {
* @param {*} value value
* @param {*} forcedColor forcedColor
*/
setColor(value, forcedColor) {
setColor(value: string, forcedColor: string) {
this.forcedColors[stringifyAndTrim(value)] = forcedColor;

return this;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { makeSingleton } from '@superset-ui/core';
import CategoricalScheme from './CategoricalScheme';
import ColorSchemeRegistry from './ColorSchemeRegistry';

class CategoricalSchemeRegistry extends ColorSchemeRegistry {}
class CategoricalSchemeRegistry extends ColorSchemeRegistry<CategoricalScheme> {}

const getInstance = makeSingleton(CategoricalSchemeRegistry);

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export interface ColorSchemeConfig {
colors: string[];
description?: string;
id: string;
label?: string;
}

export default class ColorScheme {
colors: string[];
description: string;
id: string;
label: string;

constructor(config: ColorSchemeConfig) {
const { colors, description = '', id, label } = config;
this.id = id;
this.label = label || id;
this.colors = colors;
this.description = description;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { RegistryWithDefaultKey } from '@superset-ui/core';

export default class ColorSchemeRegistry extends RegistryWithDefaultKey {
export default class ColorSchemeRegistry<T> extends RegistryWithDefaultKey<T> {
constructor() {
super({
name: 'ColorScheme',
setFirstItemAsDefault: true,
});
}

get(key?: string) {
return super.get(key) as T | undefined;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { scaleLinear } from 'd3-scale';
import ColorScheme from './ColorScheme';
import ColorScheme, { ColorSchemeConfig } from './ColorScheme';

function range(count) {
function range(count: number) {
const values = [];
for (let i = 0; i < count; i += 1) {
values.push(i);
Expand All @@ -10,28 +10,34 @@ function range(count) {
return values;
}

export interface SequentialSchemeConfig extends ColorSchemeConfig {
isDiverging?: boolean;
}

export default class SequentialScheme extends ColorScheme {
constructor(input) {
super(input);
const { isDiverging = false } = input;
isDiverging: boolean;

constructor(config: SequentialSchemeConfig) {
super(config);
const { isDiverging = false } = config;
this.isDiverging = isDiverging;
}

createLinearScale(extent = [0, 1]) {
createLinearScale(extent: number[] = [0, 1]) {
// Create matching domain
// because D3 continuous scale uses piecewise mapping
// between domain and range.
const valueScale = scaleLinear().range(extent);
const denominator = this.colors.length - 1;
const domain = range(this.colors.length).map(i => valueScale(i / denominator));

return scaleLinear()
return scaleLinear<string>()
.domain(domain)
.range(this.colors)
.clamp(true);
}

getColors(numColors = this.colors.length) {
getColors(numColors: number = this.colors.length): string[] {
if (numColors === this.colors.length) {
return this.colors;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { makeSingleton } from '@superset-ui/core';
import ColorSchemeRegistry from './ColorSchemeRegistry';
import SequentialScheme from './SequentialScheme';

class SequentialSchemeRegistry extends ColorSchemeRegistry {}
class SequentialSchemeRegistry extends ColorSchemeRegistry<SequentialScheme> {}

const getInstance = makeSingleton(SequentialSchemeRegistry);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import * as CategoricalColorNamespace from './CategoricalColorNamespace';

export { CategoricalColorNamespace };
export { ColorSchemeConfig } from './ColorScheme';
export { default as CategoricalColorScale } from './CategoricalColorScale';
export { default as CategoricalScheme } from './CategoricalScheme';
export { default as getCategoricalSchemeRegistry } from './CategoricalSchemeRegistrySingleton';
export { default as getSequentialSchemeRegistry } from './SequentialSchemeRegistrySingleton';
export { default as SequentialScheme } from './SequentialScheme';
export { default as SequentialScheme, SequentialSchemeConfig } from './SequentialScheme';

export const BRAND_COLOR = '#00A699';
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
* Ensure value is a string
* @param {any} value
*/
export default function stringifyAndTrim(value) {
export default function stringifyAndTrim(value?: number | string) {
return String(value).trim();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/* eslint-disable import/prefer-default-export */

export interface ColorsLookup {
[key: string]: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ describe('CategoricalColorNamespace', () => {
expect(scale).toBeDefined();
expect(scale.getColor('dog')).toBeDefined();
});
it('returns a scale when a schemeId is not specified and there is no default key', () => {
getCategoricalSchemeRegistry().clearDefaultKey();
const namespace = getNamespace('new-space');
const scale = namespace.getScale();
expect(scale).toBeDefined();
getCategoricalSchemeRegistry().setDefaultKey('testColors');
});
it('returns same scale if the scale with that name already exists in this namespace', () => {
const namespace = getNamespace('test-get-scale2');
const scale1 = namespace.getScale('testColors');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe('CategoricalColorScale', () => {
expect(c3).not.toBe(c1);
});
it('recycles colors when number of items exceed available colors', () => {
const colorSet = {};
const colorSet: { [key: string]: number } = {};
const scale = new CategoricalColorScale(['blue', 'red', 'green']);
const colors = [
scale.getColor('pig'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@ import ColorScheme from '../src/ColorScheme';

describe('ColorScheme', () => {
describe('new ColorScheme()', () => {
it('requires name and color', () => {
expect(() => new ColorScheme()).toThrow();
expect(() => new ColorScheme({ id: 'test' })).toThrow();
expect(() => new ColorScheme({ colors: ['red', 'blue'] })).toThrow();
});
it('returns an instance of ColorScheme', () => {
const scheme = new ColorScheme({ id: 'test', colors: ['red', 'blue'] });
expect(scheme).toBeInstanceOf(ColorScheme);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export default function isRequired(field: string) {
export default function isRequired(field: string): never {
throw new Error(`${field} is required.`);
}

0 comments on commit 9cdbcad

Please sign in to comment.