mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-06 20:08:12 +08:00
drawGraph
This commit is contained in:
+4
-1
@@ -6,7 +6,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run clean && webpack --config configuration/webpack/webpack.config.prod.js",
|
"build": "npm run clean && webpack --config configuration/webpack/webpack.config.prod.js",
|
||||||
"clean": "rimraf build",
|
"clean": "rimraf build",
|
||||||
"dev": "node server/development.js",
|
"start": "node server/development.js",
|
||||||
"lint": "eslint src",
|
"lint": "eslint src",
|
||||||
"prod": "cross-env NODE_ENV=production PORT=3000 node server/production.js",
|
"prod": "cross-env NODE_ENV=production PORT=3000 node server/production.js",
|
||||||
"test": "nyc --reporter=lcov --reporter=text mocha test/.setup.js test/**/*.spec.js"
|
"test": "nyc --reporter=lcov --reporter=text mocha test/.setup.js test/**/*.spec.js"
|
||||||
@@ -24,9 +24,12 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"d3": "^4.10.0",
|
"d3": "^4.10.0",
|
||||||
|
"deck.gl": "^4.1.2",
|
||||||
"dsv-loader": "^2.0.0",
|
"dsv-loader": "^2.0.0",
|
||||||
"express": "^4.14.0",
|
"express": "^4.14.0",
|
||||||
|
"gl-matrix": "^2.4.0",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.4",
|
||||||
|
"luma.gl": "^4.0.3",
|
||||||
"react": "^15.3.0",
|
"react": "^15.3.0",
|
||||||
"react-dom": "^15.3.0",
|
"react-dom": "^15.3.0",
|
||||||
"react-helmet": "^4.0.0",
|
"react-helmet": "^4.0.0",
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.domain {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<svg width="960" height="960"></svg>
|
||||||
|
<script src="https://d3js.org/d3.v4.min.js"></script>
|
||||||
|
<script src="https://d3js.org/d3-contour.v1.min.js"></script>
|
||||||
|
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var svg = d3.select("svg"),
|
||||||
|
width = +svg.attr("width"),
|
||||||
|
height = +svg.attr("height"),
|
||||||
|
margin = {top: 20, right: 30, bottom: 30, left: 40};
|
||||||
|
|
||||||
|
var x = d3.scaleLog()
|
||||||
|
.domain([2e-1, 5e0])
|
||||||
|
.rangeRound([margin.left, width - margin.right]);
|
||||||
|
|
||||||
|
var y = d3.scaleLog()
|
||||||
|
.domain([3e2, 2e4])
|
||||||
|
.rangeRound([height - margin.bottom, margin.top]);
|
||||||
|
|
||||||
|
var color = d3.scaleSequential(d3.interpolateYlGnBu)
|
||||||
|
.domain([0, 1.8]); // Points per square pixel.
|
||||||
|
|
||||||
|
svg.append("g")
|
||||||
|
.attr("transform", "translate(0," + (height - margin.bottom) + ")")
|
||||||
|
.call(d3.axisBottom(x).ticks(null, ".1f"))
|
||||||
|
.select(".tick:last-of-type text")
|
||||||
|
.select(function() { return this.parentNode.appendChild(this.cloneNode()); })
|
||||||
|
.attr("y", -3)
|
||||||
|
.attr("dy", null)
|
||||||
|
.attr("font-weight", "bold")
|
||||||
|
.text("Carats");
|
||||||
|
|
||||||
|
svg.append("g")
|
||||||
|
.attr("transform", "translate(" + margin.left + ",0)")
|
||||||
|
.call(d3.axisLeft(y).ticks(null, ".1s"))
|
||||||
|
.select(".tick:last-of-type text")
|
||||||
|
.select(function() { return this.parentNode.appendChild(this.cloneNode()); })
|
||||||
|
.attr("x", 3)
|
||||||
|
.attr("text-anchor", "start")
|
||||||
|
.attr("font-weight", "bold")
|
||||||
|
.text("Price (USD)");
|
||||||
|
|
||||||
|
d3.tsv("diamonds.tsv", function(d) {
|
||||||
|
d.carat = +d.carat;
|
||||||
|
d.price = +d.price;
|
||||||
|
return d;
|
||||||
|
}, function(error, diamonds) {
|
||||||
|
if (error) throw error;
|
||||||
|
|
||||||
|
svg.insert("g", "g")
|
||||||
|
.attr("fill", "none")
|
||||||
|
.attr("stroke", "#000")
|
||||||
|
.attr("stroke-width", 0.5)
|
||||||
|
.attr("stroke-linejoin", "round")
|
||||||
|
.selectAll("path")
|
||||||
|
.data(d3.contourDensity()
|
||||||
|
.x(function(d) { return x(d.carat); })
|
||||||
|
.y(function(d) { return y(d.price); })
|
||||||
|
.size([width, height])
|
||||||
|
.bandwidth(10)
|
||||||
|
(diamonds))
|
||||||
|
.enter().append("path")
|
||||||
|
.attr("fill", function(d) { return color(d.value); })
|
||||||
|
.attr("d", d3.geoPath());
|
||||||
|
});
|
||||||
@@ -56,7 +56,7 @@ export const drawGraph = (data, context, expressionsCountsMap) => {
|
|||||||
context.clearRect(0, 0, width, height);
|
context.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
/* ! create a scale to map between expression values and colors, remove to somewhere else */
|
/* ! create a scale to map between expression values and colors, remove to somewhere else */
|
||||||
const expressionToColorScale = d3.scaleLog()
|
const expressionToColorScale = d3.scaleLinear()
|
||||||
.domain([0, expressionsCountsMap.maxValue])
|
.domain([0, expressionsCountsMap.maxValue])
|
||||||
.range([1,0])
|
.range([1,0])
|
||||||
|
|
||||||
@@ -76,14 +76,14 @@ export const drawGraph = (data, context, expressionsCountsMap) => {
|
|||||||
);
|
);
|
||||||
context.fill();
|
context.fill();
|
||||||
|
|
||||||
if (i < 20) {
|
// if (i < 20) {
|
||||||
console.log(
|
// console.log(
|
||||||
'0 to 1 scale', expressionToColorScale(expressionsCountsMap[p[0]]),
|
// '0 to 1 scale', expressionToColorScale(expressionsCountsMap[p[0]]),
|
||||||
'color', d3.interpolateViridis(expressionToColorScale(
|
// 'color', d3.interpolateViridis(expressionToColorScale(
|
||||||
expressionsCountsMap[p[0]]
|
// expressionsCountsMap[p[0]]
|
||||||
))
|
// ))
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
context.fillStyle = d3.interpolateViridis(expressionToColorScale(
|
context.fillStyle = d3.interpolateViridis(expressionToColorScale(
|
||||||
expressionsCountsMap[p[0]]
|
expressionsCountsMap[p[0]]
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -1,25 +1,235 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import DeckGL, {PointCloudLayer, ScreenGridLayer, COORDINATE_SYSTEM} from 'deck.gl';
|
||||||
|
import OrbitController from './orbit-control';
|
||||||
|
import { Popup } from './popup';
|
||||||
|
|
||||||
class Heatmap extends React.Component {
|
class Heatmap extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
|
||||||
|
|
||||||
|
this.onChangeViewport = this.onChangeViewport.bind(this);
|
||||||
|
this.onInitialized = this.onInitialized.bind(this);
|
||||||
|
this.onResize = this.onResize.bind(this);
|
||||||
|
this.onUpdate = this.onUpdate.bind(this);
|
||||||
|
this.onHover = this.onHover.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
points: [],
|
||||||
|
sampleExpressionMatrix: [
|
||||||
|
{color:[0, 255, 0], position: [100, 100]},
|
||||||
|
{color:[0, 255, 0], position: [100, 100]},
|
||||||
|
{color:[0, 255, 0], position: [100, 100]},
|
||||||
|
],
|
||||||
|
progress: 0,
|
||||||
|
popup: {
|
||||||
|
displayed: false,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
title: '',
|
||||||
|
},
|
||||||
|
viewport: {
|
||||||
|
lookAt: [0, 0, 0],
|
||||||
|
distance: 1,
|
||||||
|
rotationX: 0,
|
||||||
|
rotationY: 0,
|
||||||
|
fov: 30,
|
||||||
|
minDistance: 0.5,
|
||||||
|
maxDistance: 3
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
getColor(cluster) {
|
||||||
|
let color = [0, 0, 0];
|
||||||
|
|
||||||
|
switch(cluster){
|
||||||
|
case 0:
|
||||||
|
color = [166, 206, 227];
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
color = [31, 120, 180];
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
color = [178, 223, 138];
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
color = [51, 160, 44];
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
color = [251, 154, 153];
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
color = [227, 26, 28];
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
color = [253, 191, 111];
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
color = [255, 127, 0];
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
color = [202, 178, 214];
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
color = [106, 61, 154];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
window.addEventListener('resize', this.onResize);
|
||||||
|
this.onResize();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.canvas.fitBounds([-0.5, -0.5, -0.5], [0.5, 0.5, 0.5]);
|
||||||
|
|
||||||
|
this.fetchData();
|
||||||
|
|
||||||
|
window.requestAnimationFrame(this.onUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
window.removeEventListener('resize', this.onResize);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData() {
|
||||||
|
fetch('https://raw.githubusercontent.com/zdenekhynek/data-science-capstone-visualisation/master/public/clusters.json')
|
||||||
|
.then((res) => {
|
||||||
|
res.json().then((obj) => {
|
||||||
|
const clusters = Object.keys(obj).map((k) => obj[k])
|
||||||
|
const points = clusters.map((cluster) => {
|
||||||
|
const position = [cluster.x, cluster.y, cluster.z];
|
||||||
|
const color = [255,0,0];
|
||||||
|
const id = cluster.id;
|
||||||
|
const title = cluster.webTitle;
|
||||||
|
return { id, title, position, color };
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({ points, progress: 1 })
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onHover(d) {
|
||||||
|
let popup = { displayed: false };
|
||||||
|
console.log('d', d)
|
||||||
|
|
||||||
|
if (d.object) {
|
||||||
|
const object = d.object;
|
||||||
|
popup = {
|
||||||
|
id: object.id,
|
||||||
|
title: object.title,
|
||||||
|
displayed: true,
|
||||||
|
x: d.x,
|
||||||
|
y: d.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ popup });
|
||||||
|
}
|
||||||
|
|
||||||
|
onResize() {
|
||||||
|
const {innerWidth: width, innerHeight: height} = window;
|
||||||
|
this.setState({width: width / 1.5, height: height / 1.5});
|
||||||
|
}
|
||||||
|
|
||||||
|
onInitialized(gl) {
|
||||||
|
gl.clearColor(0, 0, 0, 1);
|
||||||
|
gl.enable(gl.DEPTH_TEST);
|
||||||
|
gl.depthFunc(gl.LEQUAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeViewport(viewport) {
|
||||||
|
this.setState({
|
||||||
|
rotating: !viewport.isDragging,
|
||||||
|
viewport: {...this.state.viewport, ...viewport}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdate() {
|
||||||
|
const {viewport} = this.state;
|
||||||
|
window.requestAnimationFrame(this.onUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPointCloudLayer() {
|
||||||
|
return this.state.points.length && new PointCloudLayer({
|
||||||
|
id: 'point-cloud-layer',
|
||||||
|
data: this.state.points,
|
||||||
|
projectionMode: COORDINATE_SYSTEM.IDENTITY,
|
||||||
|
pickable: true,
|
||||||
|
onHover: this.onHover,
|
||||||
|
getPosition: d => d.position,
|
||||||
|
getNormal: d => [0, 0.5, 0.2],
|
||||||
|
getColor: d => d.color,
|
||||||
|
radiusPixels: 2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderGridLayer() {
|
||||||
|
/**
|
||||||
|
* Data format:
|
||||||
|
* [
|
||||||
|
* {position: [-122.4, 37.7]},
|
||||||
|
* ...
|
||||||
|
* ]
|
||||||
|
*/
|
||||||
|
const screenGridLayer = new ScreenGridLayer({
|
||||||
|
id: 'screen-grid-layer',
|
||||||
|
data: this.state.sampleExpressionMatrix,
|
||||||
|
projectionMode: COORDINATE_SYSTEM.IDENTITY,
|
||||||
|
pickable: true,
|
||||||
|
getPosition: d => d.position,
|
||||||
|
getColor: d => d.color,
|
||||||
|
cellSizePixels: 40
|
||||||
|
});
|
||||||
|
|
||||||
|
return screenGridLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDeckGLCanvas() {
|
||||||
|
const {width, height, viewport} = this.state;
|
||||||
|
const canvasProps = {width, height, ...viewport};
|
||||||
|
const glViewport = OrbitController.getViewport(canvasProps);
|
||||||
|
|
||||||
|
return width && height && (
|
||||||
|
<OrbitController {...canvasProps} ref={canvas => {
|
||||||
|
this.canvas = canvas;
|
||||||
|
}} onChangeViewport={this.onChangeViewport}>
|
||||||
|
<DeckGL
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
viewport={glViewport}
|
||||||
|
layers={[
|
||||||
|
// this.renderPointCloudLayer(),
|
||||||
|
this.renderGridLayer()
|
||||||
|
].filter(Boolean)}
|
||||||
|
onWebGLInitialized={this.onInitialized}/>
|
||||||
|
</OrbitController>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {width, height, popup} = this.state;
|
||||||
|
if (!width || !height) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderedPopup = (popup.displayed)? <Popup {...popup} /> : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{marginTop: 50}}>
|
<div id="heatmap">
|
||||||
<h3> Heatmap </h3>
|
{this.renderDeckGLCanvas()}
|
||||||
|
{renderedPopup}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Executable
+171
@@ -0,0 +1,171 @@
|
|||||||
|
/* global window */
|
||||||
|
import React, {Component, PropTypes} from 'react';
|
||||||
|
import {PerspectiveViewport} from 'deck.gl';
|
||||||
|
import {vec3} from 'gl-matrix';
|
||||||
|
|
||||||
|
/* Utils */
|
||||||
|
// constrain number between bounds
|
||||||
|
function clamp(x, min, max) {
|
||||||
|
if (x < min) {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
if (x > max) {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ua = typeof window.navigator !== 'undefined' ?
|
||||||
|
window.navigator.userAgent.toLowerCase() : '';
|
||||||
|
const firefox = ua.indexOf('firefox') !== -1;
|
||||||
|
|
||||||
|
/* Interaction */
|
||||||
|
|
||||||
|
export default class OrbitController extends Component {
|
||||||
|
|
||||||
|
static getViewport({width, height, lookAt, distance, rotationX, rotationY, fov}) {
|
||||||
|
const cameraPos = vec3.add([], lookAt, [0, 0, distance]);
|
||||||
|
vec3.rotateX(cameraPos, cameraPos, lookAt, rotationX / 180 * Math.PI);
|
||||||
|
vec3.rotateY(cameraPos, cameraPos, lookAt, rotationY / 180 * Math.PI);
|
||||||
|
|
||||||
|
return new PerspectiveViewport({
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
lookAt,
|
||||||
|
far: 1000,
|
||||||
|
near: 0.1,
|
||||||
|
fovy: fov,
|
||||||
|
eye: cameraPos
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this._dragStartPos = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onDragStart(evt) {
|
||||||
|
const {pageX, pageY} = evt;
|
||||||
|
this._dragStartPos = [pageX, pageY];
|
||||||
|
this.props.onChangeViewport({isDragging: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
_onDrag(evt) {
|
||||||
|
if (this._dragStartPos) {
|
||||||
|
const {pageX, pageY} = evt;
|
||||||
|
const {width, height} = this.props;
|
||||||
|
const dx = (pageX - this._dragStartPos[0]) / width;
|
||||||
|
const dy = (pageY - this._dragStartPos[1]) / height;
|
||||||
|
|
||||||
|
if (evt.shiftKey || evt.ctrlKey || evt.altKey || evt.metaKey) {
|
||||||
|
// pan
|
||||||
|
const {lookAt, distance, rotationX, rotationY, fov} = this.props;
|
||||||
|
|
||||||
|
const unitsPerPixel = distance / Math.tan(fov / 180 * Math.PI / 2) / 2;
|
||||||
|
|
||||||
|
const newLookAt = vec3.add([], lookAt, [-unitsPerPixel * dx, unitsPerPixel * dy, 0]);
|
||||||
|
vec3.rotateX(newLookAt, newLookAt, lookAt, rotationX / 180 * Math.PI);
|
||||||
|
vec3.rotateY(newLookAt, newLookAt, lookAt, rotationY / 180 * Math.PI);
|
||||||
|
|
||||||
|
this.props.onChangeViewport({
|
||||||
|
lookAt: newLookAt
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// rotate
|
||||||
|
const {rotationX, rotationY} = this.props;
|
||||||
|
const newRotationX = clamp(rotationX - dy * 180, -90, 90);
|
||||||
|
const newRotationY = (rotationY - dx * 180) % 360;
|
||||||
|
|
||||||
|
this.props.onChangeViewport({
|
||||||
|
rotationX: newRotationX,
|
||||||
|
rotationY: newRotationY
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this._dragStartPos = [pageX, pageY];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onDragEnd() {
|
||||||
|
this._dragStartPos = null;
|
||||||
|
this.props.onChangeViewport({isDragging: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
_onWheel(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
let value = evt.deltaY;
|
||||||
|
// Firefox doubles the values on retina screens...
|
||||||
|
if (firefox && evt.deltaMode === window.WheelEvent.DOM_DELTA_PIXEL) {
|
||||||
|
value /= window.devicePixelRatio;
|
||||||
|
}
|
||||||
|
if (evt.deltaMode === window.WheelEvent.DOM_DELTA_LINE) {
|
||||||
|
value *= 40;
|
||||||
|
}
|
||||||
|
if (value !== 0 && value % 4.000244140625 === 0) {
|
||||||
|
// This one is definitely a mouse wheel event.
|
||||||
|
// Normalize this value to match trackpad.
|
||||||
|
value = Math.floor(value / 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
const {distance, minDistance, maxDistance} = this.props;
|
||||||
|
const newDistance = clamp(distance * Math.pow(1.01, value), minDistance, maxDistance);
|
||||||
|
|
||||||
|
this.props.onChangeViewport({
|
||||||
|
distance: newDistance
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// public API
|
||||||
|
fitBounds(min, max) {
|
||||||
|
const {fov} = this.props;
|
||||||
|
const size = Math.max(max[0] - min[0], max[1] - min[1], max[2] - min[2]);
|
||||||
|
const newDistance = size / Math.tan(fov / 180 * Math.PI / 2) / 2;
|
||||||
|
|
||||||
|
this.props.onChangeViewport({
|
||||||
|
distance: newDistance
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div style={{position: 'relative', userSelect: 'none'}}
|
||||||
|
onMouseDown={this._onDragStart.bind(this)}
|
||||||
|
onMouseMove={this._onDrag.bind(this)}
|
||||||
|
onMouseLeave={this._onDragEnd.bind(this)}
|
||||||
|
onMouseUp={this._onDragEnd.bind(this)}
|
||||||
|
onWheel={this._onWheel.bind(this)} >
|
||||||
|
|
||||||
|
{this.props.children}
|
||||||
|
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OrbitController.propTypes = {
|
||||||
|
// target position
|
||||||
|
lookAt: PropTypes.arrayOf(PropTypes.number),
|
||||||
|
// camera distance
|
||||||
|
distance: PropTypes.number.isRequired,
|
||||||
|
minDistance: PropTypes.number,
|
||||||
|
maxDistance: PropTypes.number,
|
||||||
|
// rotation
|
||||||
|
rotationX: PropTypes.number,
|
||||||
|
rotationY: PropTypes.number,
|
||||||
|
// field of view
|
||||||
|
fov: PropTypes.number,
|
||||||
|
// viewport width in pixels
|
||||||
|
width: PropTypes.number.isRequired,
|
||||||
|
// viewport height in pixels
|
||||||
|
height: PropTypes.number.isRequired,
|
||||||
|
// callback
|
||||||
|
onChangeViewport: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
OrbitController.defaultProps = {
|
||||||
|
lookAt: [0, 0, 0],
|
||||||
|
rotationX: 0,
|
||||||
|
rotationY: 0,
|
||||||
|
minDistance: 0,
|
||||||
|
maxDistance: Infinity,
|
||||||
|
fov: 50
|
||||||
|
};
|
||||||
Executable
+51
@@ -0,0 +1,51 @@
|
|||||||
|
import React, {PureComponent} from 'react';
|
||||||
|
|
||||||
|
export class Popup extends PureComponent {
|
||||||
|
render() {
|
||||||
|
const { title, x, y } = this.props;
|
||||||
|
const style = {
|
||||||
|
position: 'absolute',
|
||||||
|
top: y,
|
||||||
|
left: x,
|
||||||
|
maxWidth: '200px',
|
||||||
|
padding: '10px',
|
||||||
|
color: 'white',
|
||||||
|
backgroundColor: 'black',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
transform: 'translate(10px, -50%)',
|
||||||
|
};
|
||||||
|
|
||||||
|
const arrowStyle = {
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '-14px',
|
||||||
|
width: '7px',
|
||||||
|
height: '5px',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
border: '7px solid transparent',
|
||||||
|
borderRight: '7px solid black',
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={style}>
|
||||||
|
<div style={arrowStyle} />
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Popup.propTypes = {
|
||||||
|
id: React.PropTypes.string,
|
||||||
|
title: React.PropTypes.string,
|
||||||
|
x: React.PropTypes.number,
|
||||||
|
y: React.PropTypes.number,
|
||||||
|
};
|
||||||
|
|
||||||
|
Popup.defaultProps = {
|
||||||
|
id: 'id',
|
||||||
|
title: '',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
};
|
||||||
@@ -9,6 +9,7 @@ import Categorical from "./categorical/categorical";
|
|||||||
import Continuous from "./continuous/continuous";
|
import Continuous from "./continuous/continuous";
|
||||||
import Joy from "./joy/joy";
|
import Joy from "./joy/joy";
|
||||||
import Graph from "./graph/graph";
|
import Graph from "./graph/graph";
|
||||||
|
import Heatmap from "./heatmap/heatmap";
|
||||||
|
|
||||||
class Home extends React.Component {
|
class Home extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -22,7 +23,7 @@ class Home extends React.Component {
|
|||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
|
||||||
const prefix = "http://ec2-34-234-75-156.compute-1.amazonaws.com/api/";
|
const prefix = "http://api.clustering.czi.technology/api/";
|
||||||
const version = "v0.1/";
|
const version = "v0.1/";
|
||||||
|
|
||||||
const expressions = fetch(`${prefix}${version}expression`, {
|
const expressions = fetch(`${prefix}${version}expression`, {
|
||||||
@@ -39,6 +40,10 @@ class Home extends React.Component {
|
|||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => { this.setState({expressions: data}) })
|
.then((data) => { this.setState({expressions: data}) })
|
||||||
|
|
||||||
|
const initialize = fetch(`${prefix}${version}initialize`)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => { this.setState({initialize: data}) })
|
||||||
|
|
||||||
const graph = fetch(`${prefix}${version}graph`)
|
const graph = fetch(`${prefix}${version}graph`)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => { this.setState({vertices: data}) })
|
.then((data) => { this.setState({vertices: data}) })
|
||||||
@@ -50,7 +55,7 @@ class Home extends React.Component {
|
|||||||
|
|
||||||
createExpressionsCountsMap () {
|
createExpressionsCountsMap () {
|
||||||
|
|
||||||
const CHANGE_ME_MAGIC_GENE_INDEX = 3;
|
const CHANGE_ME_MAGIC_GENE_INDEX = 5;
|
||||||
|
|
||||||
const expressionsCountsMap = {};
|
const expressionsCountsMap = {};
|
||||||
|
|
||||||
@@ -75,16 +80,13 @@ class Home extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
console.log('app:', this.props, this.state)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Helmet title="cellxgene" />
|
<Helmet title="cellxgene" />
|
||||||
|
{/* <Heatmap/> */}
|
||||||
<h1><Link to="/page2">cellxgene</Link></h1>
|
<h1><Link to="/page2">cellxgene</Link></h1>
|
||||||
Showing a cluster informed downsampling of 10,000 cells.
|
|
||||||
<button
|
|
||||||
className={buttonStyles.primaryButton}>
|
|
||||||
Refresh.
|
|
||||||
</button>
|
|
||||||
<h3 style={{marginTop: 50}}> Gene selection criteria </h3>
|
<h3 style={{marginTop: 50}}> Gene selection criteria </h3>
|
||||||
{false ? <Joy data={this.state.expressions && this.state.expressions.data}/> : ""}
|
{false ? <Joy data={this.state.expressions && this.state.expressions.data}/> : ""}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user