I've recently created a D3 chart that uses JSON to populate it. The chart is made up of nodes that have the movies name next to it. Then the chart has links which connect each node together. However I also have a panel which I want to display the nodes information. For example if I clicked on the 'blu-ray' node, the panel will populate the node title 'blu-ray' and all the movies that are of the blu-ray format. These are all stored in the JSON array. How would I go about doing this? Close example to what I'm trying to achive is on http://jsfiddle.net/sXkjc/994/... except I want to populate the panels information by clicking the node instead of the buttons, can someone please help?
D3 Code:
<script>
//Setting the svg & the charts width etc
var diameter = 560;
var tree = d3.layout.tree()
.size([360, diameter / 2 - 120])
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
var diagonal = d3.svg.diagonal.radial()
.projection(function(d) { return [d.y, d.x / 180 * Math.PI]; });
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter - 5)
.append("g")
.attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
//connecting the JSON file
d3.json("data.json", function(error, root) {
var nodes = tree.nodes(root),
links = tree.links(nodes);
//creating the links
var link = svg.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", diagonal);
//creating the circles
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
node.append("circle")
.attr("r", 4.5);
node.append("text")
.attr("dy", ".31em")
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.attr("transform", function(d) { return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)"; })
.text(function(d) { return d.name; });
});
d3.select(self.frameElement).style("height", diameter - 150 + "px");
</script>
JSON:
{
"name": "Movies",
"children": [
{
"name": "Blu-Ray",
"children": [
{
"name": "Transformers",
"url": "www.my-media-website.com",
"dependsOn": ["Content API", "Search API", "Account API", "Picture API", "Facebook", "Twitter"],
"technos": ["PHP", "Silex", "Javascript", "NGINX", "Varnish"],
"host": { "Amazon": ["fo-1", "fo-2"] }
},
{
"name": "Saving Private Ryan",
"dependsOn": ["Content API", "Search API", "Account API"]
},
{
"name": "Star Trek Into Darkness",
"dependsOn": ["Content API"]
}
],
"dependsOn": ["Transformers", "Saving Private Ryan", "Star Trek Into Darkness"]
}
]}
Panel:
<div class="panel">
<h3>You have selected:</h3>
<p>Node Detail will go the p tags!</p>
</div>
You have to do two things:
1. Attach 'id' attribute to each of your node. This id should be unique. you can use id field from your json.
2. Write click handler for your nodes like following:
node.on('click', function(d) {
var clickedId = d3.select(this).attr("id");
/* your code to use clickedId to get the data of clicked node from your JSON */
});
This will solve your problem.
Related
I'm trying to display a network graph with clustered nodes but I'm having trouble with the nested nodes in D3. The first "layer" contains clusters and each node of the first layer can contain multiple nodes. Links in the network would probably only occur between clusters (meaning between nodes of the first layer).
Here is the code I have so far. I'm able to display the first level of nodes. I cannot figure out how to display the nested nodes (see code in const data in each node children).
const node_radius = 100;
const width = 800;
const height = 400;
const links = [
{ "source": 1, "target": 6}
] ;
const data = [
{
"id":1,
"level": "cluster",
"name": "analytics1",
"children": [
{
"id":2,
"name": "animate1",
"level": "leaf",
"size": 15,
"parent": 1
},
{
"id":3,
"name": "animate2",
"level": "leaf",
"size": 15,
"parent": 1
},
{
"id":4,
"name": "animate3",
"level": "leaf",
"size": 15,
"parent": 1
}
]
},
{
"id":6,
"name": "analytics2",
"level": "cluster",
"children": [
{
"id":7,
"name": "animate4",
"level": "leaf",
"size": 10,
"parent": 6
}
]
}
]
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var simulation = d3.forceSimulation()
// pull nodes together based on the links between them
.force("link", d3.forceLink().id(function(d) { return d.id; }).strength(0.0001))
// push nodes apart to space them out
.force("charge", d3.forceManyBody().strength(-10))
// add some collision detection so they don't overlap
.force("collide", d3.forceCollide().radius(node_radius))
// and draw them around the centre of the space
.force("center", d3.forceCenter(width / 2, height / 2));
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links).enter().append("line")
.attr("stroke-width", 5)
.attr("stroke","#000");
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("id", function(d) {return "circle"+d.id;})
.attr("class", "node")
.attr("r", node_radius)
.style("opacity", 0.2)
.attr("dx", 12)
.attr("dy", ".35em");
var text = svg.append("g")
.attr("class", "label")
.selectAll("text")
.data(data)
.enter().append("text")
.text(function(d) { return d.name });
// Update and restart the simulation.
simulation.nodes(data).on("tick", ticked);
simulation.force("link").links(links);
simulation.alpha(1).restart();
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", positionNode);
text
.attr("dx", function(d) { return d.x - 30; })
.attr("dy", function(d) { return d.y + 15; });
}
// move the node based on forces calculations
function positionNode(d) {
// keep the node within the boundaries of the svg
if (d.x < node_radius) {
d.x = 2*node_radius
};
if (d.y < node_radius) {
d.y = 2*node_radius
};
if (d.x > width-node_radius) {
d.x = width-(2*node_radius)
};
if (d.y > height-node_radius) {
d.y = height-(2*node_radius)
};
return "translate(" + d.x + "," + d.y + ")";
}
<script src="https://d3js.org/d3.v4.min.js"></script>
I would like to have something like the following image. The two clusters are displayed and in each group, all children (leaf nodes) are represented by a smaller node. Node size should be customizable for both "layers" from data input.
Example I'm trying to follow on Fiddle.
I have also tried to use d3.pack() to pack circles inside of other circles. Here is an example. The problem I have currently with this approach is that I did not succeed in adding space and links between nodes of the first "layer" (between clusters). The high level clusters are also packed together and it would be impossible to add comprehensible links betwen them.
I finally succeeded in merging the d3.pack() example with the clustering example. Here is my solution.
var data = [
{
"id":1,
"level": "cluster",
"name": "analytics1",
"children": [
{
"id":2,
"name": "animate1",
"level": "leaf",
"size": 8,
"parent": 1,
"icon":"https://image.freepik.com/free-icon/apple-logo_318-40184.jpg"
},
{
"id":3,
"name": "animate2",
"level": "leaf",
"size": 10,
"parent": 1,
"icon": "https://www.freelogodesign.org/img/logo-ex-7.png"
},
{
"id":4,
"name": "animate3",
"level": "leaf",
"size": 5,
"parent": 1,
"icon": "http://brandmark.io/logo-rank/random/pepsi.png"
}
]
},
{
"id":6,
"name": "analytics2",
"level": "cluster",
"children": [
{
"id":7,
"name": "animate4",
"level": "leaf",
"size": 10,
"parent": 6,
"icon":"https://www.seoclerk.com/pics/558390-11FO8A1505384509.png"
}
]
}
]
var links = [
{ "source": 1, "target": 6}
] ;
var w = 1200, h = 500;
var cluster_padding = 5;
var node_padding = 2;
var size_ratio =100;
var color = d3.scaleOrdinal(d3.schemeCategory20c);
let sumSizes = 0;
data.forEach(function(cluster){
cluster.children.forEach(function(node){
sumSizes += node.size;
});
});
// Compute sum of sizes for cluster size.
data.forEach(function(cluster){
cluster.size = (
cluster.children.map(function(d){return d.size;})
.reduce(function(acc, val){ return acc+val+node_padding; })/sumSizes
)*size_ratio + cluster_padding;
cluster.children = cluster.children.sort(function(a,b){
return (a.size < b.size) ? 1 : ((b.size < a.size) ? -1 : 0);
})
cluster.children.forEach(function(node){
node.parentSize = cluster.size;
node.size = node.size*size_ratio/sumSizes;
});
});
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
////////////////////////
// outer force layout
var outerSimulation = d3.forceSimulation()
// pull nodes together based on the links between them
.force("link", d3.forceLink().id(function(d) { return d.id; }).strength(0.001))
// push nodes apart to space them out
.force("charge", d3.forceManyBody().strength(-5))
// add some collision detection so they don't overlap
.force("collide", d3.forceCollide().radius(function(d){return d.size+cluster_padding;}))
// and draw them around the centre of the space
.force("center", d3.forceCenter(w / 2, h / 2));
var outerLinks = svg.selectAll("line")
.data(links)
.enter().append("line")
.attr("class", "links")
.attr("stroke-width", 5);
var outerNodes = svg.selectAll("g.outer")
.data(data, function (d) {return d.id;})
.enter()
.append("g")
.attr("class", "outer")
.attr("id", function (d) {return "cluster"+d.id;})
.attr("x", w/2)
.attr("y", w/2)
.call(d3.drag());
outerNodes.append("circle")
.style("fill", function(d,i){return color(i);})
.style("stroke", "blue")
.attr("r", function(d){return d.size});
// Update and restart the simulation.
outerSimulation.nodes(data).on("tick", outerTick);
outerSimulation.force("link").links(links);
outerSimulation.alpha(1).restart();
////////////////////////
// inner force layouts
var innerNodes = [];
var innerTexts = [];
var packs = [];
var margin = 20;
data.forEach(function(n){
// Pack hierarchy definition
var pack = d3.pack()
.size([2*n.size, 2*n.size])
.padding(cluster_padding);
var root = d3.hierarchy(n)
.sum(function(d) { return d.size; })
.sort(function(a, b) { return b.value - a.value; });
var nodes = pack(root).descendants();
// Round images
var defs = svg.append("defs").attr("id", "imgdefs")
var pattern = defs
.selectAll("pattern")
.data(nodes.filter(function(d) { return d.parent }))
.enter().append("pattern")
.attr("id", function(d){return "photo"+d.data.name})
.attr("height", 1)
.attr("width", 1)
.attr("x", "0")
.attr("y", "0");
var image = pattern.append('image')
.attr("class","roundImg")
.attr("id", function(d){return "photo"+d.data.name;})
.attr("xlink:href", function(d){return d.data.icon ? d.data.icon : "";})
.attr("height", function(d){return 3.2*d.r ;})
;
// Nodes
var circle = svg.select("g.outer#cluster"+n.id).selectAll("g.inner")
.data(nodes.filter(function(d) { return d.parent }))
.enter().append("circle")
.attr("class", "node node--leaf ")
.attr("id", function(d) {return d.data.name})
.style("fill", function(d) { return "url(#photo"+d.data.name+")";})
.attr("r", function(d) { return d.r; })
.attr("transform", function(d) { return "translate("+(d.x-n.size) +","+ (d.y-n.size)+")"; })
;
});
////////////////////////
// functions
function outerTick (e) {
outerNodes.attr("transform", positionNode);
outerLinks
.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; });
}
function positionNode(d) {
// keep the node within the boundaries of the svg
if (d.x - d.size < 0) {
d.x = d.size + 2
};
if (d.y - d.size < 0) {
d.y = d.size + 2
};
if (d.x + d.size > w) {
d.x = w - d.size - 2
};
if (d.y + d.size > h) {
d.y = h - d.size - 2
};
return "translate(" + d.x + "," + d.y + ")";
}
<!DOCTYPE html>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<link rel="stylesheet" type="text/css" href="css/pack.css">
<body>
<div class="packed" id="packed"></div>
</body>
As the title says, I'm trying to make an interactive choropleth using d3. I've found an interesting example that I'm trying to replicate for another location.
Concretely, I'm trying to plot the Washington state, at the zip code level.
I've added the code I have at the moment that could be potentially edited, it's based on this example here is the live demo that shows the final result.
This is working for the California state, however when changing the zip code topojson state (to Washington state) the plot doesn't work. Also there are no explicit errors. The error could be in differences in the topojson.
This is the california topojson, here the Washington version.
Below are the first values pretty printed for each topojson.
California topojson:
{
"type": "Topology",
"objects": {
"zip": {
"type": "GeometryCollection",
"crs": {
"type": "name",
"properties": {
"name": "urn:ogc:def:crs:OGC:1.3:CRS84"
}
},
"geometries": [
{
"type": "Polygon",
"properties": {
"zipcode": 94601
},
"arcs": [
[
0,
1,
2,
3,
4,
5
]
]
}
Washington topojson:
{
"type": "Topology",
"objects": {
"tl_2010_53_zcta510": {
"type": "GeometryCollection",
"crs": {
"type": "name",
"properties": {
"name": "urn:ogc:def:crs:OGC:1.3:CRS84"
}
},
"geometries": [
{
"type": "Polygon",
"properties": {
"zipcode": "98822"
},
"arcs": [
[
0,
1,
2,
3
],
[
4
]
]
}
The following is the main.js function. I assume this and inspecting both topojson files could help find where the problem might be. Basically the only thing changing is the topojson file, then the main.js function should reflect these changes.
Also the "fake_data.csv" would just represent a serie of zipcode:value pairs as:
zip,values
98001,1
98002,1
98003,1
98004,2
98005,2
98006,2
main.js
(function chart() {
var width = 1000,
height = 1200,
centered;
var rateById = d3.map();
var quantize = d3.scale.quantize()
.domain([0, 100000])
.range(d3.range(9).map(function(i) { return "q" + i + "-9"; }));
var projection = d3.geo.albersUsa()
.scale(6000)
.translate([2300, 680]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("#ca-chart").append("svg")
.attr("width", width)
.attr("height", height);
var tooltip = d3.select("#ca-chart").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
svg.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height)
.on("click", clicked);
var g = svg.append("g");
// These are the two lines that are different from the working example
queue()
.defer(d3.json, "https://gist.githubusercontent.com/martinbel/e14cd6ecd565914f53af/raw/e3a3a8332c20fe3cee6d7fd2a9ac01ad43f7aaa4/WA.topojson")
.defer(d3.csv, "fake_data.csv", function(d) { rateById.set(d.zip.toString(), +d.values); })
.await(ready);
function ready(error, zipcode) {
var features = topojson.feature(zipcode, zipcode.objects.tl_2010_53_zcta510).features;
g.append("g")
.attr("class", "state")
.selectAll("path")
.data(topojson.feature(zipcode, zipcode.objects.tl_2010_53_zcta510).features)
.enter().append("path")
.attr("d", path)
.attr("stroke", "#333")
.attr("stroke-width", "1.5px")
.attr("fill", "#fff");
g.append("g")
.attr("class", "zipcodes")
.selectAll("path")
.data(features)
.enter().append("path")
.attr("class", getColorClass)
.attr("d", path)
.on("click", clicked)
.on("mouseover", mouseover)
.on("mouseout", mouseout);
}
function getColorClass(d) {
return quantize(rateById.get(d.properties.zipcode));
}
function getPopulation(d) {
return rateById.get(getZip(d)).toString();
}
function getZip(d) {
return d && d.properties ? d.properties.zipcode : null;
}
function mouseout(d) {
d3.select(this)
.style("stroke", null);
tooltip.transition()
.duration(250)
.style("opacity", 0);
}
function mouseover(d) {
d3.select(this.parentNode.appendChild(this))
.style("stroke", "#F00");
tooltip.transition()
.duration(250)
.style("opacity", 1);
tooltip
.html("<p><strong>Zipcode: " + getZip(d) + "<br>Population: " + getPopulation(d) + "</strong></p>")
.style("left", (d3.event.pageX + 25) + "px")
.style("top", (d3.event.pageY - 28) + "px");
}
function clicked(d) {
var x, y, k;
if (d && centered !== d) {
var centroid = path.centroid(d);
x = centroid[0];
y = centroid[1];
k = 8; // control zoom depth
centered = d;
} else {
x = width / 2;
y = height / 2;
k = 1;
centered = null;
}
g.selectAll("path")
.classed("active", centered && function(d) { return d === centered; });
g.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
}
d3.select(self.frameElement).style("height", height + "px");
}());
The topojson file was produced in the following way:
curl -O "ftp://ftp2.census.gov/geo/tiger/TIGER2010/ZCTA5/2010/tl_2010_53_zcta510.zip"
unzip "tl_2010_53_zcta510.zip"
ogr2ogr -f GeoJSON -s_srs crs:84 -t_srs crs:84 tl_2010_53_zcta510.geojson tl_2010_53_zcta510.shp
topojson -o tl_2010_53_zcta510.topojson --properties zipcode=ZCTA5CE10 tl_2010_53_zcta510.geojson
The issue is that you're drawing the paths outside of the active viewing area. Try just this as your projection and you'll see the paths.
var projection = d3.geo.albersUsa()
// .scale(6000)
// .translate([2300, 680]);
You'll have to edit the scaling/translation for Washington...might be helpful to make your svg width and height ginormous (10000px or so) as you do this just so you can see where the map ends up.
This solved it for showing the map and display it without having to hack the parameters by hand. Actually the trick is to use a projection that supports the .center() method, albersUsa doesn't. Then it's much easier to sort out the scale parameter.
var projection = d3.geo.mercator()
.center([-120.574951, 47.361153])
.scale(5000)
.translate([(width) / 2, (height)/2]);
After this some other issues appeared.
Im learning d3 js layout and having difficulty in adding node to the tree layout.
Hoping for a way of adding dynamic node(children) after clicking the parent node.
My current implementation do add the node, but instead of updating it, it added new children and keep the child from before.
Can someone help me understand the problem and a correct way of approaching this.
Here is my code and my Fiddle (click on the root node):
HTML
<div id="body">
</div>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
JS
var margin = {top: 100, right: 50, bottom: 100, left: 50},
width = 900 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var tree = d3.layout.tree()
.separation(function(a, b) { return a.children === b.children ? 1 : 1.2; })
.size([width, height]);
var svg = d3.select("body")
.attr("bgcolor", "#fff")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var dataOne = {
"name": "Mike and Marcia",
"children": [
{
"name": "Children",
"children": [
{ "name": "Mikael" }
]
}
]
};
var drawTree = function(source){
var nodes = tree.nodes(source);
var node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("g");
var boxes = node.selectAll('g')
.data(nodes)
.enter()
.append('g')
.append('rect')
.attr("width", 50)
.attr("height", 40)
.attr("fill", "tan")
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y + 50; });
boxes.on("click", function(d,i){
clickOutside(d);
});
};
var clickOutside = function(value){
var newData = {
"name": "Mike and Marcia",
"children": [{
"name": "Children",
"children": [{
"name": "Mikael"
}]
},{
"name": "Pets",
"children": []
}]
};
drawTree(newData);
console.log(value);
}
drawTree(dataOne);
The problem is that you are drawing the new graph over the old graph.
That is the reason why you get the impression that its adding child to the old parent.
So the correct approach would be to
draw the graph
remove all nodes that is not required.
So 1st point
var nodedata = svg.selectAll(".node")
.data(nodes, function(d){ /* function which return the Unique id of each node */ return d.name;})
//make all the nodes.
nodedata.enter().append('g')
.attr("class", "node")
.append('rect')
.attr("width", 50)
.attr("height", 40)
.attr("fill", "tan")
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y + 50; })
.on("click", function(d,i){
clickOutside(d);
});
2nd point
Remove all nodes which are intersection of first set of passed data and the second set of passed data.
nodedata.exit().remove();
Last point add children to parent
You can change the JSON
var dataOne = {
"name": "Mike and Marcia",
"children": [
{
"name": "Children",
"children": [
{ "name": "Mikael" }
]
}
]
};
Add whatever structure JSON and pass it down your drawTree function
Working code here
Going to need a bubble chart for something I was working on and was using http://bl.ocks.org/mbostock/4063269 as an example. My data will be coming in as a flat format, so I wouldn't need to process the node tree and flatten it, so I removed that part.
Problem is that after removing that and simplifying the rest, it doesn't seem to do anything with the data. I'm guessing I have the data formatted incorrectly somehow, but I'm not sure.
http://tributary.io/inlet/b54cdb7104c40b1d7df3
I get no errors on it running, but I obviously have to be missing something here, right?
Even though your data is flat, the pack layout expects hierarchical data. You have to give it at least one children of your root node:
var json = {
"data": {
"children": [{ //<-- needs a child...
"name": "test",
"value": 55
}, {
"name": "test2",
"value": 34
}]
}
};
Full code:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
</head>
<body>
<script>
var json = {
"data": {
"children": [{
"name": "test",
"value": 55
}, {
"name": "test2",
"value": 34
}]
}
};
var diameter = 960,
format = d3.format(",d"),
color = d3.scale.category20c();
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.padding(1.5);
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("class", "bubble");
var node = svg.selectAll(".node")
.data(bubble.nodes(json.data))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.append("title")
.text(function(d) {
return d.name + ": " + format(d.value);
});
node.append("circle")
.attr("r", function(d) {
return d.r;
})
.style("fill", function(d) {
return color(d.name);
});
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) {
return d.name;
});
</script>
</body>
</html>
Have been struggling for ages to create a horizontal tree layout with rectangles instead of circles, and have the text wrap inside those rectangles. Nothing I do seems to work, I have tried this code but whoever made it has left out a crucial step, in defining the variable d before the line
if (d.name.length > 26) which stops the whole script from running.
I have also been trying to use d3plus.js from http://d3plus.org/ in order to wrap text inside rect tags but it actually doesn't work half the time and seems to need a trigger like a click function in order to work. Also considering using this example as a guide on text wrapping.
In my research I have not found that anybody has done the combination of horizontal, rectangles and wrapped text in one diagram.
Also I am a bit of a d3 noob so all help is appreciated.
Here is a JSFiddle.
Here is the current code I am using which isn't working:
var w = 960,
h = 2000,
i = 0,
duration = 500,
root;
var tree = d3.layout.tree()
.size([h, w - 160]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var vis = d3.select("#container").append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("transform", "translate(40,0)");
root = treeData[0];
root.x0 = h / 2;
root.y0 = 0;
update(root);
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse();
// Update the nodes…
var node = vis.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
var nodeEnter = node.enter().append("svg:g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; });
// Enter any new nodes at the parent's previous position.
nodeEnter.append("svg:rect")
.attr("width", 150)
.attr("height", function(d) { return (d.name.length > 30) ? 38 : 19;})
.attr("y",-11)
.attr("rx",2)
.attr("ry",2)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; })
.on("click", click);
if (d.name.length > 26) {
nodeEnter.append("svg:text")
.attr("x", function(d) { return d._children ? -8 : 8; })
.attr("y", 3)
.text(function(d) { return d.name; });
} else {
nodeEnter.append("svg:text")
.attr("x", function(d) { return d._children ? -8 : 8; })
.attr("y", 3)
.append("svg:tspan")
.text(function(d) { return d.name.slice(0,26); })
.append("svg:tspan")
.attr("x", function(d) { return d._children ? -8 : 8; })
.attr("y",15)
.text(function(d) { return d.name.slice(26); });
}
}
// Transition nodes to their new position.
nodeEnter.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
.style("opacity", 1)
.select("rect")
.style("fill", "lightsteelblue");
node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
.style("opacity", 1);
node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
.style("opacity", 1e-6)
.remove();
// Update the links…
var link = vis.selectAll("path.link")
.data(tree.links(nodes), function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("svg:path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
})
.transition()
.duration(duration)
.attr("d", diagonal);
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
d3.select(self.frameElement).style("height", "2000px");
As well as my json:
var treeData = [
{
"name": "Do trainees require direction as to what to do or how to do the task (either before they start or while they are completing it?",
"children": [
{
"name": "Can they satisfactorily complete the task assigned to them?",
"children": [
{
"name": "Rating level 4",
"parent": "A",
},
{
"name": "How many problems / queries are there that still need to be addressed / resolved to be able to satisfactorily complete the task?",
"children": [
{
"name": "Are problems / queries fundamental to the completion of the task at hand?",
"children": [
{
"name": "Rating level 4",
},
{
"name": "Can the problems be resolved by the trainee (after receiving guidance)?",
"children": [
{
"name": "Rating level 3",
},
{
"name": "Can the problems be resolved by the trainee (after receiving guidance)?",
"children": [
{
"name": "Rating level 2",
},
{
"name": "Rating level 1",
}
]
}
]
}
]
},
{
"name": "Are problems / queries fundamental to the completion of the task at hand?",
}
]
}
]
},
{
"name": "Can they satisfactorily complete the task assigned to them?",
"children": [
{
"name": "Rating 1",
},
{
"name": "Rating 2",
},
{
"name": "Rating 3",
},
{
"name": "Rating 4",
}
]
}
]
}];
Your code throws an error at this line:
if (d.name.length > 26) {
d isn't defined. When d3 code references d, it's usually in the scope of a data binding. At this place in the code, you are not looping a binding, like:
nodeEnter.append("text")
.attr("x", function(d) {
return d._children ? -8 : 8;
})
.attr("y", 3)
.attr("dy", "0em")
.text(function(d) {
return d.name; // d is defined from the binding
});
That said, I like the wrap function you link to. So add your text like above and then wrap the text:
wrap(d3.selectAll('text'),150);
Here's a quick modification to the wrap that'll resize your rects as well:
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
// find corresponding rect and reszie
d3.select(this.parentNode.children[0]).attr('height', 19 * (lineNumber+1));
});
}
Example here.