每次更新时,是否需要遍历(MVC)模型中的所有元素?

时间:2017-01-18 01:59:20

标签: d3.js

以下代码基于Bostock的Circle Dragging I

原始代码在拖动圆圈时更新(MVC)模型。函数d的参数dragged是被拖动的数组circles的元素。

<!DOCTYPE html>
<meta charset="utf-8">
<style>

    .active {
        stroke: #000;
        stroke-width: 2px;
    }

</style>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>

    var svg = d3.select("svg"),
        width = +svg.attr("width"),
        height = +svg.attr("height"),
        radius = 32;

    var circles = [
        {x: 100, y: 100},
        {x: 100, y: 200},
        {x: 100, y: 300}
    ];
    var lastIndex = 2;

    var color = d3.scaleOrdinal()
                  .range(d3.schemeCategory20);

    svg.selectAll("circle")
       .data(circles)
       .enter().append("circle")
       .attr("cx", function(d) { return d.x; })
       .attr("cy", function(d) { return d.y; })
       .attr("r", radius)
       .style("fill", function(d, i) { return color(i); })
       .call(d3.drag()
               .on("start", dragstarted)
               .on("drag", dragged)
               .on("end", dragended));

    function dragstarted(d) {
        d3.select(this).raise().classed("active", true);
    }

    function dragged(d) {
        d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
    }

    function dragended(d) {
        d3.select(this).classed("active", false);
    }
    //----------------Addition----------------
    var click = function() {
        if (d3.event.defaultPrevented) return;

        var p = d3.mouse(this),
            x = p[0],
            y = p[1];
        circles[++lastIndex] = {x: x, y: y};

        svg.append('circle')
           .attr('cx', x)
           .attr('cy', y)
           .attr('r', radius)
           .style("fill", function(d, i) { return color(lastIndex); })
           .call(d3.drag()
                   .on("start", dragstarted)
                   .on("drag", dragged)
                   .on("end", dragended));
    };

    svg.on('click', click);

</script>

在修改中(在“添加”下),函数click可以添加新的圆圈,但拖动行为对新点不起作用,可能是因为除非通过svg.data(circles).enter.append('circle')...添加圆圈,D3.js不会将SVG圈中的引用保留回circles数组中的条目。

解决方案是将svg.data(circles).enter.append('circle')...置于函数refresh()中,并在将新圆圈插入数组refresh()后调用circles

这样做意味着当数组circles具有n个元素时,会在插入n个元素时遍历(n+1个步骤)。< / p>

是否可以在启用拖动行为的情况下插入新圆圈,并在拖动过程中保持模型和视图同步,而不必在插入每个新圆圈时遍历circles

2 个答案:

答案 0 :(得分:3)

只需设置每个新圈子的datum即可。这样,D3可以在拖动时设置d.xd.y

svg.append('circle')
    .datum(circles[lastIndex])
    //etc...

以下是仅包含该更改的更新代码:http://blockbuilder.org/anonymous/6dbf734683e9e8e485a1cda46978b58e

这是S.O.使用相同代码的代码段:

.active {
  stroke: #000;
  stroke-width: 2px;
}
<svg width="500" height="400"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height"),
    radius = 32;

var circles = d3.range(20).map(function() {
  return {
    x: Math.round(Math.random() * (width - radius * 2) + radius),
    y: Math.round(Math.random() * (height - radius * 2) + radius)
  };
});

var color = d3.scaleOrdinal()
    .range(d3.schemeCategory20);

svg.selectAll("circle")
  .data(circles)
  .enter().append("circle")
    .attr("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; })
    .attr("r", radius)
    .style("fill", function(d, i) { return color(i); })
    .call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended));

function dragstarted(d) {
  d3.select(this).raise().classed("active", true);
}

function dragged(d) {
    d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}

function dragended(d) {
  d3.select(this).classed("active", false);
}
  
  var lastIndex = 0;
  
  var click = function() {
        if (d3.event.defaultPrevented) return;

        var p = d3.mouse(this),
            x = p[0],
            y = p[1];
        circles[++lastIndex] = {x: x, y: y};

        svg.append('circle')
        	.datum(circles[lastIndex])
           .attr('cx', x)
           .attr('cy', y)
           .attr('r', radius)
           .style("fill", function(d, i) { return color(lastIndex); })
           .call(d3.drag()
                   .on("start", dragstarted)
                   .on("drag", dragged)
                   .on("end", dragended));
    };

    svg.on('click', click);

</script>

更简单的替代方法就是更改dragged函数,因此它不依赖于任何绑定数据:

function dragged() {
    d3.select(this).attr("cx", d3.event.x).attr("cy", d3.event.y);
}

这似乎不仅是最简单的解决方案,也是最好的解决方案。正如@ his answer中的@altocumulus所指出的那样:

  

由于SVG DOM隐含地在DOM中携带定位信息,因此无需在模型中明确复制此信息,即在绑定到DOM元素的数据中。

答案 1 :(得分:1)

这是Gerardo Furtado正确answer的附录,不是替代品,也不是替代品。我决定提供更多信息,并分享我对此的看法。

正如Gerardo指出的那样,你的假设是,对于新添加的圈子,拖动行为不起作用是错误的。拖动行为已成功附加,即使对于新圈子也是如此。但是,dragged()函数会中断,因为它会尝试访问并分配绑定到拖动圆的数据。由于新添加的圈子没有绑定到它们的数据,因此会导致错误,导致拖动行为不适用于这些圈子。正如Gerardo所建议的,通过使用.datum()将数据绑定到新的圈子或者在拖动处理函数中删除数据访问,有两种方法。

尽管两种方法都有效,但我更喜欢后者,因为它简单。如果除了跟踪圈子的位置之外,您不需要将数据用于任何其他目的,则无需使此模型保持最新状态。由于SVG DOM隐含地在DOM中携带定位信息,因此无需在模型中明确复制此信息,即在绑定到DOM元素的数据中。

在绘制<canvas>元素时,您肯定需要使用模型来跟踪位置变化。画布更多地遵循 draw-and-forget 方法,并且不受分层DOM的支持。与SVG相比,它没有内置的独特可选元素概念,因此缺乏对数据绑定到元素的支持。为了能够跟踪您的元素,您必须自己提供模型支持此视图

我认为,记住这种差异,可以更深入地了解数据更新进入链接示例的原因,尽管Mike Bostock以其相当简洁的代码而闻名。但是块Circle Dragging I也链接到Circle Dragging II,它使用画布执行相同的操作。注意到两个块都是在同一天设置的,SVG演示,即 Circle Dragging I ,似乎已经从画布示例中遗留了一些剩余代码。