mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-06 09:28:13 +08:00
refactor regraph functionality to use crossfilter; add reset graph
This commit is contained in:
@@ -65,6 +65,12 @@ const regraph = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resetGraph = () => {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
dispatch({ type: "reset graph" });
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const initialize = () => {
|
const initialize = () => {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({ type: "initialize started" });
|
dispatch({ type: "initialize started" });
|
||||||
@@ -218,6 +224,7 @@ export default {
|
|||||||
initialize,
|
initialize,
|
||||||
requestCells,
|
requestCells,
|
||||||
regraph,
|
regraph,
|
||||||
|
resetGraph,
|
||||||
requestGeneExpressionCounts,
|
requestGeneExpressionCounts,
|
||||||
requestGeneExpressionCountsPOST,
|
requestGeneExpressionCountsPOST,
|
||||||
requestSingleGeneExpressionCountsForColoringPOST,
|
requestSingleGeneExpressionCountsForColoringPOST,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { connect } from "react-redux";
|
|||||||
return {
|
return {
|
||||||
colorAccessor: state.controls.colorAccessor,
|
colorAccessor: state.controls.colorAccessor,
|
||||||
colorScale: state.controls.colorScale,
|
colorScale: state.controls.colorScale,
|
||||||
allCellsMetadata: state.controls.allCellsMetadata
|
cellsMetadata: state.controls.cellsMetadata
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
class HistogramBrush extends React.Component {
|
class HistogramBrush extends React.Component {
|
||||||
@@ -52,7 +52,7 @@ class HistogramBrush extends React.Component {
|
|||||||
calcHistogramCache(nextProps) {
|
calcHistogramCache(nextProps) {
|
||||||
// recalculate expensive stuff
|
// recalculate expensive stuff
|
||||||
const allValuesForContinuousFieldAsArray = _.map(
|
const allValuesForContinuousFieldAsArray = _.map(
|
||||||
nextProps.allCellsMetadata,
|
nextProps.cellsMetadata,
|
||||||
nextProps.metadataField
|
nextProps.metadataField
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import { margin, width, height, createDimensions } from "./util";
|
|||||||
colorAccessor: state.controls.colorAccessor,
|
colorAccessor: state.controls.colorAccessor,
|
||||||
colorScale: state.controls.colorScale,
|
colorScale: state.controls.colorScale,
|
||||||
graphBrushSelection: state.controls.graphBrushSelection,
|
graphBrushSelection: state.controls.graphBrushSelection,
|
||||||
allCellsMetadata: state.controls.allCellsMetadata,
|
cellsMetadata: state.controls.cellsMetadata,
|
||||||
axesHaveBeenDrawn: state.controls.axesHaveBeenDrawn
|
axesHaveBeenDrawn: state.controls.axesHaveBeenDrawn
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@@ -96,7 +96,7 @@ class Parallel extends React.Component {
|
|||||||
/* https://stackoverflow.com/questions/23123138/perform-debounce-in-react-js */
|
/* https://stackoverflow.com/questions/23123138/perform-debounce-in-react-js */
|
||||||
if (
|
if (
|
||||||
nextProps.ranges &&
|
nextProps.ranges &&
|
||||||
nextProps.allCellsMetadata &&
|
nextProps.cellsMetadata &&
|
||||||
nextProps.axesHaveBeenDrawn
|
nextProps.axesHaveBeenDrawn
|
||||||
) {
|
) {
|
||||||
if (this.state._drawLinesCanvas) {
|
if (this.state._drawLinesCanvas) {
|
||||||
@@ -106,7 +106,7 @@ class Parallel extends React.Component {
|
|||||||
this.state.ctx.clearRect(0, 0, width, height);
|
this.state.ctx.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
const _drawLinesCanvas = drawLinesCanvas(
|
const _drawLinesCanvas = drawLinesCanvas(
|
||||||
nextProps.allCellsMetadata,
|
nextProps.cellsMetadata,
|
||||||
this.state.dimensions,
|
this.state.dimensions,
|
||||||
this.state.xscale,
|
this.state.xscale,
|
||||||
this.state.ctx,
|
this.state.ctx,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import FaSave from "react-icons/lib/fa/download";
|
|||||||
|
|
||||||
@connect(state => {
|
@connect(state => {
|
||||||
return {
|
return {
|
||||||
allCellsMetadata: state.controls.allCellsMetadata,
|
cellsMetadata: state.controls.cellsMetadata,
|
||||||
opacityForDeselectedCells: state.controls.opacityForDeselectedCells,
|
opacityForDeselectedCells: state.controls.opacityForDeselectedCells,
|
||||||
responsive: state.responsive,
|
responsive: state.responsive,
|
||||||
crossfilter: state.controls.crossfilter
|
crossfilter: state.controls.crossfilter
|
||||||
@@ -131,7 +131,7 @@ class Graph extends React.Component {
|
|||||||
// we could add some sort of color-specific indicator to the app state.
|
// we could add some sort of color-specific indicator to the app state.
|
||||||
if (
|
if (
|
||||||
!this.renderCache.colors ||
|
!this.renderCache.colors ||
|
||||||
this.props.allCellsMetadata != nextProps.allCellsMetadata
|
this.props.cellsMetadata != nextProps.cellsMetadata
|
||||||
) {
|
) {
|
||||||
if (!this.renderCache.colors)
|
if (!this.renderCache.colors)
|
||||||
this.renderCache.colors = new Float32Array(3 * cellCount);
|
this.renderCache.colors = new Float32Array(3 * cellCount);
|
||||||
@@ -244,6 +244,24 @@ class Graph extends React.Component {
|
|||||||
alignItems: "baseline"
|
alignItems: "baseline"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
this.props.dispatch(actions.resetGraph());
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: "white",
|
||||||
|
padding: "10px 20px",
|
||||||
|
marginRight: 10,
|
||||||
|
borderRadius: 2,
|
||||||
|
backgroundColor: globals.brightBlue,
|
||||||
|
border: "none",
|
||||||
|
cursor: "pointer"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
reset graph
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
this.props.dispatch(actions.regraph());
|
this.props.dispatch(actions.regraph());
|
||||||
|
|||||||
@@ -31,22 +31,20 @@ const updateCellColorsMiddleware = store => {
|
|||||||
action.type === "color by continuous metadata" ||
|
action.type === "color by continuous metadata" ||
|
||||||
action.type === "color by categorical metadata";
|
action.type === "color by categorical metadata";
|
||||||
|
|
||||||
if (!filterJustChanged || !s.controls.allCellsOnClient) {
|
if (!filterJustChanged || !s.controls.cellsMetadata) {
|
||||||
return next(
|
return next(
|
||||||
action
|
action
|
||||||
); /* if the cells haven't loaded or the action wasn't a color change, bail */
|
); /* if the cells haven't loaded or the action wasn't a color change, bail */
|
||||||
}
|
}
|
||||||
|
|
||||||
let allCellsMetadataWithUpdatedColors = s.controls.allCellsMetadata.slice(
|
let cellsMetadataWithUpdatedColors = s.controls.cellsMetadata.slice(0);
|
||||||
0
|
|
||||||
);
|
|
||||||
let colorScale;
|
let colorScale;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
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 allCellsMetadata 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 cell["__color__"] and cell["__colorRGB__"]
|
This is available to all the draw functions as cell["__color__"] and cell["__colorRGB__"]
|
||||||
*/
|
*/
|
||||||
@@ -54,8 +52,8 @@ const updateCellColorsMiddleware = store => {
|
|||||||
if (action.type === "color by categorical metadata") {
|
if (action.type === "color by categorical metadata") {
|
||||||
colorScale = d3.scaleOrdinal().range(globals.ordinalColors);
|
colorScale = d3.scaleOrdinal().range(globals.ordinalColors);
|
||||||
|
|
||||||
for (let i = 0; i < allCellsMetadataWithUpdatedColors.length; i++) {
|
for (let i = 0; i < cellsMetadataWithUpdatedColors.length; i++) {
|
||||||
const cell = allCellsMetadataWithUpdatedColors[i];
|
const cell = cellsMetadataWithUpdatedColors[i];
|
||||||
let c = colorScale(cell[action.colorAccessor]);
|
let c = colorScale(cell[action.colorAccessor]);
|
||||||
cell.__color__ = c;
|
cell.__color__ = c;
|
||||||
cell.__colorRGB__ = parseRGB(c);
|
cell.__colorRGB__ = parseRGB(c);
|
||||||
@@ -68,10 +66,10 @@ const updateCellColorsMiddleware = store => {
|
|||||||
.domain([0, action.rangeMaxForColorAccessor])
|
.domain([0, action.rangeMaxForColorAccessor])
|
||||||
.range([1, 0]);
|
.range([1, 0]);
|
||||||
|
|
||||||
_.each(allCellsMetadataWithUpdatedColors, (cell, i) => {
|
_.each(cellsMetadataWithUpdatedColors, (cell, i) => {
|
||||||
let c = d3.interpolateViridis(colorScale(cell[action.colorAccessor]));
|
let c = d3.interpolateViridis(colorScale(cell[action.colorAccessor]));
|
||||||
allCellsMetadataWithUpdatedColors[i]["__color__"] = c;
|
cellsMetadataWithUpdatedColors[i]["__color__"] = c;
|
||||||
allCellsMetadataWithUpdatedColors[i]["__colorRGB__"] = parseRGB(c);
|
cellsMetadataWithUpdatedColors[i]["__colorRGB__"] = parseRGB(c);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,12 +111,12 @@ const updateCellColorsMiddleware = store => {
|
|||||||
0
|
0
|
||||||
]); /* invert viridis... probably pass this scale through to others */
|
]); /* invert viridis... probably pass this scale through to others */
|
||||||
|
|
||||||
_.each(allCellsMetadataWithUpdatedColors, (cell, i) => {
|
_.each(cellsMetadataWithUpdatedColors, (cell, i) => {
|
||||||
let c = d3.interpolateViridis(
|
let c = d3.interpolateViridis(
|
||||||
colorScale(expressionMap[cell.CellName][indexOfGene])
|
colorScale(expressionMap[cell.CellName][indexOfGene])
|
||||||
);
|
);
|
||||||
allCellsMetadataWithUpdatedColors[i]["__color__"] = c;
|
cellsMetadataWithUpdatedColors[i]["__color__"] = c;
|
||||||
allCellsMetadataWithUpdatedColors[i]["__colorRGB__"] = parseRGB(c);
|
cellsMetadataWithUpdatedColors[i]["__colorRGB__"] = parseRGB(c);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +124,7 @@ const updateCellColorsMiddleware = store => {
|
|||||||
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
|
||||||
*/
|
*/
|
||||||
let modifiedAction = Object.assign({}, action, {
|
let modifiedAction = Object.assign({}, action, {
|
||||||
allCellsMetadataWithUpdatedColors,
|
cellsMetadataWithUpdatedColors,
|
||||||
colorScale
|
colorScale
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,13 @@
|
|||||||
import uri from "urijs";
|
import uri from "urijs";
|
||||||
import * as globals from "../globals";
|
import * as globals from "../globals";
|
||||||
|
|
||||||
|
/*
|
||||||
|
XXX: this file should be obsolete. We just need to complete the refactoring
|
||||||
|
of parallel.js and it can be removed entirely.
|
||||||
|
|
||||||
|
It is currently not in use - the middleware constructor does not include include it
|
||||||
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
https://medium.com/@jacobp100/you-arent-using-redux-middleware-enough-94ffe991e6
|
https://medium.com/@jacobp100/you-arent-using-redux-middleware-enough-94ffe991e6
|
||||||
storeInstance => functionToCallWithAnActionThatWillSendItToTheNextMiddleware => actionThatDispatchWasCalledWith => valueToUseAsTheReturnValueOfTheDispatchCall
|
storeInstance => functionToCallWithAnActionThatWillSendItToTheNextMiddleware => actionThatDispatchWasCalledWith => valueToUseAsTheReturnValueOfTheDispatchCall
|
||||||
@@ -34,11 +41,7 @@ const updateCellSelectionMiddleware = store => {
|
|||||||
action.type === "categorical metadata filter none of these" ||
|
action.type === "categorical metadata filter none of these" ||
|
||||||
action.type === "categorical metadata filter all of these";
|
action.type === "categorical metadata filter all of these";
|
||||||
|
|
||||||
if (
|
if (!filterJustChanged || !s.controls.cellsMetadata) {
|
||||||
!filterJustChanged ||
|
|
||||||
!s.controls.allCellsOnClient
|
|
||||||
/* graph is set at the same time as allCells, so we assume it exists */
|
|
||||||
) {
|
|
||||||
return next(
|
return next(
|
||||||
action
|
action
|
||||||
); /* if the cells haven't loaded or the action wasn't a filter, bail */
|
); /* if the cells haven't loaded or the action wasn't a filter, bail */
|
||||||
@@ -48,7 +51,7 @@ const updateCellSelectionMiddleware = store => {
|
|||||||
- make a FRESH copy of all of the cells
|
- make a FRESH copy of all of the cells
|
||||||
- metadata has cellname and index, and that's all we ever need to reference cell info
|
- metadata has cellname and index, and that's all we ever need to reference cell info
|
||||||
*/
|
*/
|
||||||
let newSelection = s.controls.allCellsMetadata.slice(0);
|
let newSelection = s.controls.cellsMetadata.slice(0);
|
||||||
// _.forEach(newSelection, cell => (cell.__selected__ = true));
|
// _.forEach(newSelection, cell => (cell.__selected__ = true));
|
||||||
for (let i = 0; i < newSelection.length; i++) {
|
for (let i = 0; i < newSelection.length; i++) {
|
||||||
newSelection[i].__selected__ = true;
|
newSelection[i].__selected__ = true;
|
||||||
@@ -58,7 +61,7 @@ const updateCellSelectionMiddleware = store => {
|
|||||||
in plain language...
|
in plain language...
|
||||||
|
|
||||||
(a) once the cells have loaded.
|
(a) once the cells have loaded.
|
||||||
(b) each time a user changes ANY control we need to update allCellsMetadata
|
(b) each time a user changes ANY control we need to update cellsMetadata
|
||||||
there are two states:
|
there are two states:
|
||||||
|
|
||||||
1. control state we already know about (state.foo)
|
1. control state we already know about (state.foo)
|
||||||
|
|||||||
Vendored
+106
-81
@@ -65,14 +65,89 @@ function deduceDimensionType(attributes, fieldName) {
|
|||||||
return dimensionType;
|
return dimensionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create view state from /cells data response. Used both during a data
|
||||||
|
// load and during a graph reset.
|
||||||
|
//
|
||||||
|
function createViewState(schema, data) {
|
||||||
|
const cellsMetadata = data.metadata.slice(0);
|
||||||
|
|
||||||
|
/*
|
||||||
|
construct a copy of the ranges object that only has categorical
|
||||||
|
replace all counts with bool flags
|
||||||
|
ie., everything starts out checked
|
||||||
|
we mutate this map in the actions below
|
||||||
|
*/
|
||||||
|
const categoricalAsBooleansMap = {};
|
||||||
|
_.each(data.ranges, (value, key) => {
|
||||||
|
if (
|
||||||
|
key !== "CellName" &&
|
||||||
|
value.options /* it's categorical, it has options instead of ranges */
|
||||||
|
) {
|
||||||
|
const optionsAsBooleans = {};
|
||||||
|
_.each(value.options, (_value, _key) => {
|
||||||
|
optionsAsBooleans[_key] = true;
|
||||||
|
});
|
||||||
|
categoricalAsBooleansMap[key] = optionsAsBooleans;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const graph = data.graph;
|
||||||
|
_.each(cellsMetadata, (cell, idx) => {
|
||||||
|
cell.__cellIndex__ = idx;
|
||||||
|
cell.__color__ =
|
||||||
|
"rgba(0,0,0,1)"; /* initial color for all cells in all charts */
|
||||||
|
cell.__colorRGB__ = parseRGB(cell.__color__);
|
||||||
|
cell.__x__ = graph[idx][1];
|
||||||
|
cell.__y__ = graph[idx][2];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build the selection crossfilter.
|
||||||
|
//
|
||||||
|
let cellsCrossfilter = crossfilter(cellsMetadata);
|
||||||
|
let cellsDimensionsMap = {};
|
||||||
|
cellsDimensionsMap.x = cellsCrossfilter.dimension(r => r.__x__, Float32Array);
|
||||||
|
cellsDimensionsMap.y = cellsCrossfilter.dimension(r => r.__y__, Float32Array);
|
||||||
|
|
||||||
|
// Now walk the schema and make an appropriate dimension for each
|
||||||
|
// metadata field. This is a simplistic mapping, and could be
|
||||||
|
// optmized to use smaller scalars (to save memory) or larger
|
||||||
|
// floating point where precision is needed.
|
||||||
|
//
|
||||||
|
_.forEach(schema, (attributes, key) => {
|
||||||
|
if (key !== "CellName") {
|
||||||
|
const dimensionType = deduceDimensionType(attributes, key);
|
||||||
|
if (dimensionType) {
|
||||||
|
cellsDimensionsMap[key] = cellsCrossfilter.dimension(
|
||||||
|
r => r[key],
|
||||||
|
dimensionType
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
cellsMetadata,
|
||||||
|
crossfilter: {
|
||||||
|
cells: cellsCrossfilter,
|
||||||
|
dimensionMap: cellsDimensionsMap
|
||||||
|
},
|
||||||
|
categoricalAsBooleansMap
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const Controls = (
|
const Controls = (
|
||||||
state = {
|
state = {
|
||||||
|
/* Universe - all cells known to us. Set once, during initial load */
|
||||||
_ranges: null /* this comes from initialize, this is universe */,
|
_ranges: null /* this comes from initialize, this is universe */,
|
||||||
allGeneNames: null,
|
allGeneNames: null,
|
||||||
allCellsOnClient: null /* this comes from cells endpoint, this is world */,
|
allCells: null /* this comes from cells endpoint, this is universe */,
|
||||||
allCellsMetadata: null /* this comes from user actions, all draw components use this, it is created by middleware */,
|
allCellsMetadata: null /* this comes from cells endpoint, and is just the metadata for universe */,
|
||||||
|
|
||||||
|
/* View / World - all cells currently being displayed. May be a subset of Universe. */
|
||||||
|
cellsMetadata: null,
|
||||||
crossfilter: null /* the current user selection state */,
|
crossfilter: null /* the current user selection state */,
|
||||||
categoricalAsBooleansMap: null,
|
categoricalAsBooleansMap: null,
|
||||||
|
|
||||||
colorAccessor: null,
|
colorAccessor: null,
|
||||||
colorScale: null,
|
colorScale: null,
|
||||||
opacityForDeselectedCells: 0.2,
|
opacityForDeselectedCells: 0.2,
|
||||||
@@ -101,91 +176,41 @@ const Controls = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
case "request cells success": {
|
case "request cells success": {
|
||||||
const allCellsMetadata = action.data.data.metadata.slice(0);
|
|
||||||
const allCellsMetadataMap = _.keyBy(allCellsMetadata, "CellName");
|
|
||||||
|
|
||||||
/*
|
|
||||||
construct a copy of the ranges object that only has categorical
|
|
||||||
replace all counts with bool flags
|
|
||||||
ie., everything starts out checked
|
|
||||||
we mutate this map in the actions below
|
|
||||||
*/
|
|
||||||
const categoricalAsBooleansMap = {};
|
|
||||||
_.each(action.data.data.ranges, (value, key) => {
|
|
||||||
if (
|
|
||||||
key !== "CellName" &&
|
|
||||||
value.options /* it's categorical, it has options instead of ranges */
|
|
||||||
) {
|
|
||||||
const optionsAsBooleans = {};
|
|
||||||
_.each(value.options, (_value, _key) => {
|
|
||||||
optionsAsBooleans[_key] = true;
|
|
||||||
});
|
|
||||||
categoricalAsBooleansMap[key] = optionsAsBooleans;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const graph = action.data.data.graph;
|
|
||||||
_.each(allCellsMetadata, (cell, idx) => {
|
|
||||||
cell.__cellIndex__ = idx;
|
|
||||||
cell.__color__ =
|
|
||||||
"rgba(0,0,0,1)"; /* initial color for all cells in all charts */
|
|
||||||
cell.__colorRGB__ = parseRGB(cell.__color__);
|
|
||||||
cell.__x__ = graph[idx][1];
|
|
||||||
cell.__y__ = graph[idx][2];
|
|
||||||
});
|
|
||||||
|
|
||||||
// Build the selection crossfilter.
|
|
||||||
//
|
|
||||||
let cellsCrossfilter = crossfilter(allCellsMetadata);
|
|
||||||
let cellsDimensionsMap = {};
|
|
||||||
cellsDimensionsMap.x = cellsCrossfilter.dimension(
|
|
||||||
r => r.__x__,
|
|
||||||
Float32Array
|
|
||||||
);
|
|
||||||
cellsDimensionsMap.y = cellsCrossfilter.dimension(
|
|
||||||
r => r.__y__,
|
|
||||||
Float32Array
|
|
||||||
);
|
|
||||||
|
|
||||||
// Now walk the schema and make an appropriate dimension for each
|
|
||||||
// metadata field. This is a simplistic mapping, and could be
|
|
||||||
// optmized to use smaller scalars (to save memory) or larger
|
|
||||||
// floating point where precision is needed.
|
|
||||||
//
|
|
||||||
// If we don't have a schema (bad server!), fake it by inferring
|
// If we don't have a schema (bad server!), fake it by inferring
|
||||||
// important fields from the ranges element.
|
// important fields from the ranges element.
|
||||||
//
|
//
|
||||||
if (!state.schema) {
|
if (!state.schema) {
|
||||||
state.schema = createSchemaByDataSniffing(action.data.data.ranges);
|
state.schema = createSchemaByDataSniffing(data.ranges);
|
||||||
}
|
}
|
||||||
_.forEach(state.schema, (attributes, key) => {
|
|
||||||
if (key !== "CellName") {
|
/* Set viewable world to the provided cell data */
|
||||||
const dimensionType = deduceDimensionType(attributes, key);
|
const viewState = createViewState(state.schema, action.data.data);
|
||||||
if (dimensionType) {
|
|
||||||
cellsDimensionsMap[key] = cellsCrossfilter.dimension(
|
|
||||||
r => r[key],
|
|
||||||
dimensionType
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
// this is only used as a flag that data has loaded. Could be
|
/* Universe - initialize once */
|
||||||
// removed (other variables would suffice for the same test).
|
allCells: state.allCells ? state.allCells : action.data,
|
||||||
allCellsOnClient: action.data.data,
|
allCellsMetadata: state.allCellsMetadata
|
||||||
allCellsMetadata,
|
? state.allCellsMetadata
|
||||||
allCellsMetadataMap,
|
: viewState.cellsMetadata,
|
||||||
categoricalAsBooleansMap,
|
allCellsMetadataMap: state.allCellsMetadataMap
|
||||||
crossfilter: {
|
? state.allCellsMetadataMap
|
||||||
cells: cellsCrossfilter,
|
: _.keyBy(viewState.cellsMetadata, "CellName"),
|
||||||
dimensionMap: cellsDimensionsMap
|
|
||||||
},
|
/* World */
|
||||||
|
...viewState,
|
||||||
|
|
||||||
graphBrushSelection: null /* if we are getting new cells from the server, the layout (probably? definitely?) just changed, so this is now irrelevant, and we WILL need to call a function to reset state of this kind when cells success happens */
|
graphBrushSelection: null /* if we are getting new cells from the server, the layout (probably? definitely?) just changed, so this is now irrelevant, and we WILL need to call a function to reset state of this kind when cells success happens */
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/* * * * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * * * *
|
||||||
User events
|
User events
|
||||||
* * * * * * * * * * * * * * * * * */
|
* * * * * * * * * * * * * * * * * */
|
||||||
|
case "reset graph": {
|
||||||
|
/* Reset viewable world to the entire Universe */
|
||||||
|
const viewState = createViewState(state.schema, state.allCells.data);
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
...viewState
|
||||||
|
});
|
||||||
|
}
|
||||||
case "parallel coordinates axes have been drawn": {
|
case "parallel coordinates axes have been drawn": {
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
axesHaveBeenDrawn: true
|
axesHaveBeenDrawn: true
|
||||||
@@ -338,23 +363,23 @@ const Controls = (
|
|||||||
case "color by continuous metadata":
|
case "color by continuous metadata":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
colorAccessor: action.colorAccessor,
|
colorAccessor: action.colorAccessor,
|
||||||
allCellsMetadata:
|
cellsMetadata:
|
||||||
action.allCellsMetadataWithUpdatedColors /* this comes from middleware */,
|
action.cellsMetadataWithUpdatedColors /* this comes from middleware */,
|
||||||
colorScale: action.colorScale
|
colorScale: action.colorScale
|
||||||
});
|
});
|
||||||
case "color by expression":
|
case "color by expression":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
colorAccessor: action.gene,
|
colorAccessor: action.gene,
|
||||||
allCellsMetadata:
|
cellsMetadata:
|
||||||
action.allCellsMetadataWithUpdatedColors /* this comes from middleware */,
|
action.cellsMetadataWithUpdatedColors /* this comes from middleware */,
|
||||||
colorScale: action.colorScale
|
colorScale: action.colorScale
|
||||||
});
|
});
|
||||||
case "color by categorical metadata":
|
case "color by categorical metadata":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
colorAccessor:
|
colorAccessor:
|
||||||
action.colorAccessor /* pass the scale through additionally, and it's a legend! */,
|
action.colorAccessor /* pass the scale through additionally, and it's a legend! */,
|
||||||
allCellsMetadata:
|
cellsMetadata:
|
||||||
action.allCellsMetadataWithUpdatedColors /* this comes from middleware */,
|
action.cellsMetadataWithUpdatedColors /* this comes from middleware */,
|
||||||
colorScale: action.colorScale
|
colorScale: action.colorScale
|
||||||
});
|
});
|
||||||
case "store current cell selection as differential set 1":
|
case "store current cell selection as differential set 1":
|
||||||
|
|||||||
Reference in New Issue
Block a user