mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-24 01:58:12 +08:00
joyplots working
This commit is contained in:
-93
@@ -5,99 +5,6 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#db5945">
|
||||
<title>Cellx</title>
|
||||
<style>
|
||||
/*
|
||||
|
||||
This code can be found: https://bl.ocks.org/syntagmatic/05a5b0897a48890133beb59c815bd953
|
||||
|
||||
body {
|
||||
min-width: 760px;
|
||||
}
|
||||
*/
|
||||
|
||||
.parcoords {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.parcoords svg,
|
||||
.parcoords canvas {
|
||||
font: 10px sans-serif;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.parcoords canvas {
|
||||
opacity: 0.9;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.axis .title {
|
||||
font-size: 10px;
|
||||
transform: rotate(-21deg) translate(-5px,-6px);
|
||||
fill: #222;
|
||||
}
|
||||
|
||||
.axis line,
|
||||
.axis path {
|
||||
fill: none;
|
||||
stroke: #ccc;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.axis .tick text {
|
||||
fill: #222;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/*
|
||||
old, from reference bl.ocks
|
||||
.axis.manufac_name .tick text,
|
||||
.axis.food_group .tick text {
|
||||
opacity: 1;
|
||||
}
|
||||
*/
|
||||
|
||||
.axis:hover line,
|
||||
.axis:hover path,
|
||||
.axis.active line,
|
||||
.axis.active path {
|
||||
fill: none;
|
||||
stroke: #222;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.axis:hover .title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.axis:hover .tick text {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.axis.active .title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.axis.active .tick text {
|
||||
opacity: 1;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.brush .extent {
|
||||
fill-opacity: .3;
|
||||
stroke: #fff;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
pre {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
margin: 6px 12px;
|
||||
tab-size: 40;
|
||||
font-size: 10px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>If you're seeing this message, that means <strong>JavaScript has been disabled on your browser</strong>, please <strong>enable JS</strong> to make this app work.</noscript>
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
+3
-1
@@ -24,6 +24,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"d3": "^4.10.0",
|
||||
"dsv-loader": "^2.0.0",
|
||||
"express": "^4.14.0",
|
||||
"lodash": "^4.17.4",
|
||||
"react": "^15.3.0",
|
||||
@@ -31,7 +32,8 @@
|
||||
"react-helmet": "^4.0.0",
|
||||
"react-hot-loader": "^3.0.0-beta.2",
|
||||
"react-router-dom": "4.1.2",
|
||||
"serve-favicon": "^2.3.0"
|
||||
"serve-favicon": "^2.3.0",
|
||||
"whatwg-fetch": "^2.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^6.4.0",
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
.primaryButton {
|
||||
border: 1px solid black;
|
||||
padding: 10px 15px;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -42,7 +42,7 @@ const Category = ({category, title}) => (
|
||||
const Categorical = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{marginTop: 50}}>
|
||||
<h3>Categorical Metadata</h3>
|
||||
{
|
||||
_.map(createCategoryCounts(cells),
|
||||
@@ -66,3 +66,12 @@ export default Categorical;
|
||||
<p> <button> Tumor [400] [40] [4] </button> </p>
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
|
||||
Each category has a color associated with it - ie., color by location should show up on these buttons,
|
||||
Does colorby live here? Like a global, with the category labels at the top? If not, we have to
|
||||
duplicate the category names somewhere else - but then, not all (like Cluster_2d_color) won't be listed here.
|
||||
|
||||
*/
|
||||
|
||||
@@ -3,16 +3,55 @@ import _ from "lodash";
|
||||
import cells from "../../../data/GBM_metadata.js";
|
||||
import drawParallelCoordinates from "./drawParallelCoordinates";
|
||||
import createContinuousRanges from "./createContinuousRanges";
|
||||
import styles from './parallelCoordinates.css';
|
||||
|
||||
const Continuous = () => {
|
||||
import {
|
||||
margin,
|
||||
width,
|
||||
height
|
||||
} from "./util";
|
||||
|
||||
drawParallelCoordinates(cells)
|
||||
class Continuous extends React.Component {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3> Continuous Metadata </h3>
|
||||
</div>
|
||||
)
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
ctx: null,
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
drawParallelCoordinates(cells)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div id="parcoords_wrapper" style={{marginTop: 50}}>
|
||||
<h3> Continuous Metadata </h3>
|
||||
<div style={{marginBottom: 30}}>
|
||||
Color By:
|
||||
<button style={{marginLeft: 10}}>Cluster_2d_color</button>
|
||||
<button style={{marginLeft: 10}}>Cluster_CNV_color</button>
|
||||
<button style={{marginLeft: 10}}>Location.color</button>
|
||||
<button style={{marginLeft: 10}}>Sample.name.color</button>
|
||||
<button style={{marginLeft: 10}}>Sample.type.color</button>
|
||||
<button style={{marginLeft: 10}}>Selection.color</button>
|
||||
<button style={{marginLeft: 10}}>housekeeping_cluster_color</button>
|
||||
<button style={{marginLeft: 10}}>recluster_myeloid</button>
|
||||
<button style={{marginLeft: 10}}>recluster_myeloid_color</button>
|
||||
</div>
|
||||
<div
|
||||
className={styles.parcoords}
|
||||
id="parcoords"
|
||||
style={{
|
||||
width: width + margin.left + margin.right + "px",
|
||||
height: height + margin.top + margin.bottom + "px"
|
||||
}}></div>
|
||||
<pre id="parcoords_output" className={styles.pre}></pre>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
export default Continuous;
|
||||
|
||||
@@ -112,10 +112,7 @@ const drawParallelCoordinates = (data) => {
|
||||
******************************************
|
||||
******************************************/
|
||||
|
||||
var container = d3.select("body").append("div")
|
||||
.attr("class", "parcoords")
|
||||
.style("width", width + margin.left + margin.right + "px")
|
||||
.style("height", height + margin.top + margin.bottom + "px");
|
||||
var container = d3.select("#parcoords")
|
||||
|
||||
var svg = container.append("svg")
|
||||
.attr("width", width + margin.left + margin.right)
|
||||
@@ -131,18 +128,19 @@ const drawParallelCoordinates = (data) => {
|
||||
.style("margin-top", margin.top + "px")
|
||||
.style("margin-left", margin.left + "px");
|
||||
|
||||
|
||||
var ctx = canvas.node().getContext("2d");
|
||||
ctx.globalCompositeOperation = 'darken';
|
||||
ctx.globalAlpha = 0.15;
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.scale(devicePixelRatio, devicePixelRatio);
|
||||
|
||||
var output = d3.select("body").append("pre");
|
||||
var output = d3.select("#parcoords_output");
|
||||
|
||||
var axes = svg.selectAll(".axis")
|
||||
var axes = svg.selectAll(".parcoords_axis")
|
||||
.data(dimensions)
|
||||
.enter().append("g")
|
||||
.attr("class", "axis")
|
||||
.attr("class", `${styles.axis} parcoords_axis`)
|
||||
.attr("transform", function(d,i) { return "translate(" + xscale(i) + ")"; });
|
||||
|
||||
/*****************************************
|
||||
@@ -196,7 +194,8 @@ const drawParallelCoordinates = (data) => {
|
||||
|
||||
*/
|
||||
|
||||
ctx.strokeStyle = d["Cluster_2d_color"]; //"rgba(0,0,0,.4)" /* color(d.food_group); */
|
||||
ctx.strokeStyle = d["Sample.name.color"];
|
||||
// ctx.strokeStyle = "rgba(180,180,180,.4)";
|
||||
ctx.beginPath();
|
||||
var coords = project(d);
|
||||
coords.forEach((p,i) => {
|
||||
@@ -240,9 +239,8 @@ const drawParallelCoordinates = (data) => {
|
||||
render.invalidate();
|
||||
|
||||
var actives = [];
|
||||
svg.selectAll(".axis .brush")
|
||||
svg.selectAll(".parcoords_axis .parcoords_brush")
|
||||
.filter(function (d) {
|
||||
console.log('this', this)
|
||||
return d3.brushSelection(this);
|
||||
})
|
||||
.each(function(d) {
|
||||
@@ -263,12 +261,14 @@ const drawParallelCoordinates = (data) => {
|
||||
}
|
||||
});
|
||||
|
||||
/* Reset canvas */
|
||||
ctx.clearRect(0,0,width,height);
|
||||
ctx.globalAlpha = d3.min([0.85/Math.pow(selected.length,0.3),1]);
|
||||
|
||||
/* pass the result of the filter above to a fresh canvas, using RAF */
|
||||
render(selected);
|
||||
|
||||
output.text(d3.tsvFormat(selected.slice(0,24)));
|
||||
// console.log('fires brush', actives)
|
||||
output.text(d3.tsvFormat(selected.slice(0,22)));
|
||||
}
|
||||
|
||||
function brushstart() {
|
||||
@@ -297,13 +297,13 @@ const drawParallelCoordinates = (data) => {
|
||||
d3.select(this).call(renderAxis);
|
||||
})
|
||||
.append("text")
|
||||
.attr("class", "title")
|
||||
.attr("class", styles.title)
|
||||
.attr("text-anchor", "start")
|
||||
.text(function(d) { return "description" in d ? d.description : d.key; });
|
||||
|
||||
// Add and store a brush for each axis.
|
||||
axes.append("g")
|
||||
.attr("class", "brush")
|
||||
.attr("class", `${styles.brush} parcoords_brush`)
|
||||
.each(function(d) {
|
||||
d3.select(this).call(d.brush = d3.brushY()
|
||||
.extent([[-10,0], [10,height]])
|
||||
@@ -316,7 +316,7 @@ const drawParallelCoordinates = (data) => {
|
||||
.attr("x", -8)
|
||||
.attr("width", 16);
|
||||
|
||||
output.text(d3.tsvFormat(data.slice(0,24)));
|
||||
output.text(d3.tsvFormat(data.slice(0,22)));
|
||||
|
||||
function project(d) {
|
||||
return dimensions.map(function(p,i) {
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
|
||||
This code can be found: https://bl.ocks.org/syntagmatic/05a5b0897a48890133beb59c815bd953
|
||||
|
||||
body {
|
||||
min-width: 760px;
|
||||
}
|
||||
*/
|
||||
|
||||
.parcoords {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.parcoords svg,
|
||||
.parcoords canvas {
|
||||
font: 10px sans-serif;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.parcoords canvas {
|
||||
opacity: 0.9;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.axis .title {
|
||||
font-size: 10px;
|
||||
transform: rotate(-21deg) translate(-5px,-6px);
|
||||
fill: #222;
|
||||
}
|
||||
|
||||
.axis line,
|
||||
.axis path {
|
||||
fill: none;
|
||||
stroke: #ccc;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.axis .tick text {
|
||||
fill: #222;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/*
|
||||
old, from reference bl.ocks
|
||||
.axis.manufac_name .tick text,
|
||||
.axis.food_group .tick text {
|
||||
opacity: 1;
|
||||
}
|
||||
*/
|
||||
|
||||
.axis:hover line,
|
||||
.axis:hover path,
|
||||
.axis.active line,
|
||||
.axis.active path {
|
||||
fill: none;
|
||||
stroke: #222;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.axis:hover .title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.axis:hover .tick text {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.axis.active .title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.axis.active .tick text {
|
||||
opacity: 1;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.brush .extent {
|
||||
fill-opacity: .3;
|
||||
stroke: #fff;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.pre {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
margin: 6px 12px;
|
||||
tab-size: 40;
|
||||
font-size: 10px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export const margin = {top: 66, right: 110, bottom: 20, left: 188};
|
||||
export const width = document.body.clientWidth - margin.left - margin.right;
|
||||
const paddingRight = 120;
|
||||
|
||||
export const margin = {top: 66, right: 110, bottom: 20, left: 60};
|
||||
export const width = document.body.clientWidth - margin.left - margin.right - paddingRight;
|
||||
export const height = 340 - margin.top - margin.bottom;
|
||||
export const innerHeight = height - 2;
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
var margin = {top: 20, right: 10, bottom: 30, left: 40},
|
||||
width = 960 - margin.left - margin.right,
|
||||
height = 500 - margin.top - margin.bottom;
|
||||
|
||||
var svg = d3.select("body").append("svg")
|
||||
.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]);
|
||||
|
||||
var y = d3.scale.linear()
|
||||
.range([height, 0]);
|
||||
|
||||
var xAxis = d3.svg.axis()
|
||||
.scale(x)
|
||||
.orient("bottom");
|
||||
|
||||
var yAxis = d3.svg.axis()
|
||||
.scale(y)
|
||||
.orient("left");
|
||||
|
||||
var xg = svg.append("g")
|
||||
.attr("class", "x axis")
|
||||
.attr("transform", "translate(0," + height + ")");
|
||||
|
||||
var yg = svg.append("g")
|
||||
.attr("class", "y axis");
|
||||
|
||||
var chartArea = d3.select("body").append("div")
|
||||
.style("left", margin.left + "px")
|
||||
.style("top", margin.top + "px");
|
||||
|
||||
var canvas = chartArea.append("canvas")
|
||||
.attr("width", width)
|
||||
.attr("height", height);
|
||||
|
||||
var context = canvas.node().getContext("2d");
|
||||
|
||||
context.fillStyle = "#f0f";
|
||||
|
||||
// Layer on top of canvas, example of selection details
|
||||
var highlight = chartArea.append("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.append("circle")
|
||||
.attr("r", 7)
|
||||
.classed("hidden", true);
|
||||
|
||||
redraw();
|
||||
|
||||
function redraw() {
|
||||
|
||||
// Randomize the scale
|
||||
var scale = 1 + Math.floor(Math.random() * 10);
|
||||
|
||||
// Redraw axes
|
||||
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) {
|
||||
|
||||
// Get points
|
||||
return d3.range(1000).map(function(d){
|
||||
|
||||
return [
|
||||
Math.random() * scale,
|
||||
Math.random() * scale
|
||||
];
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
path,
|
||||
line {
|
||||
shape-rendering: crispEdges;
|
||||
}
|
||||
|
||||
div,
|
||||
svg,
|
||||
canvas {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
svg {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.axis path,
|
||||
.axis line {
|
||||
fill: none;
|
||||
stroke: #000;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
circle {
|
||||
stroke-width: 4px;
|
||||
stroke: #000;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import _ from "lodash";
|
||||
// import styles from "./joy.css";
|
||||
// import drawGraph from "./drawGraph";
|
||||
// import joyParser from "./joyParser";
|
||||
|
||||
class Graph extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.data) {
|
||||
console.log('graph data 55', nextProps.data)
|
||||
// drawGraph(graphParser(nextProps.data));
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div id="joyplot_wrapper" style={{marginTop: 50}}>
|
||||
<h3> Graph </h3>
|
||||
<div id="joyplot"> </div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
export default Graph;
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import _ from "lodash";
|
||||
|
||||
class Heatmap extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{marginTop: 50}}>
|
||||
<h3> Heatmap </h3>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
export default Heatmap;
|
||||
+59
-13
@@ -1,22 +1,68 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import _ from "lodash";
|
||||
import Helmet from 'react-helmet';
|
||||
import Container from './container';
|
||||
import Helmet from "react-helmet";
|
||||
import Container from "./container";
|
||||
import buttonStyles from "./buttons.css";
|
||||
|
||||
import Categorical from "./categorical/categorical";
|
||||
import Continuous from "./continuous/continuous";
|
||||
import Joy from "./joy/joy";
|
||||
import Graph from "./graph/graph";
|
||||
|
||||
const Home = () => {
|
||||
class Home extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showClustersPlaceholderImage: false,
|
||||
heatmap: null,
|
||||
graph: null,
|
||||
};
|
||||
}
|
||||
componentWillMount() {
|
||||
// d3.csv("http://ec2-34-234-75-156.compute-1.amazonaws.com/api/v0.1/metadata", (d) => {
|
||||
// console.log("from ec2",d)
|
||||
// })
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Helmet title="cellxgene" />
|
||||
<h1><Link to="/page2">cellxgene</Link></h1>
|
||||
<Categorical/>
|
||||
<Continuous/>
|
||||
</Container>
|
||||
)
|
||||
const prefix = "http://ec2-34-234-75-156.compute-1.amazonaws.com/api/";
|
||||
const version = "v0.1/";
|
||||
|
||||
const expressions = fetch(`${prefix}${version}expression`, {
|
||||
method: "post",
|
||||
headers: new Headers({
|
||||
'Content-Type': 'application/json'
|
||||
}),
|
||||
body: JSON.stringify({
|
||||
"celllist": ["1001000173.G8", "1001000173.D4"],
|
||||
"genelist": ["1/2-SBSRNA4", "A1BG", "A1BG-AS1", "A1CF", "A2LD1", "A2M", "A2ML1", "A2MP1", "A4GALT"]
|
||||
})
|
||||
})
|
||||
// const expressions = fetch(`${prefix}${version}${expression}`)
|
||||
.then((res) => res.json())
|
||||
.then((data) => { this.setState({heatmap: data}) })
|
||||
|
||||
const graph = fetch(`${prefix}${version}graph`)
|
||||
.then((res) => res.json())
|
||||
.then((data) => { this.setState({graph: data}) })
|
||||
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Container>
|
||||
<Helmet title="cellxgene" />
|
||||
<h1><Link to="/page2">cellxgene</Link></h1>
|
||||
<h3 style={{marginTop: 50}}> Gene selection criteria </h3>
|
||||
<Joy data={this.state.heatmap && this.state.heatmap.data}/>
|
||||
|
||||
<Categorical/>
|
||||
<Continuous/>
|
||||
<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>
|
||||
<Graph data={this.state.graph}/>
|
||||
{this.state.showClustersPlaceholderImage ? <img width={750} src="https://s10.postimg.org/s6z3zbcvt/tsne.png"></img> : null}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
export default Home;
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import styles from "./joy.css";
|
||||
|
||||
/*
|
||||
via https://bl.ocks.org/armollica/3b5f83836c1de5cca7b1d35409a013e3
|
||||
*/
|
||||
|
||||
var margin = { top: 30, right: 10, bottom: 30, left: 100 },
|
||||
width = 400 - margin.left - margin.right,
|
||||
height = 600 - margin.top - margin.bottom;
|
||||
|
||||
// Percent two area charts can overlap
|
||||
var overlap = 0.4;
|
||||
|
||||
var formatTime = d3.timeFormat('%I %p');
|
||||
|
||||
var x = function(d) { return d.time; },
|
||||
xScale = d3.scaleTime().range([0, width]),
|
||||
xValue = function(d) { return xScale(x(d)); },
|
||||
xAxis = d3.axisBottom(xScale).tickFormat(formatTime);
|
||||
|
||||
var y = function(d) { return d.value; },
|
||||
yScale = d3.scaleLinear(),
|
||||
yValue = function(d) { return yScale(y(d)); };
|
||||
|
||||
var activity = function(d) { return d.key; },
|
||||
activityScale = d3.scaleBand().range([0, height]),
|
||||
activityValue = function(d) { return activityScale(activity(d)); },
|
||||
activityAxis = d3.axisLeft(activityScale);
|
||||
|
||||
var area = d3.area()
|
||||
.x(xValue)
|
||||
.y1(yValue);
|
||||
|
||||
var line = area.lineY1();
|
||||
|
||||
function parseTime(offset) {
|
||||
var date = new Date(2017, 0, 1); // chose an arbitrary day
|
||||
return d3.timeMinute.offset(date, offset);
|
||||
}
|
||||
|
||||
function row(d) {
|
||||
return {
|
||||
activity: d.activity,
|
||||
time: parseTime(d.time),
|
||||
value: +d.p_smooth
|
||||
};
|
||||
}
|
||||
|
||||
const drawJoy = (data) => {
|
||||
|
||||
console.log('drawJoy: ', data)
|
||||
|
||||
var svg = d3.select('#joyplot').append('svg')
|
||||
.attr('width', width + margin.left + margin.right)
|
||||
.attr('height', height + margin.top + margin.bottom)
|
||||
.append('g')
|
||||
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
||||
|
||||
d3.tsv("https://gist.githubusercontent.com/armollica/3b5f83836c1de5cca7b1d35409a013e3/raw/783d1bbc2dd3aabbfcba83ece4a670de6f1ec371/data.tsv", row, function(error, dataFlat) {
|
||||
|
||||
// Sort by time
|
||||
dataFlat.sort(function(a, b) { return a.time - b.time; });
|
||||
|
||||
var data = d3.nest()
|
||||
.key(function(d) { return d.activity; })
|
||||
.entries(dataFlat);
|
||||
|
||||
// Sort activities by peak activity time
|
||||
function peakTime(d) {
|
||||
var i = d3.scan(d.values, function(a, b) { return y(b) - y(a); });
|
||||
return d.values[i].time;
|
||||
};
|
||||
data.sort(function(a, b) { return peakTime(b) - peakTime(a); });
|
||||
|
||||
console.log('sorted', data)
|
||||
|
||||
xScale.domain(d3.extent(dataFlat, x));
|
||||
|
||||
activityScale.domain(data.map(function(d) { return d.key; }));
|
||||
|
||||
var areaChartHeight = (1 + overlap) * (height / activityScale.domain().length);
|
||||
|
||||
yScale
|
||||
.domain(d3.extent(dataFlat, y))
|
||||
.range([areaChartHeight, 0]);
|
||||
|
||||
area.y0(yScale(0));
|
||||
|
||||
var gActivity = svg.append('g').attr('class', "activities")
|
||||
.selectAll('.activity').data(data)
|
||||
.enter().append('g')
|
||||
.attr('class', function(d) { return `${styles.activity} ${styles.activity["--"+d.key]}`; })
|
||||
.attr('transform', function(d) {
|
||||
var ty = activityValue(d) - activityScale.bandwidth() + 5;
|
||||
return 'translate(0,' + ty + ')';
|
||||
});
|
||||
|
||||
gActivity.append('path').attr('class', styles.area)
|
||||
.datum(function(d) { return d.values; })
|
||||
.attr('d', area);
|
||||
|
||||
gActivity.append('path').attr('class', styles.line)
|
||||
.datum(function(d) { return d.values; })
|
||||
.attr('d', line);
|
||||
|
||||
svg.append('g').attr('class', `${styles.axis} ${styles["axis--x"]}`)
|
||||
.attr('transform', 'translate(0,' + height + ')')
|
||||
.call(xAxis);
|
||||
|
||||
svg.append('g').attr('class', `${styles.axis} ${styles["axis--activity"]}`)
|
||||
.call(activityAxis);
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
export default drawJoy;
|
||||
@@ -0,0 +1,42 @@
|
||||
svg {
|
||||
display: block;
|
||||
/*margin: 0 auto;*/
|
||||
}
|
||||
|
||||
.axis .domain {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.axis--x text {
|
||||
fill: #999;
|
||||
}
|
||||
|
||||
.axis--x line {
|
||||
stroke: #aaa;
|
||||
}
|
||||
|
||||
.axis--activity .tick line {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.axis--activity text {
|
||||
font-size: 12px;
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
/*.axis--activity .tick:nth-child(odd) text {
|
||||
fill: #222;
|
||||
}*/
|
||||
|
||||
.line {
|
||||
fill: none;
|
||||
stroke: #fff;
|
||||
}
|
||||
|
||||
.area {
|
||||
fill: #448cab;
|
||||
}
|
||||
|
||||
.activity:nth-child(odd) .area {
|
||||
fill: #5ca3c1;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import _ from "lodash";
|
||||
import styles from "./joy.css";
|
||||
import drawJoy from "./drawJoy";
|
||||
import joyParser from "./joyParser";
|
||||
|
||||
class Joy extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.data) {
|
||||
console.log('joyplot data 44', nextProps.data)
|
||||
drawJoy(joyParser(nextProps.data));
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div id="joyplot_wrapper" style={{marginTop: 50}}>
|
||||
<h3> Joy </h3>
|
||||
<p> Cell expression distribution per gene & if differential expression, Ie., cells for cluster 5, top genes expressed by cluster 8</p>
|
||||
<div id="joyplot"> </div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
export default Joy;
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
|
||||
|
||||
|
||||
const joyParser = (data, count = 20) => {
|
||||
const genes = [];
|
||||
|
||||
/* setup */
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const gene = {
|
||||
key: data.genes[i], /* key values naming: https://bl.ocks.org/armollica/3b5f83836c1de5cca7b1d35409a013e3 */
|
||||
values: []
|
||||
}
|
||||
|
||||
data.cells.forEach((cell) => {
|
||||
gene.values.push({
|
||||
value: cell["e"][i]
|
||||
})
|
||||
})
|
||||
|
||||
genes.push(gene)
|
||||
}
|
||||
|
||||
|
||||
|
||||
return genes;
|
||||
}
|
||||
|
||||
export default joyParser;
|
||||
@@ -8,22 +8,14 @@
|
||||
}
|
||||
|
||||
.hero {
|
||||
font-size: 48;
|
||||
font-size: 48px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.subHero {
|
||||
font-size: 18;
|
||||
max-width: 600;
|
||||
text-align: "center";
|
||||
}
|
||||
|
||||
.primaryButton {
|
||||
border: 1px solid black;
|
||||
padding: 10px 15px;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-size: 18px;
|
||||
max-width: 600px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.or {
|
||||
|
||||
@@ -2,6 +2,8 @@ import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Helmet from 'react-helmet';
|
||||
import styles from './page2.css';
|
||||
import buttonStyles from './buttons.css';
|
||||
|
||||
|
||||
import Container from './container';
|
||||
|
||||
@@ -14,9 +16,9 @@ const Page2 = () => (
|
||||
A data exploration interface for single cell genetic expression matrices. Subset, filter, cluster & validate, all in one place.
|
||||
</p>
|
||||
<div>
|
||||
<Link to="/"><button className={styles.primaryButton}>explore a sample dataset</button></Link>
|
||||
<Link to="/"><button className={buttonStyles.primaryButton}>explore a sample dataset</button></Link>
|
||||
<span className={styles.or}>or</span>
|
||||
<button className={styles.primaryButton}>get started with your own data</button>
|
||||
<button className={buttonStyles.primaryButton}>get started with your own data</button>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
Reference in New Issue
Block a user