vanilla javascript to Reactjs component? - javascript
I have a little vanilla javascript code, and am hoping to convert it into a React component. I really dont know where to begin, and I have a few questions that seem be very simple.
First, how do i even get started converting this to React? Do i place all of these variables in the constructor, or in the render (before the return) Is all of this inline styling sufficient, without any external CSS?
This is the class (d3-based KMeans clustering visualization):
var flag = false;
var WIDTH = d3.select("#kmeans")[0][0].offsetWidth - 20;
var HEIGHT = Math.max(300, WIDTH * .7);
var svg = d3.select("#kmeans svg")
.attr('width', WIDTH)
.attr('height', HEIGHT)
.style('padding', '10px')
.style('background', '#223344')
.style('cursor', 'pointer')
.style('-webkit-user-select', 'none')
.style('-khtml-user-select', 'none')
.style('-moz-user-select', 'none')
.style('-ms-user-select', 'none')
.style('user-select', 'none')
.on('click', function() {
d3.event.preventDefault();
step();
});
d3.selectAll("#kmeans button")
.style('padding', '.5em .8em');
d3.selectAll("#kmeans label")
.style('display', 'inline-block')
.style('width', '15em');
var lineg = svg.append('g');
var dotg = svg.append('g');
var centerg = svg.append('g');
d3.select("#step")
.on('click', function() { step(); draw(); });
d3.select("#restart")
.on('click', function() { restart(); draw(); });
d3.select("#reset")
.on('click', function() { init(); draw(); });
var groups = [], dots = [];
function step() {
d3.select("#restart").attr("disabled", null);
if (flag) {
moveCenter();
draw();
} else {
updateGroups();
draw();
}
flag = !flag;
}
function init() {
d3.select("#restart").attr("disabled", "disabled");
//var N = parseInt(d3.select('#N')[0][0].value, 10);
//var K = parseInt(d3.select('#K')[0][0].value, 10);
var N = 700;
var K = 9;
groups = [];
for (var i = 0; i < K; i++) {
var g = {
dots: [],
color: 'hsl(' + (i * 360 / K) + ',100%,50%)',
center: {
x: Math.random() * WIDTH,
y: Math.random() * HEIGHT
},
init: {
center: {}
}
};
g.init.center = {
x: g.center.x,
y: g.center.y
};
groups.push(g);
}
dots = [];
flag = false;
for (i = 0; i < N; i++) {
var dot ={
x: Math.random() * WIDTH,
y: Math.random() * HEIGHT,
group: undefined
};
dot.init = {
x: dot.x,
y: dot.y,
group: dot.group
};
dots.push(dot);
}
}
function restart() {
flag = false;
d3.select("#restart").attr("disabled", "disabled");
groups.forEach(function(g) {
g.dots = [];
g.center.x = g.init.center.x;
g.center.y = g.init.center.y;
});
for (var i = 0; i < dots.length; i++) {
var dot = dots[i];
dots[i] = {
x: dot.init.x,
y: dot.init.y,
group: undefined,
init: dot.init
};
}
}
function draw() {
var circles = dotg.selectAll('circle')
.data(dots);
circles.enter()
.append('circle');
circles.exit().remove();
circles
.transition()
.duration(500)
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('fill', function(d) { return d.group ? d.group.color : '#ffffff'; })
.attr('r', 5);
if (dots[0].group) {
var l = lineg.selectAll('line')
.data(dots);
var updateLine = function(lines) {
lines
.attr('x1', function(d) { return d.x; })
.attr('y1', function(d) { return d.y; })
.attr('x2', function(d) { return d.group.center.x; })
.attr('y2', function(d) { return d.group.center.y; })
.attr('stroke', function(d) { return d.group.color; });
};
updateLine(l.enter().append('line'));
updateLine(l.transition().duration(500));
l.exit().remove();
} else {
lineg.selectAll('line').remove();
}
var c = centerg.selectAll('path')
.data(groups);
var updateCenters = function(centers) {
centers
.attr('transform', function(d) { return "translate(" + d.center.x + "," + d.center.y + ") rotate(45)";})
.attr('fill', function(d,i) { return d.color; })
.attr('stroke', '#aabbcc');
};
c.exit().remove();
updateCenters(c.enter()
.append('path')
.attr('d', d3.svg.symbol().type('cross'))
.attr('stroke', '#aabbcc'));
updateCenters(c
.transition()
.duration(500));}
function moveCenter() {
groups.forEach(function(group, i) {
if (group.dots.length == 0) return;
// get center of gravity
var x = 0, y = 0;
group.dots.forEach(function(dot) {
x += dot.x;
y += dot.y;
});
group.center = {
x: x / group.dots.length,
y: y / group.dots.length
};
});
}
function updateGroups() {
groups.forEach(function(g) { g.dots = []; });
dots.forEach(function(dot) {
// find the nearest group
var min = Infinity;
var group;
groups.forEach(function(g) {
var d = Math.pow(g.center.x - dot.x, 2) + Math.pow(g.center.y - dot.y, 2);
if (d < min) {
min = d;
group = g;
}
});
// update group
group.dots.push(dot);
dot.group = group;
});
}
init(); draw();
Any help in understanding (or or course providing converted code) would be so helpful.
If you're beginning from scratch, set up a boiler plate.For e.g create-react-app
Install d3 library in this project
Create a component for this and import and render that component in your app.js
Make these variables as part of the state of the component and method inside the component as class methods
Related
Contamination in a graph using d3js
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>
Get visible root node in zoomable sunburst
I made a zoomable sunburst visualisation with labels (see in action, or check out the code). When clicking on an item, the innermost visible node has its label turned sideways. I'd like to fix the label just for this one node, but I haven't found a way to do this. Is there a way to say "if (current node is the root of visible nodes)"? Any other ideas? The full visualization: Zoomed in after click. I'd like to make the 'Calm' node text horizontal:
While not perfect, this modified version of the code you were using adjusts the text of the currently selected node as it animates and makes it horizontal. var width = 960, height = 700, radius = (Math.min(width, height) / 2) - 10; var formatNumber = d3.format(",d"); var x = d3.scaleLinear() .range([0, 2 * Math.PI]); var y = d3.scaleLinear() .range([0, radius]); var color = d3.scaleOrdinal(d3.schemeCategory20); var partition = d3.partition(); function startAngle(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x0))); } function endAngle(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x1))); } function innerRadius(d) { return Math.max(0, y(d.y0)); } function outerRadius(d) { return Math.max(0, y(d.y1)); } var arc = d3.arc() .startAngle( function(d) { return startAngle(d); }) .endAngle( function(d) { return endAngle(d); }) .innerRadius(function(d) { return innerRadius(d); }) .outerRadius(function(d) { return outerRadius(d); }) var texttransform = function(d) { var translation = y(d.y0); var rotation = computeTextRotation(d); if (rotation > 90 && rotation < 270) { rotation = rotation + 180; translation = -translation; } return ( "rotate(" + rotation + ")" + "translate(" + translation + ",0)" ); } var transition = {}; function calcTransitionPercentage(){ var now = Date.now()-transition.clockNow; if(!transition.delay || now > transition.delay){ return Math.min(1,(now-(transition.delay||0))/transition.duration); } return 0; } function computeTextRotation(d) { if (d.depth === 0) { return 0; } var current = x((d.x0 + d.x1)/2); var angle = (current - Math.PI / 2) / Math.PI * 180; if(transition.node === d){ angle -= 90 * calcTransitionPercentage(); } return (angle > 90 || angle < 270) ? angle : 180 + angle ; } var textanchor = function(d) { if (d.depth === 0) { return "middle"; } var rotation = computeTextRotation(d); return (rotation > 90 && rotation < 270) ? "end" : "start"; } var textdx = function(d) { if (d.depth === 0) { return 0; } var rotation = computeTextRotation(d); return (rotation > 90 && rotation < 270) ? -6 : 6; } var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height) .append("g") .attr("transform", "translate(" + width / 2 + "," + (height / 2) + ")"); function calcFontSize(d) { const xFactor = 12, yFactor = 7.5 ; // stub if (d.depth === 0) { return "30px"; } // use inner arc len as text height delimiter var innerArc = (endAngle(d) - startAngle(d)) * 2 * Math.PI * innerRadius(d); var len = (d.y1-d.y0) * radius; return Math.min(innerArc / yFactor, len / d.data.textlen * xFactor) + "px"; } function click(d = { x0: 0, x1: 1, y0: 0, y1: 1 }) { transition = {clockNow: Date.now(), duration: 750, node: d } var trans = svg.transition().duration(750); trans.selectAll("path") .attrTween("d", function(n) { return function() { return arc(n); }; }) .tween("scale", function() { var xd = d3.interpolate(x.domain(), [d.x0, d.x1]), yd = d3.interpolate(y.domain(), [d.y0, 1]), yr = d3.interpolate(y.range(), [d.y0 ? 20 : 0, radius]); return function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); }; }); trans.selectAll("text") .attrTween("transform", function(n) { return function() { return texttransform(n); }; }) .attrTween("text-anchor", function(n) { return function() { return textanchor(n); }; }) .attrTween("dx", function(n) { return function() { return textdx(n); }; }) .styleTween("font-size", function(n) { return function() { return calcFontSize(n); }; }); trans.selectAll("text") .delay(400) .attrTween("opacity", function(n) { return function() { if (d === n || n.ancestors().includes(d)) { return 1; } else { return 0; } }; }); } d3.text('https://raw.githubusercontent.com/manooh/NVSee/master/data/feelings_EN.txt', function(error, raw){ if (error) throw error; // replace two-space indentation with pipes raw = raw.replace(new RegExp(' ', 'g'), '|'); //read pipe-delimited data var dsv = d3.dsvFormat('|'); var flatData = dsv.parse(raw); var rData = currentNode = tree(flatData); rData = d3.hierarchy(rData); var nodes = partition(rData .sum(function(d) { return 1; }) // each leaf gets a size of 1 .sort(function(a, b) { d3.ascending(a.name, b.name) }) // not working? ) .descendants(); g = svg.selectAll("path") .data(nodes) .enter().append("g"); path = g.append("path") .attr("d", arc) .style("fill", function(d, i) { var c; if (d.depth === 0) { return "white"; } else if (d.depth === 1) { c = color((d.children ? d : d.parent).data.name); } else if (d.depth > 1) { c = d3.color(d.parent.data.color).darker(); } d.data.color = c; return c; }) .on("click", click) .append("title") .text(function(d) { return d.data.name }); text = g.append("text") .style("fill", function(d) { if (d.depth === 0) { return "#CCC"; } else { return "#FFF"; }}) .attr("class", "svglabel") .attr("transform", texttransform) .attr("text-anchor", textanchor) .attr("dx", textdx) .attr("dy", ".35em") // vertical-align .text(function(d) { return d.data.name; }) .style("font-size", function(d) { // hack. save text len as property to make accessible in transiton d.data.textlen = this.getComputedTextLength(); return calcFontSize(d); }); }); function tree(nodes) { var curr, parent, root; var lev = 1; nodes.forEach(function(d) { if (!root) { // handle root (first node) curr = { name: d.d1, children: [] }; root = curr; parent = curr; } else { if (d['d' + (lev+1)]) { // handle children lev = lev+1; parent = curr; } else if (d['d' + (lev-1)]) { // handle moving up the hierarchy lev = lev-1; parent = parent.parent; } else if (!d['d' + lev]) { // if it's neither child, nor moving up, nor a sibling, handle exception throw "unhandled tree level"; } curr = { name: d['d' + lev], children: [] }; curr.parent = parent; parent.children.push(curr); } }); return root; } .svglabel { font-family: sans-serif; pointer-events: none; } body { -webkit-touch-callout: none; /* iOS Safari */ -webkit-user-select: none; /* Safari */ -khtml-user-select: none; /* Konqueror HTML */ -moz-user-select: none; /* Firefox */ -ms-user-select: none; /* Internet Explorer/Edge */ user-select: none; /* Non-prefixed version, currently supported by Chrome and Opera */ } <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.js"></script>
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.
d3 second graph kills first one's force layout
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.