mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-05-09 20:12:53 +00:00
Draw text on topology
This commit is contained in:
parent
e0ccfda3e2
commit
8f600b3e72
@ -10,6 +10,7 @@ describe('SvgToDrawingHelper', () => {
|
|||||||
|
|
||||||
it('should parse attributes', () => {
|
it('should parse attributes', () => {
|
||||||
const node = document.createElement("text");
|
const node = document.createElement("text");
|
||||||
|
node.innerText = "Text";
|
||||||
node.setAttribute("fill", "#00000");
|
node.setAttribute("fill", "#00000");
|
||||||
node.setAttribute("fill-opacity", "1.0");
|
node.setAttribute("fill-opacity", "1.0");
|
||||||
node.setAttribute("font-family", "TypeWriter");
|
node.setAttribute("font-family", "TypeWriter");
|
||||||
@ -17,10 +18,12 @@ describe('SvgToDrawingHelper', () => {
|
|||||||
node.setAttribute("font-weight", "bold");
|
node.setAttribute("font-weight", "bold");
|
||||||
|
|
||||||
const drawing = textConverter.convert(node);
|
const drawing = textConverter.convert(node);
|
||||||
|
expect(drawing.text).toEqual("Text");
|
||||||
expect(drawing.fill).toEqual("#00000");
|
expect(drawing.fill).toEqual("#00000");
|
||||||
expect(drawing.fill_opacity).toEqual(1.0);
|
expect(drawing.fill_opacity).toEqual(1.0);
|
||||||
expect(drawing.font_family).toEqual("TypeWriter");
|
expect(drawing.font_family).toEqual("TypeWriter");
|
||||||
expect(drawing.font_size).toEqual(10.0);
|
expect(drawing.font_size).toEqual(10.0);
|
||||||
expect(drawing.font_weight).toEqual("bold");
|
expect(drawing.font_weight).toEqual("bold");
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -6,6 +6,8 @@ export class TextConverter implements SvgConverter {
|
|||||||
convert(node: Node): TextElement {
|
convert(node: Node): TextElement {
|
||||||
const drawing = new TextElement();
|
const drawing = new TextElement();
|
||||||
|
|
||||||
|
drawing.text = node.textContent;
|
||||||
|
|
||||||
const fill = node.attributes.getNamedItem('fill');
|
const fill = node.attributes.getNamedItem('fill');
|
||||||
if (fill) {
|
if (fill) {
|
||||||
drawing.fill = fill.value;
|
drawing.fill = fill.value;
|
||||||
|
@ -4,6 +4,7 @@ import { DrawingElement } from "./drawing-element";
|
|||||||
export class TextElement implements DrawingElement {
|
export class TextElement implements DrawingElement {
|
||||||
height: number;
|
height: number;
|
||||||
width: number;
|
width: number;
|
||||||
|
text: string;
|
||||||
fill: string;
|
fill: string;
|
||||||
fill_opacity: number;
|
fill_opacity: number;
|
||||||
font_family: string;
|
font_family: string;
|
||||||
|
@ -2,6 +2,7 @@ import {Widget} from "./widget";
|
|||||||
import {Drawing} from "../models/drawing";
|
import {Drawing} from "../models/drawing";
|
||||||
import {SVGSelection} from "../models/types";
|
import {SVGSelection} from "../models/types";
|
||||||
import {Layer} from "../models/layer";
|
import {Layer} from "../models/layer";
|
||||||
|
import { TextDrawingWidget } from "./drawings/text-drawing";
|
||||||
|
|
||||||
|
|
||||||
export class DrawingsWidget implements Widget {
|
export class DrawingsWidget implements Widget {
|
||||||
@ -20,43 +21,48 @@ export class DrawingsWidget implements Widget {
|
|||||||
.append<SVGGElement>('g')
|
.append<SVGGElement>('g')
|
||||||
.attr('class', 'drawing');
|
.attr('class', 'drawing');
|
||||||
|
|
||||||
const parser = new DOMParser();
|
// const parser = new DOMParser();
|
||||||
|
|
||||||
const drawing_image = drawing_enter.append<SVGImageElement>('image')
|
// const drawing_image = drawing_enter.append<SVGImageElement>('image')
|
||||||
.attr('xlink:href', (d: Drawing) => {
|
// .attr('xlink:href', (d: Drawing) => {
|
||||||
let svg = d.svg;
|
// let svg = d.svg;
|
||||||
if (svg.indexOf("xmlns") < 0) {
|
// if (svg.indexOf("xmlns") < 0) {
|
||||||
svg = svg.replace('svg', 'svg xmlns="http://www.w3.org/2000/svg"');
|
// svg = svg.replace('svg', 'svg xmlns="http://www.w3.org/2000/svg"');
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return 'data:image/svg+xml;base64,' + btoa(svg);
|
// return 'data:image/svg+xml;base64,' + btoa(svg);
|
||||||
})
|
// })
|
||||||
.attr('width', (d: Drawing) => {
|
// .attr('width', (d: Drawing) => {
|
||||||
const svg_dom = parser.parseFromString(d.svg, 'text/xml');
|
// const svg_dom = parser.parseFromString(d.svg, 'text/xml');
|
||||||
const roots = svg_dom.getElementsByTagName('svg');
|
// const roots = svg_dom.getElementsByTagName('svg');
|
||||||
if (roots.length > 0) {
|
// if (roots.length > 0) {
|
||||||
if (roots[0].hasAttribute('width')) {
|
// if (roots[0].hasAttribute('width')) {
|
||||||
return roots[0].getAttribute('width');
|
// return roots[0].getAttribute('width');
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return 0;
|
// return 0;
|
||||||
})
|
// })
|
||||||
.attr('height', (d: Drawing) => {
|
// .attr('height', (d: Drawing) => {
|
||||||
const svg_dom = parser.parseFromString(d.svg, 'text/xml');
|
// const svg_dom = parser.parseFromString(d.svg, 'text/xml');
|
||||||
const roots = svg_dom.getElementsByTagName('svg');
|
// const roots = svg_dom.getElementsByTagName('svg');
|
||||||
if (roots.length > 0) {
|
// if (roots.length > 0) {
|
||||||
if (roots[0].hasAttribute('height')) {
|
// if (roots[0].hasAttribute('height')) {
|
||||||
return roots[0].getAttribute('height');
|
// return roots[0].getAttribute('height');
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return 0;
|
// return 0;
|
||||||
});
|
// });
|
||||||
|
|
||||||
const drawing_merge = drawing.merge(drawing_enter)
|
const drawing_merge = drawing.merge(drawing_enter)
|
||||||
.attr('transform', (d: Drawing) => {
|
.attr('transform', (d: Drawing) => {
|
||||||
return `translate(${d.x},${d.y})`;
|
return `translate(${d.x},${d.y})`;
|
||||||
});
|
});
|
||||||
|
|
||||||
drawing.exit().remove();
|
const text_drawing = new TextDrawingWidget();
|
||||||
|
text_drawing.draw(drawing_merge);
|
||||||
|
|
||||||
|
drawing
|
||||||
|
.exit()
|
||||||
|
.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
import { instance, mock, when } from "ts-mockito";
|
||||||
|
import { TestSVGCanvas } from "../../../testing";
|
||||||
|
import { TextDrawingWidget } from "./text-drawing";
|
||||||
|
import { Drawing } from "../../models/drawing";
|
||||||
|
import { TextElement } from "../../models/drawings/text-element";
|
||||||
|
|
||||||
|
describe('TextDrawingWidget', () => {
|
||||||
|
let svg: TestSVGCanvas;
|
||||||
|
let widget: TextDrawingWidget;
|
||||||
|
let drawing: Drawing;
|
||||||
|
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
svg = new TestSVGCanvas();
|
||||||
|
drawing = new Drawing();
|
||||||
|
widget = new TextDrawingWidget();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
svg.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should draw text drawing', () => {
|
||||||
|
drawing.svg = '<svg height="23" width="106"><text fill="#000000" fill-opacity="1.0" ' +
|
||||||
|
'font-family="TypeWriter" font-size="10.0" font-weight="bold">THIS IS TEXT</text></svg>';
|
||||||
|
|
||||||
|
const drawings = svg.canvas.selectAll<SVGGElement, Drawing>('g.drawing').data([drawing]);
|
||||||
|
const drawings_enter = drawings.enter().append<SVGGElement>('g').classed('drawing', true);
|
||||||
|
const drawings_merge = drawings.merge(drawings_enter);
|
||||||
|
|
||||||
|
widget.draw(drawings_merge);
|
||||||
|
|
||||||
|
const drew = drawings_merge.selectAll<SVGTextElement, TextElement>('text.text_element');
|
||||||
|
expect(drew.size()).toEqual(1);
|
||||||
|
const text_element = drew.nodes()[0];
|
||||||
|
expect(text_element.innerHTML).toEqual("THIS IS TEXT");
|
||||||
|
expect(text_element.getAttribute('style')).toEqual('font-family: "TypeWriter"; font-size: 10pt; font-weight: bold');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should draw multiline text', () => {
|
||||||
|
drawing.svg = '<svg height="23" width="106"><text>THIS' + "\n" + 'IS TEXT</text></svg>';
|
||||||
|
|
||||||
|
const drawings = svg.canvas.selectAll<SVGGElement, Drawing>('g.drawing').data([drawing]);
|
||||||
|
const drawings_enter = drawings.enter().append<SVGGElement>('g').classed('drawing', true);
|
||||||
|
const drawings_merge = drawings.merge(drawings_enter);
|
||||||
|
|
||||||
|
widget.draw(drawings_merge);
|
||||||
|
|
||||||
|
const drew = drawings_merge.selectAll<SVGTSpanElement, string>('tspan');
|
||||||
|
expect(drew.nodes().length).toEqual(2);
|
||||||
|
|
||||||
|
expect(drew.nodes()[0].innerHTML).toEqual('THIS');
|
||||||
|
expect(drew.nodes()[0].getAttribute('x')).toEqual('0');
|
||||||
|
expect(drew.nodes()[0].getAttribute('dy')).toEqual('0em');
|
||||||
|
|
||||||
|
expect(drew.nodes()[1].innerHTML).toEqual('IS TEXT');
|
||||||
|
expect(drew.nodes()[1].getAttribute('x')).toEqual('0');
|
||||||
|
expect(drew.nodes()[1].getAttribute('dy')).toEqual('1.2em');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
71
src/app/cartography/shared/widgets/drawings/text-drawing.ts
Normal file
71
src/app/cartography/shared/widgets/drawings/text-drawing.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { SVGSelection } from "../../models/types";
|
||||||
|
import { TextElement } from "../../models/drawings/text-element";
|
||||||
|
import { Drawing } from "../../models/drawing";
|
||||||
|
import { SvgToDrawingConverter } from "../../../map/helpers/svg-to-drawing-converter";
|
||||||
|
import { DrawingElement } from "../../models/drawings/drawing-element";
|
||||||
|
|
||||||
|
|
||||||
|
export class TextDrawingWidget {
|
||||||
|
public draw(view: SVGSelection) {
|
||||||
|
const drawing = view
|
||||||
|
.selectAll<SVGTextElement, TextElement>('text.text_element')
|
||||||
|
.data((d: Drawing) => {
|
||||||
|
const svgConverter = new SvgToDrawingConverter();
|
||||||
|
const elements: DrawingElement[] = [];
|
||||||
|
try {
|
||||||
|
const element = svgConverter.convert(d.svg);
|
||||||
|
elements.push(element);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Cannot convert due to Error: '${error}'`);
|
||||||
|
}
|
||||||
|
return elements.filter((e: DrawingElement) => e instanceof TextElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
const drawing_enter = drawing
|
||||||
|
.enter()
|
||||||
|
.append<SVGTextElement>('text')
|
||||||
|
.attr('class', 'text_element noselect');
|
||||||
|
|
||||||
|
const merge = drawing.merge(drawing_enter);
|
||||||
|
|
||||||
|
merge
|
||||||
|
.attr('style', (text: TextElement) => {
|
||||||
|
const styles: string[] = [];
|
||||||
|
if (text.font_family) {
|
||||||
|
styles.push(`font-family: "${text.font_family}"`);
|
||||||
|
}
|
||||||
|
if (text.font_size) {
|
||||||
|
styles.push(`font-size: ${text.font_size}pt`);
|
||||||
|
}
|
||||||
|
if (text.font_weight) {
|
||||||
|
styles.push(`font-weight: ${text.font_weight}`);
|
||||||
|
}
|
||||||
|
return styles.join("; ");
|
||||||
|
});
|
||||||
|
|
||||||
|
const lines = merge.selectAll<SVGTSpanElement, string>('tspan')
|
||||||
|
.data((text: TextElement) => {
|
||||||
|
return text.text.split(/\r?\n/);
|
||||||
|
});
|
||||||
|
|
||||||
|
const lines_enter = lines
|
||||||
|
.enter()
|
||||||
|
.append<SVGTSpanElement>('tspan');
|
||||||
|
|
||||||
|
const lines_merge = lines.merge(lines_enter);
|
||||||
|
|
||||||
|
lines_merge
|
||||||
|
.text((line) => line)
|
||||||
|
.attr('x', 0)
|
||||||
|
.attr("dy", (line, i) => i === 0 ? '0em' : '1.2em');
|
||||||
|
|
||||||
|
lines
|
||||||
|
.exit()
|
||||||
|
.remove();
|
||||||
|
|
||||||
|
drawing
|
||||||
|
.exit()
|
||||||
|
.remove();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -18,7 +18,7 @@ export class LayersWidget implements Widget {
|
|||||||
const layers_enter = layers_selection
|
const layers_enter = layers_selection
|
||||||
.enter()
|
.enter()
|
||||||
.append<SVGGElement>('g')
|
.append<SVGGElement>('g')
|
||||||
.attr('class', 'layer')
|
.attr('class', 'layer');
|
||||||
|
|
||||||
// add container for links
|
// add container for links
|
||||||
layers_enter
|
layers_enter
|
||||||
|
Loading…
x
Reference in New Issue
Block a user