Built with blockbuilder.org
forked from shimizu's block: D3 v4 - force layout
forked from anonymous's block: D3 v4 - force layout
license: gpl-3.0 |
Built with blockbuilder.org
forked from shimizu's block: D3 v4 - force layout
forked from anonymous's block: D3 v4 - force layout
<html> | |
<head> | |
<style> | |
.links path { | |
fill: none; | |
stroke-width: 3px; | |
} | |
.nodes circle { | |
fill-opacity: 0.5; | |
} | |
</style> | |
</head> | |
<body> | |
<svg id="graph" width="700" height="1000"> | |
</svg> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/4.1.1/d3.min.js"></script> | |
<script> | |
!(function(){ | |
var svg = d3.select("#graph").append("g").attr('transform', 'translate(200 200)') | |
n = 4 | |
ids = d3.range(0,n).map(function(j){ | |
return [4*j, 4*j+1] | |
}) | |
ids = [].concat.apply([], ids) | |
var drawFrom = function(rest) { | |
// select a random item, and remove it from the deck | |
i = Math.floor(Math.random()*rest.length) | |
pick = rest[i] | |
rest.splice(i,1) // i = rest.indexOf(pick) | |
// TODO: prevent ports from picking their own up/down mirror | |
// console.log(rest, pick, i) | |
return pick | |
} | |
// KNOT | |
crossings = d3.range(0,n).map(function(j){ | |
return {state: 0, // TODO: use 'state' for smoothings | |
ul: 4*j+2, ur: 4*j+3, | |
bl: drawFrom(ids), br: drawFrom(ids)} | |
}) | |
links = crossings.map(function(n, i) { | |
return [n.ul, n.ur, n.br, n.bl].map(function(goal, corner){ | |
return {target: goal, source: 4*i+corner} }) | |
}) | |
links = Array.prototype.concat.apply([], links) | |
// FIXME: preserve over/underness of crossings. | |
// tour links from base points on each knot? | |
// FIGURE | |
ports = d3.range(0,4*n).map(function(j){ | |
return {id: j} | |
}) | |
/* FIXME: given a port, return its successor (link) and predecessor (link) */ | |
fakelinks = d3.range(0,n).map(function(j){ | |
i = 4*j | |
// TODO: make permutations concise | |
ln = function(a,b) {return {fake: true, | |
source: a, target: b}} | |
return [ln(i,i+1), ln(i,i+2), ln(i,i+3), | |
ln(i+1,i+2), ln(i+1,i+3), ln(i+2,i+3)] | |
}) | |
links = Array.prototype.concat.apply(links, fakelinks) | |
// d3 setup, binding | |
var simulation = d3.forceSimulation() | |
.force("link", d3.forceLink().id(function(d) { return d.index })) | |
// .force("collide",d3.forceCollide( function(d){return d.r + 8 }).iterations(16) ) | |
.force("charge", d3.forceManyBody().strength(-100)) | |
// .force("center", d3.forceCenter(chartWidth / 2, chartWidth / 2)) | |
.force("y", d3.forceY(0)) | |
.force("x", d3.forceX(0)) | |
var port = svg.append("g") | |
.attr("class", "nodes") | |
.selectAll("circle").data(ports) | |
.enter().append("circle") | |
.attr("r", 10) | |
.attr("fill", function(d,i) { | |
return i==0 ? '#511' : i==1 ? '#373' : i==2 ? '#559' : | |
i%2==0 ? '#aaa' : '#ccc' }) | |
.call(d3.drag() | |
.on("start", dragstarted) | |
.on("drag", dragged) | |
.on("end", dragended)); | |
var link = svg.append("g") | |
.attr("class", "links") | |
.selectAll("path").data(links) | |
.enter().append("svg:path") | |
.attr("stroke", function(d,k) { | |
v = d.fake ? 0 : | |
Math.floor(4 * (4*n - k)/n).toString(16) // \in [0,f] | |
return d.source==0 ? '#955' : d.source==1 ? '#373' : d.source==2 ? '#559' : | |
k ? '#'+v+v+v : '#000' }) | |
.attr("stroke-dasharray", function(d) { | |
return d.fake ? [10,5] : [0,0] }) | |
.attr("stroke-opacity", function(d) { | |
return d.fake ? .1 : 1 }) | |
.attr("pointer-events", "none") | |
var overlay = svg.append("g") | |
.attr("class", "overlay") | |
.selectAll("circle").data(crossings) | |
.enter().append("circle") | |
.attr("r", 30) // TODO: adjust for wide spreads? | |
.attr("fill", '#99e') | |
.attr("fill-opacity", .1) | |
// FIXME: add drag event affecting all ports | |
.on("click", function(d, i) { | |
// FIXME: must update incoming edges for horz smoothing, | |
// else have (3,1) edges and not (2,2) ea. side | |
out0 = d.state < 2 ? d.br : d.state == 2 ? d.bl : d.ur | |
out1 = d.state < 2 ? d.bl : d.br | |
d.state = (d.state + 1) % 4 | |
console.log(d.state, out0, out1) | |
// TODO: more concise state-switch | |
if (d.state == 0) { // over | |
d.ul = 4*i+2; d.ur = 4*i+3; | |
d.bl = out1; d.br = out0; | |
} | |
else if (d.state == 1) { // under (FIXME: implement) | |
d.ul = 4*i+2; d.ur = 4*i+3; | |
d.bl = out1; d.br = out0; | |
} | |
else if (d.state == 2) { // vert | |
d.ul = 4*i+3; d.ur = 4*i+2; | |
d.bl = out0; d.br = out1; | |
} | |
else if (d.state == 3) { // horz | |
d.ul = 4*i+1; d.ur = out0; | |
d.bl = 4*i+2; d.br = out1; | |
} | |
up = [d.ul, d.ur, d.br, d.bl].map(function(goal, corner){ | |
return {target: ports[goal], | |
source: ports[4*i+corner], idx: i} }) | |
Array.prototype.splice.apply(links, [4*i, 4].concat(up)) | |
// console.log(links[4*i]) | |
console.log(up) | |
// general update pattern bl.ocks.org/mbostock/1095795 | |
link = link.data(links) | |
link.exit().remove() | |
link = link.enter().append("svg:path").merge(link) | |
ticked() | |
}) | |
// simulation callback, binding | |
var ticked = function() { | |
// elliptical segments: http://stackoverflow.com/a/16371890 | |
link.attr("d", function(d) { | |
var dx = d.target.x - d.source.x, | |
dy = d.target.y - d.source.y, | |
dr = d.fake ? 0 : Math.sqrt(dx * dx + dy * dy); | |
return "M" + d.source.x + "," + d.source.y + | |
"A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y; | |
}); | |
port | |
.attr("cx", function(d) { return d.x; }) | |
.attr("cy", function(d) { return d.y; }); | |
var portsIn = function(d,i) { | |
return d3.range(4*i, 4*i+4).map(function(i) {return ports[i]}) }; | |
overlay | |
.attr("cx", function(d,i) { | |
return d3.mean(portsIn(d,i).map(function(u) {return u.x}) ) }) | |
.attr("cy", function(d,i) { | |
return d3.mean(portsIn(d,i).map(function(u) {return u.y}) ) }); | |
} | |
simulation | |
.nodes(ports) | |
.on("tick", ticked); | |
simulation | |
.force("link") | |
.links(links); | |
function dragstarted(d) { | |
if (!d3.event.active) simulation.alphaTarget(0.3).restart(); | |
d.fx = d.x; | |
d.fy = d.y; | |
} | |
function dragged(d) { | |
d.fx = d3.event.x; | |
d.fy = d3.event.y; | |
} | |
function dragended(d) { | |
if (!d3.event.active) simulation.alphaTarget(0); | |
d.fx = null; | |
d.fy = null; | |
} | |
}()); | |
</script> | |
</body> | |
</html> |