mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-02-20 17:52:46 +00:00
Precalculate labels position in converters
This commit is contained in:
parent
007670e4e0
commit
622e106f12
@ -39,6 +39,8 @@ import { SelectionControlComponent } from './components/selection-control/select
|
||||
import { SelectionSelectComponent } from './components/selection-select/selection-select.component';
|
||||
import { DraggableSelectionComponent } from './components/draggable-selection/draggable-selection.component';
|
||||
import { MapSettingsManager } from './managers/map-settings-manager';
|
||||
import { FontBBoxCalculator } from './helpers/font-bbox-calculator';
|
||||
import { StylesToFontConverter } from './converters/styles-to-font-converter';
|
||||
|
||||
|
||||
@NgModule({
|
||||
@ -89,6 +91,8 @@ import { MapSettingsManager } from './managers/map-settings-manager';
|
||||
MapSymbolsDataSource,
|
||||
SelectionEventSource,
|
||||
MapSettingsManager,
|
||||
FontBBoxCalculator,
|
||||
StylesToFontConverter,
|
||||
...D3_MAP_IMPORTS
|
||||
],
|
||||
exports: [ D3MapComponent, ExperimentalMapComponent ]
|
||||
|
@ -3,10 +3,18 @@ import { Injectable } from "@angular/core";
|
||||
import { Converter } from "../converter";
|
||||
import { Label } from "../../models/label";
|
||||
import { MapLabel } from "../../models/map/map-label";
|
||||
import { FontBBoxCalculator } from '../../helpers/font-bbox-calculator';
|
||||
import { CssFixer } from '../../helpers/css-fixer';
|
||||
import { FontFixer } from '../../helpers/font-fixer';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class LabelToMapLabelConverter implements Converter<Label, MapLabel> {
|
||||
constructor(
|
||||
private fontBBoxCalculator: FontBBoxCalculator,
|
||||
private cssFixer: CssFixer,
|
||||
private fontFixer: FontFixer
|
||||
) {}
|
||||
convert(label: Label, paramaters?: {[node_id: string]: string}) {
|
||||
const mapLabel = new MapLabel();
|
||||
mapLabel.rotation = label.rotation;
|
||||
@ -14,10 +22,26 @@ export class LabelToMapLabelConverter implements Converter<Label, MapLabel> {
|
||||
mapLabel.text = label.text;
|
||||
mapLabel.x = label.x;
|
||||
mapLabel.y = label.y;
|
||||
mapLabel.originalX = label.x;
|
||||
mapLabel.originalY = label.y;
|
||||
|
||||
if (paramaters !== undefined) {
|
||||
mapLabel.id = paramaters.node_id;
|
||||
mapLabel.nodeId = paramaters.node_id;
|
||||
}
|
||||
|
||||
const fixedCss = this.cssFixer.fix(mapLabel.style);
|
||||
const fixedFont = this.fontFixer.fixStyles(fixedCss);
|
||||
const box = this.fontBBoxCalculator.calculate(mapLabel.text, fixedFont);
|
||||
|
||||
if (mapLabel.x !== null) {
|
||||
mapLabel.x += 3;
|
||||
}
|
||||
|
||||
if (mapLabel.y !== null) {
|
||||
mapLabel.y += box.height;
|
||||
}
|
||||
|
||||
return mapLabel;
|
||||
}
|
||||
}
|
||||
|
@ -3,17 +3,33 @@ import { Injectable } from "@angular/core";
|
||||
import { Converter } from "../converter";
|
||||
import { Label } from "../../models/label";
|
||||
import { MapLabel } from "../../models/map/map-label";
|
||||
import { FontBBoxCalculator } from '../../helpers/font-bbox-calculator';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class MapLabelToLabelConverter implements Converter<MapLabel, Label> {
|
||||
constructor(
|
||||
private fontBBoxCalculator: FontBBoxCalculator
|
||||
) {}
|
||||
|
||||
convert(mapLabel: MapLabel) {
|
||||
const box = this.fontBBoxCalculator.calculate(mapLabel.text, mapLabel.style);
|
||||
|
||||
const label = new Label();
|
||||
label.rotation = mapLabel.rotation;
|
||||
label.style = mapLabel.style;
|
||||
label.text = mapLabel.text;
|
||||
label.x = mapLabel.x;
|
||||
label.y = mapLabel.y;
|
||||
|
||||
if (label.x !== null) {
|
||||
label.x += 3;
|
||||
}
|
||||
|
||||
if (label.y !== null) {
|
||||
label.y -= box.height;
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
@ -5,38 +5,41 @@ import { MapNode } from "../../models/map/map-node";
|
||||
import { MapLabelToLabelConverter } from "./map-label-to-label-converter";
|
||||
import { MapPortToPortConverter } from "./map-port-to-port-converter";
|
||||
import { Node } from "../../models/node";
|
||||
import { FontBBoxCalculator } from '../../helpers/font-bbox-calculator';
|
||||
import { CssFixer } from '../../helpers/css-fixer';
|
||||
import { FontFixer } from '../../helpers/font-fixer';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class MapNodeToNodeConverter implements Converter<MapNode, Node> {
|
||||
constructor(
|
||||
private mapLabelToLabel: MapLabelToLabelConverter,
|
||||
private mapPortToPort: MapPortToPortConverter
|
||||
) {}
|
||||
|
||||
convert(mapNode: MapNode) {
|
||||
const node = new Node();
|
||||
node.node_id = mapNode.id;
|
||||
node.command_line = mapNode.commandLine;
|
||||
node.compute_id = mapNode.computeId;
|
||||
node.console = mapNode.console;
|
||||
node.console_host = mapNode.consoleHost;
|
||||
node.first_port_name = mapNode.firstPortName;
|
||||
node.height = mapNode.height;
|
||||
node.label = mapNode.label ? this.mapLabelToLabel.convert(mapNode.label) : undefined;
|
||||
node.name = mapNode.name;
|
||||
node.node_directory = mapNode.nodeDirectory;
|
||||
node.node_type = mapNode.nodeType;
|
||||
node.port_name_format = mapNode.portNameFormat;
|
||||
node.port_segment_size = mapNode.portSegmentSize;
|
||||
node.ports = mapNode.ports ? mapNode.ports.map((mapPort) => this.mapPortToPort.convert(mapPort)) : [];
|
||||
node.project_id = mapNode.projectId;
|
||||
node.status = mapNode.status;
|
||||
node.symbol = mapNode.symbol;
|
||||
node.width = mapNode.width;
|
||||
node.x = mapNode.x;
|
||||
node.y = mapNode.y;
|
||||
node.z = mapNode.z;
|
||||
return node;
|
||||
}
|
||||
constructor(
|
||||
private mapLabelToLabel: MapLabelToLabelConverter,
|
||||
private mapPortToPort: MapPortToPortConverter
|
||||
) {}
|
||||
|
||||
convert(mapNode: MapNode) {
|
||||
const node = new Node();
|
||||
node.node_id = mapNode.id;
|
||||
node.command_line = mapNode.commandLine;
|
||||
node.compute_id = mapNode.computeId;
|
||||
node.console = mapNode.console;
|
||||
node.console_host = mapNode.consoleHost;
|
||||
node.first_port_name = mapNode.firstPortName;
|
||||
node.height = mapNode.height;
|
||||
node.label = mapNode.label ? this.mapLabelToLabel.convert(mapNode.label) : undefined;
|
||||
node.name = mapNode.name;
|
||||
node.node_directory = mapNode.nodeDirectory;
|
||||
node.node_type = mapNode.nodeType;
|
||||
node.port_name_format = mapNode.portNameFormat;
|
||||
node.port_segment_size = mapNode.portSegmentSize;
|
||||
node.ports = mapNode.ports ? mapNode.ports.map((mapPort) => this.mapPortToPort.convert(mapPort)) : [];
|
||||
node.project_id = mapNode.projectId;
|
||||
node.status = mapNode.status;
|
||||
node.symbol = mapNode.symbol;
|
||||
node.width = mapNode.width;
|
||||
node.x = mapNode.x;
|
||||
node.y = mapNode.y;
|
||||
node.z = mapNode.z;
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
@ -5,38 +5,56 @@ import { MapNode } from "../../models/map/map-node";
|
||||
import { Node } from "../../models/node";
|
||||
import { LabelToMapLabelConverter } from "./label-to-map-label-converter";
|
||||
import { PortToMapPortConverter } from "./port-to-map-port-converter";
|
||||
import { FontBBoxCalculator } from '../../helpers/font-bbox-calculator';
|
||||
import { CssFixer } from '../../helpers/css-fixer';
|
||||
import { FontFixer } from '../../helpers/font-fixer';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class NodeToMapNodeConverter implements Converter<Node, MapNode> {
|
||||
constructor(
|
||||
private labelToMapLabel: LabelToMapLabelConverter,
|
||||
private portToMapPort: PortToMapPortConverter
|
||||
) {}
|
||||
constructor(
|
||||
private labelToMapLabel: LabelToMapLabelConverter,
|
||||
private portToMapPort: PortToMapPortConverter,
|
||||
private fontBBoxCalculator: FontBBoxCalculator,
|
||||
private cssFixer: CssFixer,
|
||||
private fontFixer: FontFixer
|
||||
) {}
|
||||
|
||||
convert(node: Node) {
|
||||
const mapNode = new MapNode();
|
||||
mapNode.id = node.node_id;
|
||||
mapNode.commandLine = node.command_line;
|
||||
mapNode.computeId = node.compute_id;
|
||||
mapNode.console = node.console;
|
||||
mapNode.consoleHost = node.console_host;
|
||||
mapNode.firstPortName = node.first_port_name;
|
||||
mapNode.height = node.height;
|
||||
mapNode.label = this.labelToMapLabel.convert(node.label, { node_id: node.node_id });
|
||||
mapNode.name = node.name;
|
||||
mapNode.nodeDirectory = node.node_directory;
|
||||
mapNode.nodeType = node.node_type;
|
||||
mapNode.portNameFormat = node.port_name_format;
|
||||
mapNode.portSegmentSize = node.port_segment_size;
|
||||
mapNode.ports = node.ports.map((port) => this.portToMapPort.convert(port));
|
||||
mapNode.projectId = node.project_id;
|
||||
mapNode.status = node.status;
|
||||
mapNode.symbol = node.symbol;
|
||||
mapNode.width = node.width;
|
||||
mapNode.x = node.x;
|
||||
mapNode.y = node.y;
|
||||
mapNode.z = node.z;
|
||||
return mapNode;
|
||||
convert(node: Node) {
|
||||
const mapNode = new MapNode();
|
||||
mapNode.id = node.node_id;
|
||||
mapNode.commandLine = node.command_line;
|
||||
mapNode.computeId = node.compute_id;
|
||||
mapNode.console = node.console;
|
||||
mapNode.consoleHost = node.console_host;
|
||||
mapNode.firstPortName = node.first_port_name;
|
||||
mapNode.height = node.height;
|
||||
mapNode.label = this.labelToMapLabel.convert(node.label, { node_id: node.node_id });
|
||||
mapNode.name = node.name;
|
||||
mapNode.nodeDirectory = node.node_directory;
|
||||
mapNode.nodeType = node.node_type;
|
||||
mapNode.portNameFormat = node.port_name_format;
|
||||
mapNode.portSegmentSize = node.port_segment_size;
|
||||
mapNode.ports = node.ports.map((port) => this.portToMapPort.convert(port));
|
||||
mapNode.projectId = node.project_id;
|
||||
mapNode.status = node.status;
|
||||
mapNode.symbol = node.symbol;
|
||||
mapNode.width = node.width;
|
||||
mapNode.x = node.x;
|
||||
mapNode.y = node.y;
|
||||
mapNode.z = node.z;
|
||||
|
||||
|
||||
if (mapNode.label !== undefined) {
|
||||
const fixedCss = this.cssFixer.fix(mapNode.label.style);
|
||||
const fixedFont = this.fontFixer.fixStyles(fixedCss);
|
||||
const box = this.fontBBoxCalculator.calculate(mapNode.label.text, fixedFont);
|
||||
|
||||
if (node.label.x === null || node.label.y === null) {
|
||||
mapNode.label.x = node.width / 2. - box.width / 2. + 3;
|
||||
mapNode.label.y = -8;
|
||||
}
|
||||
}
|
||||
return mapNode;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
import { Font } from "../models/font";
|
||||
import { StylesToFontConverter } from './styles-to-font-converter';
|
||||
|
||||
|
||||
describe('StylesToFontConverter', () => {
|
||||
let converter: StylesToFontConverter;
|
||||
|
||||
beforeEach(() => {
|
||||
converter = new StylesToFontConverter();
|
||||
});
|
||||
|
||||
it('should parse fonts from styles', () => {
|
||||
const styles = "font-family: TypeWriter; font-size: 10px; font-weight: bold";
|
||||
|
||||
const expectedFont: Font = {
|
||||
'font_family': 'TypeWriter',
|
||||
'font_size': 10,
|
||||
'font_weight': 'bold'
|
||||
};
|
||||
|
||||
expect(converter.convert(styles)).toEqual(expectedFont);
|
||||
});
|
||||
|
||||
});
|
47
src/app/cartography/converters/styles-to-font-converter.ts
Normal file
47
src/app/cartography/converters/styles-to-font-converter.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import * as csstree from 'css-tree';
|
||||
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Converter } from './converter';
|
||||
import { Font } from '../models/font';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class StylesToFontConverter implements Converter<string, Font> {
|
||||
convert(styles: string) {
|
||||
const font: Font = {
|
||||
'font_family': undefined,
|
||||
'font_size': undefined,
|
||||
'font_weight': undefined
|
||||
};
|
||||
|
||||
const ast = csstree.parse(styles, {
|
||||
'context': 'declarationList'
|
||||
});
|
||||
|
||||
ast.children.forEach((child) => {
|
||||
if (child.property === 'font-size') {
|
||||
child.value.children.forEach((value) => {
|
||||
if (value.type === 'Dimension') {
|
||||
font.font_size = parseInt(value.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (child.property === 'font-family') {
|
||||
child.value.children.forEach((value) => {
|
||||
if (value.type === "Identifier") {
|
||||
font.font_family = value.name;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (child.property === 'font-weight') {
|
||||
child.value.children.forEach((value) => {
|
||||
if (value.type === "Identifier") {
|
||||
font.font_weight = value.name;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return font;
|
||||
}
|
||||
}
|
@ -62,7 +62,6 @@ export class Draggable<GElement extends DraggedElementBaseType, Datum> {
|
||||
const evt = new DraggableDrag<Datum>(datum);
|
||||
evt.dx = event.sourceEvent.clientX - lastX;
|
||||
evt.dy = event.sourceEvent.clientY - lastY;
|
||||
|
||||
lastX = event.sourceEvent.clientX;
|
||||
lastY = event.sourceEvent.clientY;
|
||||
|
||||
|
@ -6,7 +6,7 @@ import { Injectable } from "@angular/core";
|
||||
@Injectable()
|
||||
export class CssFixer {
|
||||
|
||||
public fix(styles: string) {
|
||||
public fix(styles: string): string {
|
||||
const ast = csstree.parse(styles, {
|
||||
'context': 'declarationList'
|
||||
});
|
||||
|
24
src/app/cartography/helpers/font-bbox-calculator.spec.ts
Normal file
24
src/app/cartography/helpers/font-bbox-calculator.spec.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { FontBBoxCalculator } from "./font-bbox-calculator";
|
||||
|
||||
|
||||
describe('FontBBoxCalculator', () => {
|
||||
let calculator: FontBBoxCalculator;
|
||||
|
||||
beforeEach(() => {
|
||||
calculator = new FontBBoxCalculator();
|
||||
});
|
||||
|
||||
it('should calculate font width and height', () => {
|
||||
const box = calculator.calculate("My text", "font-family:Arial; font-size: 12px; font-weight:bold");
|
||||
|
||||
expect(box.height).toEqual(14);
|
||||
expect(box.width).toEqual(41.34375);
|
||||
});
|
||||
|
||||
it('should calculate font width and height for different font', () => {
|
||||
const box = calculator.calculate("My text", "font-family:Tahoma; font-size: 14px; font-weight:bold");
|
||||
|
||||
expect(box.height).toEqual(15);
|
||||
expect(box.width).toEqual(46.25);
|
||||
});
|
||||
});
|
20
src/app/cartography/helpers/font-bbox-calculator.ts
Normal file
20
src/app/cartography/helpers/font-bbox-calculator.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
@Injectable()
|
||||
export class FontBBoxCalculator {
|
||||
calculate(text: string, styles: string) {
|
||||
const element = document.createElement("text");
|
||||
element.innerText = text;
|
||||
element.setAttribute("fill", "#00000");
|
||||
element.setAttribute("fill-opacity", "0");
|
||||
element.setAttribute("style", styles);
|
||||
document.documentElement.appendChild(element);
|
||||
const bbox = element.getBoundingClientRect();
|
||||
document.documentElement.removeChild(element);
|
||||
|
||||
return {
|
||||
width: bbox.width,
|
||||
height: bbox.height
|
||||
}
|
||||
}
|
||||
}
|
@ -7,5 +7,7 @@ export class MapLabel implements Indexed {
|
||||
text: string;
|
||||
x: number;
|
||||
y: number;
|
||||
originalX: number;
|
||||
originalY: number;
|
||||
nodeId: string;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ export class LabelWidget implements Widget {
|
||||
private cssFixer: CssFixer,
|
||||
private fontFixer: FontFixer,
|
||||
private selectionManager: SelectionManager,
|
||||
private mapSettings: MapSettingsManager,
|
||||
private mapSettings: MapSettingsManager
|
||||
) {}
|
||||
|
||||
public redrawLabel(view: SVGSelection, label: MapLabel) {
|
||||
@ -56,6 +56,8 @@ export class LabelWidget implements Widget {
|
||||
|
||||
|
||||
private drawLabel(view: SVGSelection) {
|
||||
const self = this;
|
||||
|
||||
const label_body = view.selectAll<SVGGElement, MapLabel>("g.label_body")
|
||||
.data((label) => [label]);
|
||||
|
||||
@ -77,31 +79,14 @@ export class LabelWidget implements Widget {
|
||||
label_body_merge
|
||||
.select<SVGTextElement>('text.label')
|
||||
.attr('label_id', (l: MapLabel) => l.id)
|
||||
// .attr('y', (n: Node) => n.label.y - n.height / 2. + 10) // @todo: server computes y in auto way
|
||||
.attr('style', (l: MapLabel) => {
|
||||
let styles = this.cssFixer.fix(l.style);
|
||||
styles = this.fontFixer.fixStyles(styles);
|
||||
return styles;
|
||||
})
|
||||
.text((l: MapLabel) => l.text)
|
||||
.attr('x', function (this: SVGTextElement, l: MapLabel) {
|
||||
// if (l.x === null) {
|
||||
// // center
|
||||
// const bbox = this.getBBox();
|
||||
// return -bbox.width / 2.;
|
||||
// }
|
||||
return l.x + LabelWidget.NODE_LABEL_MARGIN;
|
||||
})
|
||||
.attr('y', function (this: SVGTextElement, l: MapLabel) {
|
||||
let bbox = this.getBBox();
|
||||
|
||||
// if (n.label.x === null) {
|
||||
// // center
|
||||
// bbox = this.getBBox();
|
||||
// return - n.height / 2. - bbox.height ;
|
||||
// }
|
||||
return l.y + bbox.height - LabelWidget.NODE_LABEL_MARGIN - bbox.height*0.14;
|
||||
})
|
||||
.attr('x', (l: MapLabel) => l.x)
|
||||
.attr('y', (l: MapLabel) => l.y)
|
||||
.attr('transform', (l: MapLabel) => {
|
||||
return `rotate(${l.rotation}, ${l.x}, ${l.y})`;
|
||||
})
|
||||
|
@ -23,6 +23,9 @@ import { MapNode } from '../../../cartography/models/map/map-node';
|
||||
import { MapLabelToLabelConverter } from '../../../cartography/converters/map/map-label-to-label-converter';
|
||||
import { MapPortToPortConverter } from '../../../cartography/converters/map/map-port-to-port-converter';
|
||||
import { Node } from '../../../cartography/models/node';
|
||||
import { FontBBoxCalculator } from '../../../cartography/helpers/font-bbox-calculator';
|
||||
import { CssFixer } from '../../../cartography/helpers/css-fixer';
|
||||
import { FontFixer } from '../../../cartography/helpers/font-fixer';
|
||||
|
||||
|
||||
describe('ProjectMapShortcutsComponent', () => {
|
||||
@ -56,7 +59,11 @@ describe('ProjectMapShortcutsComponent', () => {
|
||||
SettingsService,
|
||||
MapNodeToNodeConverter,
|
||||
MapLabelToLabelConverter,
|
||||
MapPortToPortConverter
|
||||
MapPortToPortConverter,
|
||||
MapLabelToLabelConverter,
|
||||
FontBBoxCalculator,
|
||||
CssFixer,
|
||||
FontFixer
|
||||
],
|
||||
declarations: [ ProjectMapShortcutsComponent ]
|
||||
})
|
||||
|
@ -39,6 +39,7 @@ import { MapPortToPortConverter } from '../../cartography/converters/map/map-por
|
||||
import { SettingsService, Settings } from '../../services/settings.service';
|
||||
import { MapLabel } from '../../cartography/models/map/map-label';
|
||||
import { MapLinkNode } from '../../cartography/models/map/map-link-node';
|
||||
import { MapLabelToLabelConverter } from '../../cartography/converters/map/map-label-to-label-converter';
|
||||
|
||||
|
||||
@Component({
|
||||
@ -91,6 +92,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
private drawingsEventSource: DrawingsEventSource,
|
||||
private linksEventSource: LinksEventSource,
|
||||
private settingsService: SettingsService,
|
||||
private mapLabelToLabel: MapLabelToLabelConverter
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@ -234,7 +236,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
onNodeCreation(appliance: Appliance) {
|
||||
this.nodeService
|
||||
.createFromAppliance(this.server, this.project, appliance, 0, 0, 'local')
|
||||
.subscribe(() => {
|
||||
.subscribe((createdNode: Node) => {
|
||||
this.projectService
|
||||
.nodes(this.server, this.project.project_id)
|
||||
.subscribe((nodes: Node[]) => {
|
||||
@ -257,8 +259,12 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
|
||||
private onNodeLabelDragged(draggedEvent: DraggedDataEvent<MapLabel>) {
|
||||
const node = this.nodesDataSource.get(draggedEvent.datum.nodeId);
|
||||
node.label.x += draggedEvent.dx;
|
||||
node.label.y += draggedEvent.dy;
|
||||
const mapLabel = draggedEvent.datum;
|
||||
mapLabel.x += draggedEvent.dx;
|
||||
mapLabel.y += draggedEvent.dy;
|
||||
|
||||
const label = this.mapLabelToLabel.convert(mapLabel);
|
||||
node.label = label;
|
||||
|
||||
this.nodeService
|
||||
.updateLabel(this.server, node, node.label)
|
||||
|
Loading…
x
Reference in New Issue
Block a user