面向对象d3

时间:2015-07-16 00:28:33

标签: javascript oop d3.js svg

我有要添加到SVG的数据对象。请考虑以下伪代码段:

var data = [], counter = 0;
for (var col=1; col<=5; col++)
  for (var row=1; row<=3; row++)
    data.push({
         id: "obj-" + ++counter
        ,x: col * 120
        ,y: row * 120
        ,width: 40
        ,height: 40
        ,shape: counter % 2 ? "circle" : "rect"
    });

 d3.select(".container").selectAll(".obj")
     .data(data)
     .enter()
         .append("g")
             .attr("id", function(d){ return d.id; }
/*** 
     now I want to draw here a circle or rect based on the shape key 
     so if (d.shape == "rect") -- we will use width and height
        if (d.shape == "rect" && d.width == d.height) we will set "r" to "width", etc.
***/

理想情况下,我会创建一个Shape类型的对象,例如

function Shape(id, shape, x, y, w, h) {

    this.id = id;
    this.shape = shape;
    this.x = x;
    this.y = y;
    this.width = w;
    this.height = h;

    this.render = function(parent) {

        var g = parent.append("g")
            .attr("id", this.id);

        switch (this.shape) {
            case "circle":
                g.append("circle")
                    .attr( /* more code here */ )
                break;
            case "rect":
                g.append("rect")
                    .attr( /* more code here */ )
                break;
            case "triangle":
                g.append("polygon")
                    .attr( /* more code here */ )
                break;
        }
    }
}

然后我可以做类似的事情:

var data = [], counter = 0;
for (var col=1; col<=5; col++)
  for (var row=1; row<=3; row++)
    data.push(new Shape({
         id: "obj-" + ++counter
        ,x: col * 120
        ,y: row * 120
        ,width: 40
        ,height: 40
        ,shape: counter % 2 ? "circle" : "rect"
    )});

但是如何从d3调用Shape的render()方法呢?即。

 d3.select(".container").selectAll(".obj")
     .data(data)
     .enter()
         /* given a datum named d, call d.render(parent) ? */

我对d3很新,所以数据加入可能是错误的方法吗?是否有不同的方法来呈现更适合此场景的数据项?

2 个答案:

答案 0 :(得分:1)

要使数据对象以面向对象的方式呈现自己,您可以使用不太知名的selection.append(name)。根据文档,您可以向.append()提供回调,该回调需要返回要追加的DOM元素:

  

选择。的追加名称

     

[...]

     

可以将名称指定为常量字符串,也可以将其指定为返回要追加的DOM元素的函数。如果name是一个函数,则传递当前数据d和当前索引i,并将此上下文作为当前DOM元素。要根据绑定数据附加任意元素,必须在函数中创建它。例如:

selection.enter().append(function(d) {
    return document.createElementNS("http://www.w3.org/2000/svg", d.type)
})

出于此问题的目的,可以将其修改为不创建元素,而是将创建委托给数据对象的.render()方法。

d3.select(".container").selectAll("g")
  .data(data)
  .enter()
  .append(function(d) {
      return d.render();   // .render() will return the DOM element to append
  });

您的.render()方法可能如下所示:

this.render = function() {

    // Create the group.
    var g = document.createElementNS(d3.ns.prefix.svg, "g");
    g.setAttribute("id", this.id);

    // Create and configure the child element based on this.shape.
    var child;
    switch (this.shape) {
        case "circle":
            child = document.createElementNS(d3.ns.prefix.svg, "circle");
            child.setAttribute("cx", this.x);
            child.setAttribute("cy", this.y);
            child.setAttribute("r", this.width/2);
            break;

        case "rect":
            child = document.createElementNS(d3.ns.prefix.svg, "rect")
            break;

        case "triangle":
            child = document.createElementNS(d3.ns.prefix.svg, "polygon")
            break;
    }

    // Append the child to the group and return the g DOM element.
    g.appendChild(child);
    return g;
}

请查看此代码段以获取有效示例:

&#13;
&#13;
function Shape(id, shape, x, y, w, h) {

    this.id = id;
    this.shape = shape;
    this.x = x;
    this.y = y;
    this.width = w;
    this.height = h;

    this.render = function() {

        // Create the group.
        var g = document.createElementNS(d3.ns.prefix.svg, "g");
        g.setAttribute("id", this.id);
        
        // Create and configure the child element based on this.shape.
        var child;
        switch (this.shape) {
            case "circle":
                child = document.createElementNS(d3.ns.prefix.svg, "circle");
                child.setAttribute("cx", this.x);
                child.setAttribute("cy", this.y);
                child.setAttribute("r", this.width/2);
                break;
                
            case "rect":
                child = document.createElementNS(d3.ns.prefix.svg, "rect")
                child.setAttribute("x", this.x);
                child.setAttribute("y", this.y);
                child.setAttribute("width", this.width);
                child.setAttribute("height", this.height);
                break;
                
            case "triangle":
                child = document.createElementNS(d3.ns.prefix.svg, "polygon")
                break;
        }
        
        // Append the child to the group and return the g DOM element.
        g.appendChild(child);
        return g;
    }
}

var data = [], counter = 0;
for (var col=1; col<=5; col++)
  for (var row=1; row<=3; row++)
    data.push(new Shape(
         "obj-" + ++counter
        ,counter % 2 ? "circle" : "rect"
        ,col * 120
        ,row * 120
        ,40
        ,40
    ));
    
console.log(data);

d3.select(".container").selectAll("g")
  .data(data)
  .enter()
  .append(function(d) {
    return d.render();   // .render() will return the DOM element to append
  });
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width="400" height="400">
  <g class="container"></g>
</svg>
&#13;
&#13;
&#13;

答案 1 :(得分:0)

我也对这个话题感兴趣。这就是我目前正在做的事情。在您的情况下,您可以选择所需的形状,而不是将文本设置为d.text。或许有人可能会以更好的方式启发我们。

// file grid.js

var chipView = Chip().radius(config.chip.radius);

function updateView() {
    view.chips = view.renderLayer.selectAll('.draggable-chip')
      .data(model.chips /* , id */)
      .call(chipView, 'update')
    ;
    view.chips.enter()
      .append('g')
      .call(chipView)
      .classed('chip draggable-chip', true)
      .call(d3.behavior.drag()
        .on('dragstart', chipDragStarting)
        .on('drag', chipDragging)
        .on('dragend', chipDragEnding)
      )
    ;
    view.chips
      .attr('transform', function(d,i){return translateString(d.col*config.grid.size, d.row*config.grid.size);})
    ;
}


// file chip.js

var Chip = function() {

  var config = {
    r: 20
  };

  function chipView(g, updateOnly) {
    // g is an array of groups (a d3 selection)
    // each g will become a chip
    updateOnly = (typeof updateOnly === 'string' || updateOnly === true);
    g.each(function() {

      var view = d3.select(this); // the svg group in g array

      function initView() {
        // clear any existing svg nodes inside 'g'
        var contents = view.selectAll(this.childNodes);
        if (contents)
          contents.remove();
        // draw the chip
        view.append('circle')
          .attr('r', config.r)
          .attr('cx', 0)
          .attr('cy', 0)
        ;
        view.append('text')
          .attr('text-anchor', 'middle')
          .attr('dy', '0.33em')
          .text(function(d,i){return d.text;})
        ;
      }
      if (!updateOnly)
        initView();

      function updateView() {
        view.attr('opacity', function(d,i){return 1 - 0.5 * d.ghost;});
        view.select('text').attr('opacity', function(d,i){return 1 - d.ghost;});
      }
      updateView();
    });
  }

  chipView.radius = function(r) {
    if (!arguments.length)
      return config.r;
    r = parseFloat(r);
    if (typeof r !== 'number')
      throw 'Error: radius must be a number.';
    config.r = r;
    return this; // for function chaining
  };

  return chipView;
};