Home > other >  Instantiating text elements of correct type SVGTextElement before appending
Instantiating text elements of correct type SVGTextElement before appending

Time:02-19

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:

enter image description here

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:

  1. 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 to document.createElementNS() is guaranteed to yield exactly that type:

     const svgText = document.createElementNS(d3.namespaces['svg'], "text") as SVGTextElement;
    
  2. 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 the namespaceURI is exactly "http://www.w3.org/2000/svg" and the qualifiedName is a key contained in the SVGElementTagNameMap interface then the compiler infers the return type to be the element type referred to by the qualifiedName in that interface.

     const svgText = document.createElementNS("http://www.w3.org/2000/svg", "text");
    

    For the above line the svgText will be of type SVGTextElement 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.

  3. 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 specify svg: 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");
    
  • Related