Skip to content

Instantly share code, notes, and snippets.

@Kcnarf
Last active December 3, 2018 08:32
Force Layout - faster final arrangement
license: mit

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 :

  • how you can use the D3's Force Layout to compute the final arrangement,
  • how you can omit the live arrangement, and display only the final arrangement
  • having all these things in less time that it takes to write it

Acknowledgments to:

stem rank trend
jean 1 0.0221834557
excelent 2 0.0172573247
bon 3 0.0152706367
conseil 4 0.0142771343
part 5 0.0139849763
ecout 6 0.0125657668
promotion 7 0.0116647465
disponibilit 8 0.0107923086
serviabl 9 0.0099251458
acesoir 10 0.0098934332
haut 11 0.009431776
pantalon 12 0.0093578819
bele 13 0.0092871115
aceuil 14 0.0092121588
nouveaut 15 0.0090155204
achat 16 0.008722844
promo 17 0.0086278463
tendanc 18 0.0085356217
manqu 19 0.0084932708
parfait 20 0.0083623534
done 21 0.008355304
feme 22 0.0083248405
vent 23 0.0083132016
avoi 24 0.0080424169
chaleureu 25 0.0078822066
comand 26 0.0075604839
quelqu 27 0.0074075752
tenu 28 0.0072492987
chausur 29 0.00711808
disponibl 30 0.0068948326
person 31 0.0068073546
originalit 32 0.0067217905
vendeu 33 0.0067063822
rien 34 0.0067047717
esay 35 0.0067006641
satisfait 36 0.006684763
cher 37 0.0066836778
acueil 38 0.0066314869
colection 39 0.0064813458
metr 40 0.0064580388
tre 41 0.0064376867
servic 42 0.0063193197
sympath 43 0.0061779954
être 44 0.0061047823
sup 45 0.0060575513
dispos 46 0.0060483871
goût 47 0.0059637097
ador 48 0.0059467335
boutiqu 49 0.0059089954
beau 50 0.0058717403
regulier 51 0.0057329804
certain 52 0.005686449
ofre 53 0.005585669
gentil 54 0.0055481446
styl 55 0.005431152
fidelit 56 0.005398658
foi 57 0.0053355415
propos 58 0.00518158
profesion 59 0.0051523297
game 60 0.0051314774
cart 61 0.0050913638
chos 62 0.0050861437
coup 63 0.0050283014
souriant 64 0.0049009439
tail 65 0.004843215
raport 66 0.0048075105
autr 67 0.0046451013
general 68 0.0046430266
reduction 69 0.0046182266
mode 70 0.004570462
achet 71 0.0045116366
amelior 72 0.0045047883
present 73 0.004487011
elev 74 0.0044661415
rest 75 0.0044256491
jol 76 0.0043042359
acueilant 77 0.0041950808
marqu 78 0.0040932108
produit 79 0.0039764739
diferent 80 0.0039120302
modern 81 0.0038882488
enseign 82 0.0038190136
agreabl 83 0.0037894
trouv 84 0.0037702036
parfoi 85 0.0036276761
port 86 0.0035256142
magasin 87 0.0034050179
chang 88 0.0031922043
agenc 89 0.003190358
clai 90 0.0031812945
ainsi 91 0.0031549695
choi 92 0.0030694137
rayon 93 0.0030580465
client 94 0.0029360714
vête 95 0.0028722762
dire 96 0.0027721774
qualit 97 0.0027403058
var 98 0.0026126169
personel 99 0.0025779703
aimabl 100 0.0025749765
internet 101 0.0025134409
pri 102 0.00238414
sai 103 0.0023536115
abordabl 104 0.0022652921
bone 105 0.0022487593
matier 106 0.0021982583
promod 107 0.0020265214
larg 108 0.0018647201
stock 109 0.0018290033
sympa 110 0.0018003201
corect 111 0.0017615927
vraiment 112 0.0016633065
petit 113 0.0016439206
aime 114 0.0015219369
model 115 0.0014867544
equip 116 0.0011670831
couleu 117 0.0011177858
seul 118 0.0010356827
bais 119 0.0010036041
renouvel 120 0.0009602366
nouvel 121 0.0009557945
niveau 122 0.0004725302
articl 123 0.0003851409
vete 124 0.000353736
domag 125 0.0002193117
raisonabl 126 -0.0001944124
esayag 127 -0.00025102
valeu 128 -0.0002974617
site 129 -0.00040451
amabilit 130 -0.0005457864
grand 131 -0.0008885675
original 132 -0.0009822167
acesibl 133 -0.0010942806
sourir 134 -0.0011092602
diversit 135 -0.001424439
tisu 136 -0.0016639137
cabin 137 -0.0025446106
ane 138 -0.0032602282
propr 139 -0.0039869126
joli 140 -0.0051917043
clas 141 -0.0052463184
variet 142 -0.007044379
atractif 143 -0.0072967461
cais 144 -0.0080716152
rang 145 -0.0081059257
robe 146 -0.0084245392
espac 147 -0.0087132705
atent 148 -0.0109695748
interesant 149 -0.0113475782
sold 150 -0.0611010533
<!DOCTYPE html>
<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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment