mirror of
https://github.com/apache/superset.git
synced 2026-04-22 17:45:21 +00:00
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:
committed by
Chris Williams
parent
bec0b4cc37
commit
f482a6cf99
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user