Skip to content

Commit

Permalink
Show clinical data numerical summary statistics as table
Browse files Browse the repository at this point in the history
  • Loading branch information
Bas Leenknegt committed Jun 8, 2022
1 parent de475f0 commit e877a63
Show file tree
Hide file tree
Showing 16 changed files with 316 additions and 117 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -177,19 +177,32 @@ describe('group comparison page screenshot tests', function() {
});
});

describe('Clinical tab', function() {
describe('Clinical tab', () => {
before(function() {
openGroupComparison(
`${CBIOPORTAL_URL}/study/summary?id=lgg_ucsf_2014_test_generic_assay`,
'chart-container-ONCOTREE_CODE',
5000
10000
);
$('.tabAnchor_clinical').click();
$('[data-test="ComparisonPageClinicalTabDiv"]').waitForExist({
timeout: 20000,
});
});

it('shows box plot for numerical data', () => {
$('[data-test="Mutation Count"]').click();
const res = checkClinicalTabPlot();
assertScreenShotMatch(res);
});

it('shows table when selecting table visualisation', () => {
$('[data-test="Mutation Count"]').click();
selectClinicalTabNumericalDisplayType('Table');
const res = checkClinicalTabPlot();
assertScreenShotMatch(res);
});

it('displays 100% stacked bar chart by default', () => {
var res = checkClinicalTabPlot();
assertScreenShotMatch(res);
Expand Down Expand Up @@ -217,3 +230,21 @@ function checkClinicalTabPlot() {
hide: ['.qtip'],
});
}

function selectClinicalTabNumericalDisplayType(type) {
setDropdownOpen(
true,
'[data-test="numericalVisualisationTypeSelector"] .Select-arrow-zone',
'[data-test="numericalVisualisationTypeSelector"] .Select-menu',
"Couldn't open clinical tab chart type dropdown"
);
$(
`[data-test="numericalVisualisationTypeSelector"] .Select-option[aria-label="${type}"]`
).click();
}

function checkClinicalTabPlot() {
return browser.checkElement('div[data-test="ClinicalTabPlotDiv"]', '', {
hide: ['.qtip'],
});
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
123 changes: 71 additions & 52 deletions src/pages/groupComparison/ClinicalData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ import {
makeBoxScatterPlotData,
} from 'pages/resultsView/plots/PlotsTabUtils';
import ScrollBar from 'shared/components/Scrollbar/ScrollBar';
import BoxScatterPlot, {
IBoxScatterPlotData,
} from 'shared/components/plots/BoxScatterPlot';
import { IBoxScatterPlotData } from 'shared/components/plots/BoxScatterPlot';
import { scatterPlotSize } from 'shared/components/plots/PlotUtils';
import {
CLINICAL_TAB_NOT_ENOUGH_GROUPS_MSG,
Expand All @@ -48,6 +46,11 @@ import { Sample } from 'cbioportal-ts-api-client';
import ComparisonStore from '../../shared/lib/comparison/ComparisonStore';
import { createSurvivalAttributeIdsDict } from 'pages/resultsView/survival/SurvivalUtil';
import { getComparisonCategoricalNaValue } from './ClinicalDataUtils';
import {
ClinicalNumericalDataVisualisation,
ClinicalNumericalVisualisationType,
} from 'pages/groupComparison/ClinicalNumericalDataVisualisation';
import { SummaryStatisticsTable } from './SummaryStatisticsTable';
import CategoryPlot, {
CategoryPlotType,
} from 'pages/groupComparison/CategoryPlot';
Expand All @@ -58,7 +61,10 @@ export interface IClinicalDataProps {

const SVG_ID = 'clinical-plot-svg';

class PlotsTabBoxPlot extends BoxScatterPlot<IBoxScatterPlotPoint> {}
export const numericalVisualisationTypeOptions = [
{ value: ClinicalNumericalVisualisationType.Plot, label: 'Plot' },
{ value: ClinicalNumericalVisualisationType.Table, label: 'Table' },
];

export class ClinicalDataEnrichmentStore extends SimpleGetterLazyMobXTableApplicationDataStore<
ClinicalDataEnrichmentWithQ
Expand Down Expand Up @@ -583,44 +589,9 @@ export default class ClinicalData extends React.Component<
}

@computed get boxPlotTooltip() {
return (d: any) => {
return (
<table className="table table-striped">
<thead>
<tr>
<th colSpan={2}>
{
this.boxPlotData.result!.data[d.eventKey]
.label
}
</th>
</tr>
</thead>
<tbody>
<tr>
<td>Maximum</td>
<td>{d.max}</td>
</tr>
<tr>
<td>75% (q3)</td>
<td>{d.q3}</td>
</tr>
<tr>
<td>Median</td>
<td>{d.median}</td>
</tr>
<tr>
<td>25% (q1)</td>
<td>{d.q1}</td>
</tr>
<tr>
<td>Minimum</td>
<td>{d.min}</td>
</tr>
</tbody>
</table>
);
};
return (data: any[], labels: string[]) => (
<SummaryStatisticsTable data={data} labels={labels} />
);
}

@computed get categoryToColor() {
Expand Down Expand Up @@ -649,17 +620,52 @@ export default class ClinicalData extends React.Component<
@observable categoryPlotType: CategoryPlotType =
CategoryPlotType.PercentageStackedBar;

@observable numericalVisualisationType: ClinicalNumericalVisualisationType =
ClinicalNumericalVisualisationType.Plot;

@action.bound
private onPlotTypeSelect(option: any) {
this.categoryPlotType = option.value;
}

@action.bound
private onNumericalVisualisationTypeSelect(option: any) {
this.numericalVisualisationType = option.value;
}

@computed private get getUtilitiesMenu() {
if (!this.highlightedRow) {
return <span></span>;
}
return (
<div style={{ marginBottom: '10px' }}>
{isNumerical(
this.highlightedRow!.clinicalAttribute.datatype
) && (
<>
<div
className="form-group"
style={{ display: 'flex', alignItems: 'center' }}
>
<label>Visualisation type</label>
<div
style={{ width: 240, marginLeft: 5 }}
data-test="numericalVisualisationTypeSelector"
>
<ReactSelect
name="numerical-visualisation-type"
value={this.numericalVisualisationType}
onChange={
this.onNumericalVisualisationTypeSelect
}
options={numericalVisualisationTypeOptions}
clearable={false}
searchable={true}
/>
</div>
</div>
</>
)}
{!this.showLogScaleControls && (
<div
className="form-group"
Expand All @@ -682,15 +688,18 @@ export default class ClinicalData extends React.Component<
</div>
)}
<div>
<label className="checkbox-inline">
<input
type="checkbox"
checked={this.swapAxes}
onClick={this.onClickSwapAxes}
data-test="SwapAxes"
/>
Swap Axes
</label>
{this.numericalVisualisationType !==
ClinicalNumericalVisualisationType.Table && (
<label className="checkbox-inline">
<input
type="checkbox"
checked={this.swapAxes}
onClick={this.onClickSwapAxes}
data-test="SwapAxes"
/>
Swap Axes
</label>
)}
{!this.showLogScaleControls &&
this.categoryPlotType !== CategoryPlotType.Heatmap && (
<label className="checkbox-inline">
Expand Down Expand Up @@ -778,7 +787,8 @@ export default class ClinicalData extends React.Component<
) {
if (this.boxPlotData.isComplete) {
plotElt = (
<PlotsTabBoxPlot
<ClinicalNumericalDataVisualisation
type={this.numericalVisualisationType}
svgId={SVG_ID}
domainPadding={75}
boxWidth={
Expand Down Expand Up @@ -889,6 +899,15 @@ export default class ClinicalData extends React.Component<

@autobind
private toolbar() {
const numerical = isNumerical(
this.highlightedRow!.clinicalAttribute.datatype
);
const visualiseAsTable =
this.numericalVisualisationType ===
ClinicalNumericalVisualisationType.Table;
if (numerical && visualiseAsTable) {
return <></>;
}
return (
<div style={{ textAlign: 'center', position: 'relative' }}>
<DownloadControls
Expand Down
76 changes: 76 additions & 0 deletions src/pages/groupComparison/ClinicalNumericalDataVisualisation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import BoxScatterPlot, {
IBoxScatterPlotProps,
toBoxPlotData,
} from 'shared/components/plots/BoxScatterPlot';
import { IBoxScatterPlotPoint } from 'pages/resultsView/plots/PlotsTabUtils';
import React from 'react';
import { computed, makeObservable } from 'mobx';
import { SummaryStatisticsTable } from './SummaryStatisticsTable';

export enum ClinicalNumericalVisualisationType {
Plot = 'Plot',
Table = 'Table',
}

export class PlotsTabBoxPlot extends BoxScatterPlot<IBoxScatterPlotPoint> {}

export type ClinicalNumericalDataVisualisationProps = IBoxScatterPlotProps<
IBoxScatterPlotPoint
> & {
type: ClinicalNumericalVisualisationType;
};

export class ClinicalNumericalDataVisualisation extends React.Component<
ClinicalNumericalDataVisualisationProps
> {
constructor(props: any) {
super(props);
makeObservable(this);
}

render() {
const isTable =
this.props.type === ClinicalNumericalVisualisationType.Table;
return <>{isTable ? this.table : this.plot}</>;
}

@computed get table() {
const groupStats = toBoxPlotData(
this.props.data,
this.props.boxCalculationFilter,
this.props.excludeLimitValuesFromBoxPlot,
this.props.logScale
);
const groupLabels = this.props.data.map(d => d.label);
return (
<SummaryStatisticsTable data={groupStats} labels={groupLabels} />
);
}

@computed get plot() {
return (
<PlotsTabBoxPlot
svgId={this.props.svgId}
domainPadding={this.props.domainPadding}
boxWidth={this.props.boxWidth}
axisLabelX={this.props.axisLabelX}
axisLabelY={this.props.axisLabelY}
data={this.props.data}
chartBase={this.props.chartBase}
scatterPlotTooltip={this.props.scatterPlotTooltip}
boxPlotTooltip={this.props.boxPlotTooltip}
horizontal={this.props.horizontal}
logScale={this.props.logScale}
size={this.props.size}
fill={this.props.fill}
stroke={this.props.stroke}
strokeOpacity={this.props.strokeOpacity}
symbol={this.props.symbol}
useLogSpaceTicks={this.props.useLogSpaceTicks}
legendLocationWidthThreshold={
this.props.legendLocationWidthThreshold
}
/>
);
}
}
55 changes: 55 additions & 0 deletions src/pages/groupComparison/SummaryStatisticsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { FunctionComponent } from 'react';
import _ from 'lodash';
import * as React from 'react';

type SummaryStatisticsTableProps = { data: any[]; labels: string[] };

export const SummaryStatisticsTable: FunctionComponent<SummaryStatisticsTableProps> = (
props: SummaryStatisticsTableProps
) => {
const headers =
props.labels.length === 1 ? (
<th colSpan={2}>{props.labels[0]}</th>
) : (
[<th />, props.labels.map(label => <th colSpan={1}>{label}</th>)]
);
return (
<table className="table table-striped" style={{ minWidth: '400px' }}>
<thead>
<tr>{headers}</tr>
</thead>
<tbody>
<tr>
<td>Maximum</td>
{props.data.map(d => (
<td>{_.round(d.max, 2)}</td>
))}
</tr>
<tr>
<td>75% (q3)</td>
{props.data.map(d => (
<td>{_.round(d.q3, 2)}</td>
))}
</tr>
<tr>
<td>Median</td>
{props.data.map(d => (
<td>{_.round(d.median, 2)}</td>
))}
</tr>
<tr>
<td>25% (q1)</td>
{props.data.map(d => (
<td>{_.round(d.q1, 2)}</td>
))}
</tr>
<tr>
<td>Minimum</td>
{props.data.map(d => (
<td>{_.round(d.min, 2)}</td>
))}
</tr>
</tbody>
</table>
);
};
Loading

0 comments on commit e877a63

Please sign in to comment.