Skip to content
This repository has been archived by the owner on Sep 4, 2020. It is now read-only.

Commit

Permalink
Feature/155186331 read cluster files (#4)
Browse files Browse the repository at this point in the history
* Change default example to our new SC poster child experiment

* Make separate requests for gene expression and cell clusters

* Remove legend and set the same symbol shape for all series in both charts; add a wider colour palette for clusters

* Change name of component to match name of file
#4

* Renamed parameters to be more explicit about their purpose
#4

* Fix tests after change of parameter
  • Loading branch information
alfonsomunozpomer committed Apr 30, 2018
1 parent a2ce2f3 commit 3c60a96
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 82 deletions.
4 changes: 2 additions & 2 deletions __test__/ClusterTSnePlot.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe(`ClusterTSnePlot`, () => {
}

const tree = renderer
.create(<ClusterTSnePlot height={500} ks={[]} k={0} onChangeK={onChangeK} perplexities={[]} perplexity={0} onChangePerplexity={onChangePerplexity} loading={true} plotData={plotData}/>)
.create(<ClusterTSnePlot height={500} ks={[]} selectedK={0} onChangeK={onChangeK} perplexities={[]} selectedPerplexity={0} onChangePerplexity={onChangePerplexity} loading={true} plotData={plotData}/>)
.toJSON()

expect(tree).toMatchSnapshot()
Expand All @@ -93,7 +93,7 @@ describe(`ClusterTSnePlot`, () => {
series: []
}

const wrapper = mount(<ClusterTSnePlot height={500} ks={[]} k={0} onChangeK={onChangeK} perplexities={[]} perplexity={0} onChangePerplexity={onChangePerplexity} loading={true} plotData={plotData}/>)
const wrapper = mount(<ClusterTSnePlot height={500} ks={[]} selectedK={0} onChangeK={onChangeK} perplexities={[]} selectedPerplexity={0} onChangePerplexity={onChangePerplexity} loading={true} plotData={plotData}/>)

expect(wrapper.find(ScatterPlotLoader).length).toBe(1)
})
Expand Down
11 changes: 6 additions & 5 deletions html/Demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import ReactDOM from 'react-dom'
import ExperimentPageView from '../src/index'

const perplexities = [1, 5, 10, 15, 20]
const ks = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]

class Demo extends React.Component {
constructor(props) {
super(props)

this.state = {
k: 2,
k: ks[Math.round((ks.length -1) / 2)],
perplexity: perplexities[Math.round((perplexities.length - 1) / 2)],
geneId: ``,
inputHighlightClusters: ``,
Expand Down Expand Up @@ -49,11 +50,11 @@ class Demo extends React.Component {

<ExperimentPageView atlasUrl={`http://localhost:8080/scxa/`}
suggesterEndpoint={`json/suggestions`}
experimentAccession={`E-MTAB-5061`}
experimentAccession={`E-GEOD-106540`}
perplexities={perplexities}
perplexity={this.state.perplexity}
ks={[2, 3, 4, 5, 6, 7, 8, 9, 10]}
k={this.state.k}
selectedPerplexity={this.state.perplexity}
ks={ks}
selectedK={this.state.k}
highlightClusters={this.state.highlightClusters}
geneId={this.state.geneId}
speciesName={'Homo sapiens'}
Expand Down
51 changes: 35 additions & 16 deletions src/ClusterTSnePlot.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,56 +22,75 @@ const _colourizeClusters = (highlightSeries) =>
})

const ClusterTSnePlot = (props) => {
const {ks, k, onChangeK, perplexities, perplexity, onChangePerplexity} = props // Select
const {ks, selectedK, onChangeK, perplexities, selectedPerplexity, onChangePerplexity} = props // Select
const {plotData, highlightClusters, height} = props // Chart
const {loading, resourcesUrl, errorMessage} = props // Overlay

const highchartsConfig = {
plotOptions: {
scatter: {
tooltip: {
headerFormat: `<b>Cluster {series.name}</b><br>`,
headerFormat: `<b>{series.name}</b><br>`,
pointFormat: `{point.name}`
},
marker: {
symbol: `circle`
}
}
},
// Generated with http://tools.medialab.sciences-po.fr/iwanthue/
colors: [
'rgba(178, 95, 188, 0.7)',
'rgba(118, 179, 65, 0.7)',
'rgba(104, 130, 207, 0.7)',
'rgba(206, 155, 68, 0.7)',
'rgba(200, 87, 123, 0.7)',
'rgba(79, 174, 132, 0.7)',
'rgba(201, 92, 63, 0.7)',
'rgba(124, 127, 57, 0.7)'
],
`rgba(212, 137, 48, 0.7)`,
`rgba(71, 193, 152, 0.7)`,
`rgba(193, 84, 47, 0.7)`,
`rgba(90, 147, 221, 0.7)`,
`rgba(194, 73, 97, 0.7)`,
`rgba(128, 177, 66, 0.7)`,
`rgba(208, 76, 134, 0.7)`,
`rgba(188, 176, 59, 0.7)`,
`rgba(132, 43, 102, 0.7)`,
`rgba(93, 188, 108, 0.7)`,
`rgba(82, 45, 128, 0.7)`,
`rgba(101, 133, 52, 0.7)`,
`rgba(169, 107, 212, 0.7)`,
`rgba(185, 140, 70, 0.7)`,
`rgba(82, 88, 180, 0.7)`,
`rgba(176, 73, 62, 0.7)`,
`rgba(101, 127, 233, 0.7)`,
`rgba(214, 126, 188, 0.7)`,
`rgba(196, 86, 178, 0.7)`,
`rgba(173, 131, 211, 0.7)`
],
chart: {
height: height
},
title: {
text: `Clusters`
},
legend: {
enabled: false
}
}

const perplexityOptions = perplexities.sort((a, b) => a - b).map((perplexity) => (
<option key={perplexity} value={perplexity}>{perplexity}</option>
))

const kOptions = ks.sort().map((k) => (
const kOptions = ks.sort((a, b) => a - b).map((k) => (
<option key={k} value={k}>{k}</option>
))

return [
<div key={`perplexity-k-select`} className={`row`}>
<div className={`column medium-6`}>
<label>t-SNE Perplexity</label>
<select value={perplexity} onChange={ (event) => { onChangePerplexity(Number(event.target.value)) } }>
<select value={selectedPerplexity} onChange={ (event) => { onChangePerplexity(Number(event.target.value)) } }>
{perplexityOptions}
</select>
</div>
<div className={`column medium-6`}>
<label>Number of clusters, <i>k</i></label>
<select value={k} onChange={ (event) => { onChangeK(Number(event.target.value)) } }>
<select value={selectedK} onChange={ (event) => { onChangeK(Number(event.target.value)) } }>
{kOptions}
</select>
</div>
Expand All @@ -97,11 +116,11 @@ ClusterTSnePlot.propTypes = {
highlightClusters: PropTypes.array,

ks: PropTypes.arrayOf(PropTypes.number).isRequired,
k: PropTypes.number.isRequired,
selectedK: PropTypes.number.isRequired,
onChangeK: PropTypes.func.isRequired,

perplexities: PropTypes.arrayOf(PropTypes.number).isRequired,
perplexity: PropTypes.number.isRequired,
selectedPerplexity: PropTypes.number.isRequired,
onChangePerplexity: PropTypes.func.isRequired,

loading: PropTypes.bool.isRequired,
Expand Down
8 changes: 7 additions & 1 deletion src/GeneExpressionTSnePlot.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,11 @@ const GeneExpressionScatterPlot = (props) => {
plotOptions: {
scatter: {
tooltip: {
headerFormat: `<b>{point.key} – Cluster {series.name}</b><br>`,
headerFormat: `<b>{point.key}</b><br>`,
pointFormat: geneId ? `Expression level: {point.expressionLevel} ${plotData.unit}` : `No gene selected`
},
marker: {
symbol: `circle`
}
}
},
Expand All @@ -90,6 +93,9 @@ const GeneExpressionScatterPlot = (props) => {
},
title: {
text: `Gene expression`
},
legend: {
enabled: false
}
}

Expand Down
128 changes: 70 additions & 58 deletions src/TSnePlotView.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,101 +12,113 @@ const fetchResponseJson = async (base, endpoint) => {
return responseJson
}

class ExperimentPageView extends React.Component {
class TSnePlotView extends React.Component {
constructor(props) {
super(props)
this.state = {
data: {
geneExpressionData: {
max: null,
min: null,
series: [],
unit: ``
},
errorMessage: null,
loadingClusters: false,
cellClustersData: {
series: []
},
geneExpressionErrorMessage: null,
cellClustersErrorMessage: null,
loadingCellClusters: false,
loadingGeneExpression: false
}
}

_fetchAndSetState({atlasUrl, experimentAccession, k, perplexity, geneId}) {
const atlasEndpoint = `json/experiments/${experimentAccession}/tsneplot/${perplexity}/clusters/${k}/expression/${geneId}`
_fetchAndSetStateCellClusters({atlasUrl, experimentAccession, selectedK, selectedPerplexity}) {
this.setState({
loadingCellClusters: true
}, () => {
fetchResponseJson(atlasUrl, `json/experiments/${experimentAccession}/tsneplot/${selectedPerplexity}/clusters/k/${selectedK}`)
.then((responseJson) => {
this.setState({
cellClustersData: responseJson,
cellClustersErrorMessage: null,
loadingCellClusters: false
})
})
.catch((reason) => {
this.setState({
cellClustersErrorMessage: `${reason.name}: ${reason.message}`,
loadingCellClusters: false
})
})
})
}

return fetchResponseJson(atlasUrl, atlasEndpoint)
.then((responseJson) => {
this.setState({
data: responseJson,
errorMessage: null,
loadingClusters: false,
loadingGeneExpression: false
_fetchAndSetStateGeneId({atlasUrl, experimentAccession, selectedPerplexity, geneId}) {
this.setState({
loadingGeneExpression: true
}, () => {
fetchResponseJson(atlasUrl, `json/experiments/${experimentAccession}/tsneplot/${selectedPerplexity}/expression/${geneId}`)
.then((responseJson) => {
this.setState({
geneExpressionData: responseJson,
geneExpressionErrorMessage: null,
loadingGeneExpression: false,
})
})
})
.catch((reason) => {
this.setState({
errorMessage: `${reason.name}: ${reason.message}`,
loadingClusters: false,
loadingGeneExpression: false
.catch((reason) => {
this.setState({
geneExpressionErrorMessage: `${reason.name}: ${reason.message}`,
loadingGeneExpression: false
})
})
})
})
}

componentWillReceiveProps(nextProps) {
if (nextProps.atlasUrl !== this.props.atlasUrl || // First two will never happen but it’s the right thing to do
nextProps.experimentAccession !== this.props.experimentAccession ||
nextProps.perplexity !== this.props.perplexity ||
nextProps.k !== this.props.k) {

this.setState({
loadingClusters: true,
loadingGeneExpression: true
})
this._fetchAndSetState(nextProps)

if (nextProps.selectedPerplexity !== this.props.selectedPerplexity) {
this._fetchAndSetStateCellClusters(nextProps)
this._fetchAndSetStateGeneId(nextProps)
} else if (nextProps.selectedK !== this.props.selectedK) {
this._fetchAndSetStateCellClusters(nextProps)
} else if (nextProps.geneId !== this.props.geneId) {

this.setState({
loadingGeneExpression: true
})
this._fetchAndSetState(nextProps)

this._fetchAndSetStateGeneId(nextProps)
}
}

componentDidMount() {
this.setState({
loadingClusters: true,
loadingGeneExpression: true
})
// Having _fetchAndSetState as callback is the right thing, but then we can’t return the promise; see tests
return this._fetchAndSetState(this.props)
this._fetchAndSetStateCellClusters(this.props)
this._fetchAndSetStateGeneId(this.props)
}

render() {
const {height, atlasUrl, resourcesUrl} = this.props
const {suggesterEndpoint, geneId, speciesName, highlightClusters, ks, k, perplexities, perplexity} = this.props
const {height, atlasUrl, resourcesUrl, suggesterEndpoint} = this.props
const {geneId, speciesName, highlightClusters} = this.props
const {ks, selectedK, perplexities, selectedPerplexity} = this.props
const {onChangePerplexity, onChangeK, onSelectGeneId} = this.props
const {loadingClusters, loadingGeneExpression, data, errorMessage} = this.state
const {loadingGeneExpression, geneExpressionData, geneExpressionErrorMessage} = this.state
const {loadingCellClusters, cellClustersData, cellClustersErrorMessage} = this.state

return (
<div className={`row`}>
<div className={`small-12 medium-6 columns`}>
<ClusterTSnePlot height={height}
plotData={data}
plotData={cellClustersData}
perplexities={perplexities}
perplexity={perplexity}
selectedPerplexity={selectedPerplexity}
onChangePerplexity={onChangePerplexity}
ks={ks}
k={k}
selectedK={selectedK}
onChangeK={onChangeK}
highlightClusters={highlightClusters}
loading={loadingClusters}
loading={loadingCellClusters}
resourcesUrl={resourcesUrl}
errorMessage={errorMessage}
errorMessage={cellClustersErrorMessage}
/>
</div>

<div className={`small-12 medium-6 columns`}>
<GeneExpressionTSnePlot height={height}
plotData={data}
plotData={geneExpressionData}
atlasUrl={atlasUrl}
suggesterEndpoint={suggesterEndpoint}
onSelectGeneId={onSelectGeneId}
Expand All @@ -115,7 +127,7 @@ class ExperimentPageView extends React.Component {
highlightClusters={[]}
loading={loadingGeneExpression}
resourcesUrl={resourcesUrl}
errorMessage={errorMessage}
errorMessage={geneExpressionErrorMessage}
/>
</div>

Expand All @@ -130,14 +142,14 @@ class ExperimentPageView extends React.Component {
}
}

ExperimentPageView.propTypes = {
TSnePlotView.propTypes = {
atlasUrl: PropTypes.string.isRequired,
suggesterEndpoint: PropTypes.string.isRequired,
experimentAccession: PropTypes.string.isRequired,
ks: PropTypes.arrayOf(PropTypes.number).isRequired,
k: PropTypes.number.isRequired,
selectedK: PropTypes.number.isRequired,
perplexities: PropTypes.arrayOf(PropTypes.number).isRequired,
perplexity: PropTypes.number.isRequired,
selectedPerplexity: PropTypes.number.isRequired,
highlightClusters: PropTypes.arrayOf(PropTypes.number),
geneId: PropTypes.string.isRequired,
speciesName: PropTypes.string.isRequired,
Expand All @@ -148,7 +160,7 @@ ExperimentPageView.propTypes = {
onChangePerplexity: PropTypes.func
}

ExperimentPageView.defaultProps = {
TSnePlotView.defaultProps = {
highlightClusters: [],
geneId: '',
speciesName: '',
Expand All @@ -158,4 +170,4 @@ ExperimentPageView.defaultProps = {
onPerplexityChange: () => {}
}

export default ExperimentPageView
export default TSnePlotView

0 comments on commit 3c60a96

Please sign in to comment.