javascript - 为什么重置过滤器在 d3 js 强制定向图中不起作用?

标签 javascript d3.js reset d3-force-directed

我正在尝试重新绘制力定向图,并在用户单击“重置过滤器”按钮时将其恢复到原始状态。

但它没有按预期工作。请引用下面的jsfiddle。

JSFiddle 链接:Working Fiddle

var filter = document.querySelector('#filter');
filter.addEventListener('change', function(event) {
d3.select("svg").remove();
  svg = d3.select("body").append("svg").attr("width","960").attr("height", "600");
  filterData(event.target.value);
})

 var resetFilter = document.querySelector('#reset');
resetFilter.addEventListener('click', function(event) {
  d3.select("svg").remove();
  svg = d3.select("body").append("svg").attr("width","960").attr("height", "600");
  graph = Object.assign({}, store);
  drawGraph(graph);
}) 

function filterData(id) {
    g.html('');
    graph = Object.assign({}, store);
    graph.nodes = [];
    graph.links = [];
    dummyStore = [id];
    store.links.forEach(function(link) {
            if(link.source.id === id) {
                graph.links.push(link);
                dummyStore.push(link.target.id);
            } else if (link.target.id === id) {
                graph.links.push(link);
                dummyStore.push(link.source.id)
            }
        });
        store.nodes.forEach(function(node) {
            if (dummyStore.includes(node.id)) {
                graph.nodes.push(node);
            }
        })
    drawGraph();
    }

有人可以告诉我这里缺少什么吗?

最佳答案

目前,您每次都在重新创建模拟,并且每次也都在重新创建可视化:您无需在节点来来去去的情况下执行输入/更新/退出循环,而是将整个面板删除干净,从 SVG 中删除所有内容。

现在,我们可以在过滤发生时添加进入/更新/退出循环,但是如果我们只需要隐藏已过滤的链接和节点,我们可以隐藏它们而不是删除它们。我在评论中澄清说,这种方法可能会令人满意,因为它使任务变得更加容易。

我们可以将已过滤掉的节点和链接的不透明度设置为0,并将指针事件设置为none,并将这些值重置为1 > 和 all 用于需要显示的链接和节点。

尽可能多地使用您的代码,我们可以得到类似的东西:

// Re-apply the filter each time the input changes:    
d3.select("input").on("keyup", function() {
  // We want to know if we have a value of ""
  var value = this.value.length ? this.value : undefined;

  nodeElements.each(function(d) {
    d.show = false; // by default don't show if a filter is applied.
  })

  // Go through each datum (d.target and d.source) and
  // set a flag to show a node if it is connected (or is) included in the filter
  // also show the link or hide it as needed: 
  linkElements.each(function(d) {
    if(value && d.source.id.indexOf(value) == -1 && d.target.id.indexOf(value) == -1) {
      d3.select(this).attr("opacity",0);
    }
    else {
      d.source.show = d.target.show = true;
      d3.select(this).attr("opacity",1);
    }
  })

  // Now just hide/show text and circles as appropriate.
  nodeElements.attr("opacity",function(d) { return d.show ? 1 : 0 });
  textElements.attr("opacity",function(d) { return d.show ? 1 : 0 });

})

为了简洁起见,我没有在这里设置指针事件,使用类同时设置不透明度和指针事件会更简单。该过滤器也区分大小写。

由于每对节点和文本的数据相同(并在链接数据中引用),因此我们不需要单独更新每个节点和文本的数据。

隐藏节点继续受到作用力:它们在每个刻度中继续位于背景中。如果我们删除 SVG 元素,但没有重新定义模拟,模拟仍然会计算它们每个刻度的位置。如果我们不想要这些东西,那么我们需要一种完全不同的方法。

这是一个片段形式的小示例:

var graph = {
'nodes':[
{'id':'Menu','group':0},
{'id':'Item1','group':1},
{'id':'Item2','group':1},
{'id':'Item3','group':1},
{'id':'Item4','group':1},
{'id':'Item5','group':1},
{'id':'SubItem1_Item1','group':2},
{'id':'SubItem2_Item1','group':2}],
'links':[

{'source':'Menu','target':'Item1','value':1,'type':'A'},
{'source':'Menu','target':'Item2','value':8,'type':'A'},
{'source':'Menu','target':'Item3','value':10,'type':'A'},
{'source':'Menu','target':'Item3','value':1,'type':'A'},
{'source':'Menu','target':'Item4','value':1,'type':'A'},
{'source':'Menu','target':'Item5','value':1,'type':'A'},

/* Item1 is linked to SubItems */
{'source':'Item1','target':'SubItem1_Item1','value':2,'type':'A'},
{'source':'Item1','target':'SubItem2_Item1','value':1,'type':'A'},

/* Interconnected Items */
{'source':'Item5','target':'Item4','value':2,'type':'A'},
{'source':'Item2','target':'Item3','value':1,'type':'A'},
]};


var width = 500;
var height= 300;
var color = d3.scaleOrdinal(d3.schemeCategory10);

var svg = d3.select("body").append("svg")
  .attr("width",width)
  .attr("height",height);
  
var grads = svg.append("defs").selectAll("radialGradient")
    .data(graph.nodes)
    .enter()
    .append("radialGradient")
    .attr("gradientUnits", "objectBoundingBox")
    .attr("cx", 0)
    .attr("fill", function(d) { return color(d.id); })
    .attr("cy", 0)
    .attr("r", "100%")
    .attr("id", function(d, i) { return "grad" + i; });
 
   grads.append("stop")
    .attr("offset", "0%")
    .style("stop-color", "white");
 
   grads.append("stop")
    .attr("offset", "100%")
    .style("stop-color",  function(d) { return color(d.id); });    



var simulation = d3.forceSimulation()
  .force("link", d3.forceLink().distance(200).id(function(d) {
    return d.id;
  }))
  .force("charge", d3.forceManyBody().strength(-1000))
  .force("center", d3.forceCenter(width / 2, height / 2));
  
var g = svg.append("g")
  .attr("class", "everything");
  
var linkElements = g.append("g")
  .attr("class", "links")
  .selectAll("line")
  .data(graph.links)
  .enter().append("line")
  .style("stroke-width",5.5)
  .style("stroke", "grey")
  

var nodeElements =  g.append("g")
  .attr("class", "nodes")
  .selectAll("circle")
  .data(graph.nodes)
  .enter().append("circle")
  .attr("r", 60)
  .attr("stroke", "#fff")
  .attr('stroke-width', 21)
  .attr("id", function(d) { return d.id })
     .attr('fill', function(d, i) { return 'url(#grad' + i + ')'; })
     .on('contextmenu', function(d){ 
        d3.event.preventDefault();
        menu(d3.mouse(svg.node())[0], d3.mouse(svg.node())[1]);
    })
      .on('mouseover', selectNode)
      .on('mouseout', releaseNode)
  .call(d3.drag()
    .on("start", dragstarted)
    .on("drag", dragged)
    .on("end", dragended));
      
var textElements = g.append("g")    // use g.append instead of svg.append to enable zoom
  .attr("class", "texts")
  .selectAll("text")
  .data(graph.nodes)
  .enter().append("text")
    .attr("text-anchor", "end")
  .text(function(node) {
    return node.id
  })
  .attr("font-size", 55)
  .attr("font-family", "sans-serif")
  .attr("fill", "black")
  .attr("style", "font-weight:bold;")
  .attr("dx", 30)
  .attr("dy", 80)


  
function ticked() {
  linkElements
    .attr("x1", function(d) { return d.source.x; })
    .attr("y1", function(d) { return d.source.y; })
    .attr("x2", function(d) { return d.target.x; })
    .attr("y2", function(d) { return d.target.y; });
  nodeElements
    .attr("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; })
    .each(d => { d3.select('#t_' + d.id).attr('x', d.x + 10).attr('y', d.y + 3); });
    textElements
    .attr('x', function(d) {
      return d.x
    })
    .attr('y', function(d) {
      return d.y
    });
}

simulation
  .nodes(graph.nodes)
  .on("tick", ticked);

simulation.force("link")
  .links(graph.links);


function zoom_actions() {
  g.attr("transform", d3.event.transform)
}

function dragstarted(d) {
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x;
  d.fy = d.y;
}

function dragged(d) {
  d.fx = d3.event.x;
  d.fy = d3.event.y;
}

function dragended(d) {
  if (!d3.event.active) simulation.alphaTarget(0);
  d.fx = null;
  d.fy = null;
}

function selectNode(selectedNode) {
  var neighbors = getNeighbors(selectedNode)

  nodeElements
    .attr('fill', function(node) {
     	return getNodeColor(node,neighbors,selectedNode);
   })
  .transition().duration(500)
  .attr('r', function(node) {
     	return getNodeRadius(node,neighbors);
   });
     
   textElements.transition().duration(500).style('font-size', function(node) {
    return getTextColor(node, neighbors)
  })
}

function releaseNode() {
nodeElements.transition().duration(500)
   .attr('r', 60);
nodeElements.attr('fill', function(d, i) { return 'url(#grad' + i + ')'; })

   linkElements.style('stroke', 'grey');
}

function getNeighbors(node) {
  return graph.links.reduce(function(neighbors, link) {
    if (link.target.id === node.id) {
      neighbors.push(link.source.id)
    } else if (link.source.id === node.id) {
      neighbors.push(link.target.id)
    }
    return neighbors
  }, [node.id])
}

function getNodeColor(node, neighbors, selectedNode) {
  // If is neighbor
  if (Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1) {
    return 'url(#grad' + selectedNode.index + ')'
    // return node.level === 1 ? '#9C4A9C' : 'rgba(251, 130, 30, 1)'
  }  else {
      return 'url(#grad' + node.index + ')'
  }
  //return node.level === 0 ? '#91007B' : '#D8ABD8'
}

function getNodeRadius(node, neighbors) {
  // If is neighbor
  if ( neighbors.indexOf(node.id) > -1) {
    return '100'
  } 
  else {
        return '60'
  }
}

function getTextColor(node, neighbors) {
  return Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1 ? '40px' : '25px'
}

d3.select("input").on("keyup", function() {
  var value = this.value.length ? this.value : undefined;
  
  nodeElements.each(function(d) {
    d.show = false; // by default don't show if a filter is applied.
  })
  
  linkElements.each(function(d) {
    if(value && d.source.id.indexOf(value) == -1 && d.target.id.indexOf(value) == -1) {
      d3.select(this).attr("opacity",0);
    }
    else {
      d.source.show = d.target.show = true;
      d3.select(this).attr("opacity",1);
    }
  })
  
  nodeElements.attr("opacity",function(d) { return d.show ? 1 : 0 });
  textElements.attr("opacity",function(d) { return d.show ? 1 : 0 });

})

d3.select("button").on("click", function() {
  d3.select("input").property("value","");
  g.selectAll("*").attr("opacity",1);
})
<script src="https://d3js.org/d3.v4.min.js"></script>
Filter: <input type="text" name="filter" id="filter"/>
<button id = 'reset'>Reset Filter</button><br />

这是一个 fiddle .

关于javascript - 为什么重置过滤器在 d3 js 强制定向图中不起作用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59979155/

相关文章:

javascript - 使用 javascript 以编程方式单击非按钮元素?

javascript - 使用组元素而不是点时数据丢失

javascript - 如何在javascript中更改行

javascript - 如何在 Javascript 中使用 eventListener 进行重置

mysql - MariaDB:每次重新启动后访问被拒绝

javascript - 如何处理 AMP 的 CORS

javascript - 模态对话框在网站中默认打开一次

javascript - 显示数据文件属性中的复选框值

javascript - D3.extent() 没有给出正确的范围

android - Path.reset 与 Path.rewind