Contamination in a graph using d3js - javascript
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>
Related
D3 stacked segment chart, sort by totals
I have been trying to create a stacked segment chart for the past few days and between all the trigonometry and key stacking it has truly been a nightmare for me to apply what seems to be the most basic of tweaks. Currently my segment chart has data that is sorted in descending order based on Active equity. I want to change this to have the data sorted by total. const csvData = `State,Active equity,Passive equity,Fixed income,Mixed assets BlackRock,1,17,0,0 Fidelity,13,2,0,0 SSgA,12,0,0,0 Hang Seng,11,0,0,0 UBS,9,0,0,1 Schroders,6,0,2,1 JP Morgan,5,2,0,1 Value Partners,1,0,6,0 First State,5,0,0,0 Invesco,4,1,0,0 HSBC,1,1,1,1 DBS,0,2,1,0 BOCI,1,1,1,0 CSOP,0,2,1,0 Principal,1,1,0,0 Allianz,2,1,0,0 Yuanta,0,2,1,0 Manulife,1,0,1,0 Aberdeen,2,0,0,0 Mirae,1,1,0,0 ,0,0,0,0`; //const data = d3.csvParse(csvData, d3.autoType); var margins = {top:20, bottom:300, left:30, right:100}; var height = 600; var width = 900; var totalWidth = width+margins.left+margins.right; var totalHeight = height+margins.top+margins.bottom; var outerRadius = (400 / 2); var innerRadius = 15; var svg = d3.select('body') .append('svg') .attr('width', totalWidth) .attr('height', totalHeight); var graphGroup = svg.append('g') .attr('transform', "translate(250,250)"); var x = d3.scaleBand() .range([0, 2 * Math.PI]) .align(0); var y = d3.scaleRadial() .range([innerRadius, outerRadius]); var z = d3.scaleOrdinal() .range(["#003366", "#4f81b9", "#95b3d7", "#f6d18b"]); d3.csv("csvData", function(d, i, columns) { for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]]; d.total = t; return d; }, function(error, data) { if (error) throw error; weave(data, function(a, b) { return b[data.columns[6]] - a[data.columns[6]]; }); x.domain(data.map(function(d) { return d.State; })); y.domain([0, d3.max(data, function(d) { return d.total; })*1.3]); z.domain(data.columns.slice(1)); graphGroup.append('g') .selectAll('g') .data(d3.stack().keys(data.columns.slice(1))(data)) .enter().append("g") .selectAll(".bg-arc2") .data(function(d) { return d; }) .enter().append("path") .attr("d", d3.arc() .innerRadius(innerRadius) .outerRadius(outerRadius+2) .startAngle(function(d) { return x(d.data.State); }) .endAngle(function(d) { return x(d.data.State) + x.bandwidth()*.90; }) .padAngle(0.1) .padRadius(innerRadius)) .attr('class','bg-arc2') .attr('fill','none') .attr('stroke-width','4px') .attr('stroke','#003366'); graphGroup.append('circle') .attr('cx',0) .attr('cy',0) .attr('r',200) .style('fill','#d9d9d9'); graphGroup.append('g') .selectAll('g') .data(d3.stack().keys(data.columns.slice(1))(data)) .enter().append("g") .selectAll(".bg-arc") .data(function(d) { return d; }) .enter().append("path") .attr("d", d3.arc() .innerRadius(innerRadius) .outerRadius(outerRadius) .startAngle(function(d) { return x(d.data.State); }) .endAngle(function(d) { return x(d.data.State) + x.bandwidth()*.95; }) .padAngle(0.1) .padRadius(innerRadius)) .attr('class','bg-arc') .attr('fill','#fff'); graphGroup.append('circle') .attr('cx',0) .attr('cy',0) .attr('r',innerRadius) .style('fill','#fff'); var stackedData = d3.stack().keys(data.columns.slice(1))(data); var stackedData2 = stackedData.sort(function(a,b) { return d3.descending(a[0].data.total, b[0].data.total)}); console.log(stackedData[0][0].data.total) console.log(stackedData2); graphGroup.append("g") .selectAll("g") .data(stackedData) .enter().append("g") .attr("fill", function(d) { return z(d.key); }) .selectAll("path") .data(function(d) { return d; }) .enter().append("path") .attr("d", d3.arc() .innerRadius(function(d) { return y(d[0]); }) .outerRadius(function(d) { return y(d[1]); }) .startAngle(function(d) { return x(d.data.State); }) .endAngle(function(d) { return x(d.data.State) + x.bandwidth()*.95; }) .padAngle(0.04) .padRadius(innerRadius)); var label = graphGroup.append("g") .selectAll("g") .data(data) .enter().append("g") .attr("text-anchor", "middle") .attr("transform", function(d) { return "rotate(" + ((x(d.State) + x.bandwidth() / 2) * 180 / Math.PI - 90) + ")translate(" + (outerRadius+25) + ",0)"; }); label.append("text") .attr("transform", function(d) { return (x(d.State) + x.bandwidth() / 2 + Math.PI / 2) % (2 * Math.PI) < Math.PI ? "rotate(90)translate(0,16)" : "rotate(-90)translate(0,-9)"; }) .text(function(d) { return d.State; }); var yAxis = graphGroup.append("g") .attr("text-anchor", "end"); var yTick = yAxis .selectAll("g") .data(y.ticks(10).slice(1)) .enter().append("g"); }); function weave(array, compare) { var i = -1, j, n = array.sort(compare).length, weave = new Array(n); while (++i < n) weave[i] = array[(j = i << 1) >= n ? (n - i << 1) - 1 : j]; while (--n >= 0) array[n] = weave[n]; } <script src="https://d3js.org/d3.v4.min.js"></script> <script src="https://gist.githubusercontent.com/mbostock/3686329aa6e1f5938df8eef12ec353fe/raw/1ab722df937c3ac86cac8292e34cfc7279b017f8/d3-scale-radial.js"></script> Relevant code here: var stackedData = d3.stack().keys(data.columns.slice(1))(data); var stackedData2 = stackedData.sort(function(a,b) { return d3.descending(a[0].data.total, b[0].data.total)}); console.log(stackedData[0][0].data.total) console.log(stackedData2); I checked via the console log to make sure I was slicing at the correct place, which I was, as confirmed by console.log(stackedData[0][0].data.total) which returned the correct value of 18. However I can't apply the sort as desired. The data is still sorted by Active equity and not total. Question The default sort for stacked radial charts seems to be whatever the first variable is. In my case, it's Active equity. With that in mind, based off my progress above, what is keeping me from applying the descending order sort by data.total as opposed to the default?
Your question is not clear: are you talking about sorting the segments within each slice, or are you talking about sorting the slices themselves? For the first case, there is an method named order, that you can use with the stack generator (the link goes to v5 docs, which are a bit different from v4). However, because you said "I want to change [the order] to have the data sorted by total", it seems to me that you're talking about sorting the slices. If that is correct, two observations: it's not sorted by Active equity, right now it's just the order of the objects in the original data array. For sorting by total you just need to change that array: data.sort(function(a, b) { return b.total - a.total; }); Also, get rid of the weave function. Here is the result: (function(global, factory) { typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("d3-scale")) : typeof define === "function" && define.amd ? define(["exports", "d3-scale"], factory) : (factory(global.d3 = global.d3 || {}, global.d3)); }(this, function(exports, d3Scale) { 'use strict'; function square(x) { return x * x; } function radial() { var linear = d3Scale.scaleLinear(); function scale(x) { return Math.sqrt(linear(x)); } scale.domain = function(_) { return arguments.length ? (linear.domain(_), scale) : linear.domain(); }; scale.nice = function(count) { return (linear.nice(count), scale); }; scale.range = function(_) { return arguments.length ? (linear.range(_.map(square)), scale) : linear.range().map(Math.sqrt); }; scale.ticks = linear.ticks; scale.tickFormat = linear.tickFormat; return scale; } exports.scaleRadial = radial; Object.defineProperty(exports, '__esModule', { value: true }); })); const csvData = `State,Active equity,Passive equity,Fixed income,Mixed assets BlackRock,1,17,0,0 Fidelity,13,2,0,0 SSgA,12,0,0,0 Hang Seng,11,0,0,0 UBS,9,0,0,1 Schroders,6,0,2,1 JP Morgan,5,2,0,1 Value Partners,1,0,6,0 First State,5,0,0,0 Invesco,4,1,0,0 HSBC,1,1,1,1 DBS,0,2,1,0 BOCI,1,1,1,0 CSOP,0,2,1,0 Principal,1,1,0,0 Allianz,2,1,0,0 Yuanta,0,2,1,0 Manulife,1,0,1,0 Aberdeen,2,0,0,0 Mirae,1,1,0,0 ,0,0,0,0`; const data = d3.csvParse(csvData, function(d, i, columns) { for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]]; d.total = t; return d; }); data.sort(function(a, b) { return b.total - a.total; }); var margins = { top: 20, bottom: 300, left: 30, right: 100 }; var height = 600; var width = 900; var totalWidth = width + margins.left + margins.right; var totalHeight = height + margins.top + margins.bottom; var outerRadius = (400 / 2); var innerRadius = 15; var svg = d3.select('body') .append('svg') .attr('width', totalWidth) .attr('height', totalHeight); var graphGroup = svg.append('g') .attr('transform', "translate(250,250)"); var x = d3.scaleBand() .range([0, 2 * Math.PI]) .align(0); var y = d3.scaleRadial() .range([innerRadius, outerRadius]); var z = d3.scaleOrdinal() .range(["#003366", "#4f81b9", "#95b3d7", "#f6d18b"]); x.domain(data.map(function(d) { return d.State; })); y.domain([0, d3.max(data, function(d) { return d.total; }) * 1.3]); z.domain(data.columns.slice(1)); graphGroup.append('g') .selectAll('g') .data(d3.stack().keys(data.columns.slice(1))(data)) .enter().append("g") .selectAll(".bg-arc2") .data(function(d) { return d; }) .enter().append("path") .attr("d", d3.arc() .innerRadius(innerRadius) .outerRadius(outerRadius + 2) .startAngle(function(d) { return x(d.data.State); }) .endAngle(function(d) { return x(d.data.State) + x.bandwidth() * .90; }) .padAngle(0.1) .padRadius(innerRadius)) .attr('class', 'bg-arc2') .attr('fill', 'none') .attr('stroke-width', '4px') .attr('stroke', '#003366'); graphGroup.append('circle') .attr('cx', 0) .attr('cy', 0) .attr('r', 200) .style('fill', '#d9d9d9'); graphGroup.append('g') .selectAll('g') .data(d3.stack().keys(data.columns.slice(1))(data)) .enter().append("g") .selectAll(".bg-arc") .data(function(d) { return d; }) .enter().append("path") .attr("d", d3.arc() .innerRadius(innerRadius) .outerRadius(outerRadius) .startAngle(function(d) { return x(d.data.State); }) .endAngle(function(d) { return x(d.data.State) + x.bandwidth() * .95; }) .padAngle(0.1) .padRadius(innerRadius)) .attr('class', 'bg-arc') .attr('fill', '#fff'); graphGroup.append('circle') .attr('cx', 0) .attr('cy', 0) .attr('r', innerRadius) .style('fill', '#fff'); var stackedData = d3.stack() .keys(data.columns.slice(1)) (data); graphGroup.append("g") .selectAll("g") .data(stackedData) .enter().append("g") .attr("fill", function(d) { return z(d.key); }) .selectAll("path") .data(function(d) { return d; }) .enter().append("path") .attr("d", d3.arc() .innerRadius(function(d) { return y(d[0]); }) .outerRadius(function(d) { return y(d[1]); }) .startAngle(function(d) { return x(d.data.State); }) .endAngle(function(d) { return x(d.data.State) + x.bandwidth() * .95; }) .padAngle(0.04) .padRadius(innerRadius)); var label = graphGroup.append("g") .selectAll("g") .data(data) .enter().append("g") .attr("text-anchor", "middle") .attr("transform", function(d) { return "rotate(" + ((x(d.State) + x.bandwidth() / 2) * 180 / Math.PI - 90) + ")translate(" + (outerRadius + 25) + ",0)"; }); label.append("text") .attr("transform", function(d) { return (x(d.State) + x.bandwidth() / 2 + Math.PI / 2) % (2 * Math.PI) < Math.PI ? "rotate(90)translate(0,16)" : "rotate(-90)translate(0,-9)"; }) .text(function(d) { return d.State; }); var yAxis = graphGroup.append("g") .attr("text-anchor", "end"); var yTick = yAxis .selectAll("g") .data(y.ticks(10).slice(1)) .enter().append("g"); function weave(array, compare) { var i = -1, j, n = array.sort(compare).length, weave = new Array(n); while (++i < n) weave[i] = array[(j = i << 1) >= n ? (n - i << 1) - 1 : j]; while (--n >= 0) array[n] = weave[n]; } <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
Updating D3 bubble radius with collide simulation
I've been wrestling very hard with D3 to try to make a simple bubble-chart using force collide that live-updates the bubble size. I can get the chart to show on the first data update with force collide. However subsequent data calls freeze the chart and the sizes are never updated: https://jsfiddle.net/d2zcfjfa/1/ node = svg.selectAll('g.node') .data(root.children) .enter() .append('g') .attr('class', 'node') .append('circle') .attr('r', function(d) { return d.r * 1.4; }) .attr('fill', function(d) { return color(d.data.name); }) .call(d3.drag() .on("start", dragStart) .on("drag", dragged) .on("end", dragEnd)); var circleUpdate = node.select('circle') .attr('r', function(d) { return d.r; }); simulation.nodes(root.children); I can get updates to work but only without using the collide simulation as seen here: https://jsfiddle.net/rgdox7g7/1/ node = svg.selectAll('g.node') .data(root.children) .enter() .append('g') .attr('id', function(d) { return d.id; }) .attr('class', 'node') .attr('transform', function(d) { return "translate(" + d.x + "," + d.y + ")"; }); var nodeUpdate = svg.selectAll('g.node') .transition() .duration(2000) .ease(d3.easeLinear); var circleUpdate = nodeUpdate.select('circle') .attr('r', function(d) { return d.r; }); node.append("circle") .attr("r", function(d) { return d.r; }) .style('fill', function(d) { return color(d.data.name); }); Everything I have tried to mix these two solutions together simply does not work. I have scoured the internet for other examples and nothing I can find is helping. Can someone please help me understand what to do? I never thought D3 would be such a frustration!
stackoverflow: the place where you have to answer your own questions. here is my working solution: https://jsfiddle.net/zc0fgh6y/ var subscription = null; var width = 600; var height = 300; var maxSpeed = 1000000; var pack = d3.pack().size([width, height]).padding(0); var svg = d3.select('svg'); var node = svg.selectAll("g.node"); var root; var nodes = []; var first = true; var scaleFactor = 1.4; var color = d3.interpolateHcl("#0faac3", "#dd2323"); var forceCollide = d3.forceCollide() .strength(.8) .radius(function(d) { return d.r; }).iterations(10); var simulationStart = d3.forceSimulation() .force("forceX", d3.forceX(width/2).strength(.04)) .force("forceY", d3.forceY(height/2).strength(.2)) .force('collide', forceCollide) .on('tick', ticked); var simulation = d3.forceSimulation() .force("forceX", d3.forceX(width/2).strength(.0005)) .force("forceY", d3.forceY(height/2).strength(.0025)) .force('collide', forceCollide) .on('tick', ticked); function ticked() { if (node) { node.attr('transform', function(d) { return "translate(" + d.x + "," + d.y + ")"; }).select('circle').attr('r', function(d) { return d.r; }); } } function rand(min, max) { return Math.random() * (max - min) + min; }; setInterval(function() { var hosts = []; for (var i = 0; i < 100; i++) { hosts.push({name: i, cpu: rand(10,100), speed: rand(0,maxSpeed)}); } root = d3.hierarchy({children: hosts}) .sum(function(d) { return d.cpu ? d.cpu : 0; }); var leaves = pack(root).leaves().map(function(item) { return { id: 'node-'+item.data.name, name: item.data.name, r: item.r * scaleFactor, x: width/2, y: height/2, cpu: item.data.cpu, speed: item.data.speed }; }); for (var i = 0; i < leaves.length; i++) { if (nodes[i] && nodes[i].id == leaves[i].id) { var oldR = nodes[i].newR; nodes[i].oldR = oldR; nodes[i].newR = leaves[i].r; nodes[i].cpu = leaves[i].cpu; nodes[i].speed = leaves[i].speed; } else { nodes[i] = leaves[i]; //nodes[i].r = 1; nodes[i].oldR = 1;//nodes[i].r; nodes[i].newR = leaves[i].r; } } if (first) { first = false; node = node.data(nodes, function(d) { return d.id; }); node = node.enter() .append('g') .attr('class', 'node'); node.append("circle") .style("fill", 'transparent'); node.append("text") .attr("dy", "0.3em") .style('fill', 'transparent') .style("text-anchor", "middle") .text(function(d) { return d.name;//.substring(0, d.r / 4); }); // transition in size node.transition() .ease(d3.easePolyInOut) .duration(950) .tween('radius', function(d) { var that = d3.select(this); var i = d3.interpolate(1, d.newR); return function(t) { d.r = i(t); that.attr('r', function(d) { return d.r; }); simulationStart.nodes(nodes).alpha(1); } }); // fade in text color node.select('text') .transition() .ease(d3.easePolyInOut) .duration(950) .style('fill', 'white'); // fade in circle size node.select('circle') .transition() .ease(d3.easePolyInOut) .duration(950) .style('fill', function(d) { return color(d.speed / maxSpeed); }); } else { // transition to new size node.transition() .ease(d3.easeLinear) .duration(950) .tween('radius', function(d) { var that = d3.select(this); var i = d3.interpolate(d.oldR, d.newR); return function(t) { d.r = i(t); that.attr('r', function(d) { return d.r; }); simulation.nodes(nodes).alpha(1); } }); // transition to new color node.select('circle') .transition() .ease(d3.easeLinear) .duration(950) .style('fill', function(d) { return color(d.speed / maxSpeed); }); } }, 1000);
D3 drag + links not moving
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 :)
Problems to label nodes in a particular force layout D3
I have made an algorithm with D3 to vizualize some datas. The algorithm is doing what i'm expecting of him. He separates nodes in multiple focis with the Force layout, links and nodes appears and disappears dynamicaly. The problems comes with the labeling of the nodes. D3 seems to do the work but multiples times for each nodes (when i look at it with firebug). Here is my code : var actions = [ {"action":"arrivee","id":"001","service":1}, {"action":"arrivee","id":"002","service":1}, {"action":"arrivee","id":"003","service":1}, {"action":"arrivee","id":"004","service":1}, {"action":"arrivee","id":"005","service":1}, {"action":"arrivee","id":"006","service":3}, {"action":"arrivee","id":"007","service":3}, {"action":"arrivee","id":"008","service":3}, {"action":"arrivee","id":"009","service":3}, {"action":"arrivee","id":"010","service":2}, {"action":"arrivee","id":"011","service":2}, {"action":"arrivee","id":"012","service":2}, {"action":"arrivee","id":"013","service":2}, {"action":"arrivee","id":"014","service":4}, {"action":"arrivee","id":"015","service":4}, {"action":"arrivee","id":"016","service":4}, {"action":"arrivee","id":"017","service":4}, {"action":"contact","id":"0","source":"001","target":"017"}, {"action":"contact","id":"1","source":"016","target":"012"}, {"action":"contact","id":"2","source":"004","target":"011"}, {"action":"contact","id":"3","source":"001","target":"010"}, {"action":"fincontact","id":"0"}, {"action":"depart","id":"017"}, {"action":"arrivee","id":"018","service":2}, {"action":"arrivee","id":"019","service":1}, {"action":"arrivee","id":"020","service":1}, {"action":"arrivee","id":"021","service":0}, {"action":"arrivee","id":"022","service":0}, {"action":"arrivee","id":"023","service":0}, {"action":"arrivee","id":"024","service":0}, {"action":"arrivee","id":"025","service":0}, {"action":"arrivee","id":"026","service":0}, {"action":"arrivee","id":"027","service":3}, {"action":"arrivee","id":"028","service":2}, {"action":"arrivee","id":"029","service":2}, {"action":"arrivee","id":"030","service":2}, {"action":"arrivee","id":"031","service":2}, {"action":"arrivee","id":"032","service":4}, {"action":"arrivee","id":"033","service":4}, {"action":"arrivee","id":"034","service":4}, {"action":"arrivee","id":"035","service":4}, {"action":"contact","id":"4","source":"013","target":"002"}, {"action":"contact","id":"5","source":"009","target":"008"}, {"action":"contact","id":"6","source":"005","target":"007"}, {"action":"contact","id":"7","source":"009","target":"014"}, {"action":"fincontact","id":"7"}, {"action":"fincontact","id":"6"}, {"action":"fincontact","id":"5"}, {"action":"fincontact","id":"4"}, {"action":"fincontact","id":"3"}, {"action":"fincontact","id":"2"}, {"action":"fincontact","id":"1"}, {"action":"depart","id":"016"}, {"action":"depart","id":"015"}, {"action":"depart","id":"014"}, {"action":"depart","id":"013"}, {"action":"depart","id":"012"}, {"action":"depart","id":"011"}, {"action":"depart","id":"010"}, {"action":"depart","id":"009"}, {"action":"depart","id":"008"}, {"action":"depart","id":"007"}, {"action":"depart","id":"006"}, {"action":"depart","id":"005"}, {"action":"depart","id":"004"}, {"action":"depart","id":"003"}, {"action":"depart","id":"002"}, {"action":"depart","id":"018"}, {"action":"depart","id":"019"}, {"action":"depart","id":"020"}, {"action":"depart","id":"021"}, {"action":"depart","id":"022"}, {"action":"depart","id":"023"}, {"action":"depart","id":"024"}, {"action":"depart","id":"025"}, {"action":"depart","id":"026"}, {"action":"depart","id":"027"}, {"action":"depart","id":"028"}, {"action":"depart","id":"029"}, {"action":"depart","id":"030"}, {"action":"depart","id":"031"}, {"action":"depart","id":"032"}, {"action":"depart","id":"033"}, {"action":"depart","id":"034"}, {"action":"depart","id":"035"}, {"action":"depart","id":"001"}] var vv = window, w = vv.innerWidth, h = vv.innerHeight; var rmax = 30; foci = [{x: 500, y: 150}, {x: 200, y: 500}, {x: 700, y: 500}, {x: 400, y: 700}, {x: 600, y: 700}]; //canevas selection var svg = d3.select("#animviz") .append("svg") .attr("width", w) .attr("height", h); var fill = d3.scale.category10(); //link and node class creation svg.append("g").attr("class", "links"); svg.append("g").attr("class", "nodes"); //to know if the graphs are up to date var uptodate = true; var nIntervId; //containers de noeuds et liens var nodes = [], links = []; var force = d3.layout.force() .nodes(nodes) .links(links) .size([w, h]) .friction(0.9) .charge(-50) .gravity(0.02) .linkDistance(50) .charge(-500) .on("tick", tick); var iter = 0; var node = svg.select(".nodes").selectAll(".node"); var link = svg.select(".links").selectAll(".link"); //repeat an action every "interval" var interval = 0.2; nIntervId = setInterval(function() { var action = readData(); addData(action); if(!uptodate){ update(); } }, interval*1000); function addData(action) { uptodate = false; switch(action.action) { case "arrivee": nodes.push({id: action.id, service: action.service}); break; case "depart": for (var i = nodes.length - 1; i >= 0; i--) { if(nodes[i].id == action.id){ nodes.splice(i, 1); } }; break; case "contact": var source; var target; for (var i = nodes.length - 1; i >= 0; i--) { if(nodes[i].id == action.source){ source = nodes[i]; } if(nodes[i].id == action.target){ target = nodes[i]; } }; links.push({source:source, target:target, id:action.id}); break; case "fincontact": for (var i = links.length - 1; i >= 0; i--) { if(links[i].id == action.id){ links.splice(i, 1); } }; break; default: uptodate = true; } } function readData(){ var n = iter; iter++; var data = actions[n]; return data; } function update() { force.start(); link = link.data(force.links(), function(d) { return d.source.id+"-"+d.target.id; }); link.enter() .append("line") .attr("class", "link") .attr("stroke", "#ccc") .attr("stroke-width", 2); link.exit().remove(); var r = d3.scale.sqrt() .domain(d3.extent(force.nodes(), function(d) {return d.weight; })) .range([15, rmax]); node = node.data(force.nodes(), function(d) { return d.id; }); node.enter() .append("g") .attr("class", "node") .call(force.drag); node.append("circle") .attr("r", 20) .style("stroke-width", "10") .style("fill", "white")//function(d, i) { return fill(d.service ); }) .style("stroke", function(d, i) { return d3.rgb(i & 1 ? "red" : "green") }) node.append("text") .attr("text-anchor","middle") .text(function(d) {return d.id}); node.exit().remove(); } function tick(e) { var k = 0.5 * e.alpha; nodes.forEach(function(o, i) { o.y += (foci[o.service].y - o.y) * k; o.x += (foci[o.service].x - o.x) * k; }); 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("cx", function(d) { return d.x = Math.max(rmax, Math.min(w - rmax, d.x)); }) .attr("cy", function(d) { return d.y = Math.max(rmax, Math.min(h - rmax, d.y)); }); } readData() read the dataframe, addData() is a big loop to have dynamical moves and update() is the D3 part where is my attempt to label my nodes. I'm missing something obvious. Thanks you.
After a few days of searches i'm able to post the working code as an answer. var actions = [ {"action":"arrivee","id":"001","service":1}, {"action":"arrivee","id":"002","service":1}, {"action":"arrivee","id":"003","service":1}, {"action":"arrivee","id":"004","service":1}, {"action":"arrivee","id":"005","service":1}, {"action":"arrivee","id":"006","service":3}, {"action":"arrivee","id":"007","service":3}, {"action":"arrivee","id":"008","service":3}, {"action":"arrivee","id":"009","service":3}, {"action":"arrivee","id":"010","service":2}, {"action":"arrivee","id":"011","service":2}, {"action":"arrivee","id":"012","service":2}, {"action":"arrivee","id":"013","service":2}, {"action":"arrivee","id":"014","service":4}, {"action":"arrivee","id":"015","service":4}, {"action":"arrivee","id":"016","service":4}, {"action":"arrivee","id":"017","service":4}, {"action":"contact","id":"0","source":"001","target":"017"}, {"action":"contact","id":"1","source":"016","target":"012"}, {"action":"contact","id":"2","source":"004","target":"011"}, {"action":"contact","id":"3","source":"001","target":"010"}, {"action":"fincontact","id":"0"}, {"action":"depart","id":"017"}, {"action":"arrivee","id":"018","service":2}, {"action":"arrivee","id":"019","service":1}, {"action":"arrivee","id":"020","service":1}, {"action":"arrivee","id":"021","service":0}, {"action":"arrivee","id":"022","service":0}, {"action":"arrivee","id":"023","service":0}, {"action":"arrivee","id":"024","service":0}, {"action":"arrivee","id":"025","service":0}, {"action":"arrivee","id":"026","service":0}, {"action":"arrivee","id":"027","service":3}, {"action":"arrivee","id":"028","service":2}, {"action":"arrivee","id":"029","service":2}, {"action":"arrivee","id":"030","service":2}, {"action":"arrivee","id":"031","service":2}, {"action":"arrivee","id":"032","service":4}, {"action":"arrivee","id":"033","service":4}, {"action":"arrivee","id":"034","service":4}, {"action":"arrivee","id":"035","service":4}, {"action":"contact","id":"4","source":"013","target":"002"}, {"action":"contact","id":"5","source":"009","target":"008"}, {"action":"contact","id":"6","source":"005","target":"007"}, {"action":"contact","id":"7","source":"009","target":"014"}, {"action":"fincontact","id":"7"}, {"action":"fincontact","id":"6"}, {"action":"fincontact","id":"5"}, {"action":"fincontact","id":"4"}, {"action":"fincontact","id":"3"}, {"action":"fincontact","id":"2"}, {"action":"fincontact","id":"1"}, {"action":"depart","id":"016"}, {"action":"depart","id":"015"}, {"action":"depart","id":"014"}, {"action":"depart","id":"013"}, {"action":"depart","id":"012"}, {"action":"depart","id":"011"}, {"action":"depart","id":"010"}, {"action":"depart","id":"009"}, {"action":"depart","id":"008"}, {"action":"depart","id":"007"}, {"action":"depart","id":"006"}, {"action":"depart","id":"005"}, {"action":"depart","id":"004"}, {"action":"depart","id":"003"}, {"action":"depart","id":"002"}, {"action":"depart","id":"018"}, {"action":"depart","id":"019"}, {"action":"depart","id":"020"}, {"action":"depart","id":"021"}, {"action":"depart","id":"022"}, {"action":"depart","id":"023"}, {"action":"depart","id":"024"}, {"action":"depart","id":"025"}, {"action":"depart","id":"026"}, {"action":"depart","id":"027"}, {"action":"depart","id":"028"}, {"action":"depart","id":"029"}, {"action":"depart","id":"030"}, {"action":"depart","id":"031"}, {"action":"depart","id":"032"}, {"action":"depart","id":"033"}, {"action":"depart","id":"034"}, {"action":"depart","id":"035"}, {"action":"depart","id":"001"}] var vv = window, w = vv.innerWidth, h = vv.innerHeight; foci = [{x: 500, y: 150}, {x: 200, y: 500}, {x: 700, y: 500}, {x: 400, y: 700}, {x: 600, y: 700}]; var svg = d3.select("body").append("svg") .attr("width", w) .attr("height", h); var links = [], nodes = []; var force = d3.layout.force() .nodes(nodes) .links(links) .size([w, h]) .friction(0.9) .charge(-50) .gravity(0.02) .linkDistance(50) .charge(-500) .on("tick", tick); var link; var node; var iter = 0; var interval = 0.2; var uptodate = true; var nIntervId; nIntervId = setInterval(function() { //alert("je passe"); var action = readData(); addData(action); if(!uptodate){ update(); } }, interval*1000); function addData(action) { uptodate = false; switch(action.action) { case "arrivee": nodes.push({id: action.id, service: action.service}); break; case "depart": for (var i = nodes.length - 1; i >= 0; i--) { if(nodes[i].id == action.id){ nodes.splice(i, 1); } }; break; case "contact": var source; var target; for (var i = nodes.length - 1; i >= 0; i--) { if(nodes[i].id == action.source){ source = nodes[i]; } if(nodes[i].id == action.target){ target = nodes[i]; } }; links.push({source:source, target:target, id:action.id}); break; case "fincontact": for (var i = links.length - 1; i >= 0; i--) { if(links[i].id == action.id){ links.splice(i, 1); } }; break; default: uptodate = true; } } function readData(){ var n = iter; iter++; var data = actions[n]; return data; } function update(){ force.start(); link = svg.selectAll(".link") .data(force.links()); link.enter().append("line") .attr("class", "link"); link.exit().remove(); node = svg.selectAll(".node") .data(force.nodes()); node.enter().append("g") .attr("class", "node") .call(force.drag); node.append("circle") .attr("r", 20) .style("stroke-width", "10") .style("fill", "white")//function(d, i) { return fill(d.service ); }) .style("stroke", function(d, i) { return d3.rgb(i & 1 ? "red" : "green") }); node.append("text") .attr("text-anchor","middle") .text(function(d) { return d.id }); node.exit().remove(); } function tick(e) { var k = 0.5 * e.alpha; nodes.forEach(function(o, i) { o.y += (foci[o.service].y - o.y) * k; o.x += (foci[o.service].x - o.x) * k; }); 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 + ")"; }); }; Thank you for helping.
Error with force cluster layout d3
I'm trying to do a graph like this example: http://bl.ocks.org/mbostock/1747543 The problem that I have is that when I execute my html, there are an error that says: SyntaxError: expected expression, got '.' My code is: var width = 900, height = 500; var color = d3.scale.category20(); var force = d3.layout.force() .gravity(.1) .charge(-120) .linkDistance(30) .size([width, height]); var svg = d3.select("body").select('#contenedor').select('#contenido').append("svg") .attr("width", width) .attr("height", height); d3.json("fisica_noms.json", function(error, graph) { if (error) throw error; var clusters = [] var getColors = function(category){ var nods = graph.nodes; var subs = []; var m = {}; for (var ele in nods){ for(var categs in ele){ if (!m[categs] && categs == category){ m[categs] = true; subs.push(categs); }; }; }; return subs; }; var sameCategory = function(category,subCategory){ var nods = graph.nodes; var subs = []; for(var i=0; i<nods.length;i++){ if(nods[i][category]===subCategory){ subs.push(nods[i])}; }; return subs }; var searchClusters = function(){ var nods = graph.nodes; var nCom = getColors('Comunitat').length; for(var i=0; i<nCom;i++){ clusters.push(sameCategory('Comunitat',i)); clusters[i] = d3.max(clusters[i], function(d) {return d.degree;}); }; force .nodes(graph.nodes) .links(graph.links) .start(); var link = svg.selectAll(".link") .data(graph.links) .enter().append("line") .attr("class", "link") .style("weight", function(d) { return Math.sqrt(d.value); }); force.linkStrength(function(link){return link.value}) ; var node = svg.selectAll(".node") .data(graph.nodes) .enter().append("circle") .attr("class", "node") .attr("r", function(d){return d.degree/60}) .style("fill", function(d) { return color(d.Comunitat); }) .call(force.drag) .on('click',function(d){showInfo(d);}); node.append("title") .text(function(d) { return d.id; }); force.on("tick",tick); function tick (e) { 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; }); console.log(e); node.attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); .each(cluster(10 * e.alpha * e.alpha)) }; // Move d to be adjacent to the cluster node. function cluster(alpha) { return function(d) { var cluster = clusters[d.Comunitat]; if (cluster === d) return; var x = d.x - cluster.x, y = d.y - cluster.y, l = Math.sqrt(x * x + y * y), r = d.degree/60 + cluster.degree/60; if (l != r) { l = (l - r) / l * alpha; d.x -= x *= l; d.y -= y *= l; cluster.x += x; cluster.y += y; } }; } }); }; The console says that my error is here: .each(cluster(10 * e.alpha * e.alpha))