在追加之前实例化正确类型 SVGTextElement 的文本元素

Instantiating text elements of correct type SVGTextElement before appending

我需要根据给定数据以编程方式实例化几个文本元素。这些文本元素应该被一个矩形包围,该矩形的宽度与具有最大宽度的文本元素相同。像这样:

目前,我正在使用一种方法来创建文本节点,如下所示:

let textnodes = data.map(d => {
  let svgText = document.createElementNS(d3.namespaces['svg'],'text')
  let textNode = document.createTextNode(d.text)

  svgText.appendChild(textNode)
  return svgText
})

之后,我计算宽度并最终应用最大函数,其中 getTextWidth 是 self-made 方法:

Math.max(...textnodes.map(t=> {
      return t.firstChild ? this.getTextWidth(
        t.firstChild.nodeValue,
        this.config.attributesFontSize + "px " + this.config.attributesFont ) : 0
    }))

我的问题是:有没有办法创建一个 SVGTextElement 作为 D3 的类型提供而不是使用 document.createElementNS(...)?我问的原因是,SVGTextElement 让我有可能使用 getBBox() 方法,该方法免费提供 width 属性。理想情况下,我想像这样实例化我的文本节点:

data.map({ d => 
  let text = new SVGTextElement()
  text.setAttribute(...)
  ...
})

但是,不允许使用这样的构造函数。

问题有多种解决方案:

  1. 第一个可能的解决方案是将结果(根据称为 type assertion 的 TypeScript)转换为 SVGTextElement,因为您知道您对 document.createElementNS() 的调用保证产生完全相同的类型:

     const svgText = document.createElementNS(d3.namespaces['svg'], "text") as SVGTextElement;
    
  2. 然而,有一个更优雅的解决方案,让编译器为您完成工作。在 DOM type definitions for the .createElementNS method there is an overload 特别是在 SVG 命名空间中创建元素:

     createElementNS<K extends keyof SVGElementTagNameMap>(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: K): SVGElementTagNameMap[K];
    

    在此定义中,SVGElementTagNameMap 是一个将 SVG 命名空间的所有元素名称映射到相应类型的接口。因此,上述类型定义应被解读为:如果 namespaceURI 恰好是 "http://www.w3.org/2000/svg" 并且 qualifiedNameSVGElementTagNameMap 接口中包含的键,则编译器推断return 类型为该接口中 qualifiedName 引用的元素类型。

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

    对于上面的行,svgText 将是 SVGTextElement 类型,正如您所需要的那样。

    旁注:由于某些我无法理解的原因,您必须将命名空间指定为字符串,您不能通过 d3.namespaces.svg 或任何其他引用来引用该字符串。也许有人可以填写缺失的信息以进一步改进这个答案。

  3. 如果你想要一个 D3-ish 解决方案,你可以使用 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 作为方法:

     export function create<NewGElement extends Element>(name: string): Selection<NewGElement, undefined, null, undefined>;
    

    您的代码可能如下所示:

     const svgTextSel = create<SVGTextElement>("svg:text");