diff --git a/src/app/app.module.ts b/src/app/app.module.ts index e399996d8f83295b254effe9fd85be7ad235d9a8..c6df78bde8fb03947251684c1f297928d757298a 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -16,7 +16,7 @@ import { TaskListComponent } from './components/task-list/task-list.component'; import { ToggleComponent } from './components/toggle/toggle.component'; import { InfoTileComponent } from './components/info-tile/info-tile.component'; import { CustomProteinsComponent } from './dialogs/custom-proteins/custom-proteins.component'; -import { DownloadButtonComponent } from './components/download-button/download-button.component'; +import { DownloadButtonComponent } from './components/network/network-menu/download-button/download-button.component'; import { MatTooltipModule } from '@angular/material/tooltip'; @@ -34,10 +34,15 @@ import { faRulerVertical, faDna, faMicroscope, faBook, faPause, faTrash, faSpinner, faExclamationTriangle, faPlus, faExpand, faInfo, faRocket, faAngleDown, faSearch, faFastForward, faExternalLinkAlt, faTasks, faFilter, faMinus, faUpload, faAngleDoubleDown, faSync, faBroom, faAngleDoubleUp, faChild, faHeadSideMask, faBiohazard, - faBullseye + faBullseye, faSeedling } from '@fortawesome/free-solid-svg-icons'; import { TooltipModule } from 'primeng/tooltip'; -import { NetworkMenuComponent } from './components/network-menu/network-menu.component'; +import { NetworkMenuComponent } from './components/network/network-menu/network-menu.component'; +import { NetworkComponent } from './components/network/network.component'; +import { ToggleInplaceComponent } from './components/network/network-menu/toggle-inplace/toggle-inplace.component'; +import { NetworkMenuLeftComponent } from './components/network/network-menu-left/network-menu-left.component'; +import { ToggleInplaceReversedComponent } from './components/network/network-menu-left/toggle-inplace-reversed/toggle-inplace-reversed.component'; +import { DownloadButtonInverseComponent } from './components/network/network-menu-left/download-button-inverse/download-button-inverse.component'; @NgModule({ declarations: [ @@ -56,6 +61,11 @@ import { NetworkMenuComponent } from './components/network-menu/network-menu.com DrugTableComponent, DownloadButtonComponent, NetworkMenuComponent, + NetworkComponent, + ToggleInplaceComponent, + NetworkMenuLeftComponent, + ToggleInplaceReversedComponent, + DownloadButtonInverseComponent, ], imports: [ BrowserModule, @@ -80,7 +90,7 @@ export class AppModule { faCheck, faCamera, faDownload, faRulerVertical, faDna, faMicroscope, faBook, faPause, faTrash, faSpinner, faExclamationTriangle, faPlus, faExpand, faInfo, faRocket, faAngleDown, faSearch, faFastForward, faExternalLinkAlt, faTasks, faFilter, faMinus, faUpload, faAngleDoubleDown, - faSync, faBroom, faAngleDoubleUp, faChild, faHeadSideMask, faBiohazard, faBullseye); + faSync, faBroom, faAngleDoubleUp, faChild, faHeadSideMask, faBiohazard, faBullseye, faSeedling); const NetworkExpander = createCustomElement(ExplorerPageComponent, { injector }); // Register the custom element with the browser. customElements.define('drugst-one', NetworkExpander); diff --git a/src/app/components/analysis-panel/analysis-panel.component.html b/src/app/components/analysis-panel/analysis-panel.component.html index e8c7cafc04b2f75f1f7f06f9a6ce276f511868b3..63a732a826fba725686ef39c5cfd802f62407d46 100644 --- a/src/app/components/analysis-panel/analysis-panel.component.html +++ b/src/app/components/analysis-panel/analysis-panel.component.html @@ -7,307 +7,319 @@ </span> Analysis Results </p> + + <a + (click)="analysis.removeTask(token); close()" + class="card-header-icon" + aria-label="delete" + > + <span class="icon has-text-danger" title="Delete analysis"> + <i class="fas fa-trash" aria-hidden="true"></i> + </span> + </a> + <a (click)="close()" class="card-header-icon" aria-label="close"> - <span class="icon"> + <span class="icon" title="Close analysis"> <i class="fas fa-times" aria-hidden="true"></i> </span> </a> </header> - <div class="card-content" [class.network-view-settings]="tab === 'network'"> + <div + class="card-content tab-header-small" + [class.network-view-settings]="tab === 'network'" + > <div class="tabs is-centered tabs-header"> <ul> - <li [class.is-active]="tab === 'table'"><a (click)="tab = 'table'">Table</a></li> - <li [class.is-active]="tab === 'network'"><a (click)="tab = 'network'">Network</a></li> - <li [class.is-active]="tab === 'meta'"><a (click)="tab = 'meta'">Parameters</a></li> + <li [class.is-active]="tab === 'table'"> + <a (click)="tab = 'table'">Table</a> + </li> + <li [class.is-active]="tab === 'network'"> + <a (click)="tab = 'network'">Network</a> + </li> + <li [class.is-active]="tab === 'meta'"> + <a (click)="tab = 'meta'">Parameters</a> + </li> </ul> </div> - <div class="tab-content meta" *ngIf="task && task.info.done" [class.is-visible]="tab === 'meta'"> + <div + class="tab-content meta" + *ngIf="task && task.info.done" + [class.is-visible]="tab === 'meta'" + > <div *ngIf="task"> - <p *ngIf="task.info.algorithm !== 'quick' && task.info.algorithm !== 'super'"> - Algorithm: <strong class="has-text-normal">{{algorithmNames[task.info.algorithm]}}</strong> + <p + *ngIf=" + task.info.algorithm !== 'quick' && task.info.algorithm !== 'super' + " + > + Algorithm: + <strong class="has-text-normal">{{ + algorithmNames[task.info.algorithm] + }}</strong> </p> <div> <table class="table is-narrow"> <tbody> - <tr *ngIf="result && result.geneInteractionDataset !== undefined"> + <tr + *ngIf="result && result.geneInteractionDataset !== undefined" + > <td>Protein-Protein Interaction Dataset</td> - <td>{{result.geneInteractionDataset.name}} (Version {{result.geneInteractionDataset.version}})</td> + <td> + {{ result.geneInteractionDataset.name }} (Version + {{ result.geneInteractionDataset.version }}) + </td> </tr> - <tr *ngIf="result && result.drugInteractionDataset !== undefined"> + <tr + *ngIf="result && result.drugInteractionDataset !== undefined" + > <td>Protein-Drug Interaction Dataset</td> - <td>{{result.drugInteractionDataset.name}} (Version {{result.drugInteractionDataset.version}})</td> + <td> + {{ result.drugInteractionDataset.name }} (Version + {{ result.drugInteractionDataset.version }}) + </td> + </tr> + <tr *ngIf="task.info.parameters.resultSize !== undefined"> + <td>Result Size</td> + <td>{{ task.info.parameters.resultSize }}</td> + </tr> + <tr *ngIf="task.info.parameters.k !== undefined"> + <td>K</td> + <td>{{ task.info.parameters.k }}</td> + </tr> + <tr *ngIf="task.info.parameters.numTrees !== undefined"> + <td>Number of trees</td> + <td>{{ task.info.parameters.numTrees }}</td> + </tr> + <tr *ngIf="task.info.parameters.tolerance !== undefined"> + <td>Tolerance</td> + <td>{{ task.info.parameters.tolerance }}</td> + </tr> + <tr *ngIf="task.info.parameters.dampingFactor !== undefined"> + <td>Damping Factor</td> + <td>{{ task.info.parameters.dampingFactor }}</td> + </tr> + <tr *ngIf="task.info.parameters.maxDeg !== undefined"> + <td>Maximum Degree</td> + <td>{{ task.info.parameters.maxDeg }}</td> + </tr> + <tr *ngIf="task.info.parameters.hubPenalty !== undefined"> + <td>Hub Penality</td> + <td>{{ task.info.parameters.hubPenalty }}</td> + </tr> + <tr + *ngIf=" + task.info.parameters.includeIndirectDrugs !== undefined && + task.info.target === 'drug' + " + > + <td>Include indirect drugs</td> + <td> + <i + *ngIf="task.info.parameters.includeIndirectDrugs" + class="fa fa-check" + ></i> + <i + *ngIf="!task.info.parameters.includeIndirectDrugs" + class="fa fa-times" + ></i> + </td> + </tr> + <tr + *ngIf=" + task.info.parameters.includeNonApprovedDrugs !== + undefined && task.info.target === 'drug' + " + > + <td>Include non-approved drugs</td> + <td> + <i + *ngIf="task.info.parameters.includeNonApprovedDrugs" + class="fa fa-check" + ></i> + <i + *ngIf="!task.info.parameters.includeNonApprovedDrugs" + class="fa fa-times" + ></i> + </td> </tr> - <tr *ngIf="task.info.parameters.resultSize !== undefined"> - <td>Result Size</td> - <td>{{task.info.parameters.resultSize}}</td> - </tr> - <tr *ngIf="task.info.parameters.k !== undefined"> - <td>K</td> - <td>{{task.info.parameters.k}}</td> - </tr> - <tr *ngIf="task.info.parameters.numTrees !== undefined"> - <td>Number of trees</td> - <td>{{task.info.parameters.numTrees}}</td> - </tr> - <tr *ngIf="task.info.parameters.tolerance !== undefined"> - <td>Tolerance</td> - <td>{{task.info.parameters.tolerance}}</td> - </tr> - <tr *ngIf="task.info.parameters.dampingFactor !== undefined"> - <td>Damping Factor</td> - <td>{{task.info.parameters.dampingFactor}}</td> - </tr> - <tr *ngIf="task.info.parameters.maxDeg !== undefined"> - <td>Maximum Degree</td> - <td>{{task.info.parameters.maxDeg}}</td> - </tr> - <tr *ngIf="task.info.parameters.hubPenalty !== undefined"> - <td>Hub Penality</td> - <td>{{task.info.parameters.hubPenalty}}</td> - </tr> - <tr *ngIf="task.info.parameters.includeIndirectDrugs !== undefined && task.info.target === 'drug'"> - <td>Include indirect drugs</td> - <td> - <i *ngIf="task.info.parameters.includeIndirectDrugs" class="fa fa-check"></i> - <i *ngIf="!task.info.parameters.includeIndirectDrugs" class="fa fa-times"></i> - </td> - </tr> - <tr *ngIf="task.info.parameters.includeNonApprovedDrugs !== undefined && task.info.target === 'drug'"> - <td>Include non-approved drugs</td> - <td> - <i *ngIf="task.info.parameters.includeNonApprovedDrugs" class="fa fa-check"></i> - <i *ngIf="!task.info.parameters.includeNonApprovedDrugs" class="fa fa-times"></i> - </td> - </tr> </tbody> </table> </div> - <div *ngIf="task.info.algorithm === 'quick' || task.info.algorithm === 'super'"> + <div + *ngIf=" + task.info.algorithm === 'quick' || task.info.algorithm === 'super' + " + > <p> - Algorithm: <strong>{{algorithmNames['multisteiner']}}</strong> + Algorithm: <strong>{{ algorithmNames["multisteiner"] }}</strong> </p> <table class="table is-narrow"> <tbody> - <tr> - <td>Number of Trees</td> - <td>1</td> - </tr> - <tr> - <td>Tolerance</td> - <td>0</td> - </tr> - <tr> - <td>Hub Penality</td> - <td>1</td> - </tr> + <tr> + <td>Number of Trees</td> + <td>1</td> + </tr> + <tr> + <td>Tolerance</td> + <td>0</td> + </tr> + <tr> + <td>Hub Penality</td> + <td>1</td> + </tr> </tbody> </table> <p> - Algorithm: <strong>{{algorithmNames['closeness']}}</strong> + Algorithm: <strong>{{ algorithmNames["closeness"] }}</strong> </p> <table class="table is-narrow"> <tbody> - <tr> - <td>Include indirect drugs</td> - <td> - <i class="fa fa-times"></i> - </td> - </tr> - <tr> - <td>Include non-approved drugs</td> - <td> - <i class="fa fa-check"></i> - </td> - </tr> - <tr> - <td>Hub Penality</td> - <td>1</td> - </tr> - <tr> - <td>Result Size</td> - <td>10</td> - </tr> + <tr> + <td>Include indirect drugs</td> + <td> + <i class="fa fa-times"></i> + </td> + </tr> + <tr> + <td>Include non-approved drugs</td> + <td> + <i class="fa fa-check"></i> + </td> + </tr> + <tr> + <td>Hub Penality</td> + <td>1</td> + </tr> + <tr> + <td>Result Size</td> + <td>10</td> + </tr> </tbody> </table> </div> </div> </div> - <div class="tab-content" *ngIf="task && task.info.done" [class.is-visible]="tab === 'network'"> - <div class="card-image canvas-content" #networkWithLegend> - <div *ngIf="myConfig.showLegend"> - <app-network-legend [config]="myConfig" [context]="legendContext"></app-network-legend> + <div class="tab-content" [class.is-visible]="tab === 'network'"> + <!-- network start --> + <app-network + networkType="analysis" + [nodeData]="nodeData" + [legendContext]="legendContext" + ></app-network> + <!-- network end --> + </div> + <div + class="content tab-content scrollable table-tab" + *ngIf="task && task.info.done" + [class.is-visible]="tab === 'table'" + > + <div class="columns m-1"> + <!-- column normalization for drugs button START --> + <div class="column"> + <h4 class="is-4"> + <span class="icon"><i class="fa fa-capsules"></i></span> + <span>Drugs</span> + </h4> </div> - <div class="parent fullheight"> - <div class="center image1 fullheight" #network> - <button class="button is-loading center" alt="loading...">Loading</button> + <div class="column"> + <div class="field has-addons is-pulled-right m-1"> + <a + [href]="downloadLink('drugs')" + class="button is-primary control is-outlined is-rounded is-pulled-right is-small" + > + <span class="icon"><i class="fa fa-download"></i></span> + <span>Download</span> + </a> </div> - </div> - </div> - - <footer *ngIf="myConfig.showNetworkMenu" class="card-footer toolbar network-footer-toolbar"> - <div class="network-footer-toolbar-inner-container"> - - <div *ngIf="myConfig.showNetworkMenuButtonScreenshot" class="network-footer-toolbar-element"> - <button class="button is-primary is-rounded has-tooltip" - pTooltip="Take a screenshot of the current network." - [tooltipStyleClass]="'drgstn drgstn-tooltip'" - tooltipPosition="top" - [ngClass]="{ 'button-small': drugstoneConfig.smallStyle }" - (click)="toImage()"> - <span class="icon"> - <i class="fas fa-camera" aria-hidden="true"></i> - </span> - <span [ngClass]="{'text-normal':drugstoneConfig.smallStyle}"> - Screenshot + <div + class="field has-addons is-pulled-right m-1" + *ngIf="tableHasScores && task.info.algorithm !== 'proximity'" + > + <p class="control"> + <button + class="button is-rounded has-tooltip is-small" + pTooltip="Normalize the scores" + [tooltipStyleClass]="'drgstn drgstn-tooltip'" + tooltipPosition="top" + [class.is-primary]="tableNormalize" + (click)="toggleNormalization(true)" + [ngClass]="{ 'text-small': drugstoneConfig.smallStyle }" + > + <span class="icon is-small"> + <i class="fa fa-ruler-vertical"></i> </span> - </button> + <span>Normalization On</span> + </button> + </p> + <p class="control"> + <button + class="button is-rounded has-tooltip is-small" + pTooltip="Disable normalization of the scores." + [tooltipStyleClass]="'drgstn drgstn-tooltip'" + tooltipPosition="top" + [class.is-primary]="!tableNormalize" + (click)="toggleNormalization(false)" + [ngClass]="{ 'text-small': drugstoneConfig.smallStyle }" + > + <span>Off</span> + </button> + </p> </div> + </div> + <!-- column normalization for drugs button START --> + </div> - <ng-container *ngIf="myConfig.showNetworkMenuButtonExportGraphml"> - <app-download-button [nodeData]=nodeData [buttonId]="'analysis-download'"></app-download-button> - </ng-container> - - <!-- <div class="field">--> - <!-- <p class="control footer-buttons">--> - <!-- <button class="button is-danger is-rounded has-tooltip" data-tooltip="Delete the current analysis."--> - <!-- (click)="analysis.removeTask(token); close()">--> - <!-- <span class="icon">--> - <!-- <i class="fas fa-trash" aria-hidden="true"></i>--> - <!-- </span>--> - <!-- <span>--> - <!-- Delete Analysis--> - <!-- </span>--> - <!-- </button>--> - <!-- </p>--> - <!-- </div>--> + <div class="columns m-1"> + <!-- drug table or placeholder if no drugs found START --> + <div class="column"> + <div *ngIf="tableDrugs.length === 0 && task.info.target === 'drug'"> + <i>No drugs have been found.</i> + </div> + <app-drug-table + [tableDrugs]="tableDrugs" + [tableDrugScoreTooltip]="tableDrugScoreTooltip" + [tableHasScores]="tableHasScores" + ></app-drug-table> + </div> + <!-- drug table or placeholder if no drugs found END --> + </div> - <div class="dropdown is-up network-footer-toolbar-element" - [class.is-active]="expressionExpanded"> - <div class="dropdown-trigger"> - <button (click)="expressionExpanded=!expressionExpanded" - class="button is-rounded is-primary" [class.is-outlined]="!selectedTissue" - aria-haspopup="true" aria-controls="dropdown-menu" [ngClass]="{'button-small':drugstoneConfig.smallStyle}" - pTooltip="Tissue expression data is provided by the GTEx project." [tooltipStyleClass]="'drgstn drgstn-tooltip'" tooltipPosition="top" + <div *ngIf="tableProteins.length > 0" class="table-header"> + <div class="columns m-1"> + <div class="column"> + <h4 class="is-4"> + <span class="icon"><i class="fa fa-dna"></i></span> + <span>Proteins</span> + </h4> + </div> + <div class="column"> + <div class="field has-addons is-pulled-right m-1 control"> + <a + [href]="downloadLink('proteins')" + class="button is-primary is-rounded is-outlined is-pulled-right is-small" > - <div [ngClass]="{'text-small':drugstoneConfig.smallStyle}"> - <span *ngIf="!selectedTissue">Tissue</span> - <span *ngIf="selectedTissue">{{selectedTissue.name}}</span> - <span class="icon is-small"> - <i class="fas" - [class.fa-angle-up]="expressionExpanded" - [class.fa-angle-left]="!expressionExpanded" aria-hidden="true"></i> - </span> - </div> - </button> - </div> - <div class="dropdown-menu" id="dropdown-menu" role="menu"> - <div class="dropdown-content tissue-dropdown" [ngClass]="{'button-small':drugstoneConfig.smallStyle}"> - <div class="scroll-area" [ngClass]="{'text-small':drugstoneConfig.smallStyle}"> - <a (click)="selectTissue(null)" - [class.is-active]="!selectedTissue" - class="dropdown-item"> - None - </a> - <a *ngFor="let tissue of analysis.getTissues()" - (click)="selectTissue(tissue)" - [class.is-active]="selectedTissue && tissue.netexId === selectedTissue.netexId" - class="dropdown-item"> - {{tissue.name}} - </a> - </div> - </div> + <span class="icon"><i class="fa fa-download"></i></span> + <span>Download</span> + </a> </div> </div> - - <app-toggle class="network-footer-toolbar-element" textOn="Seeds On" textOff="Off" - tooltipOn="Highlight seed nodes ON." - tooltipOff="Highlight seed nodes OFF." - [value]="highlightSeeds" (valueChange)="updateHighlightSeeds($event)"></app-toggle> - - <app-toggle *ngIf="task.info.target === 'drug-target'" class="network-footer-toolbar-element" - textOn="Drugs" textOff="Off" - tooltipOn="Display adjacent drugs ON." - tooltipOff="Display adjacent drugs OFF." - [value]="adjacentDrugs" (valueChange)="updateAdjacentDrugs($event)"></app-toggle> - <app-toggle - class="network-footer-toolbar-element" - textOn="Disorders (protein)" - textOff="Off" - tooltipOn="Show disorders adjacent to all currently displayed proteins/genes ON." - tooltipOff="Show disorders adjacent to all currently displayed proteins/genes OFF." - [value]="adjacentDisordersProtein" - (valueChange)="updateAdjacentProteinDisorders($event)" - ></app-toggle> - <app-toggle - class="network-footer-toolbar-element" - textOn="Disorders (drugs)" - textOff="Off" - tooltipOn="Show disorders adjacent to all currently displayed drugs ON." - tooltipOff="Show disorders adjacent to all currently displayed drugs OFF." - [value]="adjacentDisordersDrug" - [disabled]="!hasDrugsLoaded()" - (valueChange)="updateAdjacentDrugDisorders($event)" - ></app-toggle> - - <app-toggle class="network-footer-toolbar-element" textOn="Animation" textOff="Off" - tooltipOn="Enable the network animation." - tooltipOff="Disable the network animation and freeze nodes." - [value]="drugstoneConfig.config.physicsOn" (valueChange)="updatePhysicsEnabled($event)"></app-toggle> </div> - </footer> - </div> - <div class="content tab-content scrollable table-tab" *ngIf="task && task.info.done" - [class.is-visible]="tab === 'table'"> - <div class="field has-addons is-pulled-right m-1" *ngIf="tableHasScores && task.info.algorithm !== 'proximity'"> - <p class="control"> - <button class="button is-rounded has-tooltip" pTooltip="Normalize the scores" [tooltipStyleClass]="'drgstn drgstn-tooltip'" tooltipPosition="top" - [class.is-primary]="tableNormalize" (click)="toggleNormalization(true)" [ngClass]="{ 'button-small': drugstoneConfig.smallStyle }"> - <span class="icon is-small"> - <i class="fa fa-ruler-vertical"></i> - </span> - <span>Normalization On</span> - </button> - </p> - <p class="control"> - <button class="button is-rounded has-tooltip" pTooltip="Disable normalization of the scores." [tooltipStyleClass]="'drgstn drgstn-tooltip'" - tooltipPosition="top" - [class.is-primary]="!tableNormalize" (click)="toggleNormalization(false)" [ngClass]="{ 'button-small': drugstoneConfig.smallStyle }"> - <span>Off</span> - </button> - </p> </div> - <div *ngIf="tableDrugs.length === 0 && task.info.target === 'drug'"> - <i>No drugs have been found.</i> - </div> - <div *ngIf="tableDrugs.length > 0" class="table-header"> - <h4 class="is-4"> - <span class="icon"><i class="fa fa-capsules"></i></span> - <span>Drugs</span> - </h4> - <a [href]="downloadLink('drugs')" class="button is-primary is-outlined is-pulled-right is-small"> - <span class="icon"><i class="fa fa-download"></i></span> - <span>Download</span> - </a> - </div> - <app-drug-table [tableDrugs]="tableDrugs" [tableDrugScoreTooltip]="tableDrugScoreTooltip" - [tableHasScores]="tableHasScores"></app-drug-table> - <div *ngIf="tableProteins.length > 0" class="table-header"> - <h4 class="is-4"> - <span class="icon"><i class="fa fa-dna"></i></span> - <span>Proteins</span> - </h4> - <a [href]="downloadLink('proteins')" class="button is-primary is-outlined is-pulled-right is-small"> - <span class="icon"><i class="fa fa-download"></i></span> - <span>Download</span> - </a> + <div class="columns m-1"> + <div class="column"> + <app-prot-table + [tableHasScores]="tableHasScores" + [tableProteins]="tableProteins" + [tableProteinScoreTooltip]="tableDrugScoreTooltip" + [tableProteinSelection]="tableProteinSelection" + [tableSelectedProteins]="tableSelectedProteins" + ></app-prot-table> + </div> </div> - <app-prot-table [tableHasScores]="tableHasScores" [tableProteins]="tableProteins" - [tableProteinScoreTooltip]="tableDrugScoreTooltip" - [tableProteinSelection]="tableProteinSelection" - [tableSelectedProteins]="tableSelectedProteins"></app-prot-table> + </div> </div> </div> diff --git a/src/app/components/analysis-panel/analysis-panel.component.scss b/src/app/components/analysis-panel/analysis-panel.component.scss index 5ad165461e2232e7b49d8ed96c81deb710d4862f..92b3e1380674851115135894a532fd7e87761833 100644 --- a/src/app/components/analysis-panel/analysis-panel.component.scss +++ b/src/app/components/analysis-panel/analysis-panel.component.scss @@ -6,6 +6,10 @@ width: 100% !important; } +.tab-header-small { + padding: 0 !important; +} + .network-view-settings { // remove margin from tab header when network is displayed, so that network // does not disappear in empty border @@ -16,7 +20,7 @@ } .tab-content { width: 100%; - height: calc(100% - #{$network-header-height} - #{$analysis-tab-header-height} - #{$analysis-tab-header-padding}); + height: calc(100% - #{$network-header-height} - #{$analysis-tab-header-height}); } } @@ -44,10 +48,6 @@ } } -.table-header { - margin-bottom: 50px; -} - .checkbox-col { width: 50px; } @@ -71,12 +71,6 @@ overflow-y: auto; } -.network-footer-toolbar{ - .dropdown-menu{ - z-index: $analysis-network-toolbar-dropdown-z !important; - } -} - .legend { z-index: $analysis-network-legend-z !important; } diff --git a/src/app/components/analysis-panel/analysis-panel.component.ts b/src/app/components/analysis-panel/analysis-panel.component.ts index d493875933eb8a183f8f0b92cc01779fe8473f1d..7bc6f2826603c9512b1b0adb65795bb05a86533e 100644 --- a/src/app/components/analysis-panel/analysis-panel.component.ts +++ b/src/app/components/analysis-panel/analysis-panel.component.ts @@ -1,4 +1,5 @@ import { + AfterViewInit, Component, ElementRef, EventEmitter, @@ -19,7 +20,7 @@ import { getDrugNodeId, getProteinNodeId, getWrapperFromNode, - legendContext, + LegendContext, Node, Task, Tissue, @@ -33,6 +34,7 @@ import {defaultConfig, IConfig} from 'src/app/config'; import { mapCustomEdge, mapCustomNode } from 'src/app/main-network'; import { downLoadFile, pieChartContextRenderer, removeDuplicateObjectsFromList } from 'src/app/utils'; import { DrugstoneConfigService } from 'src/app/services/drugstone-config/drugstone-config.service'; +import { NetworkHandlerService } from 'src/app/services/network-handler/network-handler.service'; declare var vis: any; @@ -56,9 +58,8 @@ interface Baited { templateUrl: './analysis-panel.component.html', styleUrls: ['./analysis-panel.component.scss'], }) -export class AnalysisPanelComponent implements OnInit, OnChanges { +export class AnalysisPanelComponent implements OnInit, OnChanges, AfterViewInit { - @ViewChild('network', {static: false}) networkEl: ElementRef; @ViewChild('networkWithLegend', {static: false}) networkWithLegendEl: ElementRef; @Input() token: string | null = null; @Input() @@ -98,9 +99,6 @@ export class AnalysisPanelComponent implements OnInit, OnChanges { public adjacentDrugDisorderList: Node[] = []; public adjacentDrugDisorderEdgesList: Node[] = []; - public highlightSeeds = false; - public seedMap: NodeAttributeMap; - private proteins: any; public effects: any; @@ -112,7 +110,7 @@ export class AnalysisPanelComponent implements OnInit, OnChanges { public tableNormalize = false; public tableHasScores = false; - public legendContext: legendContext = 'drugTarget'; + public LegendContext: LegendContext = 'drugTarget'; public expressionExpanded = false; public selectedTissue: Tissue | null = null; @@ -123,14 +121,20 @@ export class AnalysisPanelComponent implements OnInit, OnChanges { public tableProteinScoreTooltip = ''; public expressionMap: NodeAttributeMap; - public gradientMap: NodeAttributeMap = {}; - constructor(public drugstoneConfig: DrugstoneConfigService, private http: HttpClient, public analysis: AnalysisService, public netex: NetexControllerService) { + public legendContext: LegendContext = 'drug'; + + constructor(public networkHandler: NetworkHandlerService, public drugstoneConfig: DrugstoneConfigService, private http: HttpClient, public analysis: AnalysisService, public netex: NetexControllerService) { } async ngOnInit() { } + ngAfterViewInit() { + console.log(this.networkHandler.networks) + this.networkHandler.setActiveNetwork('analysis'); + } + async ngOnChanges(changes: SimpleChanges) { await this.refresh(); } @@ -174,12 +178,12 @@ export class AnalysisPanelComponent implements OnInit, OnChanges { this.result = await this.netex.getTaskResult(this.token); const nodeAttributes = this.result.nodeAttributes || {}; - this.seedMap = nodeAttributes.isSeed || {}; + this.networkHandler.activeNetwork.seedMap = nodeAttributes.isSeed || {}; // Reset this.nodeData = {nodes: null, edges: null}; - this.networkEl.nativeElement.innerHTML = ''; - this.network = null; + this.networkHandler.activeNetwork.networkEl.nativeElement.innerHTML = ''; + this.networkHandler.activeNetwork.networkInternal = null; this.showDrugs = false; // Create @@ -187,12 +191,12 @@ export class AnalysisPanelComponent implements OnInit, OnChanges { this.setInputNetwork.emit({nodes: nodes, edges: edges}); this.nodeData.nodes = new vis.DataSet(nodes); this.nodeData.edges = new vis.DataSet(edges); - const container = this.networkEl.nativeElement; + const container = this.networkHandler.activeNetwork.networkEl.nativeElement; const isBig = nodes.length > 100 || edges.length > 100; const options = NetworkSettings.getOptions(isBig ? 'analysis-big' : 'analysis', this.myConfig.physicsOn); this.drugstoneConfig.config.physicsOn = !isBig; - this.network = new vis.Network(container, this.nodeData, options); + this.networkHandler.activeNetwork.networkInternal = new vis.Network(container, this.nodeData, options); this.tableDrugs = nodes.filter( e => e.netexId && e.netexId.startsWith('d')); this.tableDrugs.forEach((r) => { @@ -203,7 +207,7 @@ export class AnalysisPanelComponent implements OnInit, OnChanges { this.tableSelectedProteins = []; this.tableProteins.forEach((r) => { r.rawScore = r.score; - r.isSeed = this.seedMap[r.id]; + r.isSeed = this.networkHandler.activeNetwork.seedMap[r.id]; const wrapper = getWrapperFromNode(r); if (this.analysis.inSelection(wrapper)) { this.tableSelectedProteins.push(r); @@ -221,11 +225,11 @@ export class AnalysisPanelComponent implements OnInit, OnChanges { } } - this.network.on('deselectNode', (properties) => { + this.networkHandler.activeNetwork.networkInternal.on('deselectNode', (properties) => { this.showDetailsChange.emit(null); }); - this.network.on('doubleClick', (properties) => { + this.networkHandler.activeNetwork.networkInternal.on('doubleClick', (properties) => { const nodeIds: Array<string> = properties.nodes; if (nodeIds.length > 0) { const nodeId = nodeIds[0]; @@ -244,7 +248,7 @@ export class AnalysisPanelComponent implements OnInit, OnChanges { } }); - this.network.on('click', (properties) => { + this.networkHandler.activeNetwork.networkInternal.on('click', (properties) => { const selectedNodes = this.nodeData.nodes.get(properties.nodes); if (selectedNodes.length > 0) { this.showDetailsChange.emit(getWrapperFromNode(selectedNodes[0])); @@ -266,11 +270,11 @@ export class AnalysisPanelComponent implements OnInit, OnChanges { if (!node) { continue; } - const pos = this.network.getPositions([item.id]); + const pos = this.networkHandler.activeNetwork.networkInternal.getPositions([item.id]); node.x = pos[item.id].x; node.y = pos[item.id].y; - const isSeed = this.highlightSeeds ? this.seedMap[node.id] : false; - const gradient = (this.gradientMap !== {}) && (this.gradientMap[item.id]) ? this.gradientMap[item.id] : 1.0; + const isSeed = this.networkHandler.activeNetwork.highlightSeeds ? this.networkHandler.activeNetwork.seedMap[node.id] : false; + const gradient = (this.networkHandler.activeNetwork.gradientMap !== {}) && (this.networkHandler.activeNetwork.gradientMap[item.id]) ? this.networkHandler.activeNetwork.gradientMap[item.id] : 1.0; const nodeStyled = NetworkSettings.getNodeStyle( node, this.myConfig, @@ -307,8 +311,8 @@ export class AnalysisPanelComponent implements OnInit, OnChanges { // drugType = node.status; // drugInTrial = node.inTrial; // } - const isSeed = this.highlightSeeds ? this.seedMap[node.id] : false; - const gradient = (this.gradientMap !== {}) && (this.gradientMap[node.id]) ? this.gradientMap[node.id] : 1.0; + const isSeed = this.networkHandler.activeNetwork.highlightSeeds ? this.networkHandler.activeNetwork.seedMap[node.id] : false; + const gradient = (this.networkHandler.activeNetwork.gradientMap !== {}) && (this.networkHandler.activeNetwork.gradientMap[node.id]) ? this.networkHandler.activeNetwork.gradientMap[node.id] : 1.0; const nodeStyled = NetworkSettings.getNodeStyle( node, this.myConfig, @@ -336,7 +340,7 @@ export class AnalysisPanelComponent implements OnInit, OnChanges { } this.emitVisibleItems(true); - this.setLegendContext(); + this.networkHandler.activeNetwork.setLegendContext(); } public emitVisibleItems(on: boolean) { @@ -352,11 +356,11 @@ export class AnalysisPanelComponent implements OnInit, OnChanges { } close() { - this.gradientMap = {}; + this.networkHandler.activeNetwork.gradientMap = {}; this.expressionExpanded = false; this.expressionMap = undefined; - this.seedMap = {}; - this.highlightSeeds = false; + this.networkHandler.activeNetwork.seedMap = {}; + this.networkHandler.activeNetwork.highlightSeeds = false; this.showDrugs = false; this.analysis.switchSelection('main'); this.token = null; @@ -398,78 +402,78 @@ export class AnalysisPanelComponent implements OnInit, OnChanges { } - public saveAddNodes(nodeList: Node[]) { - const existing = this.nodeData.nodes.get().map(n => n.id); - const toAdd = nodeList.filter(n => existing.indexOf(n.id) === -1) - this.nodeData.nodes.add(toAdd); - } - - public saveRemoveDisorders(nodeList: Node[]) { - const other = this.adjacentDrugDisorderList === nodeList ? this.adjacentProteinDisorderList : this.adjacentDrugDisorderList - if (other == null) - this.nodeData.nodes.remove(nodeList); - else { - const otherIds = other.map(d => d.id); - const rest = nodeList.filter(d => otherIds.indexOf(d.id) === -1) - this.nodeData.nodes.remove(rest) - } - } - - public updateAdjacentProteinDisorders(bool: boolean) { - this.adjacentDisordersProtein = bool; - if (this.adjacentDisordersProtein) { - this.netex.adjacentDisorders(this.nodeData.nodes, 'proteins').subscribe(response => { - for (const interaction of response.edges) { - const edge = {from: interaction.protein, to: interaction.disorder}; - this.adjacentProteinDisorderEdgesList.push(mapCustomEdge(edge, this.myConfig)); - } - for (const disorder of response.disorders) { - disorder.group = 'defaultDisorder'; - disorder.id = disorder.netexId; - this.adjacentProteinDisorderList.push(mapCustomNode(disorder, this.myConfig)) - } - this.saveAddNodes(this.adjacentProteinDisorderList); - this.nodeData.edges.add(this.adjacentProteinDisorderEdgesList); - this.emitVisibleItems(true); - }); - this.legendContext = this.adjacentDrugs ? 'adjacentDrugsAndDisorders' : 'adjacentDisorders'; - } else { - this.saveRemoveDisorders(this.adjacentProteinDisorderList); - this.nodeData.edges.remove(this.adjacentProteinDisorderEdgesList); - this.adjacentProteinDisorderList = []; - this.adjacentProteinDisorderEdgesList = []; - this.legendContext = this.adjacentDisordersDrug ? this.legendContext : this.adjacentDrugs ? 'adjacentDrugs' : 'explorer'; - this.emitVisibleItems(true); - } - } - - public updateAdjacentDrugDisorders(bool: boolean) { - this.adjacentDisordersDrug = bool; - if (this.adjacentDisordersDrug) { - this.netex.adjacentDisorders(this.nodeData.nodes, 'drugs').subscribe(response => { - for (const interaction of response.edges) { - const edge = {from: interaction.drug, to: interaction.disorder}; - this.adjacentDrugDisorderEdgesList.push(mapCustomEdge(edge, this.myConfig)); - } - for (const disorder of response.disorders) { - disorder.group = 'defaultDisorder'; - disorder.id = disorder.netexId; - this.adjacentDrugDisorderList.push(mapCustomNode(disorder, this.myConfig)); - } - this.saveAddNodes(this.adjacentDrugDisorderList); - this.nodeData.edges.add(this.adjacentDrugDisorderEdgesList); - this.emitVisibleItems(true); - }); - this.legendContext = this.adjacentDrugs ? 'adjacentDrugsAndDisorders' : 'adjacentDisorders'; - } else { - this.saveRemoveDisorders(this.adjacentDrugDisorderList); - this.nodeData.edges.remove(this.adjacentDrugDisorderEdgesList); - this.adjacentDrugDisorderList = []; - this.adjacentDrugDisorderEdgesList = []; - this.legendContext = this.adjacentDisordersProtein ? this.legendContext : this.adjacentDrugs ? 'adjacentDrugs' : 'explorer'; - this.emitVisibleItems(true); - } - } + // public saveAddNodes(nodeList: Node[]) { + // const existing = this.nodeData.nodes.get().map(n => n.id); + // const toAdd = nodeList.filter(n => existing.indexOf(n.id) === -1) + // this.nodeData.nodes.add(toAdd); + // } + + // public saveRemoveDisorders(nodeList: Node[]) { + // const other = this.adjacentDrugDisorderList === nodeList ? this.adjacentProteinDisorderList : this.adjacentDrugDisorderList + // if (other == null) + // this.nodeData.nodes.remove(nodeList); + // else { + // const otherIds = other.map(d => d.id); + // const rest = nodeList.filter(d => otherIds.indexOf(d.id) === -1) + // this.nodeData.nodes.remove(rest) + // } + // } + + // public updateAdjacentProteinDisorders(bool: boolean) { + // this.adjacentDisordersProtein = bool; + // if (this.adjacentDisordersProtein) { + // this.netex.adjacentDisorders(this.nodeData.nodes, 'proteins').subscribe(response => { + // for (const interaction of response.edges) { + // const edge = {from: interaction.protein, to: interaction.disorder}; + // this.adjacentProteinDisorderEdgesList.push(mapCustomEdge(edge, this.myConfig)); + // } + // for (const disorder of response.disorders) { + // disorder.group = 'defaultDisorder'; + // disorder.id = disorder.netexId; + // this.adjacentProteinDisorderList.push(mapCustomNode(disorder, this.myConfig)) + // } + // this.saveAddNodes(this.adjacentProteinDisorderList); + // this.nodeData.edges.add(this.adjacentProteinDisorderEdgesList); + // this.emitVisibleItems(true); + // }); + // this.legendContext = this.adjacentDrugs ? 'adjacentDrugsAndDisorders' : 'adjacentDisorders'; + // } else { + // this.saveRemoveDisorders(this.adjacentProteinDisorderList); + // this.nodeData.edges.remove(this.adjacentProteinDisorderEdgesList); + // this.adjacentProteinDisorderList = []; + // this.adjacentProteinDisorderEdgesList = []; + // this.legendContext = this.adjacentDisordersDrug ? this.legendContext : this.adjacentDrugs ? 'adjacentDrugs' : 'explorer'; + // this.emitVisibleItems(true); + // } + // } + + // public updateAdjacentDrugDisorders(bool: boolean) { + // this.adjacentDisordersDrug = bool; + // if (this.adjacentDisordersDrug) { + // this.netex.adjacentDisorders(this.nodeData.nodes, 'drugs').subscribe(response => { + // for (const interaction of response.edges) { + // const edge = {from: interaction.drug, to: interaction.disorder}; + // this.adjacentDrugDisorderEdgesList.push(mapCustomEdge(edge, this.myConfig)); + // } + // for (const disorder of response.disorders) { + // disorder.group = 'defaultDisorder'; + // disorder.id = disorder.netexId; + // this.adjacentDrugDisorderList.push(mapCustomNode(disorder, this.myConfig)); + // } + // this.saveAddNodes(this.adjacentDrugDisorderList); + // this.nodeData.edges.add(this.adjacentDrugDisorderEdgesList); + // this.emitVisibleItems(true); + // }); + // this.legendContext = this.adjacentDrugs ? 'adjacentDrugsAndDisorders' : 'adjacentDisorders'; + // } else { + // this.saveRemoveDisorders(this.adjacentDrugDisorderList); + // this.nodeData.edges.remove(this.adjacentDrugDisorderEdgesList); + // this.adjacentDrugDisorderList = []; + // this.adjacentDrugDisorderEdgesList = []; + // this.legendContext = this.adjacentDisordersProtein ? this.legendContext : this.adjacentDrugs ? 'adjacentDrugs' : 'explorer'; + // this.emitVisibleItems(true); + // } + // } public downloadLink(view: string): string { return `${environment.backend}task_result/?token=${this.token}&view=${view}&fmt=csv`; @@ -580,124 +584,71 @@ export class AnalysisPanelComponent implements OnInit, OnChanges { }; } - public setLegendContext() { - const target = this.task.info.target; - if (target === 'drug' || this.adjacentDrugs) { - if (this.highlightSeeds) { - this.legendContext = "drugAndSeeds"; - } else { - this.legendContext = "drug"; - } - - } else if (target === 'drug-target') { - if (this.highlightSeeds) { - this.legendContext = "drugTargetAndSeeds"; - } else { - this.legendContext = 'drugTarget' - } - } else { - throw `Could not set legend context based on ${target}.` - } - } - - public updateHighlightSeeds(bool: boolean) { - this.highlightSeeds = bool; - const updatedNodes = []; - for (const item of this.proteins) { - if (item.netexId === undefined) { - // nodes that are not mapped to backend remain untouched - continue; - } - const node: Node = this.nodeData.nodes.get(item.id); - if (!node) { - continue; - } - const pos = this.network.getPositions([item.id]); - node.x = pos[item.id].x; - node.y = pos[item.id].y; - const isSeed = this.highlightSeeds ? this.seedMap[node.id] : false; - const gradient = (this.gradientMap !== {}) && (this.gradientMap[item.id]) ? this.gradientMap[item.id] : 1.0; - Object.assign( - node, - NetworkSettings.getNodeStyle( - node, - this.myConfig, - isSeed, - this.analysis.inSelection(getWrapperFromNode(item)), - gradient - ) - ) - updatedNodes.push(node); - } - this.nodeData.nodes.update(updatedNodes); - this.setLegendContext(); - } - - hasDrugsLoaded(): boolean { - if (this.nodeData == null || this.nodeData.nodes == null) - return false; - return this.nodeData.nodes.get().filter((node: Node) => node.drugId && node.netexId.startsWith('dr')).length > 0; - } - - public updateAdjacentDrugs(bool: boolean) { - this.adjacentDrugs = bool; - if (this.adjacentDrugs) { - this.netex.adjacentDrugs(this.myConfig.interactionDrugProtein, this.nodeData.nodes).subscribe(response => { - for (const interaction of response.pdis) { - const edge = {from: interaction.protein, to: interaction.drug}; - this.adjacentDrugEdgesList.push(mapCustomEdge(edge, this.myConfig)); - } - for (const drug of response.drugs) { - drug.group = 'foundDrug'; - drug.id = getDrugNodeId(drug) - this.adjacentDrugList.push(mapCustomNode(drug, this.myConfig)) - } - this.nodeData.nodes.add(this.adjacentDrugList); - this.nodeData.edges.add(this.adjacentDrugEdgesList); - this.emitVisibleItems(true); - }) - this.legendContext = this.adjacentDisordersDrug || this.adjacentDisordersProtein ? 'adjacentDrugsAndDisorders' : 'adjacentDrugs'; - } else { - this.nodeData.nodes.remove(this.adjacentDrugList); - this.nodeData.edges.remove(this.adjacentDrugEdgesList); - this.adjacentDrugList = []; - this.adjacentDrugEdgesList = []; - - this.legendContext = this.adjacentDisordersDrug || this.adjacentDisordersProtein ? 'adjacentDisorders' : 'explorer'; - this.emitVisibleItems(true); - } - } - - public updatePhysicsEnabled(bool: boolean) { - this.drugstoneConfig.config.physicsOn = bool; - this.network.setOptions({ - physics: { - enabled: this.drugstoneConfig.config.physicsOn, - stabilization: { - enabled: false, - }, - } - }); - } - - public toImage() { - this.downloadDom(this.networkWithLegendEl.nativeElement).catch(error => { - console.error("Falling back to network only screenshot. Some components seem to be inaccessable, most likely the legend is a custom image with CORS access problems on the host server side.") - this.downloadDom(this.networkEl.nativeElement).catch(e => { - console.error("Some network content seems to be inaccessable for saving as a screenshot. This can happen due to custom images used as nodes. Please ensure correct CORS accessability on the images host server.") - console.error(e) - }); - }); - } - - public downloadDom(dom: object) { - return domtoimage.toPng(dom, {bgcolor: '#ffffff'}).then((generatedImage) => { - const a = document.createElement('a'); - a.href = generatedImage; - a.download = `Network.png`; - a.click(); - }); - } + // hasDrugsLoaded(): boolean { + // if (this.nodeData == null || this.nodeData.nodes == null) + // return false; + // return this.nodeData.nodes.get().filter((node: Node) => node.drugId && node.netexId.startsWith('dr')).length > 0; + // } + + // public updateAdjacentDrugs(bool: boolean) { + // this.adjacentDrugs = bool; + // if (this.adjacentDrugs) { + // this.netex.adjacentDrugs(this.myConfig.interactionDrugProtein, this.nodeData.nodes).subscribe(response => { + // for (const interaction of response.pdis) { + // const edge = {from: interaction.protein, to: interaction.drug}; + // this.adjacentDrugEdgesList.push(mapCustomEdge(edge, this.myConfig)); + // } + // for (const drug of response.drugs) { + // drug.group = 'foundDrug'; + // drug.id = getDrugNodeId(drug) + // this.adjacentDrugList.push(mapCustomNode(drug, this.myConfig)) + // } + // this.nodeData.nodes.add(this.adjacentDrugList); + // this.nodeData.edges.add(this.adjacentDrugEdgesList); + // this.emitVisibleItems(true); + // }) + // this.legendContext = this.adjacentDisordersDrug || this.adjacentDisordersProtein ? 'adjacentDrugsAndDisorders' : 'adjacentDrugs'; + // } else { + // this.nodeData.nodes.remove(this.adjacentDrugList); + // this.nodeData.edges.remove(this.adjacentDrugEdgesList); + // this.adjacentDrugList = []; + // this.adjacentDrugEdgesList = []; + + // this.legendContext = this.adjacentDisordersDrug || this.adjacentDisordersProtein ? 'adjacentDisorders' : 'explorer'; + // this.emitVisibleItems(true); + // } + // } + + // public updatePhysicsEnabled(bool: boolean) { + // this.drugstoneConfig.config.physicsOn = bool; + // this.networkHandler.activeNetwork.networkInternal.setOptions({ + // physics: { + // enabled: this.drugstoneConfig.config.physicsOn, + // stabilization: { + // enabled: false, + // }, + // } + // }); + // } + + // public toImage() { + // this.downloadDom(this.networkWithLegendEl.nativeElement).catch(error => { + // console.error("Falling back to network only screenshot. Some components seem to be inaccessable, most likely the legend is a custom image with CORS access problems on the host server side.") + // this.downloadDom(this.networkHandler.activeNetwork.networkEl.nativeElement).catch(e => { + // console.error("Some network content seems to be inaccessable for saving as a screenshot. This can happen due to custom images used as nodes. Please ensure correct CORS accessability on the images host server.") + // console.error(e) + // }); + // }); + // } + + // public downloadDom(dom: object) { + // return domtoimage.toPng(dom, {bgcolor: '#ffffff'}).then((generatedImage) => { + // const a = document.createElement('a'); + // a.href = generatedImage; + // a.download = `Network.png`; + // a.click(); + // }); + // } public tableProteinSelection = (e): void => { const oldSelection = [...this.tableSelectedProteins]; @@ -728,84 +679,84 @@ export class AnalysisPanelComponent implements OnInit, OnChanges { } } - public selectTissue(tissue: Tissue | null) { - this.expressionExpanded = false; - if (!tissue) { - this.selectedTissue = null; - const updatedNodes = []; - for (const item of this.proteins) { - if (item.netexId === undefined) { - // nodes that are not mapped to backend remain untouched - continue; - } - const node: Node = this.nodeData.nodes.get(item.id); - if (!node) { - continue; - } - const pos = this.network.getPositions([item.id]); - node.x = pos[item.id].x; - node.y = pos[item.id].y; - const isSeed = this.highlightSeeds ? this.seedMap[node.id] : false; - Object.assign( - node, - NetworkSettings.getNodeStyle( - node, - this.myConfig, - isSeed, - this.analysis.inSelection(getWrapperFromNode(item)), - 1.0 - ) - ) - updatedNodes.push(node); - } - this.nodeData.nodes.update(updatedNodes); - // delete expression values - this.expressionMap = undefined; - this.gradientMap = {}; - } else { - this.selectedTissue = tissue - const minExp = 0.3; - // filter out non-proteins, e.g. drugs - const proteinNodes = []; - this.nodeData.nodes.forEach(element => { - if (element.id.startsWith('p') && element.netexId !== undefined) { - proteinNodes.push(element); - } - }); - this.netex.tissueExpressionGenes(this.selectedTissue, proteinNodes).subscribe((response) => { - this.expressionMap = response; - const updatedNodes = []; - // mapping from netex IDs to network IDs, TODO check if this step is necessary - const networkIdMappping = {} - this.nodeData.nodes.forEach(element => { - networkIdMappping[element.netexId] = element.id - }); - const maxExpr = Math.max(...Object.values(this.expressionMap)); - for (const [netexId, expressionlvl] of Object.entries(this.expressionMap)) { - const networkId = networkIdMappping[netexId] - const node = this.nodeData.nodes.get(networkId); - if (node === null) { - continue; - } - const wrapper = getWrapperFromNode(node) - this.gradientMap[netexId] = expressionlvl !== null ? (Math.pow(expressionlvl / maxExpr, 1 / 3) * (1 - minExp) + minExp) : -1; - const pos = this.network.getPositions([networkId]); - node.x = pos[networkId].x; - node.y = pos[networkId].y; - const isSeed = this.highlightSeeds ? this.seedMap[node.id] : false; - Object.assign(node, - NetworkSettings.getNodeStyle( - node, - this.myConfig, - isSeed, - this.analysis.inSelection(wrapper), - this.gradientMap[netexId])); - node.shape = 'custom'; - node.ctxRenderer = pieChartContextRenderer; - updatedNodes.push(node); - } - this.nodeData.nodes.update(updatedNodes); - }) - } - } + // public selectTissue(tissue: Tissue | null) { + // this.expressionExpanded = false; + // if (!tissue) { + // this.selectedTissue = null; + // const updatedNodes = []; + // for (const item of this.proteins) { + // if (item.netexId === undefined) { + // // nodes that are not mapped to backend remain untouched + // continue; + // } + // const node: Node = this.nodeData.nodes.get(item.id); + // if (!node) { + // continue; + // } + // const pos = this.networkHandler.activeNetwork.networkInternal.getPositions([item.id]); + // node.x = pos[item.id].x; + // node.y = pos[item.id].y; + // const isSeed = this.highlightSeeds ? this.seedMap[node.id] : false; + // Object.assign( + // node, + // NetworkSettings.getNodeStyle( + // node, + // this.myConfig, + // isSeed, + // this.analysis.inSelection(getWrapperFromNode(item)), + // 1.0 + // ) + // ) + // updatedNodes.push(node); + // } + // this.nodeData.nodes.update(updatedNodes); + // // delete expression values + // this.expressionMap = undefined; + // this.gradientMap = {}; + // } else { + // this.selectedTissue = tissue + // const minExp = 0.3; + // // filter out non-proteins, e.g. drugs + // const proteinNodes = []; + // this.nodeData.nodes.forEach(element => { + // if (element.id.startsWith('p') && element.netexId !== undefined) { + // proteinNodes.push(element); + // } + // }); + // this.netex.tissueExpressionGenes(this.selectedTissue, proteinNodes).subscribe((response) => { + // this.expressionMap = response; + // const updatedNodes = []; + // // mapping from netex IDs to network IDs, TODO check if this step is necessary + // const networkIdMappping = {} + // this.nodeData.nodes.forEach(element => { + // networkIdMappping[element.netexId] = element.id + // }); + // const maxExpr = Math.max(...Object.values(this.expressionMap)); + // for (const [netexId, expressionlvl] of Object.entries(this.expressionMap)) { + // const networkId = networkIdMappping[netexId] + // const node = this.nodeData.nodes.get(networkId); + // if (node === null) { + // continue; + // } + // const wrapper = getWrapperFromNode(node) + // this.gradientMap[netexId] = expressionlvl !== null ? (Math.pow(expressionlvl / maxExpr, 1 / 3) * (1 - minExp) + minExp) : -1; + // const pos = this.networkHandler.activeNetwork.networkInternal.getPositions([networkId]); + // node.x = pos[networkId].x; + // node.y = pos[networkId].y; + // const isSeed = this.highlightSeeds ? this.seedMap[node.id] : false; + // Object.assign(node, + // NetworkSettings.getNodeStyle( + // node, + // this.myConfig, + // isSeed, + // this.analysis.inSelection(wrapper), + // this.gradientMap[netexId])); + // node.shape = 'custom'; + // node.ctxRenderer = pieChartContextRenderer; + // updatedNodes.push(node); + // } + // this.nodeData.nodes.update(updatedNodes); + // }) + // } + // } } diff --git a/src/app/components/analysis-panel/drug-table/drug-table.component.html b/src/app/components/analysis-panel/drug-table/drug-table.component.html index 6b298ae5540ccade989ff1ba88c95dd793240f1e..f26a224e105340969668bf2feb55eae3a4281b9e 100644 --- a/src/app/components/analysis-panel/drug-table/drug-table.component.html +++ b/src/app/components/analysis-panel/drug-table/drug-table.component.html @@ -1,4 +1,4 @@ -<p-table *ngIf="tableDrugs.length > 0" [value]="tableDrugs" class="drgstn"> +<p-table styleClass="table is-hoverable is-narrow drgstn" *ngIf="tableDrugs.length > 0" [value]="tableDrugs" class=""> <ng-template pTemplate="header"> <tr> <th [pSortableColumn]="'drugId'"> @@ -23,10 +23,10 @@ </th> <th *ngIf="tableHasScores" [pSortableColumn]="'score'"> Score - <button class="button is-light has-tooltip tooltip-button" + <span class="has-tooltip tooltip-button has-text-info question-icon" [pTooltip]="tableDrugScoreTooltip" [tooltipStyleClass]="'drgstn drgstn-tooltip'" tooltipPosition="top"> <fa-icon [icon]="faQuestionCircle" class="icon"></fa-icon> - </button> + </span> <p-sortIcon [field]="'score'"></p-sortIcon> </th> </tr> @@ -40,7 +40,7 @@ <fa-icon [icon]="faCheck" [classes]="['icon']"></fa-icon> Approved </span> - <span *ngIf="e.status === 'unapproved'"> + <span *ngIf="e.status !== 'approved'"> <fa-icon [icon]="faTimes" [classes]="['icon']"></fa-icon> </span> </td> diff --git a/src/app/components/analysis-panel/prot-table/prot-table.component.html b/src/app/components/analysis-panel/prot-table/prot-table.component.html index bd642f8fd456f392d7821998cc7d3a288ee1e0be..c44cba8f5ce9147d66e1919cd5953c3a66c7c989 100644 --- a/src/app/components/analysis-panel/prot-table/prot-table.component.html +++ b/src/app/components/analysis-panel/prot-table/prot-table.component.html @@ -1,6 +1,6 @@ <p-table *ngIf="tableProteins.length > 0" selectionMode="multiple" [value]="tableProteins" [selection]="tableSelectedProteins" dataKey="uniprotAc" - (selectionChange)="tableProteinSelection($event)" class="drgstn"> + (selectionChange)="tableProteinSelection($event)" styleClass="table is-hoverable is-narrow drgstn"> <ng-template pTemplate="header"> <tr> <th class="checkbox-col"> @@ -20,10 +20,10 @@ </th> <th *ngIf="tableHasScores" [pSortableColumn]="'score'"> Score - <button class="button is-light has-tooltip tooltip-button" + <span class="question-icon has-text-info has-tooltip tooltip-button" [pTooltip]="tableProteinScoreTooltip" [tooltipStyleClass]="'drgstn drgstn-tooltip'" tooltipPosition="top"> <fa-icon [icon]="faQuestionCircle" class="icon"></fa-icon> - </button> + </span> <p-sortIcon [field]="'score'"></p-sortIcon> </th> <th [pSortableColumn]="'isSeed'"> diff --git a/src/app/components/network-legend/network-legend.component.scss b/src/app/components/network-legend/network-legend.component.scss index c090b6d97266f56c477839c29d532956f3a25998..4c8f20cb6cc9c43831d86115a6bda3933b376013 100644 --- a/src/app/components/network-legend/network-legend.component.scss +++ b/src/app/components/network-legend/network-legend.component.scss @@ -22,13 +22,15 @@ div.legend { z-index: $explorer-networklegend-z; // border size below legend margin-bottom: 2px; + transform-origin: bottom left; + zoom: $legend-scaling; + -moz-transform: scale($legend-scaling); &.right { right: 0; } &.legend-small { - transform-origin: bottom left; - zoom: 0.8; - -moz-transform: scale(0.8); + zoom: $legend-scaling-small; + -moz-transform: scale($legend-scaling-small); } table { z-index: $explorer-networklegend-foreground-z; diff --git a/src/app/components/network-legend/network-legend.component.ts b/src/app/components/network-legend/network-legend.component.ts index e75ed973daacbfa49d41bba1120310aded82a14c..cc42a71e400290e079953efd3272bf34216a3f04 100644 --- a/src/app/components/network-legend/network-legend.component.ts +++ b/src/app/components/network-legend/network-legend.component.ts @@ -1,5 +1,5 @@ import {Component, Input, OnInit} from '@angular/core'; -import { legendContext } from 'src/app/interfaces'; +import { LegendContext } from 'src/app/interfaces'; import { DrugstoneConfigService } from 'src/app/services/drugstone-config/drugstone-config.service'; import {IConfig} from '../../config'; @@ -10,7 +10,7 @@ import {IConfig} from '../../config'; }) export class NetworkLegendComponent implements OnInit { - @Input() context: legendContext; + @Input() context: LegendContext; @Input() config: IConfig; private contextNodeGroupsToDelete = { diff --git a/src/app/components/download-button/download-button.component.html b/src/app/components/network/network-menu-left/download-button-inverse/download-button-inverse.component.html similarity index 82% rename from src/app/components/download-button/download-button.component.html rename to src/app/components/network/network-menu-left/download-button-inverse/download-button-inverse.component.html index ea3069f9805894a108134c575038120d9f81daf1..016a063290230501342ba605cf7ca7ee929d631b 100644 --- a/src/app/components/download-button/download-button.component.html +++ b/src/app/components/network/network-menu-left/download-button-inverse/download-button-inverse.component.html @@ -1,16 +1,20 @@ -<div class="network-footer-toolbar-element"> - <div class="dropdown is-hoverable is-up"> +<div class="dropdown is-hoverable"> <div class="dropdown-trigger"> <button [id]="buttonId" class="button is-primary is-rounded has-tooltip" aria-haspopup="true" attr.aria-controls="{{ 'controls-' + buttonId }}" + [ngClass]="{ + 'is-small': drugstoneConfig.smallStyle + }" > + <span [ngClass]="{ 'text-normal': drugstoneConfig.smallStyle }" + >Download</span + > <span class="icon"> - <i class="fas fa-download" aria-hidden="true"></i> - </span> - <span [ngClass]="{ 'text-normal': drugstoneConfig.smallStyle }">Download</span> + <i class="fas fa-download" aria-hidden="true"></i> + </span> </button> </div> <div class="dropdown-menu" id="{{ 'controls-' + buttonId }}" role="menu"> @@ -38,4 +42,4 @@ </div> </div> </div> -</div> + \ No newline at end of file diff --git a/src/app/components/network/network-menu-left/download-button-inverse/download-button-inverse.component.scss b/src/app/components/network/network-menu-left/download-button-inverse/download-button-inverse.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..24324a65cd38f58f5368dc70565e896a56e15792 --- /dev/null +++ b/src/app/components/network/network-menu-left/download-button-inverse/download-button-inverse.component.scss @@ -0,0 +1,14 @@ +@import "src/stylesheets/variables"; + +.dropdown-menu{ + z-index: $network-tissue-options-z !important; + + .dropdown-content { + padding-left: 0.5rem; + padding-right: 0.5rem; + .dropdown-item { + padding: .375rem 0rem !important; + padding-left: 0.5rem !important; + } + } +} diff --git a/src/app/components/network/network-menu-left/download-button-inverse/download-button-inverse.component.spec.ts b/src/app/components/network/network-menu-left/download-button-inverse/download-button-inverse.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..88962fb50c1c116a8946f60bd01de0117c32f3cf --- /dev/null +++ b/src/app/components/network/network-menu-left/download-button-inverse/download-button-inverse.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DownloadButtonInverseComponent } from './download-button-inverse.component'; + +describe('DownloadButtonInverseComponent', () => { + let component: DownloadButtonInverseComponent; + let fixture: ComponentFixture<DownloadButtonInverseComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ DownloadButtonInverseComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DownloadButtonInverseComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/network/network-menu-left/download-button-inverse/download-button-inverse.component.ts b/src/app/components/network/network-menu-left/download-button-inverse/download-button-inverse.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..1bd4a31e997c72fdf2ddd4baeccd2cd028c9c789 --- /dev/null +++ b/src/app/components/network/network-menu-left/download-button-inverse/download-button-inverse.component.ts @@ -0,0 +1,28 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { DrugstoneConfigService } from 'src/app/services/drugstone-config/drugstone-config.service'; +import { NetexControllerService } from 'src/app/services/netex-controller/netex-controller.service'; +import { downLoadFile } from 'src/app/utils'; + +@Component({ + selector: 'app-download-button-inverse', + templateUrl: './download-button-inverse.component.html', + styleUrls: ['./download-button-inverse.component.scss'] +}) +export class DownloadButtonInverseComponent implements OnInit { + + @Input() nodeData: { nodes: any, edges: any } = {nodes: null, edges: null}; + @Input() buttonId: string; + + constructor(public drugstoneConfig: DrugstoneConfigService, public netex: NetexControllerService) { } + + ngOnInit(): void { + } + + public downloadLink(fmt) { + const data = {nodes: this.nodeData.nodes.get(), edges: this.nodeData.edges.get(), fmt: fmt}; + this.netex.graphExport(data).subscribe(response => { + return downLoadFile(response, `application/${fmt}`, fmt); + }); + } + +} \ No newline at end of file diff --git a/src/app/components/network/network-menu-left/network-menu-left.component.html b/src/app/components/network/network-menu-left/network-menu-left.component.html new file mode 100644 index 0000000000000000000000000000000000000000..e64e241a833cf66db4ec782d3382652fb3a2fc8d --- /dev/null +++ b/src/app/components/network/network-menu-left/network-menu-left.component.html @@ -0,0 +1,232 @@ +<div + class="network-footer-toolbar drgstn-box-shadow" + [ngClass]=" + networkHandler.activeNetwork.networkSidebarOpen ? 'opened' : 'closed' + " + [class.no-header]="networkHandler.activeNetwork.networkType === 'analysis'" + [class.small-sidebar]="drugstoneConfig.smallStyle" +> + <button + (click)="networkHandler.activeNetwork.toggleNetworkSidebar()" + class="button is-small is-primary network-toolbar-toggle" + [ngClass]="{ rotated: !networkHandler.activeNetwork.networkSidebarOpen }" + > + <i class="fas fa-angle-left"></i> + </button> + <div class="network-footer-toolbar-inner-container"> + <div class="rows"> + <div + class="row is-full m-1 is-pulled-right" + *ngIf="networkHandler.activeNetwork.networkType === 'analysis'" + > + <app-toggle-inplace-reversed + class="network-footer-toolbar-element" + text="Seeds" + tooltip="Highlight seed nodes." + [value]="networkHandler.activeNetwork.highlightSeeds" + (valueChange)=" + networkHandler.activeNetwork.updateHighlightSeeds($event) + " + icon="fas fa-seedling" + ></app-toggle-inplace-reversed> + </div> + <div + class="row is-full m-1 is-pulled-right" + *ngIf="drugstoneConfig.config.showNetworkMenuButtonScreenshot" + > + <div class="network-footer-toolbar-element"> + <button + (click)="networkHandler.activeNetwork.toImage()" + class="button is-primary is-rounded has-tooltip" + pTooltip="Take a screenshot of the current network." + [tooltipStyleClass]="'drgstn drgstn-tooltip'" + tooltipPosition="top" + [ngClass]="{ + 'is-small': drugstoneConfig.smallStyle + }" + > + <span + [ngClass]="{ + 'text-normal': drugstoneConfig.smallStyle + }" + >Screenshot</span + > + <span class="icon"> + <i class="fas fa-camera" aria-hidden="true"></i> + </span> + </button> + </div> + </div> + <div + class="row is-full m-1 is-pulled-right" + *ngIf="drugstoneConfig.config.showNetworkMenuButtonExportGraphml" + > + <div class="network-footer-toolbar-element"> + <app-download-button-inverse + [nodeData]="networkHandler.activeNetwork.nodeData" + [buttonId]="'explorer-download'" + ></app-download-button-inverse> + </div> + </div> + <div + class="row is-full m-1 is-pulled-right" + *ngIf="drugstoneConfig.config.showNetworkMenuButtonExpression" + > + <div + class="dropdown network-footer-toolbar-element" + [class.is-active]="networkHandler.activeNetwork.expressionExpanded" + > + <div class="dropdown-trigger"> + <button + (click)=" + networkHandler.activeNetwork.expressionExpanded = + !networkHandler.activeNetwork.expressionExpanded + " + class="button is-rounded is-primary" + [class.is-outlined]="!networkHandler.activeNetwork.selectedTissue" + aria-haspopup="true" + aria-controls="dropdown-menu" + pTooltip="Tissue expression data is provided by the GTEx project." + [tooltipStyleClass]="'drgstn drgstn-tooltip'" + tooltipPosition="top" + [ngClass]="{ + 'is-small': drugstoneConfig.smallStyle + }" + > + <span + *ngIf="networkHandler.activeNetwork.expressionExpanded" + class="icon is-small" + > + <i class="fas fa-angle-down" aria-hidden="true"></i> + </span> + <span + *ngIf="!networkHandler.activeNetwork.expressionExpanded" + class="icon is-small" + > + <i class="fas fa-angle-left rotated" aria-hidden="true"></i> + </span> + <span + *ngIf="!networkHandler.activeNetwork.selectedTissue" + [ngClass]="{ + 'text-small': drugstoneConfig.smallStyle + }" + >Tissue</span + > + <span + *ngIf="networkHandler.activeNetwork.selectedTissue" + [ngClass]="{ + 'text-small': drugstoneConfig.smallStyle + }" + >{{ networkHandler.activeNetwork.selectedTissue.name }}</span + > + <span class="icon is-small"> + <i class="fas fa-child"></i> + </span> + </button> + </div> + <div class="dropdown-menu" id="dropdown-menu" role="menu"> + <div class="dropdown-content tissue-dropdown"> + <div class="scroll-area"> + <a + (click)="networkHandler.activeNetwork.selectTissue(null)" + [class.is-active]=" + !networkHandler.activeNetwork.selectedTissue + " + class="dropdown-item" + > + None + </a> + <a + *ngFor=" + let tissue of networkHandler.activeNetwork.analysis.getTissues() + " + (click)="networkHandler.activeNetwork.selectTissue(tissue)" + [class.is-active]=" + networkHandler.activeNetwork.selectedTissue && + tissue.netexId === + networkHandler.activeNetwork.selectedTissue.netexId + " + class="dropdown-item" + > + {{ tissue.name }} + </a> + </div> + </div> + </div> + </div> + </div> + <div + class="row is-full m-1 is-pulled-right" + *ngIf="drugstoneConfig.config.showNetworkMenuButtonAdjacentDrugs" + > + <app-toggle-inplace-reversed + class="network-footer-toolbar-element" + text="{{ + drugstoneConfig.config.networkMenuButtonAdjacentDrugsLabel + }}" + icon="fas fa-capsules" + tooltip="Display adjacent drugs." + [value]="networkHandler.activeNetwork.adjacentDrugs" + (valueChange)=" + networkHandler.activeNetwork.updateAdjacentDrugs($event) + " + ></app-toggle-inplace-reversed> + </div> + <div + class="row is-full m-1 is-pulled-right" + *ngIf=" + drugstoneConfig.config.showNetworkMenuButtonAdjacentDisordersProteins + " + > + <app-toggle-inplace-reversed + class="network-footer-toolbar-element" + text="{{ + drugstoneConfig.config + .networkMenuButtonAdjacentDisordersProteinsLabel + }}" + tooltip="Show disorders adjacent to all currently displayed proteins/genes." + [value]="networkHandler.activeNetwork.adjacentDisordersProtein" + (valueChange)=" + networkHandler.activeNetwork.updateAdjacentProteinDisorders($event) + " + icon="fas fa-head-side-mask" + ></app-toggle-inplace-reversed> + </div> + <div + class="row is-full m-1 is-pulled-right" + *ngIf=" + drugstoneConfig.config.showNetworkMenuButtonAdjacentDisordersDrugs + " + > + <app-toggle-inplace-reversed + class="network-footer-toolbar-element" + text="{{ + drugstoneConfig.config.networkMenuButtonAdjacentDisordersDrugsLabel + }}" + tooltip="Show disorders adjacent to all currently displayed drugs." + [value]="networkHandler.activeNetwork.adjacentDisordersDrug" + [disabled]="!networkHandler.activeNetwork.hasDrugsLoaded()" + (valueChange)=" + networkHandler.activeNetwork.updateAdjacentDrugDisorders($event) + " + icon="fas fa-biohazard" + ></app-toggle-inplace-reversed> + </div> + <div + class="row is-full m-1 is-pulled-right" + *ngIf="drugstoneConfig.config.showNetworkMenuButtonAnimation" + > + <app-toggle-inplace-reversed + class="network-footer-toolbar-element" + text="{{ drugstoneConfig.config.networkMenuButtonAnimationLabel }}" + tooltip="Toggle the network animation." + [value]="drugstoneConfig.config.physicsOn" + (valueChange)=" + networkHandler.activeNetwork.updatePhysicsEnabled($event) + " + icon="fas fa-bullseye" + ></app-toggle-inplace-reversed> + </div> + </div> + </div> +</div> diff --git a/src/app/components/network/network-menu-left/network-menu-left.component.scss b/src/app/components/network/network-menu-left/network-menu-left.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..c26d9f642b2aa2c974afed27011c939d3af0aeba --- /dev/null +++ b/src/app/components/network/network-menu-left/network-menu-left.component.scss @@ -0,0 +1,101 @@ +@import "src/stylesheets/variables"; + +@keyframes hideSidebarLeft { + from { + left: 0; + } + to { + left: $network-footer-right-closed; + } +} + +@keyframes showSidebarLeft { + from { + left: $network-footer-right-closed; + } + to { + left: 0; + } +} + +@keyframes hideSidebarLeftSmall { + from { + left: 0; + } + to { + left: $network-footer-right-closed-small; + } +} + +@keyframes showSidebarLeftSmall { + from { + left: $network-footer-right-closed-small; + } + to { + left: 0; + } +} + +.network-footer-toolbar { + position: absolute; + height: calc(100% - #{$network-header-height}); + width: $network-footer-width; + top: $network-header-height; + background-color: var(--drgstn-panel-secondary); + z-index: $network-footer-container-z; + &.small-sidebar { + width: $network-footer-width-small; + } + &.no-header { + top: 0; + height: 100%; + } + &.opened { + left: 0; + animation-name: showSidebarLeft; + animation-duration: 1s; + &.small-sidebar { + animation-name: showSidebarLeftSmall; + } + } + &.closed { + left: $network-footer-right-closed; + animation-name: hideSidebarLeft; + animation-duration: 1s; + &.small-sidebar { + animation-name: hideSidebarLeftSmall; + left: $network-footer-right-closed-small; + } + } + &-inner-container { + width: 100%; + height: $network-footer-inner-container-height; + position: absolute; + top: 0; + } + + &-element { + position: relative; + margin-right: 10px; + } + + .network-toolbar-toggle { + z-index: $network-footer-container-toggle-z; + position: absolute !important; + top: calc(50% - 10px); + display: inline-block; + margin: 0px; + right: -20px; + + .dropdown-menu { + z-index: $network-tissue-options-z; + .scroll-area { + height: 15rem; + } + } + } +} + +.dropdown-menu{ + z-index: $network-tissue-options-z !important; +} \ No newline at end of file diff --git a/src/app/components/network/network-menu-left/network-menu-left.component.spec.ts b/src/app/components/network/network-menu-left/network-menu-left.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..bd35d8eb2984718171352b740515ac18b974a16d --- /dev/null +++ b/src/app/components/network/network-menu-left/network-menu-left.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NetworkMenuLeftComponent } from './network-menu-left.component'; + +describe('NetworkMenuLeftComponent', () => { + let component: NetworkMenuLeftComponent; + let fixture: ComponentFixture<NetworkMenuLeftComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ NetworkMenuLeftComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(NetworkMenuLeftComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/network/network-menu-left/network-menu-left.component.ts b/src/app/components/network/network-menu-left/network-menu-left.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..42f4c2aa35dd371918eef26c14f1746e6f6fe50d --- /dev/null +++ b/src/app/components/network/network-menu-left/network-menu-left.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit } from '@angular/core'; +import { DrugstoneConfigService } from 'src/app/services/drugstone-config/drugstone-config.service'; +import { NetworkHandlerService } from 'src/app/services/network-handler/network-handler.service'; + +@Component({ + selector: 'app-network-menu-left', + templateUrl: './network-menu-left.component.html', + styleUrls: ['./network-menu-left.component.scss'] +}) +export class NetworkMenuLeftComponent implements OnInit { + + constructor(public drugstoneConfig: DrugstoneConfigService, public networkHandler: NetworkHandlerService) { } + + ngOnInit(): void { + } + +} diff --git a/src/app/components/network/network-menu-left/toggle-inplace-reversed/toggle-inplace-reversed.component.html b/src/app/components/network/network-menu-left/toggle-inplace-reversed/toggle-inplace-reversed.component.html new file mode 100644 index 0000000000000000000000000000000000000000..e40f39af9f94faca67d4b0ba1cfb5db154e94b13 --- /dev/null +++ b/src/app/components/network/network-menu-left/toggle-inplace-reversed/toggle-inplace-reversed.component.html @@ -0,0 +1,24 @@ +<button + class="button is-rounded has-tooltip" + [pTooltip]="tooltip" + [tooltipStyleClass]="'drgstn drgstn-tooltip'" + tooltipPosition="right" + (click)="toggle()" + [ngClass]="{ + 'is-primary': value, + 'is-small': drugstoneConfig.smallStyle + }" +> + <span *ngIf="!value" class="icon is-small"> + <i class="fa {{ iconOff }}"></i> + </span> + <span *ngIf="value" class="icon is-small"> + <i class="fa {{ iconOn }}"></i> + </span> + <span [ngClass]="{ 'text-small': drugstoneConfig.smallStyle }">{{ + text + }}</span> + <span *ngIf="icon" class="icon is-small"> + <i class="{{ icon }}"></i> + </span> +</button> diff --git a/src/app/components/network-menu/network-menu.component.html b/src/app/components/network/network-menu-left/toggle-inplace-reversed/toggle-inplace-reversed.component.scss similarity index 100% rename from src/app/components/network-menu/network-menu.component.html rename to src/app/components/network/network-menu-left/toggle-inplace-reversed/toggle-inplace-reversed.component.scss diff --git a/src/app/components/network/network-menu-left/toggle-inplace-reversed/toggle-inplace-reversed.component.spec.ts b/src/app/components/network/network-menu-left/toggle-inplace-reversed/toggle-inplace-reversed.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..969ee65a7037fa9b93f7daa9014d00dd332b0a55 --- /dev/null +++ b/src/app/components/network/network-menu-left/toggle-inplace-reversed/toggle-inplace-reversed.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ToggleInplaceReversedComponent } from './toggle-inplace-reversed.component'; + +describe('ToggleInplaceReversedComponent', () => { + let component: ToggleInplaceReversedComponent; + let fixture: ComponentFixture<ToggleInplaceReversedComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ToggleInplaceReversedComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ToggleInplaceReversedComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/network/network-menu-left/toggle-inplace-reversed/toggle-inplace-reversed.component.ts b/src/app/components/network/network-menu-left/toggle-inplace-reversed/toggle-inplace-reversed.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..13f1f71fa9ee9e5e7c9feaa58b081f682b914b83 --- /dev/null +++ b/src/app/components/network/network-menu-left/toggle-inplace-reversed/toggle-inplace-reversed.component.ts @@ -0,0 +1,33 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { DrugstoneConfigService } from 'src/app/services/drugstone-config/drugstone-config.service'; + + +@Component({ + selector: 'app-toggle-inplace-reversed', + templateUrl: './toggle-inplace-reversed.component.html', + styleUrls: ['./toggle-inplace-reversed.component.scss'] +}) +export class ToggleInplaceReversedComponent implements OnInit { + + @Input() iconOn = 'fa-check'; + @Input() iconOff = 'fa-times'; + + @Input() text = 'Button'; + @Input() tooltip: string; + @Input() disabled = false; + @Input() icon: string; + + @Input() value: boolean; + @Output() valueChange = new EventEmitter<boolean>(); + + constructor(public drugstoneConfig: DrugstoneConfigService) { + } + + ngOnInit(): void { + } + + public toggle() { + this.value = !this.value; + this.valueChange.emit(this.value); + } +} diff --git a/src/app/components/network/network-menu/download-button/download-button.component.html b/src/app/components/network/network-menu/download-button/download-button.component.html new file mode 100644 index 0000000000000000000000000000000000000000..8754652cbfb75c95c15394440d8dc00c4650ec16 --- /dev/null +++ b/src/app/components/network/network-menu/download-button/download-button.component.html @@ -0,0 +1,44 @@ +<div class="dropdown is-hoverable"> + <div class="dropdown-trigger"> + <button + [id]="buttonId" + class="button is-primary is-rounded has-tooltip" + aria-haspopup="true" + attr.aria-controls="{{ 'controls-' + buttonId }}" + [ngClass]="{ + 'is-small': drugstoneConfig.smallStyle + }" + > + <span class="icon"> + <i class="fas fa-download" aria-hidden="true"></i> + </span> + <span [ngClass]="{ 'text-normal': drugstoneConfig.smallStyle }" + >Download</span + > + </button> + </div> + <div class="dropdown-menu" id="{{ 'controls-' + buttonId }}" role="menu"> + <div class="dropdown-content"> + <a + (click)="downloadLink('graphml')" + class="dropdown-item" + [ngClass]="{ 'text-normal': drugstoneConfig.smallStyle }" + >.graphml</a + > + <!-- <hr class="dropdown-divider" /> --> + <a + (click)="downloadLink('json')" + class="dropdown-item" + [ngClass]="{ 'text-normal': drugstoneConfig.smallStyle }" + >.json</a + > + <!-- <hr class="dropdown-divider" /> --> + <a + (click)="downloadLink('csv')" + class="dropdown-item" + [ngClass]="{ 'text-normal': drugstoneConfig.smallStyle }" + >.csv</a + > + </div> + </div> +</div> diff --git a/src/app/components/download-button/download-button.component.scss b/src/app/components/network/network-menu/download-button/download-button.component.scss similarity index 81% rename from src/app/components/download-button/download-button.component.scss rename to src/app/components/network/network-menu/download-button/download-button.component.scss index 509b92cb622d82d2cd5db4b6693c89c38836d981..56db66d253c3b5691738f753fe7c309192d4f02a 100644 --- a/src/app/components/download-button/download-button.component.scss +++ b/src/app/components/network/network-menu/download-button/download-button.component.scss @@ -1,8 +1,8 @@ @import "src/stylesheets/variables"; .dropdown-menu{ - z-index: $analysis-network-toolbar-dropdown-z !important; - + z-index: $network-tissue-options-z !important; + .dropdown-content { padding-left: 0.5rem; padding-right: 0.5rem; diff --git a/src/app/components/download-button/download-button.component.spec.ts b/src/app/components/network/network-menu/download-button/download-button.component.spec.ts similarity index 100% rename from src/app/components/download-button/download-button.component.spec.ts rename to src/app/components/network/network-menu/download-button/download-button.component.spec.ts diff --git a/src/app/components/download-button/download-button.component.ts b/src/app/components/network/network-menu/download-button/download-button.component.ts similarity index 100% rename from src/app/components/download-button/download-button.component.ts rename to src/app/components/network/network-menu/download-button/download-button.component.ts diff --git a/src/app/components/network/network-menu/network-menu.component.html b/src/app/components/network/network-menu/network-menu.component.html new file mode 100644 index 0000000000000000000000000000000000000000..0d3d9b439d98c92b7d8746a950e4da56b021a9b0 --- /dev/null +++ b/src/app/components/network/network-menu/network-menu.component.html @@ -0,0 +1,232 @@ +<div + class="network-footer-toolbar drgstn-box-shadow" + [ngClass]=" + networkHandler.activeNetwork.networkSidebarOpen ? 'opened' : 'closed' + " + [class.no-header]="networkHandler.activeNetwork.networkType === 'analysis'" + [class.small-sidebar]="drugstoneConfig.smallStyle" +> + <button + (click)="networkHandler.activeNetwork.toggleNetworkSidebar()" + class="button is-small is-primary network-toolbar-toggle" + [ngClass]="{ rotated: networkHandler.activeNetwork.networkSidebarOpen }" + > + <i class="fas fa-angle-left"></i> + </button> + <div class="network-footer-toolbar-inner-container"> + <div class="rows"> + <div + class="row is-full m-1" + *ngIf="networkHandler.activeNetwork.networkType === 'analysis'" + > + <app-toggle-inplace + class="network-footer-toolbar-element" + text="Seeds" + tooltip="Highlight seed nodes." + [value]="networkHandler.activeNetwork.highlightSeeds" + (valueChange)=" + networkHandler.activeNetwork.updateHighlightSeeds($event) + " + icon="fas fa-seedling" + ></app-toggle-inplace> + </div> + <div + class="row is-full m-1" + *ngIf="drugstoneConfig.config.showNetworkMenuButtonScreenshot" + > + <div class="network-footer-toolbar-element"> + <button + (click)="networkHandler.activeNetwork.toImage()" + class="button is-primary is-rounded has-tooltip" + pTooltip="Take a screenshot of the current network." + [tooltipStyleClass]="'drgstn drgstn-tooltip'" + tooltipPosition="top" + [ngClass]="{ + 'is-small': drugstoneConfig.smallStyle + }" + > + <span class="icon"> + <i class="fas fa-camera" aria-hidden="true"></i> + </span> + <span + [ngClass]="{ + 'text-normal': drugstoneConfig.smallStyle + }" + >Screenshot</span + > + </button> + </div> + </div> + <div + class="row is-full m-1" + *ngIf="drugstoneConfig.config.showNetworkMenuButtonExportGraphml" + > + <div class="network-footer-toolbar-element"> + <app-download-button + [nodeData]="networkHandler.activeNetwork.nodeData" + [buttonId]="'explorer-download'" + ></app-download-button> + </div> + </div> + <div + class="row is-full m-1" + *ngIf="drugstoneConfig.config.showNetworkMenuButtonExpression" + > + <div + class="dropdown network-footer-toolbar-element" + [class.is-active]="networkHandler.activeNetwork.expressionExpanded" + > + <div class="dropdown-trigger"> + <button + (click)=" + networkHandler.activeNetwork.expressionExpanded = + !networkHandler.activeNetwork.expressionExpanded + " + class="button is-rounded is-primary" + [class.is-outlined]="!networkHandler.activeNetwork.selectedTissue" + aria-haspopup="true" + aria-controls="dropdown-menu" + pTooltip="Tissue expression data is provided by the GTEx project." + [tooltipStyleClass]="'drgstn drgstn-tooltip'" + tooltipPosition="top" + [ngClass]="{ + 'is-small': drugstoneConfig.smallStyle + }" + > + <span class="icon is-small"> + <i class="fas fa-child"></i> + </span> + <span + *ngIf="!networkHandler.activeNetwork.selectedTissue" + [ngClass]="{ + 'text-small': drugstoneConfig.smallStyle + }" + >Tissue</span + > + <span + *ngIf="networkHandler.activeNetwork.selectedTissue" + [ngClass]="{ + 'text-small': drugstoneConfig.smallStyle + }" + >{{ networkHandler.activeNetwork.selectedTissue.name }}</span + > + <span + *ngIf="networkHandler.activeNetwork.expressionExpanded" + class="icon is-small" + > + <i class="fas fa-angle-down" aria-hidden="true"></i> + </span> + <span + *ngIf="!networkHandler.activeNetwork.expressionExpanded" + class="icon is-small" + > + <i class="fas fa-angle-left" aria-hidden="true"></i> + </span> + </button> + </div> + <div class="dropdown-menu" id="dropdown-menu" role="menu"> + <div class="dropdown-content tissue-dropdown"> + <div class="scroll-area"> + <a + (click)="networkHandler.activeNetwork.selectTissue(null)" + [class.is-active]=" + !networkHandler.activeNetwork.selectedTissue + " + class="dropdown-item" + > + None + </a> + <a + *ngFor=" + let tissue of networkHandler.activeNetwork.analysis.getTissues() + " + (click)="networkHandler.activeNetwork.selectTissue(tissue)" + [class.is-active]=" + networkHandler.activeNetwork.selectedTissue && + tissue.netexId === + networkHandler.activeNetwork.selectedTissue.netexId + " + class="dropdown-item" + > + {{ tissue.name }} + </a> + </div> + </div> + </div> + </div> + </div> + <div + class="row is-full m-1" + *ngIf="drugstoneConfig.config.showNetworkMenuButtonAdjacentDrugs" + > + <app-toggle-inplace + class="network-footer-toolbar-element" + text="{{ + drugstoneConfig.config.networkMenuButtonAdjacentDrugsLabel + }}" + icon="fas fa-capsules" + tooltip="Display adjacent drugs." + [value]="networkHandler.activeNetwork.adjacentDrugs" + (valueChange)=" + networkHandler.activeNetwork.updateAdjacentDrugs($event) + " + ></app-toggle-inplace> + </div> + <div + class="row is-full m-1" + *ngIf=" + drugstoneConfig.config.showNetworkMenuButtonAdjacentDisordersProteins + " + > + <app-toggle-inplace + class="network-footer-toolbar-element" + text="{{ + drugstoneConfig.config + .networkMenuButtonAdjacentDisordersProteinsLabel + }}" + tooltip="Show disorders adjacent to all currently displayed proteins/genes." + [value]="networkHandler.activeNetwork.adjacentDisordersProtein" + (valueChange)=" + networkHandler.activeNetwork.updateAdjacentProteinDisorders($event) + " + icon="fas fa-head-side-mask" + ></app-toggle-inplace> + </div> + <div + class="row is-full m-1" + *ngIf=" + drugstoneConfig.config.showNetworkMenuButtonAdjacentDisordersDrugs + " + > + <app-toggle-inplace + class="network-footer-toolbar-element" + text="{{ + drugstoneConfig.config.networkMenuButtonAdjacentDisordersDrugsLabel + }}" + tooltip="Show disorders adjacent to all currently displayed drugs." + [value]="networkHandler.activeNetwork.adjacentDisordersDrug" + [disabled]="!networkHandler.activeNetwork.hasDrugsLoaded()" + (valueChange)=" + networkHandler.activeNetwork.updateAdjacentDrugDisorders($event) + " + icon="fas fa-biohazard" + ></app-toggle-inplace> + </div> + <div + class="row is-full m-1" + *ngIf="drugstoneConfig.config.showNetworkMenuButtonAnimation" + > + <app-toggle-inplace + class="network-footer-toolbar-element" + text="{{ drugstoneConfig.config.networkMenuButtonAnimationLabel }}" + tooltip="Toggle the network animation." + [value]="drugstoneConfig.config.physicsOn" + (valueChange)=" + networkHandler.activeNetwork.updatePhysicsEnabled($event) + " + icon="fas fa-bullseye" + ></app-toggle-inplace> + </div> + </div> + </div> +</div> diff --git a/src/app/components/network/network-menu/network-menu.component.scss b/src/app/components/network/network-menu/network-menu.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..30b82ad763d8cea580c27e42007d412be28361ae --- /dev/null +++ b/src/app/components/network/network-menu/network-menu.component.scss @@ -0,0 +1,101 @@ +@import "src/stylesheets/variables"; + +@keyframes hideSidebar { + from { + right: 0; + } + to { + right: $network-footer-right-closed; + } +} + +@keyframes showSidebar { + from { + right: $network-footer-right-closed; + } + to { + right: 0; + } +} + +@keyframes hideSidebarSmall { + from { + right: 0; + } + to { + right: $network-footer-right-closed-small; + } +} + +@keyframes showSidebarSmall { + from { + right: $network-footer-right-closed-small; + } + to { + right: 0; + } +} + +.network-footer-toolbar { + position: absolute; + height: calc(100% - #{$network-header-height}); + width: $network-footer-width; + top: $network-header-height; + background-color: var(--drgstn-panel-secondary); + z-index: $network-footer-container-z; + &.small-sidebar { + width: $network-footer-width-small; + } + &.no-header { + top: 0; + height: 100%; + } + &.opened { + right: 0; + animation-name: showSidebar; + animation-duration: 1s; + &.small-sidebar { + animation-name: showSidebarSmall; + } + } + &.closed { + right: $network-footer-right-closed; + animation-name: hideSidebar; + animation-duration: 1s; + &.small-sidebar { + animation-name: hideSidebarSmall; + right: $network-footer-right-closed-small; + } + } + &-inner-container { + width: 100%; + height: $network-footer-inner-container-height; + position: absolute; + top: 0; + } + + &-element { + position: relative; + margin-left: 10px; + } + + .network-toolbar-toggle { + z-index: $network-footer-container-toggle-z; + position: relative; + top: calc(50% - 10px); + display: inline-block; + margin: 0px; + left: -20px; + &.rotated { + -ms-transform: rotate(180deg); /* IE 9 */ + transform: rotate(180deg); + } + .dropdown-menu { + z-index: $network-tissue-options-z; + + .scroll-area { + height: 15rem; + } + } + } +} diff --git a/src/app/components/network-menu/network-menu.component.spec.ts b/src/app/components/network/network-menu/network-menu.component.spec.ts similarity index 100% rename from src/app/components/network-menu/network-menu.component.spec.ts rename to src/app/components/network/network-menu/network-menu.component.spec.ts diff --git a/src/app/components/network-menu/network-menu.component.ts b/src/app/components/network/network-menu/network-menu.component.ts similarity index 55% rename from src/app/components/network-menu/network-menu.component.ts rename to src/app/components/network/network-menu/network-menu.component.ts index b765b74df0807aface422ae5b987c37cc6d4278b..24e9f36ec58a01ae50d0b21984eda33c5655f527 100644 --- a/src/app/components/network-menu/network-menu.component.ts +++ b/src/app/components/network/network-menu/network-menu.component.ts @@ -1,5 +1,6 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { DrugstoneConfigService } from 'src/app/services/drugstone-config/drugstone-config.service'; +import { NetworkHandlerService } from 'src/app/services/network-handler/network-handler.service'; @Component({ selector: 'app-network-menu', @@ -8,10 +9,7 @@ import { DrugstoneConfigService } from 'src/app/services/drugstone-config/drugst }) export class NetworkMenuComponent implements OnInit { - constructor(public drugstoneConfig: DrugstoneConfigService) { } - - @Input() networkContext: any; - + constructor(public drugstoneConfig: DrugstoneConfigService, public networkHandler: NetworkHandlerService) { } ngOnInit(): void { } diff --git a/src/app/components/network/network-menu/toggle-inplace/toggle-inplace.component.html b/src/app/components/network/network-menu/toggle-inplace/toggle-inplace.component.html new file mode 100644 index 0000000000000000000000000000000000000000..a15950dee6784d3d7145a63837534196dbcd7d13 --- /dev/null +++ b/src/app/components/network/network-menu/toggle-inplace/toggle-inplace.component.html @@ -0,0 +1,24 @@ +<button + class="button is-rounded has-tooltip" + [pTooltip]="tooltip" + [tooltipStyleClass]="'drgstn drgstn-tooltip'" + tooltipPosition="left" + (click)="toggle()" + [ngClass]="{ + 'is-primary': value, + 'is-small': drugstoneConfig.smallStyle + }" +> + <span *ngIf="icon" class="icon is-small"> + <i class="{{ icon }}"></i> + </span> + <span [ngClass]="{ 'text-small': drugstoneConfig.smallStyle }">{{ + text + }}</span> + <span *ngIf="!value" class="icon is-small"> + <i class="fa {{ iconOff }}"></i> + </span> + <span *ngIf="value" class="icon is-small"> + <i class="fa {{ iconOn }}"></i> + </span> +</button> \ No newline at end of file diff --git a/src/app/components/network-menu/network-menu.component.scss b/src/app/components/network/network-menu/toggle-inplace/toggle-inplace.component.scss similarity index 100% rename from src/app/components/network-menu/network-menu.component.scss rename to src/app/components/network/network-menu/toggle-inplace/toggle-inplace.component.scss diff --git a/src/app/components/network/network-menu/toggle-inplace/toggle-inplace.component.spec.ts b/src/app/components/network/network-menu/toggle-inplace/toggle-inplace.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..755ba6979e0bfe290f77a90cbfb8bcd6a5ee374d --- /dev/null +++ b/src/app/components/network/network-menu/toggle-inplace/toggle-inplace.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ToggleInplaceComponent } from './toggle-inplace.component'; + +describe('ToggleInplaceComponent', () => { + let component: ToggleInplaceComponent; + let fixture: ComponentFixture<ToggleInplaceComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ToggleInplaceComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ToggleInplaceComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/network/network-menu/toggle-inplace/toggle-inplace.component.ts b/src/app/components/network/network-menu/toggle-inplace/toggle-inplace.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..ffd5daabb7a37bdf892017ccceece1f32f1af24b --- /dev/null +++ b/src/app/components/network/network-menu/toggle-inplace/toggle-inplace.component.ts @@ -0,0 +1,34 @@ +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import { DrugstoneConfigService } from 'src/app/services/drugstone-config/drugstone-config.service'; + +@Component({ + selector: 'app-toggle-inplace', + templateUrl: './toggle-inplace.component.html', + styleUrls: ['./toggle-inplace.component.scss'] +}) +export class ToggleInplaceComponent implements OnInit { + + @Input() iconOn = 'fa-check'; + @Input() iconOff = 'fa-times'; + + @Input() text = 'Button'; + @Input() tooltip: string; + @Input() disabled = false; + @Input() icon: string; + + @Input() value: boolean; + @Output() valueChange = new EventEmitter<boolean>(); + + constructor(public drugstoneConfig: DrugstoneConfigService) { + } + + ngOnInit(): void { + } + + public toggle() { + this.value = !this.value; + this.valueChange.emit(this.value); + } + + +} diff --git a/src/app/components/network/network.component.html b/src/app/components/network/network.component.html new file mode 100644 index 0000000000000000000000000000000000000000..d2ac6fec9f3d7f2884f6704aae3790ed1fc96f0f --- /dev/null +++ b/src/app/components/network/network.component.html @@ -0,0 +1,39 @@ +<div class="card network"> + <header class="card-header network-header" *ngIf="networkType === 'explorer'"> + <p class="card-header-title"> + {{ drugstoneConfig.config.title }} + </p> + </header> + <div + class="card-content network-view-settings" + [class.analysis]="networkType === 'analysis'" + > + <div class="card-image canvas-content" #networkWithLegend> + <div *ngIf="drugstoneConfig.config.showLegend"> + <app-network-legend + [config]="drugstoneConfig.config" + [context]="legendContext" + ></app-network-legend> + </div> + <div class="center image1 fullheight" #network> + <button class="button is-loading center" alt="loading..."> + Loading + </button> + </div> + </div> + + <app-network-menu + *ngIf=" + drugstoneConfig.config.showNetworkMenu && + drugstoneConfig.config.showNetworkMenu === 'right' + " + ></app-network-menu> + + <app-network-menu-left + *ngIf=" + drugstoneConfig.config.showNetworkMenu && + drugstoneConfig.config.showNetworkMenu === 'left' + " + ></app-network-menu-left> + </div> +</div> diff --git a/src/app/components/network/network.component.scss b/src/app/components/network/network.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..e7d0a467d7e22c3f75314f74bc9942e3db1fffb0 --- /dev/null +++ b/src/app/components/network/network.component.scss @@ -0,0 +1,15 @@ +@import "src/stylesheets/variables"; + +.network-view-settings { + // remove margin from tab header when network is displayed, so that network + // does not disappear in empty border + padding: 0 !important; + height: calc(100% - #{$network-header-height}); + &.analysis { + height: calc(100% - 0.5rem); + } +} + +.dropdown-menu{ + z-index: $network-tissue-options-z !important; +} diff --git a/src/app/components/network/network.component.spec.ts b/src/app/components/network/network.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..e59b17ea400e2c151c90de726dd62e08472d3328 --- /dev/null +++ b/src/app/components/network/network.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NetworkComponent } from './network.component'; + +describe('NetworkComponent', () => { + let component: NetworkComponent; + let fixture: ComponentFixture<NetworkComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ NetworkComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(NetworkComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/network/network.component.ts b/src/app/components/network/network.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..40127ca80c685782fcf111bb6fbadf3b17746069 --- /dev/null +++ b/src/app/components/network/network.component.ts @@ -0,0 +1,403 @@ +import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; +import domtoimage from 'dom-to-image'; +import { InteractionDatabase } from 'src/app/config'; +import { DrugstoneConfigService } from 'src/app/services/drugstone-config/drugstone-config.service'; +import { NetexControllerService } from 'src/app/services/netex-controller/netex-controller.service'; +import { OmnipathControllerService } from 'src/app/services/omnipath-controller/omnipath-controller.service'; +import { mapCustomEdge, mapCustomNode, mapNetexEdge, ProteinNetwork } from '../../main-network'; +import { + getDrugNodeId, + getWrapperFromNode, + LegendContext, + Node, + NodeData, + NodeAttributeMap, + NodeInteraction, + Tissue, + Wrapper, + NetworkType +} from '../../interfaces'; +import { AnalysisService } from 'src/app/services/analysis/analysis.service'; +import { NetworkSettings } from 'src/app/network-settings'; +import { pieChartContextRenderer } from 'src/app/utils'; +import { NetworkHandlerService } from 'src/app/services/network-handler/network-handler.service'; + + +@Component({ + selector: 'app-network', + templateUrl: './network.component.html', + styleUrls: ['./network.component.scss'] +}) +export class NetworkComponent implements OnInit { + + @Input() public networkType: NetworkType; + @Input() public nodeData: NodeData; + @Input() public legendContext: LegendContext; + + + @ViewChild('network', { static: false }) networkEl: ElementRef; + @ViewChild('networkWithLegend', { static: false }) networkWithLegendEl: ElementRef; + + public networkInternal: any = null; + + public selectedWrapper: Wrapper | null = null; + + public adjacentDrugs = false; + + public adjacentDisordersProtein = false; + public adjacentDisordersDrug = false; + public adjacentDrugList: Node[] = []; + public adjacentDrugEdgesList: Node[] = []; + public adjacentProteinDisorderList: Node[] = []; + public adjacentProteinDisorderEdgesList: Node[] = []; + public adjacentDrugDisorderList: Node[] = []; + public adjacentDrugDisorderEdgesList: Node[] = []; + + public currentDataset = []; + + public currentViewProteins: Node[] = []; + public currentViewSelectedTissue: Tissue | null = null; + public currentViewNodes: Node[] = []; + public currentViewEdges: NodeInteraction[]; + + public expressionExpanded = false; + public selectedTissue: Tissue | null = null; + + // change this to true to have sidebar open per default + public networkSidebarOpen = false; + + public queryItems: Wrapper[] = []; + + public networkPositions: any; + + public highlightSeeds = false; + public seedMap: NodeAttributeMap = {}; + + // keys are node netexIds + public expressionMap: NodeAttributeMap = {}; + public gradientMap: NodeAttributeMap = {}; + + + constructor(public networkHandler: NetworkHandlerService, public analysis: AnalysisService, public drugstoneConfig: DrugstoneConfigService, public netex: NetexControllerService, public omnipath: OmnipathControllerService) { } + + ngOnInit(): void { + this.networkHandler.networks[this.networkType] = this; + } + + async getInteractions(key: InteractionDatabase) { + let edges = []; + if (key == 'omnipath') { + const names = this.nodeData.nodes.map((node) => node.label); + const nameToNetworkId = {}; + this.nodeData.nodes.map((node) => nameToNetworkId[node.label] = node.id); + edges = await this.omnipath.getInteractions(names, this.drugstoneConfig.config.identifier, nameToNetworkId); + } + this.nodeData.edges.update(edges); + } + + updateQueryItems() { + this.queryItems = []; + this.currentViewNodes.forEach((protein) => { + this.queryItems.push(getWrapperFromNode(protein)); + }); + } + + public saveAddNodes(nodeList: Node[]) { + const existing = this.nodeData.nodes.get().map(n => n.id); + const toAdd = nodeList.filter(n => existing.indexOf(n.id) === -1) + this.nodeData.nodes.add(toAdd); + } + + public updateAdjacentProteinDisorders(bool: boolean) { + this.adjacentDisordersProtein = bool; + if (this.adjacentDisordersProtein) { + this.netex.adjacentDisorders(this.nodeData.nodes, 'proteins').subscribe(response => { + for (const interaction of response.edges) { + const edge = { from: interaction.protein, to: interaction.disorder }; + this.adjacentProteinDisorderEdgesList.push(mapCustomEdge(edge, this.drugstoneConfig.config)); + } + for (const disorder of response.disorders) { + disorder.group = 'defaultDisorder'; + disorder.id = disorder.netexId; + this.adjacentProteinDisorderList.push(mapCustomNode(disorder, this.drugstoneConfig.config)) + } + this.saveAddNodes(this.adjacentProteinDisorderList); + this.nodeData.edges.add(this.adjacentProteinDisorderEdgesList); + this.updateQueryItems(); + }); + this.legendContext = this.adjacentDrugs ? 'adjacentDrugsAndDisorders' : 'adjacentDisorders'; + } else { + this.saveRemoveDisorders(this.adjacentProteinDisorderList); + this.nodeData.edges.remove(this.adjacentProteinDisorderEdgesList); + this.adjacentProteinDisorderList = []; + this.adjacentProteinDisorderEdgesList = []; + this.legendContext = this.adjacentDisordersDrug ? this.legendContext : this.adjacentDrugs ? 'adjacentDrugs' : 'explorer'; + this.updateQueryItems(); + } + } + + public updateAdjacentDrugDisorders(bool: boolean) { + this.adjacentDisordersDrug = bool; + if (this.adjacentDisordersDrug) { + this.netex.adjacentDisorders(this.nodeData.nodes, 'drugs').subscribe(response => { + for (const interaction of response.edges) { + const edge = { from: interaction.drug, to: interaction.disorder }; + this.adjacentDrugDisorderEdgesList.push(mapCustomEdge(edge, this.drugstoneConfig.config)); + } + for (const disorder of response.disorders) { + disorder.group = 'defaultDisorder'; + disorder.id = disorder.netexId; + this.adjacentDrugDisorderList.push(mapCustomNode(disorder, this.drugstoneConfig.config)); + } + this.saveAddNodes(this.adjacentDrugDisorderList); + this.nodeData.edges.add(this.adjacentDrugDisorderEdgesList); + this.updateQueryItems(); + }); + this.legendContext = this.adjacentDrugs ? 'adjacentDrugsAndDisorders' : 'adjacentDisorders'; + } else { + this.saveRemoveDisorders(this.adjacentDrugDisorderList); + this.nodeData.edges.remove(this.adjacentDrugDisorderEdgesList); + this.adjacentDrugDisorderList = []; + this.adjacentDrugDisorderEdgesList = []; + this.legendContext = this.adjacentDisordersProtein ? this.legendContext : this.adjacentDrugs ? 'adjacentDrugs' : 'explorer'; + this.updateQueryItems(); + } + } + + public updateAdjacentDrugs(bool: boolean) { + this.adjacentDrugs = bool; + if (this.adjacentDrugs) { + this.netex.adjacentDrugs(this.drugstoneConfig.config.interactionDrugProtein, this.nodeData.nodes).subscribe(response => { + for (const interaction of response.pdis) { + const edge = { from: interaction.protein, to: interaction.drug }; + this.adjacentDrugEdgesList.push(mapCustomEdge(edge, this.drugstoneConfig.config)); + } + for (const drug of response.drugs) { + drug.group = 'foundDrug'; + drug.id = getDrugNodeId(drug) + this.adjacentDrugList.push(mapCustomNode(drug, this.drugstoneConfig.config)) + } + this.nodeData.nodes.add(this.adjacentDrugList); + this.nodeData.edges.add(this.adjacentDrugEdgesList); + this.updateQueryItems(); + }) + this.legendContext = this.adjacentDisordersDrug || this.adjacentDisordersProtein ? 'adjacentDrugsAndDisorders' : 'adjacentDrugs'; + } else { + this.nodeData.nodes.remove(this.adjacentDrugList); + this.nodeData.edges.remove(this.adjacentDrugEdgesList); + this.adjacentDrugList = []; + this.adjacentDrugEdgesList = []; + + this.legendContext = this.adjacentDisordersDrug || this.adjacentDisordersProtein ? 'adjacentDisorders' : 'explorer'; + this.updateQueryItems(); + } + } + + public saveRemoveDisorders(nodeList: Node[]) { + const other = this.adjacentDrugDisorderList === nodeList ? this.adjacentProteinDisorderList : this.adjacentDrugDisorderList + if (other == null) + this.nodeData.nodes.remove(nodeList); + else { + const otherIds = other.map(d => d.id); + const rest = nodeList.filter(d => otherIds.indexOf(d.id) === -1) + this.nodeData.nodes.remove(rest) + } + } + + public toImage() { + this.downloadDom(this.networkWithLegendEl.nativeElement).catch(error => { + console.error('Falling back to network only screenshot. Some components seem to be inaccessable, most likely the legend is a custom image with CORS access problems on the host server side.'); + this.downloadDom(this.networkEl.nativeElement).catch(e => { + console.error('Some network content seems to be inaccessable for saving as a screenshot. This can happen due to custom images used as nodes. Please ensure correct CORS accessability on the images host server.'); + console.error(e); + }); + }); + } + + public downloadDom(dom: object) { + return domtoimage.toPng(dom, { bgcolor: '#ffffff' }).then((generatedImage) => { + const a = document.createElement('a'); + a.href = generatedImage; + a.download = `Network.png`; + a.click(); + }); + } + + public updatePhysicsEnabled(bool: boolean) { + this.drugstoneConfig.config.physicsOn = bool; + this.networkInternal.setOptions({ + physics: { + enabled: this.drugstoneConfig.config.physicsOn, + stabilization: { + enabled: false, + }, + } + }); + } + + public zoomToNode(id: string) { + // get network object, depending on whether analysis is open or not + this.nodeData.nodes.getIds(); + const coords = this.networkInternal.getPositions(id)[id]; + if (!coords) { + return; + } + let zoomScale = null; + if (id.startsWith('eff')) { + zoomScale = 1.0; + } else { + zoomScale = 3.0; + } + this.networkInternal.moveTo({ + position: { x: coords.x, y: coords.y }, + scale: zoomScale, + animation: true, + }); + } + + toggleNetworkSidebar() { + this.networkSidebarOpen = !this.networkSidebarOpen; + } + + public selectTissue(tissue: Tissue | null) { + this.expressionExpanded = false; + if (!tissue) { + this.selectedTissue = null; + const updatedNodes = []; + // for (const item of this.proteins) { + for (const item of this.currentViewProteins) { + if (item.netexId === undefined) { + // nodes that are not mapped to backend remain untouched + continue; + } + const node: Node = this.nodeData.nodes.get(item.id); + if (!node) { + continue; + } + const pos = this.networkInternal.getPositions([item.id]); + node.x = pos[item.id].x; + node.y = pos[item.id].y; + Object.assign( + node, + NetworkSettings.getNodeStyle( + node, + this.drugstoneConfig.config, + false, + this.analysis.inSelection(getWrapperFromNode(item)), + 1.0 + ) + ) + updatedNodes.push(node); + } + this.nodeData.nodes.update(updatedNodes); + // delete expression values + this.expressionMap = undefined; + } else { + this.selectedTissue = tissue + const minExp = 0.3; + // filter out non-proteins, e.g. drugs + const proteinNodes = []; + this.nodeData.nodes.forEach(element => { + if (element.id.startsWith('p') && element.netexId !== undefined) { + proteinNodes.push(element); + } + }); + this.netex.tissueExpressionGenes(this.selectedTissue, proteinNodes).subscribe((response) => { + this.expressionMap = response; + const updatedNodes = []; + // mapping from netex IDs to network IDs, TODO check if this step is necessary + const networkIdMappping = {} + this.nodeData.nodes.forEach(element => { + networkIdMappping[element.netexId] = element.id + }); + const maxExpr = Math.max(...Object.values(this.expressionMap)); + for (const [netexId, expressionlvl] of Object.entries(this.expressionMap)) { + const networkId = networkIdMappping[netexId] + const node = this.nodeData.nodes.get(networkId); + if (node === null) { + continue; + } + const wrapper = getWrapperFromNode(node) + const gradient = expressionlvl !== null ? (Math.pow(expressionlvl / maxExpr, 1 / 3) * (1 - minExp) + minExp) : -1; + const pos = this.networkInternal.getPositions([networkId]); + node.x = pos[networkId].x; + node.y = pos[networkId].y; + Object.assign(node, + NetworkSettings.getNodeStyle( + node, + this.drugstoneConfig.config, + node.isSeed, + this.analysis.inSelection(wrapper), + gradient)); + + // try out custom ctx renderer + node.shape = 'custom'; + node.ctxRenderer = pieChartContextRenderer; + updatedNodes.push(node); + } + this.nodeData.nodes.update(updatedNodes); + }) + } + this.currentViewSelectedTissue = this.selectedTissue; + } + + public hasDrugsLoaded(): boolean { + if (this.nodeData == null || this.nodeData.nodes == null) + return false; + return this.nodeData.nodes.get().filter((node: Node) => node.drugId && node.netexId.startsWith('dr')).length > 0; + } + + public setLegendContext() { + if (this.hasDrugsLoaded() || this.adjacentDrugs) { + if (this.highlightSeeds) { + this.legendContext = "drugAndSeeds"; + } else { + this.legendContext = "drug"; + } + } else { + if (this.highlightSeeds) { + this.legendContext = "drugTargetAndSeeds"; + } else { + this.legendContext = 'drugTarget' + } + } + } + + /** + * To highlight the seeds in the analysis network, not used in the browser network + * @param bool + */ + public updateHighlightSeeds(bool: boolean) { + this.highlightSeeds = bool; + const updatedNodes = []; + for (const item of this.currentViewProteins) { + if (item.netexId === undefined) { + // nodes that are not mapped to backend remain untouched + continue; + } + const node: Node = this.nodeData.nodes.get(item.id); + if (!node) { + continue; + } + const pos = this.networkHandler.activeNetwork.networkInternal.getPositions([item.id]); + node.x = pos[item.id].x; + node.y = pos[item.id].y; + const isSeed = this.highlightSeeds ? this.seedMap[node.id] : false; + const gradient = (this.gradientMap !== {}) && (this.gradientMap[item.id]) ? this.gradientMap[item.id] : 1.0; + Object.assign( + node, + NetworkSettings.getNodeStyle( + node, + this.drugstoneConfig.config, + isSeed, + this.analysis.inSelection(getWrapperFromNode(item)), + gradient + ) + ) + updatedNodes.push(node); + } + this.nodeData.nodes.update(updatedNodes); + this.setLegendContext(); + } + +} diff --git a/src/app/components/query-tile/query-tile.component.html b/src/app/components/query-tile/query-tile.component.html index 1a50789cc63f6ec4e80fa5250e484bc1fd9d3796..9ad4bf805893df29d942c490c60bcd049f46481a 100644 --- a/src/app/components/query-tile/query-tile.component.html +++ b/src/app/components/query-tile/query-tile.component.html @@ -1,8 +1,19 @@ <div class="content"> - <ng-select [items]="queryItems" bindLabel="data.label" bindValue="data.label" [virtualScroll]="true" - [placeholder]="getLabel()" [hideSelected]="true" [searchFn]="querySearch" (change)="select($event)" pTooltip="Find nodes in the network." [tooltipStyleClass]="'drgstn drgstn-tooltip'" tooltipPosition="top"> + <ng-select + [items]="queryItems" + bindLabel="data.label" + bindValue="data.label" + [virtualScroll]="true" + [placeholder]="getLabel()" + [hideSelected]="true" + [searchFn]="querySearch" + (change)="select($event)" + pTooltip="Find nodes in the network." + [tooltipStyleClass]="'drgstn drgstn-tooltip'" + tooltipPosition="top" + > <ng-template ng-option-tmp let-item="item"> - <span>{{item.data.label}}</span> + <span>{{ item.data.label }}</span> </ng-template> </ng-select> </div> diff --git a/src/app/components/query-tile/query-tile.component.ts b/src/app/components/query-tile/query-tile.component.ts index 797412560df1e7ea35f15e3d32374cb4cbe1eba1..0b1e6fe082a99491dc2d1027f20ffe62ca34a691 100644 --- a/src/app/components/query-tile/query-tile.component.ts +++ b/src/app/components/query-tile/query-tile.component.ts @@ -1,18 +1,32 @@ -import {Component, Input, Output, EventEmitter} from '@angular/core'; -import {element} from 'protractor'; -import {Node, Wrapper} from '../../interfaces'; +import { Component, Input, Output, EventEmitter, OnInit, ViewChild } from '@angular/core'; +import { NgSelectComponent } from '@ng-select/ng-select'; +import { NetworkHandlerService } from 'src/app/services/network-handler/network-handler.service'; +import { Wrapper } from '../../interfaces'; @Component({ selector: 'app-query-tile-component', templateUrl: './query-tile.component.html', styleUrls: ['./query-tile.component.scss'] }) -export class QueryTileComponent { +export class QueryTileComponent implements OnInit { + constructor(public networkHandler: NetworkHandlerService) { + + } + + ngOnInit(): void { + this.networkHandler.getChange$.forEach(data => this.reset()); + } + + @ViewChild(NgSelectComponent) ngSelectComponent: NgSelectComponent; @Output() selectItem: EventEmitter<any> = new EventEmitter(); @Input() queryItems: Wrapper[]; - selectedItem = undefined; + public selectedItem = undefined; + + public reset() { + this.ngSelectComponent.handleClearClick(); + } private listStartsWith = (elments: any[], term) => { for (const e of elments) { @@ -25,7 +39,7 @@ export class QueryTileComponent { querySearch = (term: string, item: Wrapper) => { term = term.toLowerCase(); - const data = {...item.data} + const data = { ...item.data } // add possible missing attributes to not throw errors if (data.ensg === undefined) { data.ensg = [] diff --git a/src/app/components/task-list/task-list.component.html b/src/app/components/task-list/task-list.component.html index 0ab3438624ca93f6b4c4bb7a0aa711b377d2d7a0..6902950f966632bc8559c77ee1e4c61d2d9c95f1 100644 --- a/src/app/components/task-list/task-list.component.html +++ b/src/app/components/task-list/task-list.component.html @@ -46,7 +46,7 @@ </small> </div> <div class="column is-2 pt-0 pb-0"> - <a (click)="analysis.removeTask(task.token)" class="text-danger is-pulled-right"> + <a (click)="analysis.removeTask(task.token)" class="has-text-danger is-pulled-right"> <span class="icon"> <i class="fa fa-trash"></i> </span> @@ -79,7 +79,7 @@ <div class="column is-2 pt-0 pb-0"> <a (click)="analysis.removeTask(task.token)" - class="text-danger is-pulled-right" + class="has-text-danger is-pulled-right" > <span class="icon"> <i class="fa fa-trash"></i> @@ -122,7 +122,7 @@ <small>Finished {{ task.info.finishedAt | date: "short" }}</small> </div> <div class="column is-2 pt-0 pb-0"> - <a (click)="analysis.removeTask(task.token)" class="text-danger is-pulled-right"> + <a (click)="analysis.removeTask(task.token)" class="has-text-danger is-pulled-right"> <span class="icon"> <i class="fa fa-trash"></i> </span> @@ -149,13 +149,13 @@ </div> </div> <div class="columns mb-0"> - <div class="column is-8 pt-0 pb-0 text-danger"> + <div class="column is-8 pt-0 pb-0 has-text-danger"> <small class="status-field">{{ task.info.status }}</small> </div> <div class="column is-2 pt-0 pb-0"> <a (click)="analysis.removeTask(task.token)" - class="text-danger is-pulled-right" + class="has-text-danger is-pulled-right" > <span class="icon "> <i class="fa fa-trash"></i> diff --git a/src/app/components/toggle/toggle.component.html b/src/app/components/toggle/toggle.component.html index a018217905c37a54f384a0caaa0bc534489d8f11..2819451506717ab724df4ee10eb9a8ee46ddbc9d 100644 --- a/src/app/components/toggle/toggle.component.html +++ b/src/app/components/toggle/toggle.component.html @@ -1,4 +1,4 @@ -<!-- <div class="field has-addons"> +<div class="field has-addons"> <p class="control has-tooltip"> <button class="button is-rounded has-tooltip" @@ -8,7 +8,7 @@ tooltipPosition="top" [class.is-primary]="value" (click)="toggle(true)" - [ngClass]="{ 'switch-small': drugstoneConfig.smallStyle }" + [ngClass]="{ 'is-small': drugstoneConfig.smallStyle }" > <span class="icon is-small"> <i class="fa {{ iconOn }}"></i> @@ -25,7 +25,7 @@ tooltipPosition="top" [class.is-primary]="!value" (click)="toggle(false)" - [ngClass]="{ 'switch-small': drugstoneConfig.smallStyle }" + [ngClass]="{ 'is-small': drugstoneConfig.smallStyle }" > <span [ngClass]="{ 'text-small': drugstoneConfig.smallStyle }">{{ textOff }}</span> <span *ngIf="iconOff" class="icon is-small"> @@ -33,29 +33,4 @@ </span> </button> </p> -</div> --> - -<button - class="button is-rounded has-tooltip" - [pTooltip]="tooltipOff" - [tooltipStyleClass]="'drgstn drgstn-tooltip drgstn-tooltip-left'" - tooltipPosition="left" - (click)="toggle()" - [ngClass]="{ - 'is-primary': value, - 'switch-small': drugstoneConfig.smallStyle - }" -> - <span *ngIf="icon" class="icon is-small"> - <i class="{{ icon }}"></i> - </span> - <span [ngClass]="{ 'text-small': drugstoneConfig.smallStyle }">{{ - textOn - }}</span> - <span *ngIf="!value" class="icon is-small"> - <i class="fa {{ iconOff }}"></i> - </span> - <span *ngIf="value" class="icon is-small"> - <i class="fa {{ iconOn }}"></i> - </span> -</button> +</div> diff --git a/src/app/components/toggle/toggle.component.ts b/src/app/components/toggle/toggle.component.ts index 2c7ada6732b56208c9210127839b55628fe4e395..11d42d2d1a3da97cbc7aa8c49f132eafa7c48aff 100644 --- a/src/app/components/toggle/toggle.component.ts +++ b/src/app/components/toggle/toggle.component.ts @@ -1,4 +1,4 @@ -import {Component, Directive, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; import { DrugstoneConfigService } from 'src/app/services/drugstone-config/drugstone-config.service'; @Component({ @@ -16,8 +16,6 @@ export class ToggleComponent implements OnInit { @Input() tooltipOn: string; @Input() tooltipOff: string; @Input() disabled = false; - @Input() icon: string; - @Input() value: boolean; @Output() valueChange = new EventEmitter<boolean>(); @@ -28,8 +26,8 @@ export class ToggleComponent implements OnInit { ngOnInit(): void { } - public toggle( ) { - this.value = !this.value + public toggle(value) { + this.value = value; this.valueChange.emit(this.value); } diff --git a/src/app/config.ts b/src/app/config.ts index da336d66e1fd431fb8b15a49020cc3acc1372db5..cbf90bf60a03617f0ad803f7b649aa588a7938b1 100644 --- a/src/app/config.ts +++ b/src/app/config.ts @@ -37,7 +37,7 @@ export interface IConfig { legendPos: 'left' | 'right'; taskTargetName: string, taskDrugName: string, - // showSidebar: boolean; + showSidebar: false | 'left' | 'right'; showOverview: boolean; showQuery: boolean; showItemSelector: boolean; @@ -45,14 +45,18 @@ export interface IConfig { showAdvAnalysis: boolean; showTasks: boolean; showSelection: boolean; - showNetworkMenu: boolean; + showNetworkMenu: false | 'left' | 'right'; showNetworkMenuButtonExpression: boolean; showNetworkMenuButtonScreenshot: boolean; showNetworkMenuButtonExportGraphml: boolean; showNetworkMenuButtonAdjacentDrugs: boolean; + networkMenuButtonAdjacentDrugsLabel: string; showNetworkMenuButtonAdjacentDisordersProteins: boolean; + networkMenuButtonAdjacentDisordersProteinsLabel: string; showNetworkMenuButtonAdjacentDisordersDrugs: boolean; + networkMenuButtonAdjacentDisordersDrugsLabel: string; showNetworkMenuButtonAnimation: boolean; + networkMenuButtonAnimationLabel: string; showLegend: boolean; showLegendNodes: boolean; showLegendEdges: boolean; @@ -61,7 +65,6 @@ export interface IConfig { interactionDrugProtein: InteractionDrugProteinDB; interactionProteinProtein: InteractionProteinProteinDB; autofillEdges: boolean; - sidebarPos: 'left' | 'right'; interactions?: InteractionDatabase; physicsOn?: boolean; identifier?: Identifier; @@ -81,7 +84,7 @@ export const defaultConfig: IConfig = { taskDrugName: 'Drug Search', showLegendNodes: true, showLegendEdges: true, - // showSidebar: true, + showSidebar: 'left', showOverview: true, showQuery: true, showItemSelector: true, @@ -89,15 +92,19 @@ export const defaultConfig: IConfig = { showAdvAnalysis: true, showSelection: true, showTasks: true, - showNetworkMenu: true, + showNetworkMenu: 'right', showLegend: true, showNetworkMenuButtonExpression: true, showNetworkMenuButtonScreenshot: true, showNetworkMenuButtonExportGraphml: true, showNetworkMenuButtonAdjacentDrugs: true, + networkMenuButtonAdjacentDrugsLabel: 'Drugs', showNetworkMenuButtonAdjacentDisordersProteins: true, + networkMenuButtonAdjacentDisordersProteinsLabel: 'Disorders (protein)', showNetworkMenuButtonAdjacentDisordersDrugs: true, + networkMenuButtonAdjacentDisordersDrugsLabel: 'Disorders (drug)', showNetworkMenuButtonAnimation: true, + networkMenuButtonAnimationLabel: 'Animation', identifier: 'symbol', interactionDrugProtein: 'DrugBank', interactionProteinProtein: 'STRING', @@ -105,7 +112,6 @@ export const defaultConfig: IConfig = { edgeShadow: true, autofillEdges: true, physicsOn: false, - sidebarPos: 'left', nodeGroups: { // all NodeGroups but the default group must be set, if not provided by the user, they will be taken from here // IMPORTANT: node color must be hexacode! diff --git a/src/app/interfaces.ts b/src/app/interfaces.ts index e4e936582b16efc57bda3de7964ce662c1dfbc42..d84da4c1ec071f23d59027395c383b74a202106f 100644 --- a/src/app/interfaces.ts +++ b/src/app/interfaces.ts @@ -32,7 +32,14 @@ export interface Tissue { name: string; } -export type legendContext = 'explorer' | 'adjacentDrugs' | 'drug' | 'drugTarget' | +export interface NodeData { + nodes: any; + edges: any; +} + +export type NetworkType = 'explorer' | 'analysis' + +export type LegendContext = 'explorer' | 'adjacentDrugs' | 'drug' | 'drugTarget' | 'drugTargetAndSeeds' | 'drugAndSeeds' | 'adjacentDisorders' | 'adjacentDrugsAndDisorders'; /// netexId to expressionlvl diff --git a/src/app/pages/explorer-page/explorer-page.component.html b/src/app/pages/explorer-page/explorer-page.component.html index 9bf8a9e664ccc085d245fd478c1dfb258e7c7ad7..996b7169156baca3c56d256a48fa3423a1b52188 100644 --- a/src/app/pages/explorer-page/explorer-page.component.html +++ b/src/app/pages/explorer-page/explorer-page.component.html @@ -11,16 +11,16 @@ <app-custom-proteins [(show)]="showCustomProteinsDialog" - [visibleNodes]="currentViewNodes" + [visibleNodes]="networkHandler.activeNetwork.currentViewNodes" > </app-custom-proteins> <app-add-expressed-proteins [(show)]="showThresholdDialog" - [selectedTissue]="currentViewSelectedTissue" - [visibleNodes]="currentViewNodes" - [currentViewProteins]="currentViewProteins" - [expressionMap]="expressionMap" + [selectedTissue]="networkHandler.activeNetwork.currentViewSelectedTissue" + [visibleNodes]="networkHandler.activeNetwork.currentViewNodes" + [currentViewProteins]="networkHandler.activeNetwork.currentViewProteins" + [expressionMap]="networkHandler.activeNetwork.expressionMap" > </app-add-expressed-proteins> @@ -29,11 +29,11 @@ class="drugstone explorer columns is-gapless" [ngClass]="{ 'is-flex-direction-row-reverse': - drugstoneConfig.config.sidebarPos === 'right' + drugstoneConfig.config.showSidebar === 'right' }" > <!-- Start left sidebar --> - <div class="drugstone sidebar column"> + <div class="drugstone sidebar column" *ngIf="drugstoneConfig.config.showSidebar"> <div *ngIf="drugstoneConfig.config.showItemSelector" class="card bar-large mt-0" @@ -43,15 +43,19 @@ [ngClass]="{ 'b-text-small': drugstoneConfig.smallStyle }" > <p class="card-header-title"> - <span *ngIf="!selectedWrapper" class="icon"> + <span + *ngIf="!networkHandler.activeNetwork.selectedWrapper" + class="icon" + > <i class="fas fa-info" aria-hidden="true"></i> </span> - <!-- <i *ngIf="selectedWrapper && selectedWrapper.data.netexId && selectedWrapper.data.netexId.startsWith('p')" class="fas fa-dna" aria-hidden="true"></i> - <i *ngIf="selectedWrapper && selectedWrapper.data.netexId && selectedWrapper.data.netexId.startsWith('d')" class="fas fa-capsules" - aria-hidden="true"></i> --> - <span *ngIf="!selectedWrapper">No item selected</span> - <span *ngIf="selectedWrapper"> - <span>{{ selectedWrapper.data.type }}</span> + <span *ngIf="!networkHandler.activeNetwork.selectedWrapper" + >No item selected</span + > + <span *ngIf="networkHandler.activeNetwork.selectedWrapper"> + <span>{{ + networkHandler.activeNetwork.selectedWrapper.data.type + }}</span> </span> </p> <a @@ -70,7 +74,9 @@ </header> <div *ngIf="collapseDetails"> <div class="card-content"> - <app-info-tile [wrapper]="selectedWrapper"></app-info-tile> + <app-info-tile + [wrapper]="networkHandler.activeNetwork.selectedWrapper" + ></app-info-tile> </div> </div> </div> @@ -118,7 +124,9 @@ <p class="heading">Nodes</p> <p class="title"> {{ - currentViewNodes != null ? currentViewNodes.length : 0 + networkHandler.activeNetwork.currentViewNodes != null + ? networkHandler.activeNetwork.currentViewNodes.length + : 0 }} </p> </div> @@ -128,7 +136,9 @@ <p class="heading">Interactions</p> <p class="title"> {{ - currentViewEdges != null ? currentViewEdges.length : 0 + networkHandler.activeNetwork.currentViewEdges != null + ? networkHandler.activeNetwork.currentViewEdges.length + : 0 }} </p> </div> @@ -172,7 +182,7 @@ <div class="control"> <app-query-tile-component (selectItem)="queryAction($event)" - [queryItems]="queryItems" + [queryItems]="networkHandler.activeNetwork.queryItems" ></app-query-tile-component> </div> </div> @@ -518,7 +528,7 @@ (click)=" analysis.removeAllTasks(); selectedAnalysisToken = null " - class="card-footer-item text-danger" + class="card-footer-item has-text-danger" pTooltip="Delete all tasks." [tooltipStyleClass]="'drgstn drgstn-tooltip'" tooltipPosition="top" @@ -633,7 +643,7 @@ </span> </a> <a (click)="analysis.removeSeeds(currentViewNodes)" - class="card-footer-item text-danger" tooltipPosition="top" pTooltip="Remove all seeds."> + class="card-footer-item has-text-danger" tooltipPosition="top" pTooltip="Remove all seeds."> <span class="icon"> <i class="fa fa-minus"></i> </span> @@ -679,7 +689,7 @@ <a *ngIf="analysis.getSelection().length" (click)="analysis.resetSelection()" - class="card-footer-item text-danger" + class="card-footer-item has-text-danger" tooltipPosition="top" pTooltip="Remove all entries from the selection." > @@ -698,239 +708,23 @@ <!-- Start network block --> <div class="drugstone network column" id="main-column"> + <!-- analysis panel with analysis network --> <div class="analysis-view" *ngIf="selectedAnalysisToken"> <app-analysis-panel [(token)]="selectedAnalysisToken" - (showDetailsChange)="selectedWrapper = $event" + (showDetailsChange)=" + networkHandler.activeNetwork.selectedWrapper = $event + " (visibleItems)="analysisWindowChanged($event)" [config]="drugstoneConfig.config" ></app-analysis-panel> </div> - - <div class="card network"> - <header class="card-header network-header"> - <p class="card-header-title"> - {{ drugstoneConfig.config.title }} - </p> - </header> - <div class="card-content explorer-network-view-settings"> - <div class="card-image canvas-content" #networkWithLegend> - <div *ngIf="drugstoneConfig.config.showLegend"> - <app-network-legend - [config]="drugstoneConfig.config" - [context]="legendContext" - ></app-network-legend> - </div> - <div class="center image1 fullheight" #network> - <button class="button is-loading center" alt="loading..."> - Loading - </button> - </div> - </div> - - <div - *ngIf="drugstoneConfig.config.showNetworkMenu" - class="network-footer-toolbar drgstn-box-shadow" - [ngClass]="networkSidebarOpen ? 'opened' : 'closed'" - > - <div class="network-footer-toolbar-inner-container"> - <button - (click)="toggleNetworkSidebar()" - class="button is-small is-primary network-toolbar-toggle" - [ngClass]="{ rotated: networkSidebarOpen }" - > - <i class="fas fa-angle-left"></i> - </button> - <div class="rows"> - <div - class="row is-full m-1" - *ngIf=" - drugstoneConfig.config.showNetworkMenuButtonScreenshot - " - > - <div class="network-footer-toolbar-element"> - <button - (click)="toImage()" - class="button is-primary is-rounded has-tooltip" - pTooltip="Take a screenshot of the current network." - [tooltipStyleClass]="'drgstn drgstn-tooltip'" - tooltipPosition="top" - > - <span class="icon"> - <i class="fas fa-camera" aria-hidden="true"></i> - </span> - <span - [ngClass]="{ - 'text-normal': drugstoneConfig.smallStyle - }" - >Screenshot</span - > - </button> - </div> - </div> - <div - class="row is-full m-1" - *ngIf=" - drugstoneConfig.config.showNetworkMenuButtonExportGraphml - " - > - <app-download-button - [nodeData]="nodeData" - [buttonId]="'explorer-download'" - ></app-download-button> - </div> - <div - class="row is-full m-1" - *ngIf=" - drugstoneConfig.config.showNetworkMenuButtonExpression - " - > - <div - class="dropdown network-footer-toolbar-element" - [class.is-active]="expressionExpanded" - > - <div class="dropdown-trigger"> - <button - (click)="expressionExpanded = !expressionExpanded" - class="button is-rounded is-primary" - [class.is-outlined]="!selectedTissue" - aria-haspopup="true" - aria-controls="dropdown-menu" - pTooltip="Tissue expression data is provided by the GTEx project." - [tooltipStyleClass]="'drgstn drgstn-tooltip'" - tooltipPosition="top" - > - <span class="icon is-small"> - <i class="fas fa-child"></i> - </span> - <span - *ngIf="!selectedTissue" - [ngClass]="{ - 'text-small': drugstoneConfig.smallStyle - }" - >Tissue</span - > - <span *ngIf="selectedTissue" [ngClass]="{ - 'text-small': drugstoneConfig.smallStyle - }">{{ - selectedTissue.name - }}</span> - <span - *ngIf="expressionExpanded" - class="icon is-small" - > - <i class="fas fa-angle-down" aria-hidden="true"></i> - </span> - <span - *ngIf="!expressionExpanded" - class="icon is-small" - > - <i class="fas fa-angle-left" aria-hidden="true"></i> - </span> - </button> - </div> - <div class="dropdown-menu" id="dropdown-menu" role="menu"> - <div class="dropdown-content tissue-dropdown"> - <div class="scroll-area"> - <a - (click)="selectTissue(null)" - [class.is-active]="!selectedTissue" - class="dropdown-item" - > - None - </a> - <a - *ngFor="let tissue of analysis.getTissues()" - (click)="selectTissue(tissue)" - [class.is-active]=" - selectedTissue && - tissue.netexId === selectedTissue.netexId - " - class="dropdown-item" - > - {{ tissue.name }} - </a> - </div> - </div> - </div> - </div> - </div> - <div - class="row is-full m-1" - *ngIf=" - drugstoneConfig.config.showNetworkMenuButtonAdjacentDrugs - " - > - <app-toggle - class="network-footer-toolbar-element" - textOn="Drugs" - textOff="Off" - icon="fas fa-capsules" - tooltipOn="Display adjacent drugs ON." - tooltipOff="Display adjacent drugs OFF." - [value]="adjacentDrugs" - (valueChange)="updateAdjacentDrugs($event)" - ></app-toggle> - </div> - <div - class="row is-full m-1" - *ngIf=" - drugstoneConfig.config - .showNetworkMenuButtonAdjacentDisordersProteins - " - > - <app-toggle - class="network-footer-toolbar-element" - textOn="Disorders (protein)" - textOff="Off" - tooltipOn="Show disorders adjacent to all currently displayed proteins/genes ON." - tooltipOff="Show disorders adjacent to all currently displayed proteins/genes OFF." - [value]="adjacentDisordersProtein" - (valueChange)="updateAdjacentProteinDisorders($event)" - icon="fas fa-head-side-mask" - ></app-toggle> - </div> - <div - class="row is-full m-1" - *ngIf=" - drugstoneConfig.config - .showNetworkMenuButtonAdjacentDisordersDrugs - " - > - <app-toggle - class="network-footer-toolbar-element" - textOn="Disorders (drug)" - textOff="Off" - tooltipOn="Show disorders adjacent to all currently displayed drugs ON." - tooltipOff="Show disorders adjacent to all currently displayed drugs OFF." - [value]="adjacentDisordersDrug" - [disabled]="!hasDrugsLoaded()" - (valueChange)="updateAdjacentDrugDisorders($event)" - icon="fas fa-biohazard" - ></app-toggle> - </div> - <div - class="row is-full m-1" - *ngIf=" - drugstoneConfig.config.showNetworkMenuButtonAnimation - " - > - <app-toggle - class="network-footer-toolbar-element" - textOn="Animation" - textOff="Off" - tooltipOn="Enable the network animation." - tooltipOff="Disable the network animation and freeze nodes." - [value]="drugstoneConfig.config.physicsOn" - (valueChange)="updatePhysicsEnabled($event)" - icon="fas fa-bullseye" - ></app-toggle> - </div> - </div> - </div> - </div> - </div> - </div> + <!-- explorer network --> + <app-network + networkType="explorer" + [nodeData]="nodeData" + legendContext="explorer" + ></app-network> </div> <!-- End network block --> diff --git a/src/app/pages/explorer-page/explorer-page.component.scss b/src/app/pages/explorer-page/explorer-page.component.scss index bc84f49f7c2572a9dfbc65aef71fbbb0d05bc017..942baac37e1572924fd52d6efe720366243cba61 100644 --- a/src/app/pages/explorer-page/explorer-page.component.scss +++ b/src/app/pages/explorer-page/explorer-page.component.scss @@ -1,6 +1,5 @@ @import "src/stylesheets/variables"; - .quick-icon { display: flex; align-content: center; @@ -37,10 +36,9 @@ margin: auto 0; font-weight: bold; } - } .notification.small { - padding: .5rem 1rem !important; + padding: 0.5rem 1rem !important; } .divisor-rapid { margin-top: -10px; @@ -48,22 +46,14 @@ text-align: center; } } -.card-content.quick-find.small{ - padding: .5rem !important; +.card-content.quick-find.small { + padding: 0.5rem !important; } .card-content.quick-find { padding: 1.5rem !important; } -.explorer-network-view-settings { - // remove margin from tab header when network is displayed, so that network - // does not disappear in empty border - padding: 0; - height: calc(100% - #{$network-header-height}); -} - - .quick-start-btn { align-self: center; justify-self: center; @@ -74,7 +64,7 @@ } .align-vmiddle { - width:100%; + width: 100%; display: flex; vertical-align: middle; height: 50px; @@ -83,7 +73,9 @@ display: inline-block; } - button, span, div { + button, + span, + div { vertical-align: middle; margin-left: 5px; margin-right: 5px; @@ -106,7 +98,7 @@ } } -.scroll-x{ +.scroll-x { overflow-x: auto; } @@ -117,18 +109,3 @@ .task-list-container { padding: 0.5rem !important; } - -.network-toolbar-toggle { - position: relative; - top: calc(50% - 10px); - display: inline-block; - margin: 0px; - left: -20px; - &.rotated { - -ms-transform: rotate(180deg); /* IE 9 */ - transform: rotate(180deg); - } -} - - - diff --git a/src/app/pages/explorer-page/explorer-page.component.ts b/src/app/pages/explorer-page/explorer-page.component.ts index c8cbb1fb52bd0d8af6cd920e17b11c88fbb30117..a76ec4adc558942a78cfdb370afd4ec26202007d 100644 --- a/src/app/pages/explorer-page/explorer-page.component.ts +++ b/src/app/pages/explorer-page/explorer-page.component.ts @@ -1,7 +1,6 @@ import { AfterViewInit, Component, - ElementRef, EventEmitter, HostListener, Input, @@ -10,27 +9,26 @@ import { ViewChild, } from '@angular/core'; import { - getDrugNodeId, getWrapperFromNode, - legendContext, Node, - NodeAttributeMap, NodeInteraction, Tissue, Wrapper } from '../../interfaces'; -import {mapCustomEdge, mapCustomNode, mapNetexEdge, ProteinNetwork} from '../../main-network'; -import {AnalysisService} from '../../services/analysis/analysis.service'; -import {OmnipathControllerService} from '../../services/omnipath-controller/omnipath-controller.service'; -import domtoimage from 'dom-to-image'; -import {NetworkSettings} from '../../network-settings'; -import {defaultConfig, EdgeGroup, IConfig, InteractionDatabase, NodeGroup} from '../../config'; -import {NetexControllerService} from 'src/app/services/netex-controller/netex-controller.service'; -import {pieChartContextRenderer, removeDuplicateObjectsFromList} from '../../utils'; +import { ProteinNetwork, mapNetexEdge } from '../../main-network'; +import { AnalysisService } from '../../services/analysis/analysis.service'; +import { OmnipathControllerService } from '../../services/omnipath-controller/omnipath-controller.service'; +import { NetworkSettings } from '../../network-settings'; +import { defaultConfig, EdgeGroup, NodeGroup } from '../../config'; +import { NetexControllerService } from 'src/app/services/netex-controller/netex-controller.service'; +import { removeDuplicateObjectsFromList } from '../../utils'; import * as merge from 'lodash/fp/merge'; -import {AnalysisPanelComponent} from 'src/app/components/analysis-panel/analysis-panel.component'; +import { AnalysisPanelComponent } from 'src/app/components/analysis-panel/analysis-panel.component'; import * as JSON5 from 'json5'; import { DrugstoneConfigService } from 'src/app/services/drugstone-config/drugstone-config.service'; +import { NetworkHandlerService } from 'src/app/services/network-handler/network-handler.service'; +import { NetworkComponent } from 'src/app/components/network/network.component'; + declare var vis: any; @@ -43,7 +41,7 @@ declare var vis: any; export class ExplorerPageComponent implements OnInit, AfterViewInit { private networkJSON = '{"nodes": [], "edges": []}'; - private networkPositions = undefined; + public _config: string; @Input() public onload: undefined | string; @@ -56,53 +54,21 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { if (config == null) { return; } - if (this.id == null) - setTimeout(() => { - this.config = config; - }, 200); - // add settings to config - - const configObj = typeof config === 'string' ? config.length === 0 ? {}: JSON5.parse(config) : config; - this.drugstoneConfig.config = merge(this.drugstoneConfig.config, configObj); - - // update Drugst.One according to the settings - // check if config updates affect network - let updateNetworkFlag = false; - for (const key of Object.keys(configObj)) { - if (key === 'nodeGroups') { - this.setConfigNodeGroup(key, configObj[key]); - updateNetworkFlag = true; - } else if (key === 'edgeGroups') { - this.setConfigEdgeGroup(key, configObj[key]); - updateNetworkFlag = true; - } else if (key === 'interactions') { - this.getInteractions(configObj[key]); - } - } - // trigger updates on config e.g. in legend - this.drugstoneConfig.config = {...this.drugstoneConfig.config}; - if (updateNetworkFlag && typeof this.networkJSON !== 'undefined') { - // update network if network config has changed and networkJSON exists - if (this.networkInternal !== undefined) { - // a network exists, save node positions - this.networkPositions = this.networkInternal.getPositions(); - } - this.createNetwork().then(() => { - if(this.drugstoneConfig.config.physicsOn) { - this.updatePhysicsEnabled(true); - } - }); - + this._config = config; + if (this.id !== null) { + this.activateConfig(); } } @Input() public set network(network: string | undefined) { + console.log('setting network data', network) + if (network == null) { return; } this.networkJSON = JSON.stringify(typeof network === 'string' ? JSON5.parse(network) : network); - this.createNetwork(); + this.activateConfig(); } @Output() @@ -115,7 +81,6 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { public windowWidth = 0; public showDetails = false; - public selectedWrapper: Wrapper | null = null; public collapseAnalysisQuick = true; public collapseAnalysis = true; @@ -132,28 +97,9 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { public proteins: Node[]; public edges: NodeInteraction[]; - public networkSidebarOpen = true; - - private networkInternal: any; // this will store the vis Dataset - public nodeData: { nodes: any, edges: any } = {nodes: null, edges: null}; - - private dumpPositions = false; - public adjacentDrugs = false; - - public adjacentDrugList: Node[] = []; - public adjacentDrugEdgesList: Node[] = []; + public nodeData: { nodes: any, edges: any } = { nodes: null, edges: null }; - public adjacentDisordersProtein = false; - public adjacentDisordersDrug = false; - - public adjacentProteinDisorderList: Node[] = []; - public adjacentProteinDisorderEdgesList: Node[] = []; - - public adjacentDrugDisorderList: Node[] = []; - public adjacentDrugDisorderEdgesList: Node[] = []; - - public queryItems: Wrapper[] = []; public showAnalysisDialog = false; public showThresholdDialog = false; public analysisDialogTarget: 'drug' | 'drug-target'; @@ -170,32 +116,12 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { this.selectedAnalysisToken = token; } - public currentDataset = []; - - public currentViewProteins: Node[]; - public currentViewSelectedTissue: Tissue | null = null; - public currentViewNodes: Node[]; - public currentViewEdges: NodeInteraction[]; - - public expressionExpanded = false; - public selectedTissue: Tissue | null = null; - - public legendContext: legendContext = 'explorer'; - - // keys are node netexIds - public expressionMap: NodeAttributeMap = undefined; - - @ViewChild('network', {static: false}) networkEl: ElementRef; - @ViewChild('networkWithLegend', {static: false}) networkWithLegendEl: ElementRef; - - @ViewChild(AnalysisPanelComponent, {static: false}) - private analysisPanel: AnalysisPanelComponent; - constructor( public omnipath: OmnipathControllerService, public analysis: AnalysisService, public drugstoneConfig: DrugstoneConfigService, - public netex: NetexControllerService) { + public netex: NetexControllerService, + public networkHandler: NetworkHandlerService) { this.showDetails = false; this.analysis.subscribeList(async (items, selected) => { @@ -214,7 +140,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { if (!node) { continue; } - const pos = this.networkInternal.getPositions([wrapper.id]); + const pos = this.networkHandler.activeNetwork.networkInternal.getPositions([wrapper.id]); node.x = pos[wrapper.id].x; node.y = pos[wrapper.id].y; const nodeStyled = NetworkSettings.getNodeStyle( @@ -256,145 +182,84 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { } async ngAfterViewInit() { - // TODO find out if this had a function? we were loading the network twice - // this.createNetwork(); + console.log(this.networkHandler.networks) + this.networkHandler.setActiveNetwork('explorer'); if (this.onload) { // tslint:disable-next-line:no-eval eval(this.onload); } - } - async getInteractions(key: InteractionDatabase) { - let edges = []; - if (key == 'omnipath') { - const names = this.nodeData.nodes.map((node) => node.label); - const nameToNetworkId = {}; - this.nodeData.nodes.map((node) => nameToNetworkId[node.label] = node.id); - edges = await this.omnipath.getInteractions(names, this.drugstoneConfig.config.identifier, nameToNetworkId); - } - this.nodeData.edges.update(edges); - } + public activateConfig() { - private async getNetwork() { - - const network = JSON.parse(this.networkJSON); - if (this.drugstoneConfig.config.identifier === 'ensg') { - // @ts-ignore - network.nodes.forEach(node => { - node.id = this.removeEnsemblVersion(node.id); - }); - if (network.edges != null) - // @ts-ignore - network.edges.forEach(edge => { - edge.from = this.removeEnsemblVersion(edge.from); - edge.to = this.removeEnsemblVersion(edge.to); - }); - } - - // map data to nodes in backend - if (network.nodes != null && network.nodes.length) { - network.nodes = await this.netex.mapNodes(network.nodes, this.drugstoneConfig.config.identifier); - } + const configObj = typeof this._config === 'string' ? this._config.length === 0 ? {} : JSON5.parse(this._config) : this._config; + this.drugstoneConfig.config = merge(this.drugstoneConfig.config, configObj); - if (this.drugstoneConfig.config.identifier === 'ensg') { - // remove possible duplicate IDs - network.nodes = removeDuplicateObjectsFromList(network.nodes, 'netexId'); + // update Drugst.One according to the settings + // check if config updates affect network + let updateNetworkFlag = false; + for (const key of Object.keys(configObj)) { + if (key === 'nodeGroups') { + this.setConfigNodeGroup(key, configObj[key]); + updateNetworkFlag = true; + } else if (key === 'edgeGroups') { + this.setConfigEdgeGroup(key, configObj[key]); + updateNetworkFlag = true; + } else if (key === 'interactions') { + this.networkHandler.activeNetwork.getInteractions(configObj[key]); + } } - - // at this point, we have nodes synched with the backend - // use netexIds where posssible, but use original id as node name if no label given - const nodeIdMap = {}; - network.nodes.forEach((node) => { - // set node label to original id before node id will be set to netex id - node.label = node.label ? node.label : node.id; - - nodeIdMap[node.id] = node.netexId ? node.netexId : node.id; - node.id = nodeIdMap[node.id]; - }); - - // adjust edge labels accordingly and filter - const edges = new Array(); - if (network.edges != null) - network.edges.forEach(edge => { - edge.from = nodeIdMap[edge.from]; - edge.to = nodeIdMap[edge.to]; - // check if edges have endpoints - if (edge.from !== undefined && edge.to !== undefined) { - edges.push(edge); + // trigger updates on config e.g. in legend + this.drugstoneConfig.config = { ...this.drugstoneConfig.config }; + if (updateNetworkFlag && typeof this.networkJSON !== 'undefined') { + // update network if network config has changed and networkJSON exists + if (this.networkHandler.activeNetwork.networkInternal !== null) { + // a network exists, save node positions + this.networkHandler.activeNetwork.networkPositions = this.networkHandler.activeNetwork.networkInternal.getPositions(); + } + this.createNetwork().then(() => { + if (this.drugstoneConfig.config.physicsOn) { + this.networkHandler.activeNetwork.updatePhysicsEnabled(true); } }); - // remove edges without endpoints - network.edges = edges; - this.inputNetwork = network; - this.proteins = network.nodes; - this.edges = network.edges; - } - - private setWindowWidth(width: number) { - this.windowWidth = width; - this.drugstoneConfig.smallStyle = this.windowWidth < 1250; - } - - private zoomToNode(id: string) { - // get network object, depending on whether analysis is open or not - const network = this.selectedAnalysisToken ? this.analysisPanel.network : this.networkInternal; - - this.nodeData.nodes.getIds(); - const coords = network.getPositions(id)[id]; - if (!coords) { - return; - } - let zoomScale = null; - if (id.startsWith('eff')) { - zoomScale = 1.0; - } else { - zoomScale = 3.0; } - network.moveTo({ - position: {x: coords.x, y: coords.y}, - scale: zoomScale, - animation: true, - }); } - public async openSummary(item: Wrapper, zoom: boolean) { - this.selectedWrapper = item; - // add expression information if loaded - if (this.expressionMap && this.selectedWrapper.id in this.expressionMap) { - this.selectedWrapper.expression = this.expressionMap[this.selectedWrapper.id] - } - if (zoom) { - this.zoomToNode(item.id); + analysisWindowChanged($event: [any[], [Node[], Tissue], NodeInteraction[]]) { + if ($event) { + this.networkHandler.setActiveNetwork('analysis'); + this.networkHandler.activeNetwork.currentViewNodes = $event[0]; + this.networkHandler.activeNetwork.currentViewEdges = $event[2]; + this.networkHandler.activeNetwork.currentViewProteins = $event[1][0]; + this.networkHandler.activeNetwork.currentViewSelectedTissue = $event[1][1]; + } else { + this.networkHandler.setActiveNetwork('explorer'); } - this.showDetails = true; - } - - public async closeSummary() { - this.selectedWrapper = null; - this.showDetails = false; - } - - removeEnsemblVersion(versionId: string): string { - return versionId.startsWith('ENSG') ? versionId.split('.')[0] : versionId; + // changes for either way (analysis open and close) + this.networkHandler.activeNetwork.selectedWrapper = null; + this.networkHandler.activeNetwork.updateQueryItems(); + // this.fillQueryItems(this.currentViewNodes); } + /** + * Creates the explorer network. Analysis component has distinct function. + */ public async createNetwork() { this.analysis.resetSelection(); - this.selectedWrapper = null; + + this.networkHandler.activeNetwork.selectedWrapper = null; // getNetwork synchronizes the input network with the database await this.getNetwork(); this.proteinData = new ProteinNetwork(this.proteins, this.edges); - if (this.networkPositions) { - this.proteinData.updateNodePositions(this.networkPositions) + if (this.networkHandler.activeNetwork.networkPositions) { + this.proteinData.updateNodePositions(this.networkHandler.activeNetwork.networkPositions) } // TODO do we still need this? // this.proteinData.linkNodes(); - const {nodes, edges} = this.proteinData.mapDataToNetworkInput(this.drugstoneConfig.config); - + const { nodes, edges } = this.proteinData.mapDataToNetworkInput(this.drugstoneConfig.config); if (this.drugstoneConfig.config.autofillEdges && nodes.length) { const netexEdges = await this.netex.fetchEdges(nodes, this.drugstoneConfig.config.interactionProteinProtein); edges.push(...netexEdges.map(netexEdge => mapNetexEdge(netexEdge, this.drugstoneConfig.config))) @@ -402,13 +267,13 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { this.nodeData.nodes = new vis.DataSet(nodes); this.nodeData.edges = new vis.DataSet(edges); - const container = this.networkEl.nativeElement; + const container = this.networkHandler.activeNetwork.networkEl.nativeElement; - const options = NetworkSettings.getOptions('main',this.drugstoneConfig.config.physicsOn); + const options = NetworkSettings.getOptions('main', this.drugstoneConfig.config.physicsOn); - this.networkInternal = new vis.Network(container, this.nodeData, options); + this.networkHandler.activeNetwork.networkInternal = new vis.Network(container, this.nodeData, options); - this.networkInternal.on('doubleClick', (properties) => { + this.networkHandler.activeNetwork.networkInternal.on('doubleClick', (properties) => { const nodeIds: Array<string> = properties.nodes; if (nodeIds != null && nodeIds.length > 0) { const nodeId = nodeIds[0]; @@ -425,7 +290,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { } } }); - this.networkInternal.on('click', (properties) => { + this.networkHandler.activeNetwork.networkInternal.on('click', (properties) => { const nodeIds: Array<string> = properties.nodes; if (nodeIds != null && nodeIds.length > 0) { const nodeId = nodeIds[0]; @@ -436,160 +301,116 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { this.closeSummary(); } }); - this.networkInternal.on('deselectNode', (properties) => { + this.networkHandler.activeNetwork.networkInternal.on('deselectNode', (properties) => { this.closeSummary(); }); - if (this.selectedWrapper) { - this.zoomToNode(this.selectedWrapper.id); + if (this.networkHandler.activeNetwork.selectedWrapper) { + this.zoomToNode(this.networkHandler.activeNetwork.selectedWrapper.id); } - this.currentViewNodes = this.nodeData.nodes; - this.currentViewEdges = this.nodeData.edges; + this.networkHandler.activeNetwork.currentViewNodes = this.nodeData.nodes; + this.networkHandler.activeNetwork.currentViewEdges = this.nodeData.edges; - this.queryItems = []; - this.updateQueryItems(); - this.currentViewProteins = this.proteins; + this.networkHandler.activeNetwork.queryItems = []; + this.networkHandler.activeNetwork.updateQueryItems(); + this.networkHandler.activeNetwork.currentViewProteins = this.proteins; // this.fillQueryItems(this.currentViewNodes); - if (this.selectedWrapper) { - this.networkInternal.selectNodes([this.selectedWrapper.id]); + if (this.networkHandler.activeNetwork.selectedWrapper) { + this.networkHandler.activeNetwork.networkInternal.selectNodes([this.networkHandler.activeNetwork.selectedWrapper.id]); } } - updateQueryItems() { - this.queryItems = []; - this.currentViewNodes.forEach((protein) => { - this.queryItems.push(getWrapperFromNode(protein)); - }); + public zoomToNode(id: string) { + this.networkHandler.activeNetwork.zoomToNode(id); } - // fillQueryItems(hostProteins: Node[]) { - // this.queryItems = []; - // hostProteins.forEach((protein) => { - // this.queryItems.push(getWrapperFromNode(protein)); - // }); - // - // - // this.currentViewProteins = this.proteins; - // } + private async getNetwork() { - public queryAction(item: any) { - if (item) { - this.openSummary(item, true); + const network = JSON.parse(this.networkJSON); + console.log('network', network) + if (this.drugstoneConfig.config.identifier === 'ensg') { + // @ts-ignore + network.nodes.forEach(node => { + node.id = this.removeEnsemblVersion(node.id); + }); + if (network.edges != null) + // @ts-ignore + network.edges.forEach(edge => { + edge.from = this.removeEnsemblVersion(edge.from); + edge.to = this.removeEnsemblVersion(edge.to); + }); } - } - public updatePhysicsEnabled(bool: boolean) { - this.drugstoneConfig.config.physicsOn = bool; - this.networkInternal.setOptions({ - physics: { - enabled: this.drugstoneConfig.config.physicsOn, - stabilization: { - enabled: false, - }, - } - }); - } + // map data to nodes in backend + console.log('network.nodes', network.nodes) + if (network.nodes != null && network.nodes.length) { + network.nodes = await this.netex.mapNodes(network.nodes, this.drugstoneConfig.config.identifier); + } - public updateAdjacentProteinDisorders(bool: boolean) { - this.adjacentDisordersProtein = bool; - if (this.adjacentDisordersProtein) { - this.netex.adjacentDisorders(this.nodeData.nodes, 'proteins').subscribe(response => { - for (const interaction of response.edges) { - const edge = {from: interaction.protein, to: interaction.disorder}; - this.adjacentProteinDisorderEdgesList.push(mapCustomEdge(edge, this.drugstoneConfig.config)); - } - for (const disorder of response.disorders) { - disorder.group = 'defaultDisorder'; - disorder.id = disorder.netexId; - this.adjacentProteinDisorderList.push(mapCustomNode(disorder, this.drugstoneConfig.config)) - } - this.saveAddNodes(this.adjacentProteinDisorderList); - this.nodeData.edges.add(this.adjacentProteinDisorderEdgesList); - this.updateQueryItems(); - }); - this.legendContext = this.adjacentDrugs ? 'adjacentDrugsAndDisorders' : 'adjacentDisorders'; - } else { - this.saveRemoveDisorders(this.adjacentProteinDisorderList); - this.nodeData.edges.remove(this.adjacentProteinDisorderEdgesList); - this.adjacentProteinDisorderList = []; - this.adjacentProteinDisorderEdgesList = []; - this.legendContext = this.adjacentDisordersDrug ? this.legendContext : this.adjacentDrugs ? 'adjacentDrugs' : 'explorer'; - this.updateQueryItems(); + if (this.drugstoneConfig.config.identifier === 'ensg') { + // remove possible duplicate IDs + network.nodes = removeDuplicateObjectsFromList(network.nodes, 'netexId'); } - } - public updateAdjacentDrugDisorders(bool: boolean) { - this.adjacentDisordersDrug = bool; - if (this.adjacentDisordersDrug) { - this.netex.adjacentDisorders(this.nodeData.nodes, 'drugs').subscribe(response => { - for (const interaction of response.edges) { - const edge = {from: interaction.drug, to: interaction.disorder}; - this.adjacentDrugDisorderEdgesList.push(mapCustomEdge(edge, this.drugstoneConfig.config)); - } - for (const disorder of response.disorders) { - disorder.group = 'defaultDisorder'; - disorder.id = disorder.netexId; - this.adjacentDrugDisorderList.push(mapCustomNode(disorder, this.drugstoneConfig.config)); + // at this point, we have nodes synched with the backend + // use netexIds where posssible, but use original id as node name if no label given + const nodeIdMap = {}; + network.nodes.forEach((node) => { + // set node label to original id before node id will be set to netex id + node.label = node.label ? node.label : node.id; + + nodeIdMap[node.id] = node.netexId ? node.netexId : node.id; + node.id = nodeIdMap[node.id]; + }); + + // adjust edge labels accordingly and filter + const edges = new Array(); + if (network.edges != null) + network.edges.forEach(edge => { + edge.from = nodeIdMap[edge.from]; + edge.to = nodeIdMap[edge.to]; + // check if edges have endpoints + if (edge.from !== undefined && edge.to !== undefined) { + edges.push(edge); } - this.saveAddNodes(this.adjacentDrugDisorderList); - this.nodeData.edges.add(this.adjacentDrugDisorderEdgesList); - this.updateQueryItems(); }); - this.legendContext = this.adjacentDrugs ? 'adjacentDrugsAndDisorders' : 'adjacentDisorders'; - } else { - this.saveRemoveDisorders(this.adjacentDrugDisorderList); - this.nodeData.edges.remove(this.adjacentDrugDisorderEdgesList); - this.adjacentDrugDisorderList = []; - this.adjacentDrugDisorderEdgesList = []; - this.legendContext = this.adjacentDisordersProtein ? this.legendContext : this.adjacentDrugs ? 'adjacentDrugs' : 'explorer'; - this.updateQueryItems(); - } + // remove edges without endpoints + network.edges = edges; + this.inputNetwork = network; + this.proteins = network.nodes; + this.edges = network.edges; } - public saveAddNodes(nodeList: Node[]) { - const existing = this.nodeData.nodes.get().map(n => n.id); - const toAdd = nodeList.filter(n => existing.indexOf(n.id) === -1) - this.nodeData.nodes.add(toAdd); + private setWindowWidth(width: number) { + this.windowWidth = width; + this.drugstoneConfig.smallStyle = this.windowWidth < 1250; } - public saveRemoveDisorders(nodeList: Node[]) { - const other = this.adjacentDrugDisorderList === nodeList ? this.adjacentProteinDisorderList : this.adjacentDrugDisorderList - if (other == null) - this.nodeData.nodes.remove(nodeList); - else { - const otherIds = other.map(d => d.id); - const rest = nodeList.filter(d => otherIds.indexOf(d.id) === -1) - this.nodeData.nodes.remove(rest) + public async openSummary(item: Wrapper, zoom: boolean) { + this.networkHandler.activeNetwork.selectedWrapper = item; + // add expression information if loaded + if (this.networkHandler.activeNetwork.expressionMap && this.networkHandler.activeNetwork.selectedWrapper.id in this.networkHandler.activeNetwork.expressionMap) { + this.networkHandler.activeNetwork.selectedWrapper.expression = this.networkHandler.activeNetwork.expressionMap[this.networkHandler.activeNetwork.selectedWrapper.id] + } + if (zoom) { + this.zoomToNode(item.id); } + this.showDetails = true; } - public updateAdjacentDrugs(bool: boolean) { - this.adjacentDrugs = bool; - if (this.adjacentDrugs) { - this.netex.adjacentDrugs(this.drugstoneConfig.config.interactionDrugProtein, this.nodeData.nodes).subscribe(response => { - for (const interaction of response.pdis) { - const edge = {from: interaction.protein, to: interaction.drug}; - this.adjacentDrugEdgesList.push(mapCustomEdge(edge, this.drugstoneConfig.config)); - } - for (const drug of response.drugs) { - drug.group = 'foundDrug'; - drug.id = getDrugNodeId(drug) - this.adjacentDrugList.push(mapCustomNode(drug, this.drugstoneConfig.config)) - } - this.nodeData.nodes.add(this.adjacentDrugList); - this.nodeData.edges.add(this.adjacentDrugEdgesList); - this.updateQueryItems(); - }) - this.legendContext = this.adjacentDisordersDrug || this.adjacentDisordersProtein ? 'adjacentDrugsAndDisorders' : 'adjacentDrugs'; - } else { - this.nodeData.nodes.remove(this.adjacentDrugList); - this.nodeData.edges.remove(this.adjacentDrugEdgesList); - this.adjacentDrugList = []; - this.adjacentDrugEdgesList = []; + public async closeSummary() { + this.networkHandler.activeNetwork.selectedWrapper = null; + this.showDetails = false; + } - this.legendContext = this.adjacentDisordersDrug || this.adjacentDisordersProtein ? 'adjacentDisorders' : 'explorer'; - this.updateQueryItems(); + removeEnsemblVersion(versionId: string): string { + return versionId.startsWith('ENSG') ? versionId.split('.')[0] : versionId; + } + + public queryAction(item: any) { + if (item) { + this.openSummary(item, true); } } @@ -677,43 +498,6 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { this.drugstoneConfig.config[key] = edgeGroups; } - public toImage() { - this.downloadDom(this.networkWithLegendEl.nativeElement).catch(error => { - console.error('Falling back to network only screenshot. Some components seem to be inaccessable, most likely the legend is a custom image with CORS access problems on the host server side.'); - this.downloadDom(this.networkEl.nativeElement).catch(e => { - console.error('Some network content seems to be inaccessable for saving as a screenshot. This can happen due to custom images used as nodes. Please ensure correct CORS accessability on the images host server.'); - console.error(e); - }); - }); - } - - public downloadDom(dom: object) { - return domtoimage.toPng(dom, {bgcolor: '#ffffff'}).then((generatedImage) => { - const a = document.createElement('a'); - a.href = generatedImage; - a.download = `Network.png`; - a.click(); - }); - } - - analysisWindowChanged($event: [any[], [Node[], Tissue], NodeInteraction[]]) { - if ($event) { - this.currentViewNodes = $event[0]; - this.currentViewEdges = $event[2]; - this.currentViewProteins = $event[1][0]; - this.currentViewSelectedTissue = $event[1][1]; - } else { - this.currentViewNodes = this.nodeData.nodes; - this.currentViewEdges = this.nodeData.edges; - this.currentViewProteins = this.proteins; - this.currentViewSelectedTissue = this.selectedTissue; - } - // changes for either way (analysis open and close) - this.selectedWrapper = null; - this.updateQueryItems(); - // this.fillQueryItems(this.currentViewNodes); - } - gProfilerLink(): string { // nodes in selection have netexId const queryString = this.analysis.getSelection() @@ -736,107 +520,16 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { 'background='; } - public selectTissue(tissue: Tissue | null) { - this.expressionExpanded = false; - if (!tissue) { - this.selectedTissue = null; - const updatedNodes = []; - for (const item of this.proteins) { - if (item.netexId === undefined) { - // nodes that are not mapped to backend remain untouched - continue; - } - const node: Node = this.nodeData.nodes.get(item.id); - if (!node) { - continue; - } - const pos = this.networkInternal.getPositions([item.id]); - node.x = pos[item.id].x; - node.y = pos[item.id].y; - Object.assign( - node, - NetworkSettings.getNodeStyle( - node, - this.drugstoneConfig.config, - false, - this.analysis.inSelection(getWrapperFromNode(item)), - 1.0 - ) - ) - updatedNodes.push(node); - } - this.nodeData.nodes.update(updatedNodes); - // delete expression values - this.expressionMap = undefined; - } else { - this.selectedTissue = tissue - const minExp = 0.3; - // filter out non-proteins, e.g. drugs - const proteinNodes = []; - this.nodeData.nodes.forEach(element => { - if (element.id.startsWith('p') && element.netexId !== undefined) { - proteinNodes.push(element); - } - }); - this.netex.tissueExpressionGenes(this.selectedTissue, proteinNodes).subscribe((response) => { - this.expressionMap = response; - const updatedNodes = []; - // mapping from netex IDs to network IDs, TODO check if this step is necessary - const networkIdMappping = {} - this.nodeData.nodes.forEach(element => { - networkIdMappping[element.netexId] = element.id - }); - const maxExpr = Math.max(...Object.values(this.expressionMap)); - for (const [netexId, expressionlvl] of Object.entries(this.expressionMap)) { - const networkId = networkIdMappping[netexId] - const node = this.nodeData.nodes.get(networkId); - if (node === null) { - continue; - } - const wrapper = getWrapperFromNode(node) - const gradient = expressionlvl !== null ? (Math.pow(expressionlvl / maxExpr, 1 / 3) * (1 - minExp) + minExp) : -1; - const pos = this.networkInternal.getPositions([networkId]); - node.x = pos[networkId].x; - node.y = pos[networkId].y; - Object.assign(node, - NetworkSettings.getNodeStyle( - node, - this.drugstoneConfig.config, - node.isSeed, - this.analysis.inSelection(wrapper), - gradient)); - - // try out custom ctx renderer - node.shape = 'custom'; - node.ctxRenderer = pieChartContextRenderer; - updatedNodes.push(node); - } - this.nodeData.nodes.update(updatedNodes); - }) - } - - this.currentViewSelectedTissue = this.selectedTissue; - } - emitTaskEvent(eventObject: object) { this.taskEvent.emit(eventObject); } - hasDrugsLoaded(): boolean { - if (this.nodeData == null || this.nodeData.nodes == null) - return false; - return this.nodeData.nodes.get().filter((node: Node) => node.drugId && node.netexId.startsWith('dr')).length > 0; - } - setInputNetwork(network: any) { if (network == null) - this.inputNetwork = {nodes: this.proteins, edges: this.edges} + this.inputNetwork = { nodes: this.proteins, edges: this.edges } else this.inputNetwork = network; } - toggleNetworkSidebar() { - this.networkSidebarOpen = !this.networkSidebarOpen; - } } diff --git a/src/app/services/network-handler/network-handler.service.spec.ts b/src/app/services/network-handler/network-handler.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..fdbf3aa1b6e8df0e79303853c81a33bdbfd3e851 --- /dev/null +++ b/src/app/services/network-handler/network-handler.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { NetworkHandlerService } from './network-handler.service'; + +describe('NetworkHandlerService', () => { + let service: NetworkHandlerService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(NetworkHandlerService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/network-handler/network-handler.service.ts b/src/app/services/network-handler/network-handler.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..d6515d496656f9033e7f14762c1edc62089cb030 --- /dev/null +++ b/src/app/services/network-handler/network-handler.service.ts @@ -0,0 +1,34 @@ +import { Type, Injectable } from '@angular/core'; +import { Subject } from 'rxjs'; +import { NetworkComponent } from 'src/app/components/network/network.component'; +import { NetworkType } from 'src/app/interfaces'; +import { AnalysisService } from '../analysis/analysis.service'; +import { DrugstoneConfigService } from '../drugstone-config/drugstone-config.service'; +import { NetexControllerService } from '../netex-controller/netex-controller.service'; +import { OmnipathControllerService } from '../omnipath-controller/omnipath-controller.service'; + +@Injectable({ + providedIn: 'root' +}) +export class NetworkHandlerService { + + constructor(public networkHandler: NetworkHandlerService, public analysis: AnalysisService, public drugstoneConfig: DrugstoneConfigService, public netex: NetexControllerService, public omnipath: OmnipathControllerService) { } + + private change = new Subject<any>(); + + public networks: {NetworkType: NetworkComponent} | {} = {}; + public activeNetwork: NetworkComponent = new NetworkComponent(this.networkHandler, this.analysis, this.drugstoneConfig, this.netex, this.omnipath); + + public setActiveNetwork(network: NetworkType) { + this.triggerChange(); + this.activeNetwork = this.networks[network]; + } + + public triggerChange() { + this.change.next(true); + } + + get getChange$ () { + return this.change.asObservable(); + } +} diff --git a/src/index.html b/src/index.html index fc462d609ad9c731cae2c8202896eec8d9a2d39d..903991b5fd1e4a57789e293bcb8e65cb1b813609 100644 --- a/src/index.html +++ b/src/index.html @@ -28,8 +28,12 @@ <button onclick=changeConfigStr('{"legendPos":"left"}') > Legend to Left </button> <br> <button onclick=changeConfigStr('{"legendPos":"right"}') > Legend to Right </button> <br> <button onclick=changeConfigStr('{"interactions":"omnipath"}') >Get Omnipath Interactions </button> <br> -<button onclick=changeConfigStr('{"sidebarPos":"left"}') > Mode sidebar to left</button><br> -<button onclick=changeConfigStr('{"sidebarPos":"right"}') > Move sidebr to right</button><br> +<button onclick=changeConfigStr('{"showSidebar":false}') > Hide sidebar</button><br> +<button onclick=changeConfigStr('{"showSidebar":"left"}') > Mode sidebar to left</button><br> +<button onclick=changeConfigStr('{"showSidebar":"right"}') > Move sidebr to right</button><br> +<button onclick=changeConfigStr('{"showNetworkMenu":false}') > Hide networkMenu</button><br> +<button onclick=changeConfigStr('{"showNetworkMenu":"left"}') > Mode networkMenu to left</button><br> +<button onclick=changeConfigStr('{"showNetworkMenu":"right"}') > Move networkMenu to right</button><br> <button onclick="initTaskEventListener()">Init Task-Eventlistener</button> <br> <input id="taskID" type="text" /> <button onclick=loadTaskID(document.getElementById("taskID").value) class="button is-primary" >Load TaskID</button> <br> diff --git a/src/stylesheets/styles.scss b/src/stylesheets/styles.scss index 2fd036edebc79d7e0019376106ffe6791c6d88ee..35a48dc3d27c4a4a3a8171e42899854ea4e2341b 100644 --- a/src/stylesheets/styles.scss +++ b/src/stylesheets/styles.scss @@ -1,25 +1,29 @@ @charset "utf-8"; -@import url('https://fonts.googleapis.com/css?family=Varela+Round'); +@import url("https://fonts.googleapis.com/css?family=Varela+Round"); @import "../../node_modules/animate.css/animate.min"; @import "primeng"; @import "bulma"; @import "variables"; #appWindow { - @import '~@ng-select/ng-select/themes/default.theme'; + @import "~@ng-select/ng-select/themes/default.theme"; .ng-dropdown-panel .ng-dropdown-panel-items { max-height: 150px !important; } .drgstn-box-shadow { - box-shadow: 0 .125em .25em hsla(0,0%,4%,.1); + box-shadow: 0 0.125em 0.25em hsla(0, 0%, 4%, 0.1); } .fullheight { height: 100%; } + .question-icon { + vertical-align: middle; + } + .card-content .tabs-header { margin-bottom: 0; } @@ -29,56 +33,6 @@ z-index: $explorer-network-z; } - @keyframes hideSidebar { - from {right: 0;} - to {right: $network-footer-right-closed} - } - - @keyframes showSidebar { - from {right: $network-footer-right-closed} - to {right: 0;} - } - - // general network settings, some will be overwritten in analysis - .network-footer-toolbar { - position: absolute; - height: calc(100% - #{$network-header-height}); - width: $network-footer-width; - top: $network-header-height; - background-color: var(--drgstn-panel-secondary); - z-index: $network-footer-container-z; - - &.opened { - right: 0; - animation-name: showSidebar; - animation-duration: 1s; - } - &.closed { - right: $network-footer-right-closed; - animation-name: hideSidebar; - animation-duration: 1s; - } - - &-inner-container { - width: 100%; - height: $network-footer-inner-container-height; - position: absolute; - } - - &-element { - position: relative; - margin-left: 10px; - } - - .dropdown-menu { - z-index: $explorer-network-toolbar-dropdown-z; - - .scroll-area { - height: 15rem; - } - } - } - nav.navbar { height: 60px; } @@ -91,14 +45,18 @@ .tissue-dropdown { padding: 5px; - .scroll-area { - max-height: 600px; + max-height: 250px; overflow-y: scroll; padding-right: 5px; } } + .rotated { + -ms-transform: rotate(180deg); /* IE 9 */ + transform: rotate(180deg); + } + div.navbar-menu { margin-left: 5px; } @@ -107,12 +65,10 @@ margin-right: 5px; } - .mb-3 { margin-bottom: 10px; } - img.inline { height: 30px; align: middle; @@ -170,12 +126,12 @@ div.drugstone.network { height: 100%; position: relative; - } div.card.network { width: 100%; height: $height; + overflow: hidden; } div.parent { @@ -192,7 +148,6 @@ justify-content: center; } - div.drugstone.explorer { height: $height; margin-left: 10px; @@ -233,18 +188,18 @@ font-size: $b-text-smaller-font-size; } - .button-small { - padding: 0 10px 0 10px !important; - height: 36px !important; - } - .switch-small { - padding: $button-small-padding; - height: 36px !important; - } - .dropdown-item { - color: var(--drgstn-text-primary) + color: var(--drgstn-text-primary); } -} + // general network settings, some will be overwritten in analysis + .analysis-view { + .network-footer-toolbar { + z-index: $analysis-network-footer-container-z; + .network-toolbar-toggle { + z-index: $analysis-network-footer-container-toggle-z; + } + } + } +} diff --git a/src/stylesheets/theme-styles.scss b/src/stylesheets/theme-styles.scss index 8df7c7d4f1aa42e7f249beffda78cc4eeb43a4b3..ebb7699580e2aa0640ac635071098f368376927e 100644 --- a/src/stylesheets/theme-styles.scss +++ b/src/stylesheets/theme-styles.scss @@ -3,7 +3,7 @@ --drgstn-secondary: #fd6818; --drgstn-success: #48C774; --drgstn-warning: #ffdd00; - --drgstn-danger: #c527ff; + --drgstn-danger: #f14668; --drgstn-background: #ffffff; --drgstn-panel: #f7f7f7; --drgstn-info: #61c43d; @@ -75,6 +75,10 @@ background-color: var(--drgstn-panel) !important; } + .tabs li a{ + border-bottom: none; + } + .tabs li.is-active a, .tabs.is-toggle li.is-active a, .tabs a:hover { border-color: var(--drgstn-primary) !important; color: var(--drgstn-text-primary) !important; @@ -190,11 +194,7 @@ color: var(--drgstn-primary) !important; } - footer a .text-danger:focus, footer a .text-danger:hover { - color: var(--drgstn-danger) !important; - } - - footer .text-danger, a .text-danger { + .has-text-danger { color: var(--drgstn-danger) !important; } @@ -202,7 +202,6 @@ background-color: var(--drgstn-primary) !important; } - .ng-select .ng-select-container .ng-value-container .ng-input > input { color: var(--drgstn-text-primary) !important; } diff --git a/src/stylesheets/theme.css b/src/stylesheets/theme.css index 167af160e32c31b5ea6864f1b720c1984475146e..afb54953f06bdba6c9b91ac64397d2dd7ae3ad03 100644 --- a/src/stylesheets/theme.css +++ b/src/stylesheets/theme.css @@ -3,7 +3,7 @@ --drgstn-secondary: #cb32dc; --drgstn-success: #7236d4; --drgstn-warning: #FFDD57FF; - --drgstn-danger: #47efd6; + --drgstn-danger: #f14668; --drgstn-background: #FFFFFF; --drgstn-panel: #ff3a3a; --drgstn-info: #61c43d; diff --git a/src/stylesheets/variables.scss b/src/stylesheets/variables.scss index 16a831781f6101953e2b75ce2a19fa40319eee24..221d033400e869870e46fcaa0fb5d0d5aa7c07a9 100644 --- a/src/stylesheets/variables.scss +++ b/src/stylesheets/variables.scss @@ -5,8 +5,17 @@ $row-data-selector-height: auto; $network-header-height: 3rem; $network-footer-height: 0rem; $network-footer-width: 15rem; -$network-footer-right-closed: -12rem; +$network-footer-width-small: 12rem; +$network-closed-width: 3rem; +$network-closed-width-small: 2.5rem; +$network-footer-right-closed: calc( + -#{$network-footer-width} + #{$network-closed-width} +); +$network-footer-right-closed-small: calc( + -#{$network-footer-width-small} + #{$network-closed-width-small} +); $network-footer-container-z: 20; +$network-footer-container-toggle-z: 21; $network-footer-inner-container-height: 25rem; $network-footer-space-button-to-border: 0.5rem; @@ -14,13 +23,14 @@ $network-footer-space-button-to-border: 0.5rem; $explorer-network-z: 10; $explorer-networklegend-z: 20; $explorer-networklegend-foreground-z: 21; -$explorer-network-toolbar-dropdown-z: 20; $analysis-view-z: 30; $analysis-network-z: 40; $analysis-network-legend-z: 40; -$analysis-network-toolbar-dropdown-z: 50; +$analysis-network-footer-container-z: 60; +$analysis-network-footer-container-toggle-z: 70; +$network-tissue-options-z: 80; $analysis-tab-header-height: 2.5rem; $analysis-tab-header-padding: 1.5rem; @@ -34,11 +44,13 @@ $legend-star-size: 20px; $legend-square-width: $legend-row-height; $legend-edge-width: 20px; $legend-edge-height: 3px; -$legend-star-color: #FC0; +$legend-star-color: #fc0; $legend-triangle-size: $legend-row-height; $legend-triangle-height: 29px; $legend-hexagon-font-size: 38px; $legend-diamond-size: 25px; +$legend-scaling: 0.8; +$legend-scaling-small: 0.6; $height: 100%; @@ -50,6 +62,3 @@ $b-text-small-font-size: 14px; $b-text-smaller-font-size: 12px; $text-normal-font-size: 12px; $text-small-font-size: 11px; - -$button-small-padding: 3px 10px 3px 10px; -