I need to instantiate several text elements programmatically from given data. These text elements should be surrounded by a rectangle that has the width of the text element with the largest width. Like this:
At the moment, I am using a method to create the textnode, that looks like this:
let textnodes = data.map(d => {
let svgText = document.createElementNS(d3.namespaces['svg'],'text')
let textNode = document.createTextNode(d.text)
svgText.appendChild(textNode)
return svgText
})
Afterwards, I compute the width and finally apply a maximum function, where getTextWidth
is a self-made method:
Math.max(...textnodes.map(t=> {
return t.firstChild ? this.getTextWidth(
t.firstChild.nodeValue,
this.config.attributesFontSize "px " this.config.attributesFont ) : 0
}))
My question is: Is there a way to create a SVGTextElement
provided as a type from D3 instead of using document.createElementNS(...)
? The reason I am asking is, that SVGTextElement
gives me the possibility to use the getBBox()
method that gives me the width
property for free. Ideally I would like to instantiate my text nodes like this:
data.map({ d =>
let text = new SVGTextElement()
text.setAttribute(...)
...
})
However, using the constructor like this is not permitted.
CodePudding user response:
There are multiple solutions to the problem:
The first possible solution to this is casting the result—in terms of TypeScript called type assertion—to
SVGTextElement
because you know that your call todocument.createElementNS()
is guaranteed to yield exactly that type:const svgText = document.createElementNS(d3.namespaces['svg'], "text") as SVGTextElement;
There is, however, a more elegant solution to this by letting the compiler do the work for you. In the DOM type definitions for the
.createElementNS
method there is an overload especially for creating elements in the SVG namespace:createElementNS<K extends keyof SVGElementTagNameMap>(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: K): SVGElementTagNameMap[K];
In this definition the
SVGElementTagNameMap
is an interface mapping all element names of the SVG namespace to the corresponding type. Thus, the above type definition is to be read as: If thenamespaceURI
is exactly"http://www.w3.org/2000/svg"
and thequalifiedName
is a key contained in theSVGElementTagNameMap
interface then the compiler infers the return type to be the element type referred to by thequalifiedName
in that interface.const svgText = document.createElementNS("http://www.w3.org/2000/svg", "text");
For the above line the
svgText
will be of typeSVGTextElement
just as you need it to be.Side note: for some reasons that elude me, you have to specify the namespace as a string, you cannot refer to the string by
d3.namespaces.svg
or any other reference. Maybe someone could fill in the missing information to further improve this answer.If you want a D3-ish solution you can use
d3.create()
which will create a detached element and return a new selection containg that single element. The compiler will not be able to automatically infer the type, though, because you need to specifysvg:
as the prefix for the SVG namespace. You can, however, resort to generics to specify the type as can be seen from the type defintion for the method:export function create<NewGElement extends Element>(name: string): Selection<NewGElement, undefined, null, undefined>;
Your code could look like this:
const svgTextSel = create<SVGTextElement>("svg:text");