mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-28 23:18:11 +08:00
Begin integrating blueprint (#359)
* toast test * modify webpack build to allow for global CSS; use CSS modules to scope component styling * remove heatmap * add label to index resets * swapping out buttons for blueprint * adding constants to globals * swapping out blueprint buttons * title position, sidebar improvements * sidebar styles * adding toasts for validation errors * zebra section trial * integrating blueprint, switching buttons over, breaking out scatter * clean up console
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
// jshint esversion: 6
|
||||
import React from "react";
|
||||
import _ from "lodash";
|
||||
import { Button } from "@blueprintjs/core";
|
||||
import { connect } from "react-redux";
|
||||
import * as globals from "../../globals";
|
||||
|
||||
@connect()
|
||||
class CellSetButton extends React.Component {
|
||||
set() {
|
||||
const {
|
||||
differential,
|
||||
crossfilter,
|
||||
dispatch,
|
||||
eitherCellSetOneOrTwo
|
||||
} = this.props;
|
||||
|
||||
const set = _.map(crossfilter.allFiltered(), "name");
|
||||
|
||||
if (!differential.diffExp) {
|
||||
/* diffexp needs to be cleared before we store a new set */
|
||||
dispatch({
|
||||
type: `store current cell selection as differential set ${eitherCellSetOneOrTwo}`,
|
||||
data: set
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { differential, eitherCellSetOneOrTwo } = this.props;
|
||||
const cellListName = `celllist${eitherCellSetOneOrTwo}`;
|
||||
return (
|
||||
<span style={{ marginRight: 10 }}>
|
||||
<Button type="button" onClick={this.set.bind(this)}>
|
||||
{eitherCellSetOneOrTwo}
|
||||
{": "}
|
||||
{differential[cellListName]
|
||||
? `${differential[cellListName].length} cells`
|
||||
: "0 cells"}
|
||||
</Button>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CellSetButton;
|
||||
@@ -0,0 +1,79 @@
|
||||
// jshint esversion: 6
|
||||
import React from "react";
|
||||
import _ from "lodash";
|
||||
import { Button } from "@blueprintjs/core";
|
||||
import { connect } from "react-redux";
|
||||
import * as globals from "../../globals";
|
||||
import actions from "../../actions";
|
||||
import CellSetButton from "./cellSetButtons";
|
||||
|
||||
@connect(state => ({
|
||||
differential: state.differential,
|
||||
world: state.controls.world,
|
||||
crossfilter: state.controls.crossfilter,
|
||||
selectionUpdate: _.get(state.controls, "crossfilter.updateTime", null)
|
||||
}))
|
||||
class Expression extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
computeDiffExp() {
|
||||
const { dispatch, differential } = this.props;
|
||||
if (differential.celllist1 && differential.celllist2) {
|
||||
dispatch(
|
||||
actions.requestDifferentialExpression(
|
||||
differential.celllist1,
|
||||
differential.celllist2
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
clearDifferentialExpression() {
|
||||
const { dispatch, differential } = this.props;
|
||||
dispatch({
|
||||
type: "clear differential expression",
|
||||
diffExp: differential.diffExp
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { differential } = this.props;
|
||||
if (!differential) {
|
||||
return null;
|
||||
}
|
||||
const haveBothCellSets =
|
||||
!!differential.celllist1 && !!differential.celllist2;
|
||||
return (
|
||||
<div style={{ marginRight: 10, marginBottom: 10 }}>
|
||||
<CellSetButton {...this.props} eitherCellSetOneOrTwo={1} />
|
||||
<CellSetButton {...this.props} eitherCellSetOneOrTwo={2} />
|
||||
{!differential.diffExp ? (
|
||||
<Button
|
||||
style={{ marginTop: 10 }}
|
||||
disabled={!haveBothCellSets}
|
||||
intent="primary"
|
||||
type="button"
|
||||
onClick={this.computeDiffExp.bind(this)}
|
||||
>
|
||||
Compute Differential Expression
|
||||
</Button>
|
||||
) : null}
|
||||
{differential.diffExp ? (
|
||||
<Button
|
||||
type="button"
|
||||
style={{ marginTop: 10 }}
|
||||
intent="warning"
|
||||
onClick={this.clearDifferentialExpression.bind(this)}
|
||||
>
|
||||
Clear Differential Expression
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Expression;
|
||||
@@ -5,10 +5,11 @@ import React from "react";
|
||||
import _ from "lodash";
|
||||
import * as d3 from "d3";
|
||||
import { connect } from "react-redux";
|
||||
import { FaPlusCircle } from "react-icons/fa";
|
||||
import HistogramBrush from "../brushableHistogram";
|
||||
import * as globals from "../../globals";
|
||||
import actions from "../../actions";
|
||||
import { ErrorToastTopCenter } from "../framework/toasters";
|
||||
import ExpressionButtons from "./expressionButtons";
|
||||
|
||||
@connect(state => {
|
||||
const metadata = _.get(state.controls.world, "obsAnnotations", null);
|
||||
@@ -45,13 +46,18 @@ class GeneExpression extends React.Component {
|
||||
const { gene } = this.state;
|
||||
|
||||
if (userDefinedGenes.indexOf(gene) !== -1) {
|
||||
console.log("That gene already exists");
|
||||
ErrorToastTopCenter.show({
|
||||
message: "That gene already exists"
|
||||
});
|
||||
} else if (userDefinedGenes.length > 15) {
|
||||
console.log(
|
||||
"That's too many genes, you can have at most 15 user defined genes"
|
||||
);
|
||||
ErrorToastTopCenter.show({
|
||||
message:
|
||||
"That's too many genes, you can have at most 15 user defined genes"
|
||||
});
|
||||
} else if (!_.find(world.varAnnotations, { name: gene })) {
|
||||
console.log("That doesn't appear to be a valid gene name.");
|
||||
ErrorToastTopCenter.show({
|
||||
message: "That doesn't appear to be a valid gene name."
|
||||
});
|
||||
} else {
|
||||
dispatch(actions.requestUserDefinedGene(gene));
|
||||
dispatch({
|
||||
@@ -68,79 +74,88 @@ class GeneExpression extends React.Component {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
onKeyDown={this.keyPress.bind(this)}
|
||||
onChange={e => {
|
||||
this.setState({ gene: e.target.value });
|
||||
}}
|
||||
type="text"
|
||||
value={gene}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
<div
|
||||
style={{
|
||||
border: "none",
|
||||
background: "none",
|
||||
cursor: "pointer",
|
||||
outline: "none",
|
||||
marginTop: 20,
|
||||
padding: 0
|
||||
padding: globals.leftSidebarSectionPadding,
|
||||
marginTop: 30
|
||||
}}
|
||||
onClick={this.handleClick.bind(this)}
|
||||
>
|
||||
<FaPlusCircle
|
||||
style={{
|
||||
display: "inline-block",
|
||||
marginLeft: 7,
|
||||
position: "relative",
|
||||
top: 5,
|
||||
color: globals.brightBlue,
|
||||
fontSize: 22
|
||||
}}
|
||||
/>{" "}
|
||||
add gene{" "}
|
||||
</button>
|
||||
{world && userDefinedGenes.length > 0 ? (
|
||||
<p>User defined genes</p>
|
||||
) : null}
|
||||
{world && userDefinedGenes.length > 0
|
||||
? _.map(userDefinedGenes, geneName => {
|
||||
const values = world.varDataCache[geneName];
|
||||
if (!values) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<HistogramBrush
|
||||
key={geneName}
|
||||
field={geneName}
|
||||
ranges={d3.extent(values)}
|
||||
isUserDefined
|
||||
/>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
{differential.diffExp ? <p> Differentially Expressed Genes </p> : null}
|
||||
{differential.diffExp
|
||||
? _.map(differential.diffExp, value => {
|
||||
const annotations = world.varAnnotations[value[0]];
|
||||
const { name } = annotations;
|
||||
const values = world.varDataCache[name];
|
||||
if (!values) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<HistogramBrush
|
||||
key={name}
|
||||
field={name}
|
||||
ranges={d3.extent(values)}
|
||||
isDiffExp
|
||||
diffExp_avgDiff={value[1]}
|
||||
diffExp_set1AvgExp={value[4]}
|
||||
diffExp_set2AvgExp={value[5]}
|
||||
/>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
<p style={globals.leftSidebarSectionHeading}>Custom genes</p>
|
||||
<div style={{ marginTop: 11 }} className="bp3-control-group">
|
||||
<div className="bp3-input-group">
|
||||
<span className="bp3-icon bp3-icon-symbol-cross" />
|
||||
<input
|
||||
onKeyDown={this.keyPress.bind(this)}
|
||||
onChange={e => {
|
||||
this.setState({ gene: e.target.value });
|
||||
}}
|
||||
value={gene}
|
||||
type="text"
|
||||
className="bp3-input"
|
||||
placeholder="Add a custom gene"
|
||||
style={{ paddingRight: 94 }}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="bp3-button bp3-intent-primary"
|
||||
onClick={this.handleClick.bind(this)}
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
{world && userDefinedGenes.length > 0
|
||||
? _.map(userDefinedGenes, geneName => {
|
||||
const values = world.varDataCache[geneName];
|
||||
if (!values) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<HistogramBrush
|
||||
key={geneName}
|
||||
field={geneName}
|
||||
ranges={d3.extent(values)}
|
||||
isUserDefined
|
||||
/>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
padding: globals.leftSidebarSectionPadding
|
||||
}}
|
||||
>
|
||||
<p
|
||||
style={Object.assign({}, globals.leftSidebarSectionHeading, {
|
||||
marginTop: 40
|
||||
})}
|
||||
>
|
||||
Differentially Expressed Genes
|
||||
</p>
|
||||
<ExpressionButtons />
|
||||
{differential.diffExp
|
||||
? _.map(differential.diffExp, value => {
|
||||
const annotations = world.varAnnotations[value[0]];
|
||||
const { name } = annotations;
|
||||
const values = world.varDataCache[name];
|
||||
if (!values) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<HistogramBrush
|
||||
key={name}
|
||||
field={name}
|
||||
ranges={d3.extent(values)}
|
||||
isDiffExp
|
||||
avgDiff={value[1]}
|
||||
set1AvgExp={value[4]}
|
||||
set2AvgExp={value[5]}
|
||||
/>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user