Skip to content

Commit

Permalink
Study view km plot optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
dippindots committed Apr 26, 2023
1 parent c5d86ba commit 4cfc31e
Show file tree
Hide file tree
Showing 9 changed files with 372 additions and 65 deletions.
1 change: 1 addition & 0 deletions src/pages/groupComparison/Survival.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,7 @@ export default class Survival extends React.Component<ISurvivalProps, {}> {
this.selectedSurvivalPlotPrefix
]
}
compactMode={false}
/>
</div>
</div>
Expand Down
156 changes: 102 additions & 54 deletions src/pages/resultsView/survival/SurvivalChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
filterScatterData,
SurvivalPlotFilters,
SurvivalSummary,
SURVIVAL_COMPACT_MODE_THRESHOLD,
} from './SurvivalUtil';
import { toConditionalPrecision } from 'shared/lib/NumberUtils';
import { getPatientViewUrl } from '../../../shared/api/urls';
Expand Down Expand Up @@ -88,10 +89,13 @@ export interface ISurvivalChartProps {
legendLabelComponent?: any;
yAxisTickCount?: number;
xAxisTickCount?: number;
// Compact mode will hide censoring dots in the chart and do binning based on configuration
compactMode?: boolean;
}

const MIN_GROUP_SIZE_FOR_LOGRANK = 10;
// Start to down sampling when there are more than 1000 dots in the plot.
// TODO: 1000 samples is our current setting, but we should make this configurable
const SURVIVAL_DOWN_SAMPLING_THRESHOLD = 1000;

@observer
Expand Down Expand Up @@ -269,15 +273,30 @@ export default class SurvivalChart
// The filter is only available when user zooms in the plot.
@computed
get scatterData(): GroupedScatterData {
return filterScatterData(
this.unfilteredScatterData,
this.scatterFilter,
{
xDenominator: this.downSamplingDenominators.x,
yDenominator: this.downSamplingDenominators.y,
threshold: SURVIVAL_DOWN_SAMPLING_THRESHOLD,
}
);
if (this.props.compactMode) {
return filterScatterData(
this.unfilteredScatterData,
this.scatterFilter,
{
xDenominator: this.downSamplingDenominators.x,
yDenominator: this.downSamplingDenominators.y,
threshold: SURVIVAL_DOWN_SAMPLING_THRESHOLD,
enableCensoringCross: false,
floorTimeToMonth: true,
}
);
} else {
return filterScatterData(
this.unfilteredScatterData,
this.scatterFilter,
{
xDenominator: this.downSamplingDenominators.x,
yDenominator: this.downSamplingDenominators.y,
threshold: SURVIVAL_DOWN_SAMPLING_THRESHOLD,
enableCensoringCross: true,
}
);
}
}

public static defaultProps: Partial<ISurvivalChartProps> = {
Expand Down Expand Up @@ -790,6 +809,77 @@ export default class SurvivalChart
));
}

@computed get tooltipContent() {
return (
<div>
Patient ID:{' '}
<a
href={getPatientViewUrl(
this.tooltipModel.datum.studyId,
this.tooltipModel.datum.patientId
)}
target="_blank"
>
{this.tooltipModel.datum.patientId}
</a>
<br />
{!!this.props.showCurveInTooltip && [
`Curve: ${this.tooltipModel.datum.group}`,
<br />,
]}
{this.props.yLabelTooltip}:{' '}
{this.tooltipModel.datum.y.toFixed(2)}%<br />
{this.tooltipModel.datum.status
? this.props.xLabelWithEventTooltip
: this.props.xLabelWithoutEventTooltip}
: {this.tooltipModel.datum.x.toFixed(2)} months{' '}
{this.tooltipModel.datum.status ? '' : '(censored)'}
<br />
{this.props.analysisClinicalAttribute && (
<span>
{this.props.analysisClinicalAttribute.displayName}:{' '}
{
this.props.patientToAnalysisGroups[
this.tooltipModel.datum.uniquePatientKey
]
}
</span>
)}
<br />
Number of patients at risk: {this.tooltipModel.datum.atRisk}
</div>
);
}

@computed get compactTooltipContent() {
return (
<div>
Events during [{this.tooltipModel.datum.x},
{this.tooltipModel.datum.x + 1}) months
<br />
{this.tooltipModel.datum.numberOfEvents !== undefined && (
<>
Patients with an event:{' '}
{this.tooltipModel.datum.numberOfEvents}
</>
)}
<br />
{this.tooltipModel.datum.numberOfCensored !== undefined && (
<>
Censored patients:{' '}
{this.tooltipModel.datum.numberOfCensored}
</>
)}
<br />
<br />% event free at interval end:{' '}
{this.tooltipModel.datum.y.toFixed(2)}%
<br />
Patients at risk at interval end:{' '}
{this.tooltipModel.datum.atRisk}
</div>
);
}

public render() {
if (
_.flatten(_.values(this.props.sortedGroupedSurvivals)).length === 0
Expand Down Expand Up @@ -829,51 +919,9 @@ export default class SurvivalChart
onMouseEnter={this.tooltipMouseEnter}
onMouseLeave={this.tooltipMouseLeave}
>
<div>
Patient ID:{' '}
<a
href={getPatientViewUrl(
this.tooltipModel.datum.studyId,
this.tooltipModel.datum.patientId
)}
target="_blank"
>
{this.tooltipModel.datum.patientId}
</a>
<br />
{!!this.props.showCurveInTooltip && [
`Curve: ${this.tooltipModel.datum.group}`,
<br />,
]}
{this.props.yLabelTooltip}:{' '}
{this.tooltipModel.datum.y.toFixed(2)}%<br />
{this.tooltipModel.datum.status
? this.props.xLabelWithEventTooltip
: this.props.xLabelWithoutEventTooltip}
: {this.tooltipModel.datum.x.toFixed(2)} months{' '}
{this.tooltipModel.datum.status
? ''
: '(censored)'}
<br />
{this.props.analysisClinicalAttribute && (
<span>
{
this.props.analysisClinicalAttribute
.displayName
}
:{' '}
{
this.props.patientToAnalysisGroups[
this.tooltipModel.datum
.uniquePatientKey
]
}
</span>
)}
<br />
Number of patients at risk:{' '}
{this.tooltipModel.datum.atRisk}
</div>
{this.props.compactMode
? this.compactTooltipContent
: this.tooltipContent}
</Popover>
)}
{this.props.showTable && (
Expand Down
Loading

0 comments on commit 4cfc31e

Please sign in to comment.