score:1

Accepted answer

You need to understand the difference between an array of data and a d3 selection. In your code, link and node are d3 selections representing the circles and lines. They are not representations of the underlying data.

They do, however, supply some functions that can be useful in this context. For example, you can use .each(...) instead of forEach to loop over the elements, or .data() to get the objects that the d3 selection represents as an array, so link.data().map is definitely a valid function.

I implemented what you want by adding a property isVisible to every node or link that determines visibility. However, this is absolutely not the best or only way to do this, so do feel free to explore alternatives.

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 => link.isVisible !== false);
  const nodes = data.nodes.filter(node =>
    node.isVisible !== false);

  link = linksContainer.selectAll("line")
    .data(links)
    .join("line")
    .attr("class", "line")

  node = nodesContainer.selectAll(".node")
    .data(nodes, d => d.id)
    .join("g")
    .attr("class", "node")
    .call(d3.drag()
      .on("start", dragStarted)
      .on("drag", dragged)
      .on("end", dragEnded)
    )

  node.selectAll("circle")
    .data(d => [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 => {
      link.isVisible = link.target.id === d.id;
    });
  const visibleNodeIds = [
    d.id,
    ...link.data()
    .filter(l => l.isVisible)
    .map(l => l.source.id)
  ];

  node.data()
    .forEach(node => {
      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;
}
<script src="https://d3js.org/d3.v7.js"></script>


Related Query

More Query from same tag