mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-02 18:08:13 +08:00
drawing graph
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import styles from './parallelCoordinates.css';
|
import styles from './parallelCoordinates.css';
|
||||||
|
import renderQueue from "./renderQueue";
|
||||||
import {
|
import {
|
||||||
margin,
|
margin,
|
||||||
width,
|
width,
|
||||||
@@ -17,89 +18,6 @@ Canvas Parallel coordinates with svg brushing via /* via https://bl.ocks.org/syn
|
|||||||
******************************************
|
******************************************
|
||||||
******************************************/
|
******************************************/
|
||||||
|
|
||||||
/*****************************************
|
|
||||||
******************************************
|
|
||||||
Render Queue via http://bl.ocks.org/syntagmatic/raw/3341641/render-queue.js
|
|
||||||
******************************************
|
|
||||||
******************************************/
|
|
||||||
|
|
||||||
const renderQueue = (function(callback1234) {
|
|
||||||
var _queue = [], // data to be rendered
|
|
||||||
_rate = 1000, // number of calls per frame
|
|
||||||
_invalidate = function() {}, // invalidate last render queue
|
|
||||||
_clear = function() {}; // clearing function
|
|
||||||
|
|
||||||
var rq = function(data) {
|
|
||||||
if (data) rq.data(data);
|
|
||||||
_invalidate();
|
|
||||||
_clear();
|
|
||||||
rq.render();
|
|
||||||
};
|
|
||||||
|
|
||||||
rq.render = function() {
|
|
||||||
var valid = true;
|
|
||||||
_invalidate = rq.invalidate = function() {
|
|
||||||
valid = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
function doFrame() {
|
|
||||||
if (!valid) return true;
|
|
||||||
var chunk = _queue.splice(0,_rate);
|
|
||||||
chunk.map(callback1234);
|
|
||||||
timer_frame(doFrame);
|
|
||||||
}
|
|
||||||
|
|
||||||
doFrame();
|
|
||||||
};
|
|
||||||
|
|
||||||
rq.data = function(data) {
|
|
||||||
_invalidate();
|
|
||||||
_queue = data.slice(0); // creates a copy of the data
|
|
||||||
return rq;
|
|
||||||
};
|
|
||||||
|
|
||||||
rq.add = function(data) {
|
|
||||||
_queue = _queue.concat(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
rq.rate = function(value) {
|
|
||||||
if (!arguments.length) return _rate;
|
|
||||||
_rate = value;
|
|
||||||
return rq;
|
|
||||||
};
|
|
||||||
|
|
||||||
rq.remaining = function() {
|
|
||||||
return _queue.length;
|
|
||||||
};
|
|
||||||
|
|
||||||
// clear the canvas
|
|
||||||
rq.clear = function(func) {
|
|
||||||
if (!arguments.length) {
|
|
||||||
_clear();
|
|
||||||
return rq;
|
|
||||||
}
|
|
||||||
_clear = func;
|
|
||||||
return rq;
|
|
||||||
};
|
|
||||||
|
|
||||||
rq.invalidate = _invalidate;
|
|
||||||
|
|
||||||
var timer_frame = window.requestAnimationFrame
|
|
||||||
|| window.webkitRequestAnimationFrame
|
|
||||||
|| window.mozRequestAnimationFrame
|
|
||||||
|| window.oRequestAnimationFrame
|
|
||||||
|| window.msRequestAnimationFrame
|
|
||||||
|| function(callback) { setTimeout(callback, 17); };
|
|
||||||
|
|
||||||
return rq;
|
|
||||||
});
|
|
||||||
|
|
||||||
/*****************************************
|
|
||||||
******************************************
|
|
||||||
MAIN: drawParallelCoordinates
|
|
||||||
******************************************
|
|
||||||
******************************************/
|
|
||||||
|
|
||||||
const drawParallelCoordinates = (data) => {
|
const drawParallelCoordinates = (data) => {
|
||||||
|
|
||||||
const d3_functor = (v) => {
|
const d3_functor = (v) => {
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*****************************************
|
||||||
|
******************************************
|
||||||
|
Render Queue via http://bl.ocks.org/syntagmatic/raw/3341641/render-queue.js
|
||||||
|
******************************************
|
||||||
|
******************************************/
|
||||||
|
|
||||||
|
const renderQueue = (function(callback1234) {
|
||||||
|
var _queue = [], // data to be rendered
|
||||||
|
_rate = 1000, // number of calls per frame
|
||||||
|
_invalidate = function() {}, // invalidate last render queue
|
||||||
|
_clear = function() {}; // clearing function
|
||||||
|
|
||||||
|
var rq = function(data) {
|
||||||
|
if (data) rq.data(data);
|
||||||
|
_invalidate();
|
||||||
|
_clear();
|
||||||
|
rq.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
rq.render = function() {
|
||||||
|
var valid = true;
|
||||||
|
_invalidate = rq.invalidate = function() {
|
||||||
|
valid = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
function doFrame() {
|
||||||
|
if (!valid) return true;
|
||||||
|
var chunk = _queue.splice(0,_rate);
|
||||||
|
chunk.map(callback1234);
|
||||||
|
timer_frame(doFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
doFrame();
|
||||||
|
};
|
||||||
|
|
||||||
|
rq.data = function(data) {
|
||||||
|
_invalidate();
|
||||||
|
_queue = data.slice(0); // creates a copy of the data
|
||||||
|
return rq;
|
||||||
|
};
|
||||||
|
|
||||||
|
rq.add = function(data) {
|
||||||
|
_queue = _queue.concat(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
rq.rate = function(value) {
|
||||||
|
if (!arguments.length) return _rate;
|
||||||
|
_rate = value;
|
||||||
|
return rq;
|
||||||
|
};
|
||||||
|
|
||||||
|
rq.remaining = function() {
|
||||||
|
return _queue.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
// clear the canvas
|
||||||
|
rq.clear = function(func) {
|
||||||
|
if (!arguments.length) {
|
||||||
|
_clear();
|
||||||
|
return rq;
|
||||||
|
}
|
||||||
|
_clear = func;
|
||||||
|
return rq;
|
||||||
|
};
|
||||||
|
|
||||||
|
rq.invalidate = _invalidate;
|
||||||
|
|
||||||
|
var timer_frame = window.requestAnimationFrame
|
||||||
|
|| window.webkitRequestAnimationFrame
|
||||||
|
|| window.mozRequestAnimationFrame
|
||||||
|
|| window.oRequestAnimationFrame
|
||||||
|
|| window.msRequestAnimationFrame
|
||||||
|
|| function(callback) { setTimeout(callback, 17); };
|
||||||
|
|
||||||
|
return rq;
|
||||||
|
});
|
||||||
|
|
||||||
|
export default renderQueue;
|
||||||
|
|||||||
@@ -1,118 +1,67 @@
|
|||||||
var margin = {top: 20, right: 10, bottom: 30, left: 40},
|
import styles from "./graph.css";
|
||||||
|
import renderQueue from "../continuous/renderQueue";
|
||||||
|
|
||||||
|
const margin = {top: 20, right: 10, bottom: 30, left: 40},
|
||||||
width = 960 - margin.left - margin.right,
|
width = 960 - margin.left - margin.right,
|
||||||
height = 500 - margin.top - margin.bottom;
|
height = 500 - margin.top - margin.bottom;
|
||||||
|
|
||||||
var svg = d3.select("body").append("svg")
|
const x = d3.scaleLinear()
|
||||||
.attr("width", width + margin.left + margin.right)
|
|
||||||
.attr("height", height + margin.top + margin.bottom)
|
|
||||||
.append("g")
|
|
||||||
.attr("transform", "translate(" + margin.left + " " + margin.top + ")");
|
|
||||||
|
|
||||||
var factory = d3.geom.quadtree()
|
|
||||||
.extent([
|
|
||||||
[0, 0],
|
|
||||||
[width, height]
|
|
||||||
]);
|
|
||||||
|
|
||||||
var x = d3.scale.linear()
|
|
||||||
.range([0, width]);
|
.range([0, width]);
|
||||||
|
|
||||||
var y = d3.scale.linear()
|
const y = d3.scaleLinear()
|
||||||
.range([height, 0]);
|
.range([height, 0]);
|
||||||
|
|
||||||
var xAxis = d3.svg.axis()
|
/******************************************
|
||||||
.scale(x)
|
*******************************************
|
||||||
.orient("bottom");
|
Main
|
||||||
|
*******************************************
|
||||||
|
******************************************/
|
||||||
|
|
||||||
var yAxis = d3.svg.axis()
|
const drawGraph = (data) => {
|
||||||
.scale(y)
|
|
||||||
.orient("left");
|
|
||||||
|
|
||||||
var xg = svg.append("g")
|
var svg = d3.select("#graphAttachPoint").append("svg")
|
||||||
.attr("class", "x axis")
|
.attr("width", width + margin.left + margin.right)
|
||||||
.attr("transform", "translate(0," + height + ")");
|
.attr("height", height + margin.top + margin.bottom)
|
||||||
|
.attr("class", `${styles.graphSVG}`)
|
||||||
|
.append("g")
|
||||||
|
.attr("transform", "translate(" + margin.left + " " + margin.top + ")");
|
||||||
|
|
||||||
var yg = svg.append("g")
|
var chartArea = d3.select("#graphAttachPoint").append("div")
|
||||||
.attr("class", "y axis");
|
.style("left", margin.left + "px")
|
||||||
|
.style("top", margin.top + "px");
|
||||||
|
|
||||||
var chartArea = d3.select("body").append("div")
|
var canvas = chartArea.append("canvas")
|
||||||
.style("left", margin.left + "px")
|
.attr("width", width)
|
||||||
.style("top", margin.top + "px");
|
.attr("height", height)
|
||||||
|
.attr("class", `${styles.graphCanvas}`);
|
||||||
|
|
||||||
var canvas = chartArea.append("canvas")
|
var context = canvas.node().getContext("2d");
|
||||||
.attr("width", width)
|
|
||||||
.attr("height", height);
|
|
||||||
|
|
||||||
var context = canvas.node().getContext("2d");
|
context.fillStyle = "rgba(0,0,0,.5)";
|
||||||
|
|
||||||
context.fillStyle = "#f0f";
|
const draw = () => {
|
||||||
|
|
||||||
// Layer on top of canvas, example of selection details
|
// Update canvas
|
||||||
var highlight = chartArea.append("svg")
|
context.clearRect(0, 0, width, height);
|
||||||
.attr("width", width)
|
|
||||||
.attr("height", height)
|
|
||||||
.append("circle")
|
|
||||||
.attr("r", 7)
|
|
||||||
.classed("hidden", true);
|
|
||||||
|
|
||||||
redraw();
|
data.forEach((p, i) => {
|
||||||
|
|
||||||
function redraw() {
|
context.beginPath();
|
||||||
|
/* context.arc(x,y,r,sAngle,eAngle,counterclockwise); */
|
||||||
|
context.arc(
|
||||||
|
x(p[1]), /* x */
|
||||||
|
y(p[2]), /* y */
|
||||||
|
2, /* r */
|
||||||
|
0, /* sAngle */
|
||||||
|
2 * Math.PI /* eAngle */
|
||||||
|
);
|
||||||
|
context.fill();
|
||||||
|
|
||||||
// Randomize the scale
|
});
|
||||||
var scale = 1 + Math.floor(Math.random() * 10);
|
}
|
||||||
|
|
||||||
// Redraw axes
|
draw();
|
||||||
x.domain([0, scale]);
|
|
||||||
y.domain([0, scale]);
|
|
||||||
xg.call(xAxis);
|
|
||||||
yg.call(yAxis);
|
|
||||||
|
|
||||||
var points = randomPoints(scale);
|
|
||||||
|
|
||||||
var tree = factory(points);
|
|
||||||
|
|
||||||
// Update canvas
|
|
||||||
context.clearRect(0, 0, width, height);
|
|
||||||
|
|
||||||
points.forEach(function(p,i){
|
|
||||||
|
|
||||||
context.beginPath();
|
|
||||||
context.arc(x(p[0]), y(p[1]), 5, 0, 2 * Math.PI);
|
|
||||||
context.fill();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
canvas.on("mousemove",function(){
|
|
||||||
|
|
||||||
var mouse = d3.mouse(this),
|
|
||||||
closest = tree.find([x.invert(mouse[0]), y.invert(mouse[1])]);
|
|
||||||
|
|
||||||
highlight.attr("cx", x(closest[0]))
|
|
||||||
.attr("cy", y(closest[1]));
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
canvas.on("mouseover",function(){
|
|
||||||
highlight.classed("hidden", false);
|
|
||||||
});
|
|
||||||
|
|
||||||
canvas.on("mouseout",function(){
|
|
||||||
highlight.classed("hidden", true);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function randomPoints(scale) {
|
export default drawGraph;
|
||||||
|
|
||||||
// Get points
|
|
||||||
return d3.range(1000).map(function(d){
|
|
||||||
|
|
||||||
return [
|
|
||||||
Math.random() * scale,
|
|
||||||
Math.random() * scale
|
|
||||||
];
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
path,
|
.graphCanvas path {
|
||||||
line {
|
|
||||||
shape-rendering: crispEdges;
|
shape-rendering: crispEdges;
|
||||||
}
|
}
|
||||||
|
|
||||||
div,
|
#graphWrapper {
|
||||||
svg,
|
position: relative;
|
||||||
canvas {
|
}
|
||||||
|
|
||||||
|
#graphAttachPoint,
|
||||||
|
.graphSVG,
|
||||||
|
.graphCanvas {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
#graphSVG {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,31 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
// import styles from "./joy.css";
|
import styles from "./graph.css";
|
||||||
// import drawGraph from "./drawGraph";
|
import drawGraph from "./drawGraph";
|
||||||
// import joyParser from "./joyParser";
|
|
||||||
|
|
||||||
class Graph extends React.Component {
|
class Graph extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
drawn: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (nextProps.data) {
|
/* maybe should do a check here to confirm ref exists and pass it? */
|
||||||
console.log('graph data 55', nextProps.data)
|
if (nextProps.data && !this.state.drawn) {
|
||||||
// drawGraph(graphParser(nextProps.data));
|
console.log('About to draw graph. Data:', nextProps.data)
|
||||||
|
drawGraph(nextProps.data.data)
|
||||||
|
this.setState({drawn: true});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div id="joyplot_wrapper" style={{marginTop: 50}}>
|
<div id="graphWrapper">
|
||||||
<h3> Graph </h3>
|
<h3> Graph </h3>
|
||||||
<div id="joyplot"> </div>
|
<div id="graphAttachPoint"> </div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-3
@@ -19,7 +19,7 @@ class Home extends React.Component {
|
|||||||
graph: null,
|
graph: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
componentWillMount() {
|
componentDidMount() {
|
||||||
// d3.csv("http://ec2-34-234-75-156.compute-1.amazonaws.com/api/v0.1/metadata", (d) => {
|
// d3.csv("http://ec2-34-234-75-156.compute-1.amazonaws.com/api/v0.1/metadata", (d) => {
|
||||||
// console.log("from ec2",d)
|
// console.log("from ec2",d)
|
||||||
// })
|
// })
|
||||||
@@ -51,13 +51,22 @@ class Home extends React.Component {
|
|||||||
<Container>
|
<Container>
|
||||||
<Helmet title="cellxgene" />
|
<Helmet title="cellxgene" />
|
||||||
<h1><Link to="/page2">cellxgene</Link></h1>
|
<h1><Link to="/page2">cellxgene</Link></h1>
|
||||||
|
<button
|
||||||
|
className={buttonStyles.primaryButton}>
|
||||||
|
Showing a cluster informed downsampling of 10,000 cells. Refresh.
|
||||||
|
</button>
|
||||||
<h3 style={{marginTop: 50}}> Gene selection criteria </h3>
|
<h3 style={{marginTop: 50}}> Gene selection criteria </h3>
|
||||||
<Joy data={this.state.heatmap && this.state.heatmap.data}/>
|
{false ? <Joy data={this.state.heatmap && this.state.heatmap.data}/> : ""}
|
||||||
|
|
||||||
<Categorical/>
|
<Categorical/>
|
||||||
<Continuous/>
|
<Continuous/>
|
||||||
<h3 style={{marginTop: 50}}> T-SNE plot </h3>
|
<h3 style={{marginTop: 50}}> T-SNE plot </h3>
|
||||||
<button onClick={() => { this.setState({showClustersPlaceholderImage: true}) }} style={{marginBottom: 20}} className={buttonStyles.primaryButton}>Compute clustering using [n] cells in current metadata selection</button>
|
<button
|
||||||
|
onClick={() => { this.setState({showClustersPlaceholderImage: true}) }}
|
||||||
|
style={{marginBottom: 20}}
|
||||||
|
className={buttonStyles.primaryButton}>
|
||||||
|
Compute clustering using [n] cells in current metadata selection
|
||||||
|
</button>
|
||||||
<Graph data={this.state.graph}/>
|
<Graph data={this.state.graph}/>
|
||||||
{this.state.showClustersPlaceholderImage ? <img width={750} src="https://s10.postimg.org/s6z3zbcvt/tsne.png"></img> : null}
|
{this.state.showClustersPlaceholderImage ? <img width={750} src="https://s10.postimg.org/s6z3zbcvt/tsne.png"></img> : null}
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
Reference in New Issue
Block a user