2

Below is an excerpt of my JS code to implement a force layout graph in D3. I have two requirements:

  1. Clicking a node should cause child nodes to be retrieved from the backend and added to the graph (this works).

  2. When dragging a node, it should remain fixed when dragging stops. When that same node is clicked in combination with the CMD or CTRL key, the node should be released again. (this works as well)

My issue is that when I click a node to expand it further, the node becomes fixed as well. Thus, clicking inadvertently caused the drag event to be called. Dragging however does not cause the node to expand further (due to if (d3.event.defaultPrevented) return;).

function visNetwork(graph_id) {

    this.graph_id = graph_id;

    /* Some code removed for brevity */

    var dragstart = function(d) {
        console.log('Detected drag...');
        d3.event.sourceEvent.preventDefault();
        d3.select(this).classed("fixed", d.fixed = true);
    };

    // set up the D3 visualisation in the specified element
    var w = 2000,
        h = 2000;

    var vis = d3.select("#svg-container")
        .append("svg")
        .attr("id", this.graph_id)
        //necessary to convert the SVG to canvas
        .attr("version", 1.1)
        .attr("xmlns", "http://www.w3.org/2000/svg")
         //better to keep the viewBox dimensions with variables
        .attr("viewBox", "0 0 " + w + " " + h )
        .attr("preserveAspectRatio", "xMidYMid meet");

    var force = d3.layout.force()
        .charge(-1500)
        .linkDistance(150)
        .size([w, h])
        .gravity(.1);

    var drag = force.drag()
        .on("dragstart", dragstart);

    var nodes = force.nodes(),
        links = force.links();

    var update = function() {
        var link = vis.selectAll("line")
            .data(links, function(d) {
                return d.source.id + "-" + d.target.id;
            });

        link.enter().insert("line", "g")
            .attr("id", function(d) {
                return d.source.id + "-" + d.target.id;
            })
            .attr("class", "link")
            .attr("stroke", "#ccc");

        link.exit().remove();

        var node = vis.selectAll("g.node")
            .data(nodes, function(d) {
                return d.id;
            });

        var nodeEnter = node.enter().append("g")
            .attr("class", "node")
            .on('click', function(d, i) {
                if (d3.event.defaultPrevented) {
                    console.log('Ignoring click event...');
                    return;
                }
                console.log('Captured click event...');

                //Release fixed node when CTRL or CMD key is pressed
                if (d3.event.ctrlKey || d3.event.metaKey) {
                    d3.select(this).classed("fixed", d.fixed = false);
                    return;
                }
                modifyGraph(d.id, d3.event);
                })
                .call(drag);

        nodeEnter.append("svg:circle"); /* Code removed for brevity */

        nodeEnter.append("svg:text"); /* Code removed for brevity */

        node.exit().remove();

        force.on("tick", function() { /* Code removed for brevity */ });
        // Restart the force layout.
        force.start();
    };
    update();
}
DocZerø
  • 8,037
  • 11
  • 38
  • 66
  • 2
    Which version of d3 are you using? Read also the comments of [this answer](http://stackoverflow.com/a/19951105/1006854). – Mehdi Jan 21 '16 at 10:01
  • could you mock up a fiddle that shows the basics of your problem ? – thatOneGuy Jan 21 '16 at 10:02
  • @mef I'm using D3.js version 3.5.12 – DocZerø Jan 21 '16 at 10:03
  • 2
    Would it be possible to fix the node once "drag" occurs, and ignore "dragstart"? – Culme Jan 21 '16 at 10:08
  • @Culme yes, that actually fixed it. I replaced `dragstart` by `drag`. This is also hinted at when looking at [the post](http://stackoverflow.com/questions/19931307/d3-differentiate-between-click-and-drag-for-an-element-which-has-a-drag-behavior/19951105#19951105) @mef referenced and [here](http://stackoverflow.com/a/19077477/3165737): `click` will still call `dragstart`, but it won't call `drag` because of the `d3.event.defaultPrevented` check. – DocZerø Jan 21 '16 at 10:11
  • @Culme if you'd create an answer out of your comment, I'd happily accept it as the answer. – DocZerø Jan 21 '16 at 10:12

1 Answers1

0

In this case, you could achieve what you want by fixing the node once "drag" occurs, ignoring "dragstart" altoghether.

Please also read the question and answers linked to by @mef in the comments section, which contains more on the subject.

Community
  • 1
  • 1
Culme
  • 1,065
  • 13
  • 21