diff --git a/src/app/cartography/shared/models/layer.ts b/src/app/cartography/shared/models/layer.ts new file mode 100644 index 00000000..88b9ea57 --- /dev/null +++ b/src/app/cartography/shared/models/layer.ts @@ -0,0 +1,10 @@ +import {Drawing} from "./drawing"; +import {Link} from "./link"; +import {Node} from "./node"; + +export class Layer { + index: number; + nodes: Node[]; + drawings: Drawing[]; + links: Link[]; +} diff --git a/src/app/cartography/shared/models/node.ts b/src/app/cartography/shared/models/node.ts index 9572985c..f53e16b7 100644 --- a/src/app/cartography/shared/models/node.ts +++ b/src/app/cartography/shared/models/node.ts @@ -2,6 +2,7 @@ import {Label} from "./label"; import {Port} from "../../../shared/models/port"; import {Selectable} from "../managers/selection-manager"; + export class Node implements Selectable { command_line: string; compute_id: string; diff --git a/src/app/cartography/shared/widgets/drawings.widget.ts b/src/app/cartography/shared/widgets/drawings.widget.ts index b6eedd34..6c2c24b6 100644 --- a/src/app/cartography/shared/widgets/drawings.widget.ts +++ b/src/app/cartography/shared/widgets/drawings.widget.ts @@ -1,14 +1,20 @@ import {Widget} from "./widget"; import {Drawing} from "../models/drawing"; import {SVGSelection} from "../models/types"; +import {Layer} from "../models/layer"; export class DrawingsWidget implements Widget { constructor() {} - public draw(view: SVGSelection, drawings: Drawing[]) { - const drawing = view.selectAll('g.drawing') - .data(drawings); + public draw(view: SVGSelection, drawings?: Drawing[]) { + const drawing = view + .selectAll('g.drawing') + .data((l: Layer) => { + return l.drawings; + }, (d: Drawing) => { + return d.drawing_id; + }); const drawing_enter = drawing.enter() .append('g') @@ -53,8 +59,4 @@ export class DrawingsWidget implements Widget { drawing.exit().remove(); } - - private appendSVG(svg: string) { - - } } diff --git a/src/app/cartography/shared/widgets/graph.widget.ts b/src/app/cartography/shared/widgets/graph.widget.ts index 9cada770..1613f9f1 100644 --- a/src/app/cartography/shared/widgets/graph.widget.ts +++ b/src/app/cartography/shared/widgets/graph.widget.ts @@ -10,6 +10,9 @@ import { DrawingsWidget } from "./drawings.widget"; import { DrawingLineWidget } from "./drawing-line.widget"; import {SelectionTool} from "../tools/selection-tool"; import {MovingTool} from "../tools/moving-tool"; +import {LayersWidget} from "./layers.widget"; +import {Layer} from "../models/layer"; + export class GraphLayout implements Widget { private nodes: Node[] = []; @@ -22,6 +25,7 @@ export class GraphLayout implements Widget { private drawingLineTool: DrawingLineWidget; private selectionTool: SelectionTool; private movingTool: MovingTool; + private layersWidget: LayersWidget; private centerZeroZeroPoint = true; @@ -32,6 +36,7 @@ export class GraphLayout implements Widget { this.drawingLineTool = new DrawingLineWidget(); this.selectionTool = new SelectionTool(); this.movingTool = new MovingTool(); + this.layersWidget = new LayersWidget(); } public setNodes(nodes: Node[]) { @@ -54,6 +59,10 @@ export class GraphLayout implements Widget { return this.linksWidget; } + public getDrawingsWidget() { + return this.drawingsWidget; + } + public getDrawingLineTool() { return this.drawingLineTool; } @@ -89,9 +98,56 @@ export class GraphLayout implements Widget { (ctx: Context) => `translate(${ctx.getSize().width / 2}, ${ctx.getSize().height / 2})`); } - this.linksWidget.draw(canvas, this.links); - this.nodesWidget.draw(canvas, this.nodes); - this.drawingsWidget.draw(canvas, this.drawings); + + const layers = {}; + + this.nodes.forEach((n: Node) => { + const key = n.z.toString(); + if (!(key in layers)) { + layers[key] = new Layer(); + layers[key].nodes = []; + layers[key].drawings = []; + layers[key].links = []; + } + layers[key].nodes.push(n); + }); + + + this.drawings.forEach((d: Drawing) => { + const key = d.z.toString(); + if (!(key in layers)) { + layers[key] = new Layer(); + layers[key].nodes = []; + layers[key].drawings = []; + layers[key].links = []; + } + layers[key].drawings.push(d); + }); + + this.links.forEach((l: Link) => { + if (!l.source || !l.target) { + return; + } + + const key = Math.min(l.source.z, l.target.z).toString(); + + if (!(key in layers)) { + layers[key] = new Layer(); + layers[key].nodes = []; + layers[key].drawings = []; + layers[key].links = []; + } + layers[key].links.push(l); + }); + + const layers_list: Layer[] = Object.keys(layers).sort((a: string, b: string) => { + return Number(a) - Number(b); + }).map((key: string) => { + return layers[key]; + }); + + this.layersWidget.graphLayout = this; + this.layersWidget.draw(canvas, layers_list); this.drawingLineTool.draw(view, context); this.selectionTool.draw(view, context); diff --git a/src/app/cartography/shared/widgets/layers.widget.ts b/src/app/cartography/shared/widgets/layers.widget.ts new file mode 100644 index 00000000..2517f2dd --- /dev/null +++ b/src/app/cartography/shared/widgets/layers.widget.ts @@ -0,0 +1,38 @@ +import { Widget } from "./widget"; +import { SVGSelection } from "../models/types"; +import { GraphLayout } from "./graph.widget"; +import { Layer } from "../models/layer"; + + +export class LayersWidget implements Widget { + public graphLayout: GraphLayout; + + public draw(view: SVGSelection, layers: Layer[]) { + + const layers_selection = view + .selectAll('g.layer') + .data(layers); + + layers_selection + .enter() + .append('g') + .attr('class', 'layer') + .attr('data-index', (layer: Layer) => layer.index); + + layers_selection + .exit() + .remove(); + + this.graphLayout + .getNodesWidget() + .draw(layers_selection); + + this.graphLayout + .getDrawingsWidget() + .draw(layers_selection); + + this.graphLayout + .getLinksWidget() + .draw(layers_selection); + } +} diff --git a/src/app/cartography/shared/widgets/links.widget.ts b/src/app/cartography/shared/widgets/links.widget.ts index a989ec04..4dfb9f61 100644 --- a/src/app/cartography/shared/widgets/links.widget.ts +++ b/src/app/cartography/shared/widgets/links.widget.ts @@ -1,12 +1,13 @@ -import {BaseType, select, Selection} from "d3-selection"; +import { select } from "d3-selection"; import { Widget } from "./widget"; import { SVGSelection } from "../models/types"; import { Link } from "../models/link"; import { LinkStatus } from "../models/link-status"; import { MultiLinkCalculatorHelper } from "../../map/helpers/multi-link-calculator-helper"; -import {SerialLinkWidget} from "./serial-link.widget"; -import {EthernetLinkWidget} from "./ethernet-link.widget"; +import { SerialLinkWidget } from "./serial-link.widget"; +import { EthernetLinkWidget } from "./ethernet-link.widget"; +import { Layer } from "../models/layer"; export class LinksWidget implements Widget { @@ -96,29 +97,30 @@ export class LinksWidget implements Widget { }); } - public draw(view: SVGSelection, links: Link[]) { - const self = this; - - this.multiLinkCalculatorHelper.assignDataToLinks(links); - - const linksLayer = view.selectAll("g.links").data([{}]); - linksLayer - .enter() - .append('g') - .attr("class", "links"); - - const link = linksLayer + public draw(view: SVGSelection, links?: Link[]) { + const link = view .selectAll("g.link") - .data(links.filter((l: Link) => { - return l.target && l.source; - })); + .data((layer: Layer) => { + console.log(layer.links); + if (layer.links) { + const layer_links = layer.links.filter((l: Link) => { + return l.target && l.source; + }); + this.multiLinkCalculatorHelper.assignDataToLinks(layer_links); + return layer_links; + } + return []; + }, (l: Link) => { + return l.link_id; + }); + 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) + .attr('map-target', (l: Link) => l.target.node_id); const merge = link.merge(link_enter); diff --git a/src/app/cartography/shared/widgets/nodes.widget.ts b/src/app/cartography/shared/widgets/nodes.widget.ts index 8e456804..ffb8bb8c 100644 --- a/src/app/cartography/shared/widgets/nodes.widget.ts +++ b/src/app/cartography/shared/widgets/nodes.widget.ts @@ -1,9 +1,10 @@ import { Widget } from "./widget"; import { Node } from "../models/node"; import { SVGSelection } from "../models/types"; -import {event, select} from "d3-selection"; +import {event, select, Selection} from "d3-selection"; import {D3DragEvent, drag} from "d3-drag"; import {Symbol} from "../models/symbol"; +import {Layer} from "../models/layer"; export class NodesWidget implements Widget { @@ -78,16 +79,23 @@ export class NodesWidget implements Widget { } - public draw(view: SVGSelection, nodes: Node[]) { + public draw(view: SVGSelection, nodes?: Node[]) { const self = this; - const node = view - .selectAll('g.node') - .data(nodes, (n: Node) => { - return n.node_id; - }); + let nodes_selection: Selection = view + .selectAll('g.node'); - const node_enter = node + if (nodes) { + nodes_selection = nodes_selection.data(nodes); + } else { + nodes_selection = nodes_selection.data((l: Layer) => { + return l.nodes; + }, (n: Node) => { + return n.node_id; + }); + } + + const node_enter = nodes_selection .enter() .append('g') .attr('class', 'node'); @@ -134,7 +142,7 @@ export class NodesWidget implements Widget { .attr('y', '0'); } - const node_merge = node + const node_merge = nodes_selection .merge(node_enter) .classed('selected', (n: Node) => n.is_selected) .on("contextmenu", function (n: Node, i: number) { @@ -175,7 +183,7 @@ export class NodesWidget implements Widget { node_merge.call(dragging()); - node + nodes_selection .exit() .remove(); } diff --git a/src/app/shared/services/http-server.service.spec.ts b/src/app/shared/services/http-server.service.spec.ts index 1950af56..ac433c1c 100644 --- a/src/app/shared/services/http-server.service.spec.ts +++ b/src/app/shared/services/http-server.service.spec.ts @@ -6,6 +6,11 @@ import { Server } from '../models/server'; import { HttpServer } from './http-server.service'; +class MyType { + id: number; +} + + describe('HttpServer', () => { let httpClient: HttpClient; let httpTestingController: HttpTestingController; @@ -44,6 +49,37 @@ describe('HttpServer', () => { expect(req.request.responseType).toEqual("json"); }); + it('should make GET query for get method and return instance of type', () => { + const testData: MyType = {id: 3}; + + service.get(server, '/test').subscribe(data => { + expect(data instanceof MyType).toBeFalsy(); + expect(data).toEqual(testData); + }); + + const req = httpTestingController.expectOne('http://127.0.0.1:3080/v2/test'); + expect(req.request.method).toEqual("GET"); + expect(req.request.responseType).toEqual("json"); + + req.flush({id: 3}); + }); + + it('HttpClient should make GET query for get method and return instance of type', () => { + const testData: MyType = {id: 3}; + + httpClient.get('http://localhost/test').subscribe(data => { + // when this condition is true, it would be great + expect(data instanceof MyType).toBeFalsy(); + expect(data).toEqual(testData); + }); + + const req = httpTestingController.expectOne('http://localhost/test'); + expect(req.request.method).toEqual("GET"); + expect(req.request.responseType).toEqual("json"); + + req.flush(testData); + }); + it('should make GET query for getText method', () => { service.getText(server, '/test').subscribe();