Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

D3 updating graph with new elements create edges with the wrong nodes

My code creates a graph and creates a pivot point on each node, if you double click them it'll fetch more data associated with that node and hopefully creates new links. Now here's the problem I'm running into:

enter image description here enter image description here

I clicked on one of the outermost nodes but for some reason the new links were being attached to the first node (the blue one). Any idea why this is happening?

function draw_graph(graph) {
    var color = d3.scaleOrdinal(d3.schemeCategory20);

    var svg = d3.select("svg"),
        width = +svg.attr("width"),
        height = +svg.attr("height"),
        node,
        link;

    svg.append('defs').append('marker')
        .attrs({
            'id': 'arrowhead',
            'viewBox': '-0 -5 10 10',
            'refX': 13,
            'refY': 0,
            'orient': 'auto',
            'markerWidth': 13,
            'markerHeight': 13,
            'xoverflow': 'visible'
        })
        .append('svg:path')
        .attr('d', 'M 0,-5 L 10 ,0 L 0,5')
        .attr('fill', '#999')
        .style('stroke', 'none');

    var simulation = d3.forceSimulation()
        .force("link", d3.forceLink().id(function (d) {
            return d.id;
        }).distance(200).strength(1))
        .force("charge", d3.forceManyBody())
        .force("center", d3.forceCenter(width / 2, height / 2));

    update(graph.links, graph.nodes);

    svg.selectAll('circle').on('dblclick', function () {
        var pivot_id = ($(this).siblings('title').text())
        console.log('pivoting on', pivot_id)
        pivot_search(pivot_id)
    });


    function update(links, nodes) {
        link = svg.selectAll(".link")
            .data(links)
            .enter()
            .append("line")
            .attr("class", "link")
            .attr('marker-end', 'url(#arrowhead)')


        edgepaths = svg.selectAll(".edgepath")
            .data(links)
            .enter()
            .append('path')
            .attrs({
                'class': 'edgepath',
                'fill-opacity': 0,
                'stroke-opacity': 0,
                'id': function (d, i) {
                    return 'edgepath' + i
                }
            })
            .style("pointer-events", "none");

        edgelabels = svg.selectAll(".edgelabel")
            .data(links)
            .enter()
            .append('text')
            .style("pointer-events", "none")
            .attrs({
                'class': 'edgelabel',
                'id': function (d, i) {
                    return 'edgelabel' + i
                },
                'font-size': 10,
                'fill': '#aaa'
            });

        node = svg.selectAll(".node")
            .data(nodes)
            .enter()
            .append("g")
            .attr("class", "node")
            .call(d3.drag()
                .on("start", dragstarted)
                .on("drag", dragged)
            );

        node.append("circle")
            .attr("r", 5)
            .attr("fill", function (d) {
                return color(d.group);
            })


        node.append("title")
            .text(function (d) {
                return d.id;
            });

        node.append("text")
            .attr("dy", -3)
            .text(function (d) {
                return d.label;
            });


        simulation
            .nodes(nodes)
            .on("tick", ticked);

        simulation.force("link")
            .links(links);

    }

    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;
            });

        node
            .attr("transform", function (d) {
                return "translate(" + d.x + ", " + d.y + ")";
            });

        edgepaths.attr('d', function (d) {
            return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
        });

        edgelabels.attr('transform', function (d) {
            if (d.target.x < d.source.x) {
                var bbox = this.getBBox();

                rx = bbox.x + bbox.width / 2;
                ry = bbox.y + bbox.height / 2;
                return 'rotate(180 ' + rx + ' ' + ry + ')';
            }
            else {
                return 'rotate(0)';
            }
        });
    }

    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 pivot_search(entity_id) {
    var json = {
        'nodes': [],
        'links': [],
    }
    get_entities({'id': entity_id})
        .done(function (data) {
            json.nodes.push({
                'label': data['results'][0]['label'],
                'id': data['results'][0]['id'],
                'group': data['results'][0]['entity_type'],
            })
            get_entities({
                'related_entities': entity_id,
                'related_entities__entity_instance__entity_type__strong_entity': true,
                'page_size': 500
            })
                .done(function (data) {
                    for (var i = 0; i < data['results'].length; i++) {
                        json.nodes.push({
                            'label': data['results'][i]['label'],
                            'id': data['results'][i]['id'],
                            'group': data['results'][i]['entity_type'],
                        })
                        json.links.push({
                            'source': entity_id,
                            'target': data['results'][i]['id'],

                        })
                    }
                    draw_graph(json)
                })
        })
}

EDIT: Upon further investigations seems like it's replacing the existing node links with the new data and creating new potentially duplicate nodes.

link = svg.selectAll('.link')
            .data(links, function (d) {
                return d.id;
            })
            .enter()
            .append('line')
            .attr('class', 'link')
            .attr('marker-end', 'url(#arrowhead)')


        edgepaths = svg.selectAll('.edgepath')
            .data(links)
            .enter()
            .append('path')
            .attrs({
                'class': 'edgepath',
                'fill-opacity': 0,
                'stroke-opacity': 0,
                'id': function (d, i) {
                    return 'edgepath' + i
                }
            })
            .style('pointer-events', 'none');

        node = svg.selectAll('.node')
            .data(nodes, function (d) {
                return d.id;
            })
            .enter()
            .append('g')
            .attr('class', 'node')
            .call(d3.drag()
                .on('start', dragstarted)
                .on('drag', dragged)
            );

I added an ID to help deal with node duplication, but now I have a problem with index root displacement.

enter image description here enter image description here

like image 564
Stupid.Fat.Cat Avatar asked Sep 29 '17 20:09

Stupid.Fat.Cat


1 Answers

It appears that the problem that you are having is due to the fact that your data might be duplicating within D3's data-join functionality. It is likely that the best way to solve the issue is to create a "UUID / GUID" for each node in your data before D3 binds it (see here for an example). Once you have done that, then you can bind the data and use the data-join's key-specify function (see here for an explanation) to tell D3 to use the UUID / GUID values you created for each object to guarantee consistency. From there, you should be able to handle the parent-child relationships easier.

Edit #1

Since that worked for the duplicate values, the next problem that you are likely having is because the reference to the "source" object is not set up the way that D3 would expect. In D3, the link's "source" property is a reference to the actual source object, where you are just providing the ID value (see here for the D3v4 docs reference). Try providing the reference to the actual source object within the array and that should fix it.

Edit #2

You are correct in that you are handling NEW data coming into the visualization, but I don't think that you're handling EXISTING or OLD (meaning, data points that are no longer relevant and the nodes / links need to be removed). In this case, try updating your code with the following example from Mike Bostock (the original creator of the D3.js library) here and then report back once that is done. It is possible that the new nodes that you are seeing are simply the older nodes that need to be removed since they no longer have the children tied to them, so D3js sees them as "new" or "existing" nodes that actually need to be removed.

like image 162
th3n3wguy Avatar answered Sep 23 '22 23:09

th3n3wguy