简体   繁体   中英

d3 Not “De-Selecting” Nodes Properly

I have this "select node" functionality in my d3 force-graph. For some reason it is not behaving as it should. The code should make it so when a user double clicks the node it gets selected/deselected (Goes back to normal color/turns yellow). Everything works when you are selecting then deselecting a node. The issue comes in the following cases:

  1. If you select a node A then select node B (at this point they are both yellow as it should) then go back and double click node A to de-select it gets read as being selected again (stays yellow).
  2. When you select node A then select node B. Then you double click node B to de-select it will work as intended. But when you go back to double click node A (which is still selected) to de-select it will get read in as selected.

Here are some code snippets from where I think the issue is.

var edges = [];
var nodes = [];
var nodesHash = {};

function update() {

    // clear stack of selected nodes
    selectedNodes = [];

    // Update link data based on edges array.
    link = link.data(edges);

    // Create new links
    link.enter().append("line")
            .attr("class", "link")
            .style("stroke-width", 1.5);

    // Delete removed links
    link.exit().remove();

    // Update node data based on nodes array.
    node = node.data(nodes);

    // Create new nodes
    node.enter().append("g")
            .attr("class", "node")
            .attr("id", function(d) { return d.data['id'] })
            .call(force.drag)
            .on('mouseover', connectedNodes)
            .on('mouseleave', restore)
            .on('dblclick', highlight);

    // Delete removed nodes
    node.exit().remove();

    node.append("circle").attr("r", 11);
    node.classed("selected", function(d) { return d === d.selected; })

    // Node behavior for checking if selected otherwise colors nodes to color given from JSON.
    node.style("fill", function(d) {
        if (d.selected === false) {
            return d.data['color']
            update();
        }
        else {
            return "yellow";
            update();
        }
    }).select("circle").style("stroke", "black");

    // Link color based on JSON data.
    link.style("stroke", function(d) { return d.data['color'] });

    // Adds text to nodes
    node.append("text")
            .attr("dx", 12)
            .attr("dy", ".35em")
            .style("fill", "black")
            .text(function (d) { return d.data['label']; });

    // Creates an index used to figure out neighbor nodes.
    root.edges.forEach(function (d) {
        linkedByIndex[d.data.source + "," + d.data.target] = 1;
    });

    // responsive behavior for graph based on window.
    window.addEventListener('resize', resize);

    force.on("tick", function() {
        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 + ")"; });
    });
    force.start();
}

// Reset Mouse Variables
function resetMouseVars() {
    mousedown_node = null;
    mouseup_node = null;
    mousedown_link = null;
}

// Highlighting of selected node.
function highlight(d) {
    mousedown_node = d;
    if (mousedown_node != selected_node) {
        console.log("Selected: " + mousedown_node.data['id']);
        selected_node = mousedown_node;
        selected_node.selected = true;
    }
    else {
        console.log("De-Selected: " + mousedown_node.data['id']);
        selected_node = mousedown_node;
        selected_node.selected = false;
    }
    resetMouseVars();
    update();
}

function spliceLinksForNode(node) {
    toSplice = edges.filter(
            function(e) {
                return (e.source === node) || (e.target === node); });
    toSplice.map(
            function(e) {
                edges.splice(edges.indexOf(e), 1); });
}

// Delete node with prompt
function deleteNode() {
    console.log("Prompted to delete: " + selected_node);
    if (confirm("Deleting selected elements will remove them from the graph entirely. Are you sure?")) {
        if (!selected_node) alert("No node selected");
        if (selected_node) {
            console.log("Deleted: " + selected_node);
            selected_node.removed = true;
            nodes.splice(nodes.indexOf(selected_node), 1);
            spliceLinksForNode(selected_node);
        }
        selected_node = null;
        update();
    }
}

So in my code the node.on('dblclick', highlight) is just changing the currently selected nodes property of whether it is selected or not. The node.style is doing the actual checking of the property and changing the color.

Any assistance on why this odd behavior is happening will be appreciated!

If you select A, then selected_node gets set to A.

Then, if you select B, then selected_node gets set to B

Then, if you click A again then, inside highlight() , the expression mousedown_node != selected_node evaluates to true because A, which is mousedown_node is not equal selected_node , which is still B, from the previous selection.

So that's a bug.

If you are allowing multi-selection then there's no way a single variable selected_node is sufficient to capture the selection state. If you had a selected_nodes Array from which you add and remove nodes, then you can check if selected_nodes.indexOf(mousedown_node) > -1 to determine if it's selected.

But really, I don't see why you need all this logic at all — unless maybe some code that you didn't include is relying on selected_node . Really, all your highlight function needs to be is just

function highlight(d) {
  d.selected = !d.selected;
  update();
}

And that should fix your problem.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM