var egoNode; // 0, 107, 348, 414, 686, 698, 1684, 1912, 3437, 3980... var table; // data loaded from csv var vertices = { }; var edges = [ ]; var adj = { }; // adjacency list. adj[3] = [5, 7] means there is an edge // from 3 to 5, and from 3 to 7 // Guarantee: adj[x].contains(y) == adj[y].contains(x) var iteration = 0; var iterations = 50; var plotHeight; var plotWidth; var t; function preload() { // Change ego node by suffixing URL with egoNode=123 var pEgoNode = getURLParams().egoNode; if (pEgoNode) { egoNode = int(pEgoNode); } else { egoNode = 698; // default } table = loadTable(egoNode + '.edges.csv', 'csv'); } function unique_array(arr) { var uniqueArr = []; for (var i = 0 ; i < arr.length ; i++) { if (uniqueArr.indexOf(arr[i]) < 0) { // not contains uniqueArr.push(arr[i]); } } return uniqueArr; } function int_array(arr) { var intArr = []; for (var i = 0 ; i < arr.length ; i++) { intArr[i] = int(arr[i]); } return intArr; } class Vertex { constructor(id) { this.id = id; this.disp_x = 0; this.disp_y = 0; this.pos_x = int(random(plotWidth)); this.pos_y = int(random(plotHeight)); } } function create_vertex(id) { return new Vertex(id); } class Edge { constructor(u, v) { this.u = u; this.v = v; // color this.r = random(256); this.g = random(256); this.b = random(256); } } function create_edge(u, v) { return new Edge(u, v); } function vector_norm(x, y) { return sqrt(x*x + y*y); } function fr91(W, L, V, E) { var area = W * L; var V_length = 0; for (var v in V) V_length++; var k = sqrt(area/V_length); var k2 = k*k; var fa = function(x) { return x*x/k; } var fr = function(x) { return x==0 ? 0 : k2/x; } var cool = function(x) { return t - t/(iteration+2); } // Calculate repulsive forces for (var j in V) { var v = V[j]; v.disp_x = 0; v.disp_y = 0; for (var l in V) { if (j != l) { var u = V[l]; var delta_x = v.pos_x - u.pos_x; var delta_y = v.pos_y - u.pos_y; var dist = vector_norm(delta_x, delta_y); if (dist > 0) { v.disp_x = v.disp_x + delta_x/dist * fr(dist); v.disp_y = v.disp_y + delta_y/dist * fr(dist); } } } } // Calculate attractive forces for (var j in E) { var e = E[j]; var lambda_x = e.v.pos_x - e.u.pos_x; var lambda_y = e.v.pos_y - e.u.pos_y; var dist = vector_norm(lambda_x, lambda_y); if (dist > 0) { var ax = lambda_x/dist * fa(dist); var ay = lambda_y/dist * fa(dist); e.v.disp_x -= ax; e.v.disp_y -= ay; e.u.disp_x += ax; e.u.disp_y += ay; } } // Limit max displacement to temperature t and // prevent from displacement outside frame for (var j in V) { var v = V[j]; var v_disp_norm = vector_norm(v.disp_x, v.disp_y); if (v_disp_norm > 0) { var ddx = v.disp_x/v_disp_norm; var ddy = v.disp_y/v_disp_norm; v.pos_x += ddx * min(v_disp_norm, t); v.pos_y += ddy * min(v_disp_norm, t); v.pos_x = min(W-30, max(30, v.pos_x)); v.pos_y = min(L-30, max(30, v.pos_y)); } } // Reduce the temperature as the layout approaches a // better configuration t = cool(t); } function setup() { plotHeight = windowHeight; plotWidth = windowWidth; t = plotWidth / 10; createCanvas(plotWidth, plotHeight); //randomSeed(500); frameRate(24); noSmooth(); // Prepare data var rows = table.getRows(); // Create Vertex and Edge objects // and add them to vertices and edges data structures for (var i = 0 ; i < rows.length ; i++) { var u_id = int(rows[i].getNum(0)); var v_id = int(rows[i].getNum(1)); if (!vertices.hasOwnProperty(u_id)) { vertices[u_id] = create_vertex(u_id); } if (!vertices.hasOwnProperty(v_id)) { vertices[v_id] = create_vertex(v_id); } var u = vertices[u_id]; var v = vertices[v_id]; edges.push(create_edge(u, v)); // Update adjacency matrix if (!adj.hasOwnProperty(u_id)) adj[u_id] = []; adj[u_id].push(v_id); if (!adj.hasOwnProperty(v_id)) adj[v_id] = []; adj[v_id].push(u_id); } // Create a vertex for the ego node var egoVertex = create_vertex(egoNode); vertices[egoNode] = egoVertex; // Create an edge from each of the other vertices to the ego node adj[egoNode] = []; for (var i in vertices) { if (i == egoNode) continue; var u = vertices[i]; edges.push(create_edge(u, egoVertex)); if (!adj.hasOwnProperty(u.id)) adj[u.id] = []; adj[u.id].push(egoNode); adj[egoNode].push(u.id); } } function draw() { background(255); // Execute algorithm var W = plotWidth; var L = plotHeight; var V = vertices; var E = edges; if (t > 0 && iteration < iterations) { fr91(W, L, V, E); iteration++; } else { noLoop(); } // Draw for (var i in E) { var e = E[i]; if (e.u.id == egoNode || e.v.id == egoNode) { // Ego node: Light gray edges stroke(224); } else { stroke(e.r, e.g, e.b); } line(e.u.pos_x, e.u.pos_y, e.v.pos_x, e.v.pos_y); } stroke(0); textAlign(CENTER); textSize(10); for (var i in V) { var v = V[i]; var x = v.pos_x; var y = v.pos_y; if (v.id == egoNode) { continue; // let's do it later so it appears on top } else { strokeWeight(4); point(x, y); strokeWeight(1); text(v.id, x, y-5); } } // Ego node: Make it BIG! var egoVertex = vertices[egoNode]; push(); fill(255, 0, 0); // red push(); stroke(255, 0, 0); strokeWeight(8); point(egoVertex.pos_x, egoVertex.pos_y); pop(); push(); textSize(20); text(egoNode, egoVertex.pos_x, egoVertex.pos_y-10); pop(); pop(); } function mouseMoved() { redraw(); var margin = 5; for (var i in vertices) { var v = vertices[i]; if (mouseX > v.pos_x - margin && mouseX < v.pos_x + margin && mouseY > v.pos_y - margin && mouseY < v.pos_y + margin) { push(); for (var j in adj[v.id]) { var u_id = adj[v.id][j]; var u = vertices[u_id]; strokeWeight(4); line(v.pos_x, v.pos_y, u.pos_x, u.pos_y); } pop(); break; } } }