mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-10 06:00:56 +08:00
break up expression heatmap into sub components
This commit is contained in:
@@ -0,0 +1,246 @@
|
|||||||
|
import React from "react";
|
||||||
|
import _ from "lodash";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import * as globals from "../../globals";
|
||||||
|
import styles from "./expression.css";
|
||||||
|
import SectionHeader from "../framework/sectionHeader"
|
||||||
|
import actions from "../../actions";
|
||||||
|
import ReactAutocomplete from "react-autocomplete";
|
||||||
|
import getContrast from "font-color-contrast"; // https://www.npmjs.com/package/font-color-contrast
|
||||||
|
|
||||||
|
class HeatmapSquare extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
value: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const contrastColor = getContrast(
|
||||||
|
this.props.backgroundColor
|
||||||
|
.substring(4, this.props.backgroundColor.length-1)
|
||||||
|
.replace(/ /g, "")
|
||||||
|
.split(",")
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<p style={{
|
||||||
|
padding: "12px 6px",
|
||||||
|
textAlign: "center",
|
||||||
|
color: contrastColor,
|
||||||
|
width: 40,
|
||||||
|
fontSize: 12,
|
||||||
|
margin: 0,
|
||||||
|
backgroundColor: this.props.backgroundColor,
|
||||||
|
}}>
|
||||||
|
{this.props.text}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************
|
||||||
|
***********************************
|
||||||
|
***********************************
|
||||||
|
Row
|
||||||
|
***********************************
|
||||||
|
***********************************
|
||||||
|
**********************************/
|
||||||
|
@connect((state) => {
|
||||||
|
|
||||||
|
return {
|
||||||
|
scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
|
||||||
|
scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
|
||||||
|
colorAccessor: state.controls.colorAccessor,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
class HeatmapRow extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
value: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleGeneColorScaleClick(gene) {
|
||||||
|
return () => {
|
||||||
|
this.props.dispatch(
|
||||||
|
actions.requestSingleGeneExpressionCountsForColoringPOST(this.props.gene)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleSetGeneAsScatterplotX(gene) {
|
||||||
|
return () => {
|
||||||
|
this.props.dispatch({
|
||||||
|
type: "set scatterplot x",
|
||||||
|
data: this.props.gene
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleSetGeneAsScatterplotY(gene) {
|
||||||
|
return () => {
|
||||||
|
this.props.dispatch({
|
||||||
|
type: "set scatterplot y",
|
||||||
|
data: this.props.gene
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
width: 220,
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
alignItems: "baseline",
|
||||||
|
}}>
|
||||||
|
<div style={{width: 200}}>
|
||||||
|
<span
|
||||||
|
onClick={this.handleSetGeneAsScatterplotX(this.props.gene).bind(this)}
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
color: "#0074D9", // http://clrs.cc/
|
||||||
|
cursor: "pointer",
|
||||||
|
position: "relative",
|
||||||
|
top: 1,
|
||||||
|
fontWeight: 700,
|
||||||
|
marginRight: 6,
|
||||||
|
border: this.props.scatterplotXXaccessor === this.props.gene ? "1px solid black" : "none",
|
||||||
|
}}>X</span>
|
||||||
|
<span
|
||||||
|
onClick={this.handleSetGeneAsScatterplotY(this.props.gene).bind(this)}
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
color: "#0074D9", // http://clrs.cc/
|
||||||
|
cursor: "pointer",
|
||||||
|
position: "relative",
|
||||||
|
top: 1,
|
||||||
|
fontWeight: 700,
|
||||||
|
marginRight: 6,
|
||||||
|
border: this.props.scatterplotYYaccessor === this.props.gene ? "1px solid black" : "none",
|
||||||
|
}}>Y</span>
|
||||||
|
<span
|
||||||
|
onClick={this.handleGeneColorScaleClick(this.props.gene).bind(this)}
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
cursor: "pointer",
|
||||||
|
position: "relative",
|
||||||
|
top: 2,
|
||||||
|
marginRight: 6,
|
||||||
|
border: this.props.colorAccessor === this.props.gene ? "1px solid black" : "none",
|
||||||
|
}}>🖌️</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 14
|
||||||
|
}}>
|
||||||
|
{this.props.gene}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<HeatmapSquare
|
||||||
|
backgroundColor={this.props.greyColorScale(this.props.set1exp)}
|
||||||
|
text={this.props.set1exp}/>
|
||||||
|
<HeatmapSquare
|
||||||
|
backgroundColor={this.props.greyColorScale(this.props.set2exp)}
|
||||||
|
text={this.props.set2exp}/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************
|
||||||
|
***********************************
|
||||||
|
***********************************
|
||||||
|
DiffExp Heatmap
|
||||||
|
***********************************
|
||||||
|
***********************************
|
||||||
|
**********************************/
|
||||||
|
|
||||||
|
@connect((state) => {
|
||||||
|
return {
|
||||||
|
differential: state.differential,
|
||||||
|
allGeneNames: state.controls.allGeneNames
|
||||||
|
}
|
||||||
|
})
|
||||||
|
class Heatmap extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
value: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
if (!this.props.differential.diffExp) return null
|
||||||
|
|
||||||
|
const topGenesForCellSet1 = this.props.differential.diffExp.data.celllist1;
|
||||||
|
const topGenesForCellSet2 = this.props.differential.diffExp.data.celllist2;
|
||||||
|
|
||||||
|
const extent = d3.extent(
|
||||||
|
_.union(
|
||||||
|
topGenesForCellSet1.mean_expression_cellset1,
|
||||||
|
topGenesForCellSet1.mean_expression_cellset2,
|
||||||
|
topGenesForCellSet2.mean_expression_cellset1,
|
||||||
|
topGenesForCellSet2.mean_expression_cellset2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const greyColorScale = d3.scaleSequential()
|
||||||
|
.domain(extent)
|
||||||
|
.interpolator(d3.interpolateGreys);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Color by any gene:
|
||||||
|
<ReactAutocomplete
|
||||||
|
items={this.props.allGeneNames}
|
||||||
|
shouldItemRender={(item, value) => item.toLowerCase().indexOf(value.toLowerCase()) > -1}
|
||||||
|
getItemValue={item => item}
|
||||||
|
renderItem={(item, highlighted) =>
|
||||||
|
<div
|
||||||
|
key={item}
|
||||||
|
style={{ backgroundColor: highlighted ? '#eee' : 'transparent'}}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
value={this.state.value}
|
||||||
|
onChange={e => this.setState({ value: e.target.value })}
|
||||||
|
onSelect={(value) => {
|
||||||
|
this.setState({ value });
|
||||||
|
this.props.dispatch(actions.requestSingleGeneExpressionCountsForColoringPOST(value))
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
width: 165,
|
||||||
|
fontWeight: 700,
|
||||||
|
}}>
|
||||||
|
<p style={{width: 100}}>Gene</p>
|
||||||
|
<p>1</p>
|
||||||
|
<p>2</p>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
topGenesForCellSet1.topgenes.map((gene, i) => {
|
||||||
|
return <HeatmapRow
|
||||||
|
key={gene}
|
||||||
|
gene={gene}
|
||||||
|
greyColorScale={greyColorScale}
|
||||||
|
set1exp={Math.floor(topGenesForCellSet1.mean_expression_cellset1[i])}
|
||||||
|
set2exp={Math.floor(topGenesForCellSet1.mean_expression_cellset2[i])}
|
||||||
|
/>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{
|
||||||
|
topGenesForCellSet2.topgenes.map((gene, i) => {
|
||||||
|
return <HeatmapRow
|
||||||
|
key={gene}
|
||||||
|
gene={gene}
|
||||||
|
greyColorScale={greyColorScale}
|
||||||
|
set1exp={Math.floor(topGenesForCellSet2.mean_expression_cellset1[i])}
|
||||||
|
set2exp={Math.floor(topGenesForCellSet2.mean_expression_cellset2[i])}
|
||||||
|
/>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Heatmap;
|
||||||
@@ -3,177 +3,11 @@ import _ from "lodash";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
import styles from "./expression.css";
|
import styles from "./expression.css";
|
||||||
import SectionHeader from "../framework/sectionHeader"
|
|
||||||
import actions from "../../actions";
|
import actions from "../../actions";
|
||||||
import ReactAutocomplete from "react-autocomplete";
|
import Heatmap from "./diffExpHeatmap";
|
||||||
|
|
||||||
|
|
||||||
@connect((state) => {
|
|
||||||
return {
|
|
||||||
differential: state.differential,
|
|
||||||
allGeneNames: state.controls.allGeneNames
|
|
||||||
}
|
|
||||||
})
|
|
||||||
class DiffExp extends React.Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
this.state = {
|
|
||||||
value: '',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handleGeneColorScaleClick(gene) {
|
|
||||||
return () => {
|
|
||||||
this.props.dispatch(actions.requestSingleGeneExpressionCountsForColoringPOST(gene))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
if (!this.props.differential.diffExp) return null
|
|
||||||
|
|
||||||
const topGenesForCellSet1 = this.props.differential.diffExp.data.celllist1;
|
|
||||||
const topGenesForCellSet2 = this.props.differential.diffExp.data.celllist2;
|
|
||||||
const rectSide = 10;
|
|
||||||
const rightMargin = 0;
|
|
||||||
|
|
||||||
const extent = d3.extent(
|
|
||||||
_.union(
|
|
||||||
topGenesForCellSet1.mean_expression_cellset1,
|
|
||||||
topGenesForCellSet1.mean_expression_cellset2,
|
|
||||||
topGenesForCellSet2.mean_expression_cellset1,
|
|
||||||
topGenesForCellSet2.mean_expression_cellset2
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
const greyColorScale = d3.scaleSequential()
|
|
||||||
.domain(extent)
|
|
||||||
.interpolator(d3.interpolateGreys);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
Color by any gene:
|
|
||||||
<ReactAutocomplete
|
|
||||||
items={this.props.allGeneNames}
|
|
||||||
shouldItemRender={(item, value) => item.toLowerCase().indexOf(value.toLowerCase()) > -1}
|
|
||||||
getItemValue={item => item}
|
|
||||||
renderItem={(item, highlighted) =>
|
|
||||||
<div
|
|
||||||
key={item}
|
|
||||||
style={{ backgroundColor: highlighted ? '#eee' : 'transparent'}}
|
|
||||||
>
|
|
||||||
{item}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
value={this.state.value}
|
|
||||||
onChange={e => this.setState({ value: e.target.value })}
|
|
||||||
onSelect={(value) => {
|
|
||||||
this.setState({ value });
|
|
||||||
this.props.dispatch(actions.requestSingleGeneExpressionCountsForColoringPOST(value))
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
width: 400,
|
|
||||||
fontWeight: 700,
|
|
||||||
}}>
|
|
||||||
<p style={{width: 200}}>Gene</p>
|
|
||||||
<p>Set 1 exp</p>
|
|
||||||
<p>Set 2 exp</p>
|
|
||||||
</div>
|
|
||||||
{
|
|
||||||
topGenesForCellSet1.topgenes.map((gene, i) => {
|
|
||||||
return (
|
|
||||||
<div key={gene} style={{
|
|
||||||
width: 400,
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "baseline",
|
|
||||||
}}>
|
|
||||||
<div style={{width: 200}}>
|
|
||||||
<span
|
|
||||||
onClick={this.handleGeneColorScaleClick(gene).bind(this)}
|
|
||||||
style={{
|
|
||||||
fontSize: 16,
|
|
||||||
cursor: "pointer",
|
|
||||||
position: "relative",
|
|
||||||
top: 2,
|
|
||||||
marginRight: 6
|
|
||||||
}}>🖌️</span>
|
|
||||||
<span>{gene}</span>
|
|
||||||
</div>
|
|
||||||
<p style={{
|
|
||||||
padding: 20,
|
|
||||||
color: extent[1] === topGenesForCellSet1.mean_expression_cellset1[i] ? "white" : "black",
|
|
||||||
width: 60,
|
|
||||||
margin: 0,
|
|
||||||
backgroundColor: greyColorScale(Math.floor(topGenesForCellSet1.mean_expression_cellset1[i])),
|
|
||||||
}}>
|
|
||||||
{Math.floor(topGenesForCellSet1.mean_expression_cellset1[i])}
|
|
||||||
</p>
|
|
||||||
<p style={{
|
|
||||||
padding: 20,
|
|
||||||
color: extent[1] === topGenesForCellSet1.mean_expression_cellset2[i] ? "white" : "black",
|
|
||||||
width: 60,
|
|
||||||
margin: 0,
|
|
||||||
backgroundColor: greyColorScale(Math.floor(topGenesForCellSet1.mean_expression_cellset2[i])),
|
|
||||||
}}>
|
|
||||||
{Math.floor(topGenesForCellSet1.mean_expression_cellset2[i])}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
{
|
|
||||||
topGenesForCellSet2.topgenes.map((gene, i) => {
|
|
||||||
return (
|
|
||||||
<div key={gene} style={{
|
|
||||||
width: 400,
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "baseline",
|
|
||||||
}}>
|
|
||||||
<div style={{width: 200}}>
|
|
||||||
<span
|
|
||||||
onClick={this.handleGeneColorScaleClick(gene).bind(this)}
|
|
||||||
style={{
|
|
||||||
fontSize: 16,
|
|
||||||
cursor: "pointer",
|
|
||||||
position: "relative",
|
|
||||||
top: 2,
|
|
||||||
marginRight: 6
|
|
||||||
}}>🖌️</span>
|
|
||||||
<span>{gene}</span>
|
|
||||||
</div>
|
|
||||||
<p style={{
|
|
||||||
padding: 20,
|
|
||||||
color: extent[1] === topGenesForCellSet2.mean_expression_cellset1[i] ? "white" : "black",
|
|
||||||
width: 60,
|
|
||||||
margin: 0,
|
|
||||||
backgroundColor: greyColorScale(Math.floor(topGenesForCellSet2.mean_expression_cellset1[i])),
|
|
||||||
}}>
|
|
||||||
{Math.floor(topGenesForCellSet2.mean_expression_cellset1[i])}
|
|
||||||
</p>
|
|
||||||
<p style={{
|
|
||||||
padding: 20,
|
|
||||||
color: extent[1] === topGenesForCellSet2.mean_expression_cellset2[i] ? "white" : "black",
|
|
||||||
width: 60,
|
|
||||||
margin: 0,
|
|
||||||
backgroundColor: greyColorScale(Math.floor(topGenesForCellSet2.mean_expression_cellset2[i])),
|
|
||||||
}}>
|
|
||||||
{Math.floor(topGenesForCellSet2.mean_expression_cellset2[i])}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// <p> Top genes expressed by cellset 2 </p>
|
// <p> Top genes expressed by cellset 2 </p>
|
||||||
// <p> Gene {"............"} Exp in set 1 {"............"} Exp in set 2 </p>
|
// <p> Gene {"............"} Exp in set 1 {"............"} Exp in set 2 </p>
|
||||||
|
|
||||||
|
|
||||||
@connect((state) => {
|
@connect((state) => {
|
||||||
return {
|
return {
|
||||||
currentCellSelection: state.controls.currentCellSelection,
|
currentCellSelection: state.controls.currentCellSelection,
|
||||||
@@ -276,7 +110,6 @@ class Expression extends React.Component {
|
|||||||
border: "none",
|
border: "none",
|
||||||
borderRadius: 3,
|
borderRadius: 3,
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
|
|
||||||
}}
|
}}
|
||||||
onClick={this.set2.bind(this)}>
|
onClick={this.set2.bind(this)}>
|
||||||
{
|
{
|
||||||
@@ -298,55 +131,15 @@ class Expression extends React.Component {
|
|||||||
border: "none",
|
border: "none",
|
||||||
borderRadius: 3,
|
borderRadius: 3,
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
|
|
||||||
}}
|
}}
|
||||||
onClick={this.computeDiffExp.bind(this)}>
|
onClick={this.computeDiffExp.bind(this)}>
|
||||||
Compute differential expression
|
Compute differential expression
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<DiffExp/>
|
<Heatmap/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Expression;
|
export default Expression;
|
||||||
|
|
||||||
|
|
||||||
// <SectionHeader text="Differential Expression"/>
|
|
||||||
// <SectionHeader text="Color by expression"/>
|
|
||||||
// {
|
|
||||||
// _.map(this.props.expression.genes, (gene) => {
|
|
||||||
// return (
|
|
||||||
// <button
|
|
||||||
// key={gene}
|
|
||||||
// onClick={this.handleClick(gene).bind(this)}
|
|
||||||
// style={{marginRight: 10}}>
|
|
||||||
// {gene}
|
|
||||||
// </button>
|
|
||||||
// )
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// Gene count:
|
|
||||||
// <input placeholder={"Default is 5"}/>
|
|
||||||
|
|
||||||
// <svg>
|
|
||||||
// <g transform="translate(200, 40)">
|
|
||||||
// {
|
|
||||||
// _.map(topGenesForCellSet1.topgenes, (gene, i) => {
|
|
||||||
// return (
|
|
||||||
// <g key={gene}>
|
|
||||||
// <rect
|
|
||||||
// width={rectSide}
|
|
||||||
// height={rectSide}
|
|
||||||
// x={i * (rectSide + rightMargin)}
|
|
||||||
// fill={"rgb(255,0,0)"}
|
|
||||||
// />
|
|
||||||
// <g>
|
|
||||||
// )
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// </g>
|
|
||||||
// </svg>
|
|
||||||
|
|||||||
Reference in New Issue
Block a user