Skip to content

Commit

Permalink
Allow users to specify label->color mapping (#3879)
Browse files Browse the repository at this point in the history
Users can define `label_colors` in a dashboard's JSON metadata that
enforces a label to color mapping.

This also makes the function that maps labels to colors case insensitive.
  • Loading branch information
mistercrunch authored Nov 17, 2017
1 parent a84bd52 commit a82bb58
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 4 deletions.
16 changes: 16 additions & 0 deletions docs/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,19 @@ When adding columns to a table, you can have Superset detect and merge the
new columns in by using the "Refresh Metadata" action in the
``Source -> Tables`` page. Simply check the box next to the tables
you want the schema refreshed, and click ``Actions -> Refresh Metadata``.

Is there a way to force the use specific colors?
------------------------------------------------

It is possible on a per-dashboard basis by providing a mapping of
labels to colors in the ``JSON Metadata`` attribute using the
``label_colors`` key.

..code::

{
"label_colors": {
"Girls": "#FF69B4",
"Boys": "#ADD8E6"
}
}
10 changes: 10 additions & 0 deletions superset/assets/javascripts/dashboard/reducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as actions from './actions';
import { getParam } from '../modules/utils';
import { alterInArr, removeFromArr } from '../reduxUtils';
import { applyDefaultFormData } from '../explore/stores/store';
import { getColorFromScheme } from '../modules/colors';

export function getInitialState(bootstrapData) {
const { user_id, datasources, common } = bootstrapData;
Expand All @@ -25,6 +26,15 @@ export function getInitialState(bootstrapData) {
//
}

// Priming the color palette with user's label-color mapping provided in
// the dashboard's JSON metadata
if (dashboard.metadata && dashboard.metadata.label_colors) {
const colorMap = dashboard.metadata.label_colors;
for (const label in colorMap) {
getColorFromScheme(label, null, colorMap[label]);
}
}

dashboard.posDict = {};
dashboard.layout = [];
if (dashboard.position_json) {
Expand Down
25 changes: 22 additions & 3 deletions superset/assets/javascripts/modules/colors.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,36 @@ export const spectrums = {
],
};

/**
* Get a color from a scheme specific palette (scheme)
* The function cycles through the palette while memoizing labels
* association to colors. If the function is called twice with the
* same string, it will return the same color.
*
* @param {string} s - The label for which we want to get a color
* @param {string} scheme - The palette name, or "scheme"
* @param {string} forcedColor - A color that the caller wants to
forcibly associate to a label.
*/
export const getColorFromScheme = (function () {
// Color factory
const seen = {};
return function (s, scheme) {
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);
let stringifyS = String(s).toLowerCase();
// next line is for superset series that should have the same color
stringifyS = stringifyS.replace('---', '');

if (forcedColor && !forcedColors[stringifyS]) {
forcedColors[stringifyS] = forcedColor;
}
if (forcedColors[stringifyS]) {
return forcedColors[stringifyS];
}

if (seen[selectedScheme] === undefined) {
seen[selectedScheme] = {};
}
Expand Down
17 changes: 16 additions & 1 deletion superset/assets/spec/javascripts/modules/colors_spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe('colors', () => {
const color1 = getColorFromScheme('CA');
expect(color1).to.equal(ALL_COLOR_SCHEMES.bnbColors[0]);
});
it('series with same scheme should have the same color', () => {
it('getColorFromScheme series with same scheme should have the same color', () => {
const color1 = getColorFromScheme('CA', 'bnbColors');
const color2 = getColorFromScheme('CA', 'googleCategory20c');
const color3 = getColorFromScheme('CA', 'bnbColors');
Expand All @@ -19,7 +19,22 @@ describe('colors', () => {
expect(color1).to.equal(color3);
expect(color4).to.equal(ALL_COLOR_SCHEMES.bnbColors[1]);
});
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('girls', 'bnbColors', 'pink')).to.equal('pink');
expect(getColorFromScheme('girls', 'bnbColors')).to.equal('pink');
expect(getColorFromScheme('girls', 'googleCategory20c')).to.equal('pink');
});
it('getColorFromScheme is not case sensitive', () => {
const c1 = getColorFromScheme('girls', 'bnbColors');
const c2 = getColorFromScheme('Girls', 'bnbColors');
const c3 = getColorFromScheme('GIRLS', 'bnbColors');
expect(c1).to.equal(c2);
expect(c3).to.equal(c2);
});
it('hexToRGB converts properly', () => {
expect(hexToRGB('#FFFFFF')).to.have.same.members([255, 255, 255, 255]);
expect(hexToRGB('#000000')).to.have.same.members([0, 0, 0, 255]);
Expand Down

0 comments on commit a82bb58

Please sign in to comment.