Add user-defined category-label colors (#1402)

* Add user-defined category-label colors

Fixes https://github.com/chanzuckerberg/cellxgene/issues/1152

As described in https://github.com/chanzuckerberg/cellxgene/issues/1307

* Respond to feedback from @bkmartinjr in nodejs

* Respond to feedback from @bkmartinjr in python

* Add tests to the server module

* Autoformat python, run linter

* Make colors_get error handling specific

* Respond to feedback from @bkmartinjr

* Respond to feedback from @bkmartinjr

* Fix whitespace

* Fix python lint errrors

* Update documentation

* Add --disable-user-colors option to launch and cxgtool.py

* Fix python formatting

* Rename '--disable-user-colors' to '--disable-custom-colors'
This commit is contained in:
Matt Weiden
2020-04-26 22:52:57 -07:00
committed by GitHub
parent 3c191c3f93
commit 546e272a60
91 changed files with 801 additions and 271 deletions
+39 -10
View File
@@ -11,13 +11,16 @@ import { range } from "../range";
/*
create new colors state object. Paramters:
- world - current world object
- mode - color-by mode. One of: null, "color by expression",
"color by continuous metadata", "color by categorical metadata"
-
- colorMode - color-by mode. One of {null, "color by expression", "color by continuous metadata",
"color by categorical metadata"}
- colorAccessor - the obs annotations used for color-by
*/
export function createColors(world, colorMode = null, colorAccessor = null) {
export function createColors(world, colorMode = null, colorAccessor = null, userColors = null) {
switch (colorMode) {
case "color by categorical metadata": {
if (userColors && colorAccessor in userColors) {
return createUserColors(world, colorAccessor, userColors);
}
return createColorsByCategoricalMetadata(world, colorAccessor);
}
case "color by continuous metadata": {
@@ -36,8 +39,29 @@ export function createColors(world, colorMode = null, colorAccessor = null) {
}
}
function createColorsByCategoricalMetadata(world, accessor) {
const { categories } = world.schema.annotations.obsByName[accessor];
export function loadUserColorConfig(userColors) {
const convertedUserColors = {};
Object.keys(userColors).forEach(category => {
const [colors, scaleMap] = Object.keys(userColors[category]).reduce((acc, label, i) => {
const color = parseRGB(userColors[category][label]);
acc[0][label] = color;
acc[1][i] = d3.rgb(255 * color[0], 255 * color[1], 255 * color[2]);
return acc;
}, [{}, {}]);
const scale = i => scaleMap[i];
convertedUserColors[category] = { colors, scale };
});
return convertedUserColors;
}
function createUserColors(world, colorAccessor, userColors) {
const { colors, scale } = userColors[colorAccessor];
const rgb = createRgbArray(world, colors, colorAccessor);
return { rgb, scale };
}
function createColorsByCategoricalMetadata(world, colorAccessor) {
const { categories } = world.schema.annotations.obsByName[colorAccessor];
const scale = d3
.scaleSequential(interpolateRainbow)
@@ -49,14 +73,19 @@ function createColorsByCategoricalMetadata(world, accessor) {
return acc;
}, {});
const rgb = createRgbArray(world, colors, colorAccessor);
return { rgb, scale };
}
export function createRgbArray(world, colors, colorAccessor) {
const rgb = new Array(world.nObs);
const df = world.obsAnnotations;
const data = df.col(accessor).asArray();
const data = df.col(colorAccessor).asArray();
for (let i = 0, len = df.length; i < len; i += 1) {
const cat = data[i];
rgb[i] = colors[cat];
const label = data[i];
rgb[i] = colors[label];
}
return { rgb, scale };
return rgb;
}
function createColorsByContinuousMetadata(world, accessor) {