[DO NOT MERGE] performance improvements in color-by feature (#535)

performance improvements in color-by feature
This commit is contained in:
Bruce Martin
2018-12-27 14:05:49 -08:00
committed by GitHub
parent bc59074300
commit cfcf16aa69
3 changed files with 56 additions and 48 deletions
@@ -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;
+43 -33
View File
@@ -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
}); });
+9 -12
View File
@@ -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