updating a d3js tree map - javascript

I'm trying to render a tree map using d3.js that periodically fetches data and animates/transitions based on changes in mostly static data (few values change). I'm working from the example here.
So I have something along the lines of:
var w = 960,
h = 500,
color = d3.scale.category20c();
var treemap = d3.layout.treemap()
.size([w, h])
//.sticky(true)
.value(function(d) { return d.size; });
var div = d3.select("#chart").append("div")
.style("position", "relative")
.style("width", w + "px")
.style("height", h + "px");
function update (json) {
var d = div.data([json]).selectAll("div")
.data(treemap.nodes, function (d) { return d.name; });
d.enter().append("div")
.attr("class", "cell")
.style("background", function(d) { return d.children ? color(d.name) : null; })
.call(cell)
.text(function(d) { return d.children ? null : d.name; });
d.exit().remove();
};
d3.json("flare.json", update);
setTimeout(function () {
d3.json("flare2.json", update);
}, 3000);
function cell() {
this
.style("left", function(d) { return d.x + "px"; })
.style("top", function(d) { return d.y + "px"; })
.style("width", function(d) { return d.dx - 1 + "px"; })
.style("height", function(d) { return d.dy - 1 + "px"; });
}
Where flare2.json is a copy of flare.json found here, but with one node removed.
➜ test git:(master) ✗ diff flare.json flare2.json
10d9
< {"name": "AgglomerativeCluster", "size": 3938},
380c379
< }
\ No newline at end of file
---
> }
The problem is, after 3 seconds, the data is fetched and the text for the AgglomerativeCluster is removed, but not the box it was in. I can't say that I fully understand d3js enough to know what exactly I'm doing wrong.

After RTFM [1, 2, 3], I learned that d3.js separates the ideas of updating existing nodes, adding new nodes, and removing dead nodes. I had the code for adding and removing, but I was missing the update code. Simply adding this did the trick:
d.transition().duration(750).call(cell);
After creating var d but before the call to d.enter().

Related

d3 Force Graph Utilizing more than 1 JSON

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

d3.js custom layout exit() not working

I want to build a Windows Explorer like hierarchical visualization. As I want to compute the x and y coordinates manually, I created a custom layout based on the first example described here:
Custom layout in d3.js?
My layout function looks like this:
function myTreeLayout(data) {
var nodes = []; // or reuse data directly depending on layout
//load all nodes and their subnodes:
var coreelement=data;
coreelement.x=0;
coreelement.y=0;
positions(coreelement,0);
//nodes.push(coreelement); //core element
function child_recursion(element) {
nodes.push(element);
if (element.children!=null){
element.children.forEach(function(child) {
child_recursion(child);});
};
}
child_recursion(coreelement);
return nodes;
}
function positions(d,pos_y) { //pos_y is the target position (y) of the element
var sum_y;
sum_y=rowheight; //the sum of all vertical space used by that element
if (d.parent!=null)
{d.x=d.parent.x+10;}
else
{ d.x=0;}
d.y=pos_y;
if (d.children) {
d.children.forEach(function(child) {
child.parent=d;
sum_y+=positions(child,pos_y+sum_y);
});
}
return sum_y;
}
The computation of the coordinates works fine. I then bind the data using the following code:
d3.json("data/kdsf-neu.json", function(error, data) {
root = data;
root.x0 = 0;
root.y0 = 0;
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
root.children.forEach(collapse);
update(root);
});
function update(source) {
// Compute the new tree layout.
var nodes = myTreeLayout(root);
/*,links = tree.links(nodes);*/
// Update the nodes…
var node = vis.selectAll("g.node_coltree")
.data(nodes, function(d) {
return d.Nodeid;
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g").classed("g.node_coltree", true)
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
})
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
nodeEnter.append("svg:rect")
.attr("x", function(d) {
return 0;
})
.attr("y", function(d) {
return 0;
})
.attr("width", 10)
.attr("height", rowheight - 2)
.attr("class", function(d) {
var codearray = jQuery.makeArray(d.tags);
if ($.inArray(tags.Extended, codearray) >= 0) {
return 'erweiterungsteil_Fill';
} else if ($.inArray(tags.NotIncluded, codearray) >= 0) {
return 'nichtAufgenommen_Fill';
} else if ($.inArray(tags.Optional, codearray) >= 0) {
return 'optional_Fill';
} else if ($.inArray(tags.obligatorischWennVorhanden, codearray) >= 0) {
return 'obligatorisch_Fill';
} else if ($.inArray(tags.teilweiserForschungsbezug, codearray) >= 0) {
return 'pubSchale2_Fill';
} else if ($.inArray(tags.PublikationenSchale2, codearray) >= 0) {
return 'pubSchale2_Fill';
} else if ($.inArray(tags.Included, codearray) >= 0) {
return 'aufgenommen_Fill';
} else {
return "#FEFEFE";
}
})
.on("click", click)
.on("mouseover", function(d) {
updatedetails(d);
});
nodeEnter.append("text")
.attr("x", function(d) {
return 12;
})
.attr("y", function(d) {
return 7;
})
.text(function(d) {
return d.name;
})
.attr("dy", "0.35em")
.on("mouseover", function(d) {
updatedetails(d);
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
When I start the script, the elements are located at the right positions:
(As I am not allowed to post images, here a link:)
When I click on an element, however, the exit function does not seem to work:
https://www.dropbox.com/s/3phyu3tx9m13ydt/2.PNG?dl=0
After clicking on an element, the sub-elements are located at the appropriate target positions, but the old elements are not exiting.
I tried to stay close to the example for coltree, therefore I am also completely recalculating the whole tree after each click:
function update(source) {
// Compute the new tree layout.
var nodes = myTreeLayout(root);
I already checked the nodes element, it holds only the desired elements after the click. I therefore suspect, that there is some problem with the exit function and the custom layout.
Related questions:
My problem might be related to this question:
D3.js exit() not seeming to get updated information
Therefore, I followed the steps there:
I use a custom (externally computed single) index when calling data:
.data(nodes , function(d) { return d.Nodeid; });
I added the classed function when appending the node:
var nodeEnter = node.enter().append("g").classed("g.node_coltree",true)
Still, the elements stay in the graph - none are exiting.
Do I need to add something to the layout function, that d3 knows how to work with exiting elements? Or is something else wrong? Any help is highly appreciated.
EDIT: Here is the jsfiddle:
http://jsfiddle.net/MathiasRiechert/nhgejcy0/8/
When clicking on the root node, all sub-elements should disappear. Similarly, when opening a node, the elements should be moving. Both does not seem to happen.
You've got a fairly simple mistake in your code.
Here is an updated fiddle: http://jsfiddle.net/nhgejcy0/11/
The only difference is:
var nodeEnter = node.enter().append("g").classed("node_coltree", true)
.attr("x", function (d) {
return d.x;
})
.attr("y", function (d) {
return d.y;
})
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
Specifically, the first line was changed from:
var nodeEnter = node.enter().append("g").classed("g.node_coltree", true)
to:
var nodeEnter = node.enter().append("g").classed("node_coltree", true)
In your version, you were using classed(...) to add a class to your nodes of g.node_coltree, but you were selecting using .node_coltree, which didn't match, so your code just kept adding more and more g elements to the svg. Your enter selection contained a new g element for each item in your nodes array. This meant that your update and exit selections were always empty, resulting in nothing being removed.
I found this by inspecting the DOM and seeing that a new list of g elements was being appended every time a node was collapsed or expanded. If the selections were working properly, this wouldn't happen. It was then just a matter of tracking down whether the selection was wrong, or whether you were appending a different attribute when you created the nodes. In this case, it looks like the attribute was created incorrectly.

How to fill D3 SVG with image instead of colour with fill? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I would like to create something like the image below but instead of the circles I would like to use images of emoji's (the whatsapp emoticons etc.)
The data I would like to extract from the JSON should look something like this:
{
'url': 'see-no-evil.png',
'hits': 456
},
{
'url': 'speak-no-evil.png',
'hits': 425
},
The emoji's should appear on a canvas randomly and the one with the most "hits" should be displayed the biggest and the one with the least hits as the smallest. It should be as simple as possible and automatic update as soon as I change the values or add something to the JSON.
Does anyone know how I can achieve this?
I found a really great example but I don't know how to play with the code so that I can get images instead of colours?
http://codepen.io/girliemac/pen/cDfmb
d3 is a good library to use for this.
In your example, try replacing this code:
vis.enter().append('circle')
.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; })
.attr('r', function(d) { return d.r; })
.attr('class', function(d) { return d.className; });
with this code:
vis.enter().append("image")
.attr("xlink:href", "your-image-url.png")
.attr("width", function(d) { return 2*d.r; })
.attr("height", function(d) { return 2*d.r; })
.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; })
.attr('class', function(d) { return d.className; });
Note that you will have to play with the size of the images a bit because the image will be square and you are trying to deal with circles. Try to find an image where the emoji touches the edges of the image.
Hope this helps! Let me know how it goes.
EDIT: solution part 2 based on discussion in the comments
(function() {
var json = {"countries_msg_vol": [
{"url":"emoji-1.png","hits":100},{"url":"emoji-2.png","hits":200}
]};
// D3 Bubble Chart
var diameter = 600;
var svg = d3.select('#graph').append('svg')
.attr('width', diameter)
.attr('height', diameter);
var bubble = d3.layout.pack()
.size([diameter, diameter])
.value(function(d) {return d.size;})
// .sort(function(a, b) {
// return -(a.value - b.value)
// })
.padding(3);
// generate data with calculated layout values
var nodes = bubble.nodes(processData(json)); // filter out the outer bubble
var vis = svg.selectAll('circle')
.data(nodes);
vis.enter().append("image")
.attr("xlink:href", function(d){
return d.url;})
.attr("width", function(d) {
console.log("d.r:"+d.r);
return d.r; })
.attr("height", function(d) { return d.r; })
.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; })
.attr('class', function(d) { return d.className; });
function processData(data) {
var objs = data.countries_msg_vol;
var newDataSet = [];
for(var i=0; i<objs.length; i++) {
var obj = objs[i];
console.log(obj.url+" "+obj.hits);
newDataSet.push({url: obj.url, className: obj.url, size: obj.hits});
}
return {children: newDataSet};
}
})();

Multiple Force Layouts Cause Conflicts in Tick Functions

I'm attempting to put multiple D3 force layouts on a page at the same time. The number of force layouts is ideally variable, depending on the number of roots returned from a dynamic API. I have followed the answers on this question regarding multiple force layouts and have successfully put each layout in a separate div, in a separate svg.
However, the issue is twofold:
1) The svgs seem to be drawn at the same time, causing conflicts in the alpha cooling parameter (on "tick" of each graph). Thus, the only layout that is positioned the way it is intended is the last svg drawn on the page. The tick function contains code that shapes the force layout similar to a weeping willow tree, with the root node sitting on top and the children falling below it.
2) Setting a loop to iterate on the full results list from the API causes D3 to crash, and an error "Uncaught TypeError: Cannot read property 'textContent' of null."
I think the ideal solution would be to draw each force layout after the previous one has been successfully rendered, in a way that does not cause the alpha cooling parameters (on "tick") to conflict, or overloading the D3 library with too many instances of the force layout at once. Does someone have insight into this issue? Here is my code:
/* ... GET THE RESULTS FROM THE API ...*/
function handleRequest2(json) {
allroots = json[1]['data']['children'];
(function() {
var index = 0;
function LoopThrough() {
currentRoot = allroots[index];
if (index < allroots.length) {
/* DRAW THE GRAPH */
draw_graphs(currentRoot, index);
++index;
LoopThrough();
};
}
LoopThrough();
})();
}
//Force Layout Code
function draw_graphs(root, id) {
var root_id = "map-" + id.toString();
var force;
var vis;
var link;
var node;
var w = 980;
var h = 1000;
var k = 0;
// Create a separate div to house each SVG graph
div = document.createElement("div");
div.style.width = "980px";
div.style.height = "1000px";
div.style.cssFloat="left";
div.id = root_id;
$(div).addClass("chattermap-map");
// Append the div to the chart container
$('#chart').append(div);
force = d3.layout.force()
.size([w, h])
.charge(-250)
.gravity(0)
.on("tick", tick);
// Create the SVG and append it to the created div
vis = d3.select("#"+root_id)
.append("svg:svg")
.attr("width", w)
.attr("height", h)
.attr("id",root_id);
// Put the Reddit JSON in the correct format for the Force Layout
nodes = flatten(root),
links = optimize(d3.layout.tree().links(nodes));
// Calculations for the sizing of the nodes
avgNetPositive = getAvgNetPositive();
maxNetPositive = d3.max(netPositiveArray);
minNetPositive = d3.min(netPositiveArray);
// Create a logarithmic scale that sizes the nodes
radius = d3.scale.pow().exponent(.3).domain([minNetPositive,maxNetPositive]).range([5,30]);
// Fix the root node to the top of the svg
root.data.fixed = true;
root.data.x = w/2;
root.data.y = 50;
// Start the force layout.
force
.nodes(nodes)
.links(links)
.start();
// Update the links
link = vis.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 = vis.selectAll("circle.node")
.data(nodes, function(d) {return d.id; })
.style("fill", function(d) {
return '#2960b5';
});
// Enter any new nodes.
node.enter().append("svg:circle")
.attr("class", "node")
.attr("cx", function(d) {return d.x; })
.attr("cy", function(d) {return d.y; })
.attr("r", function(d) {
//Get the net positive reaction
var netPositive = d.ups - d.downs;
var relativePositivity = netPositive/avgNetPositive;
//Scale the radii based on the logarithmic scale defined earlier
return radius(netPositive);
})
.style("fill", function(d) {
return '#2960b5';
})
// Allow dragging on click
.call(force.drag);
// Exit any old nodes.
node.exit().remove();
//This will add the name of the author to the node HTML
node.append("author").text(function(d) {return d.author});
//Add the body of the comment to the node
node.append("comment").text(function(d) {return Encoder.htmlDecode(d.body_html)});
//Add the UNIX timestamp to the node
node.append("timestamp").text(function(d) {return moment.unix(d.created_utc).fromNow();})
//On load, assign the root node to the tooltip
numberOfNodes = node[0].length;
rootNode = d3.select(node[0][parseInt(numberOfNodes) - 1]);
rootNodeComment = rootNode.select("comment").text();
rootNodeAuthor = rootNode.select("author").text();
rootNodeTimestamp = rootNode.select("timestamp").text();
// Create the tooltip div for the comments
tooltip_div = d3.select("#"+root_id).append("div")
.attr("class", "tooltip")
.style("opacity", 1);
//Add the HTML to the tooltip for the root
tooltip_div .html("<span class='commentAuthor'>" + rootNodeAuthor + "</span><span class='bulletTimeAgo'>•</span><span class='timestamp'>" + rootNodeTimestamp + "</span><br>" + rootNodeComment)
//Position the tooltip based on the position of the current node, and it's size
.style("left", (rootNode.attr("cx") - (-rootNode.attr("r")) - (-9)) + "px")
.style("top", (rootNode.attr("cy") - 15) + "px");
node.on("mouseover", function() {
currentNode = d3.select(this);
currentTitle = currentNode.select("comment").text();
currentAuthor = currentNode.select("author").text();
currentTimestamp = currentNode.select("timestamp").text();
tooltip_div.transition()
.duration(200)
.style("opacity", 1);
// Add the HTML for all other tooltips on mouseover
tooltip_div .html("<span class='commentAuthor'>" + currentAuthor + "</span><span class='bulletTimeAgo'>•</span><span class='timestamp'>" + currentTimestamp + "</span><br>" + currentTitle)
//Position the tooltip based on the position of the current node, and it's size
.style("left", (currentNode.attr("cx") - (-currentNode.attr("r")) - (-9)) + "px")
.style("top", (currentNode.attr("cy") - 15) + "px");
});
// Fade out the tooltip on mouseout
node.on("mouseout", function(d) {
tooltip_div.transition()
.duration(500)
.style("opacity", 1);
});
// Optimize the JSON output of Reddit for D3
function flatten(root) {
var nodes = [], i = 0, j = 0;
function recurse(node) {
if (node['data']['replies'] != "" && node['kind'] != "more") {
node['data']['replies']['data']['children'].forEach(recurse);
}
if (node['kind'] !="more") {
//Add an ID value to the node starting at 1
node.data.id = ++i;
node.data.name = node.data.body;
//Put the replies in the key 'children' to work with the tree layout
if (node.data.replies != "") {
node.data.children = node.data.replies.data.children;
//Remove the extra 'data' layer for each child
for (j=0; j < node.data.children.length; j++) {
node.data.children[j] = node.data.children[j].data;
}
} else {
node.data.children = "";
}
var comment = node.data;
nodes.push(comment);
}
}
recurse(root);
return nodes;
}
// Optimize the JSON for use with Links
function optimize(linkArray) {
optimizedArray = [];
for (k=0; k < linkArray.length; k++) {
if(typeof linkArray[k].target.count == 'undefined') {
optimizedArray.push(linkArray[k]);
}
}
return optimizedArray;
}
// Get the average net positive upvotes for use in sizing
function getAvgNetPositive() {
var sum = 0;
netPositiveArray = []
//Select all the nodes
var allNodes = d3.selectAll(nodes)[0];
//For each node, get the net positive votes and add it to the sum
for (i=0; i < allNodes.length; i++) {
var netPositiveEach = allNodes[i]["ups"] - allNodes[i]["downs"];
sum += netPositiveEach;
netPositiveArray.push(netPositiveEach);
}
var avgNetPositive = sum/allNodes.length;
return avgNetPositive;
}
function tick(e) {
var kx = .4 * e.alpha, ky = 1.4 * e.alpha;
links.forEach(function(d, i) {
d.target.x += (d.source.x - d.target.x) * kx;
d.target.y += (d.source.y + 80 - d.target.y) * ky;
});
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("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
// // Remove the animation effect of the force layout
// while ((force.alpha() > 1e-2) && (k < 150)) {
// force.tick(),
// k = k + 1;
// }
}
Thanks in advance!
You should be able to make several force layouts work at the same time if you encapsulate them in their own namespace, e.g. through separate functions. You can however also do what you want by listening to the end event -- see the documentation. This way, you can "chain" the layouts, starting each one once the previous has finished.
Regarding the other error, it looks like this would be caused by incomplete/faulty data.

d3.js updating visual

I have a treemap I put together with d3.js. I populate the data via getJSON. It works great. However, I have this functionality in a setInterval method and it doesnt seem to be refreshing itself.
var treemap = d3.layout.treemap()
.padding(4)
.size([w, h])
.value(function(d) { return d.size; });
var svg = d3.select("#chart").append("svg")
.style("position", "relative")
.style("width", w + "px")
.style("height", h + "px");
function redraw3(json) {
var cell = svg.data([json]).selectAll("g")
.data(treemap)
.enter().append("g")
.attr("class", "cell")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
cell.append("rect")
.attr("width", function(d) { return d.dx; })
.attr("height", function(d) { return d.dy; })
.style("fill", function(d) { return d.children ? color(d.data.name) : null; });
cell.append("text")
.attr("x", function(d) { return d.dx / 2; })
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.children ? null : d.data.name; });
}
setInterval(function() {
d3.json("http://localhost:8080/dev_tests/d3/examples/data/flare2.json", function(json) {
redraw3(json);
});
}, 3000);
My question specifically, is why when I change data in the json file doesn't it show up 3 seconds later in the treemap?
Thank you in advance.
What's in the data? Because if the data array has the same length, the enter() selection (which corresponds to previously unbound data) will have a length of zero. Mike Bostock wrote a great tutorial called Thinking with Joins, which I would recommend reading before you go any further.
The svg.data() call seems redundant, and for clarity's sake I'd recommend doing this instead:
var leaves = treemap(json);
console.log("leaves:", leaves); // so you can see what's happening
// cell here is the bound selection, which has 3 parts
var cell = svg.selectAll("g")
.data(leaves);
// you might want to console.log(cell) here too so you can take a look
// 1. the entering selection is new stuff
var entering = cell.enter()
.append("g")
entering.append("rect")
// [update rectangles]
entering.append("text")
// [update text]
// 2. the exiting selection is old stuff
cell.exit().remove();
// 3. everything else is the "updating" selection
cell.select("rect")
// [update rectangles]
cell.select("text")
// [update text]
You can also encapsulate the updating of cells in a function and "call" it on both the entering and updating selections, so you don't have to write the same code twice:
function update() {
cell.select("rect")
// [update rectangles]
cell.select("text")
// [update text]
}
entering.append("rect");
entering.append("text");
entering.call(update);
cell.call(update);

Categories