mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-09 19:40:55 +08:00
replace canvas with webgl scatter
This commit is contained in:
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user