javascript - implement General Update Pattern for d3.js force layout -


beginning this example (possibly not best choice), set trying develop application suit purposes, , learn d3.js in process. after lot of naive tinkering, managed toy force layout test data satisfied me in appearance , behavior. i've set trying understand mb's general update pattern in context of particular example, users can interactively modify graph. have not yet grasped principle.

starting small, thought create function add single additional link graph between nodes labeled "walteri" , "roberti de fonte" (there's button executes addedge(), or can execute js console). in broken sort of way, had desired result; however, existing graph remained in place while duplicate graph generated containing additional link. it's clear there general update pattern i'm still not understanding.

if has , can offer insight, i'd grateful.

managing updates
main issue have re-appending group elements every update.
can d3 manage this...

//nodes bag //update var circles = svg.selectall(".circles")             .data(["circles_g"]); //enter circles.enter()     .append("svg:g")     .attr("class", "circles"); 

you need make 1 element array drive it. nice thing placed on __data__ member added g element d3, handy debugging well.

general pattern
speaking, defensive pattern...

//update var update = baseselection.selectall(elementselector)             .data(values, key),     //enter         enter = update.enter().append(appendelement)             .call(initstuff),     //enter() has side effect of adding enter nodes update selection     //so update include enter nodes      //update+enter         updateenter = update             .call(stufftodoeverytimethedatachanges);     //exit     exit = update.exit().remove() 

first time through update array of nulls same structure data.
.selectall() returns 0 length selection in case , nothing useful.

on subsequent updates, .selectall not empty , compared values, using keys, determine nodes update, enter , exit nodes. that's why need select before data join.

the important thing understand has .enter().append(...), appending elements on enter selection. if append them on update selection (the 1 returned data join) re-enter same elements , see similar behaviour getting.

the enter selection array of simple objects of form { __data__: data }
update , exit selections arrays of arrays of references dom elements.

the data method in d3 keeps closure on enter , exit selections accessed .enter() , .exit() methods on update. both return objects which, among other things, 2-d arrays (all selections in d3 arrays of groups, groups arrays of nodes.). enter member given reference update can merge two. done because, in majority of cases, same stuff done both groups.

revised code
there strange bug links disappeared when edge added unrelated due nan's in d.x , d.y in nodes.
if don't rebuild force layout every time in showme , if this...

links.push({ "source": nodes[i], "target": nodes[j], "type": "is_a_tenant_of" }); force.start(); showme(); 

the bug goes away , works fine.

this because internal state layout not include links, particularly strengths , distances arrays. internal force.tick() method uses these calculate new link lengths , if there more links members of these arrays, return undefined , link, length calculation return nan , multiplied node x , y values calculate new d.x , d.y.
recalculated in force.start()

also, can move force = d3.layout.force()....start(); separate function , call once @ start.

d3.json("force-directed-edges.json", function(error, data){         if (error) return console.warn(error)                 nodes = data.nodes,                  links = data.links,                 predicates = data.predicates,                 json = json.stringify(data, undefined, 2);                  (n in nodes) { // don't want require incoming data have links array each node                         nodes[n].links = []                 }                  links.foreach(function(link, i) {                         // kept 'or' check, in case we're building nodes links                         link.source = nodes[link.source] || (nodes[link.source] = {name: link.source});                         link.target = nodes[link.target] || (nodes[link.target] = { name: link.target });                         // dijkstra searching, we'll need adjacency lists: node.links. (easier thought)                         link.source.links.push(link);                         link.target.links.push(link);                 });                  nodes = d3.values(nodes);                 restart()                 showme(); });  function randomnode(i) {     var j;     {         j = math.round(math.random() * (nodes.length - 1))     } while (j === (i ? : -1))     return j } function addedge() {     var = randomnode(), j = randomnode(i);      links.push({ "source": nodes[i], "target": nodes[j], "type": "is_a_tenant_of" });     force.start();     showme(); }  function restart() {     force = d3.layout.force()         .nodes(nodes)         .links(links)         .size([w, h])         .linkdistance(function (link) {             var wt = link.target.weight;             return wt > 2 ? wt * 10 : 60;         })         .charge(-600)         .gravity(.01)         .friction(.75)         //.theta(0)         .on("tick", tick)         .start(); } function showme() {     //marker types       var defs = svg.selectall("defs")                 .data(["defs"], function (d) { return d }).enter()                 .append("svg:defs")                     .selectall("marker")                         .data(predicates)                         .enter().append("svg:marker")                             .attr("id", string)                             .attr("viewbox", "0 -5 10 10")                             .attr("refx", 30)                             .attr("refy", 0)                             .attr("markerwidth", 4)                             .attr("markerheight", 4)                             .attr("orient", "auto")                         .append("svg:path")                             .attr("d", "m0,-5l10,0l0,5"),     //link bag             //update             paths = svg.selectall(".paths")                 .data(["paths_g"]);             //enter     paths.enter()         .append("svg:g")         .attr("class", "paths");      //links     //update     path = paths.selectall("path")         .data(links);     //enter     path.enter()         .append("svg:path");     //update+enter     path         .attr("indx", function (d, i) { return })         .attr("id", function (d) { return d.source.index + "_" + d.target.index; })         .attr("class", function (d) { return "link " + d.type; })         .attr("marker-end", function (d) { return "url(#" + d.type + ")"; });     //exit               path.exit().remove();      //link labels bag     //update     var path_labels = svg.selectall(".labels")             .data(["labels_g"]);             //enter     path_labels.enter()         .append("svg:g")         .attr("class", "labels");      //link labels             //update     var path_label = path_labels.selectall(".path_label")                 .data(links);             //enter     path_label.enter()         .append("svg:text")             .append("svg:textpath")                 .attr("startoffset", "50%")                 .attr("text-anchor", "middle")                 .style("fill", "#000")                 .style("font-family", "arial");             //update+enter     path_label         .attr("class", function (d, i) { return "path_label " + }) //edit*******************************************************************       .selectall('textpath') //edit*******************************************************************         .attr("xlink:href", function (d) { return "#" + d.source.index + "_" + d.target.index; })         .text(function (d) { return d.type; }),     //exit     path_label.exit().remove();      //nodes bag             //update     var circles = svg.selectall(".circles")                 .data(["circles_g"]);             //enter     circles.enter()         .append("svg:g")         .attr("class", "circles");      //nodes     //update     circle = circles.selectall(".nodes")                 .data(nodes);     //enter     circle.enter().append("svg:circle")                 .attr("class", function (d) { return "nodes " + d.index })                 .attr("stroke", "#000");     //update+enter     circle         .on("click", clicked)         .on("dblclick", dblclick)         .on("contextmenu", cmdclick)         .attr("fill", function (d, i) {             console.log(i + " " + d.types[0] + " " + node_colors[d.types[0]])             return node_colors[d.types[0]];         })         .attr("r", function (d) { return d.types.indexof("document") == 0 ? 24 : 12; })         .call(force.drag);     //exit     circle.exit().remove();      //anchors bag     //update     var textbag = svg.selectall(".anchors")                 .data(["anchors_g"]);             //enter             textbag.enter()                 .append("svg:g")                 .attr("class", "anchors"),      //anchors             //update             textupdate = textbag.selectall("g")                 .data(nodes, function (d) { return d.name; }),     //enter     textenter = textupdate.enter()         .append("svg:g")         .attr("text-anchor", "middle")         .attr("class", function (d) { return "anchors " + d.index });      // copy of text thick white stroke legibility.     textenter.append("svg:text")                 .attr("x", 8)                 .attr("y", ".31em")                 .attr("class", "shadow")                 .text(function (d) { return d.name; });      textenter.append("svg:text")                 .attr("x", 8)                 .attr("y", ".31em")                 .text(function (d) { return d.name; });     textupdate.exit().remove();     text = textupdate;      // calling force.drag() here returns drag _behavior_ on set listener     // node element event listeners     force.drag().on("dragstart", function (d) {         d3.selectall(".dbox").style("z-index", 0);         d3.select("#dbox" + d.index).style("z-index", 1);     }) } 

edit
in response comment below @jjon , own edification, here minimum changes original code same naming conventions , differential comments. mods required adding links unchanged , not discussed...

function showme() {     svg     /////////////////////////////////////////////////////////////////////////////////////     //problem     //  defs element added document every update     //solution:     //  create data join on defs      //  append marker definitions on resulting enter selection     //  appended once     /////////////////////////////////////////////////////////////////////////////////////  //add//////////////////////////////////////////////////////////////////////////////////     .selectall("defs")     .data(["defs"], function (d) { return d }).enter() ///////////////////////////////////////////////////////////////////////////////////////      .append("svg:defs")         .selectall("marker")         .data(predicates)         .enter().append("svg:marker")             .attr("id", string)             .attr("viewbox", "0 -5 10 10")             .attr("refx", 30)             .attr("refy", 0)             .attr("markerwidth", 4)             .attr("markerheight", 4)             .attr("orient", "auto")         .append("svg:path")             .attr("d", "m0,-5l10,0l0,5");      /////////////////////////////////////////////////////////////////////////////////////     //problem     //  g element added document every update     //solution:     //  create data join on g , class .paths      //  append path g on resulting enter selection     //  appeneded once     /////////////////////////////////////////////////////////////////////////////////////  //add//////////////////////////////////////////////////////////////////////////////////     //link bag     //update     paths = svg         .selectall(".paths")         .data(["paths_g"]);     //enter     paths.enter() ///////////////////////////////////////////////////////////////////////////////////////          .append("svg:g")  //add//////////////////////////////////////////////////////////////////////////////////         .attr("class", "paths"); ///////////////////////////////////////////////////////////////////////////////////////      //links     //update     path = paths    //replace svg paths///////////////////////////////////////////////         .selectall("path")         .data(links);      path.enter().append("svg:path")             .attr("id", function (d) { return d.source.index + "_" + d.target.index; })             .attr("class", function (d) { return "link " + d.type; })             .attr("marker-end", function (d) { return "url(#" + d.type + ")"; });      path.exit().remove();      /////////////////////////////////////////////////////////////////////////////////////     //problem     //  g structure added every update     //solution:     //  create data join on g , class .labels      //  append labels g on resulting enter selection     //  appeneded once     //  include .exit().remove() defensive     //note:     //  don't chain .enter() on object assigned path_label     //  .data(...) returns update selection includes enter() , exit() methods     //  .enter() returns standard selection doesn't have .exit() member     //  needed if links removed or if node indexing changes     /////////////////////////////////////////////////////////////////////////////////////  //add//////////////////////////////////////////////////////////////////////////////////     //link labels bag     //update     var path_labels = svg.selectall(".labels")             .data(["labels_g"]);     //enter     path_labels.enter() ///////////////////////////////////////////////////////////////////////////////////////          .append("svg:g")  //add//////////////////////////////////////////////////////////////////////////////////         .attr("class", "labels"); ///////////////////////////////////////////////////////////////////////////////////////      //link labels     //update     var path_label = path_labels         .selectall(".path_label")         .data(links);     //enter     path_label         .enter().append("svg:text")             .attr("class", "path_label")             .append("svg:textpath")                 .attr("startoffset", "50%")                 .attr("text-anchor", "middle")                 .attr("xlink:href", function (d) { return "#" + d.source.index + "_" + d.target.index; })                 .style("fill", "#000")                 .style("font-family", "arial")                 .text(function (d) { return d.type; });  //add//////////////////////////////////////////////////////////////////////////////////     path_label.exit().remove(); ///////////////////////////////////////////////////////////////////////////////////////      /////////////////////////////////////////////////////////////////////////////////////     //problem     //  g structure added every update     //solution:     //  create data join on g , class .circles      //  append labels g on resulting enter selection     //  appeneded once     //  include .exit().remove() defensive     /////////////////////////////////////////////////////////////////////////////////////  //add//////////////////////////////////////////////////////////////////////////////////     //nodes bag     //update     var circles = svg.selectall(".circles")                 .data(["circles_g"]);     //enter     circles.enter() ///////////////////////////////////////////////////////////////////////////////////////          .append("svg:g")  //add//////////////////////////////////////////////////////////////////////////////////         .attr("class", "circles"); ///////////////////////////////////////////////////////////////////////////////////////      //nodes     //update     circle = circles         .selectall(".node") //select on class instead of tag name//////////////////////////         .data(nodes);     circle                              //don't chain in order keep update selection////////////         .enter().append("svg:circle")             .attr("class", "node")             .attr("fill", function (d, i) {                 return node_colors[d.types[0]];             })             .attr("r", function (d) { return d.types.indexof("document") == 0 ? 24 : 12; })             .attr("stroke", "#000")             .on("click", clicked)             .on("dblclick", dblclick)             .on("contextmenu", cmdclick)             .call(force.drag);  //add//////////////////////////////////////////////////////////////////////////////////     circle.exit().remove(); ///////////////////////////////////////////////////////////////////////////////////////  //add//////////////////////////////////////////////////////////////////////////////////     //anchors bag     //update     var textbag = svg.selectall(".anchors")                 .data(["anchors_g"]);     //enter     textbag.enter() ///////////////////////////////////////////////////////////////////////////////////////          .append("svg:g")  //add//////////////////////////////////////////////////////////////////////////////////         .attr("class", "anchors");      //anchors     //update     text = textbag ///////////////////////////////////////////////////////////////////////////////////////          .selectall(".anchor")             .data(nodes, function (d) { return d.name}); var textenter = text            //don't chain in order keep update selection//////////         .enter()         .append("svg:g")             .attr("class", "anchor")             .attr("text-anchor", "middle");  //add//////////////////////////////////////////////////////////////////////////////////     text.exit().remove; ///////////////////////////////////////////////////////////////////////////////////////      // copy of text thick white stroke legibility.     textenter.append("svg:text")             .attr("x", 8)             .attr("y", ".31em")             .attr("class", "shadow")             .text(function (d) { return d.name; });      textenter.append("svg:text")             .attr("x", 8)             .attr("y", ".31em")             .text(function (d) { return d.name; });      // calling force.drag() here returns drag _behavior_ on set listener     // node element event listeners     force.drag().on("dragstart", function (d) {         d3.selectall(".dbox").style("z-index", 0);         d3.select("#dbox" + d.index).style("z-index", 1);     }) } 

Comments

Popular posts from this blog

shopping cart - Page redirect not working PHP -

php - How to modify a menu to show sub-menus -

python - Installing PyDev in eclipse is failed -