如何基于数据创建SVG形状?

如何基于数据创建SVG形状?,svg,d3.js,Svg,D3.js,我有数据结构和形状定义: var data = [ { "id": "first", "shapes": [ { "shape": "polygon", "points": [["8","64"],["8","356"],["98","356"],["98","64"]] } ] }, { "id": "second", "shapes": [ { "shape

我有数据结构和形状定义:

var data = [
  {
    "id": "first",
    "shapes": [
      {
        "shape": "polygon",
        "points": [["8","64"],["8","356"],["98","356"],["98","64"]]
      }
    ]
  },
  {
    "id": "second",
    "shapes": [
      {
        "shape": "ellipse",
        "cx": "63", "cy": "306", "rx": "27","ry": "18"
      }, {
        "shape": "polygon",
        "points": [["174","262"],["171","252"],["167","262"]]
      }
    ]
  }   
];    // in the data may be stored any SVG shape
我想创建SVG:

<svg width="218" height="400">
  <g transform="translate(0,400) scale(1,-1)">
    <g>
      <polygon points="8,64 8,356 98,356 98,64"/>
    </g>
    <g>
      <ellipse cx="63" cy="306" rx="27" ry="18"/>
      <polygon points="174,262 171,252 167,262"/>
    </g>
  </g>
</svg>
现在,我正在为组形状绑定数据:

var shapes = groups.selectAll(".shape").data(function (d) {
  return d.shapes;
}, function(d,i){return [d.shape,i].join('-');});
到目前为止,一切如期而至。现在,我想为每个进入的DOM节点分派具有适当形状的绘图函数,但显然
shapes.enter()。each()
在此上下文中不起作用(未定义)。我认为它更适合于每个DOM节点,而不是要绑定的每个数据。这是有效的:

shapes.enter().append("g").each(function(draw, i) {
  var shape = draw.shape;
  d3.select(this).call(drawer[shape]);
});
但令人痛苦的副作用是SVG有两个级别的


...

如何摆脱它?如何正确构建基于数据的形状?

棘手的问题。遗憾的是,不能在enter()选择中调用每个。您也不能在append语句中使用
函数(d){}

但我通过对数据数组本身进行Javascript调用,使其正常工作。它使用数组项、索引和数组本身作为参数调用函数,您可以指定
这个
上下文——我刚刚将所需的父元素作为选择传入

神奇的小提琴:
(比您的案例简单,但应能轻松适应)


这有点令人困惑,因为
forEach
中的D3代码都是每个元素的,
d
i
值已经可以使用,所以D3方法调用中不需要任何内部函数。但一旦你弄明白了这一点,一切都会好起来。

根据AmeliaBR提供的数据添加基本形状的两种方法都可以,但稍微超出了d3.js API。经过长期考虑,我决定补充回答我自己的问题

寻找其他解决方案的灵感来自Lars Kotthoff的评论,该评论建议使用路径而不是原始SVG形状。在这种方法中,不应添加第二级
,而应添加

原始
抽屉
生成基本形状的对象将返回
的属性
d
的值


工作示例您可以在

上查看,我通过谷歌找到了这个问题,与上面链接的JSFIDLE已经停止工作。现在必须调用document.createElements,为其提供SVG名称空间,否则形状不会显示


我创建了一个更新的。我还清除了许多不必要的部分,所以应该更容易看到发生了什么。

您可以将其放入JSFIDLE或jsbin中吗。我想我应该检查每个函数以查看呈现的是什么输出,可能会将svg形状作为字符串输出,并将其附加到每个循环中的innerHtml中。您可以使用或使用预计算的形状这样做。您实际上可以使用一个函数返回要附加在
.append()中的DOM元素
@Lars Kotthoff:这是规范中所说的,但是它在运行时抛出一个
NotFoundError
。对
append()
中匿名函数的第一次调用正确地传递了数据,但返回时出错——即使我只是返回一个静态字符串。我假设问题是在实际的append函数调用中没有正确设置对
this
的引用。这是我试过的,欢迎任何建议。如果您同意它应该可以工作,那么在gitHub上把它作为一个问题提出来可能是值得的。您需要创建并返回实际的DOM元素——啊,谢谢@Lars,没有仔细阅读API。因此,对于@Marcin,我认为这是“正确”的方法。我已经整理好了小提琴,所以形状的其余属性在这里设置得很好:但是Lars添加的重要一行是
shapes.enter().append(函数(d,I){return document.createElement(d.type);})
:函数调用使用普通的旧Javascript创建元素,并将其返回给D3 append方法。@AmeliaBR感谢您的回答。我正在考虑通过输入数据节点迭代
forEach()
,并通过
document
API创建DOM节点,就像在d3.jsapi之外一样。我更喜欢直接使用D3.Nice转换函数修改集合和创建节点。只要不存在维护单独元素类型的逻辑原因,这可能是最佳解决方案,因为现在所有元素都是一组统一的“路径”元素,它们接受相同的属性(例如,左上角的x和y)。我会坚持使用基于路径的形状,因为它可以在它们之间形成平滑的二者之间。当然,这对您来说很好,但在我的情况下,我希望使用矩形或圆形,具体取决于“节点类型”。给定的节点永远不会更改类型,因此不需要更改。此外,我还想让其他人知道我修复了小提琴——上面链接的小提琴坏了。
shapes.enter().append("g").each(function(draw, i) {
  var shape = draw.shape;
  d3.select(this).call(drawer[shape]);
});
<svg width="218" height="400">
  <g transform="translate(0,400) scale(1,-1)">
    <g>
      <g>
        <polygon points="8,64 8,356 98,356 98,64"/>
      </g>
    </g>
  ...
</svg>
shapes.enter().append("path").attr("d", function(d) {
  return drawer[d.shape](d);
});
    var drawer = {
        polygon: function (d) {
            return [
                "M", d.points[0].join(','),
                "L", d.points
                  .slice(1, d.points.length)
                  .map(function (e) {
                    return e.join(",")
                }), "Z"].join(" ");
        },
        ellipse: function (d) {
            return [
                'M', [d.cx, d.cy].join(','),
                'm', [-d.rx, 0].join(','),
                'a', [d.rx, d.ry].join(','),
                0, "1,0", [2 * d.rx, 0].join(','),
                'a', [d.rx, d.ry].join(','),
                0, "1,0", [-2 * d.rx, 0].join(',')].join(' ');
        },
        circle: function (d) {
            return this.ellipse({
                cx: d.cx,
                cy: d.cy,
                rx: d.r,
                ry: d.r
            });
        },
        rect: function (d) {
            return this.polygon({
                points: [
                    [d.x, d.y],
                    [d.x + d.width, d.y],
                    [d.x + d.width, d.y + d.height],
                    [d.x, d.y + d.height]
                ]
            });
        },
        path: function (d) {
            return d.d;
        }
};