CODE:
// some colour variables
var tcBlack = "#130C0E";
// rest of vars
var w = 1255,
h = 925,
maxNodeSize = 50,
x_browser = 10,
y_browser = 15,
root;
var vis;
var force = d3.layout.force();
vis = d3.select("#vis").append("svg").attr("width", w).attr("height", h);
d3.json("marvel.json", function(json) {
root = json;
root.fixed = true;
root.x = w / 2;
root.y = h / 4;
// Build the path
var defs = vis.insert("svg:defs")
.data(["end"]);
defs.enter().append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
update();
});
function update() {
var nodes = flatten(root),
links = d3.layout.tree().links(nodes);
// Restart the force layout.
force.nodes(nodes)
.links(links)
.gravity(0.05)
.charge(-1500)
.linkDistance(100)
.friction(0.5)
.linkStrength(function(l, i) {return 1; })
.size([w, h])
.on("tick", tick)
.start();
var path = vis.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
path.enter().insert("svg:path")
.attr("class", "link")
// .attr("marker-end", "url(#end)")
.style("stroke", "#eee");
// Exit any old paths.
path.exit().remove();
// Update the nodes…
var node = vis.selectAll("g.node")
.data(nodes, function(d) { return d.id; });
// Enter any new nodes.
var nodeEnter = node.enter().append("svg:g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.on("click", click)
.call(force.drag);
// Append a circle
nodeEnter.append("svg:circle")
.attr("r", function(d) { return Math.sqrt(40000) / 10 || 30; })
.style("fill", "#ffffff");
// Append images
var images = nodeEnter.append("svg:image")
.attr("xlink:href", function(d) { return d.img;})
.attr("x", function(d) { return -25;})
.attr("y", function(d) { return -25;})
.attr("height", 50)
.attr("width", 50);
// make the image grow a little on mouse over and add the text details on click
var setEvents = images
// Append hero text
.on( 'click', function (d) {
// d3.select("h1").html(d.hero);
d3.select("h3").html(d.name);
// d3.select("h3").html ("Take me to " + "<a href='" + d.link + "' >" + d.hero + " web page ⇢"+ "</a>" );
})
.on( 'mouseenter', function() {
// select element in current context
d3.select( this )
.transition()
.attr("x", function(d) { return -60;})
.attr("y", function(d) { return -60;})
.attr("height", 100)
.attr("width", 100);
})
// set back
.on( 'mouseleave', function() {
d3.select( this )
.transition()
.attr("x", function(d) { return -25;})
.attr("y", function(d) { return -25;})
.attr("height", 50)
.attr("width", 50);
});
// Append hero name on roll over next to the node as well
nodeEnter.append("text")
.attr("class", "nodetext")
.attr("x", x_browser)
.attr("y", y_browser +15)
.attr("fill", "red")
.text(function(d) { return d.name; });
// Exit any old nodes.
node.exit().remove();
// Re-select for update.
path = vis.selectAll("path.link");
node = vis.selectAll("g.node");
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + ","
+ d.source.y
+ "A" + dr + ","
+ dr + " 0 0,1 "
+ d.target.x + ","
+ d.target.y;
});
node.attr("transform", nodeTransform);
}
}
function nodeTransform(d) {
d.x = Math.max(maxNodeSize, Math.min(w - (d.imgwidth/2 || 16), d.x));
d.y = Math.max(maxNodeSize, Math.min(h - (d.imgheight/2 || 16), d.y));
return "translate(" + d.x + "," + d.y + ")";
}
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update();
}
/**
* Returns a list of all nodes under the root.
*/
function flatten(root) {
var nodes = [];
var i = 0;
function recurse(node) {
if (node.children)
node.children.forEach(recurse);
if (!node.id)
node.id = ++i;
nodes.push(node);
}
recurse(root);
return nodes;
}
I'm using the above code to display image using d3.js. But the image is not showing as circle. I want the child node images to fill in the circle properly.
So please help to to fix this. Answers will be appreciated. Thanks
This is the example I use: d3 | Force layout with images
var marvelJson={
"name": "marvel",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/marvel.png",
"children": [
{
"name": "Heroes",
"children": [
{
"hero": "Spider-Man",
"name": "Peter Benjamin Parker",
"link": "http://marvel.com/characters/54/spider-man",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/top_spiderman.png",
"size": 40000
},
{
"hero": "CAPTAIN MARVEL",
"name": "Carol Danvers",
"link": "http://marvel.com/characters/9/captain_marvel",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/top_captainmarvel.png",
"size": 40000
},
{
"hero": "HULK",
"name": "Robert Bruce Banner",
"link": "http://marvel.com/characters/25/hulk",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/top_hulk.png",
"size": 40000
},
{
"hero": "Black Widow",
"name": "Natalia 'Natasha' Alianovna Romanova",
"link": "http://marvel.com/characters/6/black_widow",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/top_blackwidow.png",
"size": 40000
},
{
"hero": "Daredevil",
"name": "Matthew Michael Murdock",
"link": "http://marvel.com/characters/11/daredevil",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/top_daredevil.png",
"size": 40000
},
{
"hero": "Wolverine",
"name": "James Howlett",
"link": "http://marvel.com/characters/66/wolverine",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/top_wolverine.png",
"size": 40000
},
{
"hero": "Captain America",
"name": "Steven Rogers",
"link": "http://marvel.com/characters/8/captain_america",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/top_captainamerica.png",
"size": 40000
},
{
"hero": "Iron Man",
"name": "Anthony Edward 'Tony' Stark",
"link": "http://marvel.com/characters/29/iron_man",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/top_ironman.png",
"size": 40000
},
{
"hero": "THOR",
"name": "Thor Odinson",
"link": "http://marvel.com/characters/60/thor",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/top_thor.png",
"size": 40000
}
]
},
{
"name": "Villains",
"children": [
{
"hero": "Dr. Doom",
"name": "Victor von Doom",
"link": "http://marvel.com/characters/13/dr_doom",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/drdoom.png",
"size": 40000
},
{
"hero": "Mystique",
"name": "Unrevealed",
"link": "http://marvel.com/characters/1552/mystique",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/mystique.png",
"size": 40000
},
{
"hero": "Red Skull",
"name": "Johann Shmidt",
"link": "http://marvel.com/characters/1901/red_skull",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/redskull.png",
"size": 40000
},
{
"hero": "Ronan",
"name": "Ronan",
"link": "http://marvel.com/characters/49/ronan",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/ronan.png",
"size": 40000
},
{
"hero": "Magneto",
"name": "Max Eisenhardt",
"link": "http://marvel.com/characters/35/magneto",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/magneto.png",
"size": 40000
},
{
"hero": "Thanos",
"name": "Thanos",
"link": "http://marvel.com/characters/58/thanos",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/thanos.png",
"size": 40000
},
{
"hero": "Black Cat",
"name": "Felicia Hardy",
"link": "http://marvel.com/characters/271/black_cat",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/blackcat.png",
"size": 40000
}
]
},
{
"name": "Teams",
"children": [
{
"hero": "Avengers",
"name": "",
"link": "http://marvel.com/characters/68/avengers",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/avengers.png",
"size": 40000
},
{
"hero": "Guardians of the Galaxy",
"name": "",
"link": "http://marvel.com/characters/70/guardians_of_the_galaxy",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/gofgalaxy.png",
"size": 40000
},
{
"hero": "Defenders",
"name": "",
"link": "http://marvel.com/characters/534/defenders",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/defenders.png",
"size": 40000
},
{
"hero": "X-Men",
"name": "",
"link": "http://marvel.com/characters/71/x-men",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/xmen.png",
"size": 40000
},
{
"hero": "Fantastic Four",
"name": "",
"link": "http://marvel.com/characters/69/fantastic_four",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/fantasticfour.png",
"size": 40000
},
{
"hero": "Inhumans",
"name": "",
"link": "http://marvel.com/characters/1040/inhumans",
"img": "https://dl.dropboxusercontent.com/u/19954023/marvel_force_chart_img/inhumans.png",
"size": 40000
}
]
}
]
}
/*
var imgurl = "http://wallpapers.androlib.com/wallicons/wallpaper.big-pqC.cs.png"
var margin = {top: 20, right: 10, bottom: 20, left: 10};
var width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var svg = d3.select("body").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 defs = svg.append("defs").attr("id", "imgdefs")
var catpattern = defs.append("pattern")
.attr("id", "catpattern")
.attr("height", 1)
.attr("width", 1)
.attr("x", "0")
.attr("y", "0")
catpattern.append("image")
.attr("x", -130)
.attr("y", -220)
.attr("height", 640)
.attr("width", 480)
.attr("xlink:href", imgurl)
svg.append("circle")
.attr("r", 100)
.attr("cy", 80)
.attr("cx", 120)
.attr("fill", "url(#catpattern)")
*/
// some colour variables
var tcBlack = "#130C0E";
// rest of vars
var w = 960,
h = 800,
maxNodeSize = 50,
x_browser = 20,
y_browser = 25,
root;
var vis;
var force = d3.layout.force();
vis = d3.select("#vis").append("svg").attr("width", w).attr("height", h);
//d3.json("marvel.json", function(json) {
var json = marvelJson;
root = json;
root.fixed = true;
root.x = w / 2;
root.y = h / 4;
// Build the path
var defs = vis.insert("svg:defs")
.data(["end"]);
defs.enter().append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
try{
update();
}catch(e){
console.log(e);
}
//});
/**
*
*/
function update() {
var nodes = flatten(root),
links = d3.layout.tree().links(nodes);
// Restart the force layout.
force.nodes(nodes)
.links(links)
.gravity(0.05)
.charge(-1500)
.linkDistance(100)
.friction(0.5)
.linkStrength(function(l, i) {return 1; })
.size([w, h])
.on("tick", tick)
.start();
var path = vis.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
path.enter().insert("svg:path")
.attr("class", "link")
// .attr("marker-end", "url(#end)")
.style("stroke", "#eee");
// Exit any old paths.
path.exit().remove();
// Update the nodes…
var node = vis.selectAll("g.node")
.data(nodes, function(d) { return d.id; });
// Enter any new nodes.
var nodeEnter = node.enter().append("svg:g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.on("click", click)
.call(force.drag);
/*Dinesh Code*/
/*
var defs = svg.append("defs").attr("id", "imgdefs")
var catpattern = defs.append("pattern")
.attr("id", "catpattern")
.attr("height", 1)
.attr("width", 1)
.attr("x", "0")
.attr("y", "0")
catpattern.append("image")
.attr("x", -130)
.attr("y", -220)
.attr("height", 640)
.attr("width", 480)
.attr("xlink:href", imgurl)
svg.append("circle")
.attr("r", 100)
.attr("cy", 80)
.attr("cx", 120)
.attr("fill", "url(#catpattern)")
*/
/******* Dinesh Code End******/
nodeEnter.append("defs").attr("id", "imgdefs")
.append("pattern")
.attr("id", function(d){ if(d.img)return d.img.replace(/[/|.|:]/g, ""); else null; })
.attr("height", 1)
.attr("width", 1)
.attr("x", "0")
.attr("y", "0")
.append("image")
.attr("height", function(d) { return 2* Math.sqrt(d.size) / 10 || 4.5; })
.attr("width", function(d) { return 2* Math.sqrt(d.size) / 10 || 4.5; })
.attr("xlink:href", function(d){if(d.img)return d.img+""; else null; })
// Append a circle
var images = nodeEnter.append("svg:circle")
.attr("r", function(d) { return Math.sqrt(d.size) / 10 || 4.5; })
.attr("fill", function(d){ if(d.img)return "url(#"+d.img.replace(/[/|.|:]/g, "")+")" ; else null; });
// Append images
/* var images = nodeEnter.append("svg:image")
.attr("xlink:href", function(d) { return d.img;})
.attr("x", function(d) { return -25;})
.attr("y", function(d) { return -25;})
.attr("height", 50)
.attr("width", 50);*/
// make the image grow a little on mouse over and add the text details on click
var setEvents = images
// Append hero text
.on( 'click', function (d) {
d3.select("h1").html(d.hero);
d3.select("h2").html(d.name);
d3.select("h3").html ("Take me to " + "<a href='" + d.link + "' >" + d.hero + " web page ⇢"+ "</a>" );
})
.on( 'mouseenter', function(d) {
if(d.img){
//console.log(d3.select(this).parent);
d3.select("pattern#"+d.img.replace(/[/|.|:]/g, "")).select("image").attr("width","100").attr("height","100");
// select element in current context
d3.select( this )
.transition()
.attr("x", function(d) { return -60;})
.attr("y", function(d) { return -60;})
.attr("r", "50")
//.attr("height", 100)
//.attr("width", 100);
}
})
// set back
.on( 'mouseleave', function(d) {
if(d.img){
d3.select("pattern#"+d.img.replace(/[/|.|:]/g, "")).select("image").attr("width","40").attr("height","40");
d3.select( this )
.transition()
.attr("x", function(d) { return -25;})
.attr("y", function(d) { return -25;})
.attr("r", "20")
//.attr("height", 50)
//.attr("width", 50);
}
});
// Append hero name on roll over next to the node as well
nodeEnter.append("text")
.attr("class", "nodetext")
.attr("x", x_browser)
.attr("y", y_browser +15)
.attr("fill", tcBlack)
.text(function(d) { return d.hero; });
// Exit any old nodes.
node.exit().remove();
// Re-select for update.
path = vis.selectAll("path.link");
node = vis.selectAll("g.node");
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + ","
+ d.source.y
+ "A" + dr + ","
+ dr + " 0 0,1 "
+ d.target.x + ","
+ d.target.y;
});
node.attr("transform", nodeTransform);
}
}
/**
* Gives the coordinates of the border for keeping the nodes inside a frame
* http://bl.ocks.org/mbostock/1129492
*/
function nodeTransform(d) {
d.x = Math.max(maxNodeSize, Math.min(w - (d.imgwidth/2 || 16), d.x));
d.y = Math.max(maxNodeSize, Math.min(h - (d.imgheight/2 || 16), d.y));
return "translate(" + d.x + "," + 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();
}
/**
* Returns a list of all nodes under the root.
*/
function flatten(root) {
var nodes = [];
var i = 0;
function recurse(node) {
if (node.children)
node.children.forEach(recurse);
if (!node.id)
node.id = ++i;
nodes.push(node);
}
recurse(root);
return nodes;
}
#import url(http://fonts.googleapis.com/css?family=Source+Code+Pro:400,600);
body {font-family: "Source Code Pro", Consolas, monaco, monospace; line-height: 160%; font-size: 16px; margin: 0; }
path.link {
fill: none;
stroke-width: 2px;
}
.node:not(:hover) .nodetext {
display: none;
}
h1 { font-size: 36px; margin: 10px 0; text-transform: uppercase; font-weight: normal;}
h2, h3 { font-size: 18px; margin: 5px 0 ; font-weight: normal;}
header {padding: 20px; position: absolute; top: 0; left: 0;}
a:link { color: #EE3124; text-decoration: none;}
a:visited { color: #EE3124; }
a:hover { color: #A4CD39; text-decoration: underline;}
a:active { color: #EE3124; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.0/d3.min.js"></script>
<section id="vis"></section>
After working for few hours I made something out of it, kindly observe the code changes I've made, If you can't figure it out, comment below, I'll edit my answer with full explanation.
:D
Related
I'm trying to build a tree using d3js. I'm having a 2 sided tree with a root in the center. I am showing parents towards the right of the root and showing children towards the left of the root. here is my code.
var data = {
"name": "Root",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png",
"children": [{
"name": "3",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "4",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}],
"parent": [{
"name": "1",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "2",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}]
};
var bgColors = ['#fd90b5', '#6ca1e9', '#fa975c', '#eb7092', '#f88962', '#a094ed', '#7f8de1'];
var dr = 0;
// Left data
var data1 = {
"name": data.name,
"children": JSON.parse(JSON.stringify(data.children))
};
// Right data
var data2 = {
"name": data.name,
"children": JSON.parse(JSON.stringify(data.parent))
};
// Create d3 hierarchies
var right = d3.hierarchy(data1);
var left = d3.hierarchy(data2);
// Render both trees
drawTree(right, "right")
drawTree(left, "left")
// draw single tree
function drawTree(root, pos) {
var refType;
if (pos == 'left')
refType = 'left';
else
refType = 'right';
var SWITCH_CONST = 1;
if (pos === "left") {
SWITCH_CONST = -1;
}
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height")
var g = svg.append("g").attr("transform", "translate(" + width / 2 + ",0)");
var tree = d3.tree()
.size([height, SWITCH_CONST * (width - 150) / 2]);
tree(root)
var nodes = root.descendants();
var links = root.links();
nodes[0].x = height / 2
// Create links
var link = g.selectAll(".link")
.data(links)
.enter()
link.append("path")
.attr("class", "link")
.attr("d", function (d) {
//first return returns a curve and the second will return straight lines in
//return "M" + d.target.y + "," + d.target.x + "C" + (d.target.y + d.source.y) / 2.5 + "," + d.target.x + " " + (d.target.y + d.source.y) / 2 + "," + d.source.x + " " + d.source.y + "," + d.source.x;
return "M" + d.target.y + "," + d.target.x + "A" + dr + "," + dr + " 1,0 0 " + d.source.y + "," + d.source.x;
});
link.append("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("transform", function (d) {
return "translate(" +
((d.source.y + d.target.y) / 2) + "," +
((d.source.x + d.target.x) / 2) + ")";
})
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.data(nodes)
.text(refType);
// Create nodes
var node = g.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", function (d) {
return "node" + (d.children ? " node--internal" : " node--leaf");
})
.attr("transform", function (d) {
return "translate(" + d.y + "," + d.x + ")";
})
node.append('circle')
.attr('class', 'icon-wrap')
.attr('x', 0)
.attr('y', 0)
.attr('r', 25)
.style('fill', 'black');
node.append('image')
.attr('href', d => d.data.img)
.attr('x', '-25')
.attr('y', '-25')
.attr('height', '50')
.attr('width', '50');
node.append("text")
.attr("dy", 45)
.style("text-anchor", "middle")
.text(d => d.data.name);
}
.node circle {
fill: #999;
}
.node text {
font: 12px sans-serif;
}
.node--internal circle {
fill: #555;
}
.link {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg width="800" height="550"></svg>
Here my issue is that I'm unable to get the logo seen in the root node.
Thanks
Despite the img property existing in the data object, it is missing in the two objects you're actually passing to d3.hierarchy(), which are data1 and data2. Therefore, it should be:
var data1 = {
"name": data.name,
"img": data.img,
"children": JSON.parse(JSON.stringify(data.children))
};
The same for data2. Here is your code with those 2 changes:
var data = {
"name": "Root",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png",
"children": [{
"name": "3",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "4",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}],
"parent": [{
"name": "1",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "2",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}]
};
var bgColors = ['#fd90b5', '#6ca1e9', '#fa975c', '#eb7092', '#f88962', '#a094ed', '#7f8de1'];
var dr = 0;
// Left data
var data1 = {
"name": data.name,
"img": data.img,
"children": JSON.parse(JSON.stringify(data.children))
};
// Right data
var data2 = {
"name": data.name,
"img": data.img,
"children": JSON.parse(JSON.stringify(data.parent))
};
// Create d3 hierarchies
var right = d3.hierarchy(data1);
var left = d3.hierarchy(data2);
// Render both trees
drawTree(right, "right")
drawTree(left, "left")
// draw single tree
function drawTree(root, pos) {
var refType;
if (pos == 'left')
refType = 'left';
else
refType = 'right';
var SWITCH_CONST = 1;
if (pos === "left") {
SWITCH_CONST = -1;
}
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height")
var g = svg.append("g").attr("transform", "translate(" + width / 2 + ",0)");
var tree = d3.tree()
.size([height, SWITCH_CONST * (width - 150) / 2]);
tree(root)
var nodes = root.descendants();
var links = root.links();
nodes[0].x = height / 2
// Create links
var link = g.selectAll(".link")
.data(links)
.enter()
link.append("path")
.attr("class", "link")
.attr("d", function(d) {
//first return returns a curve and the second will return straight lines in
//return "M" + d.target.y + "," + d.target.x + "C" + (d.target.y + d.source.y) / 2.5 + "," + d.target.x + " " + (d.target.y + d.source.y) / 2 + "," + d.source.x + " " + d.source.y + "," + d.source.x;
return "M" + d.target.y + "," + d.target.x + "A" + dr + "," + dr + " 1,0 0 " + d.source.y + "," + d.source.x;
});
link.append("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("transform", function(d) {
return "translate(" +
((d.source.y + d.target.y) / 2) + "," +
((d.source.x + d.target.x) / 2) + ")";
})
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.data(nodes)
.text(refType);
// Create nodes
var node = g.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", function(d) {
return "node" + (d.children ? " node--internal" : " node--leaf");
})
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
})
node.append('circle')
.attr('class', 'icon-wrap')
.attr('x', 0)
.attr('y', 0)
.attr('r', 25)
.style('fill', 'black');
node.append('image')
.attr('href', d => d.data.img)
.attr('x', '-25')
.attr('y', '-25')
.attr('height', '50')
.attr('width', '50');
node.append("text")
.attr("dy", 45)
.style("text-anchor", "middle")
.text(d => d.data.name);
}
.node circle {
fill: #999;
}
.node text {
font: 12px sans-serif;
}
.node--internal circle {
fill: #555;
}
.link {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg width="800" height="550"></svg>
var treeData = [
{
"name": "glucose_tol",
"directions": ">",
"thresholds": "126",
"exits": 0.0,
"children": [
{
"name": "age",
"directions": ">",
"thresholds": "29",
"exits": 1.0,
"children": [
{
"name": true
},
{
"name": "mass_index",
"directions": ">",
"thresholds": "29.7",
"exits": 0.5,
"children": [
{
"name": true
},
{
"name": false
}
]
}
]
},
{
"name": false
}
]
},
];
// ************** 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;
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 + ")");
root = treeData[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.
nodes.forEach(function(d) { d.y = d.depth * 120; });
// Declare the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter the nodes.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
nodeEnter.append("circle")
.attr("r", 10)
.style("fill", "#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", 1);
// Declare the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter the links.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", diagonal);
// Add threshold and directions
link.enter().insert("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("transform", function(d) {
return "translate(" +
((d.source.x + d.target.x)/2) + "," +
((d.source.y + d.target.y)/2) + ")";
})
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
//check whether thresholds is not undefined && that target.thresholds is not undefined as it will print on both sides
if (d.source.thresholds !== undefined)
if(d.target.thresholds !== undefined)
return d.source.thresholds + ' ' + d.source.directions;
})
}
.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.5.17/d3.min.js"></script>
I am having a problem adding text in middle of the links to a tree, because the last level of the tree doesn't give me any text. This is the code I have to add the text in the links:
// Add threshold and directions
link.enter().insert("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("transform", function(d) {
return "translate(" +
((d.source.x + d.target.x)/2) + "," +
((d.source.y + d.target.y)/2) + ")";
})
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
//check whether thresholds is not undefined && that target.thresholds is not undefined as it will print on both sides
if (d.source.thresholds !== undefined)
if(d.target.thresholds !== undefined)
return d.source.thresholds + ' ' + d.source.directions;
})
if I comment these two lines : if (d.source.thresholds !== undefined) and if(d.target.thresholds !== undefined) then I get the text on all the links but on both sides, which I don't want. How do I get the text only on one side but also on the last level.
First of all you need to have a way to find out whether the current node is on the last level, or if there are more levels under it. For this we will implement a maxDepth variable that will store a maximum value of depth attribute for all nodes. This is done in the update(root) function:
// Normalize for fixed-depth.
var maxDepth = 0;
nodes.forEach(function(d) { d.y = d.depth * 120; maxDepth = (d.depth>maxDepth)?d.depth:maxDepth;});
Next we add two additional checks to the second if statement like this:
if(d.target.thresholds !== undefined || d.target.name === true && d.target.depth == maxDepth)
It will make sure the text is added to true node only on the last level.
var treeData = [
{
"name": "glucose_tol",
"directions": ">",
"thresholds": "126",
"exits": 0.0,
"children": [
{
"name": "age",
"directions": ">",
"thresholds": "29",
"exits": 1.0,
"children": [
{
"name": true
},
{
"name": "mass_index",
"directions": ">",
"thresholds": "29.7",
"exits": 0.5,
"children": [
{
"name": true
},
{
"name": false
}
]
}
]
},
{
"name": false
}
]
},
];
// ************** 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;
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 + ")");
root = treeData[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 maxDepth = 0;
nodes.forEach(function(d) { d.y = d.depth * 120; maxDepth = (d.depth>maxDepth)?d.depth:maxDepth;});
// Declare the nodes.
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter the nodes.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
nodeEnter.append("circle")
.attr("r", 10)
.style("fill", "#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", 1);
// Declare the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter the links.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", diagonal);
// Add threshold and directions
link.enter().insert("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("transform", function(d) {
return "translate(" +
((d.source.x + d.target.x)/2) + "," +
((d.source.y + d.target.y)/2) + ")";
})
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
//check whether thresholds is not undefined && that target.thresholds is not undefined as it will print on both sides
if (d.source.thresholds !== undefined)
if(d.target.thresholds !== undefined || d.target.name === true && d.target.depth == maxDepth)
return d.source.thresholds + ' ' + d.source.directions;
})
}
.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.5.17/d3.min.js"></script>
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>
I want to add Donut chart in each node of D3 tree chart (replace circle node in tree with Donut Chart)
Below is my Code. I want to replace the circle node with Donut chart in each node with different data that sholud be read from sample json.
// Sample JSON Data
var jsondata = {
"name": "CERT",
"children": [{
"name": ""
}, {
"name": ""
}, {
"name": "DNS",
"children": [{
"name": "FW",
"children": [{
"name": ""
}, {
"name": ""
}, {
"name": "ADC",
"children": [{
"name": ""
}, {
"name": ""
}, {
"name": "SERVERS",
"children": [{
"name": ""
}, {
"name": ""
}]
}]
}]
}]
}, {
"name": "FW",
"children": [{
"name": ""
}, {
"name": ""
}, {
"name": "DNS",
"children": [{
"name": ""
}, {
"name": ""
}, {
"name": "ADC",
"children": [{
"name": "SERVERS"
}]
}]
}]
}]
};
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
//width = 960 - margin.right - margin.left, height = 800 - margin.top - margin.bottom;
width = screen.width;
height = 800 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.separation(function(a, b) {
return a.parent === b.parent ? 1 : 2;
})
.children(function(d) {
return d.children;
})
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var line = d3.svg.line()
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
});
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zm = d3.behavior.zoom().scaleExtent([1, 13]).on("zoom", redraw)).append("g")
.attr("transform", "translate(" + 350 + "," + 20 + ")");;
// put JSON data to root variable
root = jsondata;
root.x0 = height / 2;
root.y0 = 0;
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([350, 20]);
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)
.on("mouseover", function(d) {
var g = d3.select(this); // The node
// The class is used to remove the additional text later
var info = g.append('text')
.classed('info', true)
.attr('x', 30)
.attr('y', 10)
.text(function(d) {
return d.name;
});
})
// Remove the info text on mouse out.
.on("mouseout", function() {
d3.select(this).select('text.info').remove();
});
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 ? -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) {
return d.children ? 20 : 10;
})
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// 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) {
return line([{
x: d.source.y,
y: d.source.x
}, {
x: d.target.y,
y: d.target.x
}]);
});
// Transition links to their new position.
link.transition()
//.duration(900)
.attr("d", function(d) {
return line([{
x: d.source.y,
y: d.source.x
}, {
x: d.target.y,
y: d.target.x
}]);
});
// Transition exiting nodes to the parent's new position.
link.exit().transition()
// .duration(duration)
.attr("d", function(d) {
return line([{
x: d.source.y,
y: d.source.x
}, {
x: d.target.y,
y: d.target.x
}]);
})
.remove();
// 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);
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
function elbow(d, i) {
return "M" + d.source.y + "," + d.source.x + "H" + d.target.y + "V" + d.target.x + (d.target.children ? "" : "h" + margin.right);
}
// 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);
}
//redraw graph after zoom
function redraw() {
svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
}
.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;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.3/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
When I try to layout my force-directed graph, the following is the error I receive. I read about this issue in Mike Bostock's github page and found that it might be due to NaN values of coordinates or all the points drawn at the same point. I checked into the console and I found that all of my points are getting drawn at the same X and Y values.
On an example data, the code worked perfectly well but no more now. My data goes like 45-50 levels away from the center node. I have successfully made a tree layout with this data. Wanted to try the force directed layout but it did not work.
Any help regarding how to draw the nodes on separate coordinates would be much appreciated.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 10, right: 10, bottom: 20, left: 40},
width = 1300 - margin.left - margin.right,
height = 800 - margin.top - margin.bottom;
var color = d3.scale.category20();
var force = d3.layout.force().charge(-120)
.linkDistance(30)
.size([width, height]);
var svg = d3.select("body").append("svg:svg")
.attr("width", width)
.attr("height", height)
//.attr("pointer-events","all")
.append('svg:g')
//.call(d3.behavior.zoom().translate([100,50]).scale(.5).on("zoom",redraw))
.append('svg:g')
.attr("transform","translate(100,50)scale(.5,.5)");
svg.append('svg:rect')
.attr('width', width)
.attr('height', height)
.attr('fill','white')
.attr('opacity',0);
function redraw() {
var trans = d3.event.translate;
var scale = d3.event.scale;
svg.attr("transform",
"translate(" + trans + ")"
+ " scale(" + scale + ")");
};
d3.json("test_data.json", function(error, graph) {
var nodeMap = {};
graph.nodes.forEach(function(x) { nodeMap[x.name] = x; });
graph.links = graph.links.map(
function(x)
{
return {
source: nodeMap[x.source],
target: nodeMap[x.target],
value: x.value
};
});
console.log(graph.nodes);
console.log(graph.links);
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke", function(d) { return color(d.value); })
.style("stroke-width", function(d) {
//console.log(d.value);
return Math.sqrt(d.value); });
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.value); })
.call(force.drag)
.on("mousedown",
function(d) {
d.fixed = true;
d3.select(this).classed("sticky", true);
}
)
.on("mouseover",fade(0.1))
.on("mouseout",fade(1));
var linkedByIndex = {};
graph.links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
}
node.append("title")
.text(function(d) {
return "Name : "+d.name+"\n"+"Parent: "+d.parent +"\n"+"Relationship: "+ d.relationship +"\n"+ "Creation Date: "+ d.cod +"\n"; });
function fade(opacity)
{
return function(d) {
node.style("stroke-opacity", function(o) {
thisOpacity = isConnected(d, o) ? 1 : opacity;
this.setAttribute('fill-opacity', thisOpacity);
return thisOpacity;
});
link.style("stroke-opacity", opacity).style("stroke-opacity", function(o) {
return o.source === d || o.target === d ? 1 : opacity;
});
};
}
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 + ")"; });
/*node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });*/
});
});
</script>
The JSON looks like:
{
"links": [
{
"source": "Me",
"target": "Adam",
"value": 10
},
{
"source": "Me",
"target": "You",
"value": 10
}
], "nodes": [
{
"ancestor": "Adam",
"cod": 19061964,
"distance": 0,
"name": "Adam",
"parent": null,
"relationship": null,
"value": 10
},
{
"ancestor": "Adam",
"cod": 13032003,
"distance": 1,
"name": "Me",
"parent": "You",
"relationship": "Father",
"value": 10
}
]
}
EDIT: I get the error in the following statement:
force
.nodes(graph.nodes)
.links(graph.links)
.start();
highlighting:
"start();"
In your data your links are to names as opposed to indices. Check out the example data from http://bl.ocks.org/mbostock/4062045.
Links have the form: {"source":1,"target":0,"value":1}, which points to the index of the node.