mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-08 00:38:11 +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="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="theme-color" content="#db5945">
|
<meta name="theme-color" content="#db5945">
|
||||||
<title>Cellx</title>
|
<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>
|
</head>
|
||||||
<body>
|
<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>
|
<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": {
|
"dependencies": {
|
||||||
"d3": "^4.10.0",
|
"d3": "^4.10.0",
|
||||||
|
"dsv-loader": "^2.0.0",
|
||||||
"express": "^4.14.0",
|
"express": "^4.14.0",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.4",
|
||||||
"react": "^15.3.0",
|
"react": "^15.3.0",
|
||||||
@@ -31,7 +32,8 @@
|
|||||||
"react-helmet": "^4.0.0",
|
"react-helmet": "^4.0.0",
|
||||||
"react-hot-loader": "^3.0.0-beta.2",
|
"react-hot-loader": "^3.0.0-beta.2",
|
||||||
"react-router-dom": "4.1.2",
|
"react-router-dom": "4.1.2",
|
||||||
"serve-favicon": "^2.3.0"
|
"serve-favicon": "^2.3.0",
|
||||||
|
"whatwg-fetch": "^2.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^6.4.0",
|
"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 = () => {
|
const Categorical = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div style={{marginTop: 50}}>
|
||||||
<h3>Categorical Metadata</h3>
|
<h3>Categorical Metadata</h3>
|
||||||
{
|
{
|
||||||
_.map(createCategoryCounts(cells),
|
_.map(createCategoryCounts(cells),
|
||||||
@@ -66,3 +66,12 @@ export default Categorical;
|
|||||||
<p> <button> Tumor [400] [40] [4] </button> </p>
|
<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 cells from "../../../data/GBM_metadata.js";
|
||||||
import drawParallelCoordinates from "./drawParallelCoordinates";
|
import drawParallelCoordinates from "./drawParallelCoordinates";
|
||||||
import createContinuousRanges from "./createContinuousRanges";
|
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 (
|
constructor(props) {
|
||||||
<div>
|
super(props);
|
||||||
<h3> Continuous Metadata </h3>
|
this.state = {
|
||||||
</div>
|
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;
|
export default Continuous;
|
||||||
|
|||||||
@@ -112,10 +112,7 @@ const drawParallelCoordinates = (data) => {
|
|||||||
******************************************
|
******************************************
|
||||||
******************************************/
|
******************************************/
|
||||||
|
|
||||||
var container = d3.select("body").append("div")
|
var container = d3.select("#parcoords")
|
||||||
.attr("class", "parcoords")
|
|
||||||
.style("width", width + margin.left + margin.right + "px")
|
|
||||||
.style("height", height + margin.top + margin.bottom + "px");
|
|
||||||
|
|
||||||
var svg = container.append("svg")
|
var svg = container.append("svg")
|
||||||
.attr("width", width + margin.left + margin.right)
|
.attr("width", width + margin.left + margin.right)
|
||||||
@@ -131,18 +128,19 @@ const drawParallelCoordinates = (data) => {
|
|||||||
.style("margin-top", margin.top + "px")
|
.style("margin-top", margin.top + "px")
|
||||||
.style("margin-left", margin.left + "px");
|
.style("margin-left", margin.left + "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);
|
||||||
|
|
||||||
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)
|
.data(dimensions)
|
||||||
.enter().append("g")
|
.enter().append("g")
|
||||||
.attr("class", "axis")
|
.attr("class", `${styles.axis} parcoords_axis`)
|
||||||
.attr("transform", function(d,i) { return "translate(" + xscale(i) + ")"; });
|
.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();
|
ctx.beginPath();
|
||||||
var coords = project(d);
|
var coords = project(d);
|
||||||
coords.forEach((p,i) => {
|
coords.forEach((p,i) => {
|
||||||
@@ -240,9 +239,8 @@ const drawParallelCoordinates = (data) => {
|
|||||||
render.invalidate();
|
render.invalidate();
|
||||||
|
|
||||||
var actives = [];
|
var actives = [];
|
||||||
svg.selectAll(".axis .brush")
|
svg.selectAll(".parcoords_axis .parcoords_brush")
|
||||||
.filter(function (d) {
|
.filter(function (d) {
|
||||||
console.log('this', this)
|
|
||||||
return d3.brushSelection(this);
|
return d3.brushSelection(this);
|
||||||
})
|
})
|
||||||
.each(function(d) {
|
.each(function(d) {
|
||||||
@@ -263,12 +261,14 @@ const drawParallelCoordinates = (data) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* Reset canvas */
|
||||||
ctx.clearRect(0,0,width,height);
|
ctx.clearRect(0,0,width,height);
|
||||||
ctx.globalAlpha = d3.min([0.85/Math.pow(selected.length,0.3),1]);
|
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);
|
render(selected);
|
||||||
|
|
||||||
output.text(d3.tsvFormat(selected.slice(0,24)));
|
output.text(d3.tsvFormat(selected.slice(0,22)));
|
||||||
// console.log('fires brush', actives)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function brushstart() {
|
function brushstart() {
|
||||||
@@ -297,13 +297,13 @@ const drawParallelCoordinates = (data) => {
|
|||||||
d3.select(this).call(renderAxis);
|
d3.select(this).call(renderAxis);
|
||||||
})
|
})
|
||||||
.append("text")
|
.append("text")
|
||||||
.attr("class", "title")
|
.attr("class", styles.title)
|
||||||
.attr("text-anchor", "start")
|
.attr("text-anchor", "start")
|
||||||
.text(function(d) { return "description" in d ? d.description : d.key; });
|
.text(function(d) { return "description" in d ? d.description : d.key; });
|
||||||
|
|
||||||
// Add and store a brush for each axis.
|
// Add and store a brush for each axis.
|
||||||
axes.append("g")
|
axes.append("g")
|
||||||
.attr("class", "brush")
|
.attr("class", `${styles.brush} parcoords_brush`)
|
||||||
.each(function(d) {
|
.each(function(d) {
|
||||||
d3.select(this).call(d.brush = d3.brushY()
|
d3.select(this).call(d.brush = d3.brushY()
|
||||||
.extent([[-10,0], [10,height]])
|
.extent([[-10,0], [10,height]])
|
||||||
@@ -316,7 +316,7 @@ const drawParallelCoordinates = (data) => {
|
|||||||
.attr("x", -8)
|
.attr("x", -8)
|
||||||
.attr("width", 16);
|
.attr("width", 16);
|
||||||
|
|
||||||
output.text(d3.tsvFormat(data.slice(0,24)));
|
output.text(d3.tsvFormat(data.slice(0,22)));
|
||||||
|
|
||||||
function project(d) {
|
function project(d) {
|
||||||
return dimensions.map(function(p,i) {
|
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};
|
const paddingRight = 120;
|
||||||
export const width = document.body.clientWidth - margin.left - margin.right;
|
|
||||||
|
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 height = 340 - margin.top - margin.bottom;
|
||||||
export const innerHeight = height - 2;
|
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 React from "react";
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from "react-router-dom";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from "react-helmet";
|
||||||
import Container from './container';
|
import Container from "./container";
|
||||||
|
import buttonStyles from "./buttons.css";
|
||||||
|
|
||||||
import Categorical from "./categorical/categorical";
|
import Categorical from "./categorical/categorical";
|
||||||
import Continuous from "./continuous/continuous";
|
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 (
|
const prefix = "http://ec2-34-234-75-156.compute-1.amazonaws.com/api/";
|
||||||
<Container>
|
const version = "v0.1/";
|
||||||
<Helmet title="cellxgene" />
|
|
||||||
<h1><Link to="/page2">cellxgene</Link></h1>
|
const expressions = fetch(`${prefix}${version}expression`, {
|
||||||
<Categorical/>
|
method: "post",
|
||||||
<Continuous/>
|
headers: new Headers({
|
||||||
</Container>
|
'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;
|
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 {
|
.hero {
|
||||||
font-size: 48;
|
font-size: 48px;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subHero {
|
.subHero {
|
||||||
font-size: 18;
|
font-size: 18px;
|
||||||
max-width: 600;
|
max-width: 600px;
|
||||||
text-align: "center";
|
text-align: center;
|
||||||
}
|
|
||||||
|
|
||||||
.primaryButton {
|
|
||||||
border: 1px solid black;
|
|
||||||
padding: 10px 15px;
|
|
||||||
background: none;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.or {
|
.or {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import React from 'react';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
import styles from './page2.css';
|
import styles from './page2.css';
|
||||||
|
import buttonStyles from './buttons.css';
|
||||||
|
|
||||||
|
|
||||||
import Container from './container';
|
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.
|
A data exploration interface for single cell genetic expression matrices. Subset, filter, cluster & validate, all in one place.
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
Reference in New Issue
Block a user