Improve categorical color management (#5815)

* Create new classes for handling categorical colors

* verify to pass existing unit tests

* separate logic for forcing color and getting color

* replace getColorFromScheme with CategoricalColorManager

* organize static functions

* migrate to new function

* Remove ALL_COLOR_SCHEMES

* move sequential colors to another file

* extract categorical colors to separate file

* move airbnb and lyft colors to separate files

* fix missing toFunction()

* Rewrite to support local and global force items, plus namespacing.

* fix references

* revert nvd3

* update namespace api

* Update the visualizations

* update usage with static functions

* update unit test

* add unit test

* rename default namespace

* add unit test for color namespace

* add unit test for namespace

* start unit test for colorschememanager

* add unit tests for color scheme manager

* check returns for chaining

* complete unit test for the new classes

* fix color tests

* update unit tests

* update unit tests

* move color scheme registration to common

* update unit test

* rename sharedForcedColors to parentForcedColors

* remove import
This commit is contained in:
Krist Wongsuphasawat
2018-09-12 14:10:26 -07:00
committed by Chris Williams
parent bec0b4cc37
commit f482a6cf99
26 changed files with 1186 additions and 595 deletions

View File

@@ -8,6 +8,7 @@
"test": "spec"
},
"scripts": {
"tdd": "mocha --require ignore-styles --compilers js:babel-core/register --require spec/helpers/shim.js 'spec/**/*_spec.*' --watch --recursive",
"test": "mocha --require ignore-styles --compilers js:babel-core/register --require spec/helpers/shim.js 'spec/**/*_spec.*'",
"test:one": "mocha --require ignore-styles --compilers js:babel-core/register --require spec/helpers/shim.js",
"cover": "babel-node node_modules/.bin/babel-istanbul cover _mocha -- --compilers babel-core/register --require spec/helpers/shim.js --require ignore-styles 'spec/**/*_spec.*'",

View File

@@ -7,10 +7,10 @@ import { Creatable } from 'react-select';
import ColorSchemeControl from
'../../../../src/explore/components/controls/ColorSchemeControl';
import { ALL_COLOR_SCHEMES } from '../../../../src/modules/colors';
import { getAllSchemes } from '../../../../src/modules/ColorSchemeManager';
const defaultProps = {
options: Object.keys(ALL_COLOR_SCHEMES).map(s => ([s, s])),
options: Object.keys(getAllSchemes()).map(s => ([s, s])),
};
describe('ColorSchemeControl', () => {

View File

@@ -0,0 +1,130 @@
import { it, describe, before } from 'mocha';
import { expect } from 'chai';
import CategoricalColorNamespace, {
getNamespace,
getScale,
getColor,
DEFAULT_NAMESPACE,
} from '../../../src/modules/CategoricalColorNamespace';
import { registerScheme } from '../../../src/modules/ColorSchemeManager';
describe('CategoricalColorNamespace', () => {
before(() => {
registerScheme('testColors', ['red', 'green', 'blue']);
registerScheme('testColors2', ['red', 'green', 'blue']);
});
it('The class constructor cannot be accessed directly', () => {
expect(CategoricalColorNamespace).to.not.be.a('Function');
});
describe('static getNamespace()', () => {
it('returns default namespace if name is not specified', () => {
const namespace = getNamespace();
expect(namespace !== undefined).to.equal(true);
expect(namespace.name).to.equal(DEFAULT_NAMESPACE);
});
it('returns namespace with specified name', () => {
const namespace = getNamespace('myNamespace');
expect(namespace !== undefined).to.equal(true);
expect(namespace.name).to.equal('myNamespace');
});
it('returns existing instance if the name already exists', () => {
const ns1 = getNamespace('myNamespace');
const ns2 = getNamespace('myNamespace');
expect(ns1).to.equal(ns2);
const ns3 = getNamespace();
const ns4 = getNamespace();
expect(ns3).to.equal(ns4);
});
});
describe('.getScale()', () => {
it('returns a CategoricalColorScale from given scheme name', () => {
const namespace = getNamespace('test-get-scale1');
const scale = namespace.getScale('testColors');
expect(scale).to.not.equal(undefined);
expect(scale.getColor('dog')).to.not.equal(undefined);
});
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');
const scale2 = namespace.getScale('testColors2');
const scale3 = namespace.getScale('testColors2');
const scale4 = namespace.getScale('testColors');
expect(scale1).to.equal(scale4);
expect(scale2).to.equal(scale3);
});
});
describe('.setColor()', () => {
it('overwrites color for all CategoricalColorScales in this namespace', () => {
const namespace = getNamespace('test-set-scale1');
namespace.setColor('dog', 'black');
const scale = namespace.getScale('testColors');
expect(scale.getColor('dog')).to.equal('black');
expect(scale.getColor('boy')).to.not.equal('black');
});
it('can override forcedColors in each scale', () => {
const namespace = getNamespace('test-set-scale2');
namespace.setColor('dog', 'black');
const scale = namespace.getScale('testColors');
scale.setColor('dog', 'pink');
expect(scale.getColor('dog')).to.equal('black');
expect(scale.getColor('boy')).to.not.equal('black');
});
it('does not affect scales in other namespaces', () => {
const ns1 = getNamespace('test-set-scale3.1');
ns1.setColor('dog', 'black');
const scale1 = ns1.getScale('testColors');
const ns2 = getNamespace('test-set-scale3.2');
const scale2 = ns2.getScale('testColors');
expect(scale1.getColor('dog')).to.equal('black');
expect(scale2.getColor('dog')).to.not.equal('black');
});
it('returns the namespace instance', () => {
const ns1 = getNamespace('test-set-scale3.1');
const ns2 = ns1.setColor('dog', 'black');
expect(ns1).to.equal(ns2);
});
});
describe('static getScale()', () => {
it('getScale() returns a CategoricalColorScale with default scheme in default namespace', () => {
const scale = getScale();
expect(scale).to.not.equal(undefined);
const scale2 = getNamespace().getScale();
expect(scale).to.equal(scale2);
});
it('getScale(scheme) returns a CategoricalColorScale with specified scheme in default namespace', () => {
const scale = getScale('testColors');
expect(scale).to.not.equal(undefined);
const scale2 = getNamespace().getScale('testColors');
expect(scale).to.equal(scale2);
});
it('getScale(scheme, namespace) returns a CategoricalColorScale with specified scheme in specified namespace', () => {
const scale = getScale('testColors', 'test-getScale');
expect(scale).to.not.equal(undefined);
const scale2 = getNamespace('test-getScale').getScale('testColors');
expect(scale).to.equal(scale2);
});
});
describe('static getColor()', () => {
it('getColor(value) returns a color from default scheme in default namespace', () => {
const value = 'dog';
const color = getColor(value);
const color2 = getNamespace().getScale().getColor(value);
expect(color).to.equal(color2);
});
it('getColor(value, scheme) returns a color from specified scheme in default namespace', () => {
const value = 'dog';
const scheme = 'testColors';
const color = getColor(value, scheme);
const color2 = getNamespace().getScale(scheme).getColor(value);
expect(color).to.equal(color2);
});
it('getColor(value, scheme, namespace) returns a color from specified scheme in specified namespace', () => {
const value = 'dog';
const scheme = 'testColors';
const namespace = 'test-getColor';
const color = getColor(value, scheme, namespace);
const color2 = getNamespace(namespace).getScale(scheme).getColor(value);
expect(color).to.equal(color2);
});
});
});

View File

@@ -0,0 +1,96 @@
import { it, describe } from 'mocha';
import { expect } from 'chai';
import CategoricalColorScale from '../../../src/modules/CategoricalColorScale';
describe('CategoricalColorScale', () => {
it('exists', () => {
expect(CategoricalColorScale !== undefined).to.equal(true);
});
describe('new CategoricalColorScale(colors, parentForcedColors)', () => {
it('can create new scale when parentForcedColors is not given', () => {
const scale = new CategoricalColorScale(['blue', 'red', 'green']);
expect(scale).to.be.instanceOf(CategoricalColorScale);
});
it('can create new scale when parentForcedColors is given', () => {
const parentForcedColors = {};
const scale = new CategoricalColorScale(['blue', 'red', 'green'], parentForcedColors);
expect(scale).to.be.instanceOf(CategoricalColorScale);
expect(scale.parentForcedColors).to.equal(parentForcedColors);
});
});
describe('.getColor(value)', () => {
it('returns same color for same value', () => {
const scale = new CategoricalColorScale(['blue', 'red', 'green']);
const c1 = scale.getColor('pig');
const c2 = scale.getColor('horse');
const c3 = scale.getColor('pig');
scale.getColor('cow');
const c5 = scale.getColor('horse');
expect(c1).to.equal(c3);
expect(c2).to.equal(c5);
});
it('returns different color for consecutive items', () => {
const scale = new CategoricalColorScale(['blue', 'red', 'green']);
const c1 = scale.getColor('pig');
const c2 = scale.getColor('horse');
const c3 = scale.getColor('cat');
expect(c1).to.not.equal(c2);
expect(c2).to.not.equal(c3);
expect(c3).to.not.equal(c1);
});
it('recycles colors when number of items exceed available colors', () => {
const colorSet = {};
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]++;
} else {
colorSet[color] = 1;
}
});
expect(Object.keys(colorSet).length).to.equal(3);
['blue', 'red', 'green'].forEach((color) => {
expect(colorSet[color]).to.equal(2);
});
});
});
describe('.setColor(value, forcedColor)', () => {
it('overrides default color', () => {
const scale = new CategoricalColorScale(['blue', 'red', 'green']);
scale.setColor('pig', 'pink');
expect(scale.getColor('pig')).to.equal('pink');
});
it('does not override parentForcedColors', () => {
const scale1 = new CategoricalColorScale(['blue', 'red', 'green']);
scale1.setColor('pig', 'black');
const scale2 = new CategoricalColorScale(['blue', 'red', 'green'], scale1.forcedColors);
scale2.setColor('pig', 'pink');
expect(scale1.getColor('pig')).to.equal('black');
expect(scale2.getColor('pig')).to.equal('black');
});
it('returns the scale', () => {
const scale = new CategoricalColorScale(['blue', 'red', 'green']);
const output = scale.setColor('pig', 'pink');
expect(scale).to.equal(output);
});
});
describe('.toFunction()', () => {
it('returns a function that wraps getColor', () => {
const scale = new CategoricalColorScale(['blue', 'red', 'green']);
const colorFn = scale.toFunction();
expect(scale.getColor('pig')).to.equal(colorFn('pig'));
expect(scale.getColor('cat')).to.equal(colorFn('cat'));
});
});
});

View File

@@ -0,0 +1,141 @@
import { it, describe, beforeEach } from 'mocha';
import { expect } from 'chai';
import ColorSchemeManager, {
getInstance,
getScheme,
getAllSchemes,
getDefaultSchemeName,
setDefaultSchemeName,
registerScheme,
registerMultipleSchemes,
} from '../../../src/modules/ColorSchemeManager';
describe('ColorSchemeManager', () => {
beforeEach(() => {
const m = getInstance();
m.clearScheme();
m.registerScheme('test', ['red', 'green', 'blue']);
m.registerScheme('test2', ['orange', 'yellow', 'pink']);
m.setDefaultSchemeName('test');
});
it('The class constructor cannot be accessed directly', () => {
expect(ColorSchemeManager).to.not.be.a('Function');
});
describe('static getInstance()', () => {
it('returns a singleton instance', () => {
const m1 = getInstance();
const m2 = getInstance();
expect(m1).to.not.equal(undefined);
expect(m1).to.equal(m2);
});
});
describe('.getScheme()', () => {
it('.getScheme() returns default color scheme', () => {
const scheme = getInstance().getScheme();
expect(scheme).to.deep.equal(['red', 'green', 'blue']);
});
it('.getScheme(name) returns color scheme with specified name', () => {
const scheme = getInstance().getScheme('test2');
expect(scheme).to.deep.equal(['orange', 'yellow', 'pink']);
});
});
describe('.getAllSchemes()', () => {
it('returns all registered schemes', () => {
const schemes = getInstance().getAllSchemes();
expect(schemes).to.deep.equal({
test: ['red', 'green', 'blue'],
test2: ['orange', 'yellow', 'pink'],
});
});
});
describe('.getDefaultSchemeName()', () => {
it('returns default scheme name', () => {
const name = getInstance().getDefaultSchemeName();
expect(name).to.equal('test');
});
});
describe('.setDefaultSchemeName()', () => {
it('set default scheme name', () => {
getInstance().setDefaultSchemeName('test2');
const name = getInstance().getDefaultSchemeName();
expect(name).to.equal('test2');
getInstance().setDefaultSchemeName('test');
});
it('returns the ColorSchemeManager instance', () => {
const instance = getInstance().setDefaultSchemeName('test');
expect(instance).to.equal(getInstance());
});
});
describe('.registerScheme(name, colors)', () => {
it('sets schemename and color', () => {
getInstance().registerScheme('test3', ['cyan', 'magenta']);
const scheme = getInstance().getScheme('test3');
expect(scheme).to.deep.equal(['cyan', 'magenta']);
});
it('returns the ColorSchemeManager instance', () => {
const instance = getInstance().registerScheme('test3', ['cyan', 'magenta']);
expect(instance).to.equal(getInstance());
});
});
describe('.registerMultipleSchemes(object)', () => {
it('sets multiple schemes at once', () => {
getInstance().registerMultipleSchemes({
test4: ['cyan', 'magenta'],
test5: ['brown', 'purple'],
});
const scheme1 = getInstance().getScheme('test4');
expect(scheme1).to.deep.equal(['cyan', 'magenta']);
const scheme2 = getInstance().getScheme('test5');
expect(scheme2).to.deep.equal(['brown', 'purple']);
});
it('returns the ColorSchemeManager instance', () => {
const instance = getInstance().registerMultipleSchemes({
test4: ['cyan', 'magenta'],
test5: ['brown', 'purple'],
});
expect(instance).to.equal(getInstance());
});
});
describe('static getScheme()', () => {
it('is equivalent to getInstance().getScheme()', () => {
expect(getInstance().getScheme()).to.equal(getScheme());
});
});
describe('static getAllSchemes()', () => {
it('is equivalent to getInstance().getAllSchemes()', () => {
expect(getInstance().getAllSchemes()).to.equal(getAllSchemes());
});
});
describe('static getDefaultSchemeName()', () => {
it('is equivalent to getInstance().getDefaultSchemeName()', () => {
expect(getInstance().getDefaultSchemeName()).to.equal(getDefaultSchemeName());
});
});
describe('static setDefaultSchemeName()', () => {
it('is equivalent to getInstance().setDefaultSchemeName()', () => {
setDefaultSchemeName('test2');
const name = getInstance().getDefaultSchemeName();
expect(name).to.equal('test2');
setDefaultSchemeName('test');
});
});
describe('static registerScheme()', () => {
it('is equivalent to getInstance().registerScheme()', () => {
registerScheme('test3', ['cyan', 'magenta']);
const scheme = getInstance().getScheme('test3');
expect(scheme).to.deep.equal(['cyan', 'magenta']);
});
});
describe('static registerMultipleSchemes()', () => {
it('is equivalent to getInstance().registerMultipleSchemes()', () => {
registerMultipleSchemes({
test4: ['cyan', 'magenta'],
test5: ['brown', 'purple'],
});
const scheme1 = getInstance().getScheme('test4');
expect(scheme1).to.deep.equal(['cyan', 'magenta']);
const scheme2 = getInstance().getScheme('test5');
expect(scheme2).to.deep.equal(['brown', 'purple']);
});
});
});

View File

@@ -1,12 +1,21 @@
import { it, describe } from 'mocha';
import { it, describe, before } from 'mocha';
import { expect } from 'chai';
import { ALL_COLOR_SCHEMES, getColorFromScheme, hexToRGB } from '../../../src/modules/colors';
import { getColorFromScheme, hexToRGB } from '../../../src/modules/colors';
import { getInstance } from '../../../src/modules/ColorSchemeManager';
import airbnb from '../../../src/modules/colorSchemes/airbnb';
import categoricalSchemes from '../../../src/modules/colorSchemes/categorical';
describe('colors', () => {
before(() => {
// Register color schemes
getInstance()
.registerScheme('bnbColors', airbnb.bnbColors)
.registerMultipleSchemes(categoricalSchemes)
.setDefaultSchemeName('bnbColors');
});
it('default to bnbColors', () => {
const color1 = getColorFromScheme('CA');
expect(color1).to.equal(ALL_COLOR_SCHEMES.bnbColors[0]);
expect(airbnb.bnbColors).to.include(color1);
});
it('getColorFromScheme series with same scheme should have the same color', () => {
const color1 = getColorFromScheme('CA', 'bnbColors');
@@ -14,19 +23,18 @@ describe('colors', () => {
const color3 = getColorFromScheme('CA', 'bnbColors');
const color4 = getColorFromScheme('NY', 'bnbColors');
expect(color1).to.equal(ALL_COLOR_SCHEMES.bnbColors[0]);
expect(color2).to.equal(ALL_COLOR_SCHEMES.googleCategory20c[0]);
expect(color1).to.equal(color3);
expect(color4).to.equal(ALL_COLOR_SCHEMES.bnbColors[1]);
expect(color1).to.not.equal(color2);
expect(color1).to.not.equal(color4);
});
it('getColorFromScheme forcing colors persists through calls', () => {
expect(getColorFromScheme('boys', 'bnbColors', 'blue')).to.equal('blue');
expect(getColorFromScheme('boys', 'bnbColors')).to.equal('blue');
expect(getColorFromScheme('boys', 'googleCategory20c')).to.equal('blue');
expect(getColorFromScheme('boys', 'googleCategory20c')).to.not.equal('blue');
expect(getColorFromScheme('girls', 'bnbColors', 'pink')).to.equal('pink');
expect(getColorFromScheme('girls', 'bnbColors')).to.equal('pink');
expect(getColorFromScheme('girls', 'googleCategory20c')).to.equal('pink');
expect(getColorFromScheme('girls', 'googleCategory20c')).to.not.equal('pink');
});
it('getColorFromScheme is not case sensitive', () => {
const c1 = getColorFromScheme('girls', 'bnbColors');

View File

@@ -1,5 +1,10 @@
/* eslint-disable global-require */
import $ from 'jquery';
import airbnb from './modules/colorSchemes/airbnb';
import categoricalSchemes from './modules/colorSchemes/categorical';
import lyft from './modules/colorSchemes/lyft';
import { getInstance } from './modules/ColorSchemeManager';
// Everything imported in this file ends up in the common entry file
// be mindful of double-imports
@@ -25,8 +30,15 @@ $(document).ready(function () {
});
});
// Register color schemes
getInstance()
.registerScheme('bnbColors', airbnb.bnbColors)
.registerMultipleSchemes(categoricalSchemes)
.registerScheme('lyftColors', lyft.lyftColors)
.setDefaultSchemeName('bnbColors');
export function appSetup() {
// A set of hacks to allow apps to run within a FAB template
// A set of hacks to allow apps to run within a FAB template
// this allows for the server side generated menus to function
window.$ = $;
window.jQuery = $;

View File

@@ -5,7 +5,6 @@ import { chart } from '../../chart/chartReducer';
import { initSliceEntities } from './sliceEntities';
import { getParam } from '../../modules/utils';
import { applyDefaultFormData } from '../../explore/store';
import { getColorFromScheme } from '../../modules/colors';
import findFirstParentContainerId from '../util/findFirstParentContainer';
import getEmptyLayout from '../util/getEmptyLayout';
import newComponentFactory from '../util/newComponentFactory';
@@ -19,6 +18,7 @@ import {
CHART_TYPE,
ROW_TYPE,
} from '../util/componentTypes';
import { getScale } from '../../modules/CategoricalColorNamespace';
export default function(bootstrapData) {
const { user_id, datasources, common, editMode } = bootstrapData;
@@ -41,7 +41,7 @@ export default function(bootstrapData) {
if (dashboard.metadata && dashboard.metadata.label_colors) {
const colorMap = dashboard.metadata.label_colors;
Object.keys(colorMap).forEach(label => {
getColorFromScheme(label, null, colorMap[label]);
getScale().setColor(label, colorMap[label]);
});
}

View File

@@ -20,13 +20,13 @@ import AnnotationTypes, {
requiresQuery,
} from '../../../modules/AnnotationTypes';
import { ALL_COLOR_SCHEMES } from '../../../modules/colors';
import PopoverSection from '../../../components/PopoverSection';
import ControlHeader from '../ControlHeader';
import { nonEmpty } from '../../validators';
import vizTypes from '../../visTypes';
import { t } from '../../../locales';
import { getScheme } from '../../../modules/ColorSchemeManager';
const AUTOMATIC_COLOR = '';
@@ -276,7 +276,7 @@ export default class AnnotationLayer extends React.PureComponent {
description = t('Select the Annotation Layer you would like to use.');
} else {
label = t('Chart');
description = `Use a pre defined Superset Chart as a source for annotations and overlays.
description = `Use a pre defined Superset Chart as a source for annotations and overlays.
'your chart must be one of these visualization types:
'[${getSupportedSourceTypes(annotationType)
.map(x => vizTypes[x].label).join(', ')}]'`;
@@ -478,7 +478,7 @@ export default class AnnotationLayer extends React.PureComponent {
renderDisplayConfiguration() {
const { color, opacity, style, width, showMarkers, hideLine, annotationType } = this.state;
const colorScheme = [...ALL_COLOR_SCHEMES[this.props.colorScheme]];
const colorScheme = [...getScheme(this.props.colorScheme)];
if (color && color !== AUTOMATIC_COLOR &&
!colorScheme.find(x => x.toLowerCase() === color.toLowerCase())) {
colorScheme.push(color);

View File

@@ -45,11 +45,15 @@ import {
mainMetric,
} from '../modules/utils';
import * as v from './validators';
import { colorPrimary, ALL_COLOR_SCHEMES, spectrums } from '../modules/colors';
import { colorPrimary } from '../modules/colors';
import { defaultViewport } from '../modules/geo';
import ColumnOption from '../components/ColumnOption';
import OptionDescription from '../components/OptionDescription';
import { t } from '../locales';
import { getAllSchemes } from '../modules/ColorSchemeManager';
import sequentialSchemes from '../modules/colorSchemes/sequential';
const ALL_COLOR_SCHEMES = getAllSchemes();
const D3_FORMAT_DOCS = 'D3 format syntax: https://github.com/d3/d3-format';
@@ -371,7 +375,7 @@ export const controls = {
clearable: false,
description: '',
renderTrigger: true,
schemes: spectrums,
schemes: sequentialSchemes,
isLinear: true,
},

View File

@@ -0,0 +1,60 @@
import CategoricalColorScale from './CategoricalColorScale';
import { getScheme, getDefaultSchemeName } from './ColorSchemeManager';
class CategoricalColorNamespace {
constructor(name) {
this.name = name;
this.scales = {};
this.forcedItems = {};
}
getScale(schemeName) {
const name = schemeName || getDefaultSchemeName();
const scale = this.scales[name];
if (scale) {
return scale;
}
const newScale = new CategoricalColorScale(
getScheme(name),
this.forcedItems,
);
this.scales[name] = newScale;
return newScale;
}
/**
* Enforce specific color for given value
* This will apply across all color scales
* in this namespace.
* @param {*} value value
* @param {*} forcedColor color
*/
setColor(value, forcedColor) {
this.forcedItems[value] = forcedColor;
return this;
}
}
const namespaces = {};
export const DEFAULT_NAMESPACE = 'GLOBAL';
export function getNamespace(name = DEFAULT_NAMESPACE) {
const instance = namespaces[name];
if (instance) {
return instance;
}
const newInstance = new CategoricalColorNamespace(name);
namespaces[name] = newInstance;
return newInstance;
}
export function getColor(value, scheme, namespace) {
return getNamespace(namespace)
.getScale(scheme)
.getColor(value);
}
export function getScale(scheme, namespace) {
return getNamespace(namespace)
.getScale(scheme);
}

View File

@@ -0,0 +1,64 @@
import { TIME_SHIFT_PATTERN } from '../utils/common';
export function cleanValue(value) {
// for superset series that should have the same color
return String(value).trim()
.toLowerCase()
.split(', ')
.filter(k => !TIME_SHIFT_PATTERN.test(k))
.join(', ');
}
export default class CategoricalColorScale {
/**
* Constructor
* @param {*} colors an array of colors
* @param {*} parentForcedColors optional parameter that comes from parent
* (usually CategoricalColorNamespace) and supersede this.forcedColors
*/
constructor(colors, parentForcedColors) {
this.colors = colors;
this.parentForcedColors = parentForcedColors;
this.forcedColors = {};
this.seen = {};
this.fn = value => this.getColor(value);
}
getColor(value) {
const cleanedValue = cleanValue(value);
const parentColor = this.parentForcedColors && this.parentForcedColors[cleanedValue];
if (parentColor) {
return parentColor;
}
const forcedColor = this.forcedColors[cleanedValue];
if (forcedColor) {
return forcedColor;
}
const seenColor = this.seen[cleanedValue];
const length = this.colors.length;
if (seenColor !== undefined) {
return this.colors[seenColor % length];
}
const index = Object.keys(this.seen).length;
this.seen[cleanedValue] = index;
return this.colors[index % length];
}
/**
* Enforce specific color for given value
* @param {*} value value
* @param {*} forcedColor forcedColor
*/
setColor(value, forcedColor) {
this.forcedColors[value] = forcedColor;
return this;
}
toFunction() {
return this.fn;
}
}

View File

@@ -0,0 +1,86 @@
class ColorSchemeManager {
constructor() {
this.schemes = {};
this.defaultSchemeName = undefined;
}
clearScheme() {
this.schemes = {};
return this;
}
getScheme(schemeName) {
return this.schemes[schemeName || this.defaultSchemeName];
}
getAllSchemes() {
return this.schemes;
}
getDefaultSchemeName() {
return this.defaultSchemeName;
}
setDefaultSchemeName(schemeName) {
this.defaultSchemeName = schemeName;
return this;
}
registerScheme(schemeName, colors) {
this.schemes[schemeName] = colors;
// If there is no default, set as default
if (!this.defaultSchemeName) {
this.defaultSchemeName = schemeName;
}
return this;
}
registerMultipleSchemes(multipleSchemes) {
Object.assign(this.schemes, multipleSchemes);
// If there is no default, set the first scheme as default
const keys = Object.keys(multipleSchemes);
if (!this.defaultSchemeName && keys.length > 0) {
this.defaultSchemeName = keys[0];
}
return this;
}
}
let singleton;
export function getInstance() {
if (!singleton) {
singleton = new ColorSchemeManager();
}
return singleton;
}
const staticFunctions = Object.getOwnPropertyNames(ColorSchemeManager.prototype)
.filter(fn => fn !== 'constructor')
.reduce((all, fn) => {
const functions = all;
functions[fn] = function (...args) {
return getInstance()[fn](...args);
};
return functions;
}, { getInstance });
const {
clearScheme,
getScheme,
getAllSchemes,
getDefaultSchemeName,
setDefaultSchemeName,
registerScheme,
registerMultipleSchemes,
} = staticFunctions;
export {
clearScheme,
getScheme,
getAllSchemes,
getDefaultSchemeName,
setDefaultSchemeName,
registerScheme,
registerMultipleSchemes,
};

View File

@@ -0,0 +1,25 @@
export default {
bnbColors: [
'#ff5a5f', // rausch
'#7b0051', // hackb
'#007A87', // kazan
'#00d1c1', // babu
'#8ce071', // lima
'#ffb400', // beach
'#b4a76c', // barol
'#ff8083',
'#cc0086',
'#00a1b3',
'#00ffeb',
'#bbedab',
'#ffd266',
'#cbc29a',
'#ff3339',
'#ff1ab1',
'#005c66',
'#00b3a5',
'#55d12e',
'#b37e00',
'#988b4e',
],
};

View File

@@ -0,0 +1,42 @@
import d3 from 'd3';
export default {
d3Category10: d3.scale.category10().range(),
d3Category20: d3.scale.category20().range(),
d3Category20b: d3.scale.category20b().range(),
d3Category20c: d3.scale.category20c().range(),
googleCategory10c: [
'#3366cc',
'#dc3912',
'#ff9900',
'#109618',
'#990099',
'#0099c6',
'#dd4477',
'#66aa00',
'#b82e2e',
'#316395',
],
googleCategory20c: [
'#3366cc',
'#dc3912',
'#ff9900',
'#109618',
'#990099',
'#0099c6',
'#dd4477',
'#66aa00',
'#b82e2e',
'#316395',
'#994499',
'#22aa99',
'#aaaa11',
'#6633cc',
'#e67300',
'#8b0707',
'#651067',
'#329262',
'#5574a6',
'#3b3eac',
],
};

View File

@@ -0,0 +1,14 @@
export default {
lyftColors: [
'#EA0B8C',
'#6C838E',
'#29ABE2',
'#33D9C1',
'#9DACB9',
'#7560AA',
'#2D5584',
'#831C4A',
'#333D47',
'#AC2077',
],
};

View File

@@ -0,0 +1,433 @@
export default {
blue_white_yellow: [
'#00d1c1',
'white',
'#ffb400',
],
fire: [
'white',
'yellow',
'red',
'black',
],
white_black: [
'white',
'black',
],
black_white: [
'black',
'white',
],
dark_blue: [
'#EBF5F8',
'#6BB1CC',
'#357E9B',
'#1B4150',
'#092935',
],
pink_grey: [
'#E70B81',
'#FAFAFA',
'#666666',
],
greens: [
'#ffffcc',
'#78c679',
'#006837',
],
purples: [
'#f2f0f7',
'#9e9ac8',
'#54278f',
],
oranges: [
'#fef0d9',
'#fc8d59',
'#b30000',
],
red_yellow_blue: [
'#d7191c',
'#fdae61',
'#ffffbf',
'#abd9e9',
'#2c7bb6',
],
brown_white_green: [
'#a6611a',
'#dfc27d',
'#f5f5f5',
'#80cdc1',
'#018571',
],
purple_white_green: [
'#7b3294',
'#c2a5cf',
'#f7f7f7',
'#a6dba0',
'#008837',
],
schemeBrBG: [
'#543005',
'#8c510a',
'#bf812d',
'#dfc27d',
'#f6e8c3',
'#c7eae5',
'#80cdc1',
'#35978f',
'#01665e',
'#003c30',
],
schemePRGn: [
'#40004b',
'#762a83',
'#9970ab',
'#c2a5cf',
'#e7d4e8',
'#d9f0d3',
'#a6dba0',
'#5aae61',
'#1b7837',
'#00441b',
],
schemePiYG: [
'#8e0152',
'#c51b7d',
'#de77ae',
'#f1b6da',
'#fde0ef',
'#e6f5d0',
'#b8e186',
'#7fbc41',
'#4d9221',
'#276419',
],
schemePuOr: [
'#2d004b',
'#542788',
'#8073ac',
'#b2abd2',
'#d8daeb',
'#fee0b6',
'#fdb863',
'#e08214',
'#b35806',
'#7f3b08',
],
schemeRdBu: [
'#67001f',
'#b2182b',
'#d6604d',
'#f4a582',
'#fddbc7',
'#d1e5f0',
'#92c5de',
'#4393c3',
'#2166ac',
'#053061',
],
schemeRdGy: [
'#67001f',
'#b2182b',
'#d6604d',
'#f4a582',
'#fddbc7',
'#e0e0e0',
'#bababa',
'#878787',
'#4d4d4d',
'#1a1a1a',
],
schemeRdYlBu: [
'#a50026',
'#d73027',
'#f46d43',
'#fdae61',
'#fee090',
'#e0f3f8',
'#abd9e9',
'#74add1',
'#4575b4',
'#313695',
],
schemeRdYlGn: [
'#a50026',
'#d73027',
'#f46d43',
'#fdae61',
'#fee08b',
'#d9ef8b',
'#a6d96a',
'#66bd63',
'#1a9850',
'#006837',
],
schemeSpectral: [
'#9e0142',
'#d53e4f',
'#f46d43',
'#fdae61',
'#fee08b',
'#e6f598',
'#abdda4',
'#66c2a5',
'#3288bd',
'#5e4fa2',
],
schemeBlues: [
'#b5d4e9',
'#93c3df',
'#6daed5',
'#4b97c9',
'#2f7ebc',
'#1864aa',
'#0a4a90',
'#08306b',
],
schemeGreens: [
'#b7e2b1',
'#97d494',
'#73c378',
'#4daf62',
'#2f984f',
'#157f3b',
'#036429',
'#00441b',
],
schemeGrays: [
'#cecece',
'#b4b4b4',
'#979797',
'#7a7a7a',
'#5f5f5f',
'#404040',
'#1e1e1e',
'#000000',
],
schemeOranges: [
'#fdc28c',
'#fda762',
'#fb8d3d',
'#f2701d',
'#e25609',
'#c44103',
'#9f3303',
'#7f2704',
],
schemePurples: [
'#cecee5',
'#b6b5d8',
'#9e9bc9',
'#8782bc',
'#7363ac',
'#61409b',
'#501f8c',
'#3f007d',
],
schemeReds: [
'#fcaa8e',
'#fc8a6b',
'#f9694c',
'#ef4533',
'#d92723',
'#bb151a',
'#970b13',
'#67000d',
],
schemeViridis: [
'#482475',
'#414487',
'#355f8d',
'#2a788e',
'#21918c',
'#22a884',
'#44bf70',
'#7ad151',
'#bddf26',
'#fde725',
],
schemeInferno: [
'#160b39',
'#420a68',
'#6a176e',
'#932667',
'#bc3754',
'#dd513a',
'#f37819',
'#fca50a',
'#f6d746',
'#fcffa4',
],
schemeMagma: [
'#140e36',
'#3b0f70',
'#641a80',
'#8c2981',
'#b73779',
'#de4968',
'#f7705c',
'#fe9f6d',
'#fecf92',
'#fcfdbf',
],
schemeWarm: [
'#963db3',
'#bf3caf',
'#e4419d',
'#fe4b83',
'#ff5e63',
'#ff7847',
'#fb9633',
'#e2b72f',
'#c6d63c',
'#aff05b',
],
schemeCool: [
'#6054c8',
'#4c6edb',
'#368ce1',
'#23abd8',
'#1ac7c2',
'#1ddfa3',
'#30ef82',
'#52f667',
'#7ff658',
'#aff05b',
],
schemeCubehelixDefault: [
'#1a1530',
'#163d4e',
'#1f6642',
'#54792f',
'#a07949',
'#d07e93',
'#cf9cda',
'#c1caf3',
'#d2eeef',
'#ffffff',
],
schemeBuGn: [
'#b7e4da',
'#8fd3c1',
'#68c2a3',
'#49b17f',
'#2f9959',
'#157f3c',
'#036429',
'#00441b',
],
schemeBuPu: [
'#b2cae1',
'#9cb3d5',
'#8f95c6',
'#8c74b5',
'#8952a5',
'#852d8f',
'#730f71',
'#4d004b',
],
schemeGnBu: [
'#bde5bf',
'#9ed9bb',
'#7bcbc4',
'#58b7cd',
'#399cc6',
'#1d7eb7',
'#0b60a1',
'#084081',
],
schemeOrRd: [
'#fdca94',
'#fdb07a',
'#fa8e5d',
'#f16c49',
'#e04530',
'#c81d13',
'#a70403',
'#7f0000',
],
schemePuBuGn: [
'#bec9e2',
'#98b9d9',
'#69a8cf',
'#4096c0',
'#19879f',
'#037877',
'#016353',
'#014636',
],
schemePuBu: [
'#bfc9e2',
'#9bb9d9',
'#72a8cf',
'#4394c3',
'#1a7db6',
'#0667a1',
'#045281',
'#023858',
],
schemePuRd: [
'#d0aad2',
'#d08ac2',
'#dd63ae',
'#e33890',
'#d71c6c',
'#b70b4f',
'#8f023a',
'#67001f',
],
schemeRdPu: [
'#fbb5bc',
'#f993b0',
'#f369a3',
'#e03e98',
'#c01788',
'#99037c',
'#700174',
'#49006a',
],
schemeYlGnBu: [
'#d5eeb3',
'#a9ddb7',
'#73c9bd',
'#45b4c2',
'#2897bf',
'#2073b2',
'#234ea0',
'#1c3185',
'#081d58',
],
schemeYlGn: [
'#e4f4ac',
'#c7e89b',
'#a2d88a',
'#78c578',
'#4eaf63',
'#2f944e',
'#15793f',
'#036034',
'#004529',
],
schemeYlOrBr: [
'#feeaa1',
'#fed676',
'#feba4a',
'#fb992c',
'#ee7918',
'#d85b0a',
'#b74304',
'#8f3204',
'#662506',
],
schemeYlOrRd: [
'#fee087',
'#fec965',
'#feab4b',
'#fd893c',
'#fa5c2e',
'#ec3023',
'#d31121',
'#af0225',
'#800026',
],
};

View File

@@ -1,529 +1,13 @@
import d3 from 'd3';
import { TIME_SHIFT_PATTERN } from '../utils/common';
import { getScale } from './CategoricalColorNamespace';
import sequentialSchemes from './colorSchemes/sequential';
import airbnb from './colorSchemes/airbnb';
import lyft from './colorSchemes/lyft';
export const brandColor = '#00A699';
export const colorPrimary = { r: 0, g: 122, b: 135, a: 1 };
// Color related utility functions go in this object
export const bnbColors = [
'#ff5a5f', // rausch
'#7b0051', // hackb
'#007A87', // kazan
'#00d1c1', // babu
'#8ce071', // lima
'#ffb400', // beach
'#b4a76c', // barol
'#ff8083',
'#cc0086',
'#00a1b3',
'#00ffeb',
'#bbedab',
'#ffd266',
'#cbc29a',
'#ff3339',
'#ff1ab1',
'#005c66',
'#00b3a5',
'#55d12e',
'#b37e00',
'#988b4e',
];
export const lyftColors = [
'#EA0B8C',
'#6C838E',
'#29ABE2',
'#33D9C1',
'#9DACB9',
'#7560AA',
'#2D5584',
'#831C4A',
'#333D47',
'#AC2077',
];
const d3Category10 = d3.scale.category10().range();
const d3Category20 = d3.scale.category20().range();
const d3Category20b = d3.scale.category20b().range();
const d3Category20c = d3.scale.category20c().range();
const googleCategory10c = [
'#3366cc',
'#dc3912',
'#ff9900',
'#109618',
'#990099',
'#0099c6',
'#dd4477',
'#66aa00',
'#b82e2e',
'#316395',
];
const googleCategory20c = [
'#3366cc',
'#dc3912',
'#ff9900',
'#109618',
'#990099',
'#0099c6',
'#dd4477',
'#66aa00',
'#b82e2e',
'#316395',
'#994499',
'#22aa99',
'#aaaa11',
'#6633cc',
'#e67300',
'#8b0707',
'#651067',
'#329262',
'#5574a6',
'#3b3eac',
];
export const ALL_COLOR_SCHEMES = {
bnbColors,
d3Category10,
d3Category20,
d3Category20b,
d3Category20c,
googleCategory10c,
googleCategory20c,
lyftColors,
};
export const spectrums = {
blue_white_yellow: [
'#00d1c1',
'white',
'#ffb400',
],
fire: [
'white',
'yellow',
'red',
'black',
],
white_black: [
'white',
'black',
],
black_white: [
'black',
'white',
],
dark_blue: [
'#EBF5F8',
'#6BB1CC',
'#357E9B',
'#1B4150',
'#092935',
],
pink_grey: [
'#E70B81',
'#FAFAFA',
'#666666',
],
greens: [
'#ffffcc',
'#78c679',
'#006837',
],
purples: [
'#f2f0f7',
'#9e9ac8',
'#54278f',
],
oranges: [
'#fef0d9',
'#fc8d59',
'#b30000',
],
red_yellow_blue: [
'#d7191c',
'#fdae61',
'#ffffbf',
'#abd9e9',
'#2c7bb6',
],
brown_white_green: [
'#a6611a',
'#dfc27d',
'#f5f5f5',
'#80cdc1',
'#018571',
],
purple_white_green: [
'#7b3294',
'#c2a5cf',
'#f7f7f7',
'#a6dba0',
'#008837',
],
schemeBrBG: [
'#543005',
'#8c510a',
'#bf812d',
'#dfc27d',
'#f6e8c3',
'#c7eae5',
'#80cdc1',
'#35978f',
'#01665e',
'#003c30',
],
schemePRGn: [
'#40004b',
'#762a83',
'#9970ab',
'#c2a5cf',
'#e7d4e8',
'#d9f0d3',
'#a6dba0',
'#5aae61',
'#1b7837',
'#00441b',
],
schemePiYG: [
'#8e0152',
'#c51b7d',
'#de77ae',
'#f1b6da',
'#fde0ef',
'#e6f5d0',
'#b8e186',
'#7fbc41',
'#4d9221',
'#276419',
],
schemePuOr: [
'#2d004b',
'#542788',
'#8073ac',
'#b2abd2',
'#d8daeb',
'#fee0b6',
'#fdb863',
'#e08214',
'#b35806',
'#7f3b08',
],
schemeRdBu: [
'#67001f',
'#b2182b',
'#d6604d',
'#f4a582',
'#fddbc7',
'#d1e5f0',
'#92c5de',
'#4393c3',
'#2166ac',
'#053061',
],
schemeRdGy: [
'#67001f',
'#b2182b',
'#d6604d',
'#f4a582',
'#fddbc7',
'#e0e0e0',
'#bababa',
'#878787',
'#4d4d4d',
'#1a1a1a',
],
schemeRdYlBu: [
'#a50026',
'#d73027',
'#f46d43',
'#fdae61',
'#fee090',
'#e0f3f8',
'#abd9e9',
'#74add1',
'#4575b4',
'#313695',
],
schemeRdYlGn: [
'#a50026',
'#d73027',
'#f46d43',
'#fdae61',
'#fee08b',
'#d9ef8b',
'#a6d96a',
'#66bd63',
'#1a9850',
'#006837',
],
schemeSpectral: [
'#9e0142',
'#d53e4f',
'#f46d43',
'#fdae61',
'#fee08b',
'#e6f598',
'#abdda4',
'#66c2a5',
'#3288bd',
'#5e4fa2',
],
schemeBlues: [
'#b5d4e9',
'#93c3df',
'#6daed5',
'#4b97c9',
'#2f7ebc',
'#1864aa',
'#0a4a90',
'#08306b',
],
schemeGreens: [
'#b7e2b1',
'#97d494',
'#73c378',
'#4daf62',
'#2f984f',
'#157f3b',
'#036429',
'#00441b',
],
schemeGrays: [
'#cecece',
'#b4b4b4',
'#979797',
'#7a7a7a',
'#5f5f5f',
'#404040',
'#1e1e1e',
'#000000',
],
schemeOranges: [
'#fdc28c',
'#fda762',
'#fb8d3d',
'#f2701d',
'#e25609',
'#c44103',
'#9f3303',
'#7f2704',
],
schemePurples: [
'#cecee5',
'#b6b5d8',
'#9e9bc9',
'#8782bc',
'#7363ac',
'#61409b',
'#501f8c',
'#3f007d',
],
schemeReds: [
'#fcaa8e',
'#fc8a6b',
'#f9694c',
'#ef4533',
'#d92723',
'#bb151a',
'#970b13',
'#67000d',
],
schemeViridis: [
'#482475',
'#414487',
'#355f8d',
'#2a788e',
'#21918c',
'#22a884',
'#44bf70',
'#7ad151',
'#bddf26',
'#fde725',
],
schemeInferno: [
'#160b39',
'#420a68',
'#6a176e',
'#932667',
'#bc3754',
'#dd513a',
'#f37819',
'#fca50a',
'#f6d746',
'#fcffa4',
],
schemeMagma: [
'#140e36',
'#3b0f70',
'#641a80',
'#8c2981',
'#b73779',
'#de4968',
'#f7705c',
'#fe9f6d',
'#fecf92',
'#fcfdbf',
],
schemeWarm: [
'#963db3',
'#bf3caf',
'#e4419d',
'#fe4b83',
'#ff5e63',
'#ff7847',
'#fb9633',
'#e2b72f',
'#c6d63c',
'#aff05b',
],
schemeCool: [
'#6054c8',
'#4c6edb',
'#368ce1',
'#23abd8',
'#1ac7c2',
'#1ddfa3',
'#30ef82',
'#52f667',
'#7ff658',
'#aff05b',
],
schemeCubehelixDefault: [
'#1a1530',
'#163d4e',
'#1f6642',
'#54792f',
'#a07949',
'#d07e93',
'#cf9cda',
'#c1caf3',
'#d2eeef',
'#ffffff',
],
schemeBuGn: [
'#b7e4da',
'#8fd3c1',
'#68c2a3',
'#49b17f',
'#2f9959',
'#157f3c',
'#036429',
'#00441b',
],
schemeBuPu: [
'#b2cae1',
'#9cb3d5',
'#8f95c6',
'#8c74b5',
'#8952a5',
'#852d8f',
'#730f71',
'#4d004b',
],
schemeGnBu: [
'#bde5bf',
'#9ed9bb',
'#7bcbc4',
'#58b7cd',
'#399cc6',
'#1d7eb7',
'#0b60a1',
'#084081',
],
schemeOrRd: [
'#fdca94',
'#fdb07a',
'#fa8e5d',
'#f16c49',
'#e04530',
'#c81d13',
'#a70403',
'#7f0000',
],
schemePuBuGn: [
'#bec9e2',
'#98b9d9',
'#69a8cf',
'#4096c0',
'#19879f',
'#037877',
'#016353',
'#014636',
],
schemePuBu: [
'#bfc9e2',
'#9bb9d9',
'#72a8cf',
'#4394c3',
'#1a7db6',
'#0667a1',
'#045281',
'#023858',
],
schemePuRd: [
'#d0aad2',
'#d08ac2',
'#dd63ae',
'#e33890',
'#d71c6c',
'#b70b4f',
'#8f023a',
'#67001f',
],
schemeRdPu: [
'#fbb5bc',
'#f993b0',
'#f369a3',
'#e03e98',
'#c01788',
'#99037c',
'#700174',
'#49006a',
],
schemeYlGnBu: [
'#d5eeb3',
'#a9ddb7',
'#73c9bd',
'#45b4c2',
'#2897bf',
'#2073b2',
'#234ea0',
'#1c3185',
'#081d58',
],
schemeYlGn: [
'#e4f4ac',
'#c7e89b',
'#a2d88a',
'#78c578',
'#4eaf63',
'#2f944e',
'#15793f',
'#036034',
'#004529',
],
schemeYlOrBr: [
'#feeaa1',
'#fed676',
'#feba4a',
'#fb992c',
'#ee7918',
'#d85b0a',
'#b74304',
'#8f3204',
'#662506',
],
schemeYlOrRd: [
'#fee087',
'#fec965',
'#feab4b',
'#fd893c',
'#fa5c2e',
'#ec3023',
'#d31121',
'#af0225',
'#800026',
],
};
export const bnbColors = airbnb.bnbColors;
export const lyftColors = lyft.lyftColors;
export function hexToRGB(hex, alpha = 255) {
if (!hex) {
@@ -546,41 +30,20 @@ export function hexToRGB(hex, alpha = 255) {
* @param {string} forcedColor - A color that the caller wants to
forcibly associate to a label.
*/
export const getColorFromScheme = (function () {
const seen = {};
const forcedColors = {};
return function (s, scheme, forcedColor) {
if (!s) {
return;
}
const selectedScheme = scheme ? ALL_COLOR_SCHEMES[scheme] : ALL_COLOR_SCHEMES.bnbColors;
let stringifyS = String(s).toLowerCase();
// next line is for superset series that should have the same color
stringifyS = stringifyS.split(', ').filter(k => !TIME_SHIFT_PATTERN.test(k)).join(', ');
if (forcedColor && !forcedColors[stringifyS]) {
forcedColors[stringifyS] = forcedColor;
}
if (forcedColors[stringifyS]) {
return forcedColors[stringifyS];
}
if (seen[selectedScheme] === undefined) {
seen[selectedScheme] = {};
}
if (seen[selectedScheme][stringifyS] === undefined) {
seen[selectedScheme][stringifyS] = Object.keys(seen[selectedScheme]).length;
}
/* eslint consistent-return: 0 */
return selectedScheme[seen[selectedScheme][stringifyS] % selectedScheme.length];
};
}());
export function getColorFromScheme(value, schemeName, forcedColor) {
const scale = getScale(schemeName);
if (forcedColor) {
scale.setColor(value, forcedColor);
return forcedColor;
}
return scale.getColor(value);
}
export const colorScalerFactory = function (colors, data, accessor, extents, outputRGBA = false) {
// Returns a linear scaler our of an array of color
if (!Array.isArray(colors)) {
/* eslint no-param-reassign: 0 */
colors = spectrums[colors];
colors = sequentialSchemes[colors];
}
let ext = [0, 1];
if (extents) {

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-param-reassign */
import d3 from 'd3';
import PropTypes from 'prop-types';
import { getColorFromScheme } from '../modules/colors';
import { getScale } from '../modules/CategoricalColorNamespace';
import './chord.css';
const propTypes = {
@@ -31,6 +31,7 @@ function chordVis(element, props) {
const div = d3.select(element);
const { nodes, matrix } = data;
const f = d3.format(numberFormat);
const colorFn = getScale(colorScheme).toFunction();
const outerRadius = Math.min(width, height) / 2 - 10;
const innerRadius = outerRadius - 24;
@@ -78,7 +79,7 @@ function chordVis(element, props) {
const groupPath = group.append('path')
.attr('id', (d, i) => 'group' + i)
.attr('d', arc)
.style('fill', (d, i) => getColorFromScheme(nodes[i], colorScheme));
.style('fill', (d, i) => colorFn(nodes[i]));
// Add a text label.
const groupText = group.append('text')
@@ -102,7 +103,7 @@ function chordVis(element, props) {
.on('mouseover', (d) => {
chord.classed('fade', p => p !== d);
})
.style('fill', d => getColorFromScheme(nodes[d.source.index], colorScheme))
.style('fill', d => colorFn(nodes[d.source.index]))
.attr('d', path);
// Add an elaborate mouseover title for each chord.

View File

@@ -6,19 +6,21 @@ import PropTypes from 'prop-types';
import AnimatableDeckGLContainer from './AnimatableDeckGLContainer';
import Legend from '../Legend';
import { getColorFromScheme, hexToRGB } from '../../modules/colors';
import { getScale } from '../../modules/CategoricalColorNamespace';
import { hexToRGB } from '../../modules/colors';
import { getPlaySliderParams } from '../../modules/time';
import sandboxedEval from '../../modules/sandbox';
function getCategories(fd, data) {
const c = fd.color_picker || { r: 0, g: 0, b: 0, a: 1 };
const fixedColor = [c.r, c.g, c.b, 255 * c.a];
const colorFn = getScale(fd.color_scheme).toFunction();
const categories = {};
data.forEach((d) => {
if (d.cat_color != null && !categories.hasOwnProperty(d.cat_color)) {
let color;
if (fd.dimension) {
color = hexToRGB(getColorFromScheme(d.cat_color, fd.color_scheme), c.a * 255);
color = hexToRGB(colorFn(d.cat_color), c.a * 255);
} else {
color = fixedColor;
}
@@ -98,10 +100,11 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
}
addColor(data, fd) {
const c = fd.color_picker || { r: 0, g: 0, b: 0, a: 1 };
const colorFn = getScale(fd.color_scheme).toFunction();
return data.map((d) => {
let color;
if (fd.dimension) {
color = hexToRGB(getColorFromScheme(d.cat_color, fd.color_scheme), c.a * 255);
color = hexToRGB(colorFn(d.cat_color), c.a * 255);
return { ...d, color };
}
return d;

View File

@@ -2,8 +2,8 @@
import d3 from 'd3';
import PropTypes from 'prop-types';
import { hierarchy } from 'd3-hierarchy';
import { getScale } from '../modules/CategoricalColorNamespace';
import { d3TimeFormatPreset } from '../modules/utils';
import { getColorFromScheme } from '../modules/colors';
import './partition.css';
// Compute dx, dy, x, y for each node and
@@ -97,6 +97,7 @@ function Icicle(element, props) {
const hasTime = ['adv_anal', 'time_series'].indexOf(chartType) >= 0;
const format = d3.format(numberFormat);
const timeFormat = d3TimeFormatPreset(dateTimeFormat);
const colorFn = getScale(colorScheme).toFunction();
div.selectAll('*').remove();
const tooltip = div
@@ -363,7 +364,7 @@ function Icicle(element, props) {
// Apply color scheme
g.selectAll('rect')
.style('fill', (d) => {
d.color = getColorFromScheme(d.name, colorScheme);
d.color = colorFn(d.name);
return d.color;
});
}

View File

@@ -2,8 +2,8 @@
import d3 from 'd3';
import PropTypes from 'prop-types';
import nv from 'nvd3';
import { getScale } from '../modules/CategoricalColorNamespace';
import { d3TimeFormatPreset } from '../modules/utils';
import { getColorFromScheme } from '../modules/colors';
import './rose.css';
const propTypes = {
@@ -62,6 +62,7 @@ function Rose(element, props) {
const numGroups = datum[times[0]].length;
const format = d3.format(numberFormat);
const timeFormat = d3TimeFormatPreset(dateTimeFormat);
const colorFn = getScale(colorScheme).toFunction();
d3.select('.nvtooltip').remove();
div.selectAll('*').remove();
@@ -70,7 +71,6 @@ function Rose(element, props) {
const legend = nv.models.legend();
const tooltip = nv.models.tooltip();
const state = { disabled: datum[times[0]].map(() => false) };
const color = name => getColorFromScheme(name, colorScheme);
const svg = div
.append('svg')
@@ -101,9 +101,9 @@ function Rose(element, props) {
.map(v => ({
key: v.name,
value: v.value,
color: color(v.name),
color: colorFn(v.name),
highlight: v.id === d.arcId,
})) : [{ key: d.name, value: d.val, color: color(d.name) }];
})) : [{ key: d.name, value: d.val, color: colorFn(d.name) }];
return {
key: 'Date',
value: d.time,
@@ -113,7 +113,7 @@ function Rose(element, props) {
legend
.width(width)
.color(d => getColorFromScheme(d.key, colorScheme));
.color(d => colorFn(d.key));
legendWrap
.datum(legendData(datum))
.call(legend);
@@ -331,7 +331,7 @@ function Rose(element, props) {
const arcs = ae
.append('path')
.attr('class', 'arc')
.attr('fill', d => color(d.name))
.attr('fill', d => colorFn(d.name))
.attr('d', arc);
function mousemove() {

View File

@@ -2,7 +2,7 @@
import d3 from 'd3';
import PropTypes from 'prop-types';
import { sankey as d3Sankey } from 'd3-sankey';
import { getColorFromScheme } from '../modules/colors';
import { getScale } from '../modules/CategoricalColorNamespace';
import './sankey.css';
const propTypes = {
@@ -49,6 +49,8 @@ function Sankey(element, props) {
.attr('class', 'sankey-tooltip')
.style('opacity', 0);
const colorFn = getScale(colorScheme).toFunction();
const sankey = d3Sankey()
.nodeWidth(15)
.nodePadding(10)
@@ -153,7 +155,7 @@ function Sankey(element, props) {
.attr('width', sankey.nodeWidth())
.style('fill', function (d) {
const name = d.name || 'N/A';
d.color = getColorFromScheme(name.replace(/ .*/, ''), colorScheme);
d.color = colorFn(name.replace(/ .*/, ''));
return d.color;
})
.style('stroke', d => d3.rgb(d.color).darker(2))

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-param-reassign */
import d3 from 'd3';
import PropTypes from 'prop-types';
import { getColorFromScheme } from '../modules/colors';
import { getScale } from '../modules/CategoricalColorNamespace';
import { wrapSvgText } from '../modules/utils';
import './sunburst.css';
@@ -68,6 +68,8 @@ function Sunburst(element, props) {
let arcs;
let gMiddleText; // dom handles
const colorFn = getScale(colorScheme).toFunction();
// Helper + path gen functions
const partition = d3.layout.partition()
.size([2 * Math.PI, radius * radius])
@@ -132,7 +134,7 @@ function Sunburst(element, props) {
.attr('points', breadcrumbPoints)
.style('fill', function (d) {
return colorByCategory ?
getColorFromScheme(d.name, colorScheme) :
colorFn(d.name) :
colorScale(d.m2 / d.m1);
});
@@ -143,7 +145,7 @@ function Sunburst(element, props) {
.style('fill', function (d) {
// Make text white or black based on the lightness of the background
const col = d3.hsl(colorByCategory ?
getColorFromScheme(d.name, colorScheme) :
colorFn(d.name) :
colorScale(d.m2 / d.m1));
return col.l < 0.5 ? 'white' : 'black';
})
@@ -377,7 +379,7 @@ function Sunburst(element, props) {
.attr('d', arc)
.attr('fill-rule', 'evenodd')
.style('fill', d => colorByCategory
? getColorFromScheme(d.name, colorScheme)
? colorFn(d.name)
: colorScale(d.m2 / d.m1))
.style('opacity', 1)
.on('mouseenter', mouseenter);

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-shadow, no-param-reassign */
import d3 from 'd3';
import PropTypes from 'prop-types';
import { getColorFromScheme } from '../modules/colors';
import { getScale } from '../modules/CategoricalColorNamespace';
import './treemap.css';
// Declare PropTypes for recursive data structures
@@ -63,6 +63,7 @@ function treemap(element, props) {
} = props;
const div = d3.select(element);
const formatNumber = d3.format(numberFormat);
const colorFn = getScale(colorScheme).toFunction();
function draw(data, eltWidth, eltHeight) {
const navBarHeight = 36;
@@ -282,7 +283,7 @@ function treemap(element, props) {
.text(d => formatNumber(d.value));
t.call(text);
g.selectAll('rect')
.style('fill', d => getColorFromScheme(d.name, colorScheme));
.style('fill', d => colorFn(d.name));
return g;
};

View File

@@ -1,7 +1,7 @@
import d3 from 'd3';
import PropTypes from 'prop-types';
import cloudLayout from 'd3-cloud';
import { getColorFromScheme } from '../../modules/colors';
import { getScale } from '../../modules/CategoricalColorNamespace';
const ROTATION = {
square: () => Math.floor((Math.random() * 2)) * 90,
@@ -50,6 +50,8 @@ function wordCloud(element, props) {
.fontWeight('bold')
.fontSize(d => scale(d.size));
const colorFn = getScale(colorScheme).toFunction();
function draw(words) {
chart.selectAll('*').remove();
@@ -67,7 +69,7 @@ function wordCloud(element, props) {
.style('font-size', d => `${d.size}px`)
.style('font-weight', 'bold')
.style('font-family', 'Helvetica')
.style('fill', d => getColorFromScheme(d.text, colorScheme))
.style('fill', d => colorFn(d.text))
.attr('text-anchor', 'middle')
.attr('transform', d => `translate(${d.x}, ${d.y}) rotate(${d.rotate})`)
.text(d => d.text);