diff --git a/src/actions/index.js b/src/actions/index.js index 9cd0e823..7d9b7002 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -65,6 +65,12 @@ const regraph = () => { }; }; +const resetGraph = () => { + return (dispatch, getState) => { + dispatch({ type: "reset graph" }); + }; +}; + const initialize = () => { return (dispatch, getState) => { dispatch({ type: "initialize started" }); @@ -89,7 +95,7 @@ const initialize = () => { // function cleanupExpressionResponse(data) { const s = store.getState(); - const metadata = s.controls.currentCellSelectionMap; + const metadata = s.controls.allCellsMetadataMap; let errorFound = false; data.data.cells = _.filter(data.data.cells, cell => { if (!errorFound && !metadata[cell.cellname]) { @@ -218,6 +224,7 @@ export default { initialize, requestCells, regraph, + resetGraph, requestGeneExpressionCounts, requestGeneExpressionCountsPOST, requestSingleGeneExpressionCountsForColoringPOST, diff --git a/src/components/continuous/continuous.js b/src/components/continuous/continuous.js index 86f38e5a..226b6d0f 100644 --- a/src/components/continuous/continuous.js +++ b/src/components/continuous/continuous.js @@ -38,7 +38,6 @@ import { margin, width, height, createDimensions } from "./util"; colorAccessor: state.controls.colorAccessor, colorScale: state.controls.colorScale, graphBrushSelection: state.controls.graphBrushSelection, - currentCellSelection: state.controls.currentCellSelection, axesHaveBeenDrawn: state.controls.axesHaveBeenDrawn }; }) diff --git a/src/components/continuous/histogramBrush.js b/src/components/continuous/histogramBrush.js index 4ac59c34..1b67e687 100644 --- a/src/components/continuous/histogramBrush.js +++ b/src/components/continuous/histogramBrush.js @@ -26,7 +26,7 @@ import { connect } from "react-redux"; return { colorAccessor: state.controls.colorAccessor, colorScale: state.controls.colorScale, - currentCellSelection: state.controls.currentCellSelection + cellsMetadata: state.controls.cellsMetadata }; }) class HistogramBrush extends React.Component { @@ -52,7 +52,7 @@ class HistogramBrush extends React.Component { calcHistogramCache(nextProps) { // recalculate expensive stuff const allValuesForContinuousFieldAsArray = _.map( - nextProps.currentCellSelection, + nextProps.cellsMetadata, nextProps.metadataField ); diff --git a/src/components/continuous/parallel.js b/src/components/continuous/parallel.js index ce3bd03e..0f53069e 100644 --- a/src/components/continuous/parallel.js +++ b/src/components/continuous/parallel.js @@ -36,7 +36,7 @@ import { margin, width, height, createDimensions } from "./util"; colorAccessor: state.controls.colorAccessor, colorScale: state.controls.colorScale, graphBrushSelection: state.controls.graphBrushSelection, - currentCellSelection: state.controls.currentCellSelection, + cellsMetadata: state.controls.cellsMetadata, axesHaveBeenDrawn: state.controls.axesHaveBeenDrawn }; }) @@ -96,7 +96,7 @@ class Parallel extends React.Component { /* https://stackoverflow.com/questions/23123138/perform-debounce-in-react-js */ if ( nextProps.ranges && - nextProps.currentCellSelection && + nextProps.cellsMetadata && nextProps.axesHaveBeenDrawn ) { if (this.state._drawLinesCanvas) { @@ -106,7 +106,7 @@ class Parallel extends React.Component { this.state.ctx.clearRect(0, 0, width, height); const _drawLinesCanvas = drawLinesCanvas( - nextProps.currentCellSelection, + nextProps.cellsMetadata, this.state.dimensions, this.state.xscale, this.state.ctx, diff --git a/src/components/expression/cellSetButtons.js b/src/components/expression/cellSetButtons.js index f3e8c32e..5291ba7e 100644 --- a/src/components/expression/cellSetButtons.js +++ b/src/components/expression/cellSetButtons.js @@ -9,13 +9,7 @@ import actions from "../../actions"; @connect() class CellSetButton extends React.Component { set() { - const set = []; - - _.each(this.props.currentCellSelection, cell => { - if (cell["__selected__"]) { - set.push(cell.CellName); - } - }); + const set = _.map(this.props.crossfilter.cells.allFiltered(), "CellName"); this.props.dispatch({ type: diff --git a/src/components/expression/expressionButtons.js b/src/components/expression/expressionButtons.js index 50c1c91c..1f0aa481 100644 --- a/src/components/expression/expressionButtons.js +++ b/src/components/expression/expressionButtons.js @@ -8,8 +8,8 @@ import CellSetButton from "./cellSetButtons"; @connect(state => { return { - currentCellSelection: state.controls.currentCellSelection, - differential: state.differential + differential: state.differential, + crossfilter: state.controls.crossfilter }; }) class Expression extends React.Component { @@ -43,7 +43,9 @@ class Expression extends React.Component {
There are currently {" " + - _.filter(this.props.currentCellSelection, "__selected__").length + + (this.props.crossfilter + ? this.props.crossfilter.cells.countFiltered() + : 0) + " "} cells selected, click a cell set button to store them.
diff --git a/src/components/graph/graph.js b/src/components/graph/graph.js index 8a8efd92..4156f935 100644 --- a/src/components/graph/graph.js +++ b/src/components/graph/graph.js @@ -22,31 +22,11 @@ import FaSave from "react-icons/lib/fa/download"; /* https://bl.ocks.org/mbostock/9078690 - quadtree for onClick / hover selections */ @connect(state => { - const vertices = - state.cells.cells && state.cells.cells.data.graph - ? state.cells.cells.data.graph - : null; - const ranges = - state.cells.cells && state.cells.cells.data.ranges - ? state.cells.cells.data.ranges - : null; - const metadata = - state.cells.cells && state.cells.cells.data.metadata - ? state.cells.cells.data.metadata - : null; - return { - ranges, - vertices, - metadata, - colorAccessor: state.controls.colorAccessor, - colorScale: state.controls.colorScale, - continuousSelection: state.controls.continuousSelection, - graphVec: state.controls.graphVec, - currentCellSelection: state.controls.currentCellSelection, - graphBrushSelection: state.controls.graphBrushSelection, + cellsMetadata: state.controls.cellsMetadata, opacityForDeselectedCells: state.controls.opacityForDeselectedCells, - responsive: state.responsive + responsive: state.responsive, + crossfilter: state.controls.crossfilter }; }) class Graph extends React.Component { @@ -55,6 +35,10 @@ class Graph extends React.Component { this.count = 0; this.inverse = mat4.identity([]); this.graphPaddingTop = 100; + this.renderCache = { + positions: null, + colors: null + }; this.state = { drawn: false, svg: null, @@ -104,42 +88,74 @@ class Graph extends React.Component { } componentWillReceiveProps(nextProps) { - if (this.state.regl && nextProps.vertices) { - /* update regl */ - const vertices = nextProps.currentCellSelection; - const vertexCount = vertices.length; - const positions = new Float32Array(2 * vertexCount); - const colors = new Float32Array(3 * vertexCount); - const sizes = new Float32Array(vertexCount); + if (this.state.regl && nextProps.crossfilter) { + /* update the regl state */ + const crossfilter = nextProps.crossfilter.cells; + const cells = crossfilter.all(); + const cellCount = cells.length; - // d3.scaleLinear().domain([0,1]).range([-1,1]) - const glScaleX = scaleLinear([0, 1], [-1, 1]); - // d3.scaleLinear().domain([0,1]).range([1,-1]) - const glScaleY = scaleLinear([0, 1], [1, -1]); + // X/Y positions for each point - a cached value that only + // changes if we have loaded entirely new cell data + // + if ( + !this.renderCache.positions || + this.props.crossfilter.cells != nextProps.crossfilter.cells + ) { + if (!this.renderCache.positions) + this.renderCache.positions = new Float32Array(2 * cellCount); - /* - Construct Vectors - */ - const graphVec = nextProps.graphVec; - for (var i = 0; i < vertexCount; i++) { - const cell = vertices[i]; - const cellIdx = cell.__cellIndex__; - const x = glScaleX(graphVec[2 * cellIdx]); - const y = glScaleY(graphVec[2 * cellIdx + 1]); - positions[2 * i] = x; - positions[2 * i + 1] = y; + // d3.scaleLinear().domain([0,1]).range([-1,1]) + const glScaleX = scaleLinear([0, 1], [-1, 1]); + // d3.scaleLinear().domain([0,1]).range([1,-1]) + const glScaleY = scaleLinear([0, 1], [1, -1]); - colors.set(cell.__colorRGB__, 3 * i); - - sizes[i] = cell.__selected__ - ? 4 - : 0.2; /* make this a function of the number of total cells, including regraph */ + for ( + let i = 0, positions = this.renderCache.positions; + i < cellCount; + i++ + ) { + positions[2 * i] = glScaleX(cells[i].__x__); + positions[2 * i + 1] = glScaleY(cells[i].__y__); + } + this.state.pointBuffer({ + data: this.renderCache.positions, + dimension: 2 + }); } - this.state.pointBuffer({ data: positions, dimension: 2 }); - this.state.colorBuffer({ data: colors, dimension: 3 }); - this.state.sizeBuffer({ data: sizes, dimension: 1 }); - this.count = vertexCount; + // Colors for each point - a cached value that only changes when + // the cell metadata changes (done by updateCellColors middleware). + // NOTE: this is a slightly pessimistic assumption, as the metadata + // could have changed for some other reason, but for now color is + // the only metadata that changes client-side. If this is problematic, + // we could add some sort of color-specific indicator to the app state. + if ( + !this.renderCache.colors || + this.props.cellsMetadata != nextProps.cellsMetadata + ) { + if (!this.renderCache.colors) + this.renderCache.colors = new Float32Array(3 * cellCount); + for (let i = 0, colors = this.renderCache.colors; i < cellCount; i++) { + colors.set(cells[i].__colorRGB__, 3 * i); + } + this.state.colorBuffer({ data: this.renderCache.colors, dimension: 3 }); + } + + // Sizes for each point - this is presumed to change each time the + // component receives new props. Almost always a true assumption, as + // most property upates are due to changes driving a crossfilter + // selection set change. + // + if ( + !this.renderCache.sizes || + this.props.crossfilter.cells != nextProps.crossfilter.cells + ) { + this.renderCache.sizes = new Float32Array(cellCount); + } + crossfilter.fillByIsFiltered(this.renderCache.sizes, 4, 0.2); + this.state.sizeBuffer({ data: this.renderCache.sizes, dimension: 1 }); + + this.count = cellCount; } if ( @@ -228,6 +244,24 @@ class Graph extends React.Component { alignItems: "baseline" }} > +