Related
I'm working on a sankey diagram that updates whenever I change the filter (radiobuttons) using d3.js and a sankey-plugin. Now I'm trying to add a feature that, whenever I hover over one of the path, I append a linear gradient to the path going from the color of the source node to the color of the target node. If I don't use the filter everything works fine, however the gradient-coloring doesn't work if I apply the filter (colors are set wrong) because the links are transitioned. I think that I have to transition the linear-gradient somehow, but I don't understand how I have to do this.
I wrote a little script that shows the problem, before clicking the button colors are correct, and after it's messed up.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-sankey#0.6"></script>
</head>
<body>
<svg id="diagram" height="150" width="600"></svg>
<button onclick="updateSankey()">Click Me!</button>
<style>
#diagram{
border: 1px solid black;
}
</style>
<script>
var target = 0;
var sankeyLinks;
var sankeyData = {nodes:[], links:[]};
calculateLinks();
initSankey();
updateSankey();
function initSankey() {
/*simple initialisation of the sankey, should explain itself*/
svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
formatNumber = d3.format(",.0f"),
format = function (d) { return formatNumber(d) + " %"; },
color = d3.scaleOrdinal(d3.schemeCategory10);
sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.extent([[1, 1], [width - 1, height - 6]])
.iterations(0);
t = d3.transition()
.duration(1500)
.ease(d3.easeLinear);
//set attributes for all links
titleGroup = svg.append("g")
.attr("class", "titles")
.attr("font-family", "sans-serif")
.attr("font-size", "150%");
diagram= svg.append("g")
.attr("class", "sankey")
// .attr("transform", "translate(" + marginleft + "," + margintop + ")");
linkGroup = diagram.append("g")
.attr("class", "links")
.attr("fill", "none");
//.attr("stroke", "#000")
//.attr("stroke-opacity", 0.2);
//set attributes for all nodes
nodeGroup = diagram.append("g")
.attr("class", "nodes")
.attr("font-family", "sans-serif")
.attr("font-size", 10);
}
function calculateLinks() {
if(target == 0)
{
target = 1;
sankeyLinks = [{source:0, target:1, value:5},{source:0, target:2, value:10},{source:0, target:3, value:15}];
}
else
{
target = 0;
sankeyLinks = [{source:0, target:2, value:15},{source:0, target:1, value:20},{source:0, target:3, value:10}];
}
}
function updateSankey() {
calculateLinks();
sankeyData.links = sankeyLinks;
sankeyData.nodes = [{name: "first"}, {name:"second"}, {name:"third"}, {name: "fourth"}];
sankey(sankeyData);
var links = linkGroup.selectAll('path')
.data(sankeyData.links);
//Set attributes for each link separately
links.enter().append("g")
.attr("id",function (d,i) {return "path"+i;})
.append("path")
.attr("stroke", "#000")
.attr("stroke-opacity", 0.15)
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function (d) {return Math.max(1, d.width); })
.on("mouseover",function (d,id) {
var pathGroup = svg.select('#path' + id);
var path = pathGroup.select("path");
path.attr("stroke","url(#grad"+id+")")
.attr("stroke-opacity","0.95");
})
.on("mouseout",function (d, id) {
pathGroup = svg.select('#path' + id);
var path = pathGroup.select("path");
path.attr("stroke","#000")
.attr("stroke-opacity","0.15");
})
.append("title")
.text(function (d) {
//tooltip info for the links
return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
var pathGradient = svg.select(".links")
.selectAll("g")
.append("defs")
.append("linearGradient")
.attr("id",function (d, id) {
return "grad" + id;
})
//.attr("from", function () {return this.parentElement.parentElement.childNodes[0].getAttribute("from");})
//.attr("to", function () {return this.parentElement.parentElement.childNodes[0].getAttribute("to");})
.attr("gradientUnit","userSpaceOnUse")
.attr("style","mix-blend-mode: multiply;")
.attr("x1","0%")
.attr("x2","100%")
.attr("y1","0%")
.attr("y2","0%");
pathGradient.append("stop")
.attr("class","from")
.attr("offset","0%")
.attr("style", function (d) {
var color = setColor(d.source);
return "stop-color:" + color + ";stop-opacity:1";
});
pathGradient.append("stop")
.attr("class","to")
.attr("offset","100%")
.attr("style",function (d) {
var color = setColor(d.target);
return "stop-color:" + color + ";stop-opacity:1";
});
links.transition(t)
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function (d) { return Math.max(1, d.width); })
.select('title')
.text(function (d) {
//same argumentation as above, we need the method again for the transition
return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
links.exit().remove();
var nodes = nodeGroup.selectAll('.node')
.data(sankeyData.nodes);
var nodesEnter = nodes.enter()
.append("g")
.attr('class', 'node');
//set attributes for each node separately
nodesEnter.append("rect")
.attr("x", function (d) { return d.x0; })
.attr("y", function (d) { return d.y0; })
.attr("height", function (d) { return d.y1 - d.y0; })
.attr("width", function (d) {
var width = d.x1 - d.x0;
return width;
})
.attr("fill", setColor)
.attr("stroke", "#000")
.attr("fill-opacity", 0.5)
//specify Pop-Up when hovering over node
nodesEnter.append("title")
.text(function (d) { return d.name + "\n" + format(d.value); });
//Update selection
var nodesUpdate = nodes.transition(t);
//same as the links we have to state the methods again in the update
nodesUpdate.select("rect")
.attr("y", function (d) { return d.y0; })
.attr("x", function (d) { return d.x0; })
.attr("height", function (d) { return d.y1 - d.y0; });
nodesUpdate.select("title")
.text(function (d) { return d.name + "\n" + format(d.value); });
//Exit selection
nodes.exit().remove();
}
function setColor(d) {
switch (d.name) {
case "first":
return "#f00";
case "second":
return "#ff0";
case "third":
return "#f0f";
case "fourth":
return "#0ff";
default:
return "#0f0";
}
}
</script>
</body>
</html>
After clicking the button once, the path from the red to the purple node has a linear gradient from red to yellow, even though I'd like it to go from red to purple.
I already realised, that I could write e.g. .iterations(15) instead of .iterations(0) in initSankey() to fix this. In the actual project I can't do this since I have to force the order of the nodes.
I hope I am clear enough in my explanation, if not, feel free to ask.
I would be extremely pleased if someone could tell me how to fix this problem.
PS. in this snippet the link on top disappears on hover, I have fixed this in the real project, here it's not a big deal.
Your issue was that the gradient urls were based on i, which could be different for a particular link with each update (ie, the order of the links may be different, so have a different i value); and data updates were not based on a constant unique id for a link.
In the snippet, I've added a unique name value for the links in the calculateLinks function, which is then used for the data joins and creating the def gradients, which means they remain constant with each update.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-sankey"></script>
</head>
<body>
<svg id="diagram" height="150" width="600"></svg>
<button onclick="updateSankey()">Click Me!</button>
<style>
#diagram{
border: 1px solid black;
}
</style>
<script>
var target = 0;
var sankeyLinks;
var sankeyData = {nodes:[], links:[]};
calculateLinks();
initSankey();
updateSankey();
function initSankey() {
svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
formatNumber = d3.format(",.0f"),
format = function (d) { return formatNumber(d) + " %"; },
sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.size([width - 1, height - 6])
t = d3.transition()
.duration(1500)
.ease(d3.easeLinear);
//set attributes for all links
titleGroup = svg.append("g")
.attr("class", "titles")
.attr("font-family", "sans-serif")
.attr("font-size", "150%");
diagram= svg.append("g")
.attr("class", "sankey")
svg.append("defs")
linkGroup = diagram.append("g")
.attr("class", "links")
.attr("fill", "none");
//set attributes for all nodes
nodeGroup = diagram.append("g")
.attr("class", "nodes")
.attr("font-family", "sans-serif")
.attr("font-size", 10);
}
function calculateLinks() {
if(target == 0)
{
target = 1;
sankeyLinks = [
{name: "firstsecond",source:0, target:1, value:5},
{name: "firstthird",source:0, target:2, value:10},
{name: "firstfourth",source:0, target:3, value:15}];
}
else
{
target = 0;
sankeyLinks = [
{name: "firstthird", source:0, target:2, value:15},
{name: "firstsecond", source:0, target:1, value:20},
{name: "firstfourth", source:0, target:3, value:10}
];
}
}
function updateSankey() {
calculateLinks();
sankeyData.links = sankeyLinks;
sankeyData.nodes = [{name: "first"}, {name:"second"}, {name:"third"}, {name: "fourth"}];
sankey(sankeyData);
var pathGradient = svg.select("defs").selectAll("linearGradient")
.data(sankeyData.links, function(d){ return d.name })
.enter()
.append("linearGradient")
.attr("id",function (d) {
return "grad" + d.name;
})
.attr("gradientUnit","userSpaceOnUse")
.attr("x1","0%")
.attr("x2","100%")
.attr("y1","0%")
.attr("y2","0%");
pathGradient.append("stop")
.attr("class","from")
.attr("offset","0%")
.attr("style", function (d) {
var color = setColor(d.source);
return "stop-color:" + color;
});
pathGradient.append("stop")
.attr("class","to")
.attr("offset","100%")
.attr("style",function (d) {
var color = setColor(d.target);
return "stop-color:" + color;
});
var links = linkGroup.selectAll('path')
.data(sankeyData.links, function(d){ return d.name });
//Set attributes for each link separately
var linksenter = links.enter()
.append("g")
.attr("id",function (d) {return "path" + d.name;})
.append("path")
.style("stroke", "#000")
.style("stroke-opacity", 0.15)
.attr("stroke-width", function (d) {return Math.max(1, d.width); })
.on("mouseover",function (d) {
var pathGroup = svg.select('#path' + d.name);
var path = pathGroup.select("path");
path.style("stroke","url(#grad" + d.name + ")")
.style("stroke-opacity","0.95");
})
.on("mouseout",function (d, id) {
pathGroup = svg.select('#path' + d.source.name + d.target.name);
var path = pathGroup.select("path");
path.style("stroke","#000")
.style("stroke-opacity","0.15");
})
linksenter.merge(links).attr("d", d3.sankeyLinkHorizontal())
links.transition(t)
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function (d) { return Math.max(1, d.width); })
.select('title')
.text(function (d) {
return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
var nodes = nodeGroup.selectAll('.node')
.data(sankeyData.nodes, function(d){ return d.name });
var nodesEnter = nodes.enter()
.append("g")
.attr('class', 'node');
nodesEnter.append("rect")
.attr("x", function (d) { return d.x0; })
.attr("y", function (d) { return d.y0; })
.attr("height", function (d) { return d.y1 - d.y0; })
.attr("width", function (d) {
var width = d.x1 - d.x0;
return width;
})
.attr("fill", setColor)
.attr("stroke", "#000")
.attr("fill-opacity", 0.5)
//specify Pop-Up when hovering over node
nodesEnter.append("title")
.text(function (d) { return d.name + "\n" + format(d.value); });
//Update selection
var nodesUpdate = nodes.transition(t);
//same as the links we have to state the methods again in the update
nodesUpdate.select("rect")
.attr("y", function (d) { return d.y0; })
.attr("x", function (d) { return d.x0; })
.attr("height", function (d) { return d.y1 - d.y0; });
nodesUpdate.select("title")
.text(function (d) { return d.name + "\n" + format(d.value); });
}
function setColor(d) {
switch (d.name) {
case "first":
return "#f00";
case "second":
return "#ff0";
case "third":
return "#f0f";
case "fourth":
return "#0ff";
default:
return "#0f0";
}
}
</script>
</body>
</html>
I am creating a sankey diagram using D3. I am trying to redraw the diagram with additional node and link and using transition to animate the previous diagram to the new diagram. I was able to add in new node and link but the old nodes and links did not change position. Since the new node and link could be added at any place within the diagram, I do not want to clear and redraw the entire svg, but use transition to get from the old diagram to the new one. The code to draw the sankey diagram is this:
function draw(data){
// Set the sankey diagram properties
var sankey = d3sankey()
.nodeWidth(17)
.nodePadding(27)
.size([width, height]);
var path = sankey.link();
var graph = data;
sankey.nodes(graph.nodes)
.links(graph.links)
.layout(32);
sankey.relayout();
// add in the links
link.selectAll(".link")
.data(graph.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("fill", "none")
.style("stroke", function(d){
return "grey";
})
.style("stroke-opacity", "0.4")
.on("mouseover", function() { d3.select(this).style("stroke-opacity", "0.7") } )
.on("mouseout", function() { d3.select(this).style("stroke-opacity", "0.4") } )
.style("stroke-width", function (d) {
return Math.max(1, d.dy);
})
.sort(function (a, b) {
return b.dy - a.dy;
});
link.transition().duration(750);
//link.exit();
// add in the nodes
var node = nodes.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
// add the rectangles for the nodes
node.append("rect")
.attr("height", function (d) {
return d.dy;
})
.attr("width", sankey.nodeWidth())
.style("fill", function (d) {
return d.color = color(d.name.replace(/ .*/, ""));
})
.style("fill-opacity", ".9")
.style("shape-rendering", "crispEdges")
.style("stroke", function (d) {
return d3.rgb(d.color).darker(2);
})
.append("title")
.text(function (d) {
return d.name + "\n" + format(d.value);
});
// add in the title for the nodes
node.append("text")
.attr("x", -6)
.attr("y", function (d) {
return d.dy / 2;
})
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("text-shadow", "0 1px 0 #fff")
.attr("transform", null)
.text(function (d) {
return d.name;
})
.filter(function (d) {
return d.x < width / 2;
})
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
node.transition().duration(750);
}
The JSFiddle
Is it possible to use transition to add in new node and link and reposition
old nodes and links?
Thanks!
I was able to do this by using moving the nodes and links to new position. The code for that is:
var nodes = d3.selectAll(".node")
.transition().duration(750)
.attr('opacity', 1.0)
.attr("transform", function (d) {
if(d.node == 3){
console.log(d.x, d.y);
}
return "translate(" + d.x + "," + d.y + ")";
});
var nodeRects = d3.selectAll(".node rect")
.attr("height", function (d) {
if(d.node == 3){
console.log(d.dy);
}
return d.dy;
})
var links = d3.selectAll(".link")
.transition().duration(750)
.attr('d', path)
.attr('opacity', 1.0)
Updated JSFiddle
I am working on a d3js side project where I am using the force layout to show nodes and lines to represent a graph. I have laid out the code such that the graph can be dynamically updated. I am doing this by:
Clearing force.nodes() then force.links()
Pushing all the nodes I want to add to force.nodes()
Updating the graph (join, enter, update, exit)
Pushing all the links I want with references to the nodes in force.nodes() to force.links
Updating the graph (join, enter, update, exit)
This works as long as I only have nodes to display, but as soon as I attempt to push a link to force.links then all hell breaks loose and the two nodes end up hiding in the top left corner.
Looking at the DOM I can see the following:
As you can see, the transform/translate parameters contain NaN values. So something blew up in my face but after two days of bug hunting I suspect that I am missing something here and need to take a cold shower.
I have stripped down my code to the smallest set that can reproduce the error. Please note from it that the nodes display fine until a link is pushed into force.links. No links are being drawn, only nodes, but the act of pushing a link where it belongs is disrupting the data in the nodes. This way of updating a graph should work as per the examples I have seen.
d3.json("data/fm.json", function(error, graph) {
if (error) throw error;
function chart(elementName) {
// look for the node in the d3 layout
var findNode = function(name) {
for (var i in nodes) {
if (nodes[i]["name"] === name) return nodes[i];
};
};
var width = 960, // default width
height = 450, // default height
color = d3.scale.category10(),
force = d3.layout.force(),
nodes = force.nodes(),
links = force.links(),
vis,
runOnceFlag = true;
vis = d3.select(elementName)
.append("svg:svg")
.attr("width", width)
.attr("height", height)
.attr("id", "svg")
.attr("pointer-events", "all")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("perserveAspectRatio", "xMinYMid")
.append('svg:g');
var update = function() {
var node = vis.selectAll("g.node")
.data(nodes, function (d) {
return d.name;
});
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.call(force.drag);
nodeEnter.append("svg:circle")
.attr("r", 12)
.attr("id", function (d) {
return "Node;" + d.name;
})
.attr("class", "nodeStrokeClass")
.attr("fill", function(d) { return color(d.group); });
nodeEnter.append("svg:text")
.attr("class", "textClass")
.attr("x", 14)
.attr("y", ".31em")
.text(function (d) {
return d.name;
});
node.exit().remove();
force.on("tick", function () {
node.attr("transform", function (d) {
console.log(d);
return "translate(" + d.x + "," + d.y + ")";
});
});
// Restart the force layout.
force
.charge(-120)
.linkDistance( function(d) { return d.value * 10 } )
.size([width, height])
.start();
};
var a = graph.nodes[0];
var b = graph.nodes[1]
nodes.push(a);
update();
nodes.push(b);
update();
var c = {"source": findNode('a'), "target": findNode('b')}
// the line below causes the error
links.push(c);
update()
};
///
chart('body');
});
This is my data:
{
"nodes":[
{"name":"a", "group":1},
{"name":"b", "group":2},
{"name":"c", "group":3},
{"name":"d", "group":4},
{"name":"e", "group":5},
{"name":"f", "group":6},
{"name":"g", "group":7},
{"name":"h", "group":1},
{"name":"i", "group":2},
{"name":"j", "group":3},
{"name":"k", "group":4},
{"name":"l", "group":5},
{"name":"m", "group":6},
{"name":"n", "group":7}
],
"links":[
{"source":0,"target":1,"value":1},
{"source":2,"target":3,"value":1},
{"source":4,"target":5,"value":1},
{"source":7,"target":8,"value":1},
{"source":9,"target":10,"value":1},
{"source":11,"target":12,"value":1},
{"source":0,"target":5,"value":1},
{"source":1,"target":5,"value":1},
{"source":0,"target":6,"value":1},
{"source":1,"target":6,"value":1},
{"source":0,"target":7,"value":1},
{"source":1,"target":7,"value":1},
{"source":2,"target":8,"value":1},
{"source":3,"target":8,"value":1},
{"source":2,"target":9,"value":1},
{"source":3,"target":9,"value":1},
{"source":4,"target":11,"value":1},
{"source":5,"target":11,"value":1},
{"source":9,"target":12,"value":1},
{"source":10,"target":12,"value":1},
{"source":11,"target":13,"value":1},
{"source":12,"target":13,"value":1}
]
}
You have some basic problems with your code, see corrected concept below...
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<style>
.link {
stroke: #2E2E2E;
stroke-width: 2px;
}
.node {
stroke: #fff;
stroke-width: 2px;
}
.textClass {
stroke: #323232;
font-family: "Lucida Grande", "Droid Sans", Arial, Helvetica, sans-serif;
font-weight: normal;
stroke-width: .5;
font-size: 14px;
}
</style>
</head>
<body>
<!--<script src="d3 CB.js"></script>-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
d3.json("data.json", function (error, graph) {
if (error) throw error;
function chart(elementName) {
// look for the node in the d3 layout
var findNode = function (name) {
for (var i in nodes) {
if (nodes[i]["name"] === name) return nodes[i];
}
};
var width = 960, // default width
height = 450, // default height
color = d3.scale.category10(),
nodes = graph.nodes,
links = graph.links,
force = d3.layout.force()
.nodes(nodes)
.links([]),
vis,
runOnceFlag = true;
vis = d3.select(elementName)
.append("svg:svg")
.attr("width", width)
.attr("height", height)
.attr("id", "svg")
.attr("pointer-events", "all")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("perserveAspectRatio", "xMinYMid")
.append('svg:g');
var update = function () {
var link = vis.selectAll("line")
.data(force.links(), function (d) {
return d.source + "-" + d.target;
});
link.enter().insert("line", "g")
.attr("id", function (d) {
return d.source + "-" + d.target;
})
.attr("stroke-width", function (d) {
return d.value / 10;
})
.attr("class", "link")
.style("stroke", "red")
.transition().duration(5000).style("stroke", "black");
link.append("title")
.text(function (d) {
return d.value;
});
link.exit().remove();
var node = vis.selectAll("g.node")
.data(nodes, function (d) {
return d.name;
});
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.call(force.drag);
nodeEnter.append("svg:circle")
.attr("r", 12)
.attr("id", function (d) {
return "Node;" + d.name;
})
.attr("class", "nodeStrokeClass")
.attr("fill", function (d) {
return color(d.group);
});
nodeEnter.append("svg:text")
.attr("class", "textClass")
.attr("x", 14)
.attr("y", ".31em")
.text(function (d) {
return d.name;
});
node.exit().remove();
force.on("tick", function () {
link.attr("x1", function (d) {
return d.source.x;
})
.attr("y1", function (d) {
return d.source.y;
})
.attr("x2", function (d) {
return d.target.x;
})
.attr("y2", function (d) {
return d.target.y;
});
node.attr("transform", function (d) {
console.log(d);
return "translate(" + d.x + "," + d.y + ")";
});
});
// Restart the force layout.
force
.charge(-120)
.linkDistance(function (d) {
return d.value * 100
})
.size([width, height])
.start();
};
update();
var c = {"source": findNode('a'), "target": findNode('b'), value: 1}
// the line below causes the error
window.setTimeout(function() {
force.links().push(c);
update()
},2000)
};
//
chart('body');
});
</script>
</body>
</html>
I am very new to d3 and svg, this may sound very naive.
I have one force layout graph and one Circle pack and want to show them both on one page side by side. I went to the "Multiple layouts on same page (multiple force layouts same page)" questions but not able to understand how can I put these layout in div element.
This one is Force layout -
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<style>
h3{
border: 1px solid green ;
}
.link {
stroke: #666;
stroke-opacity: 0.1;
stroke-width: 1.5px;
}
.node circle {
stroke: #fff;
opacity: 0.9;
stroke-width: 1.5px;
}
.node:not(:hover) .nodetext {
display: none;
}
text {
font: 17px serif;
opacity: 0.9;
pointer-events: none;
fill : red;
}
</style>
<script src=http://d3js.org/d3.v3.min.js></script>
<script>
var links= [];
var nodes= [];
var width = 800
height = 400;
var color = d3.scale.category20();
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkStrength(0.1)
.linkDistance(150)
.charge(-150)
.friction(0.6)
.gravity(0.5);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("sample1.json", function(error, data) {
nodes = data.nodes;
links = data.links;
force
.nodes(nodes)
.links(links)
.on("tick", tick)
.start();
var link = svg.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(1); });
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.style("fill", "#7a85ec")
.style("opacity", 0.9)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click",clickf)
.call(force.drag);
node.append("circle")
.attr("r", function(d) { return Math.sqrt(3*d.weight); })
node.append("svg:text")
.attr("class", "nodetext")
.attr("dx", "1.35em")
.attr("dy", "-1.35em")
.text(function(d) { return d.name });
function tick() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
function mouseover(d) {
var circle = d3.select(this);
node
.transition(500)
.style("opacity", function(o) {
return isConnected(o, d) ? 1.0 : 0.1 ;
})
.style("fill", function(o){
if (isEqual(o, d)){
return "red"
}
else return "#7a85ec"
})
;
link
.transition(500)
.style("stroke-opacity", function(o) {
return o.source === d || o.target === d ? 1 : 0.1;
})
;
}
function mouseout() {
var circle = d3.select(this);
node
.transition(500)
.style("opacity", "1.0")
.style("fill", "#7a85ec")
;
link
.transition(500)
.style("stroke-opacity", "0.1");
}
function clickf(d){
}
var linkedByIndex = {};
links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = true;
});
function isConnected(a, b) {
return isConnectedAsTarget(a, b) || isConnectedAsSource(a, b) || a.index == b.index;
}
function isConnectedAsSource(a, b) {
return linkedByIndex[a.index + "," + b.index];
}
function isConnectedAsTarget(a, b) {
return linkedByIndex[b.index + "," + a.index];
}
function isEqual(a, b) {
return a.index == b.index;
}
});
</script>
</body>
This is my circle pack (sample from mbostock) -
<!DOCTYPE html>
<meta charset="utf-8">
<style>
circle {
fill: rgb(31, 119, 180);
fill-opacity: .25;
stroke: rgb(31, 119, 180);
stroke-width: 1px;
}
.leaf circle {
fill: #ff7f0e;
fill-opacity: 1;
}
text {
font: 10px sans-serif;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var diameter = 560,
format = d3.format(",d");
var pack = d3.layout.pack()
.size([diameter - 4, diameter - 4])
.value(function(d) { return d.size; });
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(2,2)");
d3.json("flare.json", function(error, root) {
var node = svg.datum(root).selectAll(".node")
.data(pack.nodes)
.enter().append("g")
.attr("class", function(d) { return d.children ? "node" : "leaf node"; })
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("title")
.text(function(d) { return d.name + (d.children ? "" : ": " + format(d.size)); });
node.append("circle")
.attr("r", function(d) { return d.r; });
node.filter(function(d) { return !d.children; }).append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.name.substring(0, d.r / 3); });
});
d3.select(self.frameElement).style("height", diameter + "px");
</script>
Please help me put these in one page using div.
This worked for me with the changes below. Hope this helps.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
#left-div
{
width:560px;
height:560px;
border:1px solid red;
margin:10px;
display:table-cell;
}
#right-div
{
width:560px;
height:560px;
border:1px solid red;
margin:10px;
display:table-cell;
}
circle {
fill: rgb(31, 119, 180);
fill-opacity: .25;
stroke: rgb(31, 119, 180);
stroke-width: 1px;
}
.leaf circle {
fill: #ff7f0e;
fill-opacity: 1;
}
text {
font: 10px sans-serif;
}
</style>
<div id="left-div"></div>
<div id="right-div"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<body>
<script>
var diameter = 560,
format = d3.format(",d");
var pack = d3.layout.pack()
.size([diameter - 4, diameter - 4])
.value(function(d) { return d.size; });
var svg = d3.select("#left-div").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(2,2)");
d3.json("flare.json", function(error, root) {
var node = svg.datum(root).selectAll(".node")
.data(pack.nodes)
.enter().append("g")
.attr("class", function(d) { return d.children ? "node" : "leaf node"; })
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("title")
.text(function(d) { return d.name + (d.children ? "" : ": " + format(d.size)); });
node.append("circle")
.attr("r", function(d) { return d.r; });
node.filter(function(d) { return !d.children; }).append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.name.substring(0, d.r / 3); });
});
d3.select(self.frameElement).style("height", diameter + "px");
</script>
<script>
var links= [];
var nodes= [];
var width = 500
height = 500;
var color = d3.scale.category20();
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkStrength(0.1)
.linkDistance(150)
.charge(-150)
.friction(0.6)
.gravity(0.5);
var svg2 = d3.select("#right-div").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("sample1.json", function(error, data) {
nodes = data.nodes;
links = data.links;
force
.nodes(nodes)
.links(links)
.on("tick", tick)
.start();
var link = svg2.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(1); });
var node = svg2.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.style("fill", "#7a85ec")
.style("opacity", 0.9)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click",clickf)
.call(force.drag);
node.append("circle")
.attr("r", function(d) { return Math.sqrt(3*d.weight); })
node.append("svg:text")
.attr("class", "nodetext")
.attr("dx", "1.35em")
.attr("dy", "-1.35em")
.text(function(d) { return d.name });
function tick() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
function mouseover(d) {
var circle = d3.select(this);
node
.transition(500)
.style("opacity", function(o) {
return isConnected(o, d) ? 1.0 : 0.1 ;
})
.style("fill", function(o){
if (isEqual(o, d)){
return "red"
}
else return "#7a85ec"
})
;
link
.transition(500)
.style("stroke-opacity", function(o) {
return o.source === d || o.target === d ? 1 : 0.1;
})
;
}
function mouseout() {
var circle = d3.select(this);
node
.transition(500)
.style("opacity", "1.0")
.style("fill", "#7a85ec")
;
link
.transition(500)
.style("stroke-opacity", "0.1");
}
function clickf(d){
}
var linkedByIndex = {};
links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = true;
});
function isConnected(a, b) {
return isConnectedAsTarget(a, b) || isConnectedAsSource(a, b) || a.index == b.index;
}
function isConnectedAsSource(a, b) {
return linkedByIndex[a.index + "," + b.index];
}
function isConnectedAsTarget(a, b) {
return linkedByIndex[b.index + "," + a.index];
}
function isEqual(a, b) {
return a.index == b.index;
}
});
</script>
Note, some of the CSS was left out from one of the graphs. Adjust that as needed. The key to getting the d3 graph to load into a div is by this code below. Notice the # within the string name of the div id.
var svg = d3.select("#left-div").append("svg")
Instead of this code below.
var svg = d3.select("body").append("svg")
The next issue that I ran into was due to a duplicate var named "svg" between both scripts, so I renamed one of them to svg2.
I have developed a force layout to represent relationships between social groups. Now I would like to get the nodes to be distributed in a circle with links joining them. What is the best way to do this?
The complete version of the code (without data) is here http://jsfiddle.net/PatriciaW/zZSJT/
(Why do I have to include code here too? Here is the main portion)
d3.json("/relationships?nocache=" + (new Date()).getTime(),function(error,members){
var links=members.organizations.map(function(members) {
return members.member;
});
var nodes = {};
links.forEach(function(link) {
link.source = nodes[link.xsource] || (nodes[link.xsource] = {source: link.xsource, name: link.xsource, category: link.categorysource, path: link.pathsource, desc: link.descsource, title: link.titlesource});
link.target = nodes[link.xtarget] || (nodes[link.xtarget] = {target: link.xtarget, name: link.xtarget, category: link.categorytarget, path: link.pathtarget, desc: link.desctarget, title: link.titletarget});
});
force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.charge(-120)
.linkDistance(function() {return (Math.random() * 200) + 100;})
.linkStrength(0.5)
.on("tick", tick)
.start();
var link = svg.selectAll(".link")
.data(force.links())
.enter().append("line")
.attr("class", "link");
var node_drag = d3.behavior.drag()
.on("dragstart", dragstart)
.on("drag", dragmove)
.on("dragend", dragend);
var loading = svg.append("text")
.attr("x", width / 2)
.attr("y", height / 2)
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text("Simulating. One moment please…");
function dragstart(d, i) {
force.stop() // stops the force auto positioning before you start dragging
}
function dragmove(d, i) {
d.px += d3.event.dx;
d.py += d3.event.dy;
d.x += d3.event.dx;
d.y += d3.event.dy;
tick(); // this is the key to make it work together with updating both px,py,x,y on d !
}
function dragend(d, i) {
d.fixed = true; // of course set the node to fixed so the force doesn't include the node in its auto positioning stuff
tick();
force.resume();
};
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", clickAlert)
.call(node_drag);
node.append("circle")
.attr("r", 8)
.style("fill", function(d) {
return categoryColour [d.category];
})
// add an image marker
node.append("image")
.attr("x",-8)
.attr("y",-8)
.attr("width", 16)
.attr("height", 16)
.attr("xlink:href", function(d) {
return categoryImage [d.category]
})
.on("click", clickAlert)
.style("cursor", "pointer")
node.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function(d) {
return d.name;
});
// Use a timeout to allow the rest of the page to load first.
setTimeout(function() {
// Run the layout a fixed number of times.
// The ideal number of times scales with graph complexity.
force.start();
for (var i = n * n; i > 0; --i) force.tick();
force.stop();
svg.selectAll("line")
.data(links)
.enter().append("line")
.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; });
svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 4.5);
loading.remove();
}, 10);
function tick() {
link
.attr("x1", function(d) {
return d.source.x + xadj; })
.attr("y1", function(d) {
return d.source.y + yadj; })
.attr("x2", function(d) {
return d.target.x +xadj; })
.attr("y2", function(d) {
return d.target.y +yadj; });
node
.attr("transform", function(d) {
return "translate(" + (d.x + xadj) + "," + (d.y + yadj) + ")";
});
};
function mouseover() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 16);
d3.select(this).select("text")
.attr("font-size","34px")
.style("font-weight", "bold");
};
function mouseout() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 8);
d3.select(this).select("text")
.attr("font-size","12px")
.style("font-weight", "normal");
};
}) // end json
Here's someone else's solution:
This network graph uses the D3 force layout to draw nodes and links, but instead of using d3.force() to find the best node positions, we draw an invisible arc and evenly places nodes along the circumference.
<!DOCTYPE html>
<html>
<head>
<script src="http://d3js.org/d3.v3.min.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
<style>
line.node-link, path.node-link {
fill: none;
stroke: black
}
circle.node {
fill: white;
stroke: black
}
circle.node+text {
text-anchor: middle;
}
text {
font-family: sans-serif;
pointer-events: none;
}
</style>
</head>
<body>
<script type="text/javascript">
// number of random nodes (gets crowded at >25 unless you change node diameter)
var num = 20;
// returns random int between 0 and num
function getRandomInt() {return Math.floor(Math.random() * (num));}
// nodes returns a [list] of {id: 1, fixed:true}
var nodes = d3.range(num).map(function(d) { return {id: d}; });
// links returns a [list] of {source: 0, target: 1} (values refer to indicies of nodes)
var links = d3.range(num).map(function(d) { return {source: getRandomInt(), target: getRandomInt()}; });
var width = 500,
height = 500;
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([width, height]);
// evenly spaces nodes along arc
var circleCoord = function(node, index, num_nodes){
var circumference = circle.node().getTotalLength();
var pointAtLength = function(l){return circle.node().getPointAtLength(l)};
var sectionLength = (circumference)/num_nodes;
var position = sectionLength*index+sectionLength/2;
return pointAtLength(circumference-position)
}
// fades out lines that aren't connected to node d
var is_connected = function(d, opacity) {
lines.transition().style("stroke-opacity", function(o) {
return o.source === d || o.target === d ? 1 : opacity;
});
}
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// invisible circle for placing nodes
// it's actually two arcs so we can use the getPointAtLength() and getTotalLength() methods
var dim = width-80
var circle = svg.append("path")
.attr("d", "M 40, "+(dim/2+40)+" a "+dim/2+","+dim/2+" 0 1,0 "+dim+",0 a "+dim/2+","+dim/2+" 0 1,0 "+dim*-1+",0")
.style("fill", "#f5f5f5");
force.start();
// set coordinates for container nodes
nodes.forEach(function(n, i) {
var coord = circleCoord(n, i, nodes.length)
n.x = coord.x
n.y = coord.y
});
// use this one for straight line links...
// var lines = svg.selectAll("line.node-link")
// .data(links).enter().append("line")
// .attr("class", "node-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; });
// ...or use this one for curved line links
var lines = svg.selectAll("path.node-link")
.data(links).enter().append("path")
.attr("class", "node-link")
.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;
});
var gnodes = svg.selectAll('g.gnode')
.data(nodes).enter().append('g')
.attr("transform", function(d) {
return "translate("+d.x+","+d.y+")"
})
.classed('gnode', true);
var node = gnodes.append("circle")
.attr("r", 25)
.attr("class", "node")
.on("mouseenter", function(d) {
is_connected(d, 0.1)
node.transition().duration(100).attr("r", 25)
d3.select(this).transition().duration(100).attr("r", 30)
})
.on("mouseleave", function(d) {
node.transition().duration(100).attr("r", 25);
is_connected(d, 1);
});
var labels = gnodes.append("text")
.attr("dy", 4)
.text(function(d){return d.id})
</script>
</body>
</html>