如何根据数据创建SVG形状?

时间:2013-12-24 01:28:56

标签: svg d3.js

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

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>

对于我追加data的每个<g>元素:

var groups = svg.selectAll("g").data(data, function (d) {
  return d.id;
});
groups.enter().append("g");

现在我正在绑定组形状的数据:

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有两级<g>

<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>

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

3 个答案:

答案 0 :(得分:4)

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

寻找其他解决方案的灵感是Lars Kotthoff的评论,建议使用路径而不是原始的SVG形状。在此方法中,应添加<g>

,而不是添加第二级<path>
shapes.enter().append("path").attr("d", function(d) {
  return drawer[d.shape](d);
});

生成基本形状的原始drawer对象将返回d的属性<path>的值。

    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;
        }
};

工作示例您可以在http://fiddle.jshell.net/daKkJ/4/

查看

答案 1 :(得分:3)

棘手的问题。遗憾的是你不能在enter()选择中调用每个。 你也不能在附加语句中使用function(d){}(见评论)

但是我在数据阵列本身上使用Javascript forEach()调用了它。它使用数组条目,索引和数组本身作为参数调用函数,并且可以指定this上下文 - 我只是将所需的父元素作为选择传递。

神话般的小提琴:http://fiddle.jshell.net/994XM/1/
(比你的情况简单,但应该能够轻松适应)

由于forEach中的D3代码全部是每个元素,因此di值已经可用,所以有点令人困惑,所以D3方法调用中不需要任何内部函数。但是一旦你明白了,一切都会奏效。

答案 2 :(得分:1)

我通过Google发现了这个问题,与上面链接的jsfiddles已停止运作。您现在必须调用document.createElementNS,为其提供SVG命名空间,否则形状不会显示。

我创建了一个更新的fiddle。我还清理了很多不必要的内容,因此应该更容易看到发生了什么。