I have some problems using d3.js visualization network library when I want to show more than one network visualization graph.
Since I draw a new graph only the last one has its force layout working and I can't find out where things are going wrong.
Here is the jsfiddle :
https://jsfiddle.net/7mn0qy5b/2/
Here is my source :
HTML
<div id="graph1"></div>
<div id="graph2"></div>
CSS
#graph1, #graph2 {
width: 250px;
height: 250px;
border: 1px solid #000;
}
.link {
stroke: #626262;
strokeWidth: 2px;
}
JS
var graph = {};
function myGraph(el) {
this.link = {};
this.node = {};
this.container = 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 (source,target){
for(var i=0;i<links.length;i++)
{
if(links[i].source.id == source && links[i].target.id == target)
{
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 (source, target, value) {
links.push({"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];
};
return null;
};
var findNodeIndex = function(id) {
for (var i=0;i<nodes.length;i++) {
if (nodes[i].id==id){
return i;
}
};
return null;
};
// set up the D3 visualisation in the specified element
var w = 250,
h = 250;
this.vis = 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")
.append('svg:g');
this.force = d3.layout.force();
var nodes = this.force.nodes(),
links = this.force.links();
self = this;
var update = function () {
self.link = self.vis.selectAll("line")
.data(links, function(d) {
return d.source.id + "-" + d.target.id;
});
self.link.enter().append("line")
.attr("id",function(d){return d.source.id + "-" + d.target.id;})
.attr("class","link")
.append("title")
.text(function(d){
return d.value;
});
self.link.exit().remove();
self.node = self.vis.selectAll("g.node")
.data(nodes, function(d) {
return d.id;
});
var nodeEnter = self.node.enter().append("g")
.attr("class", "node")
.call(self.force.drag);
nodeEnter.append("svg:circle")
.attr("r", 16)
.attr("id",function(d) { return "svgNode_"+self.container+"_"+d.id;})
.attr("class","nodeStrokeClass");
nodeEnter.append("svg:text")
.attr("class","textClass")
.text( function(d){return d.id;}) ;
self.node.exit().remove();
self.force.on("tick", function() {
//console.log(self.container);
/*self.node.attr("cx", function(d) { return d.x = Math.max(d.radius, Math.min(svg[spaceId].attr('width') - d.radius, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(d.radius, Math.min(svg[spaceId].attr('height') - d.radius, d.y)); })
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
*/self.node.attr("transform", function(d) { return "translate("+d.x+","+d.y+")"; });
self.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; });
});
// Restart the force layout.
self.force
.gravity(.06)
.distance(100)
.charge(-300)
.size([w, h])
.start();
};
// Make it all go
update();
}
graph["#graph1"] = new myGraph("#graph1");
graph["#graph1"].addNode('A');
graph["#graph1"].addNode('B');
graph["#graph1"].addNode('C');
graph["#graph1"].addLink('A','B','10');
graph["#graph1"].addLink('A','C','8');
graph["#graph1"].addLink('B','C','15');
setTimeout(function() {
graph["#graph2"] = new myGraph("#graph2");
graph["#graph2"].addNode('D');
graph["#graph2"].addNode('E');
graph["#graph2"].addNode('F');
graph["#graph2"].addLink('D','E','10');
graph["#graph2"].addLink('D','F','8');
graph["#graph2"].addLink('E','F','15');
}, 2000);
Thank you for your help, I'm getting mad...
This line
self = this;
is missing a var keyword. Without it, self is assigned to global window scope instead the local myGraph scope. On the second run of myGraph constructor, first myGraph's window.self is overwritten with the new value. Therefore events in both myGraph objects reference the second self, which causes the breakage.
You might want to enable strict mode, so that the compiler will throw a warning on such a badly traceable error.
Related
I'm trying to simulate the diffusion of a virus in a network. I've modified a random graph generator to create a graph in which each node represents an individual. Now I would like to be able to click on a node (which infects him) and to see the propagation of the virus in the graph. For exemple, each neighbour of an infected node is infected with a probability p chosen before. For the moment, I can only click on a node and it turns red, but that's it. Any ideas ?
Here is the javascript code :
var width = 1300,
height = 1000;
n = 30
m = 100
charge = -2000
z = 0,7 // contamination parameter
function changevalue() {
n = document.getElementById("numberofnodes").value;
m = document.getElementById("numberoflinks").value;
charge = document.getElementById("chargenumber").value;
}
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.on("dblclick", create);
create();
function create () {
svg.selectAll(".link, .node").remove();
randomGraph(n, m, charge);
}
function randomGraph (n, m, charge) {
var nodes = d3.range(n).map(Object),
list = randomChoose(unorderedPairs(d3.range(n)), m),
links = list.map(function (a) { return {source: a[0], target: a[1]} });
var force = d3.layout.force()
.size([width, height])
.nodes(nodes)
.links(links)
.charge(charge)
.on("tick", tick)
.start();
var svgLinks = svg.selectAll(".link").data(links)
.enter().append("line")
.attr("class", "link");
var svgNodes = svg.selectAll(".node").data(nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 2)
.style("fill", "green")
.style("fill", function(d) {return d.color; })
.each(function() {
var sel = d3.select(this);
var state = false;
sel.on("click", function() {
state = !state;
if (state) {
sel.style("fill", "red");
} else {
sel.style("fill", function(d) { return d.color; });
}
});
});
svgNodes.transition().duration(2000)
.attr("r", function (d) { return 0.8 * d.weight })
svgLinks.transition().duration(20000)
.style("stroke-width", 1);
function tick () {
svgNodes
.attr("cx", function(d) { return d.x })
.attr("cy", function(d) { return d.y });
svgLinks
.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 randomChoose (s, k) {
var a = [], i = -1, j;
while (++i < k) {
j = Math.floor(Math.random() * s.length);
a.push(s.splice(j, 1)[0]);
};
return a;
}
function unorderedPairs (s) {
var i = -1, a = [], j;
while (++i < s.length) {
j = i;
while (++j < s.length) a.push([s[i],s[j]])
};
return a;
}
A possible approach is to create a function that takes in an array 'infected' nodes and for each infected node:
examines the links to look for this node is linked
where a link is found, randomly add the linked node to the array of newly infected
nodes, and repeat the function. The random can be based on whatever probability you want - currently the example below uses 0.5.
repeat this function a limited number of times, else it will continue
forever
The example below assigns a colour to the infected nodes based on which iteration of the function that node is infected.
console.clear()
var width = 600,
height = 500;
n = 30
m = 100
charge = -2000
z = 0, 7 // contamination parameter
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
var count = 0
var iterations = 5
var links, nodes
var colour = d3.scaleLinear()
.domain([1, 10])
.range(["red", "steelblue"])
.interpolate(d3.interpolateHcl);
create();
function create() {
svg.selectAll(".link, .node").remove();
randomGraph(n, m, charge);
}
function randomGraph(n, m, charge) {
var nodes = d3.range(n).map(function(d) {
let obj = {}
obj.r = 15
obj.state = false
return obj
});
list = randomChoose(unorderedPairs(d3.range(n)), m),
links = list.map(function(a) {
return {
source: a[0],
target: a[1]
}
});
var force = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links))
.force("collide", d3.forceCollide(30))
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", tick)
var svgLinks = svg.selectAll(".link")
.data(links)
.enter()
.append("line")
.attr("class", "link")
var svgNodes = svg.selectAll(".node")
.data(nodes)
.enter()
.append("circle")
.attr("id", d => "node-" + d.index)
.attr("class", "node")
.attr("r", 2)
.style("fill", "grey")
.on("click", startVirus)
svgNodes.transition().duration(2000)
.attr("r", function(d) {
return 0.8 * d.r
})
svgLinks.transition().duration(2000)
.style("stroke-width", 1);
function tick() {
svgNodes
.attr("cx", function(d) {
return d.x
})
.attr("cy", function(d) {
return d.y
});
svgLinks
.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 startVirus(node) {
d3.selectAll('circle').style("fill", function(d) {
d.state = false
return "grey"
})
let id = node.index
count = 0;
console.clear()
infectCircles([id])
}
function infectCircles(carrierIDs) {
count = count + 1
let potentialInfections = []
carrierIDs.forEach(function(c) {
let circleID = "#node-" + c
d3.select(circleID)
.transition(2000)
.style("fill", function(d) {
if (d.state == true) {
return d.colour
} else {
d.state = true
d.colour = colour(count)
return d.colour
}
})
links.forEach(function(l) {
if (l.source.index == c) {
if (Math.random() < 0.5) {
console.log("infect! " + l.target.index)
potentialInfections.push(l.target.index)
}
}
if (l.target.index == c) {
if (Math.random() < 0.5) {
console.log("infect! " + l.source.index)
potentialInfections.push(l.source.index)
}
}
})
if (count <= iterations) {
infectCircles(potentialInfections)
}
})
}
function randomChoose(s, k) {
var a = [],
i = -1,
j;
while (++i < k) {
j = Math.floor(Math.random() * s.length);
a.push(s.splice(j, 1)[0]);
};
return a;
}
function unorderedPairs(s) {
var i = -1,
a = [],
j;
while (++i < s.length) {
j = i;
while (++j < s.length) a.push([s[i], s[j]])
};
return a;
}
circle {
stroke: white;
stroke-width: 2;
}
line {
fill: none;
stroke: grey;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
I am developing a site locally using D3 JS. When I do anything outside of any function it is happening twice, no matter where it is within the code. Example:
console.log("2x");
Console output is:
2x
2x
However if the code is inside any of the functions it only prints once. I noticed that next to the logs there is two different locations for their origin
Console output
2x site.js:3
2x site.js?v=twO2e-dF40DXz0Jm_X753ZBfaW8vwQs0ht7UrLyed5E:3
Inside a function the logs only originate from the longer string version. This affects any code outside a function, it seems to run twice...I've included the full code for reference if required.
I have found many similarly titled or tagged questions but all of them were due to the logging occurring in a loop or otherwise, I couldn't find any where it happened in the base code.
EDIT: My code does have two console.logs but that results in 4 prints in that case sorry for being unclear on that.
JavaScript
//Azibuda
console.log("2x");
//Get the SVG element
var svg = d3.select("svg");
var width = 960, height = 600;
var color = d3.scaleOrdinal(d3.schemeCategory20);
var link = svg.append("g").selectAll(".link");
var node = svg.append("g").selectAll(".node");
var label = svg.append("g").selectAll(".label");
//Begin the force simulation
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) { return d.id; }).distance(50).strength(0.3))
.force("charge", d3.forceManyBody().strength(-15))
.force("center", d3.forceCenter(width / 2, height / 2));
//Highlight variables
var highlight_color = "blue";
var tHighlight = 0.05;
var config;
var linkedByIndex = {};
var linksAsString = {};
//Get the data
d3.json("/../../data.json", function (data) {
config = data;
if (!localStorage.graph)
{
localStorage.graph = JSON.stringify(data);
}
update();
});
function update() {
console.log(localStorage.graph);
//Create an array of source,target containing all links
config.links.forEach(function (d) {
linkedByIndex[d.source + "," + d.target] = true;
linkedByIndex[d.target + "," + d.source] = true;
//linksAsString[d.index] = d.source + "," + d.target;
});
var nodesAsString = {};
config.nodes.forEach(function (d) {
nodesAsString[d.index] = d.id + "," + d.radius;
});
//Draw links
link = link.data(config.links);
link.exit().remove();
link = link.enter().append("line")
.attr("class", "link")
.attr("stroke-width", 2)
.attr("stroke", "#888")
//.attr("opacity", function (d) { if (d.target.radius > 7) { return 1 }; return 0; })
.merge(link);
node = node.data(config.nodes);
node.exit().remove();
node = node.enter().append("circle")
.attr("class", "node")
.attr("r", function(d) { return d.radius; })
.attr("fill", function (d) { return color(d.id); })
.attr("stroke", "black")
// .attr("pointer-events", function (d) { if (d.radius <= 7) { return "none"; } return "visibleAll"; })
// .attr("opacity", function (d) { if (d.radius <= 7) { return 0; } return 1; })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("mouseover", mouseOver)
.on("mouseout", mouseOut)
.merge(node);
label = label.data(config.nodes);
label.exit().remove();
label = label.enter().append("text")
.attr("class", "label")
.attr("dx", function (d) { return d.radius * 1.25; })
.attr("dy", ".35em")
.attr("opacity", function (d) { if (d.radius <= 7) { return 0; } return 1; })
.attr("font-weight", "normal")
.style("font-size", 10)
.text(function (d) { return d.id; })
.merge(label);
//Add nodes to simulation
simulation
.nodes(config.nodes)
.on("tick", ticked);
//Add links to simulation
simulation.force("link")
.links(config.links);
simulation.alphaTarget(0.3).restart();
}
//Animating by ticks function
function ticked() {
node
.attr("cx", function (d) { return d.x = Math.max(d.radius, Math.min(width - d.radius, d.x)); })
.attr("cy", function (d) { return d.y = Math.max(d.radius, Math.min(height - d.radius, d.y)); });
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; });
label
.attr("x", function (d) { return d.x = Math.max(d.radius, Math.min(width - d.radius, d.x)); })
.attr("y", function (d) { return d.y = Math.max(d.radius, Math.min(height - d.radius, d.y)); });
}
//Using above array, check if two nodes are linked
function isConnected(node1, node2) {
return linkedByIndex[node1.id + "," + node2.id] || node1.index == node2.index;
}
//Highlight a node
function setHighlight(d) {
svg.style("cursor", "pointer");
//Set highlighted stroke around the current node, text and its links
node.style("stroke", function (tNode) {
return isConnected(d, tNode) ? highlight_color : "black";
});
label.style("font-weight", function (tNode) {
return isConnected(d, tNode) ? "bold" : "normal";
});
link.style("stroke", function (tNode) {
return tNode.source.index == d.index || tNode.target.index == d.index ? highlight_color : "#888";
});
}
//Drag/mousedown on a node
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
//Dragging a node
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
//Highlight/focus on held down node
setFocus(d);
setHighlight(d);
}
//End drag/mouseup on a node
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
//Mouse over on a node
function mouseOver(d) {
setFocus(d);
setHighlight(d);
}
//Mouse off of a node
function mouseOut(d) {
unFocus(d);
highlightOff();
}
//Turning off highlight
function highlightOff() {
svg.style("cursor", "default");
//Set node attributes back to normal
node.style("stroke", "black");
label.style("font-weight", "normal");
link.style("stroke", "#888");
}
//Focus on a node
function setFocus(d) {
//Set opacity of all non-connected nodes and their elements (text/links) to faded
node.style("opacity", function (tNode) {
return isConnected(d, tNode) ? 1 : tHighlight;
});
label.style("opacity", function (tNode) {
return isConnected(d, tNode) ? 1 : tHighlight;
});
link.style("opacity", function (tNode) {
return tNode.source.index == d.index || tNode.target.index == d.index ? 1 : tHighlight;
});
}
//Unfocus on a node (reset all to normal)
function unFocus(d) {
//node.style("opacity", function (d) { if (d.radius <= 7) { return 0; } return 1; });
//node.style("pointer-events", function (d) { if (d.radius <= 7) { return "none"; } return "visibleAll"; })
node.style("opacity", 1);
label.style("opacity", function (d) { if (d.radius <= 7) { return 0; } return 1; });
//link.style("opacity", function (d) { if (d.target.radius > 7) { return 1 }; return 0; });
link.style("opacity", 1);
}
function updateR()
{
console.log(config.nodes[2]);
config.nodes.splice(2, 1);
update();
}
var temp = JSON.parse(localStorage.graph);
//temp.nodes.push({"id":"Cheese", "radius":20});
//localStorage.graph = JSON.stringify(temp);
console.log("2x");
HTML
#{
ViewData["Title"] = "Home Page";
}
<link href="/css/geico-design-kit.css" rel="stylesheet">
<script src="~/js/geico-design-kit.bundle.js"></script>
<script src="~/js/d3.js"></script>
<script src="~/js/site.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
.link line {
stroke: #888;
}
text {
pointer-events: none;
font: 10px sans-serif;
}
</style>
<div class="col-md-12">
<div class="col-md-3">
<h2>Quick Links</h2>
<ul class="list list--unordered">
<li>Example Quick Links Here</li>
<li>Google</li>
<li>Google</li>
</ul>
</div>
</div>
<button onclick="updateR()" type="button" style="background-color:red">DO NOT PRESS!</button>
<svg id="container" width="960" height="600" style="border:1px solid black;"></svg>
<form id="nodes"></form>
If this ASP.NET view is using a layout view and the same file is referenced in that layout file, the rendered output would have multiple references.
Using D3.js's force layout I'm trying to make the links automatically generate based on the node data. The nodes already appear exactly as expected.
The database json strings use the following format:
{
"id": 1,
"title": "name"
},
{
"id": 2,
"title": "other name",
"primaryDependants": 1
}
and I'm basically trying to have it say:
'for each node, check if it has a primaryDependant property, if it does then make a link between that node and the one identified as the primary dependant.'
however I haven't dealt with force graphs before and tutorials are few and far between so I'm really struggling with how to make any change to my code without breaking it. Currently it is based on the answer here and I use the Meteor framework if that's of any relevance.
Template.tree.rendered = function () {
var graph = new myGraph("#svgdiv");
Lessons.find().observe({
added: function (doc) {
graph.addNode(doc._id, doc.title);
},
removed: function (doc) {
graph.removeNode(doc._id);
}
});
function myGraph(el) {
w = 1500,
h = 1000;
var svg = d3.select(el)
.append("svg")
.attr("width", w)
.attr("height", h)
.attr("pointer-events", "all")
svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "lightgrey");
var vis = svg.append('g');
var force2 = d3.layout.force();
var links = force2.links();
var nodes = force2.nodes();
var update = function () {
var link = vis.selectAll("line")
.data(links, function(d) {return d.source.id + "-" + d.target.id;});
link.enter().append("line")
.attr("id",function(d){return d.source.id + "-" + d.target.id;})
.attr("class","link");
link.append("title")
.text(function(d){
return d.value;
});
link.exit().remove();
var node = vis.selectAll("g")
.data(nodes, function(d) { return d.id;});
var nodeEnter = node.enter()
.append("g")
.call(force2.drag)
.append("circle")
.attr("r", 8)
.attr("fill", "#585858")
.attr("stroke", "#008db7")
.attr("stroke-width", 3)
.attr("id", function(e) { return "Node;"+e.id;})
.attr("class", ( function(f){return f.id;}));
node.exit().remove();
force2.on("tick", function() {
node.attr("transform", function(g) { return "translate(" + g.x + "," + g.y + ")"; });
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; });
});
force2
.gravity(.02)
.linkDistance( 200 )
.size([w, h])
.start();
};
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;
}
};
};
this.addNode = function (id, title) {
nodes.push({"id":id,"title":title});
update();
};
this.addLink = function (source, target, value) {
links.push({"source":findNode(source),"target":findNode(target),"value":value});
update();
};
this.removeNode = function (id) {
var i = 0;
var n = findNode(id);
nodes.splice(findNodeIndex(id),1);
update();
};
}
To create the links array based on your description:
var dataset = [{
"id": 1,
"title": "name"
},
{
"id": 2,
"title": "other name",
"primaryDependants": 1
}];
var links = [];
dataset.forEach(function(d){
if(d.primaryDependants){
links.push({source: d.id,
target: d.primaryDependants});
}});
console.log(links)
I have a series of nodes and links in a force directed graph and I have the nodes able to drag and return to their starting position nicely but the links won't follow them. I thought the tick function would automatically update the ends of each link... I have a fiddle here
My two main questions thus far are a.) why don't the links follow the nodes and how do I make it do so... b.) I've noticed when I run the script either on fiddle or on my browser there is a strange delay until I can drag a node, why is that and how do I fix it?
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.Chip{
fill: red;
/*stroke: black;*/
stroke-width: 2px;
}
.Abstraction{
fill: blue;
/*stroke: black;*/
stroke-width: 2px;
}
.Properties{
fill: green;
stroke: black;
stroke-width: 2px;
}
.link{
stroke: #777;
stroke-width: 2px;
}
</style>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
var width = 960, height = 500, colors = d3.scale.category10();
var svg = null, force = null;
var circle = null, path = null;
var nodes = null, links = null;
var nodesArray = null, linkArray = null;
var count = 0;
var element = "body"; var numEdges = 4, numNodes = 5;
var i = 0; var L = 16, r = 12, lineLimit = 10;
var d = 2 * r + L;
var R = (count - 1) * d;
var m = width / 2;
var X;
var drag = d3.behavior.drag()
.on('dragstart', dragstart)
.on('drag', drag)
.on('dragend', dragend);
svg = d3.selectAll(element).append('svg').attr('width', width).attr('height', height);
nodes = d3.range(numNodes).map(function () {
X = m - (R / 2) + (i * d);
++i;
return {
x: X,
y: (height) / 3,
fx: X,
fy: height / 3,
id: i-1,
reflexive: true
};
});
for (var i = 0; i < numNodes; ++i) {
d3.select(element).append("h3").text("Node " + i + ": " + nodes[i].id);
}
i = -1;
links = d3.range(numEdges).map(function () {
i++;
return {
//
source: nodes[i],
target: nodes[i+1],
left: false,
right: true
}
});
for (var i = 0; i < numEdges; ++i) {
d3.select(element).append("h3").text("Source: " + links[i].source.id + " Target: " + links[i].target.id);
}
force = d3.layout.force().size([width, height]).nodes(nodes).links(links).linkDistance(40).linkStrength(0.1).charge(-300);
linkArray = svg.selectAll('.link').data(links).enter().append('line').attr('class', 'link')
.attr('x1', function (d) {
return nodes[d.source.id].x;
})
.attr('y1', function (d) { return nodes[d.source.id].y; })
.attr('x2', function (d) { return nodes[d.target.id].x; })
.attr('y2', function (d) { return nodes[d.target.id].y; });
nodeArray = svg.selectAll("circle").data(nodes).enter().append('circle').attr('class', "Properties").attr('r', 12)
.attr('cx', function (d) { return d.x })
.attr('cy', function (d) { return d.y })
.style('cursor', 'pointer').call(drag);
force.on('tick', tick);
force.start();
function dragmove(d) {
d3.select(this)
.attr("cx", d.x = Math.max(radius, Math.min(width - radius, d3.event.x)))
.attr("cy", d.y = Math.max(radius, Math.min(height - radius, d3.event.y)));
}
var originalPosition = [];
function dragstart(d) {
originalPosition[0] = d.x;
originalPosition[1] = d.y;
console.log("Start: ", originalPosition[0], originalPosition[1]);
}
function drag() {
var m = d3.mouse(this);
d3.select(this)
.attr('cx', m[0])
.attr('cy', m[1]);
}
function dragend(d) {
console.log("End: ", d.x, d.y);
d3.select(this).transition().attr('cx', originalPosition[0]).attr('cy', originalPosition[1]);
}
function tick() {
nodeArray
.attr('cx', function (d) { return d.x; })
.attr('cy', function (d) { return d.y; });
console.log("ticking");
linkArray
.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; });
}
</script>
I know this is super late, but maybe it will help someone else.
Basically, you were setting the node positions on dragstart but not the links. So as you set the node position just call the tick function to move the links.
Here is your updated drag function :
function dragstart(d, i) {
force.stop() // stops the force auto positioning before you start dragging
originalPosition[0] = d.x;
originalPosition[1] = d.y;
}
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();
}
function dragend(d, i) {
d.x = originalPosition[0];
d.y = originalPosition[1];
d3.select(this).transition().attr('cx', originalPosition[0]).attr('cy', originalPosition[1]);
tick();
}
Notice the names on these functions too. You had a variable 'drag' but you also had a function 'drag' so I don't know how it actually worked without throwing an error.
Updated fiddle : https://jsfiddle.net/g9g9xe6k/6/
A tad late, but hope it helps :)
I have been using R2D3 for drawing a force directed layout. When i run it on IE8 i see Arg: Illegal input string in Vector2D as seen below:
This happens for transform attribute i apply for the layout.
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
I doubt the translate function returns some value the browser engine is not able to read. Possible a large number. Is there any way I can position the nodes without using transform attr. Following is my code:
d3.json("egoGraph.json", function(json) {
nodeSet = json.nodes;
linkSet = json.links;
var width = 600;
var height = 500;
var centerNodeSize = 30;
var nodeSize = 10;
var rscale = d3.scale.linear().domain([0, d3.max(nodeSet, function (d) {
return d.data
})]).range([5, 20]);
var color = ['#B43104','#F5ECCE', '#F3E2A9', '#F7D358', '#FFBF00', '#FF8000'];
// Create a canvas...
var svgCanvas = d3.select('#chart').append("svg:svg").attr(
"width", width).attr("height", height).append("svg:g")
.attr("class", "focalNodeCanvas").attr("transform",
"translate(" + width / 3.333333 + "," + height / 2.352 + ")")
var node_hash = [];
//var type_hash = [];
// Create a hash that allows access to each node by its id
nodeSet.forEach(function(d, i) {
node_hash[d.journalname] = d;
//type_hash[d.type] = d.type;
});
// Append the source object node and the target object node to each link records...
linkSet.forEach(function(d, i) {
d.source = node_hash[d.sourceId];
d.target = node_hash[d.targetId];
if (d.sourceId == focalNodeID) {
d.direction = "OUT";
} else {
d.direction = "IN";
}
});
// Create a force layout and bind Nodes and Links
var force = d3.layout.force().nodes(nodeSet).links(linkSet).charge(
-1000)
//.gravity(.2)
//.linkStrength(20)
.size([ width / 8, height / 10 ]).linkDistance(function(d) {
if (width < height) {
return width * 1 / 3;
} else {
return height * 1 / 3
}
}) // Controls edge length
.on("tick", tick).start();
// Draw lines for Links between Nodes
var link = svgCanvas.selectAll(".gLink").data(force.links())
.enter().append("g").attr("class", "gLink").append("line")
.attr("class", "link").attr("stroke-width", function(d) {
return (Math.random() * (10 - 1) + 1);
}).style("stroke", "#808080").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;
});
// Create Nodes
var node = svgCanvas.selectAll(".node").data(force.nodes()).enter()
.append("g").attr("class", "node").attr("fixed", function(d) {
return true
}).call(force.drag);
// Append circles to Nodes
node.append("circle").attr("x", function(d) {
return 100;
}).attr("y", function(d) {
return 30;
}).attr("r", function(d) {
if (d.journalname == focalNodeID) {
return centerNodeSize;
} else {
return rscale(d.data);
}
}) // Node radius
.style("fill", function(d) { return color[Math.floor(Math.random() * (5 - 0 + 1)) + 0]; }) // Make the nodes hollow looking
.on("mouseover", function() { d3.select(this).attr("stroke", "#808080").attr("stroke-width", 5);})
.on("mouseout", function(){ d3.select(this).attr("stroke", function(d) { return color[Math.floor(Math.random() * (5 - 0 + 1)) + 0]; }).attr("stroke-width", 0);})
.call(force.drag);
// Append text to Nodes
node.append("text").attr("x", function(d) {
if (d.journalname == focalNodeID) {
return 0;
} else {
return 20;
}
}).attr("y", function(d) {
if (d.journalname == focalNodeID) {
return 0;
} else {
return -10;
}
}).attr("text-anchor", function(d) {
return "middle";
}).attr("font-family", "Arial, Helvetica, sans-serif").style(
"font", "normal 12px Arial").text(function(d) {
return d.journalname;
});
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 + ")";
});
}
});
transform doesn't work in IE8, and in IE9 you need the browser prefix ms-
So you will need to abstract your transform call, or use an expression in CSS
http://msdn.microsoft.com/en-us/library/ms532918(v=vs.85).aspx