function d3graphscript(data, {
width = 960,
height = 600,
dr = 4,
off = 15,
expand = {},
debug = 0
} = {}) {
let net, force, force2, hullg, hull, linkg, helper_linkg, link, hlink, nodeg, helper_nodeg, node, hnode;
const pathgen = d3.svg.line().interpolate("basis");
const fill = d3.scale.category20();
const body = d3.select("body");
const vis = body.append("svg")
.attr("width", width)
.attr("height", height);
function on_hull_click(d) {
if (debug == 1) console.log("node click", d, arguments, this, expand[d.group]);
if (d.size < 0)
return;
cycleState(d);
init();
}
function on_node_click(d) {
if (debug == 1) console.log("node click", d, arguments, this, expand[d.group]);
if (d.size < 0)
return;
cycleState(d);
init();
}
function cycleState(d) {
var g = d.group, s = expand[g] || 0;
if (d.ig_link_count < 2)
s = (s ? 0 : 2);
else {
s++; s %= 3;
}
return expand[g] = s;
}
function parseJson(data) {
for (var i = 0; i < data.links.length; ++i) {
o = data.links[i];
o.source = data.nodes[o.source];
o.target = data.nodes[o.target];
}
data.helpers = { left: {}, right: {} };
hullg = vis.append("g");
if (debug) {
linkg = vis.append("g");
helper_nodeg = vis.append("g");
}
helper_linkg = vis.append("g");
nodeg = vis.append("g");
if (debug == 1) {
node = vis.append("g").append("circle")
.attr("class", "center-of-mass")
.attr("r", 10);
}
init();
vis.attr("opacity", 1e-6)
.transition()
.duration(1000)
.attr("opacity", 1);
}
function init() {
var anticollision_grid = [], xquant = 1, yquant = 1, xqthresh, yqthresh;
if (force) force.stop();
net = network(data, net, expand);
force = d3.layout.force()
.nodes(net.nodes)
.links(net.links)
.size([width, height])
.linkDistance(function (l, i) {
var n1 = l.source, n2 = l.target,
g1 = n1.group_data || n1, g2 = n2.group_data || n2,
n1_is_group = n1.size || 0, n2_is_group = n2.size || 0,
rv = 300;
if (n1.group == n2.group) {
if ((n1.link_count < 2 && !n1_is_group) || (n2.link_count < 2 && !n2_is_group)) {
rv = 2;
} else if (!n1_is_group && !n2_is_group) {
rv = 2;
} else if (g1.link_count < 4 || g2.link_count < 4) {
rv = 100;
}
} else {
if (!n1_is_group && !n2_is_group) {
rv = 50;
} else if ((n1_is_group && n2_is_group) && (g1.link_count < 4 || g2.link_count < 4)) {
rv = 100;
} else if ((n1_is_group && g1.link_count < 2) || (n2_is_group && g2.link_count < 2)) {
rv = 30;
} else if (!n1_is_group || !n2_is_group) {
rv = 100;
}
}
return l.distance = rv;
})
.gravity(1.0)
.charge(function (d, i) {
if (d.size > 0) {
return -5000;
} else {
return -1000;
}
})
.friction(0.7)
.start();
force2 = d3.layout.force()
.nodes(net.helper_nodes)
.links(net.helper_links)
.size([width, height])
.linkDistance(function (l, i) {
var n1 = l.real_source, n2 = l.real_target, rv,
lr = l.g_ref,
n1r, n2r,
dx, dy;
if (lr.source.size > 0 || lr.target.size > 0)
return 20;
return 1;
})
.gravity(0.0)
.charge(function (d, i) {
if (d.fixed)
return -10;
var l = d.link_ref,
c = l.link_count || 1;
if (l.source.size > 0 || l.target.size > 0)
return -30;
return -1;
})
.friction(0.95)
.start()
.stop();
hullg.selectAll("path.hull").remove();
hull = hullg.selectAll("path.hull")
.data(convexHulls(net.nodes, off))
.enter().append("path")
.attr("class", "hull")
.attr("d", drawCluster)
.style("fill", function (d) { return fill(d.group); })
.on("click", on_hull_click);
if (debug == 1) {
link = linkg.selectAll("line.link").data(net.links, linkid);
link.exit().remove();
link.enter().append("line")
.attr("class", "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; });
link.style("stroke-width", function (d) { return d.size || 1; });
}
hlink = helper_linkg.selectAll("path.hlink").data(net.helper_render_links, function (d) {
return d.id;
});
hlink.exit().remove();
hlink.enter().append("path")
.attr("class", "hlink");
hlink.style("stroke-width", function (d) { return d.size || 1; });
if (debug) {
hnode = helper_nodeg.selectAll("circle.node").data(net.helper_nodes, function (d) {
return d.id;
});
hnode.exit().remove();
hnode.enter().append("circle")
.attr("class", function (d) {
return "node" + (d.size > 0 ? "" : d.size < 0 ? " helper" : " leaf");
})
.attr("r", function (d) {
return d.size > 0 ? d.size + dr : d.size < 0 ? 2 : dr + 1;
})
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.style("fill", function (d) { return fill(d.group); });
}
node = nodeg.selectAll("circle.node").data(net.nodes, nodeid);
node.exit().remove();
node.enter().append("circle")
.attr("class", function (d) {
return "node" + (d.size > 0 ? d.expansion ? " link-expanded" : "" : " leaf");
})
.attr("r", function (d) {
return d.size > 0 ? d.size + dr : dr + 1;
})
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.style("fill", function (d) { return fill(d.group); })
.on("click", on_node_click);
node.call(force.drag);
var drag_in_progress = false;
var change_squared;
node
.on("mouseout.ger_fix", function (d) {
if (debug == 1) console.log("mouseout.ger_fix", this, arguments, d.fixed, drag_in_progress);
if (drag_in_progress) {
force.resume();
}
});
var resume_threshold = 0.05;
force.on("tick", function (e) {
var center = { x: 0, y: 0, weight: 0 }, singles = [],
size, c, k, mx, my, dx, dy, alpha;
drag_in_progress = false;
net.nodes.forEach(function (n) {
var w = Math.max(1, n.size || 0, n.weight || 0);
center.x += w * n.x;
center.y += w * n.y;
center.weight += w;
if (n.fixed & 2) {
drag_in_progress = true;
}
if (n.size > 0 ? n.link_count < 4 : n.group_data.link_count < 3)
singles.push(n);
});
size = force.size();
mx = size[0] / 2;
my = size[1] / 2;
singles.forEach(function (n) {
var l = n.first_link, n2 = n.first_link_target,
proj, ax, bx, ay, by, k, x, y, alpha, rej, power,
dx, dy,
n_is_group = n.size || 0,
ng = n.group_data || n,
c2,
w = Math.max(1, n.size || 0, n.weight || 0);
if (!l)
return;
power = Math.max(2, n_is_group ? n.link_count : n.group_data.link_count);
power = 2 / power;
alpha = e.alpha * power;
if (k = alpha * force.gravity() * (0.8 + power)) {
dx = (mx - n.x) * k;
dy = (my - n.y) * k;
n.x -= dx;
n.y -= dy;
center.x -= dx * w;
center.y -= dy * w;
}
});
center.x /= center.weight;
center.y /= center.weight;
if (debug == 1) {
c = vis.selectAll("circle.center-of-mass")
.attr("cx", center.x)
.attr("cy", center.y);
}
dx = mx - center.x;
dy = my - center.y;
alpha = e.alpha * 5;
dx *= alpha;
dy *= alpha;
net.nodes.forEach(function (n) {
n.x += dx;
n.y += dy;
});
change_squared = 0;
net.nodes.forEach(function (n) {
var k, dx, dy,
r = (n.size > 0 ? n.size + dr : dr + 1) + 2 ;
dx = 0;
if (n.x < r)
dx = r - n.x;
else if (n.x > size[0] - r)
dx = size[0] - r - n.x;
dy = 0;
if (n.y < r)
dy = r - n.y;
else if (n.y > size[1] - r)
dy = size[1] - r - n.y;
k = 1.2;
n.x += dx * k;
n.y += dy * k;
if (n.fixed) {
n.x = n.px;
n.y = n.py;
}
n.px = n.x;
n.py = n.y;
change_squared += (n.qx - n.x) * (n.qx - n.x);
change_squared += (n.qy - n.y) * (n.qy - n.y);
n.qx = n.x;
n.qy = n.y;
});
force2.resume();
force2.tick();
force2.stop();
if (change_squared < .005) {
if (debug == 1) console.log("fast stop: CPU load redux");
force.stop();
if (drag_in_progress) {
if (debug == 1) console.log("START monitor drag in progress", drag_in_progress);
d3.timer(function () {
drag_in_progress = false;
net.nodes.forEach(function (n) {
if (n.fixed & 2) {
drag_in_progress = true;
}
});
force.resume();
if (debug == 1) console.log("monitor drag in progress: drag ENDED", drag_in_progress);
return true;
}, 500);
}
} else if (change_squared > net.nodes.length * 5 && e.alpha < resume_threshold) {
force.alpha(Math.min(0.1, e.alpha *= 2));
resume_threshold *= 0.9;
}
if (!hull.empty()) {
hull.data(convexHulls(net.nodes, off))
.attr("d", drawCluster);
}
if (debug == 1) {
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; })
.attr("cy", function (d) { return d.y; });
});
force2.on("tick", function (e) {
net.helper_nodes.forEach(function (n) {
var o;
if (n.fixed) {
o = n.ref;
n.px = n.x = o.x;
n.py = n.y = o.y;
}
});
net.helper_links.forEach(function (l) {
var o = l.g_ref;
l.distance = o.distance;
});
net.helper_nodes.forEach(function (n) {
if (n.fixed)
return;
change_squared += (n.qx - n.x) * (n.qx - n.x);
change_squared += (n.qy - n.y) * (n.qy - n.y);
n.qx = n.x;
n.qy = n.y;
});
hlink.attr("d", function (d) {
var linedata = [
[d.real_source.x, d.real_source.y],
[d.source.x, d.source.y],
[d.target.x, d.target.y],
[d.real_target.x, d.real_target.y]
];
return pathgen(linedata);
});
if (debug) {
hnode.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; });
}
});
}
parseJson(data)
}