Skip to content

Commit

Permalink
Fix performance issue in cancer study selector
Browse files Browse the repository at this point in the history
  • Loading branch information
adufilie committed Apr 21, 2017
1 parent 80f8b13 commit e70195f
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 87 deletions.
142 changes: 68 additions & 74 deletions src/shared/components/query/CancerStudySelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ import {TypeOfCancer as CancerType, CancerStudy} from "../../api/generated/CBioP
import {FlexCol, FlexRow} from "../flexbox/FlexBox";
import * as styles_any from './styles.module.scss';
import classNames from 'classnames';
import LabeledCheckbox from "../labeledCheckbox/LabeledCheckbox";
import ReactSelect from 'react-select';
import StudyList from "./studyList/StudyList";
import {observer} from "mobx-react";
import {observer, Observer} from "mobx-react";
import {expr} from 'mobx';
import memoize from "memoize-weak-decorator";
import {QueryStoreComponent} from "./QueryStore";
import SectionHeader from "../sectionHeader/SectionHeader";
import StudyListLogic from "./StudyListLogic";

const styles = styles_any as {
CancerStudySelector: string,
Expand Down Expand Up @@ -70,19 +69,19 @@ export default class CancerStudySelector extends QueryStoreComponent<ICancerStud
this.store.selectedStudyIds = _.difference(this.store.selectedStudyIds, clickedStudyIds);
}

renderCancerTypeList()
{
CancerTypeList = observer(() => {
let cancerTypes = this.logic.cancerTypeListView.getChildCancerTypes(this.store.treeData.rootCancerType);
return (
<ul className={styles.cancerTypeList}>
{cancerTypes.map(this.renderCancerTypeListItem)}
{cancerTypes.map((cancerType, arrayIndex) => (
<this.CancerTypeListItem key={arrayIndex} cancerType={cancerType}/>
))}
</ul>
);
}
});

renderCancerTypeListItem = (cancerType:CancerType, arrayIndex:number) =>
{
let numStudies = this.logic.cancerTypeListView.getDescendantCancerStudies(cancerType).length;
CancerTypeListItem = observer(({cancerType}: {cancerType:CancerType}) => {
let numStudies = expr(() => this.logic.cancerTypeListView.getDescendantCancerStudies(cancerType).length);
let selected = _.includes(this.store.selectedCancerTypeIds, cancerType.cancerTypeId);
let highlighted = this.logic.isHighlighted(cancerType);
let liClassName = classNames({
Expand All @@ -91,12 +90,11 @@ export default class CancerStudySelector extends QueryStoreComponent<ICancerStud
[styles.selected]: selected,
[styles.matchingNodeText]: !!this.store.searchText && highlighted,
[styles.nonMatchingNodeText]: !!this.store.searchText && !highlighted,
[styles.containsSelectedStudies]: this.logic.cancerTypeContainsSelectedStudies(cancerType),
[styles.containsSelectedStudies]: expr(() => this.logic.cancerTypeContainsSelectedStudies(cancerType)),
});

return (
<li
key={arrayIndex}
className={liClassName}
onMouseDown={this.getCancerTypeListClickHandler(cancerType)}
>
Expand All @@ -108,88 +106,84 @@ export default class CancerStudySelector extends QueryStoreComponent<ICancerStud
</span>
</li>
);
}

renderStudyHeaderCheckbox = (shownStudies:CancerStudy[]) =>
{
let selectedStudies = this.store.selectedStudyIds.map(studyId => this.store.treeData.map_studyId_cancerStudy.get(studyId) as CancerStudy);
let shownAndSelectedStudies = _.intersection(shownStudies, selectedStudies);
let checked = shownAndSelectedStudies.length > 0;
let indeterminate = checked && shownAndSelectedStudies.length != shownStudies.length;
return (
<LabeledCheckbox
checked={checked}
indeterminate={indeterminate}
labelProps={{
onClick: (event:React.MouseEvent<HTMLLabelElement>) => event.stopPropagation()
}}
onChange={event => {
let shownStudyIds = shownStudies.map(study => study.studyId);
this.handleStudiesCheckbox(event, shownStudyIds);
}}
/>
);
}
});

render()
{
let searchTextOptions = this.store.searchTextPresets;
if (this.store.searchText && searchTextOptions.indexOf(this.store.searchText) < 0)
searchTextOptions = [this.store.searchText].concat(searchTextOptions as string[]);

let selectAllCheckboxProps = this.logic.mainView.getCheckboxProps(this.store.treeData.rootCancerType);

let selectedCountClass = classNames({
[styles.selectedCount]: true,
[styles.selectionsExist]: this.store.selectedStudyIds.length > 0
});

return (
<FlexCol overflow className={styles.CancerStudySelector}>
<SectionHeader promises={[this.store.cancerTypes, this.store.cancerStudies]}>
Select Studies:
{!!(!this.store.cancerTypes.isPending && !this.store.cancerStudies.isPending) && (
<span
className={selectedCountClass}
onClick={() => {
if (this.store.selectedStudyIds.length)
this.store.showSelectedStudiesOnly = !this.store.showSelectedStudiesOnly;
<Observer>
{() => {
let numSelectedStudies = expr(() => this.store.selectedStudyIds.length);
let selectedCountClass = classNames({
[styles.selectedCount]: true,
[styles.selectionsExist]: numSelectedStudies > 0
});
return (
<span
className={selectedCountClass}
onClick={() => {
if (numSelectedStudies)
this.store.showSelectedStudiesOnly = !this.store.showSelectedStudiesOnly;
}}
>
<b>{numSelectedStudies}</b> studies selected
(<b>{this.store.selectedStudies_totalSampleCount}</b> samples)
</span>
);
}}
>
<b>{this.store.selectedStudyIds.length}</b> studies selected
(<b>{this.store.selectedStudies_totalSampleCount}</b> samples)
</span>
</Observer>
)}
</SectionHeader>

<FlexRow overflow className={styles.cancerStudySelectorHeader}>
<ReactSelect
className={styles.searchTextInput}
value={this.store.searchText}
autofocus={true}
options={searchTextOptions.map(str => ({label: str, value: str}))}
placeholder='Search...'
noResultsText={false}
onCloseResetsInput={false}
onInputChange={(searchText:string) => {
this.store.searchText = searchText;
this.store.selectedCancerTypeIds = [];
}}
onChange={option => {
this.store.searchText = option ? option.value || '' : '';
this.store.selectedCancerTypeIds = [];
<Observer>
{() => {
let searchTextOptions = this.store.searchTextPresets;
if (this.store.searchText && searchTextOptions.indexOf(this.store.searchText) < 0)
searchTextOptions = [this.store.searchText].concat(searchTextOptions as string[]);

return (
<ReactSelect
className={styles.searchTextInput}
value={this.store.searchText}
autofocus={true}
options={searchTextOptions.map(str => ({label: str, value: str}))}
placeholder='Search...'
noResultsText={false}
onCloseResetsInput={false}
onInputChange={(searchText:string) => {
this.store.searchText = searchText;
this.store.selectedCancerTypeIds = [];
}}
onChange={option => {
this.store.searchText = option ? option.value || '' : '';
this.store.selectedCancerTypeIds = [];
}}
/>
);
}}
/>
</Observer>
{!!(!this.store.forDownloadTab) && (
<span className={classNames('cta', styles.selectAll)} onClick={() => this.logic.mainView.onCheck(this.store.treeData.rootCancerType, !selectAllCheckboxProps.checked)}>
{selectAllCheckboxProps.checked ? "Deselect all" : "Select all"}
</span>
<Observer>
{() => {
let selectAllChecked = expr(() => this.logic.mainView.getCheckboxProps(this.store.treeData.rootCancerType).checked);
return (
<span className={classNames('cta', styles.selectAll)} onClick={() => this.logic.mainView.onCheck(this.store.treeData.rootCancerType, !selectAllChecked)}>
{selectAllChecked ? "Deselect all" : "Select all"}
</span>
);
}}
</Observer>
)}
</FlexRow>

<FlexRow className={styles.cancerStudySelectorBody}>
<div className={styles.cancerTypeListContainer}>
{this.renderCancerTypeList()}
<this.CancerTypeList/>
</div>
<div className={styles.cancerStudyListContainer}>
<StudyList/>
Expand Down
48 changes: 35 additions & 13 deletions src/shared/components/query/studyList/StudyList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import classNames from 'classnames';
import FontAwesome from "react-fontawesome";
import LabeledCheckbox from "../../labeledCheckbox/LabeledCheckbox";
import {observer} from "mobx-react";
import {computed} from "mobx";
import {getStudySummaryUrl, getPubMedUrl} from "../../../api/urls";
import {QueryStoreComponent} from "../QueryStore";
import DefaultTooltip from "../../DefaultTooltip";
import StudyListLogic from "../StudyListLogic";
import {cached} from 'mobxpromise';
import StudyListLogic, {FilteredCancerTreeView} from "../StudyListLogic";
import {CancerTreeNode} from "../CancerStudyTreeData";

const styles = {
...styles_any as {
Expand Down Expand Up @@ -121,11 +122,8 @@ export default class StudyList extends QueryStoreComponent<IStudyListProps, void

heading = (
<li className={liClassName}>
<LabeledCheckbox
{...this.view.getCheckboxProps(cancerType)}
onChange={event => this.view.onCheck(cancerType, (event.target as HTMLInputElement).checked)}
>
{indentArrow}
<CancerTreeCheckbox view={this.view} node={cancerType}>
{indentArrow}
<span className={styles.CancerTypeName}>
{cancerType.name}
</span>
Expand All @@ -134,7 +132,7 @@ export default class StudyList extends QueryStoreComponent<IStudyListProps, void
Select All
</span>
)}
</LabeledCheckbox>
</CancerTreeCheckbox>
</li>
);
}
Expand Down Expand Up @@ -172,14 +170,11 @@ export default class StudyList extends QueryStoreComponent<IStudyListProps, void
renderStudyName = (study:CancerStudy) =>
{
return (
<LabeledCheckbox
{...this.view.getCheckboxProps(study)}
onChange={event => this.view.onCheck(study, (event.target as HTMLInputElement).checked)}
>
<CancerTreeCheckbox view={this.view} node={study}>
<span className={styles.StudyName}>
{study.name}
</span>
</LabeledCheckbox>
</CancerTreeCheckbox>
);
}

Expand Down Expand Up @@ -254,3 +249,30 @@ export default class StudyList extends QueryStoreComponent<IStudyListProps, void
);
}
}

export interface ICancerTreeCheckboxProps
{
view: FilteredCancerTreeView;
node: CancerTreeNode;
}

@observer
export class CancerTreeCheckbox extends QueryStoreComponent<ICancerTreeCheckboxProps, void>
{
@computed.struct get checkboxProps()
{
return this.props.view.getCheckboxProps(this.props.node);
}

render()
{
return (
<LabeledCheckbox
{...this.checkboxProps}
onChange={event => this.props.view.onCheck(this.props.node, (event.target as HTMLInputElement).checked)}
>
{this.props.children}
</LabeledCheckbox>
);
}
}

0 comments on commit e70195f

Please sign in to comment.