0

I am not sure if this has been done (or can be done) since I have not seen any examples or questions regarding it but I will try to explain as best as I can.

I have a d3 force graph where I am trying to give it the functionality to "expand". Example: I have a JSON with

{
"nodes": [
{"name":"p1"},
{"name":"p2"},
{"name":"p3"},
{"name":"p4"}
],
"links": [
{"source":"p1","target":"p2"},
{"source":"p1","target":"p3"},
{"source":"p3","target":"p2"},
{"source":"p3","target":"p4"}
]}

So if a user selects node p3 and selects expand. It sends a request and we get a JSON back that can comes in with new nodes and links (but can also contain duplicates). ie,

{
"nodes": [
{"name":"p3"},
{"name":"p4"},
{"name":"p5"},
{"name":"p6"}
],
"links": [
{"source":"p3","target":"p4"},
{"source":"p4","target":"p5"},
{"source":"p4","target":"p6"}
]}

Since I wasn't sure if this could be done in d3 in the first place. I tested the functionality by just appending the new JSON data to the old JSON data (dupes and all). Now I assumed that d3 would check for duplicates already in the graph (like p3 to p4) but after appending, when I run the graph all p3 p4 p5 and p6 are just floating in space with no links even though I specified the links and it created p3 p4 node even though it already was there. (The initial graph with the 4 nodes still built and was linked properly).

So is the functionality possible to perform in d3? I have seen people who want to have multiple graphs on the screen but I am doing more of like an overlap/merge.

I have tried having my initial graph created then I use a test where I press a button and I read it in another JSON but it breaks or just doesn't create anything.

My code:

// Define the dimensions of the visualization.
var width = innerWidth,
    height = innerHeight,
    color = d3.scale.category20(),
    root;

// Create an array logging what is connected to what
var linkedByIndex = { };

// Create the SVG container for the visualization and define its dimensions
var svg = d3.select('body').append('svg')
    .attr('width', width)
    .attr('height', height);

var link = svg.selectAll(".link"),
    node = svg.selectAll(".node"),
    linkText;

// Mouse Event Variables
var selected_node = null,
    selected_link = null,
    mousedown_node = null,
    mousedown_link = null,
    mouseup_node = null;

// Create the force layout
var force = d3.layout.force()
    .size([width, height])
    .charge(-650)
    .linkDistance(80);

var jsonStack = { };
var jsonCount = 0;
var jsonPath1 = "../../test/resources/cytoscape.json";
var jsonPath2 = "../../test/resources/cytoscapeexpand.json";

// Read in the JSON data.
d3.json(jsonPath1, function (error, json) {
// expands scope of json
jsonStack[jsonCount] = json;
root = jsonStack[jsonCount];
console.log("Successfully loaded" + json);
//console.log(JSON.stringify(root));
update();
jsonCount += 1;
});

d3.select('#expand').on("click", function() {
d3.json(jsonPath2, function(error, json) {
    // expands scope of json
    root = json

    update();
});
});

function update() {

// sets the source and target to use id instead of index
root.edges.forEach(function(e) {
    var sourceNode = root.nodes.filter(function(n) {
                return n.data.id === e.data.source;
        })[0],
        targetNode = root.nodes.filter(function(n) {
                return n.data.id === e.data.target;
        })[0];

    // push the EDGE attributes in the JSON to the edges array.
    edges.push({
        source: sourceNode,
        target: targetNode,
        label: e.data['label'],
        color: e.data['color']
    });
});

force
        .nodes(root.nodes)
        .links(edges)
        .start();

link = link
        .data(edges)
        .enter().append("line")
        .attr("class", "link")
        .style("stroke-width", 1.5);

node = node
        .data(root.nodes)
        .enter().append("g")
        .attr("class", "node")
    //.attr("fixed", function(d) { return d.fixed=true })
        .call(force.drag)
        .on('mouseover', connectedNodes)
        .on('mouseleave', restore)
        .on('dblclick', highlight);

node.append("circle").attr("r", 11);

node.style("fill", function(d) { return d.data['color'] }).select("circle").style("stroke", "black");
link.style("stroke", function(d) { return d.color });

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

root.edges.forEach(function (d) {
    linkedByIndex[d.data.source + "," + d.data.target] = 1;
});

resize();
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 + ")"; });

});
}

// Highlighting of selected node.
function highlight(d) {
if (d.selected == false) {
    d.selected = true;
        return d3.select(this).style("fill", "yellow");
}
else {
    d.selected = false;
        return d3.select(this).style("fill", d.data['color']);
}
update();
}
Joey
  • 1,724
  • 3
  • 18
  • 38
  • Yes, this is certainly possible. For modifying a force layout, you may find the second section of [this tutorial](https://www.airpair.com/javascript/posts/d3-force-layout-internals) helpful. – Lars Kotthoff May 04 '15 at 20:14
  • Hmmmmm. So I have to `stop`, add my files to `data()` and then `start` again? I just attempted this and I ran my test where I click a button and read in another JSON file (after the initial force graph is initialized and created) and it just makes the previous graph freeze in place and creates a new graph (with the new JSON data) over it. – Joey May 04 '15 at 21:11
  • Technically you don't have to stop the layout, but you do have to call `.start()` once all the new data has been added. [This question](http://stackoverflow.com/questions/21070899/d3-js-how-to-remove-nodes-when-link-data-updates-in-a-force-layout) and [this question](http://stackoverflow.com/questions/9539294/adding-new-nodes-to-force-directed-layout) may help, as may [this example](https://vida.io/documents/Aea4ZgQZ88Fx6AZMu). – Lars Kotthoff May 04 '15 at 21:33
  • I have tried following the examples. So now when I run the test in my code when getting the second JSON, it has a very weird behavior. When clicking the expand once, only the links will show up with no edges. Then after clicking the expand button again, the full graph shows as a duplicate but with my highlight function (when mouseover it will show neighbors) if I put the mouse over a node in the original graph if any neighboring node was suppose to be connected in the second graph it will be highlighted. I still can't get them to connect. I have updated my question with parts of my code. – Joey May 11 '15 at 14:12

0 Answers0