This commit is contained in:
bkmartinjr
2018-05-05 13:15:10 -07:00
parent d91d42f2c4
commit 90c7daf571
62 changed files with 2398 additions and 1912 deletions
+142 -107
View File
@@ -1,33 +1,37 @@
// jshint esversion: 6
// https://bl.ocks.org/Jverma/076377dd0125b1a508621441752735fc
// https://peterbeshai.com/scatterplot-in-d3-with-voronoi-interaction.html
import React from 'react';
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 styles from "./scatterplot.css";
import drawScatterplotCanvas from "./drawScatterplotCanvas";
import mat4 from 'gl-mat4';
import fit from 'canvas-fit';
import _camera from '../../util/camera.js'
import _regl from 'regl'
import _drawPoints from './drawPointsRegl'
import mat4 from "gl-mat4";
import fit from "canvas-fit";
import _camera from "../../util/camera.js";
import _regl from "regl";
import _drawPoints from "./drawPointsRegl";
import {
margin,
width,
height,
createDimensions,
} from "./util";
import { margin, width, height, createDimensions } from "./util";
@connect((state) => {
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;
const initializeRanges = state.initialize.data && state.initialize.data.data.ranges ? state.initialize.data.data.ranges : null;
@connect(state => {
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;
const initializeRanges =
state.initialize.data && state.initialize.data.data.ranges
? state.initialize.data.data.ranges
: null;
return {
ranges,
@@ -40,8 +44,8 @@ import {
scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
opacityForDeselectedCells: state.controls.opacityForDeselectedCells,
differential: state.differential,
expression: state.expression,
}
expression: state.expression
};
})
class Scatterplot extends React.Component {
constructor(props) {
@@ -53,39 +57,31 @@ class Scatterplot extends React.Component {
axes: null,
dimensions: null,
xScale: null,
yScale: null,
yScale: null
};
}
componentDidMount() {
const {
svg
} = setupScatterplot(
width,
height,
margin
);
const { svg } = setupScatterplot(width, height, margin);
this.setState({
svg
})
});
const camera = _camera(this.reglCanvas, {scale: true, rotate: false});
const regl = _regl(this.reglCanvas)
const camera = _camera(this.reglCanvas, { scale: true, rotate: false });
const regl = _regl(this.reglCanvas);
const drawPoints = _drawPoints(regl)
const drawPoints = _drawPoints(regl);
// preallocate buffers
const pointBuffer = regl.buffer()
const colorBuffer = regl.buffer()
const sizeBuffer = regl.buffer()
regl.frame(({viewportWidth, viewportHeight}) => {
const pointBuffer = regl.buffer();
const colorBuffer = regl.buffer();
const sizeBuffer = regl.buffer();
regl.frame(({ viewportWidth, viewportHeight }) => {
regl.clear({
depth: 1,
color: [1, 1, 1, 1]
})
});
drawPoints({
distance: camera.distance,
@@ -95,29 +91,28 @@ class Scatterplot extends React.Component {
count: this.count,
view: camera.view(),
scale: viewportHeight / viewportWidth
})
});
camera.tick()
})
camera.tick();
});
this.setState({
regl,
sizeBuffer,
pointBuffer,
colorBuffer,
})
colorBuffer
});
}
componentWillReceiveProps(nextProps) {
this.maybeSetupScalesAndDrawAxes(nextProps);
}
componentDidUpdate(prevProps) {
if (
(this.state.xScale && this.state.yScale) &&
(this.props.scatterplotXXaccessor && this.props.scatterplotYYaccessor) &&
this.props.scatterplotXXaccessor !== prevProps.scatterplotXXaccessor || // was CLU now FTH1 etc
(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
) {
this.drawAxesSVG(this.state.xScale, this.state.yScale);
@@ -136,20 +131,24 @@ class Scatterplot extends React.Component {
this.state.xScale &&
this.state.yScale
) {
const _currentCellSelectionMap = _.keyBy(this.props.currentCellSelection, "CellName"); /* move me to the reducer */
const _currentCellSelectionMap = _.keyBy(
this.props.currentCellSelection,
"CellName"
); /* move me to the reducer */
const positions = [];
const colors = [];
const sizes = [];
const glScaleX = d3.scaleLinear()
const glScaleX = d3
.scaleLinear()
.domain([0, width])
.range([-.95, .95]) /* padding */
.range([-0.95, 0.95]); /* padding */
const glScaleY = d3.scaleLinear()
const glScaleY = d3
.scaleLinear()
.domain([0, height])
.range([-1, 1])
.range([-1, 1]);
/*
Construct Vectors
@@ -159,21 +158,40 @@ class Scatterplot extends React.Component {
this if is necessary until we are no longer getting expression for all cells, but only for 'world'
...which will mean refetching when we regraph, or 'go back up to all cells'
*/
if (_currentCellSelectionMap[cell.cellname]) { /* fails silently, sometimes this is undefined, in which case the graph array should be shorter than the cell array, check in reducer */
if (_currentCellSelectionMap[cell.cellname]) {
/* fails silently, sometimes this is undefined, in which case the graph array should be shorter than the cell array, check in reducer */
positions.push([
glScaleX(this.state.xScale(cell.e[this.props.expression.data.genes.indexOf(this.props.scatterplotXXaccessor)])), /* scale each point first to the window as we calculate extents separately below, so no need to repeat */
glScaleY(this.state.yScale(cell.e[this.props.expression.data.genes.indexOf(this.props.scatterplotYYaccessor)]))
])
glScaleX(
this.state.xScale(
cell.e[
this.props.expression.data.genes.indexOf(
this.props.scatterplotXXaccessor
)
]
)
) /* scale each point first to the window as we calculate extents separately below, so no need to repeat */,
glScaleY(
this.state.yScale(
cell.e[
this.props.expression.data.genes.indexOf(
this.props.scatterplotYYaccessor
)
]
)
)
]);
colors.push(_currentCellSelectionMap[cell.cellname]["__colorRGB__"])
sizes.push(_currentCellSelectionMap[cell.cellname]["__selected__"] ? 4 : .2) /* make this a function of the number of total cells, including regraph */
colors.push(_currentCellSelectionMap[cell.cellname]["__colorRGB__"]);
sizes.push(
_currentCellSelectionMap[cell.cellname]["__selected__"] ? 4 : 0.2
); /* make this a function of the number of total cells, including regraph */
}
})
});
this.state.pointBuffer(positions)
this.state.colorBuffer(colors)
this.state.sizeBuffer(sizes)
this.count = positions.length
this.state.pointBuffer(positions);
this.state.colorBuffer(colors);
this.state.sizeBuffer(sizes);
this.count = positions.length;
}
}
maybeSetupScalesAndDrawAxes(nextProps) {
@@ -183,61 +201,75 @@ class Scatterplot extends React.Component {
nextProps.scatterplotXXaccessor &&
nextProps.scatterplotYYaccessor
) {
const xScale = d3.scaleLinear()
.domain(d3.extent(nextProps.expression.data.cells, (cell, i) => {
return cell.e[nextProps.expression.data.genes.indexOf(nextProps.scatterplotXXaccessor)]
}))
.range([0, width])
const xScale = d3
.scaleLinear()
.domain(
d3.extent(nextProps.expression.data.cells, (cell, i) => {
return cell.e[
nextProps.expression.data.genes.indexOf(
nextProps.scatterplotXXaccessor
)
];
})
)
.range([0, width]);
const yScale = d3.scaleLinear()
.domain(d3.extent(nextProps.expression.data.cells, (cell) => {
return cell.e[nextProps.expression.data.genes.indexOf(nextProps.scatterplotYYaccessor)]
}))
.range([height, 0])
const yScale = d3
.scaleLinear()
.domain(
d3.extent(nextProps.expression.data.cells, cell => {
return cell.e[
nextProps.expression.data.genes.indexOf(
nextProps.scatterplotYYaccessor
)
];
})
)
.range([height, 0]);
this.setState({
xScale,
yScale
})
});
}
}
drawAxesSVG(xScale, yScale) {
this.state.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);
var xAxis = d3.axisBottom().scale(xScale);
var yAxis = d3.axisLeft()
.scale(yScale);
var 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.
this.state.svg.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'x axis')
this.state.svg
.append("g")
.attr("transform", "translate(0," + height + ")")
.attr("class", "x axis")
.call(xAxis);
// y-axis is translated to (0,0)
this.state.svg.append('g')
.attr('transform', 'translate(0,0)')
.attr('class', 'y axis')
this.state.svg
.append("g")
.attr("transform", "translate(0,0)")
.attr("class", "y axis")
.call(yAxis);
// adding label. For x-axis, it's at (10, 10), and for y-axis at (width, height-10).
this.state.svg.append('text')
.attr('x', 10)
.attr('y', 10)
.attr('class', 'label')
this.state.svg
.append("text")
.attr("x", 10)
.attr("y", 10)
.attr("class", "label")
.text(this.props.scatterplotYYaccessor);
this.state.svg.append('text')
.attr('x', width)
.attr('y', height - 10)
.attr('text-anchor', 'end')
.attr('class', 'label')
this.state.svg
.append("text")
.attr("x", width)
.attr("y", height - 10)
.attr("text-anchor", "end")
.attr("class", "label")
.text(this.props.scatterplotXXaccessor);
}
render() {
@@ -248,15 +280,16 @@ class Scatterplot extends React.Component {
borderRadius: 3,
marginTop: 15,
paddingBottom: 20,
boxShadow: "3px 4px 13px 0px rgba(201,201,201,1)",
boxShadow: "3px 4px 13px 0px rgba(201,201,201,1)"
}}
id="scatterplot_wrapper">
id="scatterplot_wrapper"
>
<div
className={styles.scatterplot}
id="scatterplot"
style={{
width: width + margin.left + margin.right + "px",
height: height + margin.top + margin.bottom + "px",
width: width + margin.left + margin.right + "px",
height: height + margin.top + margin.bottom + "px"
}}
>
<canvas
@@ -266,14 +299,16 @@ class Scatterplot extends React.Component {
marginLeft: margin.left - 7,
marginTop: margin.top
}}
ref={(canvas) => { this.reglCanvas = canvas}}/>
ref={canvas => {
this.reglCanvas = canvas;
}}
/>
</div>
</div>
)
);
}
};
}
export default Scatterplot;
// <SectionHeader text="Continuous Metadata"/>