diff --git a/src/app/cartography/map/map.component.ts b/src/app/cartography/map/map.component.ts index 2c1b9488..ffa04ec5 100644 --- a/src/app/cartography/map/map.component.ts +++ b/src/app/cartography/map/map.component.ts @@ -2,7 +2,7 @@ import { Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChange } from '@angular/core'; import { D3, D3Service } from 'd3-ng2-service'; -import { Selection } from 'd3-selection'; +import {select, Selection} from 'd3-selection'; import { Node } from "../shared/models/node.model"; import { Link } from "../shared/models/link.model"; @@ -10,6 +10,7 @@ import { GraphLayout } from "../shared/widgets/graph.widget"; import { Context } from "../../map/models/context"; import { Size } from "../shared/models/size.model"; import { Drawing } from "../shared/models/drawing.model"; +import {SVGSelection} from "../../map/models/types"; @Component({ @@ -87,6 +88,17 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy { } this.graphLayout = new GraphLayout(); + + this.graphLayout.getNodesWidget().addOnNodeDraggingCallback((n: Node) => { + const linksWidget = this.graphLayout.getLinksWidget(); + linksWidget.select(this.svg).each(function(this: SVGGElement, link: Link) { + if (link.target.node_id === n.node_id || link.source.node_id === n.node_id) { + const selection = select(this); + linksWidget.revise(selection); + } + }); + }); + this.graphLayout.draw(this.svg, this.graphContext); } diff --git a/src/app/cartography/shared/widgets/graph.widget.ts b/src/app/cartography/shared/widgets/graph.widget.ts index df91ce62..56a1a93f 100644 --- a/src/app/cartography/shared/widgets/graph.widget.ts +++ b/src/app/cartography/shared/widgets/graph.widget.ts @@ -16,12 +16,24 @@ export class GraphLayout implements Widget { private links: Link[] = []; private drawings: Drawing[] = []; - private nodesWidget = new NodesWidget(); - private linksWidget = new LinksWidget(); - private drawingsWidget = new DrawingsWidget(); + private linksWidget: LinksWidget; + private nodesWidget: NodesWidget; + private drawingsWidget: DrawingsWidget; private centerZeroZeroPoint = true; + constructor() { + this.linksWidget = new LinksWidget(); + this.nodesWidget = new NodesWidget(); + + // this.nodesWidget.addOnNodeDraggingCallback((n: Node) => { + // this.linksWidget. + // // this.linksWidget.draw(); + // }); + + this.drawingsWidget = new DrawingsWidget(); + } + public setNodes(nodes: Node[]) { this.nodes = nodes; } @@ -38,6 +50,14 @@ export class GraphLayout implements Widget { return this.nodesWidget; } + public getLinksWidget() { + return this.linksWidget; + } + + public getCanvas(view: SVGSelection) { + + } + draw(view: SVGSelection, context: Context) { const self = this; @@ -55,12 +75,6 @@ export class GraphLayout implements Widget { (ctx: Context) => `translate(${ctx.getSize().width / 2}, ${ctx.getSize().height / 2})`); } - // const links = canvasEnter.append('g') - // .attr('class', 'links'); - // - // const nodes = canvasEnter.append('g') - // .attr('class', 'nodes'); - this.linksWidget.draw(canvas, this.links); this.nodesWidget.draw(canvas, this.nodes); this.drawingsWidget.draw(canvas, this.drawings); diff --git a/src/app/cartography/shared/widgets/links.widget.ts b/src/app/cartography/shared/widgets/links.widget.ts index 05455c3e..2d7ff4fb 100644 --- a/src/app/cartography/shared/widgets/links.widget.ts +++ b/src/app/cartography/shared/widgets/links.widget.ts @@ -1,4 +1,4 @@ -import { select } from "d3-selection"; +import {BaseType, select, Selection} from "d3-selection"; import { line } from "d3-shape"; import { Widget } from "./widget"; @@ -13,33 +13,21 @@ import {EthernetLinkWidget} from "./ethernet-link.widget"; export class LinksWidget implements Widget { private multiLinkCalculatorHelper = new MultiLinkCalculatorHelper(); - private getLinkWidget(link: Link) { + public getLinkWidget(link: Link) { if (link.link_type === 'serial') { return new SerialLinkWidget(); } return new EthernetLinkWidget(); } - public draw(view: SVGSelection, links: Link[]) { + public select(view: SVGSelection) { + return view.selectAll("g.link"); + } + + public revise(selection: Selection) { const self = this; - this.multiLinkCalculatorHelper.assignDataToLinks(links); - - const link = view - .selectAll("g.link") - .data(links.filter((l: Link) => { - return l.target && l.source; - })); - - const link_enter = link.enter() - .append('g') - .attr('class', 'link') - .attr('link_id', (l: Link) => l.link_id) - .attr('map-source', (l: Link) => l.source.node_id) - .attr('map-target', (l: Link) => l.target.node_id); - - link.merge(link_enter) - .each(function (this: SVGGElement, l: Link) { + selection.each(function (this: SVGGElement, l: Link) { const link_group = select(this); const link_widget = self.getLinkWidget(l); @@ -93,7 +81,28 @@ export class LinksWidget implements Widget { } return null; }); + } - link.exit().remove(); + public draw(view: SVGSelection, links: Link[]) { + const self = this; + + this.multiLinkCalculatorHelper.assignDataToLinks(links); + + const link = view + .selectAll("g.link") + .data(links.filter((l: Link) => { + return l.target && l.source; + })); + + const link_enter = link.enter() + .append('g') + .attr('class', 'link') + .attr('link_id', (l: Link) => l.link_id) + .attr('map-source', (l: Link) => l.source.node_id) + .attr('map-target', (l: Link) => l.target.node_id); + + this.revise(link.merge(link_enter)); + + link.exit().remove(); } } diff --git a/src/app/cartography/shared/widgets/nodes.widget.ts b/src/app/cartography/shared/widgets/nodes.widget.ts index f6d7f027..88ff8962 100644 --- a/src/app/cartography/shared/widgets/nodes.widget.ts +++ b/src/app/cartography/shared/widgets/nodes.widget.ts @@ -1,47 +1,66 @@ import { Widget } from "./widget"; import { Node } from "../models/node.model"; import { SVGSelection } from "../../../map/models/types"; -import { event } from "d3-selection"; +import {event, select} from "d3-selection"; +import {D3DragEvent, drag} from "d3-drag"; export interface NodeOnContextMenuListener { onContextMenu(): void; }; export class NodesWidget implements Widget { - private onContextMenuListener: NodeOnContextMenuListener; private onContextMenuCallback: (event: any, node: Node) => void; + private onNodeDraggedCallback: (event: any, node: Node) => void; + private onNodeDraggingCallbacks: ((event: any, node: Node) => void)[] = []; constructor() {} - public setOnContextMenuListener(onContextMenuListener: NodeOnContextMenuListener) { - this.onContextMenuListener = onContextMenuListener; - } - public setOnContextMenuCallback(onContextMenuCallback: (event: any, node: Node) => void) { this.onContextMenuCallback = onContextMenuCallback; } + public setOnNodeDraggedCallback(onNodeDraggedCallback: (event: any, node: Node) => void) { + this.onNodeDraggedCallback = onNodeDraggedCallback; + } + + public addOnNodeDraggingCallback(onNodeDraggingCallback: (n: Node) => void) { + this.onNodeDraggingCallbacks.push(onNodeDraggingCallback); + } + + private executeOnNodeDraggingCallback(n: Node) { + this.onNodeDraggingCallbacks.forEach((callback: (n: Node) => void) => { + callback(n); + }); + } + + public revise(selection: SVGSelection) { + selection + .attr('transform', (n: Node) => { + return `translate(${n.x},${n.y})`; + }); + + selection + .select('text.label') + .attr('x', (n: Node) => n.label.x) + .attr('y', (n: Node) => n.label.y) + .attr('style', (n: Node) => n.label.style) + .text((n: Node) => n.label.text); + + selection + .select('text.node_point_label') + .text((n: Node) => `(${n.x}, ${n.y})`); + + } + public draw(view: SVGSelection, nodes: Node[]) { const self = this; - // function dragged(this: SVGElement, node: Node) { - // const element = this; - // const e: D3DragEvent = d3.event; - // - // d3.select(this) - // .attr('transform', `translate(${e.x},${e.y})`); - // - // node.x = e.x; - // node.y = e.y; - // } - const node = view.selectAll('g.node') .data(nodes); const node_enter = node.enter() .append('g') .attr('class', 'node'); - // .call(d3.drag().on('drag', dragged)) const node_image = node_enter.append('image') .attr('xlink:href', (n: Node) => 'data:image/svg+xml;base64,' + btoa(n.icon.raw)) @@ -67,19 +86,32 @@ export class NodesWidget implements Widget { if (self.onContextMenuCallback !== null) { self.onContextMenuCallback(event, n); } - }) - .attr('transform', (n: Node) => { - return `translate(${n.x},${n.y})`; }); - node_merge.select('text.label') - .attr('x', (n: Node) => n.label.x) - .attr('y', (n: Node) => n.label.y) - .attr('style', (n: Node) => n.label.style) - .text((n: Node) => n.label.text); + this.revise(node_merge); - node_merge.select('text.node_point_label') - .text((n: Node) => `(${n.x}, ${n.y})`); + const callback = function (this: SVGGElement, n: Node) { + const e: D3DragEvent = event; + + n.x = e.x; + n.y = e.y; + + self.revise(select(this)); + self.executeOnNodeDraggingCallback(n); + }; + + const dragging = () => { + return drag() + .on('drag', callback) + .on('end', (n: Node) => { + if (self.onNodeDraggedCallback) { + const e: D3DragEvent = event; + self.onNodeDraggedCallback(e, n); + } + }); + }; + + node_merge.call(dragging()); node.exit().remove(); } diff --git a/src/app/project-map/project-map.component.ts b/src/app/project-map/project-map.component.ts index 4b16b5fd..aef97fce 100644 --- a/src/app/project-map/project-map.component.ts +++ b/src/app/project-map/project-map.component.ts @@ -177,6 +177,21 @@ export class ProjectMapComponent implements OnInit { this.mapChild.graphLayout.getNodesWidget().setOnContextMenuCallback((event: any, node: Node) => { this.nodeContextMenu.open(node, event.clientY, event.clientX); }); + + this.mapChild.graphLayout.getNodesWidget().setOnNodeDraggedCallback((event: any, node: Node) => { + const index = this.nodes.findIndex((n: Node) => n.node_id === node.node_id); + if (index >= 0) { + this.nodes[index] = node; + this.mapChild.reload(); // temporary invocation + + this.nodeService + .updatePosition(this.server, node, node.x, node.y) + .subscribe((n: Node) => { + this.nodes[index] = node; + this.mapChild.reload(); // temporary invocation + }); + } + }); } onNodeCreation(appliance: Appliance) { diff --git a/src/app/shared/databases/node-database.ts b/src/app/shared/databases/node-database.ts new file mode 100644 index 00000000..e659bb12 --- /dev/null +++ b/src/app/shared/databases/node-database.ts @@ -0,0 +1,9 @@ +import {Node} from "../../cartography/shared/models/node.model"; + +export class Database { + +} + +export class NodeDatabase extends Database { + +} diff --git a/src/app/shared/services/node.service.ts b/src/app/shared/services/node.service.ts index 8c1d1ea5..62ff87d1 100644 --- a/src/app/shared/services/node.service.ts +++ b/src/app/shared/services/node.service.ts @@ -37,4 +37,12 @@ export class NodeService { {'x': x, 'y': y, 'compute_id': compute_id}); } + updatePosition(server: Server, node: Node, x: number, y: number): Observable { + return this.httpServer + .put(server, `/projects/${node.project_id}/nodes/${node.node_id}`, { + 'x': x, + 'y': y + }) + .map(response => response.json() as Node); + } }