I am trying to get values from the "links" instead of "nodes. I guess a more helpful way of asking this is how to specify where I am requesting getting the values from my JSON.
JSON for reference:
{
"nodes": [
{"fixed":true,"data": {"id": "foo","idType":"USERNAME","color":"red"},"selected":false},
{"fixed":true,"data": {"id": "bar","idType":"USERNAME","color":"yellow"},"selected": false}
],
"links": [
{"classes":null,"data":{"color":"blue","source":"foo","target":"bar","visible":true},"grabbable":false},
{"classes":null,"data":{"color":"green","source":"bar","target":"foo","visible":true},"grabbable":false}
]}
So an example is I can get this to work
node.style("fill", function(d) { return d.data['color'] });
but not
link.style("stroke", function(d) { return d.data['color'] });
However, this works...
link.style("stroke", "red"});
In the console it says d.data['color'] is undefined. I guess I do not understand how it is being called exactly. I have seen some examples of code that has
function(d, i) { return bla bla }
and I assumed that if d is always node, maybe i might be edges but adding that to my code didn't do much. If someone could explain what the i is touching that would be good also.
Code snippet of actual code below:
// 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");
// Create the force layout
var force = d3.layout.force()
.size([width, height])
.charge(-400)
.linkDistance(50);
//Read in the JSON data.
d3.json("../test/resources/full.json", function(error, json) {
// expands scope of json
root = json
alert(root)
update();
});
function update() {
// sets the source and target to use id instead of index
var edges = [];
root.links.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];
edges.push({
source: sourceNode,
target: targetNode
});
});
force
.nodes(root.nodes)
.links(edges)
.start();
link = link
.data(edges)
.enter().append("line")
.attr("class", "link");
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('click', highlight);
// Checks for the shape and color to be made for the node.
node.append("circle")
.attr("r", 10);
node.style("fill", function(d) { return d.data['color'] });
link.style("stroke", function(d) { return d.data['color'] });
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.style("fill", "black")
// Checks what to return as the node label based on idType.
.text(function (d) {
if (d.data['idType']==="Comment") {
return d.data.attrs[1].val;
}
else if(d.data['idType']==="MEDIA") {
return "MEDIA " + d.data['id'];
}
else
return d.data['id'];
});
root.links.forEach(function (d) {
linkedByIndex[d.data.source + "," + d.data.target] = 1;
});
resize();
window.addEventListener('resize', resize);
}
Per Lars' comment, edges does not contain the original link data. So it was currently pushing only
edges.push({
source: sourceNode,
target: targetNode
});
so to add any information that isn't in my edges I would have to add it. An example is if I were to push the color attribute from JSON my code would look like
edges.push({
source: sourceNode,
target: targetNode,
color: e.data['color']
});
Related
its my json
var data =
[{
name:"Words",
children:[{
name:"Nouns",
children:[
{
name:"Table",
size:1025
},
{
name:"Box",
size:2925
},
{
name:"Bat",
size:4025
},
],
size: 2000
},
{
name:"Adverbs",
children:[
{
name:"eagerly",
size:2520
},
{
name:"badly",
size:3259
},
{
name:"easily",
size:2512
},
],
size: 425
},
{
name:"Adjectives",
children:[
{
name:"Positive",
children:[
{
name:"Amazing",
size:1250
},
{
name:"Beautiful",
size:2529
}],
size:1000
},
{
name:"Negative",
children:[
{
name:"Destructive",
size:1250
},
{
name:"Hot",
size:2529
}],
size:1000
},
],
size: 343
},
{
name:"Verbs",
children:[
{
name:"Play",
size:4310
},
{
name:"Say",
size:2943
},
{
name:"Ride",
size:4430
},
],
size: 343
},
],
size: 2000
}];
initialized with null
var g = {
data: null,
force: null
};
created graph
$(function () {
//use a global var for the data:
g.data = data;
var width = 1300,
height = 630;
//Create a sized SVG surface within viz:
var svg = d3.select("#viz")
.append("svg")
.attr("width", width)
.attr("height", height);
g.link = svg.selectAll(".link"),
g.node = svg.selectAll(".node");
//Create a graph layout engine:
g.force = d3.layout.force()
.linkDistance(150)
.charge(-300)
.gravity(0.01)
.size([width, height])
//that invokes the tick method to draw the elements in their new location:
.on("tick", tick);
//Draw the graph:
//Note that this method is invoked again
//when clicking nodes:
update();
});
function update() {
//iterate through original nested data, and get one dimension array of nodes.
var nodes = flatten(g.data);
//Each node extracted above has a children attribute.
//from them, we can use a tree() layout function in order
//to build a links selection.
var links = d3.layout.tree().links(nodes);
// pass both of those sets to the graph layout engine, and restart it
Here is where error occur
g.force.nodes(nodes) //Here is where error occur
.links(links)
.start();
///////////////////////////////////////////////////////////////////
//-------------------
// create a subselection, wiring up data, using a function to define
//how it's suppossed to know what is appended/updated/exited
g.link = g.link.data(links, function (d) {return d.target.id;});
//Get rid of old links:
g.link.exit().remove();
//Build new links by adding new svg lines:
g.link
.enter()
.insert("line", ".node")
.attr("class", "link");
// create a subselection, wiring up data, using a function to define
//how it's suppossed to know what is appended/updated/exited
g.node = g.node.data(nodes, function (d) {return d.id;});
//Get rid of old nodes:
g.node.exit().remove();
//-------------------
//create new nodes by making groupd elements, that contain circls and text:
var nodeEnter = g.node.enter()
.append("g")
.attr("class", "node")
.on("click", click)
.call(g.force.drag);
//circle within the single node group:
nodeEnter.append("circle")
.attr("r", function (d) {return Math.sqrt(d.size) || 50;});
//text within the single node group:
nodeEnter.append("text")
.attr("dy", ".35em")
.text(function (d) {
return d.name;
});
//All nodes, do the following:
g.node.select("circle")
.style("fill", color); //calls delegate
//-------------------
}
// Invoked from 'update'.
// The original source data is not the usual nodes + edge list,
// but that's what's needed for the force layout engine.
// So returns a list of all nodes under the root.
function flatten(data) {
var nodes = [],
i = 0;
//count only children (not _children)
//note that it doesn't count any descendents of collapsed _children
//rather elegant?
function recurse(node) {
if (node.children) node.children.forEach(recurse);
if (!node.id) node.id = ++i;
nodes.push(node);
}
recurse(data);
//Done:
return nodes;
}
update();
//Invoked from 'update'
//Return the color of the node
//based on the children value of the
//source data item: {name=..., children: {...}}
function color(d) {
return d._children ? "#3182bd" // collapsed package
:
d.children ? "#c6dbef" // expanded package
:
"#fd8d3c"; // leaf node
}
// Toggle children on click by switching around values on _children and children.
function click(d) {
if (d3.event.defaultPrevented) return; // ignore drag
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
//
update();
}
//event handler for every time the force layout engine
//says to redraw everthing:
function tick() {
width=1300;
height=630;
r=60;
g.node.attr("cx", function(d) { return d.x = Math.max(r, Math.min(width - r, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(r, Math.min(height - r, d.y)); });
//redraw position of every link within the link set:
g.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;
});
//same for the nodes, using a functor:
g.node.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
**Whenever i add my new json with the same format it doesnt show anything. and the error tracebacks to the code the == update(); ==
jquery version: 2.1.3
d3 version : 3.4.13
dont know if it matters or not but i am intergrating this in http://metroui.org.ua/
Metro Ui tabs
Please help!**
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();
}
So I have a d3.js force-directed graph that displays data from a JSON feed. When I click a node I requested an updated JSON feed based off the node that was clicked.
The JSON that is returned is correct. But what displays in the graph does not reflect what data is held in the JSON. I have a feeling the graph is holding onto previous graph data.
Heres a quick gif that should help visualise the issue.
Here is a JSFiddle to give you an idea of how to graph currently works.
And the Javascript on its own is at the bottom of this question.
In a bit more detail. When you click a node, it passes the word that node is associated with into a query string of a URL. I then run the d3.json using this new 'clicked' url and run an update function to recreate the graph.
So an example of how this is wrong. So if you go onto the JSFiddle and click on the node called 'piercingly' you will find that the next graph that is loaded doesn't even have the word 'piercingly' in it, and still has labels associated to bitter (the original search). Yet it if you change the variable at the top of the JS to 'piercingly' a different but correct graph is loaded.
The number of nodes is correct. But the label and other attributes in the full version (not the version on JSFiddle) are incorrect.
Any help would be much appreciated.
$wordToSearch = "bitter";
var w = 960,
h = 960,
node,
link,
root,
title;
var jsonURL = 'http://desolate-taiga-6759.herokuapp.com/word/basic/' + $wordToSearch;
d3.json(jsonURL, function(json) {
root = json.words[0]; //set root node
root.fixed = true;
root.x = w / 2;
root.y = h / 2 - 80;
update();
});
var force = d3.layout.force()
.on("tick", tick)
.charge(-700)
.gravity(0.1)
.friction(0.9)
.linkDistance(50)
.size([w, h]);
var svg = d3.select(".graph").append("svg")
.attr("width", w)
.attr("height", h);
//Update the graph
function update() {
var nodes = flatten(root),
links = d3.layout.tree().links(nodes);
// Restart the force layout.
force
.nodes(nodes)
.links(links)
.start();
// Update the links…
link = svg.selectAll("line.link")
.data(links, function(d) { return d.target.id; });
// Enter any new links.
link.enter().insert("svg:line", ".node")
.attr("class", "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; });
// Exit any old links.
link.exit().remove();
// Update the nodes…
node = svg.selectAll(".node")
.data(nodes);
var nodeE = node
.enter();
var nodeG = nodeE.append("g")
.attr("class", "node")
.call(force.drag);
nodeG.append("circle")
.attr("r", 10)
.on("click", click)
.style("fill", "red");
nodeG.append("text")
.attr("dy", 10 + 15)
.attr("text-anchor", "middle")
.text(function(d) { return d.word });
node.exit().remove();
}
function tick() {
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 + ")"; });
}
/***********************
*** CUSTOM FUNCTIONS ***
***********************/
//Request extended JSON objects when clicking a clickable node
function click(d) {
$wordClicked = d.word;
var jsonURL = 'http://desolate-taiga-6759.herokuapp.com/word/basic/' + $wordClicked;
console.log(jsonURL);
updateGraph(jsonURL);
}
// Returns a list of all nodes under the root.
function flatten(root) {
var nodes = [], i = 0;
function recurse(node) {
if (node.children) node.size = node.children.reduce(function(p, v) { return p + recurse(v); }, 0);
if (!node.id) node.id = ++i;
nodes.push(node);
return node.size;
}
root.size = recurse(root);
return nodes;
}
//Update graph with new extended JSON objects
function updateGraph(newURL) {
d3.json(newURL, function(json) {
root = json.words[0]; //set root node
root.fixed = true;
root.x = w / 2;
root.y = h / 2 - 80;
update();
});
}
function getUrlParameter(sParam)
{
var sPageURL = window.location.search.substring(1);
var sURLVariables = sPageURL.split('&');
for (var i = 0; i < sURLVariables.length; i++)
{
var sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] == sParam) {
return sParameterName[1];
}
}
}
EDIT: So I tried logging out the word when it is added to the text element. On first load all the words get logged as the get assigned to their respected text element. But when you click on the node, they don't. (Please see gif below). This is strange as I called the update function on click. So the word (in theory) should be fetched again for that node. But it doesnt.
It's quite hard to grasp on a phone but I think the reason is probably because it's getting confused about the new data. By default the data() function uses the index of the item to join to the DOM.
What you need to do instead is pass another function to your calls to data() which is described as a key function. Here you can probably just return the word.
.data(nodes, function(d) { return d.word; })
Take a look at the data function in the API docs https://github.com/mbostock/d3/wiki/Selections . I've had complex cases catch me out a couple of times where I missed a key function.
I am attempting to show and update the same data on change when the value in my dropdown changes. You will see that the Text data changes as expected but the Bar Chart isn't updating correctly. It appears to be writing over itself. You can find the running example code here: http://jsfiddle.net/khnumtemu/43oaczq8/15/
// Create a distinct list of FSMs
var uniqueValues = d3.map([])
dataset.forEach(function(d){ uniqueValues.set(d.FsmId, d); });
var newJsonStr = []
uniqueValues.forEach(function(d){ newJsonStr.push(uniqueValues.get(d)); });
// Create and Fill Dropdown
d3.select("body").append("select")
.classed('colorSelect',true)
.selectAll("option")
.data(newJsonStr)
.enter()
.append("option")
.attr("value",function(d){ return d.FsmId;})
.text(function(d){ return d.FieldSalesManagerName + " (" + d.FsmId + ")";})
.sort(function(a, b) {return d3.descending(a.FieldSalesManagerName, b.FieldSalesManagerName);});
// Inital Data OnLoad
var intsel = d3.select(".colorSelect").node().value;
d3.select("body").selectAll("p")
.data(dataset)
.enter()
.append("p")
.text(function(d){
if(d.FsmId == intsel){
return d.SalesRepName;
}
})
var w = 800;
var h = 500;
var x = d3.scale.linear().domain([0,d3.max(dataset)]).range([0,w]);
var y = d3.scale.linear().domain([0,dataset.length]).range([0,h]);
var svg = d3.select("body").append("svg")
.attr({
"id":"chart",
"width":w,
"height":h
})
svg.selectAll(".bar")
.data(dataset)
.enter()
.append("rect")
.filter(function(d,i){if(d.FsmId == intsel){
return d.AchievementCount;
}})
.attr({
"class":"bar",
"x":0,
"y": function(d,i){
return y(i);
},
"width": function(d,i){
return d.AchievementCount;
},
"height": function(d,i){ return y(1) -1; }
})
// Data Updated on Selected Change
d3.select("select")
.on("change", function(d){
var sel = d3.select(".colorSelect").node().value;
d3.select("body").selectAll("p")
.data(dataset)
.text(function(d){
if(d.FsmId == sel){
return d.SalesRepName;
}
})
svg.selectAll(".bar")
.data(dataset)
.enter()
.append("rect")
.filter(function(d,i){if(d.FsmId == sel){
return d.AchievementCount;
}})
.attr({
"class":"bar",
"x":0,
"y": function(d,i){
return y(i);
},
"width": function(d,i){
return d.AchievementCount;
},
"height": function(d,i){ return y(1) -1; }
})
});
Updating a d3 visualization follows an enter, update, and exit workflow (start your reading here and here).
You are only handling the enter case. So on every .on, you are appending rect after rect after rect. You never exit (remove rects) or update (update existing rects). The proper way to fix this would be to handle those senerios.
Of course, with a dataset as small as yours, the easy fix (you won't see a performance hit) is just to remove and re-add all the rects.
svg.selectAll(".bar").remove();
Example here.
I am trying to create a graph using d3 and force layout. I have used the following example http://bl.ocks.org/mbostock/1062288 to get started:
I also need images and labels so I looked at this example http://bl.ocks.org/mbostock/950642 to get an idea how I could add them.
My graph will also get bigger depending on the user interactions with the nodes so if a user clicks on a node that doesn't have children, an ajax request will go to the backend service to request more nodes. The graph is meant to be used as a relationship discovery application. I created the following jsfiddle http://jsfiddle.net/R2JGa/7/ to get an idea of what i'm trying to achieve.
It works relatively well but I have one annoying problem: when adding new nodes to the graph the old nodes somehow get misplaced. For instance, I start with 3 nodes, root node is "flare". The other 2 nodes are "animate" and "analytics". In my example the children will always be "x","y","z","t" anytime you click on a node that currently doesn't have children. After expanding a few nodes you will see that "animate" or "analytics" are not linked to the root node "flare" but to some other nodes (x,y,z,t). Or sometimes if you expand x or y or z or t the children nodes have a duplicate x or y or z or t. If you click on "flare" to hide the entire graph and then reopen "flare" you will see that the nodes are correctly linked and named.
I can't seem to see why this is happening. Could somebody shed some light here? I am still new to d3 and find it really interesting but these problems are so annoying...
Here is the code:
var w = 960,
h = 800,
node,
link,
root;
var force = d3.layout.force()
.charge(-1000)
.size([w, h]);
var vis = d3.select("#chart").append("svg:svg")
.attr("width", w)
.attr("height", h);
d3.json("data.json", function (json) {
root = json;
update();
});
function update() {
var nodes = flatten(root);
nodes.reverse();
nodes = nodes.sort(function (a, b) {
return a.index - b.index;
});
var links = d3.layout.tree().links(nodes);
console.log(nodes);
// Restart the force layout.
force
.nodes(nodes)
.links(links)
.linkDistance(55)
.start();
var link = vis.selectAll(".link")
.data(links);
link.enter().append("line")
.attr("class", "link");
link.exit().remove();
var node = vis.selectAll("g.node")
.data(nodes)
var groups = node.enter().append("g")
.attr("class", "node")
.attr("id", function (d) {
return d.id
})
.on('click', click)
.call(force.drag);
groups.append("image")
.attr("xlink:href", "https://github.com/favicon.ico")
.attr("x", -8)
.attr("y", -8)
.attr("width", 16)
.attr("height", 16);
groups.append("text")
.attr("dx", 12)
.attr("dy", "0.35em")
.style("font-size", "10px")
.text(function (d) {
console.log(d);
return d.name
});
node.exit().remove();
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 + ")";
});
});
}
// Color leaf nodes orange, and packages white or blue.
function color(d) {
return d._children ? "#3182bd" : d.children ? "#c6dbef" : "#fd8d3c";
}
// Toggle children on click.
function click(d) {
console.log(d);
if (d.children) {
d._children = d.children;
d.children = null;
update();
} else if (d._children) {
d.children = d._children;
d._children = null;
update();
}
else {
d3.json("expand.json", function (json) {
d.children = json.children;
update();
})
}
}
// Returns a list of all nodes under the root.
function flatten(root) {
var nodes = [], i = 0;
function recurse(node) {
if (node.children) node.children.forEach(recurse);
if (!node.id) node.id = ++i;
nodes.push(node);
}
recurse(root);
return nodes;
}
And here are the 2 json files I'm requesting:
data.json
{
"name": "flare",
"id" : "flare",
"children": [
{
"name": "analytics",
"id": "analytics"
},
{
"name": "animate",
"id": "animate"
}
]
}
And expand.json
{"children": [
{
"name": "x",
"id": "x",
"size": 1983
},
{
"name": "y",
"id": "y",
"size": 2047
},
{
"name": "z",
"id": "z",
"size": 1375
},
{
"name": "t",
"id": "t",
"size": 1375
}
]}
PS: i had to sort the nodes array otherwise bad things happened to the graph, i cannot understand why.
Here the fiddle to the working solution. I think the problem was with the way you are declaring your id's and sorting them based on array index. You should have let the id's being declared by the flattening code and then sort them based on id's given. Also in your recursive function you might want to declare the parent first and then then children.
function recurse(node) {
if(!node.id) node.id = ++i;
nodes.push(node);
if (node.children) node.children.forEach(recurse);
}
I have managed to find a solution for this starting from Yogesh's answer. Here is the code that needs to be added in the update() function.
var currentNodes = force.nodes();
var nodes = flatten(root);
var actualNodes = [];
var values = currentNodes.map(function(obj) { return obj.name});
var newNodesValues = nodes.map(function(obj) { return obj.name });
for(var i = 0; i < currentNodes.length; i++) {
if(newNodesValues.indexOf(currentNodes[i].name) !== -1) {
actualNodes.push(currentNodes[i]);
}
}
for(var i = 0; i < nodes.length; i++) {
if(values.indexOf(nodes[i].name) == -1) {
actualNodes.push(nodes[i]);
}
}
nodes = actualNodes;
var links = d3.layout.tree().links(nodes);
// Restart the force layout.
force
.nodes(nodes)
.links(links)
.linkDistance(55)
.start();
The following should do the trick:
var i = 0;
...
var link = vis.selectAll(".link")
.data(links, function (d) {
return d.id || (d.id = ++i);
});
...
var node = vis.selectAll("g.node")
.data(nodes, function (d) {
return d.id || (d.id = ++i);
});
That second argument to data() is a callback function that, when called with a datum, returns the key that binds each DOM node to it's corresponding datum. When you don't provide such a function, d3 is left with no option but to use the index to bind datum to DOM nodes.