replace canvas with webgl scatter

This commit is contained in:
Colin Megill
2018-04-19 01:53:30 -07:00
parent c4e994f2db
commit d9af0c5e51
2 changed files with 78 additions and 94 deletions
+64 -80
View File
@@ -27,34 +27,6 @@ import {
createDimensions, createDimensions,
} from "./util"; } from "./util";
const generatePoints = function (count) {
return Array(count).fill().map(function () {
return [
Math.random() - 0.5,
Math.random() - 0.5
]
})
}
const generateColors = function (count) {
return Array(count).fill().map(function () {
return [
Math.random(),
Math.random(),
Math.random()
]
})
}
const generateSizes = function (count) {
return Array(count).fill().map(function () {
return Math.random() * 10
})
}
// set constants
const count = 1283;
@connect((state) => { @connect((state) => {
const ranges = state.cells.cells && state.cells.cells.data.ranges ? state.cells.cells.data.ranges : null; const ranges = state.cells.cells && state.cells.cells.data.ranges ? state.cells.cells.data.ranges : null;
@@ -81,7 +53,7 @@ class Scatterplot extends React.Component {
this.count = 0; this.count = 0;
this.state = { this.state = {
svg: null, svg: null,
ctx: null, // ctx: null,
axes: null, axes: null,
dimensions: null, dimensions: null,
xScale: null, xScale: null,
@@ -90,12 +62,18 @@ class Scatterplot extends React.Component {
} }
componentDidMount() { componentDidMount() {
const {svg, ctx} = setupScatterplot( const {
svg
// ctx
} = setupScatterplot(
width, width,
height, height,
margin margin
); );
this.setState({svg, ctx}) this.setState({
svg
// ctx
})
const camera = _camera(this.reglCanvas, {scale: true, rotate: false}); const camera = _camera(this.reglCanvas, {scale: true, rotate: false});
const regl = _regl(this.reglCanvas) const regl = _regl(this.reglCanvas)
@@ -103,9 +81,9 @@ class Scatterplot extends React.Component {
const drawPoints = _drawPoints(regl) const drawPoints = _drawPoints(regl)
// preallocate buffers // preallocate buffers
const pointBuffer = regl.buffer(generatePoints(count)) const pointBuffer = regl.buffer()
const colorBuffer = regl.buffer(generateColors(count)) const colorBuffer = regl.buffer()
const sizeBuffer = regl.buffer(generateSizes(count)) const sizeBuffer = regl.buffer()
regl.frame(({viewportWidth, viewportHeight}) => { regl.frame(({viewportWidth, viewportHeight}) => {
@@ -120,7 +98,7 @@ class Scatterplot extends React.Component {
color: colorBuffer, color: colorBuffer,
position: pointBuffer, position: pointBuffer,
size: sizeBuffer, size: sizeBuffer,
count: count, count: this.count,
view: camera.view(), view: camera.view(),
scale: viewportHeight / viewportWidth scale: viewportHeight / viewportWidth
}) })
@@ -137,19 +115,50 @@ class Scatterplot extends React.Component {
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
this.maybeSetupScalesAndDrawAxes(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.props.scatterplotYYaccessor !== prevProps.scatterplotYYaccessor
) {
this.drawAxesSVG(this.state.xScale, this.state.yScale);
}
// if (
// this.state.xScale &&
// this.state.yScale
// ) {
// drawScatterplotCanvas(
// this.state.ctx,
// this.state.xScale,
// this.state.yScale,
// this.props.currentCellSelection,
// this.props.opacityForDeselectedCells,
// this.props.expression,
// this.props.scatterplotXXaccessor,
// this.props.scatterplotYYaccessor,
// )
// }
if ( if (
this.state.regl && this.state.regl &&
this.state.pointBuffer && this.state.pointBuffer &&
this.state.colorBuffer && this.state.colorBuffer &&
this.state.sizeBuffer && this.state.sizeBuffer &&
nextProps.currentCellSelection && this.props.currentCellSelection &&
nextProps.expression.data && this.props.expression.data &&
nextProps.expression.data.genes && this.props.expression.data.genes &&
nextProps.scatterplotXXaccessor && this.props.scatterplotXXaccessor &&
nextProps.scatterplotYYaccessor this.props.scatterplotYYaccessor &&
this.state.xScale &&
this.state.yScale
) { ) {
const _currentCellSelectionMap = _.keyBy(nextProps.currentCellSelection, "CellName"); /* move me to the reducer */ const _currentCellSelectionMap = _.keyBy(this.props.currentCellSelection, "CellName"); /* move me to the reducer */
const positions = []; const positions = [];
const colors = []; const colors = [];
@@ -161,21 +170,21 @@ class Scatterplot extends React.Component {
const glScaleY = d3.scaleLinear() const glScaleY = d3.scaleLinear()
.domain([0, height]) .domain([0, height])
.range([-.95, .95]) .range([-1, 1])
/* /*
Construct Vectors Construct Vectors
*/ */
_.each(nextProps.expression.data.cells, (cell, i) => { _.each(this.props.expression.data.cells, (cell, i) => {
/* /*
this if is necessary until we are no longer getting expression for all cells, but only for 'world' 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' ...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([ positions.push([
glScaleX(this.state.xScale(cell.e[nextProps.expression.data.genes.indexOf(nextProps.scatterplotXXaccessor)])), /* scale each point first to the window as we calculate extents separately below, so no need to repeat */ 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[nextProps.expression.data.genes.indexOf(nextProps.scatterplotYYaccessor)])) glScaleY(this.state.yScale(cell.e[this.props.expression.data.genes.indexOf(this.props.scatterplotYYaccessor)]))
]) ])
let c = _currentCellSelectionMap[cell.cellname]["__color__"]; let c = _currentCellSelectionMap[cell.cellname]["__color__"];
@@ -199,13 +208,6 @@ class Scatterplot extends React.Component {
} }
}) })
console.log("+++++++++++++++++++++++++++")
console.log('RANDOM', generatePoints(count))
console.log('------------------------------')
console.log("POINTS", positions)
console.log("+++++++++++++++++++++++++++")
this.state.pointBuffer(positions) this.state.pointBuffer(positions)
this.state.colorBuffer(colors) this.state.colorBuffer(colors)
this.state.sizeBuffer(sizes) this.state.sizeBuffer(sizes)
@@ -215,32 +217,6 @@ class Scatterplot extends React.Component {
this.count = positions.length this.count = positions.length
} }
} }
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.props.scatterplotYYaccessor !== prevProps.scatterplotYYaccessor
) {
this.drawAxesSVG(this.state.xScale, this.state.yScale);
}
if (
this.state.xScale &&
this.state.yScale
) {
drawScatterplotCanvas(
this.state.ctx,
this.state.xScale,
this.state.yScale,
this.props.currentCellSelection,
this.props.opacityForDeselectedCells,
this.props.expression,
this.props.scatterplotXXaccessor,
this.props.scatterplotYYaccessor,
)
}
}
maybeSetupScalesAndDrawAxes(nextProps) { maybeSetupScalesAndDrawAxes(nextProps) {
if ( if (
nextProps.expression && nextProps.expression &&
@@ -323,8 +299,16 @@ class Scatterplot extends React.Component {
width: width + margin.left + margin.right + "px", width: width + margin.left + margin.right + "px",
height: height + margin.top + margin.bottom + "px", height: height + margin.top + margin.bottom + "px",
}} }}
></div> >
<canvas width={width} height={height} style={{border: "1px solid black"}} ref={(canvas) => { this.reglCanvas = canvas}}/> <canvas
width={width}
height={height}
style={{
marginLeft: margin.left - 5,
marginTop: margin.top
}}
ref={(canvas) => { this.reglCanvas = canvas}}/>
</div>
</div> </div>
) )
} }
+14 -14
View File
@@ -18,23 +18,23 @@ const setupScatterplot = (
.append("g") .append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var canvas = container.append("canvas") // var canvas = container.append("canvas")
.attr("width", width * devicePixelRatio) // .attr("width", width * devicePixelRatio)
.attr("height", height * devicePixelRatio) // .attr("height", height * devicePixelRatio)
.style("width", width + "px") // .style("width", width + "px")
.style("height", height + "px") // .style("height", height + "px")
.style("margin-left", margin.left + 1 + "px") /* magic number: this seems to be visually correct when it is equal to the amount axis is translated by plus or minus... a little? */ // .style("margin-left", margin.left + 1 + "px") /* magic number: this seems to be visually correct when it is equal to the amount axis is translated by plus or minus... a little? */
.style("margin-top", margin.top + "px") // .style("margin-top", margin.top + "px")
//
var ctx = canvas.node().getContext("2d"); // var ctx = canvas.node().getContext("2d");
ctx.globalCompositeOperation = 'darken'; // ctx.globalCompositeOperation = 'darken';
ctx.globalAlpha = 0.15; // ctx.globalAlpha = 0.15;
ctx.lineWidth = 1.5; // ctx.lineWidth = 1.5;
ctx.scale(devicePixelRatio, devicePixelRatio); // ctx.scale(devicePixelRatio, devicePixelRatio);
return { return {
svg, svg,
ctx, // ctx,
} }
} }