mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-19 10:58:10 +08:00
* 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:
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"/>
|
||||
Reference in New Issue
Block a user