0

I'm new to this issue in d3.js, basically I would like an effect like this gif:

enter image description here

https://bl.ocks.org/mbostock/1062288

When you click on a node, the child nodes are hidden and if they are already hidden, they are displayed. I have no clues how to do it.

My structure is like this:

var label = {
'nodes': [],
'links': []
};

The examples I have found to build on them are with a different structure.

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<html>
<body>
<svg id='viz'></svg>
</body>

<script src='https://d3js.org/d3.v5.min.js'></script>
<script>
var width = 800;
var height = 600;
var color = d3.scaleOrdinal(d3.schemeCategory10);
d3.json("https://gist.githubusercontent.com/mapio/53fed7d84cd1812d6a6639ed7aa83868/raw/760d5793f3596c82856d4a84f9b7a84851456027/miserables.json").then(function(graph) {
var label = {
    'nodes': [],
    'links': []
};
graph.nodes.forEach(function(d, i) {
    label.nodes.push({node: d});
    label.nodes.push({node: d});
    label.links.push({
        source: i * 2,
        target: i * 2 + 1
    });
});
var labelLayout = d3.forceSimulation(label.nodes)
    .force("charge", d3.forceManyBody().strength(-50))
    .force("link", d3.forceLink(label.links).distance(0).strength(2));
var graphLayout = d3.forceSimulation(graph.nodes)
    .force("charge", d3.forceManyBody().strength(-3000))
    .force("center", d3.forceCenter(width / 2, height / 2))
    .force("x", d3.forceX(width / 2).strength(1))
    .force("y", d3.forceY(height / 2).strength(1))
    .force("link", d3.forceLink(graph.links).id(function(d) {return d.id; }).distance(50).strength(1))
    .on("tick", ticked);
var adjlist = [];
graph.links.forEach(function(d) {
    adjlist[d.source.index + "-" + d.target.index] = true;
    adjlist[d.target.index + "-" + d.source.index] = true;
});
function neigh(a, b) {
    return a == b || adjlist[a + "-" + b];
}
var svg = d3.select("#viz").attr("width", width).attr("height", height);
var container = svg.append("g");
svg.call(
    d3.zoom()
        .scaleExtent([.1, 4])
        .on("zoom", function() { container.attr("transform", d3.event.transform); })
);
var link = container.append("g").attr("class", "links")
    .selectAll("line")
    .data(graph.links)
    .enter()
    .append("line")
    .attr("stroke", "#aaa")
    .attr("stroke-width", "1px");
var node = container.append("g").attr("class", "nodes")
    .selectAll("g")
    .data(graph.nodes)
    .enter()
    .append("circle")
    .attr("r", 5)
    .attr("fill", function(d) { return color(d.group); })
node.on("mouseover", focus).on("mouseout", unfocus);
node.call(
    d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended)
);
var labelNode = container.append("g").attr("class", "labelNodes")
    .selectAll("text")
    .data(label.nodes)
    .enter()
    .append("text")
    .text(function(d, i) { return i % 2 == 0 ? "" : d.node.id; })
    .style("fill", "#555")
    .style("font-family", "Arial")
    .style("font-size", 12)
    .style("pointer-events", "none"); // to prevent mouseover/drag capture
node.on("mouseover", focus).on("mouseout", unfocus);
function ticked() {
    node.call(updateNode);
    link.call(updateLink);
    labelLayout.alphaTarget(0.3).restart();
    labelNode.each(function(d, i) {
        if(i % 2 == 0) {
            d.x = d.node.x;
            d.y = d.node.y;
        } else {
            var b = this.getBBox();
            var diffX = d.x - d.node.x;
            var diffY = d.y - d.node.y;
            var dist = Math.sqrt(diffX * diffX + diffY * diffY);
            var shiftX = b.width * (diffX - dist) / (dist * 2);
            shiftX = Math.max(-b.width, Math.min(0, shiftX));
            var shiftY = 16;
            this.setAttribute("transform", "translate(" + shiftX + "," + shiftY + ")");
        }
    });
    labelNode.call(updateNode);
}
function fixna(x) {
    if (isFinite(x)) return x;
    return 0;
}
function focus(d) {
    var index = d3.select(d3.event.target).datum().index;
    node.style("opacity", function(o) {
        return neigh(index, o.index) ? 1 : 0.1;
    });
    labelNode.attr("display", function(o) {
      return neigh(index, o.node.index) ? "block": "none";
    });
    link.style("opacity", function(o) {
        return o.source.index == index || o.target.index == index ? 1 : 0.1;
    });
}
function unfocus() {
   labelNode.attr("display", "block");
   node.style("opacity", 1);
   link.style("opacity", 1);
}
function updateLink(link) {
    link.attr("x1", function(d) { return fixna(d.source.x); })
        .attr("y1", function(d) { return fixna(d.source.y); })
        .attr("x2", function(d) { return fixna(d.target.x); })
        .attr("y2", function(d) { return fixna(d.target.y); });
}
function updateNode(node) {
    node.attr("transform", function(d) {
        return "translate(" + fixna(d.x) + "," + fixna(d.y) + ")";
    });
}
function dragstarted(d) {
    d3.event.sourceEvent.stopPropagation();
    if (!d3.event.active) graphLayout.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) graphLayout.alphaTarget(0);
    d.fx = null;
    d.fy = null;
}
}); // d3.json
</script>
</html>
halfer
  • 19,824
  • 17
  • 99
  • 186
yavg
  • 2,761
  • 7
  • 45
  • 115
  • It isn't clear what nodes are parents and what nodes are not if you don't have a hierarchy - which is complicated further in your snippet: which nodes are parents and which children when there are circular paths? – Andrew Reid Jul 24 '19 at 15:42

1 Answers1

0

It is as shown in the link you provided, when the user clicks on a node, if the node has data in children object, then data will be moved to an object called _children, and the data in children object is nullified, vice versa, if user clicks on a node without children, any data in _children object will be moved to children object and _children is nullified.

links array has the children of nodes but they are not connected together
Grouping children:

let data = 'miserables.json' ; // [ JSON DATA HERE]
let new_arr = []
data.nodes.forEach(function(el) {
  new_arr = {
              id: el.id,
              children: data.links.filter(item => item.source == el.id)                 
             }
})

Attach click event:

var node = container.append("g").attr("class", "nodes")
    .selectAll("g")
    .data(graph.nodes)
    .enter()
    .append("circle")
    .attr("r", 5)
    .attr("fill", function(d) { return color(d.group); })
    .on("click", click)



// Toggle children on click.
function click(d) {
  if (!d3.event.defaultPrevented) {
    // if there children, move them to _children and clear data in children
    if (d.children) {
      d._children = d.children;
      d.children = null;
    } else {
    // if no children, move data from _children to children and clear data in _children
      d.children = d._children;
      d._children = null;
    }
  }
}
Shiko
  • 2,448
  • 1
  • 24
  • 31
  • You need to group **links** under their parent, at that time you will have children for every parent. – Shiko Jul 25 '19 at 03:37
  • That means that with my current structure I can't do it? – yavg Jul 25 '19 at 03:40
  • I don't think with current structure, the best thing is to format the structure. – Shiko Jul 25 '19 at 03:42
  • @yavg I have added a snippet showing how to regroup – Shiko Jul 25 '19 at 03:47
  • I understand ... but it is still a complex thing to do the next step, I appreciate your help, but could you please show me how to hide the children by clicking on the node? thanks genious – yavg Jul 25 '19 at 04:05
  • I have update the answer, but you need to complete the remaining – Shiko Jul 25 '19 at 04:23