Precalculate labels position in converters

This commit is contained in:
ziajka 2018-11-30 13:59:57 +01:00
parent 007670e4e0
commit 622e106f12
15 changed files with 263 additions and 84 deletions

View File

@ -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 ]

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
});
});

View 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;
}
}

View File

@ -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;

View File

@ -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'
});

View 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);
});
});

View 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
}
}
}

View File

@ -7,5 +7,7 @@ export class MapLabel implements Indexed {
text: string;
x: number;
y: number;
originalX: number;
originalY: number;
nodeId: string;
}

View File

@ -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})`;
})

View File

@ -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 ]
})

View File

@ -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)