[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)
}))
class HistogramBrush extends React.Component {
calcHistogramCache = memoize((obsAnnotations, field, ranges) => {
calcHistogramCache = memoize((obsAnnotations, field, rangeMin, rangeMax) => {
const { world } = this.props;
const histogramCache = {};
@@ -40,7 +40,7 @@ class HistogramBrush extends React.Component {
histogramCache.x = d3
.scaleLinear()
.domain([ranges.min, ranges.max])
.domain([rangeMin, rangeMax])
.range([0, this.width]);
histogramCache.bins = d3
@@ -130,7 +130,8 @@ class HistogramBrush extends React.Component {
const histogramCache = this.calcHistogramCache(
obsAnnotations,
field,
ranges
ranges.min,
ranges.max
);
const { x, y, bins, numValues } = histogramCache;
+43 -33
View File
@@ -1,14 +1,7 @@
// jshint esversion: 6
import _ from "lodash";
import * as d3 from "d3";
import {
interpolateViridis,
interpolateSpectral,
interpolateRainbow,
interpolateBlues,
interpolateCool
} from "d3-scale-chromatic";
import * as globals from "../globals";
import { interpolateRainbow, interpolateCool } from "d3-scale-chromatic";
import parseRGB from "../util/parseRGB";
/*
@@ -54,62 +47,79 @@ const updateCellColorsMiddleware = store => next => action => {
const { obsAnnotations } = s.controls.world;
let colorScale;
const colorsByName = new Array(obsAnnotations.length);
const colorsByRGB = new Array(obsAnnotations.length);
/*
in plain language...
(a) once the cells have loaded.
(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") {
const categories = _.filter(s.controls.world.schema.annotations.obs, {
const { categories } = _.filter(s.controls.world.schema.annotations.obs, {
name: action.colorAccessor
})[0].categories;
})[0];
colorScale = d3
.scaleSequential(interpolateRainbow)
.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 c = colorScale(categories.indexOf(obs[action.colorAccessor]));
colorsByName[i] = c;
colorsByRGB[i] = parseRGB(c);
const cat = obs[key];
colorsByRGB[i] = colors[cat];
}
}
if (action.type === "color by continuous metadata") {
const colorBins = 100;
const [min, max] = [0, action.rangeMaxForColorAccessor];
colorScale = d3
.scaleLinear()
.domain([0, action.rangeMaxForColorAccessor])
.range([1, 0]);
.scaleQuantile()
.domain([min, max])
.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 c = interpolateCool(colorScale(obs[action.colorAccessor]));
colorsByName[i] = c;
colorsByRGB[i] = parseRGB(c);
const c = colorScale(obs[key]);
colorsByRGB[i] = colors[c];
}
}
if (action.type === "color by expression") {
const { gene, data } = action;
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
.scaleLinear()
.domain([_.min(expression), _.max(expression)])
.range([
1,
0
]); /* invert viridis... probably pass this scale through to others */
.scaleQuantile()
.domain([min, max])
.range(_.range(colorBins - 1, -1, -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));
}
for (let i = 0, len = expression.length; i < len; i += 1) {
const c = interpolateCool(colorScale(expression[i]));
colorsByName[i] = c;
colorsByRGB[i] = parseRGB(c);
const c = colorScale(expression[i]);
colorsByRGB[i] = colors[c];
}
}
@@ -117,7 +127,7 @@ const updateCellColorsMiddleware = store => next => action => {
append the result of all the filters to the action the user just triggered
*/
const modifiedAction = Object.assign({}, action, {
colors: { name: colorsByName, rgb: colorsByRGB },
colors: { rgb: colorsByRGB },
colorScale
});
+9 -12
View File
@@ -112,7 +112,6 @@ const Controls = (
// all of the data + selection state
world: null,
colorName: null,
colorRGB: null,
categoricalSelectionState: null,
crossfilter: null,
@@ -167,8 +166,9 @@ const Controls = (
/* first light - create world & other data-driven defaults */
const { universe } = action;
const world = World.createWorldFromEntireUniverse(universe);
const colorName = new Array(universe.nObs).fill(globals.defaultCellColor);
const colorRGB = _.map(colorName, c => parseRGB(c));
const colorRGB = new Array(universe.nObs).fill(
parseRGB(globals.defaultCellColor)
);
const categoricalSelectionState = createCategoricalSelectionState(
state,
world
@@ -223,7 +223,6 @@ const Controls = (
error: null,
universe,
world,
colorName,
colorRGB,
categoricalSelectionState,
crossfilter,
@@ -240,8 +239,9 @@ const Controls = (
action.world,
action.crossfilter
);
const colorName = new Array(world.nObs).fill(globals.defaultCellColor);
const colorRGB = _.map(colorName, c => parseRGB(c));
const colorRGB = new Array(world.nObs).fill(
parseRGB(globals.defaultCellColor)
);
const categoricalSelectionState = createCategoricalSelectionState(
state,
world
@@ -289,7 +289,6 @@ const Controls = (
loading: false,
error: null,
world,
colorName,
colorRGB,
categoricalSelectionState,
crossfilter,
@@ -442,11 +441,11 @@ const Controls = (
}
case "reset colorscale": {
const { world } = state;
const colorName = new Array(world.nObs).fill(globals.defaultCellColor);
const colorRGB = _.map(colorName, c => parseRGB(c));
const colorRGB = new Array(world.nObs).fill(
parseRGB(globals.defaultCellColor)
);
return {
...state,
colorName,
colorRGB,
colorAccessor: null
};
@@ -609,7 +608,6 @@ const Controls = (
case "color by continuous metadata": {
return {
...state,
colorName: action.colors.name,
colorRGB: action.colors.rgb,
colorAccessor: action.colorAccessor,
colorScale: action.colorScale
@@ -618,7 +616,6 @@ const Controls = (
case "color by expression": {
return {
...state,
colorName: action.colors.name,
colorRGB: action.colors.rgb,
colorAccessor: action.gene,
colorScale: action.colorScale