如何根据数据创建不同类型的SVG元素?

时间:2016-09-01 18:39:05

标签: d3.js svg

我正在尝试创建一个多样化的图例,其中包含圆形三角形,矩形,线条等,我想独立创建它们,然后使用d3来排列它们的位置和颜色,但我如何直接访问这些数据呢? / p>

d3.selectAll('g.legend')
  .data([
    // is there a way to have d3 create an element in memory but not append it?
    { svgFn: function() { this.append('rect') }, ...otherinfo },
    { svgFn: function() { this.append('circle') }, ...otherinfo },
  ]).enter()
    .append('g')
      .append(function(d) { d.svgFn.call(this)})
      .attr...

2 个答案:

答案 0 :(得分:7)

这个问题是如何基于数据创建元素的变体?如何动态追加元素?模式。在我看来,你的方法将过于复杂和杂乱,因为你需要复制函数来创建数据中的元素。这似乎不是一个优雅的解决方案。

我更希望仅指定要创建的元素的类型,即数据对象中的{type: "circle"}{type: "rect"}等,并让selection.append()方法正常工作。此方法将接受回调,而回调可能会评估数据中指定的类型并相应地创建元素:

  

#选择。追加类型<>
   [...]
  否则,类型可以是为每个所选元素计算的函数,按顺序传递当前数据( d ),当前索引( i ),以及当前组( nodes ),将其作为当前DOM元素。此函数应返回要追加的元素。

这会简化您的代码:

d3.selectAll('g.legend')
  .data([
    { type: 'rect', other: info },
    { type: 'circle', other: info }
  ])
  .enter().append('g')
  .append(function(d) { 
    return document.createElementNS(d3.namespaces.svg, d.type);
  });

附录

根据user2167582&#39; comment的要求,也可以轻松合并分配属性的解决方案。

对于使用d3-selection-multi模块的D3 v4,您可以使用多值语法传递包含要设置的键值对属性的对象。假设您要创建的元素数组如下所示:

var elementsAndAttributes = [
    { type: 'rect', attrs: { "fill": "blue", "width": "10", "height": "10" } },
    { type: 'circle', attrs: { "fill": "red", "cx": "20", "cy": "20", "r": "10" } }
];

然后,您可以在一次运行中绑定此数据并创建具有其属性的元素:

d3.selectAll('g.legend')
  .data(elementsAndAttributes)
  .enter().append('g')
  .append(function(d) {                // Create elements from data
    return document.createElementNS(d3.namespaces.svg, d.type);   // v4 namespace
  })
    .attrs(function(d) {               // Set the attributes object per element
      return d.attrs;
    });

当仍然使用D3 v3时,事情有点不同。尽管v3支持内置多值对象配置,但您不允许将对象作为函数的返回值提供(请参阅问题#277 &#34;多值映射支持。&#34; 讨论为什么会这样做。但是,您可以使用selection.each()来实现相同的目标。

d3.selectAll('g.legend')
  .data(elementsAndAttributes)
  .enter().append('g')
  .append(function(d) {                // Create elements from data
    return document.createElementNS(d3.ns.prefix.svg, d.type);  // v3 namespace
  })
    .each(function(d) {                // Iterate over all appended elements
      d3.select(this).attr(d.attrs);   // Set the attributes object per element
    });

忽略D3引用命名空间常量的方式的差异,使用selection.each()的最后一个版本实际上将在D3 v3和v4中都有效。

进一步阅读:

  1. 我的answer"Object Oriented d3"
  2. Create DOM element without appending it?如果你真的想要创建元素而不直接附加它们。

答案 1 :(得分:0)

基于altocumulus的回答并阅读doc

selection.append("div");
     

等效于:

selection.append(d3.creator("div"));

我找到了一种让d3进行工作并且不被名称空间困扰的方法

var svg = d3.select("#chart"); // or any other selection to get svg element
d3.selectAll('g.legend')
  .data(elementsAndAttributes)
  .enter().append('g')
  .append(function(d) {                // Create elements from data
    return d3.creator(d.type).bind(svg.node())();
  })
  .attrs(function(d) {               // Set the attributes object per element
      return d.attrs;
  });

请注意直接调用绑定的creator(),因为回调的结果用作appendChild(child)的参数。如果bind未知,则可以使用

  .append(function(d) {                // Create elements from data
    return d3.creator(d.type).call(svg.node());
  })

文档中提到this应该是要附加的父节点,但事实并非如此,this仅用于获取this.ownerDocument(=== document (适用于浏览器)),并将creator()调用的结果附加到正确的(g)节点上。因此,任何(svg)元素都是正确的。