Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dragging nodes with labels in d3 v4 force layout glitches

Tags:

d3.js

I'm a data journalist trying my hand at d3 v4 for the first time in order to create a force layout that shows relations between bands using data from the Spotify API. I started by following the example posted by Mike Bostock (https://bl.ocks.org/mbostock/4062045). I modified the code to wrap every svg circle element inside a "g" element:

var link = svg.append("g").attr("class", "links")
            .selectAll("line")
            .data(graph.links)
            .enter().append("line")
                .attr("stroke-width", function(d) { return Math.sqrt(d.value); });
var gnodes = svg.selectAll("g.gnode")
            .data(graph.nodes)
            .enter()
            .append("g")
            .classed("gnode", true);
var node = gnodes.append("circle")
            .attr("class", "nodes")
            .attr("r", 5)
            .style("fill", function(d) { return color(d.group); })
            .call(d3.drag()
                .on("start", dragstarted)
                .on("drag", dragged)
                .on("end", dragended));

After doing that, I create the title and label elements and append them to the nodes:

gnodes.append("title").text(function(d) { return d.label });
var labels = gnodes.append("text").text(function(d) { return d.label; });
labels.attr("transform", function(d) {
    return "translate(" + (d.x) + "," + (d.y) + ")";
});

Finally, I modified the ticked() function to (hopefully) allow for the labels to move when the nodes are being dragged. You can see by the comment I made that I already tried to apply the method to each circle svg element instead of the group element, but obviously didn't work. I'm also not sure that the last line is actually doing anything useful.

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; });
    gnodes
    //node
        .attr("transform", function(d) {
            return "translate(" + [d.x, d.y] + ")";});
    labels.attr("transform", function(d) { return "translate(" + (d.x) + "," + (d.y) + ")"; });
}

The problem is that, although the labels get displayed correctly, when a node gets dragged the node "glitches" and doesn't behave properly, most probably because the label is somehow getting confused or something and they are not moving "in sync", so to say.

I've already looked at this example, also by Mike Bostock, and this one by Moritz Stefaner, but haven't been able to implement a proper solution (both of these being d3 v3 code, as far as I know, didn't help a lot).

The full code for the html page is available at this pastebin: http://pastebin.com/qw9bYHRD and, if needed for some kind of testing, the JSON file I'm using to generate the network graph is available at this address.

I'm sorry for my poor knowledge of d3, but this is one of my first deep dives into the library (also the first time using v4), after using some other tools built on top of it for some time.

Thanks a lot in advance for your time and attention.

like image 569
juanjocerero Avatar asked Sep 15 '25 07:09

juanjocerero


1 Answers

Okay, seems like writing all that actually helped me think and I managed to solve this problem by myself. Posting the answer here just in case somebody happens to have the same problem.

The very simple solution has to do with the object on which the .call(d3.drag()) is executed. I cut this piece of code from the node variable (the circle SVG elements) and pasted it at the end of the variable that defines the g SVG elements:

.call(d3.drag()
    .on("start", dragstarted)
    .on("drag", dragged)
    .on("end", dragended)); 

So the g elements declaration ends up like this:

var gnodes = svg.selectAll("g.gnode")
    .data(graph.nodes)
    .enter()
    .append("g")
    .classed("gnode", true)
    .call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended));

And it works correctly from then on. You can even drag just the label, and the node will follow it around the canvas. Great!

like image 125
juanjocerero Avatar answered Sep 17 '25 20:09

juanjocerero