renaming backend, cellxgene to server, client respectively

This commit is contained in:
Charlotte Weaver
2018-06-26 11:41:32 -07:00
parent 59dcfe2bc4
commit dd5fa57259
97 changed files with 4 additions and 7 deletions
@@ -0,0 +1,251 @@
// jshint esversion: 6
import React from "react";
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 {
constructor(props) {
super(props);
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
}
};
}
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() {
const { width, height, popup } = this.state;
if (!width || !height) {
return null;
}
const renderedPopup = popup.displayed ? <Popup {...popup} /> : null;
return (
<div id="heatmap">
{this.renderDeckGLCanvas()}
{renderedPopup}
</div>
);
}
}
export default Heatmap;
@@ -0,0 +1,170 @@
// jshint esversion: 6
/* global window */
import React, { Component } 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.defaultProps = {
lookAt: [0, 0, 0],
rotationX: 0,
rotationY: 0,
minDistance: 0,
maxDistance: Infinity,
fov: 50
};
+45
View File
@@ -0,0 +1,45 @@
// jshint esversion: 6
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.defaultProps = {
id: "id",
title: "",
x: 0,
y: 0
};