Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
183 views
in Technique[技术] by (71.8m points)

javascript - d3.js - graph is broken after adding new nodes

I build the graph below with the following logic:

  • Mouseover a node - it's non-connected nodes & their links are transparent.
  • Mouseover a link - all nodes & links are transparent, except the touched link (which become bold) and it's two nodes.

That works great, until I try to add more nodes to the graph. Press on the Add Node button, adds another node and it's link to the party. The problem is, that the node logic is broken (the link logic still work). Any idea why?

Thanks! JSFiddle

function removeNodePopup() {
    $("#nodePopup").remove();
}

function showNodePopup(node) {
    removeNodePopup();
    if (!node['data']) {
        return;
    }

    var data = node['data'];
    var htmlStr = '';
    htmlStr += '<div id="nodePopup" >';
    htmlStr += '    <div><button id="nodePopupCloseButton" type="button" class="close" data-dismiss="alert"><span class="glyphicon glyphicon-remove" style="font-size: 13px;"> </span> </div>';
    htmlStr += '    <div class="nodePopupName">' + data['name'] + '</div>';
    if (data['desc']) {
        if (data['desc'].startsWith("http")) {
            htmlStr += '    <a class="nodePopupLink" href="' + data['desc'] + '" target="_blank">Go to post..</a>';
        }
        else {
            htmlStr += '    <div class="nodePopupDesc">' + data['desc'] + '</div>';
        }
    }
    htmlStr += '    <div class="nodePopupGroup">GROUP: ' + data['groupId'] + '</div>';
    htmlStr += '    <div class="nodePopupLeader">LEADER: ' + data['leaderId'] + '</div>';
    htmlStr += '    <div class="nodePopupImage"><img src="' + node['image'] + '" style="width: 130px;" /></div>';
    htmlStr += '</div>';

    $("body").append(htmlStr);
    $("#nodePopupCloseButton").click(removeNodePopup);
}

const LINK_DEFAULT_COLOR = "#ccc";
const NODE_DEFAULT_COLOR = "gray";
const DEFAULT_OPACITY = 1;
const BACKGROUND_OPACITY = 0.2;

function Graph(elementId) {
    var svg;
    var simulation;
    var mNodesData = [];
    var mEdgesData = [];
    var mNode = null;
    var mLink = null;
    var elementId;
    var heightDelta = 100;
    var width = window.innerWidth;
    var height = window.innerHeight - heightDelta;

    return {
        init: function () {
            svg = d3.select('#' + elementId)
                .append("svg")
                .attr("width", width)
                .attr("height", height);

            simulation = d3.forceSimulation()
                .force(".edge", d3.forceLink())
                .force("charge", d3.forceManyBody().strength(-600))
                .force("center", d3.forceCenter(width / 2, height / 2));

            mLink = svg.selectAll(".edge")
                .attr("class", "edge")
                .style("stroke", LINK_DEFAULT_COLOR)
                .style("stroke-width", function (e) {
                    return 1
                    /* e.width*/
                });

            mNode = svg.selectAll(".node")
                .attr("class", "node");
        },
        clearGraph: function () {
            $('#' + this.elementId).empty();
        },
        getNodes: function () {
            return mNodesData;
        },
        getEdges: function () {
            return mEdgesData;
        },
        addNodes: function (nodes) {
            mNodesData = mNodesData.concat(nodes);
        },
        addEdges: function (edges) {
            mEdgesData = mEdgesData.concat(edges);
        },
        onMouseOut: function () {
            // removePopup();
            mNode.select("image").style("opacity", DEFAULT_OPACITY);
            mNode.select("circle").style("stroke", NODE_DEFAULT_COLOR);
            mLink.style("opacity", DEFAULT_OPACITY).style("stroke", LINK_DEFAULT_COLOR);
        },
        draw: function () {
            mNode = svg.selectAll(".node")
                .data(mNodesData)
                .enter()
                .append("g")
                .attr("class", "node").
                merge(mNode);

            mLink = svg.selectAll(".edge")
                .data(mEdgesData)
                .enter()
                .append("line")
                .attr("class", "edge")
                .style("stroke", LINK_DEFAULT_COLOR)
                .style("stroke-width", function (e) {
                    return 2
                    /* e.width*/
                }).merge(mLink).lower();

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

            mNode.on('mouseover', function (thisNode) {
                showNodePopup(thisNode);
                var thisNodeID = thisNode.id;
                var connectedNodes = mEdgesData.filter(function(d) {
                    return d.source.id === thisNodeID || d.target.id === thisNodeID
                }).map(function(d) {
                    return d.source.id === thisNodeID ? d.target.id : d.source.id
                });

                mNode.each(function (otherNode, id) {
                    var image = d3.select(this).select("image");
                    var circle = d3.select(this).select("circle");
                    if (connectedNodes.indexOf(otherNode.id) > -1 || thisNodeID == otherNode.id) {
                        image.style("opacity", DEFAULT_OPACITY);
                        circle.style("stroke", NODE_DEFAULT_COLOR);
                    }
                    else {
                        image.style("opacity", BACKGROUND_OPACITY);
                        circle.style("stroke", "#f6f6f6");
                    }
                });

                // var filteredNodes = mNode.filter(function(otherNode) {
                //     return connectedNodes.indexOf(otherNode.id) == -1
                // });
                //
                // filteredNodes.select("image").style("opacity", BACKGROUND_OPACITY);
                // filteredNodes.select("circle").style("stroke", "#f6f6f6");
                //
                // var unfilterdNode = mNode.filter(function (otherNode) {
                //     return connectedNodes.indexOf(otherNode.id) > -1 || thisNodeID == otherNode.id;
                // });
                // unfilterdNode.select("image").style("opacity", DEFAULT_OPACITY);
                // unfilterdNode.select("circle").style("stroke", NODE_DEFAULT_COLOR);

                mLink.filter(function (otherLink) {
                    return (thisNode !== otherLink.source && thisNode !== otherLink.target);
                }).style("opacity", BACKGROUND_OPACITY);

                mLink.filter(function (otherLink) {
                    return (thisNode == otherLink.source || thisNode == otherLink.target);
                }).style("opacity", DEFAULT_OPACITY);
            })
                .on('mouseout', this.onMouseOut);

            mLink.on('mouseover', function (currentLink) {
                mLink.filter(function (otherLink) {
                    return (currentLink == otherLink);
                }).style("stroke", "black");
                mLink.filter(function (otherLink) {
                    return (currentLink !== otherLink);
                }).style("opacity", BACKGROUND_OPACITY);

                mNode.filter(function (otherNode) {
                    return (currentLink.source != otherNode || currentLink.target != otherNode);
                }).select("image").style("opacity", BACKGROUND_OPACITY);
                mNode.filter(function (otherNode) {
                    return (currentLink.source != otherNode || currentLink.target != otherNode);
                }).select("circle").style("stroke", "#f6f6f6");

                mNode.filter(function (d1) {
                    return (d1 == currentLink.source || d1 == currentLink.target);
                }).select("image").style("opacity", DEFAULT_OPACITY);
                mNode.filter(function (d1) {
                    return (d1 == currentLink.source || d1 == currentLink.target);
                }).select("circle").style("stroke", NODE_DEFAULT_COLOR);

            }).on('mouseout', this.onMouseOut);

            var nodeCircle = mNode.append("circle")
                .attr("r", function (d) {
                    return 0.5 * Math.max(d.width, d.height)
                })
                .attr("stroke", NODE_DEFAULT_COLOR)
                .attr("stroke-width", "2px")
                .attr("fill", "white");

            var nodeImage = mNode.append("image")
                .attr("xlink:href", function (d) {
                    return d.image
                })
                .attr("height", function (d) {
                    return d.height + ""
                })
                .attr("width", function (d) {
                    return d.width + ""
                })
                .attr("x", function (d) {
                    return -0.5 * d.width
                })
                .attr("y", function (d) {
                    return -0.5 * d.height
                })
                .attr("clip-path", function (d) {
                    return "circle(" + (0.48 * Math.max(d.width, d.height)) + "px)"
                });


            simulation.nodes(mNodesData);
            simulation.force(".edge").links(mEdgesData);

            simulation.on("tick", function () {
                mLink.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;
                    })

                mNode.attr("transform", function (d) {
                    return "translate(" + d.x + "," + d.y + ")"
                });
                mNode.attr("cx", function (d) {
                    return d.x = Math.max(d.width, Math.min(width - d.width, d.x));
                })
                    .attr("cy", function (d) {
                        return d.y = Math.max(d.height, Math.min(height - heightDelta - d.height, d.y));
                    });
            });

            function dragstarted(d) {
                if (!d3.event.active) simulation.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) simulation.alphaTarget(0);
                d.fx = null;
                d.fy = null;
            }
        }
    }
}

initialData = {
    "nodes": [{
        "id": 0,
        "image": "images/0.jpg",
        "height": 40,
        "width": 40,
        "data": {
            "name": "Number0",
            "groupId": "Bla1",
            "desc": "Desc1",
            "leaderId": "123-123"
        }
    }, {
        "id": 1,
        "image": "images/1.jpeg",
        "height": 100,
        "width": 100,
        "data": {
            "name": "Number1",
            "groupId": "Bla2",
            "desc": "Desc1",
            "leaderId": "123-123"
        }
    }, {
        "id": 2,
        "image": "images/2.png",
        "height": 50,
        "width": 50,
        "data": {
            "name": "Number2",
            "groupId": "Bla3",
            "desc": "Desc1",
   

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

jsfiddle

You called draw method multiple times, so you have to treat it carefully.

Only newly added nodes are supposed to be added image and circle tags, so you should change

mNode = svg.selectAll(".node")
            .data(mNodesData)
            .enter()
            .append("g")
            .attr("class", "node").
            merge(mNode);

to

var newNodes = svg.selectAll(".node")
            .data(mNodesData)
            .enter()
            .append("g")
            .attr("class", "node");

mNode=newNodes.merge(mNode);

and change

var nodeCircle = mNode.append("circle")

to

var nodeCircle = newNodes.append("circle")

And image related code are supposed to be changed in same manner.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...