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.
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'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);
Hi I am trying to group nodes generated as floating images in below code. For grouping I am using array arrInt[] which has many values of(0,1,2). Based on arrInt values, all 0's, 1's and 2's should be together. I am unable to do it on tick function
var fill = d3.scale.category10();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.csv("data/Images.csv", function(error, data) {
data.forEach(function(d) {
arrFileUrl.push(d['FingerImageName']);
arrBrightness.push(d['Brightness']);
arrPattern.push(d['Pattern']);
arrSize.push(d['Size']);
});
var boolBrig = arrBrightness.contains(brightness);
var boolSize = arrSize.contains(pixel);
if(boolBrig === true && boolSize === true){
data.forEach(function(d){
if(d['Brightness'] === brightness && d['Size'] === pixel && pattSelect.contains(d['Pattern'])){
arrMatchFile.push(d['FingerImageName']);
}
});
}
for(j=0;j<arrPattern.length;j++){
if(arrPattern[j] === "Arches"){
arrInt[j] = 0;
}
if(arrPattern[j] === "Whorls"){
arrInt[j] = 1;
}
if(arrPattern[j] === "Loops"){
arrInt[j] = 2;
}
}
var nodes = d3.range(arrFileUrl.length).map(function(i) {
return {index: arrInt[i]};
});
console.log(nodes);
var force = d3.layout.force()
.nodes(nodes)
.gravity(0.02)
//.charge(-1700)
//.friction(0.5)
.size([width, height])
.on("tick", tick)
.start();
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("image")
.attr("xlink:href", function (d,i) {
return arrFileUrl[i];
})
.attr("class", "node")
.attr("width", 120)
.attr("height", 160)
.style("stroke", "black")
.call(force.drag)
.style("opacity", function(d,i){
if(arrMatchFile.contains(arrFileUrl[i])) {
return 5;
} else {
return 0.2;
}
})
.on("mousedown", function() { d3.event.stopPropagation(); });
svg.transition()
.duration(1000);
d3.select("body")
.on("mousedown", mousedown);
function tick(e) {
// Push different nodes in different directions for clustering.
var k = 6 * e.alpha;
nodes.forEach(function(o, i) {
o.y += i & 1 ? k : -k;
o.x += i & 2 ? k : -k;
});
node.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
}
function mousedown() {
nodes.forEach(function(o, i) {
o.x += (Math.random() - .5) * 40;
o.y += (Math.random() - .5) * 40;
});
force.resume();
}
});
In
nodes.forEach(function(o, i) {
o.y += i & 1 ? k : -k;
o.x += i & 2 ? k : -k;
});
You are using the index i for clustering... this won't work, you want to use arrInt[i] instead:
nodes.forEach(function(o, i) {
o.y += arrInt[i] & 1 ? k : -k;
o.x += arrInt[i] & 2 ? k : -k;
});
Im new to javascript and english, sorry for bad english.
I have a radar chart created using this demo code
Now I want to add another area to the same graph, using a button, I am not able to do this.
Please help me.
$("#btn").click(function(){
// here i want to add another set of daata
});
$("#btn").click(function(){
d = [
{axis: "axisA", value: 2, order:0},
{axis: "axisB", value: 8, order:1},
{axis: "axisC", value: 4, order:2},
{axis: "axisD", value: 6, order:3}
];
RadarChart.draw("#chart", d);
})
var RadarChart = {draw: function(id, d, options){
var cfg = {
radius: 6, w: 600,h: 600,
factor: 1,
factorLegend: .85,
levels: 3,
maxValue: 0,
radians: 2 * Math.PI,
opacityArea: 0.5,
color: d3.scale.category10()
};
if('undefined' !== typeof options){
for(var i in options){
if('undefined' !== typeof options[i]){
cfg[i] = options[i];
}
}
}
cfg.maxValue = Math.max(cfg.maxValue, d3.max(d.map(function(o){return o.value})));
var allAxis = (d.map(function(i, j){return i.axis}));
var total = allAxis.length;
var radius = cfg.factor*Math.min(cfg.w/2, cfg.h/2);
d3.select(id).select("svg").remove();
var g = d3.select(id).append("svg").attr("width", cfg.w).attr("height", cfg.h).append("g");
var tooltip;
drawFrame();
var maxAxisValues = [];
drawAxis();
var dataValues = [];
reCalculatePoints();
var areagg = initPolygon();
drawPoly();
drawnode();
function drawFrame(){
for(var j=0; j<cfg.levels; j++){
var levelFactor = cfg.factor*radius*((j+1)/cfg.levels);
g.selectAll(".levels").data(allAxis).enter().append("svg:line")
.attr("x1", function(d, i){return levelFactor*(1-cfg.factor*Math.sin(i*cfg.radians/total));})
.attr("y1", function(d, i){return levelFactor*(1-cfg.factor*Math.cos(i*cfg.radians/total));})
.attr("x2", function(d, i){return levelFactor*(1-cfg.factor*Math.sin((i+1)*cfg.radians/total));})
.attr("y2", function(d, i){return levelFactor*(1-cfg.factor*Math.cos((i+1)*cfg.radians/total));})
.attr("class", "line").style("stroke", "grey").style("stroke-width", "0.5px").attr("transform", "translate(" + (cfg.w/2-levelFactor) + ", " + (cfg.h/2-levelFactor) + ")");;
}
}
function drawAxis(){
var axis = g.selectAll(".axis").data(allAxis).enter().append("g").attr("class", "axis");
axis.append("line")
.attr("x1", cfg.w/2)
.attr("y1", cfg.h/2)
.attr("x2", function(j, i){
maxAxisValues[i] = {x:cfg.w/2*(1-cfg.factor*Math.sin(i*cfg.radians/total)), y:0};
return maxAxisValues[i].x;
})
.attr("y2", function(j, i){
maxAxisValues[i].y = cfg.h/2*(1-cfg.factor*Math.cos(i*cfg.radians/total));
return maxAxisValues[i].y;
})
.attr("class", "line").style("stroke", "grey").style("stroke-width", "1px");
axis.append("text").attr("class", "legend")
.text(function(d){return d}).style("font-family", "sans-serif").style("font-size", "10px").attr("transform", function(d, i){return "translate(0, -10)";})
.attr("x", function(d, i){return cfg.w/2*(1-cfg.factorLegend*Math.sin(i*cfg.radians/total))-20*Math.sin(i*cfg.radians/total);})
.attr("y", function(d, i){return cfg.h/2*(1-Math.cos(i*cfg.radians/total))+20*Math.cos(i*cfg.radians/total);});
}
function reCalculatePoints(){
g.selectAll(".nodes")
.data(d, function(j, i){
dataValues[i] =
[
cfg.w/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total)),
cfg.h/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.cos(i*cfg.radians/total)),
];
});
dataValues[d[0].length] = dataValues[0];
}
function initPolygon(){
return g.selectAll("area").data([dataValues])
.enter()
.append("polygon")
.attr("class", "radar-chart-serie0")
.style("stroke-width", "2px")
.style("stroke", cfg.color(0))
.on('mouseover', function (d){
z = "polygon."+d3.select(this).attr("class");
g.selectAll("polygon").transition(200).style("fill-opacity", 0.1);
g.selectAll(z).transition(200).style("fill-opacity", 0.7);
})
.on('mouseout', function(){
g.selectAll("polygon").transition(200).style("fill-opacity", cfg.opacityArea);
})
.style("fill", function(j, i){return cfg.color(0);})
.style("fill-opacity", cfg.opacityArea);
}
function drawPoly(){
areagg.attr("points",function(de) {
var str="";
for(var pti=0;pti<de.length;pti++){
str=str+de[pti][0]+","+de[pti][1]+" ";
}
return str;
});
}
function drawnode(){
g.selectAll(".nodes")
.data(d).enter()
.append("svg:circle").attr("class", "radar-chart-serie0")
.attr('r', cfg.radius)
.attr("alt", function(j){return Math.max(j.value, 0);})
.attr("cx", function(j, i){
return cfg.w/2*(1-(Math.max(j.value, 0)/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total));
})
.attr("cy", function(j, i){
return cfg.h/2*(1-(Math.max(j.value, 0)/cfg.maxValue)*cfg.factor*Math.cos(i*cfg.radians/total));
})
.attr("data-id", function(j){return j.axis;})
.style("fill", cfg.color(0)).style("fill-opacity", 0.9)
.on('mouseover', function (d){
newX = parseFloat(d3.select(this).attr('cx')) - 10;
newY = parseFloat(d3.select(this).attr('cy')) - 5;
tooltip.attr('x', newX).attr('y', newY).text(d.value).transition(200).style('opacity', 1);
z = "polygon."+d3.select(this).attr("class");
g.selectAll("polygon").transition(200).style("fill-opacity", 0.1);
g.selectAll(z).transition(200).style("fill-opacity", 0.7);
})
.on('mouseout', function(){
tooltip.transition(200).style('opacity', 0);
g.selectAll("polygon").transition(200).style("fill-opacity", cfg.opacityArea);
})
.call(d3.behavior.drag().on("drag", move)) // for drag & drop
.append("svg:title")
.text(function(j){return Math.max(j.value, 0)});
}
//Tooltip
tooltip = g.append('text').style('opacity', 0).style('font-family', 'sans-serif').style('font-size', 13);
function move(dobj, i){
this.parentNode.appendChild(this);
var dragTarget = d3.select(this);
var oldData = dragTarget.data()[0];
var oldX = parseFloat(dragTarget.attr("cx")) - 300;
var oldY = 300 - parseFloat(dragTarget.attr("cy"));
var newY = 0, newX = 0, newValue = 0;
var maxX = maxAxisValues[i].x - 300;
var maxY = 300 - maxAxisValues[i].y;
if(oldX == 0) {
newY = oldY - d3.event.dy;
if(Math.abs(newY) > Math.abs(maxY)) {
newY = maxY;
}
newValue = (newY/oldY) * oldData.value;
}
else
{
var slope = oldY / oldX;
newX = d3.event.dx + parseFloat(dragTarget.attr("cx")) - 300;
if(Math.abs(newX) > Math.abs(maxX)) {
newX = maxX;
}
newY = newX * slope;
var ratio = newX / oldX;
newValue = ratio * oldData.value;
}
dragTarget
.attr("cx", function(){return newX + 300 ;})
.attr("cy", function(){return 300 - newY;});
d[oldData.order].value=newValue;
reCalculatePoints();
drawPoly();
}}};
<script src="http://code.jquery.com/jquery-1.10.2.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<button id="btn">Click to add</button>
<button id="btnB">Click to add Another area</button>
<div id="chart" >
</div>
<!DOCTYPE html>
<html>
<body>
<script src="http://code.jquery.com/jquery-1.10.2.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<button id="btn">Click to add</button>
<button id="btnB">Click to add Another area</button>
<div id="chart"></div>
<script>
d = [
{axis: "axisA", value: 2, order:0},
{axis: "axisB", value: 8, order:1},
{axis: "axisC", value: 4, order:2},
{axis: "axisD", value: 6, order:3}
];
var RadarChart = {draw: function(id, d, options){
var cfg = {
radius: 6, w: 600,h: 600,
factor: 1,
factorLegend: .85,
levels: 3,
maxValue: 0,
radians: 2 * Math.PI,
opacityArea: 0.5,
color: d3.scale.category10()
};
if('undefined' !== typeof options){
for(var i in options){
if('undefined' !== typeof options[i]){
cfg[i] = options[i];
}
}
}
cfg.maxValue = Math.max(cfg.maxValue, d3.max(d.map(function(o){return o.value})));
var allAxis = (d.map(function(i, j){return i.axis}));
var total = allAxis.length;
var radius = cfg.factor*Math.min(cfg.w/2, cfg.h/2);
d3.select(id).select("svg").remove();
var g = d3.select(id).append("svg").attr("width", cfg.w).attr("height", cfg.h).append("g");
var tooltip;
drawFrame();
var maxAxisValues = [];
drawAxis();
var dataValues = [];
reCalculatePoints();
var areagg = initPolygon();
drawPoly();
drawnode();
function drawFrame(){
for(var j=0; j<cfg.levels; j++){
var levelFactor = cfg.factor*radius*((j+1)/cfg.levels);
g.selectAll(".levels").data(allAxis).enter().append("svg:line")
.attr("x1", function(d, i){return levelFactor*(1-cfg.factor*Math.sin(i*cfg.radians/total));})
.attr("y1", function(d, i){return levelFactor*(1-cfg.factor*Math.cos(i*cfg.radians/total));})
.attr("x2", function(d, i){return levelFactor*(1-cfg.factor*Math.sin((i+1)*cfg.radians/total));})
.attr("y2", function(d, i){return levelFactor*(1-cfg.factor*Math.cos((i+1)*cfg.radians/total));})
.attr("class", "line").style("stroke", "grey").style("stroke-width", "0.5px").attr("transform", "translate(" + (cfg.w/2-levelFactor) + ", " + (cfg.h/2-levelFactor) + ")");;
}
}
function drawAxis(){
var axis = g.selectAll(".axis").data(allAxis).enter().append("g").attr("class", "axis");
axis.append("line")
.attr("x1", cfg.w/2)
.attr("y1", cfg.h/2)
.attr("x2", function(j, i){
maxAxisValues[i] = {x:cfg.w/2*(1-cfg.factor*Math.sin(i*cfg.radians/total)), y:0};
return maxAxisValues[i].x;
})
.attr("y2", function(j, i){
maxAxisValues[i].y = cfg.h/2*(1-cfg.factor*Math.cos(i*cfg.radians/total));
return maxAxisValues[i].y;
})
.attr("class", "line").style("stroke", "grey").style("stroke-width", "1px");
axis.append("text").attr("class", "legend")
.text(function(d){return d}).style("font-family", "sans-serif").style("font-size", "10px").attr("transform", function(d, i){return "translate(0, -10)";})
.attr("x", function(d, i){return cfg.w/2*(1-cfg.factorLegend*Math.sin(i*cfg.radians/total))-20*Math.sin(i*cfg.radians/total);})
.attr("y", function(d, i){return cfg.h/2*(1-Math.cos(i*cfg.radians/total))+20*Math.cos(i*cfg.radians/total);});
}
function reCalculatePoints(){
g.selectAll(".nodes")
.data(d, function(j, i){
dataValues[i] =
[
cfg.w/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total)),
cfg.h/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.cos(i*cfg.radians/total)),
];
});
dataValues[d[0].length] = dataValues[0];
}
function initPolygon(){
return g.selectAll("area").data([dataValues])
.enter()
.append("polygon")
.attr("class", "radar-chart-serie0")
.style("stroke-width", "2px")
.style("stroke", cfg.color(0))
.on('mouseover', function (d){
z = "polygon."+d3.select(this).attr("class");
g.selectAll("polygon").transition(200).style("fill-opacity", 0.1);
g.selectAll(z).transition(200).style("fill-opacity", 0.7);
})
.on('mouseout', function(){
g.selectAll("polygon").transition(200).style("fill-opacity", cfg.opacityArea);
})
.style("fill", function(j, i){return cfg.color(0);})
.style("fill-opacity", cfg.opacityArea);
}
function drawPoly(){
areagg.attr("points",function(de) {
var str="";
for(var pti=0;pti<de.length;pti++){
str=str+de[pti][0]+","+de[pti][1]+" ";
}
return str;
});
}
function drawnode(){
g.selectAll(".nodes")
.data(d).enter()
.append("svg:circle").attr("class", "radar-chart-serie0")
.attr('r', cfg.radius)
.attr("alt", function(j){return Math.max(j.value, 0);})
.attr("cx", function(j, i){
return cfg.w/2*(1-(Math.max(j.value, 0)/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total));
})
.attr("cy", function(j, i){
return cfg.h/2*(1-(Math.max(j.value, 0)/cfg.maxValue)*cfg.factor*Math.cos(i*cfg.radians/total));
})
.attr("data-id", function(j){return j.axis;})
.style("fill", cfg.color(0)).style("fill-opacity", 0.9)
.on('mouseover', function (d){
newX = parseFloat(d3.select(this).attr('cx')) - 10;
newY = parseFloat(d3.select(this).attr('cy')) - 5;
tooltip.attr('x', newX).attr('y', newY).text(d.value).transition(200).style('opacity', 1);
z = "polygon."+d3.select(this).attr("class");
g.selectAll("polygon").transition(200).style("fill-opacity", 0.1);
g.selectAll(z).transition(200).style("fill-opacity", 0.7);
})
.on('mouseout', function(){
tooltip.transition(200).style('opacity', 0);
g.selectAll("polygon").transition(200).style("fill-opacity", cfg.opacityArea);
})
.call(d3.behavior.drag().on("drag", move)) // for drag & drop
.append("svg:title")
.text(function(j){return Math.max(j.value, 0)});
}
//Tooltip
tooltip = g.append('text').style('opacity', 0).style('font-family', 'sans-serif').style('font-size', 13);
function move(dobj, i){
this.parentNode.appendChild(this);
var dragTarget = d3.select(this);
var oldData = dragTarget.data()[0];
var oldX = parseFloat(dragTarget.attr("cx")) - 300;
var oldY = 300 - parseFloat(dragTarget.attr("cy"));
var newY = 0, newX = 0, newValue = 0;
var maxX = maxAxisValues[i].x - 300;
var maxY = 300 - maxAxisValues[i].y;
if(oldX == 0) {
newY = oldY - d3.event.dy;
if(Math.abs(newY) > Math.abs(maxY)) {
newY = maxY;
}
newValue = (newY/oldY) * oldData.value;
}
else
{
var slope = oldY / oldX;
newX = d3.event.dx + parseFloat(dragTarget.attr("cx")) - 300;
if(Math.abs(newX) > Math.abs(maxX)) {
newX = maxX;
}
newY = newX * slope;
var ratio = newX / oldX;
newValue = ratio * oldData.value;
}
dragTarget
.attr("cx", function(){return newX + 300 ;})
.attr("cy", function(){return 300 - newY;});
d[oldData.order].value=newValue;
reCalculatePoints();
drawPoly();
}}};
$("#btnB").click(function(){
d.push({axis: "axisA", value: 1, order:4}, {axis: "axisB", value: 3, order:5}, {axis: "axisC", value: 5, order:6}, {axis: "axisD", value: 7, order:7})
RadarChart.draw("#chart", d);
});
$("#btn").click(function(){
RadarChart.draw("#chart", d);
});
</script>
</body>
<html>
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))