【问题标题】:Object Oriented d3面向对象的 d3
【发布时间】:2015-07-16 00:28:33
【问题描述】:

我有想要添加到 SVG 的数据对象。考虑以下伪 sn-p:

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 比较陌生,所以也许数据连接是错误的方法?是否有其他方法可以更好地呈现这种情况下的数据项?

【问题讨论】:

  • 这实际上不适用于 D3。我想你可以附加一个g 元素,然后调用.each(function(d) { d.render(this); }),但这有点不合时宜。
  • 是否有任何 d3 内部库可以做到这一点。 5 年过去了,他们更新了很多东西。

标签: javascript oop d3.js svg


【解决方案1】:

要让数据对象以面向对象的方式呈现自己,您可以使用selection.append(name) 的鲜为人知的用法。根据文档,您可以向.append() 提供一个回调,它需要返回一个DOM 元素来追加:

选择。追加名称

[…]

名称可以指定为常量字符串,也可以指定为返回要追加的 DOM 元素的函数。如果 name 是一个函数,则将当前数据 d 和当前索引 i 传递给它,并将 this 上下文作为当前 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;
}

看看这个 sn-p 的工作示例:

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

【讨论】:

  • 是否有任何 d3 内部库可以做到这一点。 5 年过去了,他们更新了很多东西。
  • @LukAron 恐怕,答案仍然是否定的!今天我会以不同的方式做 OOP 部分,但关于 D3 的事情几乎是一样的。
【解决方案2】:

我也对这个话题很感兴趣。这是我目前正在做的事情。在您的情况下,您可以选择所需的形状,而不是将文本设置为 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;
};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-08-25
    • 2010-10-22
    • 2011-08-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-18
    • 1970-01-01
    相关资源
    最近更新 更多