Скрыть вложенные дочерние узлы onClick()

#javascript #d3.js

#язык JavaScript #d3.js

Вопрос:

Я работаю над небольшой D3.js график и хотите скрыть подключенные узлы меньшего размера, если был нажат родительский узел большего размера. До сих пор я пробовал несколько подходов, и моя лучшая попытка-сначала отфильтровать ссылки, чтобы впоследствии получить исходные узлы.

К сожалению , я получаю newLinks.map is not a function error , либо я полностью не понимаю функцию карты, либо просто пропускаю окончательный мир.

 function onClick(event, d) {  const newLinks = link.filter(link =gt; link.target.id === d.id);   console.log(newLinks)   const newNodes = newLinks.map(link =gt; data.nodes.find(newNode =gt; newNode.id === link.source.id))  console.log(newNodes)  }  

Вопрос: Как я могу достичь того, к чему стремлюсь? Я хочу скрыть меньшие присоединенные узлы, если был нажат родительский узел.

 var svg = d3.select("body").append("svg")  .attr("width", window.innerWidth)  .attr("height", window.innerHeight)  var data = {  "nodes": [{  "id": "A",  "type": "parent"  },  {  "id": "B",  "type": "parent"  },  {  "id": "C",  "type": "child"  },  {  "id": "D",  "type": "child"  },  {  "id": "E",  "type": "child"  },  ],  "links": [{  "source": "A",  "target": "B",  "distance": 125  },  {  "source": "C",  "target": "A",  "distance": 20  },  {  "source": "D",  "target": "A",  "distance": 20  },  {  "source": "E",  "target": "B",  "distance": 20  },  ] }  var force = d3.forceSimulation()  .force("link", d3.forceLink().id(function(d) {  return d.id  }).distance(function(d) {  return d.distance  }))  .force("center", d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2))  .force("charge", d3.forceManyBody().strength(-1000))  .force("collision", d3.forceCollide().radius(setSize))  var linksContainer = svg.append("g").attr("class", "linksContainer") var nodesContainer = svg.append("g").attr("class", "nodesContainer")  initialize()  function initialize() {  link = linksContainer.selectAll("line")  .data(data.links)  .join("line")  .attr("class", "line")   node = nodesContainer.selectAll(".node")  .data(data.nodes, d =gt; d.id)  .join("g")  .attr("class", "node")  .call(d3.drag()  .on("start", dragStarted)  .on("drag", dragged)  .on("end", dragEnded)  )   node.selectAll("circle")  .data(d =gt; [d])  .join("circle")  .attr("r", setSize)  .on("click", onClick)   force  .nodes(data.nodes)  .on("tick", ticked)   force  .force("link")  .links(data.links) }   function setSize(d) {  switch (d.type) {  case "parent":  return 40  case "child":  return 20  default:  return 40  } }  function onClick(event, d) {  const newLinks = link.filter(link =gt; link.target.id === d.id);  console.log(newLinks)   const newNodes = newLinks.map(link =gt; data.nodes.find(newNode =gt; newNode.id === link.source.id))  console.log(newNodes) }  function ticked() {  link  .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;  });   node  .attr("transform", function(d) {  return "translate("   d.x   ", "   d.y   ")";  }); }  function dragStarted(event, d) {  if (!event.active) force.alphaTarget(0.3).restart();  d.fx = d.x;  d.fy = d.y; }  function dragged(event, d) {  d.fx = event.x;  d.fy = event.y; }  function dragEnded(event, d) {  if (!event.active) force.alphaTarget(0);  d.fx = undefined;  d.fy = undefined; } 
 body {  overflow: hidden;  background: #e6e7ee;  margin: 0; }  circle {  fill: whitesmoke;  stroke: black; }  line {  stroke: black; } 
 lt;script src="https://d3js.org/d3.v7.js"gt;lt;/scriptgt; 

Ответ №1:

Вам нужно понять разницу между массивом данных и выбором d3. В вашем коде link и node являются выборками d3, представляющими круги и линии. Они не являются репрезентациями базовых данных.

Однако они предоставляют некоторые функции, которые могут быть полезны в этом контексте. Например, вы можете использовать .each(...) вместо forEach для перебора элементов или .data() для получения объектов, которые выделение d3 представляет в виде массива, поэтому link.data().map это определенно допустимая функция.

Я реализовал то, что вы хотите, добавив свойство isVisible к каждому узлу или ссылке, которое определяет видимость. Однако это абсолютно не лучший или единственный способ сделать это, поэтому не стесняйтесь изучать альтернативы.

 var svg = d3.select("body").append("svg")  .attr("width", window.innerWidth)  .attr("height", window.innerHeight)  var data = {  "nodes": [{  "id": "A",  "type": "parent"  },  {  "id": "B",  "type": "parent"  },  {  "id": "C",  "type": "child"  },  {  "id": "D",  "type": "child"  },  {  "id": "E",  "type": "child"  },  ],  "links": [{  "source": "A",  "target": "B",  "distance": 125  },  {  "source": "C",  "target": "A",  "distance": 20  },  {  "source": "D",  "target": "A",  "distance": 20  },  {  "source": "E",  "target": "B",  "distance": 20  },  ] }  var force = d3.forceSimulation()  .force("link", d3.forceLink().id(function(d) {  return d.id  }).distance(function(d) {  return d.distance  }))  .force("center", d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2))  .force("charge", d3.forceManyBody().strength(-1000))  .force("collision", d3.forceCollide().radius(setSize))  var linksContainer = svg.append("g").attr("class", "linksContainer") var nodesContainer = svg.append("g").attr("class", "nodesContainer")  initialize()  function initialize() {  const links = data.links  .filter(link =gt; link.isVisible !== false);  const nodes = data.nodes.filter(node =gt;  node.isVisible !== false);   link = linksContainer.selectAll("line")  .data(links)  .join("line")  .attr("class", "line")   node = nodesContainer.selectAll(".node")  .data(nodes, d =gt; d.id)  .join("g")  .attr("class", "node")  .call(d3.drag()  .on("start", dragStarted)  .on("drag", dragged)  .on("end", dragEnded)  )   node.selectAll("circle")  .data(d =gt; [d])  .join("circle")  .attr("r", setSize)  .on("click", onClick)   force  .nodes(nodes)  .on("tick", ticked)   force  .force("link")  .links(links) }   function setSize(d) {  switch (d.type) {  case "parent":  return 40  case "child":  return 20  default:  return 40  } }  function onClick(event, d) {  link.data()  .forEach(link =gt; {  link.isVisible = link.target.id === d.id;  });  const visibleNodeIds = [  d.id,  ...link.data()  .filter(l =gt; l.isVisible)  .map(l =gt; l.source.id)  ];   node.data()  .forEach(node =gt; {  node.isVisible = visibleNodeIds.includes(node.id);  });   initialize(); }  function ticked() {  link  .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;  });   node  .attr("transform", function(d) {  return "translate("   d.x   ", "   d.y   ")";  }); }  function dragStarted(event, d) {  if (!event.active) force.alphaTarget(0.3).restart();  d.fx = d.x;  d.fy = d.y; }  function dragged(event, d) {  d.fx = event.x;  d.fy = event.y; }  function dragEnded(event, d) {  if (!event.active) force.alphaTarget(0);  d.fx = undefined;  d.fy = undefined; } 
 body {  overflow: hidden;  background: #e6e7ee;  margin: 0; }  circle {  fill: whitesmoke;  stroke: black; }  line {  stroke: black; } 
 lt;script src="https://d3js.org/d3.v7.js"gt;lt;/scriptgt;