In a previous block, I was not interesting in viewing the live arrangement of the Force Layout, because (even if it is beautifull and amazing) it takes some times to have the final arrangement.
Hence, this block (hummm ... the last lines of the code of this block) explains :
xxxxxxxxxx
<meta charset="utf-8">
<style>
circle {
stroke-width: 1.5px;
}
line {
stroke: #999;
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5.1/dat.gui.min.js"></script>
<script>
var width = 960,
height = 500,
radius = 4
csvData = [],
config = {
manyPoints: false
};
insertControls();
var fill = d3.scale.linear().domain([1,150]).range(['lightgreen', 'pink']);
var force = d3.layout.force()
.gravity(0.2)
.charge(-(radius*radius-radius))
.size([width, height])
.friction(0.7)
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("line")
.attr({x1: 0, y1: height/2, x2: width, y2: height/2})
.style("stroke", "lightgrey");
var tooltip = svg.append("g")
.attr("transform", "translate("+[width/2, 50]+")")
.style("opacity", 0);
var titles = tooltip.append("g").attr("transform", "translate("+[-5,0]+")")
titles.append("text").attr("text-anchor", "end").text("stem(fr):");
titles.append("text").attr({
"text-anchor": "end",
"transform": "translate("+[0,15]+")"
}).text("rank:");
titles.append("text").attr({
"text-anchor": "end",
"transform": "translate("+[0,30]+")"
}).text("x-value:");
var values = tooltip.append("g").attr("transform", "translate("+[5,0]+")")
var stem = values.append("text");
stem.attr("text-anchor", "start");
var rank = values.append("text");
rank.attr({
"text-anchor": "start",
"transform": "translate("+[0,15]+")"
});
var value = values.append("text");
value.attr({
"text-anchor": "start",
"transform": "translate("+[0,30]+")"
});
function dottype(d) {
d.stem = d.stem;
d.rank = +d.rank;
d.trend = +d.trend;
d.originalX = width/2+d.trend*6000;
d.x = d.originalX;
d.y = height/2;
csvData.push(d);
return d;
}
d3.csv("data.csv", dottype, function(error, foo) {
if (error) throw error;
draw();
});
function draw() {
var data = copyData(csvData);
if (config.manyPoints) { data = quadruple(data); }
svg.selectAll("circle").remove();
var node = svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("r", radius - .75)
.attr("cx", function(d) { return d.x; })
.attr("cy", height/2)
.style("fill", function(d) { return fill(d.rank); })
.style("stroke", function(d) { return d3.rgb(fill(d.rank)).darker(); })
.style("opacity", 0)
//.call(force.drag) don't need drag behaviour
.on("mouseenter", function(d) {
stem.text(d.stem);
rank.text(d.rank);
value.text(d.trend);
tooltip.transition().duration(0).style("opacity", 1); // remove fade out transition on mouseleave
})
.on("mouseleave", function(d) {
tooltip.transition().duration(1000).style("opacity", 0);
});
var iterations=0;
var startTime = Date.now();
force
.nodes(data)
.on("tick", tick)
.on("end", function() {
console.log("iterations: "+iterations);
console.log("time: "+(Date.now()-startTime));
});
// Use the d3's Force Layout to compute final position of nodes:
// usually, after calling force.start(), an internal Timer calls each 'tick()'; this Timer lets enought time for the tick() function to redraw each node's position; the drawback is that it takes some time (~5s) to have the final arrangement
// Instead, we explicitly call the tick() function (and no longer rely on the Timer); the tick() function no longer updates each node's position at each tick; this will be done once, when the force layout ends
force.stop().start();
for (var i = 0; i < 20; ++i) force.tick();
force.stop();
function tick() {
iterations++;
node.each(function(d){d.x = d.originalX; }) //constrains/fixes x-position
// The code below is no longer used
/*
node.attr("cx", function(d) {return d.x = Math.max(radius, Math.min(width - radius, d.x)); })
.attr("cy", function(d) {return d.y = Math.max(radius, Math.min(height - radius, d.y)); });
*/
}
//Now that we know the final position of each node, we can update each node's position in any way we want
node.transition()
.delay(function(d,i) {return 2*i})
.style("opacity", 1)
.attr("cx", function(d) {return d.x = Math.max(radius, Math.min(width - radius, d.x)); })
.attr("cy", function(d) {return d.y = Math.max(radius, Math.min(height - radius, d.y)); });
};
function copyData(data) {
return data.map(function(d) {
return {
id: d.id,
stem: d.stem,
rank: d.rank,
trend: d.trend,
originalX: d.originalX,
x: d.originalX,
y: d.y
}
});
};
function quadruple(data) {
// Quadruples data while maintaining order and uniq id
var quadrupledData = [],
i;
data.forEach(function(d) {
for (i=0; i<3; i++) {
quadrupledData.push({
id: d.id+"_"+i,
stem: d.stem,
rank: d.rank,
trend: d.trend,
originalX: d.originalX+i*1E-3,
x: d.x+i*1E-3,
y: d.y
})
}
quadrupledData.push(d);
})
return quadrupledData;
};
function insertControls () {
var ctrls = new dat.GUI({width: 200});
manyPointsCtrl = ctrls.add(config, "manyPoints");
manyPointsCtrl.onChange(function(value) {
draw();
});
};
</script>
</body>
https://d3js.org/d3.v3.min.js
https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5.1/dat.gui.min.js