Skip to content

Commit

Permalink
Add user color configuration for oncoprinter (cBioPortal#4817)
Browse files Browse the repository at this point in the history
* add color chooser to oncoprinter
* add tests and screenshots

---------

Co-authored-by: Bryan Lai <[email protected]>
  • Loading branch information
gblaih and Bryan Lai authored Jan 17, 2024
1 parent 1be41d2 commit 5f850c2
Show file tree
Hide file tree
Showing 13 changed files with 499 additions and 137 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.
130 changes: 130 additions & 0 deletions end-to-end-test/remote/specs/core/oncoprinterColorConfig.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
var assertScreenShotMatch = require('../../../shared/lib/testUtils')
.assertScreenShotMatch;
var assert = require('assert');
var waitForOncoprint = require('../../../shared/specUtils').waitForOncoprint;
var goToUrlAndSetLocalStorage = require('../../../shared/specUtils')
.goToUrlAndSetLocalStorage;
var getNthOncoprintTrackOptionsElements = require('../../../shared/specUtils')
.getNthOncoprintTrackOptionsElements;
var {
checkOncoprintElement,
getElementByTestHandle,
} = require('../../../shared/specUtils.js');

const TIMEOUT = 6000;

const ONCOPRINT_TIMEOUT = 60000;

const CBIOPORTAL_URL = process.env.CBIOPORTAL_URL.replace(/\/$/, '');

describe('oncoprinter clinical example data, color configuration', () => {
it('oncoprinter color configuration modal reflects user selected colors', function() {
goToUrlAndSetLocalStorage(`${CBIOPORTAL_URL}/oncoprinter`);
$('.oncoprinterClinicalExampleData').waitForExist();
$('.oncoprinterClinicalExampleData').click();
$('.oncoprinterSubmit').click();
waitForOncoprint(TIMEOUT);

var trackOptionsElts = getNthOncoprintTrackOptionsElements(2);
// open menu
$(trackOptionsElts.button_selector).click();
$(trackOptionsElts.dropdown_selector).waitForDisplayed({
timeout: 1000,
});
// click "Edit Colors" to open modal
$(trackOptionsElts.dropdown_selector + ' li:nth-child(11)').click();
browser.pause(1000);

// select new colors for track values
getElementByTestHandle('color-picker-icon').click();
$('.circle-picker').waitForDisplayed({ timeout: 1000 });
$('.circle-picker [title="#990099"]').click();
waitForOncoprint(ONCOPRINT_TIMEOUT);
getElementByTestHandle('color-picker-icon').waitForDisplayed();
getElementByTestHandle('color-picker-icon').click();
$('.circle-picker').waitForDisplayed({ reverse: true });

$$('[data-test="color-picker-icon"]')[1].click();
$('.circle-picker').waitForDisplayed({ timeout: 1000 });
$('.circle-picker [title="#109618"]').click();
waitForOncoprint(ONCOPRINT_TIMEOUT);
getElementByTestHandle('color-picker-icon').waitForDisplayed();
$$('[data-test="color-picker-icon"]')[1].click();
$('.circle-picker').waitForDisplayed({ reverse: true });

$$('[data-test="color-picker-icon"]')[2].click();
$('.circle-picker').waitForDisplayed({ timeout: 1000 });
$('.circle-picker [title="#8b0707"]').click();
waitForOncoprint(ONCOPRINT_TIMEOUT);

assert.strictEqual(
$('[data-test="color-picker-icon"] rect').getAttribute('fill'),
'#990099'
);
assert.strictEqual(
$$('[data-test="color-picker-icon"] rect')[1].getAttribute('fill'),
'#109618'
);
assert.strictEqual(
$$('[data-test="color-picker-icon"] rect')[2].getAttribute('fill'),
'#8b0707'
);
});

it('oncoprinter reflects user selected colors', () => {
// close modal
$('a.tabAnchor_oncoprint').click();
var res = checkOncoprintElement();
assertScreenShotMatch(res);
});

it('oncoprinter reset colors button is visible when default colors not used', () => {
// click "Edit Colors" to open modal and check "Reset Colors" button in modal
var trackOptionsElts = getNthOncoprintTrackOptionsElements(2);
$(trackOptionsElts.button_selector).click();
$(trackOptionsElts.dropdown_selector).waitForDisplayed({
timeout: 1000,
});
$(trackOptionsElts.dropdown_selector + ' li:nth-child(11)').click();
getElementByTestHandle('resetColors').waitForDisplayed();
});

it('oncoprinter color configuration modal reflects default colors', () => {
// click "Reset Colors" track
getElementByTestHandle('resetColors').click();
waitForOncoprint(ONCOPRINT_TIMEOUT);

assert.strictEqual(
$('[data-test="color-picker-icon"] rect').getAttribute('fill'),
'#dc3912'
);
assert.strictEqual(
$$('[data-test="color-picker-icon"] rect')[1].getAttribute('fill'),
'#3366cc'
);
assert.strictEqual(
$$('[data-test="color-picker-icon"] rect')[2].getAttribute('fill'),
'#ff9900'
);
});

it('oncoprinter reflects default colors', () => {
// close modal
$('a.tabAnchor_oncoprint').click();
var res = checkOncoprintElement();
assertScreenShotMatch(res);
});

it('oncoprinter reset colors button is hidden when default colors are used', () => {
// click "Edit Colors" to open modal and check "Reset Colors" button in modal
var trackOptionsElts = getNthOncoprintTrackOptionsElements(2);
$(trackOptionsElts.button_selector).click();
$(trackOptionsElts.dropdown_selector).waitForDisplayed({
timeout: 1000,
});
$(trackOptionsElts.dropdown_selector + ' li:nth-child(11)').click();
getElementByTestHandle('resetColors').waitForDisplayed({
reverse: true,
});
});
});
10 changes: 6 additions & 4 deletions src/pages/resultsView/ResultsViewPageStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,8 @@ interface IResultsViewExclusionSettings {
) => void;
}

const ONCOPRINT_COLOR_CONFIG = 'clinicalTracksColorConfig';

/* fields and methods in the class below are ordered based on roughly
/* chronological setup concerns, rather than on encapsulation and public API */
/* tslint:disable: member-ordering */
Expand Down Expand Up @@ -496,7 +498,7 @@ export class ResultsViewPageStore extends AnalysisStore
);

const clinicalTracksColorConfig = localStorage.getItem(
'clinicalTracksColorConfig'
ONCOPRINT_COLOR_CONFIG
);
if (clinicalTracksColorConfig !== null) {
this._userSelectedStudiesToClinicalTracksColors = JSON.parse(
Expand Down Expand Up @@ -589,8 +591,8 @@ export class ResultsViewPageStore extends AnalysisStore

@observable _userSelectedStudiesToClinicalTracksColors: {
[studies: string]: {
[label: string]: {
[value: string]: RGBAColor;
[trackLabel: string]: {
[attributeValue: string]: RGBAColor;
};
};
} = { global: {} };
Expand Down Expand Up @@ -641,7 +643,7 @@ export class ResultsViewPageStore extends AnalysisStore
] = color;
}
localStorage.setItem(
'clinicalTracksColorConfig',
ONCOPRINT_COLOR_CONFIG,
JSON.stringify(this._userSelectedStudiesToClinicalTracksColors)
);
}
Expand Down
113 changes: 112 additions & 1 deletion src/pages/staticPages/tools/oncoprinter/Oncoprinter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import OncoprintControls, {
} from 'shared/components/oncoprint/controls/OncoprintControls';
import { percentAltered } from '../../../../shared/components/oncoprint/OncoprintUtils';
import { getServerConfig } from 'config/config';
import { OncoprintJS } from 'oncoprintjs';
import { OncoprintJS, RGBAColor } from 'oncoprintjs';
import fileDownload from 'react-file-download';
import { FadeInteraction, svgToPdfDownload } from 'cbioportal-frontend-commons';
import classNames from 'classnames';
Expand All @@ -19,12 +19,34 @@ import WindowStore from '../../../../shared/components/window/WindowStore';
import { getGeneticTrackKey } from './OncoprinterGeneticUtils';
import InfoBanner from '../../../../shared/components/banners/InfoBanner';
import '../../../../globalStyles/oncoprintStyles.scss';
import _ from 'lodash';
import {
MUTATION_SPECTRUM_CATEGORIES,
MUTATION_SPECTRUM_FILLS,
} from 'shared/cache/ClinicalDataCache';
import {
hexToRGBA,
RESERVED_CLINICAL_VALUE_COLORS,
rgbaToHex,
} from 'shared/lib/Colors';
import {
getClinicalTrackColor,
getClinicalTrackValues,
} from 'shared/components/oncoprint/ResultsViewOncoprint';
import { Modal } from 'react-bootstrap';
import ClinicalTrackColorPicker from 'shared/components/oncoprint/ClinicalTrackColorPicker';
import classnames from 'classnames';
import { getDefaultClinicalAttributeColoringForStringDatatype } from './OncoprinterToolUtils';
import { OncoprintColorModal } from 'shared/components/oncoprint/OncoprintColorModal';

interface IOncoprinterProps {
divId: string;
store: OncoprinterStore;
}

const DEFAULT_UNKNOWN_COLOR = [255, 255, 255, 1];
const DEFAULT_MIXED_COLOR = [220, 57, 18, 1];

@observer
export default class Oncoprinter extends React.Component<
IOncoprinterProps,
Expand Down Expand Up @@ -379,9 +401,92 @@ export default class Oncoprinter extends React.Component<
return WindowStore.size.width - 75;
}

@action.bound
public handleSelectedClinicalTrackColorChange(
value: string,
color: RGBAColor | undefined
) {
if (this.selectedClinicalTrack) {
this.props.store.setUserSelectedClinicalTrackColor(
this.selectedClinicalTrack.label,
value,
color
);
}
}

@observable trackKeySelectedForEdit: string | null = null;

@action.bound
setTrackKeySelectedForEdit(key: string | null) {
this.trackKeySelectedForEdit = key;
}

// if trackKeySelectedForEdit is null ('Edit Colors' has not been selected in an individual track menu),
// selectedClinicalTrack will be undefined
@computed get selectedClinicalTrack() {
return _.find(
this.props.store.clinicalTracks,
t => t.key === this.trackKeySelectedForEdit
);
}

@computed get defaultClinicalAttributeColoringForStringDatatype() {
if (this.selectedClinicalTrack) {
return _.mapValues(
getDefaultClinicalAttributeColoringForStringDatatype(
this.selectedClinicalTrack.data
),
hexToRGBA
);
}
return _.mapValues(RESERVED_CLINICAL_VALUE_COLORS, hexToRGBA);
}

@autobind
private getSelectedClinicalTrackDefaultColorForValue(
attributeValue: string
) {
if (!this.selectedClinicalTrack) {
return DEFAULT_UNKNOWN_COLOR;
}
switch (this.selectedClinicalTrack.datatype) {
case 'counts':
return MUTATION_SPECTRUM_FILLS[
_.indexOf(MUTATION_SPECTRUM_CATEGORIES, attributeValue)
];
case 'string':
// Mixed refers to when an event has multiple values (i.e. Sample Type for a patient event may have both Primary and Recurrence values)
if (attributeValue === 'Mixed') {
return DEFAULT_MIXED_COLOR;
} else {
return this
.defaultClinicalAttributeColoringForStringDatatype[
attributeValue
];
}
default:
return DEFAULT_UNKNOWN_COLOR;
}
}

public render() {
return (
<div className="posRelative">
{this.selectedClinicalTrack && (
<OncoprintColorModal
setTrackKeySelectedForEdit={
this.setTrackKeySelectedForEdit
}
selectedClinicalTrack={this.selectedClinicalTrack}
handleSelectedClinicalTrackColorChange={
this.handleSelectedClinicalTrackColorChange
}
getSelectedClinicalTrackDefaultColorForValue={
this.getSelectedClinicalTrackDefaultColorForValue
}
/>
)}
<div
className={classNames('oncoprintContainer', {
fadeIn: !this.isHidden,
Expand Down Expand Up @@ -454,6 +559,12 @@ export default class Oncoprinter extends React.Component<
}
showMinimap={this.showMinimap}
onMinimapClose={this.onMinimapClose}
trackKeySelectedForEdit={
this.trackKeySelectedForEdit
}
setTrackKeySelectedForEdit={
this.setTrackKeySelectedForEdit
}
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import { MUTATION_SPECTRUM_FILLS } from '../../../../shared/cache/ClinicalDataCa
import { MolecularProfile } from 'cbioportal-ts-api-client';
import { AlterationTypeConstants } from 'shared/constants';
import { capitalize } from 'cbioportal-frontend-commons';
import { hexToRGBA } from 'shared/lib/Colors';
import { RGBAColor } from 'oncoprintjs';
import { getDefaultClinicalAttributeColoringForStringDatatype } from './OncoprinterToolUtils';

export const ONCOPRINTER_VAL_NA = 'N/A';

Expand Down Expand Up @@ -470,6 +473,11 @@ export function getHeatmapTrackKey(attributeName: string) {
export function getClinicalTracks(
attributes: OncoprinterClinicalTrackSpec[],
parsedLines: OncoprinterOrderedValuesInputLine[],
userSelectedClinicalTracksColors: {
[label: string]: {
[value: string]: RGBAColor;
};
},
excludedSampleIds?: string[]
) {
let attributeToOncoprintData = getClinicalOncoprintData(
Expand All @@ -486,10 +494,20 @@ export function getClinicalTracks(

attributes.map(attr => {
const data = attributeToOncoprintData[attr.trackName];
let datatype, numberRange, countsCategoryFills;
let datatype, category_to_color, numberRange, countsCategoryFills;
switch (attr.datatype) {
case ClinicalTrackDataType.STRING:
datatype = 'string';
category_to_color = Object.assign(
{},
_.mapValues(
getDefaultClinicalAttributeColoringForStringDatatype(
data
),
hexToRGBA
),
userSelectedClinicalTracksColors[attr.trackName]
);
break;
case ClinicalTrackDataType.NUMBER:
case ClinicalTrackDataType.LOG_NUMBER:
Expand All @@ -503,14 +521,32 @@ export function getClinicalTracks(
attr.countsCategories!.length ===
MUTATION_SPECTRUM_FILLS.length
) {
countsCategoryFills = MUTATION_SPECTRUM_FILLS;
countsCategoryFills = attr.countsCategories!.map(
(label, i) => {
if (
userSelectedClinicalTracksColors[
attr.trackName
] &&
userSelectedClinicalTracksColors[
attr.trackName
][label]
) {
return userSelectedClinicalTracksColors[
attr.trackName
][label];
} else {
return MUTATION_SPECTRUM_FILLS[i];
}
}
);
}
break;
}
ret.push({
key: getClinicalTrackKey(attr.trackName),
attributeId: attr.trackName,
label: attr.trackName,
category_to_color,
data,
datatype,
description: '',
Expand Down
Loading

0 comments on commit 5f850c2

Please sign in to comment.