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 Feb 17, 2023
1 parent 8c43fc1 commit 4e9a4b0
Show file tree
Hide file tree
Showing 5 changed files with 300 additions and 59 deletions.
1 change: 1 addition & 0 deletions src/pages/groupComparison/Survival.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,7 @@ export default class Survival extends React.Component<ISurvivalProps, {}> {
this.selectedSurvivalPlotPrefix
]
}
compactMode={false}
/>
</div>
</div>
Expand Down
157 changes: 103 additions & 54 deletions src/pages/resultsView/survival/SurvivalChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,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 +272,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 +808,79 @@ 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() {
console.log(this.tooltipModel.datum.lastYInMonth);
return (
<div>
{this.props.xLabelWithEventTooltip}: {this.tooltipModel.datum.x}
-{this.tooltipModel.datum.x + 1} months <br />
{this.props.yLabelTooltip} at the end of months (
{this.tooltipModel.datum.x} months) :{' '}
{this.tooltipModel.datum.y.toFixed(2)}%
<br />
Number of patients at risk at the end of months (
{this.tooltipModel.datum.x} months) :{' '}
{this.tooltipModel.datum.atRisk}
<br />
{this.tooltipModel.datum.numberOfEvents !== undefined && (
<>
Number of patients have event:{' '}
{this.tooltipModel.datum.numberOfEvents}
</>
)}
<br />
{this.tooltipModel.datum.numberOfCensored !== undefined && (
<>
Number of patients are censored:{' '}
{this.tooltipModel.datum.numberOfCensored}
</>
)}
</div>
);
}

public render() {
if (
_.flatten(_.values(this.props.sortedGroupedSurvivals)).length === 0
Expand Down Expand Up @@ -829,51 +920,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
101 changes: 101 additions & 0 deletions src/pages/resultsView/survival/SurvivalUtil.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
createSurvivalAttributeIdsDict,
getNumPatientsAtRisk,
sortPatientSurvivals,
floorScatterData,
} from './SurvivalUtil';

const exampleAlteredPatientSurvivals = [
Expand Down Expand Up @@ -13966,6 +13967,7 @@ describe('SurvivalUtil', () => {
xDenominator: 100,
yDenominator: 100,
threshold: 100,
enableCensoringCross: true,
}),
[]
);
Expand All @@ -13976,6 +13978,7 @@ describe('SurvivalUtil', () => {
xDenominator: 0,
yDenominator: 100,
threshold: 100,
enableCensoringCross: true,
};
assert.deepEqual(
downSampling(allScatterData, opts),
Expand All @@ -13988,6 +13991,7 @@ describe('SurvivalUtil', () => {
xDenominator: -1,
yDenominator: 100,
threshold: 100,
enableCensoringCross: true,
};
assert.deepEqual(
downSampling(allScatterData, opts),
Expand Down Expand Up @@ -14032,6 +14036,7 @@ describe('SurvivalUtil', () => {
xDenominator: 4,
yDenominator: 4,
threshold: 100,
enableCensoringCross: true,
}
),
[
Expand Down Expand Up @@ -14094,6 +14099,7 @@ describe('SurvivalUtil', () => {
threshold: 100,
xDenominator: 4,
yDenominator: 4,
enableCensoringCross: true,
}
),
[
Expand Down Expand Up @@ -14156,6 +14162,7 @@ describe('SurvivalUtil', () => {
threshold: 100,
xDenominator: 5,
yDenominator: 5,
enableCensoringCross: true,
}
),
[
Expand Down Expand Up @@ -14227,6 +14234,7 @@ describe('SurvivalUtil', () => {
threshold: 100,
xDenominator: 8,
yDenominator: 8,
enableCensoringCross: true,
}
),
[
Expand Down Expand Up @@ -14262,6 +14270,97 @@ describe('SurvivalUtil', () => {
});
});

describe('floorScatterData()', () => {
it('return correct data after do flooring', () => {
let testScatterData: ScatterData[] = [
{
x: 0,
y: 10,
patientId: '',
uniquePatientKey: '',
studyId: '',
status: true,
opacity: 1,
},
{
x: 0.5,
y: 9,
patientId: '',
uniquePatientKey: '',
studyId: '',
status: true,
opacity: 1,
},
{
x: 1,
y: 8,
patientId: '',
uniquePatientKey: '',
studyId: '',
status: true,
opacity: 1,
},
{
x: 1.2,
y: 7,
patientId: '',
uniquePatientKey: '',
studyId: '',
status: true,
opacity: 1,
},
{
x: 1.4,
y: 6,
patientId: '',
uniquePatientKey: '',
studyId: '',
status: true,
opacity: 1,
},
{
x: 1.5,
y: 6,
patientId: '',
uniquePatientKey: '',
studyId: '',
status: false,
opacity: 0,
},
];
let result = [
{
x: 0,
y: 9,
patientId: '',
uniquePatientKey: '',
studyId: '',
status: true,
opacity: 1,
numberOfEvents: 2,
numberOfCensored: 0,
atRisk: undefined,
group: undefined,
},
{
x: 1,
y: 6,
patientId: '',
uniquePatientKey: '',
studyId: '',
status: true,
opacity: 1,
numberOfEvents: 3,
numberOfCensored: 1,
atRisk: undefined,
group: undefined,
},
];

assert.deepEqual(floorScatterData(testScatterData), result);
});
});

describe('filterScatterData()', () => {
it('Return full data if the filers are undefined', () => {
let testData = {
Expand All @@ -14277,6 +14376,7 @@ describe('SurvivalUtil', () => {
xDenominator: 100,
yDenominator: 100,
threshold: 100,
enableCensoringCross: true,
}),
testData
);
Expand Down Expand Up @@ -14324,6 +14424,7 @@ describe('SurvivalUtil', () => {
xDenominator: 2,
yDenominator: 2,
threshold: 2,
enableCensoringCross: true,
}
);
assert.deepEqual(result.altered.numOfCases, 2);
Expand Down
Loading

0 comments on commit 4e9a4b0

Please sign in to comment.