diff --git a/src/app/components/network/network.component.ts b/src/app/components/network/network.component.ts index 9bfaccb779584216604b40c97f608a6753b87c82..a6b075a05d512377db92f0851b601c6b646b926a 100644 --- a/src/app/components/network/network.component.ts +++ b/src/app/components/network/network.component.ts @@ -23,6 +23,7 @@ import {NetworkHandlerService} from 'src/app/services/network-handler/network-ha import {LegendService} from 'src/app/services/legend-service/legend-service.service'; import {LoadingScreenService} from 'src/app/services/loading-screen/loading-screen.service'; import {version} from '../../../version'; + @Component({ selector: 'app-network', templateUrl: './network.component.html', @@ -30,6 +31,20 @@ import {version} from '../../../version'; }) export class NetworkComponent implements OnInit { + constructor( + public legendService: LegendService, + public networkHandler: NetworkHandlerService, + public analysis: AnalysisService, + public drugstoneConfig: DrugstoneConfigService, + public netex: NetexControllerService, + public omnipath: OmnipathControllerService, + public loadingScreen: LoadingScreenService) { + try { + this.versionString = version; + } catch (e) { + } + } + @Input() public networkType: NetworkType; @Input() public nodeData: NodeData; @@ -88,20 +103,7 @@ export class NetworkComponent implements OnInit { public nodeGroupsWithExpression: Set<string> = new Set(); - constructor( - public legendService: LegendService, - public networkHandler: NetworkHandlerService, - public analysis: AnalysisService, - public drugstoneConfig: DrugstoneConfigService, - - public netex: NetexControllerService, - public omnipath: OmnipathControllerService, - public loadingScreen: LoadingScreenService) { - try { - this.versionString = version; - }catch (e){ - } - } + private selectMode = false; ngOnInit(): void { this.networkHandler.networks[this.networkType] = this; @@ -111,6 +113,142 @@ export class NetworkComponent implements OnInit { return this.nodeData.nodes.length > 100 || this.nodeData.edges.length > 100; } + rectangleSelect(select: boolean) { + + this.selectMode = select; + const options = this.getOptions(); + if (!options.interaction) { + options.interaction = {}; + } + if (select) { + options.interaction.zoomView = false; + options.interaction.dragView = false; + this.initDragSelect(); + } else { + options.interaction.zoomView = true; + options.interaction.dragView = true; + this.removeDragSelect(); + } + this.updateOptions(options); + } + + public rect = undefined; + + + selectNodesFromHighlight() { + // const fromX; + // const toX; + // const fromY; + // const toY; + const nodesIdInDrawing = []; + const xRange = this.getStartToEnd(this.rect.startX, this.rect.w); + const yRange = this.getStartToEnd(this.rect.startY, this.rect.h); + + // @ts-ignore + const allNodes = this.nodeData.nodes.get(); + const selection = []; + for (let i = 0; i < allNodes.length; i++) { + const curNode = allNodes[i]; + const nodePosition = this.net.getPositions([curNode.id]); + const nodeXY = this.net.canvasToDOM({x: nodePosition[curNode.id].x, y: nodePosition[curNode.id].y}); + if (xRange.start <= nodeXY.x && nodeXY.x <= xRange.end && yRange.start <= nodeXY.y && nodeXY.y <= yRange.end) { + nodesIdInDrawing.push(curNode.id); + selection.push({id: curNode.id, label: curNode.label}); + } + } + this.net.selectNodes(nodesIdInDrawing); + console.log(selection); + } + + getStartToEnd(start, theLen) { + return theLen > 0 ? {start: start, end: start + theLen} : {start: start + theLen, end: start}; + } + + private ctx: CanvasRenderingContext2D; + private canvas: HTMLCanvasElement; + private net: any; + + initDragSelect() { + if (this.net === undefined) { + this.net = this.networkInternal; + this.canvas = this.net.canvas.frame.canvas; + this.canvas.oncontextmenu = () => false; + this.ctx = this.canvas.getContext('2d'); + console.log(this.ctx) + this.canvas.addEventListener('mousemove', this.dragMouseMove.bind(this)); + this.canvas.addEventListener('mousedown', this.dragMouseDown.bind(this)); + this.canvas.addEventListener('mouseup', this.dragMouseUp.bind(this)); + } + } + + + removeDragSelect() { + this.net = undefined; + if (this.canvas) { + this.canvas.removeEventListener('mousemove', this.dragMouseMove.bind(this)); + this.canvas.removeEventListener('mousedown', this.dragMouseDown.bind(this)); + this.canvas.removeEventListener('mouseup', this.dragMouseUp.bind(this)); + } + } + + private drag = false; + public offsetLeft = 0; + public offsetTop = 0; + + dragMouseMove(e) { + if (this.drag) { + this.restoreDrawingSurface(); + this.rect.w = (e.pageX - this.offsetLeft) - this.rect.startX; + this.rect.h = (e.pageY - this.offsetTop) - this.rect.startY; + + this.ctx.setLineDash([5]); + this.ctx.strokeStyle = 'rgb(0, 102, 0)'; + this.ctx.strokeRect(this.rect.startX, this.rect.startY, this.rect.w, this.rect.h); + this.ctx.setLineDash([]); + this.ctx.fillStyle = 'rgba(0, 255, 0, 0.2)'; + this.ctx.fillRect(this.rect.startX, this.rect.startY, this.rect.w, this.rect.h); + + } + } + + private canvasCursor = 'default'; + + dragMouseDown(e) { + if (e.button === 0) { + this.offsetLeft = e.target.getBoundingClientRect().left; + this.offsetTop = e.target.getBoundingClientRect().top; + this.saveDrawingSurface(); + this.rect = {}; + this.rect.startX = e.pageX - this.offsetLeft; + this.rect.startY = e.pageY - this.offsetTop; + this.drag = true; + this.canvasCursor = 'crosshair'; + } + } + + + dragMouseUp(e) { + if (e.button === 0) { + this.restoreDrawingSurface(); + this.drag = false; + + this.canvasCursor = 'default'; + this.selectNodesFromHighlight(); + } + } + + private drawingSurfaceImageData: ImageData; + + saveDrawingSurface() { + this.drawingSurfaceImageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height); + } + + + restoreDrawingSurface() { + this.ctx.putImageData(this.drawingSurfaceImageData, 0, 0); + } + + getResetInputNetwork(): NetworkData { const nodes = [...this.inputNetwork.nodes]; nodes.forEach(n => { @@ -442,6 +580,14 @@ export class NetworkComponent implements OnInit { }); } + public getOptions() { + return this.networkInternal.options; + } + + public updateOptions(options: any) { + this.networkInternal.setOptions(options); + } + public openEdgeSummary(edgeId: string) { this.selectedWrapper = undefined; const edgeMap = this.nodeData.edges.get({returnType: 'Object'}); diff --git a/src/app/pages/explorer-page/explorer-page.component.html b/src/app/pages/explorer-page/explorer-page.component.html index f8541ecd93e0094528c3b485c42ccd14048c6a94..d6f7b11e19424a81bb226b3ed49cd58ab090cd91 100644 --- a/src/app/pages/explorer-page/explorer-page.component.html +++ b/src/app/pages/explorer-page/explorer-page.component.html @@ -428,12 +428,32 @@ (click)="analysis.addFirstNeighbors()" class="card-footer-item text-primary" tooltipPosition="top" - pTooltip="Add first neighbor proteins of selection to selection." + pTooltip="Add first neighbor proteins of selection." > <app-fa-solid-icon icon="plus"></app-fa-solid-icon> <span>Add First neighbors</span> </a> </footer> + <footer class="card-footer"> + <a + (click)="analysis.addConnectedComponents()" + class="card-footer-item text-primary" + tooltipPosition="top" + pTooltip="Add full connected components of all nodes in selection." + > + <app-fa-solid-icon icon="plus"></app-fa-solid-icon> + <span>Add Connected Components</span> + </a> +<!-- <a--> +<!-- (click)="analysis.rectangleSelect()"--> +<!-- class="card-footer-item text-primary"--> +<!-- tooltipPosition="top"--> +<!-- pTooltip="Use rectangle select mode to select genes."--> +<!-- >--> +<!-- <app-fa-solid-icon icon="plus"></app-fa-solid-icon>--> +<!-- <span>Select mode</span>--> +<!-- </a>--> + </footer> <footer class="card-footer"> <a class="card-footer-item text-primary" diff --git a/src/app/pages/explorer-page/explorer-page.component.scss b/src/app/pages/explorer-page/explorer-page.component.scss index cd4da80f526376dba8f5b43e5b57ee805d948f30..1464f1993b22e29c1414c1d8b6600d21346d6076 100644 --- a/src/app/pages/explorer-page/explorer-page.component.scss +++ b/src/app/pages/explorer-page/explorer-page.component.scss @@ -7,6 +7,10 @@ margin: auto; } +.seed-selection { + max-height: 600px; +} + .quick-find { .notification { padding: 1.25rem 1rem 1.25rem 1rem !important; diff --git a/src/app/services/analysis/analysis.service.ts b/src/app/services/analysis/analysis.service.ts index d68cc591a3f607366a4975f5d790f058d54cb76f..fd8b8051cd93094d22e812be0f78c20cd65987ff 100644 --- a/src/app/services/analysis/analysis.service.ts +++ b/src/app/services/analysis/analysis.service.ts @@ -240,6 +240,42 @@ export class AnalysisService { this.addItems(wrappers); } + + public rectangleSelect(){ + this.networkHandler.activeNetwork.rectangleSelect(true); + } + + // Identifies connected components of all selected nodes and adds all nodes of the components to the selection + public addConnectedComponents() { + const wrappers: Wrapper[] = []; + const mappedNodes = {}; + this.networkHandler.activeNetwork.currentViewNodes.forEach((node) => { + if (node.drugstoneType === 'protein' && node.drugstoneId) { + mappedNodes[node.label] = node; + } + }); + const selectedNodes = new Set(this.selectedItems.keys()); + let previousSize = 0; + while (previousSize !== selectedNodes.size) { + previousSize = selectedNodes.size; + this.networkHandler.activeNetwork.currentViewEdges.forEach(edge => { + if (selectedNodes.has(edge.from)) { + selectedNodes.add(edge.to); + } + if (selectedNodes.has(edge.to)) { + selectedNodes.add(edge.from); + } + }); + } + + selectedNodes.forEach(n => { + if (!this.selectedItems.has(n)){ + wrappers.push(getWrapperFromNode(mappedNodes[n])); + } + }); + this.addItems(wrappers); + } + public addAllToSelection() { const wrappers: Wrapper[] = []; const unmappedNodes = [];