I'm having trouble hyperlinking child elements in a Reingold-Tifold tree graph. The graph is built in D3 and is based on the one that Mike Bostock uses an example: http://bl.ocks.org/mbostock/4339184
My code and a segment of my JSON file are below.
At this point, the graph works fine - if I click on a parent node, it expands to reveal a series of child nodes that are contained in my JSON file. But when I get to the child level, I want to make the word hyperlinked. I don't understand how to do that.
I've used a previous SO question/answer - Hyperlinks in d3.js objects - to understand how to add a URL to my JSON data (at this point everything is just linking to Google.co.uk). But I don't understand how to link that URL to my tree graph so that the child nodes become hyperlinked.
Can you let me know how I can do this?
Thank you, in advance, for any help. (And thanks, #IH8, for your assistance so far.)
Matt
<html xmlns:xlink="http://www.w3.org/1999/xlink">
<head><meta charset="utf-8">
<style>
.node {
cursor: pointer;
}
.node circle {
fill: #FF0000;
stroke: black;
stroke-width: 1.5px;
}
.node text {
font: 11px sans-serif;
}
.link {
fill: none;
stroke: #FF0000;
stroke-width: 1.5px;
}
</style>
<style id="style-1-cropbar-clipper">/* Copyright 2014 Evernote Corporation. All rights reserved. */
.en-markup-crop-options {
top: 18px !important;
left: 50% !important;
margin-left: -100px !important;
width: 200px !important;
border: 2px rgba(255,255,255,.38) solid !important;
border-radius: 4px !important;
}
.en-markup-crop-options div div:first-of-type {
margin-left: 0px !important;
}
</style></head><body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = 960 - margin.right - margin.left,
height = 800 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("/sites/default/files/tcs/D3/data/turbo.json", function(error, turbo) {
root = turbo;
root.x0 = height / 2;
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);
});
d3.select(self.frameElement).style("height", "800px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 180; });
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
.on("click", click);
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) { return d._children ? "red" : "#fff"; });
nodeEnter.each(function(d){
var thisNode = d3.select(this);
if (!d.children) {
thisNode.append("a")
.attr("xlink:href", function(d) { return d.url; })
.append("text")
.attr("x", 8)
.attr("dy", 3)
.attr("text-anchor", "start")
.text(function(d) { return d.name; });
} else {
thisNode.append("text")
.attr("x", -8)
.attr("dy", 3)
.attr("text-anchor", "end")
.text(function(d) { return d.name; });
}
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("circle")
.attr("r", 4.5)
.style("fill", function(d) { return d._children ? "red" : "#fff"; });
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
// 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);
}
</script>
</body>
</html>
A portion of my JSON file:
{
"name": "turbo",
"children": [
{
"name": "Level 1",
"children": [
{
"name": "Google",
"children": [
{"name": "Website", "size": 3938, "URL":"http://www.google.co.uk"},
{"name": "CommunityStructure", "size": 3812, "URL":"http://www.google.co.uk"},
{"name": "HierarchicalCluster", "size": 6714, "URL":"http://www.google.co.uk"},
{"name": "MergeEdge", "size": 743, "URL":"http://www.google.co.uk"}
]
},
{
"name": "graph",
"children": [
{"name": "BetweennessCentrality", "size": 3534, "URL":"http://www.google.co.uk"},
{"name": "LinkDistance", "size": 5731, "URL":"http://www.google.co.uk"},
{"name": "MaxFlowMinCut", "size": 7840, "URL":"http://www.google.co.uk"},
{"name": "ShortestPaths", "size": 5914, "URL":"http://www.google.co.uk"},
{"name": "SpanningTree", "size": 3416, "URL":"http://www.google.co.uk"}
]
},
{
"name": "optimization",
"children": [
{"name": "AspectRatioBanker", "size": 7074, "URL":"http://www.google.co.uk"}
]
}
]
},
. . .
Seems you've made a slight mistake when creating your dataset url attribute, or you should've adapted the code you're using to add the xlink:href attribute to the a element. You're calling:
.attr("xlink:href", function(d) { return d.url; })
But in your dataset the attribute defined like this:
"URL": "http://www.google.co.uk"
Those don't match, you should change your code to this:
.attr("xlink:href", function(d) { return d.URL; })
Or your dataset to this:
"url": "http://www.google.co.uk"
Remember that names of JSON properties are case-sensitive.
Related
This question already has answers here:
Where is d3.svg.diagonal()?
(5 answers)
Closed 5 years ago.
I've been trying to migrate from D3.js v3 to version 4. I've reviewed the changelog and updated all functions, but I'm unable to render the path from source to target nodes now that the diagonal function is removed.
I'm using a Parse Tree generated by a Python Script via HTML and d3.js. The Python Script generates an HMTL document, here it is running with D3.js version 3
function drawTree(){
var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = 1060 - margin.right - margin.left,
height = 600 - margin.top - margin.bottom;
var i = 0,
duration = 750,// animation duration
root;// stores the tree structure in json format
var tree = d3.layout.tree()
.size([height, width]);
var edge_weight = d3.scale.linear()
.domain([0, 100])
.range([0, 100]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
// adding the svg to the html structure
var svg = d3.select("div#viz").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var treeData =
{
"name": "Grandparent",
"size" : 100,
"children": [
{
"name": "Parent A",
"size": 70,
"children": [
{ "name": "Son of A",
"size" : 30,
"children": [
{ "name": "grandson of A",
"size" : 3},
{ "name": "grandson 2 of A",
"size" : 2},
{ "name": "grandson 3 of A",
"size" : 5},
{ "name": "grandaughter of A",
"size" : 20,
"children": [
{ "name": "great-grandson of A",
"size" : 15},
{ "name": "great-grandaughter of A",
"size" : 5}
]
}
],
},
{ "name": "Daughter of A" ,
"size" : 40
}
]
},
{ "name": "Parent B",
"size" : 30 }],
};
edge_weight.domain([0,treeData.size]);
// Assigns parent, children, height, depth
root = treeData;
root.x0 = height / 2;
root.y0 = 0;
root.children.forEach(collapse);
update(root);
d3.select(self.frameElement).style("height", "800px");
/**
* Updates the node.
* cloppases and expands the node bases on the structure of the source
* all 'children' nodes are expanded and '_children' nodes collapsed
* #param {json structure} source
*/
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 180; });
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append('g')
.attr('class', 'node')
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on('click', click);
nodeEnter.append("circle")
.attr('class', 'node')
.attr('r', 1e-6)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("x", function(d) { return d.children || d._children ? -10 : 10; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.name; })
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("circle")
.attr("r", function(d){ console.log(">>>>>>>>", d);return edge_weight(d.size/2);})
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff"; });
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("stroke-width", function(d){
return 1;
})
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
})
.attr("stroke", function(d){
return "lavender";});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", function(d){
/* calculating the top shift */
var source = {x: d.source.x - edge_weight(calculateLinkSourcePosition(d)), y: d.source.y};
var target = {x: d.target.x, y: d.target.y};
return diagonal({source: source, target: target});
})
.attr("stroke-width", function(d){
return edge_weight(d.target.size);
});
// 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;
});
}
/**
* Calculates the source y-axis position of the link.
* #param {json structure} link
*/
function calculateLinkSourcePosition(link) {
targetID = link.target.id;
var childrenNumber = link.source.children.length;
var widthAbove = 0;
for (var i = 0; i < childrenNumber; i++)
{
if (link.source.children[i].id == targetID)
{
// we are done
widthAbove = widthAbove + link.source.children[i].size/2;
break;
}else {
// keep adding
widthAbove = widthAbove + link.source.children[i].size
}
}
return link.source.size/2 - widthAbove;
}
/*
* Toggle children on click.
* #param {node} d
*/
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
/*
* Collapses the node d and all the children nodes of d
* #param {node} d
*/
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
/*
* Collapses the node in the tree
*/
function collapseAll() {
root.children.forEach(collapse);
update(root);
}
/*
* Expands the node d and all the children nodes of d
* #param {node} d
*/
function expand(d) {
if (d._children) {
d._children = null;
}
if (d.children) {
d.children.forEach(expand);
}
}
/*
* Expands all the nodes in the tree
*/
function expandAll() {
root.children.forEach(expand);
update(root);
}
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
.link {
fill: none;
/*stroke: steelblue;*/
opacity: 0.3;
/*stroke-width: 1.5px;*/
}
#levels{
margin-left: 120px;
}
<!DOCTYPE html>
<meta charset="utf-8">
<body onLoad="drawTree()">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.0.0/d3.min.js"></script>
<button type="button" onclick="collapseAll()">Collapse All</button>
<button type="button" onclick="expandAll()">Expand All</button>
<div id="viz"></div>
</body>
And here's as far as I got with the v4 migration.
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 900 - margin.right - margin.left,
height = 400 - margin.top - margin.bottom;
var i = 0,
duration = 750, // animation duration
root; // stores the tree structure in json format
// declares a tree layout and assigns the size
var treemap = d3.tree().size([height, width]);
var edge_weight = d3.scaleLinear()
.domain([0, 100])
.range([0, 100]);
// Creates a curved (diagonal) path from parent to the child nodes
function diagonal(s, d) {
path = `M ${s.y} ${s.x}
C ${(s.y + d.y) / 2} ${s.x},
${(s.y + d.y) / 2} ${d.x},
${d.y} ${d.x}`
return path
}
// adding the svg to the html structure
var svg = d3.select("div#viz").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var treeData = {
"name": "Grandparent",
"size": 100,
"children": [{
"name": "Parent A",
"size": 70,
"children": [{
"name": "Son of A",
"size": 30,
"children": [{
"name": "grandson of A",
"size": 3
},
{
"name": "grandson 2 of A",
"size": 2
},
{
"name": "grandson 3 of A",
"size": 5
},
{
"name": "grandaughter of A",
"size": 20,
"children": [{
"name": "great-grandson of A",
"size": 15
},
{
"name": "great-grandaughter of A",
"size": 5
}
]
}
],
},
{
"name": "Daughter of A",
"size": 40
}
]
},
{
"name": "Parent B",
"size": 30
}
],
};
edge_weight.domain([0, treeData.size]);
// Assigns parent, children, height, depth
root = d3.hierarchy(treeData, function(d) {
return d.children;
});
root.x0 = height / 2;
root.y0 = 0;
root.children.forEach(collapse);
update(root);
d3.select(self.frameElement).style("height", "800px");
/**
* Updates the node.
* cloppases and expands the node bases on the structure of the source
* all 'children' nodes are expanded and '_children' nodes collapsed
* #param {json structure} source
*/
function update(source) {
// Assigns the x and y position for the nodes
var treeData = treemap(root);
// Compute the new tree layout.
var nodes = treeData.descendants(),
links = treeData.descendants().slice(1);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append('g')
.attr('class', 'node')
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on('click', click);
nodeEnter.append("circle")
.attr('class', 'node')
.attr('r', 1e-6)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -10 : 10;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = nodeEnter.merge(node);
nodeUpdate.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
nodeUpdate.select("circle")
.attr("r", function(d) {
return edge_weight(d.data.size / 2);
})
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.id;
});
//.data(links, function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
var linkEnter = link.enter().insert('path', "g")
.attr("class", "link")
.attr('d', function(d) {
console.log("linkEnter", d);
var o = {
x: source.x,
y: source.y
}
console.log("o", o);
return diagonal(o, o)
})
.attr("stroke", function(d) {
return "cyan";
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", function(d) {
console.log("lala", d);
/* calculating the top shift */
var source = {
x: d.x - edge_weight(calculateLinkSourcePosition(d)),
y: d.y
};
var target = {
x: d.parent.x,
y: d.parent.y
};
return diagonal({
source: source,
target: target
});
})
.attr("stroke-width", function(d) {
return edge_weight(d.target.size);
});
// 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) {
console.log("stash", d);
d.x0 = d.x;
d.y0 = d.y;
});
}
/**
* Calculates the source y-axis position of the link.
* #param {json structure} link
*/
function calculateLinkSourcePosition(link) {
targetID = link.target.id;
var childrenNumber = link.source.children.length;
var widthAbove = 0;
for (var i = 0; i < childrenNumber; i++) {
if (link.source.children[i].id == targetID) {
// we are done
widthAbove = widthAbove + link.source.children[i].size / 2;
break;
} else {
// keep adding
widthAbove = widthAbove + link.source.children[i].size
}
}
return link.source.size / 2 - widthAbove;
}
/*
* Toggle children on click.
* #param {node} d
*/
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
/*
* Collapses the node d and all the children nodes of d
* #param {node} d
*/
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
/*
* Collapses the node in the tree
*/
function collapseAll() {
root.children.forEach(collapse);
update(root);
}
/*
* Expands the node d and all the children nodes of d
* #param {node} d
*/
function expand(d) {
if (d._children) {
d.children = d._children;
d._children = null;
}
if (d.children) {
d.children.forEach(expand);
}
}
/*
* Expands all the nodes in the tree
*/
function expandAll() {
root.children.forEach(expand);
update(root);
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
.link {
fill: none;
/*stroke: steelblue;*/
opacity: 0.3;
/*stroke-width: 1.5px;*/
}
#levels {
margin-left: 120px;
}
<body>
<script src="http://d3js.org/d3.v4.min.js"></script>
<button type="button" onclick="collapseAll()">Collapse All</button>
<button type="button" onclick="expandAll()">Expand All</button>
<div id="viz"></div>
</body>
Most likely I'm just blind and you'll instantly see what I did wrong, but I've been staring at this for a while now...
Thanks in advance for any help!
I'm currently working on a similar project myself and figured this could be pretty helpful.. It seems you were really close, just needed to adjust a few minor paths to reach parent/child nodes..
Basically "source" is now "parent" and there is no "target". You can see most of the updates where commented out lines are v3 with v4 updates just below. For example:
link.target.id --> link.id
link.source.children.length --> link.parent.children.length
link.source.size --> link.parent.data.size
There's a few other miscellaneous updates throughout the code as well. The one thing I couldn't get working fully were the "Expand All/Collapse All" buttons. "Expand All" seems to work ok, but "Collapse All" seems to leave the link paths alone..
Here's a working fiddle: https://jsfiddle.net/jufra0b2/
I suspect there's something that can be done to the exit link but not sure. Anyways it's a step in the right direction. Hope you figure out the rest ok..
I'm following this
tutorial to learn d3 js tree layout and i'm playing around with it. On clicking a child node I'm trying to select all the ancestor nodes and the paths connecting them.
I have changed the default click function in the tutorial to something that looks like this.
function click(d) {
console.log(d.parent);
while(d.parent)
{
d = d.parent;
console.log(d.parent);
}
}
This select all the ancestor nodes one by one , but how can i select all the connecting paths between them ?
For eg: If i want to color all the ancestor nodes and the connecting paths , how can i do that ?
var treeData = [{
"name": "Top Level",
"parent": "null",
"children": [{
"name": "Level 2: A",
"parent": "Top Level",
"children": [{
"name": "Son of A",
"parent": "Level 2: A"
}, {
"name": "Daughter of A",
"parent": "Level 2: A"
}]
}, {
"name": "Level 2: B",
"parent": "Top Level"
}]
}];
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
root.x0 = height / 2;
root.y0 = 0;
update(root);
d3.select(self.frameElement).style("height", "500px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", click);
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
nodeUpdate.select("circle")
.attr("r", 10)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
});
// 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) {
console.log(d.parent);
while (d.parent) {
d = d.parent;
console.log(d.parent);
}
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
First give Ids to link path like this:
link.enter().insert("path", "g")
.attr("class", "link")
.attr("id", function(d){ return ("link" + d.source.id + "-" + d.target.id)})//unique id
Then give id to all nodes.
nodeEnter.append("circle")
.attr("r", 1e-6)
.attr("id", function(d){return "node" + d.id;})//id of the node.
Then in the click function do the selection based on the ids:
function click(d) {
//reset all nodes color
d3.selectAll("circle").style("fill", "white");//reset all node colors
d3.selectAll("path").style("stroke", "#c3c3c3");//reset the color for all links
while (d.parent) {
d3.selectAll("#node"+d.id).style("fill", "red");//color the node
if (d.parent != "null")
d3.selectAll("#link"+d.parent.id + "-" + d.id).style("stroke", "red");//color the path
d = d.parent;
}
}
Working code here
EDIT: Plunker adapted to stack snippet:
var treeData = [{
"name": "Top Level",
"parent": "null",
"children": [{
"name": "Level 2: A",
"parent": "Top Level",
"children": [{
"name": "Son of A",
"parent": "Level 2: A"
}, {
"name": "Daughter of A",
"parent": "Level 2: A"
}]
}, {
"name": "Level 2: B",
"parent": "Top Level"
}]
}];
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 180 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
root.x0 = height / 2;
root.y0 = 0;
update(root);
d3.select(self.frameElement).style("height", "500px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", click);
nodeEnter.append("circle")
.attr("r", 1e-6)
.attr("id", function(d){return "node" + d.id;})
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
nodeUpdate.select("circle")
.attr("r", 10)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("id", function(d){ return ("link" + d.source.id + "-" + d.target.id)})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
});
// 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) {
//reset all nodes color
d3.selectAll("circle").style("fill", "white");
d3.selectAll("path").style("stroke", "#c3c3c3");
while (d.parent) {
d3.selectAll("#node"+d.id).style("fill", "red")
if (d.parent != "null")
d3.selectAll("#link"+d.parent.id + "-" + d.id).style("stroke", "red")
d = d.parent;
}
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
D3 v6 example here.
I'm working with the D3 Tree layout, similar to this jsfiddle, the only problem is that I need the orientation of each node to start at the top left of the layout and not on the top center.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
Something similar to this:
Any ideas on how to do that?
Thank you in advance.
Building off of this example.
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// figure out the placement of the "top-most" node at each depth
var nodeMap = {};
nodes.forEach(function(d) {
if (!nodeMap[d.depth] || d.x < nodeMap[d.depth]){
nodeMap[d.depth] = d.x;
}
});
// shift all nodes up
nodes.forEach(function(d) {
d.y = d.depth * 100;
d.x -= nodeMap[d.depth];
});
This produces:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tree Example</title>
<style>
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
</style>
</head>
<body>
<!-- load the d3.js library -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var treeData = [
{
"name": "Top Level",
"parent": "null",
"children": [
{
"name": "Level 2: A",
"parent": "Top Level",
"children": [
{
"name": "Son of A",
"parent": "Level 2: A"
},
{
"name": "Daughter of A",
"parent": "Level 2: A"
}
]
},
{
"name": "Level 2: B",
"parent": "Top Level",
"children": [
{
"name": "Son of B",
"parent": "Level 2: B"
},
{
"name": "Daughter of B",
"parent": "Level 2: B"
}
]
}
]
}
];
// ************** Generate the tree diagram *****************
var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = 960 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
root.x0 = height / 2;
root.y0 = 0;
update(root);
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
var nodeMap = {};
nodes.forEach(function(d) {
if (!nodeMap[d.depth] || d.x < nodeMap[d.depth]){
nodeMap[d.depth] = d.x;
}
});
nodes.forEach(function(d) {
d.y = d.depth * 100;
d.x -= nodeMap[d.depth];
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
.on("click", click);
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeEnter.append("text")
.attr("x", function(d) { return d.children || d._children ? -13 : 13; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.name; })
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("circle")
.attr("r", 10)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
// 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);
}
</script>
</body>
</html>
not only the nodes (x,y) should be interchanged but also the links connecting the nodes i.e.
var diagonal = d3.svg.diagonal()
.projection(function(d) {return [d.y,d.x];});
have a look at this example i made
<!doctype html>
<html>
<head>
<script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<script>
var data = {
"name" : "A",
"children" : [
{
"name" : "B",
"children" : [
{
"name" : "F"
},
{
"name" : "G"
}
]
},
{
"name" : "C",
"children" : [
{
"name" : "H"
},
{
"name" : "I"
},
{
"name" : "J"
}
]
},
{
"name" : "D",
"children" : [
{
"name" : "K"
},
{
"name" : "L"
}
]
},
{
"name" : "E"
}
]
};
var canvas = d3.select("body")
.append("svg")
.attr("width",600)
.attr("height",500)
.append("g")
.attr("transform","translate(50,50)");
var cluster = d3.layout.cluster()
.size([400,400]);
var nodes = cluster.nodes(data);
var links = cluster.links(nodes);
var node = canvas.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class","node")
.attr("transform",function(d) {return "translate(" + d.y + "," + d.x + ")"});
node.append("circle")
.attr("r",5)
.attr("fill","silver");
node.append("text")
.text(function(d) {return d.name;});
var diagonal = d3.svg.diagonal()
.projection(function(d) {return [d.y,d.x];});
canvas.selectAll(".link")
.data(links)
.enter()
.append("path")
.attr("class","link")
.attr("fill","none")
.attr("stroke","#ADADAD")
.attr("d",diagonal);
</script>
I am a noob in web-development. I'm trying to create a tree-like hierarchical company org chart. I tried both google's visualization chart and Mike Bostock's D3 Reingold tree.
I want these features :
tree structure : either top-down (google) or left-right (D3)
online/dynamic : viewable in browser and able to read data from json (both google & D3), not static visio or ppt diagram
collapsible : able to hide subtrees (both)
space-adjusting : nodes should fill visible area, to reduce scrolling (only D3)
attributes : display name, title & possibly picture (only google)
Above I've marked which tool allows which features, afaik.
I prefer the D3 version because it looks cool.
I can modify the .json to include additional fields (title, url to photo etc.) - here is a sample
My question is - how do I modify the D3 code to display an employee's name, then title in the next line, and maybe picture too ?
Or if that's not feasible - how do I modify the google code to automatically adjust spacing, so that all children of a node are close together, and I don't have to horizontally scroll ?
Here's a quick example. It modifies this example, to add in first name, last name, a title and a picture.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
</style>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = 960 - margin.right - margin.left,
height = 300 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var data = {
"fname": "Rachel",
"lname": "Rogers",
"title": "CEO",
"photo": "http://lorempixel.com/60/60/cats/1",
"children": [{
"fname": "Bob",
"lname": "Smith",
"title": "President",
"photo": "http://lorempixel.com/60/60/cats/2",
"children": [{
"fname": "Mary",
"lname": "Jane",
"title": "Vice President",
"photo": "http://lorempixel.com/60/60/cats/3",
"children": [{
"fname": "Bill",
"lname": "August",
"title": "Dock Worker",
"photo": "http://lorempixel.com/60/60/cats/4"
}, {
"fname": "Reginald",
"lname": "Yoyo",
"title": "Line Assembly",
"photo": "http://lorempixel.com/60/60/cats/5"
}]
}, {
"fname": "Nathan",
"lname": "Ringwald",
"title": "Comptroller",
"photo": "http://lorempixel.com/60/60/cats/6"
}]
}]
}
root = data;
root.x0 = height / 2;
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 = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 180; });
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
.on("click", click);
// add picture
nodeEnter
.append('defs')
.append('pattern')
.attr('id', function(d,i){
return 'pic_' + d.fname + d.lname;
})
.attr('height',60)
.attr('width',60)
.attr('x',0)
.attr('y',0)
.append('image')
.attr('xlink:href',function(d,i){
return d.photo;
})
.attr('height',60)
.attr('width',60)
.attr('x',0)
.attr('y',0);
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
var g = nodeEnter.append("g");
g.append("text")
.attr("x", function(d) { return d.children || d._children ? -35 : 35; })
.attr("dy", "1.35em")
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.fname + " " + d.lname; })
.style("fill-opacity", 1e-6);
g.append("text")
.attr("x", function(d) { return d.children || d._children ? -35 : 35; })
.attr("dy", "2.5em")
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.title; })
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("circle")
.attr("r", 30)
.style("fill", function(d,i){
return 'url(#pic_' + d.fname + d.lname+')';
});
nodeUpdate.selectAll("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
// 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);
}
</script>
Reversed Direction:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
</style>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = 960 - margin.right - margin.left,
height = 300 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.x, d.y]; });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var data = {
"fname": "Rachel",
"lname": "Rogers",
"title": "CEO",
"photo": "http://lorempixel.com/60/60/cats/1",
"children": [{
"fname": "Bob",
"lname": "Smith",
"title": "President",
"photo": "http://lorempixel.com/60/60/cats/2",
"children": [{
"fname": "Mary",
"lname": "Jane",
"title": "Vice President",
"photo": "http://lorempixel.com/60/60/cats/3",
"children": [{
"fname": "Bill",
"lname": "August",
"title": "Dock Worker",
"photo": "http://lorempixel.com/60/60/cats/4"
}, {
"fname": "Reginald",
"lname": "Yoyo",
"title": "Line Assembly",
"photo": "http://lorempixel.com/60/60/cats/5"
}]
}, {
"fname": "Nathan",
"lname": "Ringwald",
"title": "Comptroller",
"photo": "http://lorempixel.com/60/60/cats/6"
}]
}]
}
root = data;
root.x0 = height / 2;
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 = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 180; });
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.x0 + "," + source.y0 + ")"; })
.on("click", click);
// add picture
nodeEnter
.append('defs')
.append('pattern')
.attr('id', function(d,i){
return 'pic_' + d.fname + d.lname;
})
.attr('height',60)
.attr('width',60)
.attr('x',0)
.attr('y',0)
.append('image')
.attr('xlink:href',function(d,i){
return d.photo;
})
.attr('height',60)
.attr('width',60)
.attr('x',0)
.attr('y',0);
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
var g = nodeEnter.append("g");
g.append("text")
.attr("x", function(d) { return d.children || d._children ? -35 : 35; })
.attr("dy", "1.35em")
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.fname + " " + d.lname; })
.style("fill-opacity", 1e-6);
g.append("text")
.attr("x", function(d) { return d.children || d._children ? -35 : 35; })
.attr("dy", "2.5em")
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.title; })
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
nodeUpdate.select("circle")
.attr("r", 30)
.style("fill", function(d,i){
return 'url(#pic_' + d.fname + d.lname+')';
});
nodeUpdate.selectAll("text")
.style("fill-opacity", 1);
// 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();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
// 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);
}
</script>
If you like to create your project with D3js, Just need to use the script codes from this and replaced3.json("/mbostock/raw/4063550/flare.json", function(error, flare) with this :
d3.json("yourJsonFile/jsonFileName.json", function(error, flare)
The jsonFileName.json is your customized json file like this sample.
Also you can insert name of your pictures into the json file and replace the img tag src with it.
I am using Cluster Dendogram .
And I want to insert images in to the leaves of the dendogram according to my clustering results.
How can I do that?
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
</style>
<body>
<svg id="mySvg" width="80" height="80">
<defs id="mdef">
<pattern id="image" x="-100" y="-100" height="200" width="200" patternUnits="userSpaceOnUse" >
<image x="50" y="5" width="10" height="10" xlink:href="http://www.e-pint.com/epint.jpg"></image>
</pattern>
</defs>
</svg>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 960,
height = 2200;
var cluster = d3.layout.cluster()
.size([height, width - 160]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(40,0)");
d3.json("https://rawgit.com/bansaghi/d3.chart.layout.hierarchy/master/example/data/flare.json", function(error, root) {
var nodes = cluster.nodes(root),
links = cluster.links(nodes);
var link = svg.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", diagonal);
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
//var circle = svg.append("circle")
// .style("fill", "red")
// .style("fill", "url(#image)");
node.append("circle")
.attr("r", 4.5);
//.style("fill", "black");e
node.append("image")
.attr("xlink:href", "http://www.e-pint.com/epint.jpg")
node.append("text")
.attr("dx", function(d) { return d.children ? -8 : 8; })
.attr("dy", 3)
.style("text-anchor", function(d) { return d.children ? "end" : "start"; })
.text(function(d) { return d.name; });
});
d3.select(self.frameElement).style("height", height + "px");
console.log(d3);
</script>
In this code I added style part and node.append("image")
.attr("xlink:href", "http://www.e-pint.com/epint.jpg").
The problem is I can not see the circles filled with image. What can be wrong?
Check this JSFiddle
Here's the code that adds an image to leaves:
var leafnodes = svg.selectAll('g.leaf.node')
.append("image")
.attr("xlink:href", "http://www.e-pint.com/epint.jpg")
.attr("width", 10)
.attr("height", 10);
And prior to that leaf nodes were marked with the leaf class like so:
.attr("class", function(n) {
return n.children ? "inner node" : "leaf node";
})
UPDATE:
To add different images to nodes you can do something like that:
var imgs = ['http://***.jpg',
'http://***.jpg',
'http://***.jpg'];
var leafnodes = svg.selectAll('g.leaf.node')
.append("image")
.attr("xlink:href", function (d) {
// get random image from array
return imgs[Math.floor(Math.random() * imgs.length)];
})
.attr("width", 10)
.attr("height", 10);
Check the JSFiddle above for the full example.
You can use svg clipPath to fill the circle with image.
Sample code:
var imgRadius = 10 - 1.5; //Where 10 is the radius of node and 1.5 is the stroke width
svg.append("defs")
.append("clipPath")
.attr('id', "circleCip")
.append("circle")
.attr("r", imgRadius);
node.append("svg:image")
.attr("xlink:href", "http://www.e-pint.com/epint.jpg")
.attr("clip-path", "url(#circleCip)")
.attr("x", function(d) {
return -imgRadius;
})
.attr("y", function(d) {
return -imgRadius;
})
.attr("width", function(d) {
return imgRadius * 2;
})
.attr("height", function(d) {
return imgRadius * 2;
});
Demo
var width = 960,
height = 2200;
var cluster = d3.layout.cluster()
.size([height, width - 160]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("transform", "translate(40,0)");
var imgRadius = 10 - 1.5; //10 is the radius of node and 1.5 is the stroke width
svg.append("defs")
.append("clipPath")
.attr('id', "circleCip")
.append("circle")
.attr("r", imgRadius);
var flare = {
"name": "flare",
"children": [{
"name": "analytics",
"children": [{
"name": "cluster",
"children": [{
"name": "AgglomerativeCluster",
"size": 3938
}, {
"name": "CommunityStructure",
"size": 3812
}, {
"name": "HierarchicalCluster",
"size": 6714
}]
}, {
"name": "graph",
"children": [{
"name": "BetweennessCentrality",
"size": 3534
}, {
"name": "LinkDistance",
"size": 5731
}]
}]
}, {
"name": "animate",
"children": [{
"name": "Easing",
"size": 17010
}, {
"name": "FunctionSequence",
"size": 5842
}, {
"name": "Transitioner",
"size": 19975
}, {
"name": "TransitionEvent",
"size": 1116
}, {
"name": "Tween",
"size": 6006
}]
}]
};
root = flare;
var nodes = cluster.nodes(root),
links = cluster.links(nodes);
var link = svg.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", diagonal);
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
})
node.append("circle")
.attr("r", 10);
node.append("svg:image")
.attr("xlink:href", "http://www.e-pint.com/epint.jpg")
.attr("clip-path", "url(#circleCip)")
.attr("x", function(d) {
return -imgRadius;
})
.attr("y", function(d) {
return -imgRadius;
})
.attr("width", function(d) {
return imgRadius * 2;
})
.attr("height", function(d) {
return imgRadius * 2;
});
node.append("text")
.attr("dx", function(d) {
return d.children ? -8 : 8;
})
.attr("dy", 3)
.style("text-anchor", function(d) {
return d.children ? "end" : "start";
})
.text(function(d) {
return d.name;
});
d3.select(self.frameElement).style("height", height + "px");
console.log(d3);
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>