#130 redux refactor (#208)

* mocks for redux refactor - for discussion

* more API design on redux refactor

* add new reuqired dependencies for build

* change babel target to use modern browser

* remove dead code

* remove dead code - joy plots

* checkpoint on redux refactoring

* checkpoint on redux refactoring

* fix mistaken rebase conflict resolution

* dead code removal; add name to dataframe backmap

* rename dataframe to universe

* update eslint config to more closely match prettier

* lint

* more eslint updates to match prettier

* additional config to make eslint match prettier

* add expression data to Universe/World

* remove obsolete reducers

* lint fixes

* more eslint cleanup

* lint

* lint

* fix but in countAllOnes when dimensions gt 1

* lint; do not display name metadata field

* lint; colors refactor

* lint; colors refactor

* update comments

* first cut at regraph and reset

* enable object-curly-braces consistent mode

* lint, handle regraph with no selection

* fix expression scatterplot bugs

* fix regression legend display

* add expression data cache

* remove console logging

* reset cell color on regraph/reset

* remove obsolete server URLs

* rename UniverseV01 to Universe_REST_API_v01

* add additional comments on the varDataCache

* merge universe reducer into controls reducer; simplify initialization-related actions

* use spread operator

* fix erroneous comment

* convert universe and world state to plain objects, and functionalize supporting code (remove ES6 classes)

* use spread operator

* lint

* improve variable names

* rename obsCrossfilter to crossfilter and obsDimensionMap to dimensionMap

* rename controls2 to controls
This commit is contained in:
Bruce Martin
2018-09-17 20:43:39 -07:00
committed by GitHub
parent 7b00fea44d
commit d4e850d18f
40 changed files with 2008 additions and 1584 deletions

View File

@@ -5,38 +5,55 @@
import React from "react";
import _ from "lodash";
import { connect } from "react-redux";
import scatterplot from "./scatterplot";
import setupScatterplot from "./setupScatterplot";
import styles from "./scatterplot.css";
import _regl from "regl";
import * as d3 from "d3";
import mat4 from "gl-mat4";
import fit from "canvas-fit";
import _camera from "../../util/camera.js";
import _regl from "regl";
import _camera from "../../util/camera";
import setupScatterplot from "./setupScatterplot";
import styles from "./scatterplot.css";
import _drawPoints from "./drawPointsRegl";
import { scaleLinear } from "../../util/scaleLinear";
import { margin, width, height, createDimensions } from "./util";
import { margin, width, height } from "./util";
@connect(state => {
const ranges = _.get(state, "cells.cells.data.ranges", null);
const metadata = _.get(state, "cells.cells.data.metadata", null);
const initializeRanges = _.get(state, "initialize.data.data.ranges", null);
const {
world,
crossfilter,
scatterplotXXaccessor,
scatterplotYYaccessor
} = state.controls;
const expressionX =
world && scatterplotXXaccessor
? state.controls.world.varDataCache[scatterplotXXaccessor]
: null;
const expressionY =
world && scatterplotYYaccessor
? state.controls.world.varDataCache[scatterplotYYaccessor]
: null;
return {
ranges,
metadata,
initializeRanges,
world,
colorRGB: state.controls.colorRGB,
colorAccessor: state.controls.colorAccessor,
colorScale: state.controls.colorScale,
scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
// Accessors are var/gene names (strings)
scatterplotXXaccessor,
scatterplotYYaccessor,
opacityForDeselectedCells: state.controls.opacityForDeselectedCells,
crossfilter: state.controls.crossfilter,
differential: state.differential,
expression: state.expression
expressionX,
expressionY,
crossfilter,
// updated whenever the crossfilter selection is updated
selectionUpdate: _.get(state.controls, "crossfilter.updateTime", null)
};
})
class Scatterplot extends React.Component {
@@ -46,9 +63,6 @@ class Scatterplot extends React.Component {
this.axes = false;
this.state = {
svg: null,
// ctx: null,
axes: null,
dimensions: null,
xScale: null,
yScale: null
};
@@ -57,19 +71,10 @@ class Scatterplot extends React.Component {
componentDidMount() {
const { svg } = setupScatterplot(width, height, margin);
let scales;
const { expressionX, expressionY } = this.props;
/* if we've already got the data, user clicked back and forth between tabs, so render the scatterplot */
if (
this.props.expression &&
this.props.expression.data &&
this.props.scatterplotXXaccessor &&
this.props.scatterplotYYaccessor
) {
scales = this.setupScales(
this.props.expression,
this.props.scatterplotXXaccessor,
this.props.scatterplotYYaccessor
);
if (svg && expressionX && expressionY) {
scales = Scatterplot.setupScales(expressionX, expressionY);
this.drawAxesSVG(scales.xScale, scales.yScale, svg);
}
@@ -115,115 +120,101 @@ class Scatterplot extends React.Component {
colorBuffer
});
}
componentDidUpdate(prevProps) {
const {
svg,
xScale,
yScale,
regl,
pointBuffer,
colorBuffer,
sizeBuffer
} = this.state;
const {
world,
crossfilter,
scatterplotXXaccessor,
scatterplotYYaccessor,
expressionX,
expressionY,
colorRGB
} = this.props;
if (
this.state.svg &&
this.state.xScale &&
this.state.yScale &&
this.props.scatterplotXXaccessor &&
this.props.scatterplotYYaccessor &&
(this.props.scatterplotXXaccessor !== prevProps.scatterplotXXaccessor || // was CLU now FTH1 etc
this.props.scatterplotYYaccessor !== prevProps.scatterplotYYaccessor || // was CLU now FTH1 etc
world &&
svg &&
xScale &&
yScale &&
scatterplotXXaccessor &&
scatterplotYYaccessor &&
(scatterplotXXaccessor !== prevProps.scatterplotXXaccessor || // was CLU now FTH1 etc
scatterplotYYaccessor !== prevProps.scatterplotYYaccessor || // was CLU now FTH1 etc
!this.axes) // clicked off the tab and back again, rerender
) {
this.drawAxesSVG(this.state.xScale, this.state.yScale, this.state.svg);
this.drawAxesSVG(xScale, yScale, svg);
}
if (
this.props.metadata &&
this.state.regl &&
this.state.pointBuffer &&
this.state.colorBuffer &&
this.state.sizeBuffer &&
this.props.expression.data &&
this.props.expression.data.genes &&
this.props.scatterplotXXaccessor &&
this.props.scatterplotYYaccessor &&
this.state.xScale &&
this.state.yScale
world &&
regl &&
pointBuffer &&
colorBuffer &&
sizeBuffer &&
expressionX &&
expressionY &&
scatterplotXXaccessor &&
scatterplotYYaccessor &&
xScale &&
yScale
) {
const crossfilter = this.props.crossfilter.cells;
const data = this.props.expression.data;
const cells = data.cells;
const genes = data.genes;
const cellCount = cells.length;
const positions = new Float32Array(2 * cellCount);
const colors = new Float32Array(3 * cellCount);
const sizes = new Float32Array(cellCount);
const cellCount = expressionX.length;
const positionsBuf = new Float32Array(2 * cellCount);
const colorsBuf = new Float32Array(3 * cellCount);
const sizesBuf = new Float32Array(cellCount);
// d3.scaleLinear().domain([0, width]).range([-0.95, 0.95])
const glScaleX = scaleLinear([0, width], [-0.95, 0.95]);
// d3.scaleLinear().domain([0, height]).range([-1, 1])
const glScaleY = scaleLinear([0, height], [-1, 1]);
const geneXXaccessorIndex = genes.indexOf(
this.props.scatterplotXXaccessor
);
const geneYYaccessorIndex = genes.indexOf(
this.props.scatterplotYYaccessor
);
/*
Construct Vectors
*/
for (let i = 0; i < cellCount; i++) {
const cell = cells[i];
positions[2 * i] = glScaleX(
this.state.xScale(cell.e[geneXXaccessorIndex])
); /* scale each point first to the window as we calculate extents separately below, so no need to repeat */
positions[2 * i + 1] = glScaleY(
this.state.yScale(cell.e[geneYYaccessorIndex])
);
for (let i = 0; i < cellCount; i += 1) {
positionsBuf[2 * i] = glScaleX(xScale(expressionX[i]));
positionsBuf[2 * i + 1] = glScaleY(yScale(expressionY[i]));
}
for (let i = 0; i < cellCount; i++) {
const metadata = this.props.metadata[i];
colors.set(metadata.__colorRGB__, 3 * i);
for (let i = 0; i < cellCount; i += 1) {
colorsBuf.set(colorRGB[i], 3 * i);
}
crossfilter.fillByIsFiltered(sizes, 4, 0.2);
crossfilter.fillByIsFiltered(sizesBuf, 4, 0.2);
this.state.pointBuffer({ data: positions, dimension: 2 });
this.state.colorBuffer({ data: colors, dimension: 3 });
this.state.sizeBuffer({ data: sizes, dimension: 1 });
pointBuffer({ data: positionsBuf, dimension: 2 });
colorBuffer({ data: colorsBuf, dimension: 3 });
sizeBuffer({ data: sizesBuf, dimension: 1 });
this.count = cellCount;
}
if (
this.props.expression &&
this.props.expression.data &&
this.props.scatterplotXXaccessor &&
this.props.scatterplotYYaccessor &&
(this.props.scatterplotXXaccessor !== prevProps.scatterplotXXaccessor || // was CLU now FTH1 etc
this.props.scatterplotYYaccessor !== prevProps.scatterplotYYaccessor)
expressionX &&
expressionY &&
(scatterplotXXaccessor !== prevProps.scatterplotXXaccessor || // was CLU now FTH1 etc
scatterplotYYaccessor !== prevProps.scatterplotYYaccessor)
) {
const scales = this.setupScales(
this.props.expression,
this.props.scatterplotXXaccessor,
this.props.scatterplotYYaccessor
);
const scales = Scatterplot.setupScales(expressionX, expressionY);
this.setState(scales);
}
}
setupScales(expression, scatterplotXXaccessor, scatterplotYYaccessor) {
static setupScales(expressionX, expressionY) {
const xScale = d3
.scaleLinear()
.domain(
d3.extent(expression.data.cells, (cell, i) => {
return cell.e[expression.data.genes.indexOf(scatterplotXXaccessor)];
})
)
.domain(d3.extent(expressionX))
.range([0, width]);
const yScale = d3
.scaleLinear()
.domain(
d3.extent(expression.data.cells, cell => {
return cell.e[expression.data.genes.indexOf(scatterplotYYaccessor)];
})
)
.domain(d3.extent(expressionY))
.range([height, 0]);
return {
@@ -231,15 +222,19 @@ class Scatterplot extends React.Component {
yScale
};
}
drawAxesSVG(xScale, yScale, svg) {
const { scatterplotYYaccessor, scatterplotXXaccessor } = this.props;
svg.selectAll("*").remove();
// the axes are much cleaner and easier now. No need to rotate and orient the axis, just call axisBottom, axisLeft etc.
var xAxis = d3.axisBottom().scale(xScale);
// the axes are much cleaner and easier now. No need to rotate and orient
// the axis, just call axisBottom, axisLeft etc.
const xAxis = d3.axisBottom().scale(xScale);
var yAxis = d3.axisLeft().scale(yScale);
const yAxis = d3.axisLeft().scale(yScale);
// adding axes is also simpler now, just translate x-axis to (0,height) and it's alread defined to be a bottom axis.
// adding axes is also simpler now, just translate x-axis to (0,height)
// and it's alread defined to be a bottom axis.
svg
.append("g")
.attr("transform", "translate(0," + height + ")")
@@ -259,7 +254,7 @@ class Scatterplot extends React.Component {
.attr("x", 10)
.attr("y", 10)
.attr("class", "label")
.text(this.props.scatterplotYYaccessor);
.text(scatterplotYYaccessor);
svg
.append("text")
@@ -267,7 +262,7 @@ class Scatterplot extends React.Component {
.attr("y", height - 10)
.attr("text-anchor", "end")
.attr("class", "label")
.text(this.props.scatterplotXXaccessor);
.text(scatterplotXXaccessor);
}
render() {