diff --git a/src/app/analysis.service.ts b/src/app/analysis.service.ts index 4b040e5c870fd8e66350cbf5e35661d31152d70c..8cb9f71f808c61116ff424fd85ed039743f16659 100644 --- a/src/app/analysis.service.ts +++ b/src/app/analysis.service.ts @@ -37,7 +37,7 @@ export const MAX_TASKS = 3; export class AnalysisService { private selectedItems = new Map<string, Wrapper>(); - private selectSubject = new Subject<{ item: Wrapper, selected: boolean }>(); + private selectListSubject = new Subject<{ items: Wrapper[], selected: boolean | null }>(); public tokens: string[] = []; public finishedTokens: string[] = []; @@ -82,40 +82,62 @@ export class AnalysisService { }); } - public addItem(wrapper: Wrapper) { - if (!this.inSelection(wrapper)) { - this.selectedItems.set(wrapper.nodeId, wrapper); - this.selectSubject.next({item: wrapper, selected: true}); + public addItems(wrappers: Wrapper[]) { + const addedWrappers: Wrapper[] = []; + for (const wrapper of wrappers) { + if (!this.inSelection(wrapper)) { + addedWrappers.push(wrapper); + this.selectedItems.set(wrapper.nodeId, wrapper); + } + } + this.selectListSubject.next({items: addedWrappers, selected: true}); + } + + public removeItems(wrappers: Wrapper[]) { + const removedWrappers: Wrapper[] = []; + for (const wrapper of wrappers) { + if (this.selectedItems.delete(wrapper.nodeId)) { + removedWrappers.push(wrapper); + } } + this.selectListSubject.next({items: removedWrappers, selected: false}); } public addAllHostProteins(nodes, proteins) { + const items: Wrapper[] = []; const visibleIds = new Set<string>(nodes.getIds()); for (const protein of proteins) { const wrapper = getWrapperFromProtein(protein); const found = visibleIds.has(wrapper.nodeId); if (found && !this.inSelection(wrapper)) { - this.addItem(wrapper); + items.push(wrapper); + this.selectedItems.set(wrapper.nodeId, wrapper); } } + this.selectListSubject.next({items, selected: true}); } public addAllViralProteins(nodes, viralProteins) { + const items: Wrapper[] = []; const visibleIds = new Set<string>(nodes.getIds()); for (const viralProtein of viralProteins) { const wrapper = getWrapperFromViralProtein(viralProtein); const found = visibleIds.has(wrapper.nodeId); if (found && !this.inSelection(wrapper)) { - this.addItem(wrapper); + items.push(wrapper); + this.selectedItems.set(wrapper.nodeId, wrapper); } } + this.selectListSubject.next({items, selected: true}); } resetSelection() { - const oldSelection = this.selectedItems.values(); - for (const item of oldSelection) { - this.removeItem(item); - } + this.selectListSubject.next({items: [], selected: null}); + this.selectedItems.clear(); + } + + idInSelection(nodeId: string): boolean { + return this.selectedItems.has(nodeId); } inSelection(wrapper: Wrapper): boolean { @@ -130,13 +152,6 @@ export class AnalysisService { return this.inSelection(getWrapperFromViralProtein(viralProtein)); } - removeItem(wrapper: Wrapper) { - const item = this.selectedItems.get(wrapper.nodeId); - if (this.selectedItems.delete(wrapper.nodeId)) { - this.selectSubject.next({item, selected: false}); - } - } - getSelection(): Wrapper[] { return Array.from(this.selectedItems.values()); } @@ -145,9 +160,9 @@ export class AnalysisService { return this.selectedItems.size; } - subscribe(cb: (item: Wrapper, selected: boolean) => void) { - this.selectSubject.subscribe((event) => { - cb(event.item, event.selected); + subscribeList(cb: (items: Array<Wrapper>, selected: boolean | null) => void) { + this.selectListSubject.subscribe((event) => { + cb(event.items, event.selected); }); } diff --git a/src/app/components/analysis-window/analysis-window.component.ts b/src/app/components/analysis-window/analysis-window.component.ts index e167fc61194838cc7c78685efa3e898c96677f44..9f4d4088f8ed46d12d82c41dee1abedb57b1305a 100644 --- a/src/app/components/analysis-window/analysis-window.component.ts +++ b/src/app/components/analysis-window/analysis-window.component.ts @@ -154,10 +154,10 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { } const wrapper = node.wrapper; if (this.analysis.inSelection(wrapper)) { - this.analysis.removeItem(wrapper); + this.analysis.removeItems([wrapper]); this.analysis.getCount(); } else { - this.analysis.addItem(wrapper); + this.analysis.addItems([wrapper]); this.analysis.getCount(); } } @@ -174,40 +174,78 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { } }); - this.analysis.subscribe((item, selected) => { - if (item.type === 'host') { - // TODO: Refactor! - const found = this.tableSelectedProteins.findIndex((i) => getProteinNodeId(i) === item.nodeId); - const tableItem = this.tableProteins.find((i) => getProteinNodeId(i) === item.nodeId); - if (selected && found === -1 && tableItem) { - this.tableSelectedProteins.push(tableItem); - } - if (!selected && found !== -1 && tableItem) { - this.tableSelectedProteins.splice(found, 1); + this.analysis.subscribeList((items, selected) => { + if (selected !== null) { + const updatedNodes = []; + for (const item of items) { + const node = this.nodeData.nodes.get(item.nodeId); + if (!node) { + continue; + } + const pos = this.network.getPositions([item.nodeId]); + node.x = pos[item.nodeId].x; + node.y = pos[item.nodeId].y; + Object.assign(node, NetworkSettings.getNodeStyle(node.wrapper.type, node.isSeed, selected)); + updatedNodes.push(node); } - this.tableSelectedProteins = [...this.tableSelectedProteins]; - } else if (item.type === 'virus') { - // TODO: Refactor! - const found = this.tableSelectedViralProteins.findIndex((i) => getViralProteinNodeId(i) === item.nodeId); - const tableItem = this.tableViralProteins.find((i) => getViralProteinNodeId(i) === item.nodeId); - if (selected && found === -1 && tableItem) { - this.tableSelectedViralProteins.push(tableItem); + this.nodeData.nodes.update(updatedNodes); + + const proteinSelection = this.tableSelectedProteins; + const viralProteinSelection = this.tableSelectedViralProteins; + for (const item of items) { + if (item.type === 'host') { + // TODO: Refactor! + const found = proteinSelection.findIndex((i) => getProteinNodeId(i) === item.nodeId); + const tableItem = this.tableProteins.find((i) => getProteinNodeId(i) === item.nodeId); + if (selected && found === -1 && tableItem) { + proteinSelection.push(tableItem); + } + if (!selected && found !== -1 && tableItem) { + proteinSelection.splice(found, 1); + } + } else if (item.type === 'virus') { + // TODO: Refactor! + const found = viralProteinSelection.findIndex((i) => getViralProteinNodeId(i) === item.nodeId); + const tableItem = this.tableViralProteins.find((i) => getViralProteinNodeId(i) === item.nodeId); + if (selected && found === -1 && tableItem) { + viralProteinSelection.push(tableItem); + } + if (!selected && found !== -1 && tableItem) { + viralProteinSelection.splice(found, 1); + } + } } - if (!selected && found !== -1 && tableItem) { - this.tableSelectedViralProteins.splice(found, 1); + this.tableSelectedProteins = [...proteinSelection]; + this.tableSelectedViralProteins = [...viralProteinSelection]; + } else { + const updatedNodes = []; + this.nodeData.nodes.forEach((node) => { + const nodeSelected = this.analysis.idInSelection(node.id); + if (selected !== nodeSelected) { + Object.assign(node, NetworkSettings.getNodeStyle(node.wrapper.type, true, selected)); + updatedNodes.push(node); + } + }); + this.nodeData.nodes.update(updatedNodes); + + const proteinSelection = []; + const viralProteinSelection = []; + for (const item of items) { + if (item.type === 'host') { + const tableItem = this.tableProteins.find((i) => getProteinNodeId(i) === item.nodeId); + if (tableItem) { + proteinSelection.push(tableItem); + } + } else if (item.type === 'virus') { + const tableItem = this.tableViralProteins.find((i) => getViralProteinNodeId(i) === item.nodeId); + if (tableItem) { + viralProteinSelection.push(tableItem); + } + } } - this.tableSelectedViralProteins = [...this.tableSelectedViralProteins]; - } - - const node = this.nodeData.nodes.get(item.nodeId); - if (!node) { - return; + this.tableSelectedProteins = [...proteinSelection]; + this.tableSelectedViralProteins = [...viralProteinSelection]; } - const pos = this.network.getPositions([item.nodeId]); - node.x = pos[item.nodeId].x; - node.y = pos[item.nodeId].y; - Object.assign(node, NetworkSettings.getNodeStyle(node.wrapper.type, node.isSeed, selected)); - this.nodeData.nodes.update(node); }); } } @@ -390,20 +428,20 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { if (this.showDrugs) { const result = await this.http.get<any>( `${environment.backend}drug_interactions/?token=${this.token}`).toPromise().catch( - (err: HttpErrorResponse) => { - // simple logging, but you can do a lot more, see below - toast({ - message: 'An error occured while fetching the drugs.', - duration: 5000, - dismissible: true, - pauseOnHover: true, - type: 'is-danger', - position: 'top-center', - animate: {in: 'fadeIn', out: 'fadeOut'} + (err: HttpErrorResponse) => { + // simple logging, but you can do a lot more, see below + toast({ + message: 'An error occured while fetching the drugs.', + duration: 5000, + dismissible: true, + pauseOnHover: true, + type: 'is-danger', + position: 'top-center', + animate: {in: 'fadeIn', out: 'fadeOut'} + }); + this.showDrugs = false; + return; }); - this.showDrugs = false; - return; - }); const drugs = result.drugs; const edges = result.edges; @@ -458,35 +496,43 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { public tableProteinSelection(e) { const oldSelection = [...this.tableSelectedProteins]; this.tableSelectedProteins = e; + const addItems = []; + const removeItems = []; for (const i of this.tableSelectedProteins) { const wrapper = getWrapperFromProtein(i); if (oldSelection.indexOf(i) === -1) { - this.analysis.addItem(wrapper); + addItems.push(wrapper); } } for (const i of oldSelection) { const wrapper = getWrapperFromProtein(i); if (this.tableSelectedProteins.indexOf(i) === -1) { - this.analysis.removeItem(wrapper); + removeItems.push(wrapper); } } + this.analysis.addItems(addItems); + this.analysis.removeItems(removeItems); } public tableViralProteinSelection(e) { const oldSelection = [...this.tableSelectedViralProteins]; this.tableSelectedViralProteins = e; + const addItems = []; + const removeItems = []; for (const i of this.tableSelectedViralProteins) { const wrapper = getWrapperFromViralProtein(i); if (oldSelection.indexOf(i) === -1) { - this.analysis.addItem(wrapper); + addItems.push(wrapper); } } for (const i of oldSelection) { const wrapper = getWrapperFromViralProtein(i); if (this.tableSelectedViralProteins.indexOf(i) === -1) { - this.analysis.removeItem(wrapper); + removeItems.push(wrapper); } } + this.analysis.addItems(addItems); + this.analysis.removeItems(removeItems); } } diff --git a/src/app/components/info-box/info-box.component.html b/src/app/components/info-box/info-box.component.html index bc4f40ee68f1e9c4ac2e67a6ac24cc8af5c75e55..57e89bc8b7f8871462288d31e3d6a2489798169f 100644 --- a/src/app/components/info-box/info-box.component.html +++ b/src/app/components/info-box/info-box.component.html @@ -44,7 +44,7 @@ <div class="field has-addons add-remove-toggle" *ngIf="wrapper.type !== 'drug'"> <app-toggle [value]="analysis.inSelection(wrapper)" - (valueChange)="$event ? analysis.addItem(wrapper) : analysis.removeItem(wrapper)" textOn="Selected" + (valueChange)="$event ? analysis.addItems([wrapper]) : analysis.removeItems([wrapper])" textOn="Selected" textOff="Deselected" tooltipOn="Add protein to selection." tooltipOff="Remove protein from selection." icon="fa-plus"></app-toggle> </div> diff --git a/src/app/components/launch-analysis/launch-analysis.component.ts b/src/app/components/launch-analysis/launch-analysis.component.ts index adee8d4705627fb5866f0cfac643a7b874a0850b..63dc23feddde45d4ac106d7b84a54e1947b04def 100644 --- a/src/app/components/launch-analysis/launch-analysis.component.ts +++ b/src/app/components/launch-analysis/launch-analysis.component.ts @@ -57,7 +57,7 @@ export class LaunchAnalysisComponent implements OnInit, OnChanges { constructor(public analysis: AnalysisService) { this.hasBaits = !!analysis.getSelection().find((i) => i.type === 'virus'); - analysis.subscribe(() => { + analysis.subscribeList(() => { this.hasBaits = !!analysis.getSelection().find((i) => i.type === 'virus'); }); } diff --git a/src/app/pages/explorer-page/explorer-page.component.html b/src/app/pages/explorer-page/explorer-page.component.html index 71de0c631d613911417f10aeed72e5ce066ad8b5..6d693342633ca0a6edf9ece1ba3eded7e2a72eda 100644 --- a/src/app/pages/explorer-page/explorer-page.component.html +++ b/src/app/pages/explorer-page/explorer-page.component.html @@ -26,7 +26,6 @@ </a> </header> <div *ngIf="collapseData"> - <div class="card-content"> <app-select-dataset [datasetItems]="datasetItems" [selectedDataset]="selectedDataset" (selectedDatasetChange)="selectedDataset = $event; createNetwork($event.data)"> @@ -371,7 +370,6 @@ </div> </div> - <div class="card bar-large"> <header class="card-header"> <p class="card-header-title"> @@ -408,7 +406,7 @@ <td *ngIf="p.type == 'virus'">{{p.data.effectName}}</td> <td *ngIf="p.type == 'host'">{{p.data.name}}</td> <td> - <button (click)="analysis.removeItem(p)" class="button is-small is-danger is-outlined has-tooltip" + <button (click)="analysis.removeItems([p])" class="button is-small is-danger is-outlined has-tooltip" data-tooltip="Remove from selection."> <i class="fa fa-trash"></i> </button> diff --git a/src/app/pages/explorer-page/explorer-page.component.ts b/src/app/pages/explorer-page/explorer-page.component.ts index c83e9db454c50019b3fd89d1c75bf012ca395bc4..3611926b8ab28278b9dce15e1e9141f01c952fdb 100644 --- a/src/app/pages/explorer-page/explorer-page.component.ts +++ b/src/app/pages/explorer-page/explorer-page.component.ts @@ -114,16 +114,38 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { this.showDetails = false; - this.analysis.subscribe((item, selected) => { - const node = this.nodeData.nodes.get(item.nodeId); - if (!node) { + this.analysis.subscribeList((items, selected) => { + if (!this.nodeData.nodes) { return; } - const pos = this.network.getPositions([item.nodeId]); - node.x = pos[item.nodeId].x; - node.y = pos[item.nodeId].y; - Object.assign(node, NetworkSettings.getNodeStyle(node.wrapper.type, true, selected)); - this.nodeData.nodes.update(node); + if (selected !== null) { + if (items.length === 0) { + return; + } + const updatedNodes = []; + for (const item of items) { + const node = this.nodeData.nodes.get(item.nodeId); + if (!node) { + continue; + } + const pos = this.network.getPositions([item.nodeId]); + node.x = pos[item.nodeId].x; + node.y = pos[item.nodeId].y; + Object.assign(node, NetworkSettings.getNodeStyle(node.wrapper.type, true, selected)); + updatedNodes.push(node); + } + this.nodeData.nodes.update(updatedNodes); + } else { + const updatedNodes = []; + this.nodeData.nodes.forEach((node) => { + const nodeSelected = this.analysis.idInSelection(node.id); + if (selected !== nodeSelected) { + Object.assign(node, NetworkSettings.getNodeStyle(node.wrapper.type, true, selected)); + updatedNodes.push(node); + } + }); + this.nodeData.nodes.update(updatedNodes); + } }); } @@ -234,9 +256,9 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { const node = this.nodeData.nodes.get(nodeId); const wrapper = node.wrapper; if (this.analysis.inSelection(wrapper)) { - this.analysis.removeItem(wrapper); + this.analysis.removeItems([wrapper]); } else { - this.analysis.addItem(wrapper); + this.analysis.addItems([wrapper]); } } });