mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-06 16:08:11 +08:00
[DO NOT MERGE] performance improvements in color-by feature (#535)
performance improvements in color-by feature
This commit is contained in:
@@ -26,7 +26,7 @@ import actions from "../../actions";
|
|||||||
obsAnnotations: _.get(state.controls.world, "obsAnnotations", null)
|
obsAnnotations: _.get(state.controls.world, "obsAnnotations", null)
|
||||||
}))
|
}))
|
||||||
class HistogramBrush extends React.Component {
|
class HistogramBrush extends React.Component {
|
||||||
calcHistogramCache = memoize((obsAnnotations, field, ranges) => {
|
calcHistogramCache = memoize((obsAnnotations, field, rangeMin, rangeMax) => {
|
||||||
const { world } = this.props;
|
const { world } = this.props;
|
||||||
const histogramCache = {};
|
const histogramCache = {};
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ class HistogramBrush extends React.Component {
|
|||||||
|
|
||||||
histogramCache.x = d3
|
histogramCache.x = d3
|
||||||
.scaleLinear()
|
.scaleLinear()
|
||||||
.domain([ranges.min, ranges.max])
|
.domain([rangeMin, rangeMax])
|
||||||
.range([0, this.width]);
|
.range([0, this.width]);
|
||||||
|
|
||||||
histogramCache.bins = d3
|
histogramCache.bins = d3
|
||||||
@@ -130,7 +130,8 @@ class HistogramBrush extends React.Component {
|
|||||||
const histogramCache = this.calcHistogramCache(
|
const histogramCache = this.calcHistogramCache(
|
||||||
obsAnnotations,
|
obsAnnotations,
|
||||||
field,
|
field,
|
||||||
ranges
|
ranges.min,
|
||||||
|
ranges.max
|
||||||
);
|
);
|
||||||
|
|
||||||
const { x, y, bins, numValues } = histogramCache;
|
const { x, y, bins, numValues } = histogramCache;
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
// jshint esversion: 6
|
// jshint esversion: 6
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
import {
|
import { interpolateRainbow, interpolateCool } from "d3-scale-chromatic";
|
||||||
interpolateViridis,
|
|
||||||
interpolateSpectral,
|
|
||||||
interpolateRainbow,
|
|
||||||
interpolateBlues,
|
|
||||||
interpolateCool
|
|
||||||
} from "d3-scale-chromatic";
|
|
||||||
import * as globals from "../globals";
|
|
||||||
import parseRGB from "../util/parseRGB";
|
import parseRGB from "../util/parseRGB";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -54,62 +47,79 @@ const updateCellColorsMiddleware = store => next => action => {
|
|||||||
|
|
||||||
const { obsAnnotations } = s.controls.world;
|
const { obsAnnotations } = s.controls.world;
|
||||||
let colorScale;
|
let colorScale;
|
||||||
const colorsByName = new Array(obsAnnotations.length);
|
|
||||||
const colorsByRGB = new Array(obsAnnotations.length);
|
const colorsByRGB = new Array(obsAnnotations.length);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
in plain language...
|
in plain language...
|
||||||
(a) once the cells have loaded.
|
(a) once the cells have loaded.
|
||||||
(b) each time a user changes a color control we need to update cellsMetadata colors
|
(b) each time a user changes a color control we need to update cellsMetadata colors
|
||||||
This is available to all the draw functions as world.colorName[index] or world.colorRGB[index]
|
This is available to all the draw functions as controls.colorRGB[index]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (action.type === "color by categorical metadata") {
|
if (action.type === "color by categorical metadata") {
|
||||||
const categories = _.filter(s.controls.world.schema.annotations.obs, {
|
const { categories } = _.filter(s.controls.world.schema.annotations.obs, {
|
||||||
name: action.colorAccessor
|
name: action.colorAccessor
|
||||||
})[0].categories;
|
})[0];
|
||||||
|
|
||||||
colorScale = d3
|
colorScale = d3
|
||||||
.scaleSequential(interpolateRainbow)
|
.scaleSequential(interpolateRainbow)
|
||||||
.domain([0, categories.length]);
|
.domain([0, categories.length]);
|
||||||
|
|
||||||
for (let i = 0; i < obsAnnotations.length; i += 1) {
|
/* pre-create colors - much faster than doing it for each obs */
|
||||||
|
const colors = _.transform(categories, (acc, cat, idx) => {
|
||||||
|
acc[cat] = parseRGB(colorScale(idx));
|
||||||
|
});
|
||||||
|
|
||||||
|
const key = action.colorAccessor;
|
||||||
|
for (let i = 0, len = obsAnnotations.length; i < len; i += 1) {
|
||||||
const obs = obsAnnotations[i];
|
const obs = obsAnnotations[i];
|
||||||
const c = colorScale(categories.indexOf(obs[action.colorAccessor]));
|
const cat = obs[key];
|
||||||
colorsByName[i] = c;
|
colorsByRGB[i] = colors[cat];
|
||||||
colorsByRGB[i] = parseRGB(c);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.type === "color by continuous metadata") {
|
if (action.type === "color by continuous metadata") {
|
||||||
|
const colorBins = 100;
|
||||||
|
const [min, max] = [0, action.rangeMaxForColorAccessor];
|
||||||
colorScale = d3
|
colorScale = d3
|
||||||
.scaleLinear()
|
.scaleQuantile()
|
||||||
.domain([0, action.rangeMaxForColorAccessor])
|
.domain([min, max])
|
||||||
.range([1, 0]);
|
.range(_.range(colorBins - 1, -1, -1));
|
||||||
|
|
||||||
for (let i = 0; i < obsAnnotations.length; i += 1) {
|
/* pre-create colors - much faster than doing it for each obs */
|
||||||
|
const colors = new Array(colorBins);
|
||||||
|
for (let i = 0; i < colorBins; i += 1) {
|
||||||
|
colors[i] = parseRGB(interpolateCool(i / colorBins));
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = action.colorAccessor;
|
||||||
|
for (let i = 0, len = obsAnnotations.length; i < len; i += 1) {
|
||||||
const obs = obsAnnotations[i];
|
const obs = obsAnnotations[i];
|
||||||
const c = interpolateCool(colorScale(obs[action.colorAccessor]));
|
const c = colorScale(obs[key]);
|
||||||
colorsByName[i] = c;
|
colorsByRGB[i] = colors[c];
|
||||||
colorsByRGB[i] = parseRGB(c);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.type === "color by expression") {
|
if (action.type === "color by expression") {
|
||||||
const { gene, data } = action;
|
const { gene, data } = action;
|
||||||
const expression = data[gene]; // Float32Array
|
const expression = data[gene]; // Float32Array
|
||||||
|
const colorBins = 100;
|
||||||
|
// XXX TODO - replace _.min/_.max with the much faster finiteExtent
|
||||||
|
const [min, max] = [_.min(expression), _.max(expression)];
|
||||||
colorScale = d3
|
colorScale = d3
|
||||||
.scaleLinear()
|
.scaleQuantile()
|
||||||
.domain([_.min(expression), _.max(expression)])
|
.domain([min, max])
|
||||||
.range([
|
.range(_.range(colorBins - 1, -1, -1));
|
||||||
1,
|
|
||||||
0
|
/* pre-create colors - much faster than doing it for each obs */
|
||||||
]); /* invert viridis... probably pass this scale through to others */
|
const colors = new Array(colorBins);
|
||||||
|
for (let i = 0; i < colorBins; i += 1) {
|
||||||
|
colors[i] = parseRGB(interpolateCool(i / colorBins));
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0, len = expression.length; i < len; i += 1) {
|
for (let i = 0, len = expression.length; i < len; i += 1) {
|
||||||
const c = interpolateCool(colorScale(expression[i]));
|
const c = colorScale(expression[i]);
|
||||||
colorsByName[i] = c;
|
colorsByRGB[i] = colors[c];
|
||||||
colorsByRGB[i] = parseRGB(c);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +127,7 @@ const updateCellColorsMiddleware = store => next => action => {
|
|||||||
append the result of all the filters to the action the user just triggered
|
append the result of all the filters to the action the user just triggered
|
||||||
*/
|
*/
|
||||||
const modifiedAction = Object.assign({}, action, {
|
const modifiedAction = Object.assign({}, action, {
|
||||||
colors: { name: colorsByName, rgb: colorsByRGB },
|
colors: { rgb: colorsByRGB },
|
||||||
colorScale
|
colorScale
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Vendored
+9
-12
@@ -112,7 +112,6 @@ const Controls = (
|
|||||||
|
|
||||||
// all of the data + selection state
|
// all of the data + selection state
|
||||||
world: null,
|
world: null,
|
||||||
colorName: null,
|
|
||||||
colorRGB: null,
|
colorRGB: null,
|
||||||
categoricalSelectionState: null,
|
categoricalSelectionState: null,
|
||||||
crossfilter: null,
|
crossfilter: null,
|
||||||
@@ -167,8 +166,9 @@ const Controls = (
|
|||||||
/* first light - create world & other data-driven defaults */
|
/* first light - create world & other data-driven defaults */
|
||||||
const { universe } = action;
|
const { universe } = action;
|
||||||
const world = World.createWorldFromEntireUniverse(universe);
|
const world = World.createWorldFromEntireUniverse(universe);
|
||||||
const colorName = new Array(universe.nObs).fill(globals.defaultCellColor);
|
const colorRGB = new Array(universe.nObs).fill(
|
||||||
const colorRGB = _.map(colorName, c => parseRGB(c));
|
parseRGB(globals.defaultCellColor)
|
||||||
|
);
|
||||||
const categoricalSelectionState = createCategoricalSelectionState(
|
const categoricalSelectionState = createCategoricalSelectionState(
|
||||||
state,
|
state,
|
||||||
world
|
world
|
||||||
@@ -223,7 +223,6 @@ const Controls = (
|
|||||||
error: null,
|
error: null,
|
||||||
universe,
|
universe,
|
||||||
world,
|
world,
|
||||||
colorName,
|
|
||||||
colorRGB,
|
colorRGB,
|
||||||
categoricalSelectionState,
|
categoricalSelectionState,
|
||||||
crossfilter,
|
crossfilter,
|
||||||
@@ -240,8 +239,9 @@ const Controls = (
|
|||||||
action.world,
|
action.world,
|
||||||
action.crossfilter
|
action.crossfilter
|
||||||
);
|
);
|
||||||
const colorName = new Array(world.nObs).fill(globals.defaultCellColor);
|
const colorRGB = new Array(world.nObs).fill(
|
||||||
const colorRGB = _.map(colorName, c => parseRGB(c));
|
parseRGB(globals.defaultCellColor)
|
||||||
|
);
|
||||||
const categoricalSelectionState = createCategoricalSelectionState(
|
const categoricalSelectionState = createCategoricalSelectionState(
|
||||||
state,
|
state,
|
||||||
world
|
world
|
||||||
@@ -289,7 +289,6 @@ const Controls = (
|
|||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
world,
|
world,
|
||||||
colorName,
|
|
||||||
colorRGB,
|
colorRGB,
|
||||||
categoricalSelectionState,
|
categoricalSelectionState,
|
||||||
crossfilter,
|
crossfilter,
|
||||||
@@ -442,11 +441,11 @@ const Controls = (
|
|||||||
}
|
}
|
||||||
case "reset colorscale": {
|
case "reset colorscale": {
|
||||||
const { world } = state;
|
const { world } = state;
|
||||||
const colorName = new Array(world.nObs).fill(globals.defaultCellColor);
|
const colorRGB = new Array(world.nObs).fill(
|
||||||
const colorRGB = _.map(colorName, c => parseRGB(c));
|
parseRGB(globals.defaultCellColor)
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
colorName,
|
|
||||||
colorRGB,
|
colorRGB,
|
||||||
colorAccessor: null
|
colorAccessor: null
|
||||||
};
|
};
|
||||||
@@ -609,7 +608,6 @@ const Controls = (
|
|||||||
case "color by continuous metadata": {
|
case "color by continuous metadata": {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
colorName: action.colors.name,
|
|
||||||
colorRGB: action.colors.rgb,
|
colorRGB: action.colors.rgb,
|
||||||
colorAccessor: action.colorAccessor,
|
colorAccessor: action.colorAccessor,
|
||||||
colorScale: action.colorScale
|
colorScale: action.colorScale
|
||||||
@@ -618,7 +616,6 @@ const Controls = (
|
|||||||
case "color by expression": {
|
case "color by expression": {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
colorName: action.colors.name,
|
|
||||||
colorRGB: action.colors.rgb,
|
colorRGB: action.colors.rgb,
|
||||||
colorAccessor: action.gene,
|
colorAccessor: action.gene,
|
||||||
colorScale: action.colorScale
|
colorScale: action.colorScale
|
||||||
|
|||||||
Reference in New Issue
Block a user