#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

@@ -17,18 +17,15 @@ import HistogramBrush from "./histogramBrush";
import { margin, width, height, createDimensions } 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 metadata = _.get(state.controls.world, "obsAnnotations", null);
const ranges = _.get(state.controls.world, "summary.obs", null);
return {
ranges,
metadata,
initializeRanges,
colorAccessor: state.controls.colorAccessor,
colorScale: state.controls.colorScale,
graphBrushSelection: state.controls.graphBrushSelection,
axesHaveBeenDrawn: state.controls.axesHaveBeenDrawn
selectionUpdate: _.get(state.controls, "crossfilter.updateTime", null)
};
})
class Continuous extends React.Component {
@@ -41,17 +38,19 @@ class Continuous extends React.Component {
dimensions: null
};
}
handleBrushAction(selection) {
this.props.dispatch({
type: "continuous selection using parallel coords brushing",
data: selection
});
}
handleColorAction(key) {
this.props.dispatch({
type: "color by continuous metadata",
colorAccessor: key,
rangeMaxForColorAccessor: this.props.initializeRanges[key].range.max
rangeMaxForColorAccessor: this.props.ranges[key].range.max
});
}
@@ -60,7 +59,7 @@ class Continuous extends React.Component {
<div>
{_.map(this.props.ranges, (value, key) => {
const isColorField = key.includes("color") || key.includes("Color");
if (value.range && key !== "CellName" && !isColorField) {
if (value.range && key !== "name" && !isColorField) {
return (
<HistogramBrush
key={key}

View File

@@ -8,87 +8,90 @@ import React from "react";
import _ from "lodash";
import { connect } from "react-redux";
import FaPaintBrush from "react-icons/lib/fa/paint-brush";
import * as globals from "../../globals";
import * as d3 from "d3";
import memoize from "memoize-one";
import * as globals from "../../globals";
@connect(state => {
const initializeRanges = _.get(state, "initialize.data.data.ranges", null);
return {
initializeRanges,
initializeRanges: _.get(state.controls.world, "summary.obs"),
colorAccessor: state.controls.colorAccessor,
colorScale: state.controls.colorScale,
cellsMetadata: state.controls.cellsMetadata
obsAnnotations: _.get(state.controls.world, "obsAnnotations", null)
};
})
class HistogramBrush extends React.Component {
calcHistogramCache = memoize((obsAnnotations, metadataField, ranges) => {
// recalculate expensive stuff
const allValuesForContinuousFieldAsArray = _.map(
obsAnnotations,
metadataField
);
const histogramCache = {};
histogramCache.x = d3
.scaleLinear()
.domain([ranges.min, ranges.max])
.range([0, this.width]);
histogramCache.y = d3
.scaleLinear()
.range([this.height - this.marginBottom, 0]);
// .range([height - margin.bottom, margin.top]);
histogramCache.bins = d3
.histogram()
.domain(histogramCache.x.domain())
.thresholds(40)(allValuesForContinuousFieldAsArray);
histogramCache.numValues = allValuesForContinuousFieldAsArray.length;
return histogramCache;
});
constructor(props) {
super(props);
this.width = 300;
this.height = 100;
this.marginBottom = 20;
this.histogramCache = {};
this.state = {
svg: null,
ctx: null,
axes: null,
dimensions: null,
brush: null
};
}
calcHistogramCache(nextProps) {
// recalculate expensive stuff
const allValuesForContinuousFieldAsArray = _.map(
nextProps.cellsMetadata,
nextProps.metadataField
);
this.histogramCache.x = d3
.scaleLinear()
.domain([nextProps.ranges.min, nextProps.ranges.max])
.range([0, this.width]);
this.histogramCache.y = d3
.scaleLinear()
.range([this.height - this.marginBottom, 0]);
// .range([height - margin.bottom, margin.top]);
this.histogramCache.bins = d3
.histogram()
.domain(this.histogramCache.x.domain())
.thresholds(40)(allValuesForContinuousFieldAsArray);
this.histogramCache.numValues = allValuesForContinuousFieldAsArray.length;
}
componentWillMount() {
this.calcHistogramCache(this.props);
}
onBrush(selection, x) {
return () => {
const { dispatch, metadataField } = this.props;
if (d3.event.selection) {
this.props.dispatch({
dispatch({
type: "continuous metadata histogram brush",
selection: this.props.metadataField,
selection: metadataField,
range: [x(d3.event.selection[0]), x(d3.event.selection[1])]
});
} else {
this.props.dispatch({
dispatch({
type: "continuous metadata histogram brush",
selection: this.props.metadataField,
selection: metadataField,
range: null
});
}
};
}
drawHistogram(svgRef) {
const x = this.histogramCache.x;
const y = this.histogramCache.y;
const bins = this.histogramCache.bins;
const numValues = this.histogramCache.numValues;
const { obsAnnotations, metadataField, ranges } = this.props;
const histogramCache = this.calcHistogramCache(
obsAnnotations,
metadataField,
ranges
);
const { x, y, bins, numValues } = histogramCache;
d3.select(svgRef)
.selectAll(".bar")
.remove();
d3.select(svgRef)
.insert("g", "*")
@@ -97,6 +100,7 @@ class HistogramBrush extends React.Component {
.data(bins)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", function(d) {
return x(d.x0) + 1;
})
@@ -144,6 +148,7 @@ class HistogramBrush extends React.Component {
this.setState({ brush, xAxis });
}
}
handleColorAction() {
this.props.dispatch({
type: "color by continuous metadata",
@@ -153,6 +158,7 @@ class HistogramBrush extends React.Component {
].range.max
});
}
render() {
return (
<div

View File

@@ -1,145 +0,0 @@
// jshint esversion: 6
/* rc slider https://www.npmjs.com/package/rc-slider */
import React from "react";
import _ from "lodash";
import { connect } from "react-redux";
import styles from "./parallelCoordinates.css";
import SectionHeader from "../framework/sectionHeader";
import setupParallelCoordinates from "./setupParallelCoordinates";
import drawAxes from "./drawAxes";
import drawLinesCanvas from "./drawLinesCanvas";
import { margin, width, height, createDimensions } 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);
return {
ranges,
metadata,
initializeRanges,
colorAccessor: state.controls.colorAccessor,
colorScale: state.controls.colorScale,
graphBrushSelection: state.controls.graphBrushSelection,
cellsMetadata: state.controls.cellsMetadata,
axesHaveBeenDrawn: state.controls.axesHaveBeenDrawn
};
})
class Parallel extends React.Component {
constructor(props) {
super(props);
this.state = {
svg: null,
ctx: null,
axes: null,
dimensions: null
};
}
componentDidMount() {
const { svg, ctx } = setupParallelCoordinates(width, height, margin);
this.setState({ svg, ctx });
}
componentWillReceiveProps(nextProps) {
this.maybeDrawAxes(nextProps);
this.maybeDrawLines(nextProps);
}
maybeDrawAxes(nextProps) {
if (
!this.state.axes &&
nextProps.initializeRanges /* axes are created on full range of data */
) {
const dimensions = createDimensions(nextProps.initializeRanges);
const xscale = d3
.scalePoint()
.domain(d3.range(dimensions.length))
.range([0, width]);
const axes = drawAxes(
this.state.svg,
this.state.ctx,
dimensions,
xscale,
height,
width,
this.handleBrushAction.bind(this),
this.handleColorAction.bind(this)
);
this.setState({
axes,
xscale,
dimensions
});
this.props.dispatch({
type: "parallel coordinates axes have been drawn"
});
}
}
maybeDrawLines = _.debounce(nextProps => {
/* https://stackoverflow.com/questions/23123138/perform-debounce-in-react-js */
if (
nextProps.ranges &&
nextProps.cellsMetadata &&
nextProps.axesHaveBeenDrawn
) {
if (this.state._drawLinesCanvas) {
this.state._drawLinesCanvas.invalidate(); /* this is only necessary if the internals of drawLinesCanvas are using the render queue */
}
this.state.ctx.clearRect(0, 0, width, height);
const _drawLinesCanvas = drawLinesCanvas(
nextProps.cellsMetadata,
this.state.dimensions,
this.state.xscale,
this.state.ctx,
nextProps.colorAccessor,
nextProps.colorScale
);
this.setState({
_drawLinesCanvas /* this will only exist if the internals of drawLinesCanvas are using the render queue */
});
}
}, 200);
handleBrushAction(selection) {
this.props.dispatch({
type: "continuous selection using parallel coords brushing",
data: selection
});
}
handleColorAction(key) {
this.props.dispatch({
type: "color by continuous metadata",
colorAccessor: key,
rangeMaxForColorAccessor: this.props.initializeRanges[key].range.max
});
}
render() {
return (
<div id="parcoords_wrapper">
<div
className={styles.parcoords}
id="parcoords"
style={{
width: width + margin.left + margin.right + "px",
height: height + margin.top + margin.bottom + "px"
}}
/>
</div>
);
}
}
export default Parallel;
// <SectionHeader text="Continuous Metadata"/>