How to make d3 directional force diagram reactive in meteor? - javascript

Try as I might, I can't seem to get meteor and d3 to play nicely when it comes to a directional force diagram. Since I'm new to both Meteor and d3, I'm not sure where my failing is..
What I'm trying to do is to get (re)create just the following sample graph, but one which is reactive to the mongo data source. Any help at all would be most appreciated!
(Click here for live demo)
// get the data
d3.csv("force.csv", function(error, links) {
var nodes = {};
// Compute the distinct nodes from the links.
links.forEach(function(link) {
link.source = nodes[link.source] ||
(nodes[link.source] = {name: link.source});
link.target = nodes[link.target] ||
(nodes[link.target] = {name: link.target});
link.value = +link.value;
});
var width = 960,
height = 500;
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// build the arrow.
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
// add the links and the arrows
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
// .attr("class", function(d) { return "link " + d.type; })
.attr("class", "link")
.attr("marker-end", "url(#end)");
// define the nodes
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.call(force.drag);
// add the nodes
node.append("circle")
.attr("r", 5);
// add the text
node.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
// add the curvy lines
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", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
}
});
</script>
force.csv:
source,target,value
Harry,Sally,1.2
Harry,Mario,1.3
Sarah,Alice,0.2
Eveie,Alice,0.5
Peter,Alice,1.6
Mario,Alice,0.4
James,Alice,0.6
Harry,Carol,0.7
Harry,Nicky,0.8
Bobby,Frank,0.8
Alice,Mario,0.7
Harry,Lynne,0.5
Sarah,James,1.9
Roger,James,1.1
Maddy,James,0.3
Sonny,Roger,0.5
James,Roger,1.5
Alice,Peter,1.1
Johan,Peter,1.6
Alice,Eveie,0.5
Harry,Eveie,0.1
Eveie,Harry,2.0
Henry,Mikey,0.4
Elric,Mikey,0.6
James,Sarah,1.5
Alice,Sarah,0.6
James,Maddy,0.5
Peter,Johan,0.7
Below is my attempt using the parties example as a starting point:
parties.html:
<head>
<title>All Tomorrow's Parties</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
{{> page}}
</body>
<template name="page">
<div class="span6">
{{> map}}
</div>
{{>updateNetwork}}
</template>
<template name="map">
</template>
<template name="details">
<div class="details">
</div>
</template>
<template name="updateNetwork">
<div align="right">
<br/>
<input id="addNodeBtn" type="button" value = "Add Some Nodes">
</div>
<div id="svgDiv">
</div>
</template>
client.js:
Template.map.rendered = function () {
var self = this;
self.node = self.find("svg");
if (! self.handle) {
self.handle = Deps.autorun(function () {
if (!Session.equals("alreadyRun", true))
{
_links = Links.find({}).fetch();
// The nodes array just contains name information.
// Sample values:
// nodes["Jack"] = {name: "Jack"}
// nodes["Jill"] = {name: "Jill"}
var nodes = {};
// Compute the distinct nodes from the links.
// Go through all links, and update the total weight of the edge as well for each link,
// within the links object.
//
_links.forEach(function(link) {
link.source = nodes[link.source] ||
(nodes[link.source] = {name: link.source});
link.target = nodes[link.target] ||
(nodes[link.target] = {name: link.target});
link.value = +link.value;
});
console.log("links: " + JSON.stringify(_links));
var links = _links;
// At this point, the "links" object cotains info on the entire network, and
// is prepared to be rendered
var width = 300,
height = 200;
force = d3.layout.force();
force
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
Session.set("forceVariable", force);
Session.set("nodeArray", nodes);
//Session.set("linkArray", d3.layout.force().links());
Session.set("linkArray", links);
// Set the range
var v = d3.scale.linear().range([0, 100]);
// Scale the range of the data
v.domain([0, d3.max(links, function(d) { return d.value; })]);
// asign a type per value to encode opacity
links.forEach(function(link) {
if (v(link.value) <= 25) {
link.type = "twofive";
} else if (v(link.value) <= 50 && v(link.value) > 25) {
link.type = "fivezero";
} else if (v(link.value) <= 75 && v(link.value) > 50) {
link.type = "sevenfive";
} else if (v(link.value) <= 100 && v(link.value) > 75) {
link.type = "onezerozero";
}
});
var svg = d3.select("body").select("#svgDiv").append("svg")
.attr("width", width)
.attr("height", height);
//Session.set("currentSVG", svg);
// build the arrow.
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
// add the links and the arrows
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", "url(#end)");
console.log("At this point, force.nodes() is..." + JSON.stringify(force.nodes()));
// define the nodes
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.on("click", click)
.on("dblclick", dblclick)
.call(force.drag);
console.log("And now, at this point, force.nodes() is..." + JSON.stringify(force.nodes()));
// add the nodes
node.append("circle")
.attr("r", 5);
// add the text
node.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
} // end if (Session.equals("alreadyRun", true))
else
{
console.log("nodeArrray: " + JSON.stringify(Session.get("nodeArray")));
console.log("linkArray:" + Session.get("linkArray"));
//console.log("currentSVG: " + JSON.stringify(Session.get("currentSVG")));
//console.log("currentNodes: " + JSON.stringify(Session.get("currentSVG").nodes()));
// Session.set("nodeArray", nodes);
// Session.set("linkArray", d3.layout.force().links());
var svg = d3.select("body").select("#svgDiv").select("svg");
//var svg = Session.get("currentSVG");
// Try to access the force.nodes() object
//
//console.log("force.nodes: "+ JSON.stringify(svg.selectAll(".node").data(Session.get();)));
// build the arrow.
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
newNodes = Session.get("nodeArray");
newLinks = Session.get("linkArray");
newLinks.push({source:"Kobeeley" , target:"Cluff", value:0.8});
newLinks.forEach(function(link) {
link.source = newNodes[link.source] ||
(newNodes[link.source] = {name: link.source});
link.target = newNodes[link.target] ||
(newNodes[link.target] = {name: link.target});
link.value = +link.value;
});
console.log("newNodes is now...." + JSON.stringify(newNodes));
//var force = d3.layout.force();
// add the links and the arrows
var path = svg.append("svg:g").selectAll("path")
.data(newLinks)
.enter().append("svg:path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", "url(#end)");
// define the nodes
var node = svg.selectAll(".node")
.data(d3.values(newNodes))
.enter().append("g")
.attr("class", "node")
.call(force.drag);
// .on("click", click)
// .on("dblclick", dblclick)
// .call(force.drag);
// add the nodes
node.append("circle")
.attr("r", 5);
// add the text
node.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
var width = 960,
height = 500;
force.start();
/*
force
.nodes(d3.values(newNodes))
.links(newLinks)
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
*/
/*
var force = d3.layout.force()
.nodes(d3.values(newNodes))
.links(newLinks)
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
*/
} // end of if..else firstrun
// add the curvy lines
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", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
}
// action to take on mouse click
function click() {
d3.select(this).select("text").transition()
.duration(750)
.attr("x", 22)
.style("fill", "steelblue")
.style("stroke", "lightsteelblue")
.style("stroke-width", ".5px")
.style("font", "20px sans-serif");
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 16)
.style("fill", "lightsteelblue");
}
// action to take on mouse double click
function dblclick() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 6)
.style("fill", "#ccc");
d3.select(this).select("text").transition()
.duration(750)
.attr("x", 12)
.style("stroke", "none")
.style("fill", "black")
.style("stroke", "none")
.style("font", "10px sans-serif");
}
}); // End of deps.autorun function
}// end of (! self.handle)
};// end of Template.map.rendered
Template.updateNetwork.events({
'click #addNodeBtn': function (event, template) {
//if (! Meteor.userId()) // must be logged in to create events
// return;
//var coords = coordsRelativeToElement(event.currentTarget, event);
//openCreateDialog(coords.x / 500, coords.y / 500);
var _linksToAdd = [
{source: "Sully", target: "Roy", value: 0.2 },
{source: "Roy", target: "Jack", value: 0.8},
{source:"Juhuff", target: "Cluff", value: 0.9}
];
Session.set("alreadyRun", true);
// Update the collection
//
_linksToAdd.forEach(function (link) {
Links.insert(link);
})
}
});
model.js:
Links = new Meteor.Collection("links");
Links.allow({
insert: function (userId, doc) {
return true; // no cowboy inserts -- use createParty method
},
update: function (userId, doc, fields, modifier) {
// if (userId !== party.owner)
// return true; // not the owner
// var allowed = ["title", "description", "x", "y"];
// if (_.difference(fields, allowed).length)
// return true; // tried to write to forbidden field
// A good improvement would be to validate the type of the new
// value of the field (and if a string, the length.) In the
// future Meteor will have a schema system to makes that easier.
return true;
},
remove: function (userId, doc) {
// You can only remove parties that you created and nobody is going to.
// return party.owner === userId && attending(party) === 0;
return true;
}
});

Here's the answer that worked for me using meteor and incorporating some awesome code from another D3 question:
client.js:
Things = new Meteor.Collection("things");
Links = new Meteor.Collection("links");
if (Meteor.isClient) {
Template.diagram.rendered = function () {
var graph;
graph = new myGraph("#svgdiv");
Things.find().observe({
added: function (doc) {
graph.addNode(doc._id);
},
removed: function (doc) {
graph.removeNode(doc._id);
}
});
Links.find().observe({
added: function (doc) {
graph.addLink(doc._id, doc.source, doc.target, doc.value);
},
removed: function (doc) {
graph.removeLink(doc._id);
}
});
};
}
function myGraph(el) {
// Add and remove elements on the graph object
this.addNode = function (id) {
nodes.push({"id":id});
update();
};
this.removeNode = function (id) {
var i = 0;
var n = findNode(id);
while (i < links.length) {
if ((links[i]['source'] == n)||(links[i]['target'] == n))
{
links.splice(i,1);
}
else i++;
}
nodes.splice(findNodeIndex(id),1);
update();
};
this.removeLink = function (id){
for(var i=0;i<links.length;i++)
{
if(links[i].id === id)
{
links.splice(i,1);
break;
}
}
update();
};
this.removeallLinks = function(){
links.splice(0,links.length);
update();
};
this.removeAllNodes = function(){
nodes.splice(0,links.length);
update();
};
this.addLink = function (id, source, target, value) {
links.push({id: id, "source":findNode(source),"target":findNode(target),"value":value});
update();
};
var findNode = function(id) {
for (var i in nodes) {
if (nodes[i]["id"] === id) return nodes[i];};
};
var findNodeIndex = function(id) {
for (var i=0;i<nodes.length;i++) {
if (nodes[i].id==id){
return i;
}
};
};
// set up the D3 visualisation in the specified element
var w = 500,
h = 500;
var svg = d3.select(el)
.append("svg:svg")
.attr("width", w)
.attr("height", h)
.attr("id","svg")
.attr("pointer-events", "all")
.attr("viewBox","0 0 "+w+" "+h)
.attr("perserveAspectRatio","xMinYMid");
var vis = svg.append('svg:g');
var force = d3.layout.force();
var nodes = force.nodes(),
links = force.links();
// build the arrow.
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
var update = function () {
var link = vis.selectAll("path")
.data(links, function(d) {
return d.id;
});
link.enter().append("svg:path")
.attr("id",function(d){return d.id;})
.attr("class","link")
.attr("marker-end", "url(#end)");
link.append("title")
.text(function(d){
return d.value;
});
link.exit().remove();
var node = vis.selectAll("g.node")
.data(nodes, function(d) { return d.id;});
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.call(force.drag);
nodeEnter.append("svg:circle")
.attr("r", 16)
.attr("id",function(d) { return "Node;"+d.id;})
.attr("class","nodeStrokeClass");
nodeEnter.append("svg:text")
.attr("class","textClass")
.text( function(d){return d.id;}) ;
node.exit().remove();
force.on("tick", function() {
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
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;
});
});
// Restart the force layout.
force
.gravity(.05)
.distance(50)
.linkDistance( 50 )
.size([w, h])
.start();
};
// Make it all go
update();
}
main.html:
<body>
{{> diagram}}
</body>
<template name="diagram">
{{#constant}}
<div id="svgdiv"></div>
{{/constant}}
</template>

Related

How to show the text of D3 node on hover?

I am very new to D3 and I am trying to replicate the behavior for text like is done with the circle elements here with the mouseover,mouseout behavior. Basically, to show text when hovered and hidden when not. Do I need to create a node var as I have with circle and text or is it possible with the current implementation:
var nodes = {};
// Compute the distinct nodes from the links.
links.forEach(function(link) {
link.source = nodes[link.source] || (nodes[link.source] = {name: link.source, value: link.type});
link.target = nodes[link.target] || (nodes[link.target] = {name: link.target, value: link.type});
});
var width = 1000,
height = 900;
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(90)
.charge(-125)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// Per-type markers, as they don't inherit styles.
svg.append("defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; })
.style("stroke", function(d){
if (d.type >= 0.4) {
return '#000066'
}
if (d.type >= 0.25) {
return '#ccccff'
}
if (d.type >= 0.01) {
return 'e8f4f8'
}
else {
return '#FFFFFF'
}
});
var circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("r", 6)
.on("dblclick", dblclick)
.on('mouseover', function(d){
var nodeSelection = d3.select(this).style({opacity:'0.5'});
nodeSelection.select("text").style({opacity:'1.0'});
})
.on('mouseout', function(d){
var nodeSelection = d3.select(this).style({opacity:'0.0'});
nodeSelection.select("text").style({opacity:'0.0'});
})
.call(force.drag);
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d){
if (d.value >= 0.35){
return d.name
}
});
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform);
}
function linkArc(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;
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
var drag = force.drag()
.on("dragstart", dragstart);
function dblclick(d) {
d3.select(this).classed("fixed", d.fixed = false);
}
function dragstart(d) {
d3.select(this).classed("fixed", d.fixed = true);
}
Add opacity attribute to your text that defaults to 0. It appears you already have text to show and hide it on mouseover/mouseout. I posted a different way to do it as well if your method is not working for you.
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 8)
.attr("y", ".31em")
.attr("opacity", 1)
.text(function(d){
if (d.value >= 0.35){
return d.name
}
});
function mouseout(d) { text.style("opacity", 0); }
function mouseover(d) { text.style("opacity", 1);

Abnormal width when I have two graph D3.js on the same page

I have a problem when there is two graph on same page, the first graph has a normal size but the second which is populated by fixed data is anormaly large. The two graph are superimposed. Strange thing, if I use the code of the second graph, it works on my local website test.
This is the code of the second graph :
// http://blog.thomsonreuters.com/index.php/mobile-patent-suits-graphic-of-the-day/
var links = [
{"source":"TEST111","target":"TEST222","level":"1","life":"1","test":"1","type":"licensing"},
{"source":"TEST222","target":"TEST3333","level":"2","life":"2","test":"2","type":"licensing"},
{"source":"TEST3333","target":"TEST4444","level":"3","life":"3","test":"3","type":"licensing"}
];
var nodes = {};
// Compute the distinct nodes from the links.
links.forEach(function(link) {
link.source = nodes[link.source] || (nodes[link.source] = {name: link.source, level:link.level, life:link.life});
link.target = nodes[link.target] || (nodes[link.target] = {name: link.target, level:link.level, life:link.life});
});
var width = 960,
height = 500;
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(200)
.charge(-400)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// Per-type markers, as they don't inherit styles.
svg.append("defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 20)
.attr("markerHeight", 20)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("r", 20)
.call(force.drag)
.style("fill","red")
.on("click", click);
function click(d){
}
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.name; });
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform)
}
function linkArc(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;
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
Also, on the Pluker, the two graph are bunk but it's not the problem (It works in local). You can delete the data load if you want (line 34). This is an online Plunker to see the problem : https://plnkr.co/edit/ewoi6wao97tXAKr1QxIm?p=preview
Thanks.
Basically your problem was you were filling the path rather than just giving it a stroke.
So when you create the path just add the following :
.style("fill","none").style("stroke","red")
So now it looks like :
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.style("fill","none").style("stroke","red")
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
Updated plnkr : https://plnkr.co/edit/JgHwC4zyolj0hsg8ib4w?p=preview

Labelling multiple edges in d3.js

I would like to label multiple edges between two nodes in a d3 force diagram. By modifying this example (http://jsfiddle.net/7HZcR/3/) I have two edges displayed separately but the labels associated to the edges overlapping on one edge rather than sitting on their own line. I can't work out why that would be. Any ideas how I could fix this? Here is the relevant part of my code:
d3.csv("GHT_1_A.csv", function(error, links) {
//sort links by source, then target
links.sort(function(a,b) {
if (a.source > b.source) {return 1;}
else if (a.source < b.source) {return -1;}
else {
if (a.target > b.target) {return 1;}
if (a.target < b.target) {return -1;}
else {return 0;}
}
});
//any links with duplicate source and target get an incremented 'linknum'
for (var i=0; i<links.length; i++) {
if (i != 0 &&
links[i].source == links[i-1].source &&
links[i].target == links[i-1].target) {
links[i].linknum = links[i-1].linknum + 1;
}
else {links[i].linknum = 1;};
};
// Compute the distinct nodes from the links.
links.forEach(function(link) {
link.source = nodes[link.source] ||
(nodes[link.source] = {name: link.source});
link.target = nodes[link.target] ||
(nodes[link.target] = {name: link.target});
});
force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(200)
.charge(-1000)
.on("tick", tick)
.start();
svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// build the arrow.
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
var link = svg.append("svg:g").selectAll("g.link")
.data(force.links())
.enter().append('g')
var linkPath = link.append("svg:path")
.attr('class', 'link')
.attr("marker-end", "url(#end)");
var textPath = link.append("svg:path")
.attr("id", function(d) { return d.source.index + "_" + d.target.index; })
.attr("class", "textpath");
var path_label = svg.append("svg:g").selectAll(".path_label")
.data(force.links())
.enter().append("svg:text")
.attr("class", "path_label")
.append("svg:textPath")
.attr("startOffset", "50%")
.attr("text-anchor", "middle")
.attr("xlink:href", function(d) { return "#" + d.source.index + "_" + d.target.index; })
.style("fill", "#000")
.style("font-family", "Arial")
.text(function(d) { return d.linkQ; });
function arcPath(leftHand, d) {
var start = leftHand ? d.source : d.target,
end = leftHand ? d.target : d.source,
dx = end.x - start.x,
dy = end.y - start.y,
dr = 100/d.linknum;
//dr = Math.sqrt(dx * dx + dy * dy),
sweep = leftHand ? 0 : 1;
return "M" + start.x + "," + start.y + "A" + dr + "," + dr + " 0 0," + sweep + " " + end.x + "," + end.y;
}
var circle = svg.append("svg:g").selectAll("circle")
.data(force.nodes())
.enter().append("svg:circle")
.attr("r", 6)
.call(force.drag);
var text = svg.append("svg:g").selectAll("g")
.data(force.nodes())
.enter().append("svg:g");
// A copy of the text with a thick white stroke for legibility.
text.append("svg:text")
.attr("x", 8)
.attr("y", ".31em")
.attr("class", "shadow")
.text(function(d) { return d.name; });
text.append("svg:text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.name; });
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
linkPath.attr("d", function(d) {
return arcPath(false, d);
});
textPath.attr("d", function(d) {
return arcPath(d.source.x < d.target.x, d);
});
circle.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
text.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
};
});

D3's node's data

I have a problem with the node's data on D3.
I'm using a CSV file for my data. Suppose my CSV file has a source and a target like the following:
source target
A B
A C
A D
B D
C E
and the code for creating distinct nodes is
var nodes = {};
links.forEach(function(link) {
link.source = nodes[link.source] ||
(nodes[link.source] = {name: link.source, to: link.target, countOut: 0,
countIn: 0});
link.source.countOut++;
link.target = nodes[link.target] ||
(nodes[link.target] = {name: link.target, countOut: 0, countIn: 0});
link.target.countIn++;
});
I made a popup when there is a click event on a node. In my popup, I want it to display the source and the target of that node. But it displays only one target node for the source node. For example, the target of source A is B. It doesn't show all the targets of source A which are B, C, and D. I want it to display all of the targets that the source has. Is it possible to do that?
Here is the code for my popup
// action to take on mouse click
function popNode(d) {
d3.select("#popup").remove();
popup1 = d3.select("#aside")
.append("div")
.attr("id", "popup");
popup1.append("h2").text(d.name);
popup1.append("h2").text("to: " + d.to);
showing = true;
d3.select("#popup").on("click", function(){
d3.select("#popup").remove();
showing = false;
});
}
The code below is for what is d in the popup
var node = svg.append("svg:g").selectAll("node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.on("click", popNode)
.on("mouseover", fade(.2))
.on("mouseout", fade(1))
.call(force.drag);
// add the nodes
node.append("circle")
.attr("r", 5);
For the whole js code
// get the data
d3.json("case1.csv", function(d) {
return {
source: d.source,
date: d.date,
time: d.time,
type: d.type,
target: d.target
};
}, function(error, rows) {
console.log(rows);
links = rows;
var nodes = {};
// Compute the distinct nodes from the links.
links.forEach(function(link) {
link.source = nodes[link.source] ||
(nodes[link.source] = {name: link.source, countOut: 0, countIn: 0});
link.source.countOut++;
link.target = nodes[link.target] ||
(nodes[link.target] = {name: link.target, countOut: 0, countIn: 0});
link.target.countIn++;
});
var width = 700,
height = 450;
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
var svg = d3.select("#content").append("svg")
.attr("id", "kwai")
.attr("width", width)
.attr("height", height)
.attr("viewbox", "0 0 700 450")
.attr("perserveAspectRatio", "xMinYMid")
.call(d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoom))
.append("g");
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height);
// build the arrow.
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
// add the links and the arrows
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
// .attr("class", function(d) { return "link " + d.type; })
.attr("class", "link")
.attr("marker-end", "url(#end)")
.on("click", popLink);
// define the nodes
var node = svg.append("svg:g").selectAll("node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.on("click", popNode)
.on("mouseover", fade(.2))
.on("mouseout", fade(1))
.call(force.drag);
// add the nodes
node.append("circle")
.attr("r", 5);
// add the text
node.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
function zoom() {
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale
+ ")");
}
var linkedByIndex = {};
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;
}
var chart = $("#kwai"),
aspect = chart.width() / chart.height(),
container = chart.parent();
$(window).on("resize", function() {
var targetWidth = container.width();
chart.attr("width", targetWidth);
chart.attr("height", Math.round(targetWidth / aspect));
}).trigger("resize");
// add the curvy lines
function tick() {
path.attr("d", linkArc);
node.attr("transform", transform);
}
function linkArc(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;
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
showing = false;
// action to take on mouse click
function popNode(d) {
d3.select("#popup").remove();
popup1 = d3.select("#aside")
.append("div")
.attr("id", "popup");
popup1.append("h2").text(d.name);
//popup1.append("h2").text("to: " + d.to);
popup1.append("h2").text("out: " + d.countOut);
popup1.append("h2").text("in: " + d.countIn);
showing = true;
d3.select("#popup").on("click", function(){
d3.select("#popup").remove();
showing = false;
});
}
function popLink(d) {
d3.select("#popup").remove();
popup1 = d3.select("#aside")
.append("div")
.attr("id", "popup");
popup1.append("h2").text(d.time);
showing = true;
d3.select("#popup").on("click", function(){
d3.select("#popup").remove();
showing = false;
});
}
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;
});
path.style("opacity", opacity).style("opacity", function(o) {
return o.source === d || o.target === d ? 1 : opacity;
});
};
}
});

Adding text on a path in D3 force layout

I am creating a navigation flow diagram using D3.js. Basically, it looks alright so far, however, when I change things to include a title/tooltip on link between nodes (to reflect how many people clicked from one page to another), I am unable to do it -
var data = [{"value":"1","target":"SearchActivity","source":"CommonFriendActivity"},{"value":"1","target":"SearchActivity","source":"InviteActivity"},{"value":"1","target":"SearchActivity","source":"LikeDetailActivity"},{"value":"1","target":"Exit","source":"LoginScreenActivity"},{"value":"1","target":"InviteActivity","source":"LoginScreenActivity"},{"value":"1.5","target":"SearchActivity","source":"LoginScreenActivity"},{"value":"1","target":"Exit","source":"SearchActivity"},{"value":"1","target":"InviteActivity","source":"SearchActivity"},{"value":"1","target":"PictureActivity","source":"SearchActivity"},{"value":"1","target":"UserDetailActivity","source":"SearchActivity"},{"value":"1","target":"CommonFriendActivity","source":"UserDetailActivity"},{"value":"1","target":"Exit","source":"UserDetailActivity"},{"value":"1","target":"LikeDetailActivity","source":"UserDetailActivity"},{"value":"1","target":"SearchActivity","source":"UserDetailActivity"}];
var nodes = {};
// Compute the distinct nodes from the links.
data.forEach(function(link) {
link.source = nodes[link.source] ||
(nodes[link.source] = {name: link.source, tooltip: link.value});
link.target = nodes[link.target] ||
(nodes[link.target] = {name: link.target, tooltip: link.value});
link.value = +link.value;
});
var width = 600,
height = 500;
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(data)
.size([width, height])
.linkDistance(200)
.charge(-300)
.on("tick", tick)
.start();
// Set the range
var v = d3.scale.linear().range([0, 100]);
// Scale the range of the data
v.domain([0, d3.max(data, function(d) { return d.value; })]);
// asign a type per value to encode opacity
data.forEach(function(link) {
if (v(link.value) <= 25) {
link.type = "twofive";
} else if (v(link.value) <= 50 && v(link.value) > 25) {
link.type = "fivezero";
} else if (v(link.value) <= 75 && v(link.value) > 50) {
link.type = "sevenfive";
} else if (v(link.value) <= 100 && v(link.value) > 75) {
link.type = "onezerozero";
}
});
var svg = d3.select("#phone_layout").append("svg")
.attr("width", width)
.attr("height", height);
// build the arrow.
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
// add the links and the arrows
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", "url(#end)");
// define the nodes
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.on("click", click)
.on("dblclick", dblclick)
.call(force.drag);
// add the nodes
node.append("circle")
.attr("r", 10)
.style("fill", "steelblue")
.style("stroke", "lightsteelblue")
.style("stroke-width", ".5px")
.style("font", "20px sans-serif");
// add the text
node.append("text")
.attr("x", 15)
.attr("dy", ".9em")
.text(function(d) { return d.name; });
// add the curvy lines
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", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
}
Here is the jsfiddle - http://jsfiddle.net/uMceL/7/
PS: Links are also not looking properly but they seem fine on webpage when I use same code.
Any help/pointer will be greatly appreciated

Categories

Resources