d3:根据滑块值添加和删除force.nodes

时间:2015-05-05 21:14:23

标签: javascript d3.js

我有一段时间难以根据用户输入添加和删除节点,这是通过每次用户从复选框列表中选择要查看的内容时更新推送到force.nodes()的整个对象集来解决的。

从滑块更新但我认为需要更精细的触摸 - 我不想每次移动滑块时更新整个设置。我想在force.nodes()中推送和弹出节点。

使用我当前的代码,节点出来就好了 - 他们只是不再回来了。这里是jsfiddle - https://jsfiddle.net/hiwilson1/ancmtxux/3/

这是造成问题的部分;

function brushed() {
    var exists;

    //var newd = new Date(2013, 05, 01)

    data.forEach(function(d, i) {
        //if data point in range (between extent 0 and 1)
        if (d.date >= brush.extent()[0] && d.date <= brush.extent()[1]) {
            exists = force.nodes().some(function(node, i) {
                //check if data point already exists in force.nodes()
                return (node.mIndex == d.mIndex)                
                })
            console.log(exists)
            if (!exists) {
                force.nodes().push(d)
            }
        }
        else {
            force.nodes().splice(i, 1)
        }   
    })



    d3.select("#nodeCount").text(force.nodes().length)
}

对于每个数据点,我都要检查点是否位于extent()[0]和[1]之间。如果是,请检查force.nodes()以查看它当前是否是其中的成员。如果不是,则将其推入force.nodes()。

如果数据点不在范围之间,则将其与force.nodes()拼接。最后一点工作正常。

更新:已修复。我实际上也研究了如何过滤连接到节点的链接。 jsfiddle here - https://jsfiddle.net/hiwilson1/7oumeat5/2/。切勿尝试使用索引/硬编码索引执行此操作,将节点/链接作为对象进行比较。

顺便说一下,我看到了节点顶部的链接。如果有某种方法可以解决这个问题,我很乐意听到它。

进一步更新:为确保节点后面的链接使用.insert(&#34; line&#34;,&#34;:first-child&#34;)代替.append(&#34; line&#34; )

2 个答案:

答案 0 :(得分:2)

您当前的代码存在一些问题。根本问题在于您将data赋予force.nodes(),这意味着两个数据结构实际上是相同的。也就是说,当您从force.nodes()中删除元素时,您也会修改基础data。因此你无法将它们添加回来 - 它们已经消失了。

通过将data的副本传递给force.nodes()

,可以轻松解决此问题
var force = d3.layout.force()
    .nodes(JSON.parse(JSON.stringify(data)))

然后您从force.nodes()删除了错误的节点 - 您使用的索引是data,而不是force.nodes()。您可以在data中计算force.nodes()元素的索引,并像这样使用它:

data.forEach(function(d, i) {
        var idx = -1;
        force.nodes().forEach(function(node, j) {
            if(node.mIndex == d.mIndex) {
                idx = j;
            }
        });
        //if data point in range (between extent 0 and 1)
        if (d.date >= brush.extent()[0] && d.date <= brush.extent()[1]) {
            if (idx == -1) {
                force.nodes().push(d)
            }
        }
        else if(idx > -1) {
            force.nodes().splice(idx, 1)
        }

最后,您需要在force.start()结束时致电brushed,以便在布局确定后更改可见。

完整示例here

答案 1 :(得分:1)

在强大的@Lars Kotthoff解决问题的基础上,我将重点关注架构。这是一个更简单,更符合d3习语的架构。

主要原则是:

  1. 使用Array.prototype.filter()管理数据
  2. 使用标准的数据驱动模式 通过管理数据来驱动可视化,然后使用标准的常规更新模式将更改驱动到vis。
  3. 单独的数据事件和动画事件 仅在需要时响应数据更改...在刷拉事件中执行,而不是在每个刻度线上执行。 brushed事件是数据事件tick例程是动画事件。在不需要的情况下管理动画事件的数据并不是最佳选择。
  4. 使节点的进入和退出更平滑。
  5. 第1点的好处是过滤后的数组实际上是对原始data元素的引用数组,因此当在复制的数组上添加额外状态时,它实际上会添加到原始data元素上。 1}}数组。因此,先前的状态在被过滤回来时是可用的,因此平滑的退出和进入行为。同时,当画笔过滤时,原始data中不会删除任何元素:只有对它们的引用才会在克隆数组中删除。我不得不承认我没想到,但这是一个很好的发现,即使是偶然的! 当然这只能起作用,因为数组元素是对象。

    这里的工作示例......

    var width = 700,
        height = 600,
        padding = 20;
    
        var start = new Date(2013, 0, 1),
          end = new Date(2013, 11, 31)
    
        var data = []
    
        for (i = 0; i < 100; i++) {
          var point = {}
    
          var year = 2013;
          var month = Math.floor(Math.random() * 12)
          var day = Math.floor(Math.random() * 28)
    
          point.date = new Date(year, month, day)
          point.mIndex = i
          data.push(point)
        }
    
        var force = d3.layout.force()
              .size([width - padding, height - 100])
              .on("tick", tick)
              .start()
    
        var svg = d3.select("body").append("svg")
          .attr({
            "width": width,
            "height": height
          })
    
        //build stuff
        var x = d3.time.scale()
          .domain([start, end])
          .range([padding, width - 6 * padding])
          .clamp(true)
    
        var xAxis = d3.svg.axis()
          .scale(x)
          .tickSize(0)
          .tickPadding(20)
        //.tickFormat(d3.time.format("%x"))
    
        var brush = d3.svg.brush()
          .x(x)
          .extent([start, end])
          .on("brush", brushed1)
    
        //append stuff
        var slidercontainer = svg.append("g")
          .attr("transform", "translate(100, 500)")
    
        var axis = slidercontainer.append("g")
          .call(xAxis)
    
        var slider = slidercontainer.append("g")
          .call(brush)
          .classed("slider", true)
    
        //manipulate stuff
        d3.selectAll(".resize").append("circle")
          .attr("cx", 0)
          .attr("cy", 0)
          .attr("r", 10)
          .attr("fill", "Red")
          .classed("handle", true)
    
        d3.select(".domain")
          .select(function () { return this.parentNode.appendChild(this.cloneNode(true)) })
          .classed("halo", true)
    
        function brushed1(e) {
    
          var nodes = includedNodes(data, brush);
    
            nodes.enter().append("circle")
                .attr("r", 10)
                .attr("fill", "red")
                .call(force.drag)
                .attr("class", "node")
                .attr("cx", function (d) { return d.x })
                .attr("cy", function (d) { return d.y })
    
          nodes
            .exit()
            .remove()
    
          force
            .nodes(includedData(data, brush))
            .start()
    
        }
    
        function includedData(data, brush) {
    
          return data.filter(function (d, i, a) {
            return d.date >= brush.extent()[0] && d.date <= brush.extent()[1]
          })
    
        }
        function includedNodes(data, brush) {
          return svg.selectAll(".node")
                  .data(includedData(data, brush), function (d, i) {
                    return d.mIndex
                  })
        }
    
        function tick() {
    
          includedNodes(data, brush)
            .attr("cx", function (d) { return d.x })
            .attr("cy", function (d) { return d.y })
    
        }
        brushed1()
    .domain {
          fill: none;
          stroke: #000;
          stroke-opacity: .3;
          stroke-width: 10px;
          stroke-linecap: round;
        }
    
        .halo {
          fill: none;
          stroke: #ddd;
          stroke-width: 8px;
          stroke-linecap: round;
        }
    
        .tick {
          font-size: 10px;
        }
    
        .selecting circle {
          fill-opacity: .2;
        }
    
          .selecting circle.selected {
            stroke: #f00;
          }
    
        .handle {
          fill: #fff;
          stroke: #000;
          stroke-opacity: .5;
          stroke-width: 1.25px;
          cursor: crosshair;
        }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
    <p id="nodeCount"></p>