Skip to content

Instantly share code, notes, and snippets.

@micahstubbs
Created March 4, 2017 07:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save micahstubbs/76d8a78648be617fe7aef649c312b580 to your computer and use it in GitHub Desktop.
Save micahstubbs/76d8a78648be617fe7aef649c312b580 to your computer and use it in GitHub Desktop.
538 Twitter Graph, Annotated
border: no
license: MIT

a mashup of the 538 twitter network from @xaranke with the Network Annotation with Collision Detection block from @elijah_meeks


Using collision detection with network visualization labels

This demonstrates how to use d3-annotation() with bboxCollide to procedurally place node labels. After using the nodes data to create a network visualization of the Les Miserables play, we filter the nodes to leave out the side characters and pass that array to d3-annotation. We then create a second forceSimulation, this time using the size of the notes as the property in our bounding box collision detection, to move the labels out of each others' way.

d3-annotation by Susie Lu.

(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-quadtree')) :
typeof define === 'function' && define.amd ? define(['exports', 'd3-quadtree'], factory) :
(factory((global.d3 = global.d3 || {}),global.d3));
}(this, function (exports,d3Quadtree) { 'use strict';
function bboxCollide (bbox) {
function x (d) {
return d.x + d.vx;
}
function y (d) {
return d.y + d.vy;
}
function constant (x) {
return function () {
return x;
};
}
var nodes,
boundingBoxes,
strength = 1,
iterations = 1;
if (typeof bbox !== "function") {
bbox = constant(bbox === null ? [[0,0][1,1]] : bbox)
}
function force () {
var i,
tree,
node,
xi,
yi,
bbi,
nx1,
ny1,
nx2,
ny2
var cornerNodes = []
nodes.forEach(function (d, i) {
cornerNodes.push({node: d, vx: d.vx, vy: d.vy, x: d.x + (boundingBoxes[i][1][0] + boundingBoxes[i][0][0]) / 2, y: d.y + (boundingBoxes[i][0][1] + boundingBoxes[i][1][1]) / 2})
cornerNodes.push({node: d, vx: d.vx, vy: d.vy, x: d.x + boundingBoxes[i][0][0], y: d.y + boundingBoxes[i][0][1]})
cornerNodes.push({node: d, vx: d.vx, vy: d.vy, x: d.x + boundingBoxes[i][0][0], y: d.y + boundingBoxes[i][1][1]})
cornerNodes.push({node: d, vx: d.vx, vy: d.vy, x: d.x + boundingBoxes[i][1][0], y: d.y + boundingBoxes[i][0][1]})
cornerNodes.push({node: d, vx: d.vx, vy: d.vy, x: d.x + boundingBoxes[i][1][0], y: d.y + boundingBoxes[i][1][1]})
})
var cn = cornerNodes.length
for (var k = 0; k < iterations; ++k) {
tree = d3Quadtree.quadtree(cornerNodes, x, y).visitAfter(prepareCorners);
for (i = 0; i < cn; ++i) {
var nodeI = ~~(i / 5);
node = nodes[nodeI]
bbi = boundingBoxes[nodeI]
xi = node.x + node.vx
yi = node.y + node.vy
nx1 = xi + bbi[0][0]
ny1 = yi + bbi[0][1]
nx2 = xi + bbi[1][0]
ny2 = yi + bbi[1][1]
tree.visit(apply);
}
}
function apply (quad, x0, y0, x1, y1) {
var data = quad.data
if (data) {
var bWidth = bbLength(bbi, 0),
bHeight = bbLength(bbi, 1);
if (data.node.index !== nodeI) {
var dataNode = data.node
var bbj = boundingBoxes[dataNode.index],
dnx1 = dataNode.x + dataNode.vx + bbj[0][0],
dny1 = dataNode.y + dataNode.vy + bbj[0][1],
dnx2 = dataNode.x + dataNode.vx + bbj[1][0],
dny2 = dataNode.y + dataNode.vy + bbj[1][1],
dWidth = bbLength(bbj, 0),
dHeight = bbLength(bbj, 1)
if (nx1 <= dnx2 && dnx1 <= nx2 && ny1 <= dny2 && dny1 <= ny2) {
var xSize = [Math.min.apply(null, [dnx1, dnx2, nx1, nx2]), Math.max.apply(null, [dnx1, dnx2, nx1, nx2])]
var ySize = [Math.min.apply(null, [dny1, dny2, ny1, ny2]), Math.max.apply(null, [dny1, dny2, ny1, ny2])]
var xOverlap = bWidth + dWidth - (xSize[1] - xSize[0])
var yOverlap = bHeight + dHeight - (ySize[1] - ySize[0])
var xBPush = xOverlap * strength * (yOverlap / bHeight)
var yBPush = yOverlap * strength * (xOverlap / bWidth)
var xDPush = xOverlap * strength * (yOverlap / dHeight)
var yDPush = yOverlap * strength * (xOverlap / dWidth)
if ((nx1 + nx2) / 2 < (dnx1 + dnx2) / 2) {
node.vx -= xBPush
dataNode.vx += xDPush
}
else {
node.vx += xBPush
dataNode.vx -= xDPush
}
if ((ny1 + ny2) / 2 < (dny1 + dny2) / 2) {
node.vy -= yBPush
dataNode.vy += yDPush
}
else {
node.vy += yBPush
dataNode.vy -= yDPush
}
}
}
return;
}
return x0 > nx2 || x1 < nx1 || y0 > ny2 || y1 < ny1;
}
}
function prepareCorners (quad) {
if (quad.data) {
return quad.bb = boundingBoxes[quad.data.node.index]
}
quad.bb = [[0,0],[0,0]]
for (var i = 0; i < 4; ++i) {
if (quad[i] && quad[i].bb[0][0] < quad.bb[0][0]) {
quad.bb[0][0] = quad[i].bb[0][0]
}
if (quad[i] && quad[i].bb[0][1] < quad.bb[0][1]) {
quad.bb[0][1] = quad[i].bb[0][1]
}
if (quad[i] && quad[i].bb[1][0] > quad.bb[1][0]) {
quad.bb[1][0] = quad[i].bb[1][0]
}
if (quad[i] && quad[i].bb[1][1] > quad.bb[1][1]) {
quad.bb[1][1] = quad[i].bb[1][1]
}
}
}
function bbLength (bbox, heightWidth) {
return bbox[1][heightWidth] - bbox[0][heightWidth]
}
force.initialize = function (_) {
var i, n = (nodes = _).length; boundingBoxes = new Array(n);
for (i = 0; i < n; ++i) boundingBoxes[i] = bbox(nodes[i], i, nodes);
};
force.iterations = function (_) {
return arguments.length ? (iterations = +_, force) : iterations;
};
force.strength = function (_) {
return arguments.length ? (strength = +_, force) : strength;
};
force.bbox = function (_) {
return arguments.length ? (bbox = typeof _ === "function" ? _ : constant(+_), force) : bbox;
};
return force;
}
exports.bboxCollide = bboxCollide;
Object.defineProperty(exports, '__esModule', { value: true });
}));
{
"links":[
{
"source":46,
"target":6,
"value":5
},
{
"source":20,
"target":47,
"value":30
},
{
"source":15,
"target":25,
"value":8
},
{
"source":40,
"target":26,
"value":7
},
{
"source":46,
"target":42,
"value":15
},
{
"source":44,
"target":24,
"value":19
},
{
"source":44,
"target":20,
"value":17
},
{
"source":46,
"target":47,
"value":27
},
{
"source":47,
"target":38,
"value":20
},
{
"source":33,
"target":24,
"value":32
},
{
"source":40,
"target":48,
"value":27
},
{
"source":12,
"target":25,
"value":10
},
{
"source":36,
"target":21,
"value":17
},
{
"source":46,
"target":43,
"value":11
},
{
"source":24,
"target":25,
"value":8
},
{
"source":15,
"target":20,
"value":7
},
{
"source":50,
"target":25,
"value":13
},
{
"source":18,
"target":35,
"value":5
},
{
"source":36,
"target":0,
"value":5
},
{
"source":12,
"target":27,
"value":6
},
{
"source":14,
"target":6,
"value":21
},
{
"source":17,
"target":39,
"value":5
},
{
"source":35,
"target":23,
"value":28
},
{
"source":33,
"target":43,
"value":5
},
{
"source":47,
"target":48,
"value":12
},
{
"source":40,
"target":39,
"value":58
},
{
"source":35,
"target":20,
"value":49
},
{
"source":40,
"target":31,
"value":46
},
{
"source":40,
"target":27,
"value":29
},
{
"source":35,
"target":15,
"value":15
},
{
"source":35,
"target":24,
"value":30
},
{
"source":44,
"target":35,
"value":23
},
{
"source":27,
"target":50,
"value":13
},
{
"source":34,
"target":42,
"value":5
},
{
"source":35,
"target":47,
"value":52
},
{
"source":47,
"target":43,
"value":37
},
{
"source":18,
"target":20,
"value":5
},
{
"source":35,
"target":36,
"value":42
},
{
"source":40,
"target":37,
"value":36
},
{
"source":13,
"target":47,
"value":6
},
{
"source":27,
"target":38,
"value":12
},
{
"source":25,
"target":39,
"value":31
},
{
"source":26,
"target":43,
"value":10
},
{
"source":31,
"target":15,
"value":5
},
{
"source":49,
"target":48,
"value":21
},
{
"source":25,
"target":48,
"value":42
},
{
"source":37,
"target":39,
"value":62
},
{
"source":40,
"target":10,
"value":7
},
{
"source":47,
"target":42,
"value":8
},
{
"source":35,
"target":5,
"value":22
},
{
"source":36,
"target":5,
"value":5
},
{
"source":43,
"target":48,
"value":6
},
{
"source":36,
"target":20,
"value":62
},
{
"source":40,
"target":20,
"value":63
},
{
"source":40,
"target":33,
"value":16
},
{
"source":24,
"target":48,
"value":8
},
{
"source":40,
"target":34,
"value":11
},
{
"source":18,
"target":25,
"value":7
},
{
"source":40,
"target":46,
"value":12
},
{
"source":44,
"target":41,
"value":6
},
{
"source":40,
"target":44,
"value":45
},
{
"source":45,
"target":49,
"value":8
},
{
"source":18,
"target":30,
"value":7
},
{
"source":35,
"target":37,
"value":26
},
{
"source":33,
"target":20,
"value":8
},
{
"source":44,
"target":23,
"value":12
},
{
"source":45,
"target":25,
"value":11
},
{
"source":5,
"target":20,
"value":7
},
{
"source":20,
"target":48,
"value":30
},
{
"source":38,
"target":43,
"value":59
},
{
"source":13,
"target":25,
"value":23
},
{
"source":35,
"target":27,
"value":37
},
{
"source":44,
"target":36,
"value":15
},
{
"source":36,
"target":39,
"value":67
},
{
"source":45,
"target":39,
"value":17
},
{
"source":22,
"target":47,
"value":5
},
{
"source":36,
"target":25,
"value":25
},
{
"source":44,
"target":37,
"value":15
},
{
"source":31,
"target":47,
"value":12
},
{
"source":26,
"target":39,
"value":20
},
{
"source":31,
"target":27,
"value":15
},
{
"source":44,
"target":38,
"value":108
},
{
"source":35,
"target":42,
"value":5
},
{
"source":24,
"target":23,
"value":7
},
{
"source":46,
"target":44,
"value":37
},
{
"source":40,
"target":21,
"value":9
},
{
"source":33,
"target":36,
"value":7
},
{
"source":20,
"target":23,
"value":30
},
{
"source":18,
"target":34,
"value":9
},
{
"source":39,
"target":48,
"value":10
},
{
"source":13,
"target":20,
"value":12
},
{
"source":45,
"target":19,
"value":5
},
{
"source":44,
"target":34,
"value":17
},
{
"source":33,
"target":50,
"value":13
},
{
"source":25,
"target":23,
"value":10
},
{
"source":35,
"target":38,
"value":42
},
{
"source":40,
"target":19,
"value":16
},
{
"source":20,
"target":11,
"value":15
},
{
"source":37,
"target":25,
"value":6
},
{
"source":18,
"target":38,
"value":5
},
{
"source":44,
"target":48,
"value":9
},
{
"source":40,
"target":35,
"value":248
},
{
"source":30,
"target":20,
"value":5
},
{
"source":24,
"target":43,
"value":7
},
{
"source":44,
"target":39,
"value":25
},
{
"source":36,
"target":47,
"value":12
},
{
"source":50,
"target":43,
"value":10
},
{
"source":35,
"target":19,
"value":21
},
{
"source":33,
"target":27,
"value":14
},
{
"source":13,
"target":36,
"value":8
},
{
"source":12,
"target":36,
"value":5
},
{
"source":40,
"target":24,
"value":44
},
{
"source":44,
"target":45,
"value":15
},
{
"source":35,
"target":13,
"value":24
},
{
"source":40,
"target":29,
"value":9
},
{
"source":21,
"target":50,
"value":5
},
{
"source":46,
"target":30,
"value":10
},
{
"source":12,
"target":10,
"value":7
},
{
"source":47,
"target":25,
"value":5
},
{
"source":25,
"target":43,
"value":5
},
{
"source":45,
"target":47,
"value":13
},
{
"source":40,
"target":13,
"value":17
},
{
"source":20,
"target":3,
"value":8
},
{
"source":47,
"target":39,
"value":19
},
{
"source":46,
"target":34,
"value":16
},
{
"source":12,
"target":24,
"value":5
},
{
"source":13,
"target":19,
"value":14
},
{
"source":44,
"target":0,
"value":36
},
{
"source":35,
"target":4,
"value":8
},
{
"source":24,
"target":39,
"value":7
},
{
"source":18,
"target":40,
"value":5
},
{
"source":40,
"target":43,
"value":27
},
{
"source":35,
"target":14,
"value":7
},
{
"source":27,
"target":37,
"value":5
},
{
"source":5,
"target":15,
"value":7
},
{
"source":44,
"target":30,
"value":12
},
{
"source":24,
"target":5,
"value":7
},
{
"source":46,
"target":35,
"value":13
},
{
"source":35,
"target":9,
"value":9
},
{
"source":18,
"target":44,
"value":23
},
{
"source":9,
"target":25,
"value":10
},
{
"source":20,
"target":37,
"value":17
},
{
"source":43,
"target":39,
"value":14
},
{
"source":27,
"target":47,
"value":5
},
{
"source":40,
"target":23,
"value":9
},
{
"source":40,
"target":30,
"value":12
},
{
"source":10,
"target":25,
"value":5
},
{
"source":40,
"target":36,
"value":50
},
{
"source":35,
"target":43,
"value":23
},
{
"source":12,
"target":20,
"value":7
},
{
"source":35,
"target":10,
"value":15
},
{
"source":20,
"target":50,
"value":12
},
{
"source":45,
"target":43,
"value":15
},
{
"source":40,
"target":38,
"value":34
},
{
"source":35,
"target":22,
"value":5
},
{
"source":30,
"target":47,
"value":5
},
{
"source":44,
"target":25,
"value":17
},
{
"source":27,
"target":10,
"value":10
},
{
"source":44,
"target":31,
"value":23
},
{
"source":46,
"target":31,
"value":5
},
{
"source":35,
"target":30,
"value":34
},
{
"source":20,
"target":39,
"value":12
},
{
"source":46,
"target":45,
"value":5
},
{
"source":35,
"target":16,
"value":13
},
{
"source":44,
"target":26,
"value":46
},
{
"source":46,
"target":14,
"value":63
},
{
"source":9,
"target":39,
"value":6
},
{
"source":33,
"target":28,
"value":18
},
{
"source":36,
"target":23,
"value":6
},
{
"source":35,
"target":31,
"value":77
},
{
"source":35,
"target":39,
"value":26
},
{
"source":45,
"target":20,
"value":25
},
{
"source":33,
"target":38,
"value":6
},
{
"source":20,
"target":16,
"value":8
},
{
"source":5,
"target":23,
"value":5
},
{
"source":47,
"target":50,
"value":29
},
{
"source":17,
"target":48,
"value":27
},
{
"source":12,
"target":40,
"value":5
},
{
"source":46,
"target":25,
"value":9
},
{
"source":40,
"target":16,
"value":6
},
{
"source":36,
"target":49,
"value":12
},
{
"source":36,
"target":15,
"value":11
},
{
"source":31,
"target":43,
"value":13
},
{
"source":35,
"target":26,
"value":15
},
{
"source":12,
"target":35,
"value":47
},
{
"source":47,
"target":37,
"value":10
},
{
"source":15,
"target":50,
"value":8
},
{
"source":35,
"target":25,
"value":45
},
{
"source":21,
"target":39,
"value":9
},
{
"source":49,
"target":37,
"value":18
},
{
"source":27,
"target":20,
"value":7
},
{
"source":44,
"target":43,
"value":89
},
{
"source":15,
"target":47,
"value":9
},
{
"source":35,
"target":49,
"value":6
},
{
"source":20,
"target":25,
"value":37
},
{
"source":44,
"target":50,
"value":29
},
{
"source":24,
"target":27,
"value":12
},
{
"source":36,
"target":27,
"value":6
},
{
"source":44,
"target":4,
"value":8
},
{
"source":49,
"target":50,
"value":53
},
{
"source":40,
"target":14,
"value":9
},
{
"source":40,
"target":25,
"value":66
},
{
"source":13,
"target":24,
"value":5
},
{
"source":23,
"target":39,
"value":22
},
{
"source":31,
"target":39,
"value":16
},
{
"source":49,
"target":47,
"value":15
},
{
"source":40,
"target":50,
"value":26
},
{
"source":35,
"target":50,
"value":92
},
{
"source":40,
"target":45,
"value":9
},
{
"source":50,
"target":39,
"value":30
},
{
"source":27,
"target":43,
"value":21
},
{
"source":35,
"target":45,
"value":16
},
{
"source":20,
"target":9,
"value":6
},
{
"source":24,
"target":20,
"value":29
},
{
"source":46,
"target":50,
"value":9
},
{
"source":24,
"target":50,
"value":9
},
{
"source":33,
"target":35,
"value":21
},
{
"source":44,
"target":27,
"value":22
},
{
"source":44,
"target":47,
"value":30
},
{
"source":35,
"target":48,
"value":29
},
{
"source":40,
"target":9,
"value":5
},
{
"source":44,
"target":42,
"value":37
},
{
"source":40,
"target":15,
"value":24
},
{
"source":15,
"target":9,
"value":8
},
{
"source":44,
"target":22,
"value":5
},
{
"source":36,
"target":24,
"value":10
},
{
"source":20,
"target":43,
"value":5
},
{
"source":33,
"target":25,
"value":6
},
{
"source":40,
"target":47,
"value":29
},
{
"source":0,
"target":26,
"value":22
},
{
"source":31,
"target":20,
"value":17
}
],
"nodes":[
{
"group":1,
"name":"jayboice"
},
{
"group":1,
"name":"hrfingerhut"
},
{
"group":1,
"name":"mathisonian"
},
{
"group":1,
"name":"walthickey"
},
{
"group":1,
"name":"538viz"
},
{
"group":1,
"name":"farai"
},
{
"group":1,
"name":"cragcrest"
},
{
"group":1,
"name":"reubenfb"
},
{
"group":1,
"name":"carlbialik"
},
{
"group":1,
"name":"bencasselman"
},
{
"group":1,
"name":"burritobracket"
},
{
"group":1,
"name":"leahlibresco"
},
{
"group":1,
"name":"538bot"
},
{
"group":1,
"name":"blytheterrell"
},
{
"group":1,
"name":"natesilver538"
},
{
"group":1,
"name":"kateelazegui"
},
{
"group":1,
"name":"fstonenyc"
},
{
"group":1,
"name":"atmccann"
},
{
"group":1,
"name":"sarapatt"
},
{
"group":1,
"name":"ollie"
},
{
"group":1,
"name":"no_little_plans"
},
{
"group":1,
"name":"bycoffe"
},
{
"group":1,
"name":"neil_paine"
},
{
"group":1,
"name":"forecasterenten"
},
{
"group":1,
"name":"hemdash"
},
{
"group":1,
"name":"kylenw"
},
{
"group":1,
"name":"jodyavirgan"
},
{
"group":1,
"name":"skepticalsports"
},
{
"group":1,
"name":"datalab538"
},
{
"group":1,
"name":"micahcohen"
},
{
"group":1,
"name":"lisaechow"
},
{
"group":1,
"name":"claremalone"
},
{
"group":1,
"name":"simonelandon"
},
{
"group":1,
"name":"hickoryhigh"
},
{
"group":1,
"name":"annabarryjester"
},
{
"group":1,
"name":"datadhrumil"
},
{
"group":1,
"name":"chadwickmatlin"
},
{
"group":1,
"name":"abbyabrams"
},
{
"group":1,
"name":"paulschreiber"
},
{
"group":1,
"name":"ellawinthrop"
},
{
"group":1,
"name":"ritchiesking"
},
{
"group":1,
"name":"mashfordg"
},
{
"group":1,
"name":"andrewflowers"
},
{
"group":1,
"name":"censusamericans"
},
{
"group":1,
"name":"stephrooster"
},
{
"group":1,
"name":"mattlanza"
},
{
"group":1,
"name":"ascheink"
},
{
"group":1,
"name":"guswez"
},
{
"group":1,
"name":"pasthaaa"
},
{
"group":1,
"name":"dgoldenberg"
},
{
"group":1,
"name":"monachalabi"
}
]
}
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<link href='https://fonts.googleapis.com/css?family=Lato:300,900' rel='stylesheet' type='text/css'>
<style>
:root {
--annotation-color: #e91e56;
}
body{
background-color: white;
}
svg {
background-color: white;
font-family: 'Lato';
overflow: visible;
}
line {
stroke:#e3e3e3;
}
.editable .annotation-subject, .editable .annotation-textbox {
cursor: move;
}
.line {
fill: none;
stroke: black;
stroke-width: 1px;
}
.annotation path {
stroke: var(--annotation-color);
fill: rgba(0,0,0,0);
}
.annotation path.connector-arrow{
fill: var(--annotation-color);
}
.annotation text {
fill: var(--annotation-color);
}
.annotation-title {
font-weight: bold;
}
.annotation .annotation-subject circle.handle {
display: none;
}
.annotation-note-bg {
fill: rgba(255, 255, 255, 0);
}
circle.handle {
stroke-dasharray: 5;
stroke: grey;
fill: rgba(255, 255, 255, 0);
cursor: move;
stroke-opacity: .4;
}
circle.handle.highlight {
stroke-opacity: 1;
}
.annotation.major {
font-weight: 900;
font-size: 1em;
}
.annotation-note-label tspan {
text-anchor: middle;
}
</style>
</head>
<body>
<svg width=960 height=500></svg>
<script src='https://d3js.org/d3.v4.js'></script>
<script src='bboxCollide.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.23.1/babel.min.js'></script>
<script src='https://cdn.rawgit.com/susielu/d3-annotation/master/d3-annotation.js'></script>
<script src='vis.js'></script>
</body>
</html>
/* global d3 window makeAnnotations */
const svg = d3.select('svg');
const width = +svg.attr('width');
const height = +svg.attr('height');
const color = d3.scaleOrdinal(d3.schemeCategory20)
.range([
'#e91e56',
'#00965f',
'#00bcd4',
'#3f51b5',
'#9c27b0',
'#ff5722',
'#cddc39',
'#607d8b',
'#8bc34a'
]);
const simulation = d3.forceSimulation()
.force('link', d3.forceLink().id((d, i) => i))
.force('charge', d3.forceManyBody().strength(-80))
.force('center', d3.forceCenter(width / 2, height / 2));
d3.json('graph.json', (error, graph) => {
if (error) throw error;
const link = svg.append('g')
.attr('class', 'links')
.selectAll('line')
.data(graph.links)
.enter().append('line')
.attr('stroke-width', d => Math.sqrt(d.value));
const node = svg.append('g')
.attr('class', 'nodes')
.selectAll('circle')
.data(graph.nodes)
.enter().append('circle')
.attr('r', d => (d.type === 'major' ? 9 : 3))
.style('fill', d => d3.hsl(color(d.group)).darker())
.style('fill-opacity', d => (d.type === 'other' ? 0.5 : 1));
node.append('title')
.text(d => d.name);
window.collide = d3.bboxCollide(a => [[a.offsetCornerX - 5, a.offsetCornerY - 10], [a.offsetCornerX + a.width + 5, a.offsetCornerY + a.height + 5]])
.strength(0.5)
.iterations(1);
window.yScale = d3.scaleLinear();
simulation
.nodes(graph.nodes)
.on('tick', ticked)
.on('end', () => {
const noteBoxes = makeAnnotations.collection().noteNodes;
window.labelForce = d3.forceSimulation(noteBoxes)
.force('x', d3.forceX(a => a.positionX).strength(a => Math.max(0.25, Math.min(3, Math.abs(a.x - a.positionX) / 20))))
.force('y', d3.forceY(a => a.positionY).strength(a => Math.max(0.25, Math.min(3, Math.abs(a.x - a.positionX) / 20))))
.force('collision', window.collide)
.alpha(0.5)
.on('tick', (d) => {
makeAnnotations.annotations()
.forEach((d, i) => {
const match = noteBoxes[i];
d.dx = match.x - match.positionX;
d.dy = match.y - match.positionY;
});
makeAnnotations.update();
});
});
const nonOtherNodes = graph.nodes
.filter(d => d.type !== 'other');
simulation.force('link')
.links(graph.links);
function ticked() {
link
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y);
node
.attr('cx', d => d.x)
.attr('cy', d => d.y);
makeAnnotations.annotations()
.forEach((d, i) => {
d.position = nonOtherNodes[i];
});
}
window.makeAnnotations = d3.annotation()
.type(d3.annotationLabel)
.annotations(nonOtherNodes
.map((d, i) => ({
data: { x: d.x, y: d.y, group: d.group },
note: { label: d.name,
align: 'middle',
orientation: 'fixed' },
connector: { type: 'elbow' },
className: d.type,
})))
.accessors({ x: d => d.x, y: d => d.y });
svg.append('g')
.attr('class', 'annotation-test')
.call(makeAnnotations);
svg.selectAll('.annotation-note text')
.style('fill', d => color(d.data.group));
svg.selectAll('.annotation-connector > path')
.style('stroke', (d, i) => color(d.data.group));
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment