Drag interface labels

This commit is contained in:
ziajka 2018-11-26 13:36:33 +01:00
parent 01cefc5c79
commit 8b91549984
11 changed files with 220 additions and 88 deletions

View File

@ -12,9 +12,11 @@ import { MapNode } from '../../models/map/map-node';
import { MapDrawing } from '../../models/map/map-drawing';
import { DraggedDataEvent } from '../../events/event-source';
import { select } from 'd3-selection';
import { NodeWidget } from '../../widgets/node';
import { MapLabel } from '../../models/map/map-label';
import { LabelWidget } from '../../widgets/label';
import { InterfaceLabelWidget } from '../../widgets/interface-label';
import { MapLinkNode } from '../../models/map/map-link-node';
import { LinksEventSource } from '../../events/links-event-source';
@Component({
selector: 'app-draggable-selection',
@ -33,10 +35,12 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
private drawingsWidget: DrawingsWidget,
private linksWidget: LinksWidget,
private labelWidget: LabelWidget,
private interfaceWidget: InterfaceLabelWidget,
private selectionManager: SelectionManager,
private nodesEventSource: NodesEventSource,
private drawingsEventSource: DrawingsEventSource,
private graphDataManager: GraphDataManager
private graphDataManager: GraphDataManager,
private linksEventSource: LinksEventSource
) { }
ngOnInit() {
@ -45,7 +49,8 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
this.start = merge(
this.nodesWidget.draggable.start,
this.drawingsWidget.draggable.start,
this.labelWidget.draggable.start
this.labelWidget.draggable.start,
this.interfaceWidget.draggable.start
).subscribe((evt: DraggableStart<any>) => {
const selected = this.selectionManager.getSelected();
if (evt.datum instanceof MapNode) {
@ -65,12 +70,19 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
this.selectionManager.setSelected([evt.datum]);
}
}
if (evt.datum instanceof MapLinkNode) {
if (selected.filter((item) => item instanceof MapLinkNode && item.id === evt.datum.id).length === 0) {
this.selectionManager.setSelected([evt.datum]);
}
}
});
this.drag = merge(
this.nodesWidget.draggable.drag,
this.drawingsWidget.draggable.drag,
this.labelWidget.draggable.drag
this.labelWidget.draggable.drag,
this.interfaceWidget.draggable.drag
).subscribe((evt: DraggableDrag<any>) => {
const selected = this.selectionManager.getSelected();
const selectedNodes = selected.filter((item) => item instanceof MapNode);
@ -108,12 +120,33 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
this.labelWidget.redrawLabel(svg, label);
});
// update interface labels
selected.filter((item) => item instanceof MapLinkNode).forEach((interfaceLabel: MapLinkNode) => {
const isParentNodeSelected = selectedNodes.filter((node) => node.id === interfaceLabel.nodeId).length > 0;
if (isParentNodeSelected) {
return;
}
const link = this.graphDataManager.getLinks().filter((link) => link.nodes[0].id === interfaceLabel.id || link.nodes[1].id === interfaceLabel.id)[0];
if(link.nodes[0].id === interfaceLabel.id) {
link.nodes[0].label.x += evt.dx;
link.nodes[0].label.y += evt.dy;
}
if(link.nodes[1].id === interfaceLabel.id) {
link.nodes[1].label.x += evt.dx;
link.nodes[1].label.y += evt.dy;
}
this.linksWidget.redrawLink(svg, link);
});
});
this.end = merge(
this.nodesWidget.draggable.end,
this.drawingsWidget.draggable.end,
this.labelWidget.draggable.end,
this.interfaceWidget.draggable.end
).subscribe((evt: DraggableEnd<any>) => {
const selected = this.selectionManager.getSelected();
const selectedNodes = selected.filter((item) => item instanceof MapNode);
@ -135,7 +168,17 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
this.nodesEventSource.labelDragged.emit(new DraggedDataEvent<MapLabel>(label, evt.dx, evt.dy));
});
selected.filter((item) => item instanceof MapLinkNode).forEach((label: MapLinkNode) => {
const isParentNodeSelected = selectedNodes.filter((node) => node.id === label.nodeId).length > 0;
if (isParentNodeSelected) {
return;
}
this.linksEventSource.interfaceDragged.emit(new DraggedDataEvent<MapLinkNode>(label, evt.dx, evt.dy));
});
});
}
ngOnDestroy() {

View File

@ -44,11 +44,35 @@ export class SelectionControlComponent implements OnInit, OnDestroy {
return this.inRectangleHelper.inRectangle(rectangle, labelX, labelY);
}).map((node) => node.label);
const selectedInterfacesLabelsSources = this.graphDataManager.getLinks().filter((link) => {
if (link.source === undefined || link.nodes.length != 2 || link.nodes[0].label === undefined) {
return false;
}
const interfaceLabelX = link.source.x + link.nodes[0].label.x;
const interfaceLabelY = link.source.y + link.nodes[0].label.y;
return this.inRectangleHelper.inRectangle(rectangle, interfaceLabelX, interfaceLabelY);
}).map((link) => link.nodes[0]);
const selectedInterfacesLabelsTargets = this.graphDataManager.getLinks().filter((link) => {
if (link.target === undefined || link.nodes.length != 2 || link.nodes[1].label === undefined) {
return false;
}
const interfaceLabelX = link.target.x + link.nodes[1].label.x;
const interfaceLabelY = link.target.y + link.nodes[1].label.y;
return this.inRectangleHelper.inRectangle(rectangle, interfaceLabelX, interfaceLabelY);
}).map((link) => link.nodes[1]);
const selectedInterfaces = [
...selectedInterfacesLabelsSources,
...selectedInterfacesLabelsTargets,
]
const selected = [
...selectedNodes,
...selectedLinks,
...selectedDrawings,
...selectedLabels
...selectedLabels,
...selectedInterfaces,
];
this.selectionManager.setSelected(selected);

View File

@ -9,15 +9,21 @@ import { MapLinkNode } from "../../models/map/map-link-node";
@Injectable()
export class LinkNodeToMapLinkNodeConverter implements Converter<LinkNode, MapLinkNode> {
constructor(
private labelToMapLabel: LabelToMapLabelConverter
private labelToMapLabel: LabelToMapLabelConverter,
) {}
convert(linkNode: LinkNode) {
convert(linkNode: LinkNode, paramaters?: {[link_id: string]: string}) {
const mapLinkNode = new MapLinkNode();
mapLinkNode.nodeId = linkNode.node_id;
mapLinkNode.adapterNumber = linkNode.adapter_number;
mapLinkNode.portNumber = linkNode.port_number;
mapLinkNode.label = this.labelToMapLabel.convert(linkNode.label);
if (paramaters !== undefined) {
mapLinkNode.linkId = paramaters.link_id;
mapLinkNode.id = `${mapLinkNode.nodeId}-${mapLinkNode.linkId}`;
}
return mapLinkNode;
}
}

View File

@ -19,7 +19,7 @@ export class LinkToMapLinkConverter implements Converter<Link, MapLink> {
mapLink.captureFilePath = link.capture_file_path;
mapLink.capturing = link.capturing;
mapLink.linkType = link.link_type;
mapLink.nodes = link.nodes.map((linkNode) => this.linkNodeToMapLinkNode.convert(linkNode));
mapLink.nodes = link.nodes.map((linkNode) => this.linkNodeToMapLinkNode.convert(linkNode,{ link_id: link.link_id }));
mapLink.projectId = link.project_id;
return mapLink;
}

View File

@ -7,13 +7,13 @@ import { MapLabel } from "../../models/map/map-label";
@Injectable()
export class MapLabelToLabelConverter implements Converter<MapLabel, Label> {
convert(mapLabel: MapLabel, paramters?: any) {
const label = new Label();
label.rotation = mapLabel.rotation;
label.style = mapLabel.style;
label.text = mapLabel.text;
label.x = mapLabel.x;
label.y = mapLabel.y;
return label;
}
convert(mapLabel: MapLabel) {
const label = new Label();
label.rotation = mapLabel.rotation;
label.style = mapLabel.style;
label.text = mapLabel.text;
label.x = mapLabel.x;
label.y = mapLabel.y;
return label;
}
}

View File

@ -1,8 +1,11 @@
import { Injectable, EventEmitter } from "@angular/core";
import { MapLinkCreated } from "./links";
import { MapLinkNode } from "../models/map/map-link-node";
import { DraggedDataEvent } from "./event-source";
@Injectable()
export class LinksEventSource {
public created = new EventEmitter<MapLinkCreated>();
public interfaceDragged = new EventEmitter<DraggedDataEvent<MapLinkNode>>();
}

View File

@ -1,5 +1,8 @@
export class InterfaceLabel {
import { Indexed } from "../datasources/map-datasource";
export class InterfaceLabel implements Indexed {
constructor(
public id: string,
public link_id: string,
public direction: string,
public x: number,

View File

@ -1,8 +1,11 @@
import { MapLabel } from "./map-label";
import { Indexed } from "../../datasources/map-datasource";
export class MapLinkNode {
nodeId: string;
adapterNumber: number;
portNumber: number;
label: MapLabel;
export class MapLinkNode implements Indexed {
id: string;
nodeId: string;
linkId: string;
adapterNumber: number;
portNumber: number;
label: MapLabel;
}

View File

@ -6,50 +6,53 @@ import { CssFixer } from "../helpers/css-fixer";
import { select } from "d3-selection";
import { MapLink } from "../models/map/map-link";
import { FontFixer } from "../helpers/font-fixer";
import { SelectionManager } from "../managers/selection-manager";
import { MapLinkNode } from "../models/map/map-link-node";
import { MapNode } from "../models/map/map-node";
import { Draggable } from "../events/draggable";
@Injectable()
export class InterfaceLabelWidget {
static SURROUNDING_TEXT_BORDER = 5;
public draggable = new Draggable<SVGGElement, MapLinkNode>();
static SURROUNDING_TEXT_BORDER = 5;
private enabled = true;
constructor(
private cssFixer: CssFixer,
private fontFixer: FontFixer
private fontFixer: FontFixer,
private selectionManager: SelectionManager
) {
}
public setEnabled(enabled: boolean) {
public setEnabled(enabled) {
this.enabled = enabled;
}
draw(selection: SVGSelection) {
const link_node_position = selection
.selectAll<SVGGElement, MapLinkNode>('g.link_node_position')
.data((link: MapLink) => [
[link.source, link.nodes[0]],
[link.target, link.nodes[1]]
]);
const labels = selection
const enter_link_node_position = link_node_position
.enter()
.append<SVGGElement>('g')
.classed('link_node_position', true);
const merge_link_node_position = link_node_position.merge(enter_link_node_position);
merge_link_node_position.attr('transform', (nodeAndMapLinkNode: [MapNode, MapLinkNode]) => {
return `translate(${nodeAndMapLinkNode[0].x}, ${nodeAndMapLinkNode[0].y})`;
});
const labels = merge_link_node_position
.selectAll<SVGGElement, InterfaceLabel>('g.interface_label_container')
.data((l: MapLink) => {
const sourceInterface = new InterfaceLabel(
l.id,
'source',
Math.round(l.source.x + l.nodes[0].label.x),
Math.round(l.source.y + l.nodes[0].label.y),
l.nodes[0].label.text,
l.nodes[0].label.style,
l.nodes[0].label.rotation
);
const targetInterface = new InterfaceLabel(
l.id,
'target',
Math.round( l.target.x + l.nodes[1].label.x),
Math.round( l.target.y + l.nodes[1].label.y),
l.nodes[1].label.text,
l.nodes[1].label.style,
l.nodes[1].label.rotation
);
.data((nodeAndMapLinkNode: [MapNode, MapLinkNode]) => {
if (this.enabled) {
return [sourceInterface, targetInterface];
return [nodeAndMapLinkNode[1]];
}
return [];
});
@ -60,67 +63,66 @@ export class InterfaceLabelWidget {
.classed('interface_label_container', true);
// create surrounding rect
// enter
// .append<SVGRectElement>('rect')
// .attr('class', 'interface_label_border');
enter
.append<SVGRectElement>('rect')
.attr('class', 'interface_label_selection');
// create label
enter
.append<SVGTextElement>('text')
.attr('class', 'interface_label noselect')
.attr('interface_label_id', (i: InterfaceLabel) => `${i.direction}-${i.link_id}`)
.attr('interface_label_id', (i: MapLinkNode) => `${i.id}`)
const merge = labels
.merge(enter);
merge
.attr('width', 100)
.attr('height', 100)
.attr('transform', function(this: SVGGElement, l: InterfaceLabel) {
const textLabel = merge.select<SVGTextElement>(`text[interface_label_id="${l.direction}-${l.link_id}"]`);
const bbox = textLabel.node().getBBox();
const x = l.x ;
const y = l.y + bbox.height;
return `translate(${x}, ${y}) rotate(${l.rotation}, ${x}, ${y})`;
})
.classed('selected', (l: InterfaceLabel) => false);
// update label
merge
.select<SVGTextElement>('text.interface_label')
.text((l: InterfaceLabel) => l.text)
.attr('style', (l: InterfaceLabel) => {
let styles = this.cssFixer.fix(l.style);
.text((l: MapLinkNode) => l.label.text)
.attr('style', (l: MapLinkNode) => {
let styles = this.cssFixer.fix(l.label.style);
styles = this.fontFixer.fixStyles(styles);
return styles;
});
// .attr('x', -InterfaceLabelWidget.SURROUNDING_TEXT_BORDER)
// .attr('y', -InterfaceLabelWidget.SURROUNDING_TEXT_BORDER);
})
.attr('x', function (this: SVGTextElement, l: MapLinkNode) {
return l.label.x;
})
.attr('y', function (this: SVGTextElement, l: MapLinkNode) {
let bbox = this.getBBox();
return l.label.y + bbox.height;
})
.attr('transform', (l: MapLinkNode) => {
return `rotate(${l.label.rotation}, ${l.label.x}, ${l.label.y})`;
})
// update surrounding rect
// merge
// .select<SVGRectElement>('rect.interface_label_border')
// .attr('visibility', (l: InterfaceLabel) => false ? 'visible' : 'hidden')
// .attr('stroke-dasharray', '3,3')
// .attr('stroke-width', '0.5')
// .each(function (this: SVGRectElement, l: InterfaceLabel) {
// const current = select(this);
// const parent = select(this.parentElement);
// const text = parent.select<SVGTextElement>('text');
// const bbox = text.node().getBBox();
merge
.select<SVGRectElement>('rect.interface_label_selection')
.attr('visibility', (l: MapLinkNode) => this.selectionManager.isSelected(l) ? 'visible' : 'hidden')
.attr('stroke', 'black')
.attr('stroke-dasharray', '3,3')
.attr('stroke-width', '0.5')
.attr('fill', 'none')
.each(function (this: SVGRectElement, l: MapLinkNode) {
const current = select(this);
const textLabel = merge.select<SVGTextElement>(`text[interface_label_id="${l.id}"]`);
const bbox = textLabel.node().getBBox();
const border = 2;
// const border = InterfaceLabelWidget.SURROUNDING_TEXT_BORDER;
// current.attr('width', bbox.width + border * 2);
// current.attr('height', bbox.height + border);
// current.attr('x', - border);
// current.attr('y', - bbox.height);
// });
current.attr('width', bbox.width + border * 2);
current.attr('height', bbox.height + border * 2);
current.attr('x', bbox.x - border);
current.attr('y', bbox.y - border);
current.attr('transform', `rotate(${l.label.rotation}, ${bbox.x - border}, ${bbox.y - border})`);
});
labels
.exit()
.remove();
this.draggable.call(merge);
}
}

View File

@ -38,6 +38,7 @@ import { MapDrawing } from '../../cartography/models/map/map-drawing';
import { MapPortToPortConverter } from '../../cartography/converters/map/map-port-to-port-converter';
import { SettingsService, Settings } from '../../services/settings.service';
import { MapLabel } from '../../cartography/models/map/map-label';
import { MapLinkNode } from '../../cartography/models/map/map-link-node';
@Component({
@ -172,6 +173,10 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
this.subscriptions.push(
this.linksEventSource.created.subscribe((evt) => this.onLinkCreated(evt))
);
this.subscriptions.push(
this.linksEventSource.interfaceDragged.subscribe((evt) => this.onInterfaceLabelDragged(evt))
);
}
onProjectLoad(project: Project) {
@ -274,6 +279,24 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
});
}
private onInterfaceLabelDragged(draggedEvent: DraggedDataEvent<MapLinkNode>) {
const link = this.linksDataSource.get(draggedEvent.datum.linkId);
if (link.nodes[0].node_id === draggedEvent.datum.nodeId) {
link.nodes[0].label.x += draggedEvent.dx;
link.nodes[0].label.y += draggedEvent.dy;
}
if (link.nodes[1].node_id === draggedEvent.datum.nodeId) {
link.nodes[1].label.x += draggedEvent.dx;
link.nodes[1].label.y += draggedEvent.dy;
}
this.linkService
.updateNodes(this.server, link, link.nodes)
.subscribe((serverLink: Link) => {
this.linksDataSource.update(serverLink);
});
}
private onLinkCreated(linkCreated: MapLinkCreated) {
const sourceNode = this.mapNodeToNode.convert(linkCreated.sourceNode);
const sourcePort = this.mapPortToPort.convert(linkCreated.sourcePort);

View File

@ -5,6 +5,8 @@ import 'rxjs/add/operator/map';
import { Server } from "../models/server";
import { HttpServer } from "./http-server.service";
import {Port} from "../models/port";
import { Link } from '../models/link';
import { LinkNode } from '../models/link-node';
@Injectable()
export class LinkService {
@ -33,4 +35,27 @@ export class LinkService {
]});
}
updateNodes(
server: Server, link: Link, nodes: LinkNode[]) {
const requestNodes = nodes.map((linkNode) => {
return {
node_id: linkNode.node_id,
port_number: linkNode.port_number,
adapter_number: linkNode.adapter_number,
label: {
rotation: linkNode.label.rotation,
style: linkNode.label.style,
text: linkNode.label.text,
x: linkNode.label.x,
y: linkNode.label.y
}
}
});
return this.httpServer
.put(
server,
`/projects/${link.project_id}/links/${link.link_id}`,
{"nodes": requestNodes});
}
}