namespacing

This commit is contained in:
Colin Megill
2018-10-15 11:20:52 -07:00
parent e1a437514e
commit 2b77f8146b
7 changed files with 151 additions and 57 deletions
@@ -83,17 +83,28 @@ class HistogramBrush extends React.Component {
onBrush(selection, x) { onBrush(selection, x) {
return () => { return () => {
const { dispatch, field } = this.props; const { dispatch, field, isObs, isUserDefined, isDiffExp } = this.props;
if (d3.event.selection) { if (d3.event.selection) {
dispatch({ dispatch({
type: "continuous metadata histogram brush", type: "continuous metadata histogram brush",
selection: field, selection: field,
continuousNamespace: {
isObs,
isUserDefined,
isDiffExp
},
range: [x(d3.event.selection[0]), x(d3.event.selection[1])] range: [x(d3.event.selection[0]), x(d3.event.selection[1])]
}); });
} else { } else {
dispatch({ dispatch({
type: "continuous metadata histogram brush", type: "continuous metadata histogram brush",
selection: field, selection: field,
continuousNamespace: {
isObs,
isUserDefined,
isDiffExp
},
range: null range: null
}); });
} }
@@ -58,6 +58,7 @@ class Continuous extends React.Component {
<HistogramBrush <HistogramBrush
key={key} key={key}
field={key} field={key}
isObs
fieldValues={obsAnnotations} fieldValues={obsAnnotations}
ranges={value.range} ranges={value.range}
handleColorAction={this.handleColorAction(key).bind(this)} handleColorAction={this.handleColorAction(key).bind(this)}
+16 -14
View File
@@ -5,11 +5,11 @@ import React from "react";
import _ from "lodash"; import _ from "lodash";
import * as d3 from "d3"; import * as d3 from "d3";
import { connect } from "react-redux"; import { connect } from "react-redux";
import HistogramBrush from "../brushableHistogram";
import CirclePlus from "react-icons/lib/fa/plus-circle"; import CirclePlus from "react-icons/lib/fa/plus-circle";
import HistogramBrush from "../brushableHistogram";
import * as globals from "../../globals"; import * as globals from "../../globals";
// import ReactAutocomplete from "react-autocomplete"; /* http://emilebres.github.io/react-virtualized-checkbox/ */ // import ReactAutocomplete from "react-autocomplete"; /* http://emilebres.github.io/react-virtualized-checkbox/ */
import actions from "../../actions/"; import actions from "../../actions";
@connect(state => { @connect(state => {
const metadata = _.get(state.controls.world, "obsAnnotations", null); const metadata = _.get(state.controls.world, "obsAnnotations", null);
@@ -22,7 +22,6 @@ import actions from "../../actions/";
initializeRanges, initializeRanges,
userDefinedGenes: state.controls.userDefinedGenes, userDefinedGenes: state.controls.userDefinedGenes,
world: state.controls.world, world: state.controls.world,
dimensionMap: state.controls.dimensionMap,
colorAccessor: state.controls.colorAccessor, colorAccessor: state.controls.colorAccessor,
allGeneNames: state.controls.allGeneNames, allGeneNames: state.controls.allGeneNames,
differential: state.differential differential: state.differential
@@ -32,40 +31,42 @@ class GeneExpression extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
svg: null,
ctx: null,
axes: null,
dimensions: null,
gene: "" gene: ""
}; };
} }
keyPress(e) { keyPress(e) {
if (e.keyCode == 13) { if (e.keyCode === 13) {
this.handleClick(); this.handleClick();
} }
} }
handleClick() { handleClick() {
const { world, dispatch, userDefinedGenes } = this.props; const { world, dispatch, userDefinedGenes } = this.props;
const { gene } = this.state;
if (userDefinedGenes.indexOf(this.state.gene) !== -1) { if (userDefinedGenes.indexOf(gene) !== -1) {
console.log("That gene already exists"); console.log("That gene already exists");
} else if (userDefinedGenes.length > 15) { } else if (userDefinedGenes.length > 15) {
console.log( console.log(
"That's too many genes, you can have at most 15 user defined genes" "That's too many genes, you can have at most 15 user defined genes"
); );
} else if (!_.find(world.varAnnotations, { name: this.state.gene })) { } else if (!_.find(world.varAnnotations, { name: gene })) {
console.log("That doesn't appear to be a valid gene name."); console.log("That doesn't appear to be a valid gene name.");
} else { } else {
dispatch(actions.requestGeneExpressionCountsPOST([this.state.gene])); dispatch(actions.requestGeneExpressionCountsPOST([gene]));
dispatch({ dispatch({
type: "user defined gene", type: "user defined gene",
data: this.state.gene data: gene
}); });
this.setState({ gene: "" }); this.setState({ gene: "" });
} }
} }
render() { render() {
const { world, userDefinedGenes } = this.props; const { world, userDefinedGenes } = this.props;
const { gene } = this.state;
return ( return (
<div> <div>
<input <input
@@ -74,9 +75,10 @@ class GeneExpression extends React.Component {
this.setState({ gene: e.target.value }); this.setState({ gene: e.target.value });
}} }}
type="text" type="text"
value={this.state.gene} value={gene}
/> />
<button <button
type="button"
style={{ style={{
border: "none", border: "none",
background: "none", background: "none",
@@ -101,7 +103,7 @@ class GeneExpression extends React.Component {
<p>User defined genes</p> <p>User defined genes</p>
) : null} ) : null}
{world && userDefinedGenes.length > 0 {world && userDefinedGenes.length > 0
? _.map(userDefinedGenes, (geneName, index) => { ? _.map(userDefinedGenes, geneName => {
if (!world.varDataCache[geneName]) { if (!world.varDataCache[geneName]) {
return null; return null;
} else { } else {
+66 -33
View File
@@ -5,6 +5,13 @@ import { World, kvCache } from "../util/stateManager";
import parseRGB from "../util/parseRGB"; import parseRGB from "../util/parseRGB";
import Crossfilter from "../util/typedCrossfilter"; import Crossfilter from "../util/typedCrossfilter";
import * as globals from "../globals"; import * as globals from "../globals";
import {
layoutDimensionName,
obsAnnoDimensionName,
userDefinedDimensionName,
diffexpDimensionName,
makeContinuousDimensionName
} from "../util/nameCreators";
function createCategoricalAsBooleansMap(world) { function createCategoricalAsBooleansMap(world) {
const res = {}; const res = {};
@@ -79,8 +86,14 @@ const Controls = (
const crossfilter = Crossfilter(world.obsAnnotations); const crossfilter = Crossfilter(world.obsAnnotations);
const dimensionMap = World.createObsDimensionMap(crossfilter, world); const dimensionMap = World.createObsDimensionMap(crossfilter, world);
let worldVarDataCache = world.varDataCache; const worldVarDataCache = world.varDataCache;
// dimensionMap = {
// layout_X: dim-for-X,
// obsAnno_name: dim for an annotation,
// varData_userDefined_genename: dim for user defined expression,
// varData_diffexp_genename: dim for diff-exp added gene expression
// }
/* var dimensions */ /* var dimensions */
if (userDefinedGenes.length > 0) { if (userDefinedGenes.length > 0) {
/* /*
@@ -88,8 +101,10 @@ const Controls = (
in controls rather than an array, should be abstracted into in controls rather than an array, should be abstracted into
util ie., createDimensionsFromBothListsOfGenes(userGenes, diffExp) util ie., createDimensionsFromBothListsOfGenes(userGenes, diffExp)
*/ */
_.forEach(userDefinedGenes, (gene, index) => { _.forEach(userDefinedGenes, gene => {
dimensionMap[gene] = World.createVarDimension( dimensionMap[
userDefinedDimensionName(gene)
] = World.createVarDimension(
/* "__var__" + */ /* "__var__" + */
world, world,
worldVarDataCache, worldVarDataCache,
@@ -100,8 +115,8 @@ const Controls = (
} }
if (diffexpGenes.length > 0) { if (diffexpGenes.length > 0) {
_.forEach(diffexpGenes, (gene, index) => { _.forEach(diffexpGenes, gene => {
dimensionMap[gene] = World.createVarDimension( dimensionMap[diffexpDimensionName(gene)] = World.createVarDimension(
/* "__var__" + */ /* "__var__" + */
world, world,
worldVarDataCache, worldVarDataCache,
@@ -140,7 +155,7 @@ const Controls = (
const crossfilter = Crossfilter(world.obsAnnotations); const crossfilter = Crossfilter(world.obsAnnotations);
const dimensionMap = World.createObsDimensionMap(crossfilter, world); const dimensionMap = World.createObsDimensionMap(crossfilter, world);
let worldVarDataCache = world.varDataCache; const worldVarDataCache = world.varDataCache;
/* var dimensions */ /* var dimensions */
if (userDefinedGenes.length > 0) { if (userDefinedGenes.length > 0) {
@@ -149,8 +164,10 @@ const Controls = (
in controls rather than an array, should be abstracted into in controls rather than an array, should be abstracted into
util ie., createDimensionsFromBothListsOfGenes(userGenes, diffExp) util ie., createDimensionsFromBothListsOfGenes(userGenes, diffExp)
*/ */
_.forEach(userDefinedGenes, (gene, index) => { _.forEach(userDefinedGenes, gene => {
dimensionMap[gene] = World.createVarDimension( dimensionMap[
userDefinedDimensionName(gene)
] = World.createVarDimension(
/* "__var__" + */ /* "__var__" + */
world, world,
worldVarDataCache, worldVarDataCache,
@@ -161,8 +178,8 @@ const Controls = (
} }
if (diffexpGenes.length > 0) { if (diffexpGenes.length > 0) {
_.forEach(diffexpGenes, (gene, index) => { _.forEach(diffexpGenes, gene => {
dimensionMap[gene] = World.createVarDimension( dimensionMap[diffexpDimensionName(gene)] = World.createVarDimension(
/* "__var__" + */ /* "__var__" + */
world, world,
worldVarDataCache, worldVarDataCache,
@@ -186,6 +203,7 @@ const Controls = (
}; };
} }
case "expression load success": { case "expression load success": {
console.log("expression load success", action);
const { world, universe, crossfilter, dimensionMap } = state; const { world, universe, crossfilter, dimensionMap } = state;
let universeVarDataCache = universe.varDataCache; let universeVarDataCache = universe.varDataCache;
let worldVarDataCache = world.varDataCache; let worldVarDataCache = world.varDataCache;
@@ -223,33 +241,34 @@ const Controls = (
}; };
} }
case "request differential expression success": { case "request differential expression success": {
console.log("request diff success", action);
const { world } = state; const { world } = state;
const _diffexpGenes = []; const _diffexpGenes = [];
action.data.forEach(d => { action.data.forEach(d => {
_diffexpGenes.push(world.varAnnotations[d[0]].name); _diffexpGenes.push(world.varAnnotations[d[0]].name);
}); });
return { return {
...state, ...state,
diffexpGenes: _diffexpGenes diffexpGenes: _diffexpGenes
}; };
} }
case "clear differential expression": case "clear differential expression": {
const { world, universe, crossfilter, dimensionMap } = state; const { world, universe, crossfilter, dimensionMap } = state;
const _dimensionMap = dimensionMap; const _dimensionMap = dimensionMap;
let universeVarDataCache = universe.varDataCache; const universeVarDataCache = universe.varDataCache;
let worldVarDataCache = world.varDataCache; const worldVarDataCache = world.varDataCache;
_.forEach(action.diffExp, values => { _.forEach(action.diffExp, values => {
const name = world.varAnnotations[values[0]].name; const { name } = world.varAnnotations[values[0]].name;
// clean up crossfilter dimensions // clean up crossfilter dimensions
const filterID = dimensionMap[name]; const filterID = dimensionMap[diffexpDimensionName(name)];
const dimension = crossfilter.filters.find(d => { const dimension = crossfilter.filters.find(d => d.id === filterID);
return (d.id = filterID);
});
dimension.dim.dispose(); dimension.dim.dispose();
// clean up dimensionsMap // clean up dimensionsMap
delete _dimensionMap[name]; delete _dimensionMap[diffexpDimensionName(name)];
// clean up the varDataCaches // clean up the varDataCaches
delete universeVarDataCache[name]; delete universeVarDataCache[name];
delete worldVarDataCache[name]; delete worldVarDataCache[name];
@@ -267,8 +286,12 @@ const Controls = (
varDataCache: worldVarDataCache varDataCache: worldVarDataCache
} }
}; };
}
case "user defined gene": { case "user defined gene": {
/* this could also live in expression success with a conditional, but that handles diffexp also */ /*
this could also live in expression success with a conditional,
but that handles diffexp also
*/
const newUserDefinedGenes = state.userDefinedGenes.slice(); const newUserDefinedGenes = state.userDefinedGenes.slice();
newUserDefinedGenes.push(action.data); newUserDefinedGenes.push(action.data);
return { return {
@@ -278,9 +301,10 @@ const Controls = (
} }
case "clear user defined gene": { case "clear user defined gene": {
const { userDefinedGenes } = state; const { userDefinedGenes } = state;
const newUserDefinedGenes = _.filter(userDefinedGenes, d => { const newUserDefinedGenes = _.filter(
return d !== action.data; userDefinedGenes,
}); d => d !== action.data
);
return { return {
...state, ...state,
userDefinedGenes: newUserDefinedGenes userDefinedGenes: newUserDefinedGenes
@@ -310,11 +334,11 @@ const Controls = (
User Events User Events
*******************************/ *******************************/
case "graph brush selection change": { case "graph brush selection change": {
state.dimensionMap.x.filterRange([ state.dimensionMap[layoutDimensionName("X")].filterRange([
action.brushCoords.northwest[0], action.brushCoords.northwest[0],
action.brushCoords.southeast[0] action.brushCoords.southeast[0]
]); ]);
state.dimensionMap.y.filterRange([ state.dimensionMap[layoutDimensionName("Y")].filterRange([
action.brushCoords.southeast[1], action.brushCoords.southeast[1],
action.brushCoords.northwest[1] action.brushCoords.northwest[1]
]); ]);
@@ -324,20 +348,25 @@ const Controls = (
}; };
} }
case "graph brush deselect": { case "graph brush deselect": {
state.dimensionMap.x.filterAll(); state.dimensionMap[layoutDimensionName("X")].filterAll();
state.dimensionMap.y.filterAll(); state.dimensionMap[layoutDimensionName("Y")].filterAll();
return { return {
...state, ...state,
graphBrushSelection: null graphBrushSelection: null
}; };
} }
case "continuous metadata histogram brush": { case "continuous metadata histogram brush": {
const name = makeContinuousDimensionName(
action.continuousNamespace,
action.selection
);
// action.selection: metadata name being selected // action.selection: metadata name being selected
// action.range: filter range, or null if deselected // action.range: filter range, or null if deselected
if (!action.range) { if (!action.range) {
state.dimensionMap[action.selection].filterAll(); state.dimensionMap[name].filterAll();
} else { } else {
state.dimensionMap[action.selection].filterRange(action.range); state.dimensionMap[name].filterRange(action.range);
} }
return { ...state }; return { ...state };
} }
@@ -359,7 +388,7 @@ const Controls = (
} }
}; };
// update the filter for the one category that changed state // update the filter for the one category that changed state
state.dimensionMap[action.metadataField].filterEnum( state.dimensionMap[obsAnnoDimensionName(action.metadataField)].filterEnum(
_.filter( _.filter(
_.map( _.map(
newCategoricalAsBooleansMap[action.metadataField], newCategoricalAsBooleansMap[action.metadataField],
@@ -381,7 +410,7 @@ const Controls = (
} }
}; };
// update the filter for the one category that changed state // update the filter for the one category that changed state
state.dimensionMap[action.metadataField].filterEnum( state.dimensionMap[obsAnnoDimensionName(action.metadataField)].filterEnum(
_.filter( _.filter(
_.map( _.map(
newCategoricalAsBooleansMap[action.metadataField], newCategoricalAsBooleansMap[action.metadataField],
@@ -404,7 +433,9 @@ const Controls = (
c[k] = false; c[k] = false;
} }
); );
state.dimensionMap[action.metadataField].filterNone(); state.dimensionMap[
obsAnnoDimensionName(action.metadataField)
].filterNone();
return { return {
...state, ...state,
categoricalAsBooleansMap: newCategoricalAsBooleansMap categoricalAsBooleansMap: newCategoricalAsBooleansMap
@@ -420,7 +451,9 @@ const Controls = (
c[k] = true; c[k] = true;
} }
); );
state.dimensionMap[action.metadataField].filterAll(); state.dimensionMap[
obsAnnoDimensionName(action.metadataField)
].filterAll();
return { return {
...state, ...state,
categoricalAsBooleansMap: newCategoricalAsBooleansMap categoricalAsBooleansMap: newCategoricalAsBooleansMap
+1 -2
View File
@@ -1,5 +1,4 @@
// jshint esversion: 6 // jshint esversion: 6
import _ from "lodash";
const Differential = ( const Differential = (
state = { state = {
@@ -47,7 +46,7 @@ const Differential = (
diffExp: null, diffExp: null,
celllist1: null, celllist1: null,
celllist2: null celllist2: null
} };
case "reset World to eq Universe": case "reset World to eq Universe":
case "set World to current selection": case "set World to current selection":
return { return {
+43
View File
@@ -0,0 +1,43 @@
/*
The original motivation for this file is name collision. It's possible
to have two of the same gene, user defined and diffexpressed, on the client
simultaneously, looking at the same data in the cache but with two
dimensions and two crossfilters. It's also possible that someone could
have a obsAnnotation named X, but we are using that for layout. So
we namespace, and abstract to avoid proliferating strings throughout the
codebase.
*/
const makeDimensionName = (namespace, key) => `${namespace}_${key}`;
export const layoutDimensionName = key => makeDimensionName("layout", key);
export const obsAnnoDimensionName = key => makeDimensionName("obsAnno", key);
export const diffexpDimensionName = key =>
makeDimensionName("varData_diffexp", key);
export const userDefinedDimensionName = key =>
makeDimensionName("varData_userDefined", key);
/*
continuousNamespace = {
isObs: true,
isDiffExp: false,
isUserDefined: false
}
ie., makeContinuousDimensionName(continuousNamespace = {isObs: true}, "total_reads")
see: histogram brush, as it doesn't know what type of continuous it was with only field
*/
export const makeContinuousDimensionName = (continuousNamespace, key) => {
let name;
if (continuousNamespace.isObs) {
name = obsAnnoDimensionName(key);
} else if (continuousNamespace.isDiffExp) {
name = diffexpDimensionName(key);
} else if (continuousNamespace.isUserDefined) {
name = userDefinedDimensionName(key);
}
return name;
};
+12 -7
View File
@@ -2,6 +2,7 @@
import _ from "lodash"; import _ from "lodash";
import * as kvCache from "./keyvalcache"; import * as kvCache from "./keyvalcache";
import { layoutDimensionName, obsAnnoDimensionName } from "../nameCreators";
/* /*
World is a subset of universe. Most code should use world, and should World is a subset of universe. Most code should use world, and should
@@ -296,13 +297,14 @@ export function createVarDimension(
crossfilter, crossfilter,
geneName geneName
) { ) {
const { varDataCache, worldObsIndex } = world; const { worldObsIndex } = world;
const varData = _worldVarDataCache[geneName]; const varData = _worldVarDataCache[geneName];
const worldIndex = worldObsIndex ? idx => worldObsIndex[idx] : idx => idx; const worldIndex = worldObsIndex ? idx => worldObsIndex[idx] : idx => idx;
return crossfilter.dimension(r => { return crossfilter.dimension(
return varData[worldIndex(r.__index__)]; r => varData[worldIndex(r.__index__)],
}, Float32Array); Float32Array
);
} }
export function createObsDimensionMap(crossfilter, world) { export function createObsDimensionMap(crossfilter, world) {
@@ -317,7 +319,10 @@ export function createObsDimensionMap(crossfilter, world) {
(result, anno) => { (result, anno) => {
const dimType = deduceDimensionType(anno, anno.name); const dimType = deduceDimensionType(anno, anno.name);
if (dimType) { if (dimType) {
result[anno.name] = crossfilter.dimension(r => r[anno.name], dimType); result[obsAnnoDimensionName(anno.name)] = crossfilter.dimension(
r => r[anno.name],
dimType
);
} // else ignore the annotation } // else ignore the annotation
}, },
{} {}
@@ -327,11 +332,11 @@ export function createObsDimensionMap(crossfilter, world) {
Add crossfilter dimensions allowing filtering on layout Add crossfilter dimensions allowing filtering on layout
*/ */
const worldIndex = worldObsIndex ? idx => worldObsIndex[idx] : idx => idx; const worldIndex = worldObsIndex ? idx => worldObsIndex[idx] : idx => idx;
dimensionMap.x = crossfilter.dimension( dimensionMap[layoutDimensionName("X")] = crossfilter.dimension(
r => obsLayout.X[worldIndex(r.__index__)], r => obsLayout.X[worldIndex(r.__index__)],
Float32Array Float32Array
); );
dimensionMap.y = crossfilter.dimension( dimensionMap[layoutDimensionName("Y")] = crossfilter.dimension(
r => obsLayout.Y[worldIndex(r.__index__)], r => obsLayout.Y[worldIndex(r.__index__)],
Float32Array Float32Array
); );