Created
December 14, 2019 10:39
-
-
Save Phaiax/c0af54d2cb8615614c188d80ca02ef39 to your computer and use it in GitHub Desktop.
d4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<style> | |
canvas { | |
margin: 10 auto; | |
border: 1px solid gray; | |
display:block; | |
} | |
div#info { | |
position: absolute; | |
top: 10px; | |
left: 10px; | |
width: 200px; | |
height: 50px; | |
background: #44444444; | |
/*opacity: 0.5;*/ | |
} | |
</style> | |
</head> | |
<body> | |
<canvas id="canvas" width="1200" height="900"></canvas> | |
<div id="info"></div> | |
<script src="https://d3js.org/d3.v5.min.js"></script> | |
<script src="https://d3js.org/d3-collection.v1.min.js"></script> | |
<script src="https://d3js.org/d3-dispatch.v1.min.js"></script> | |
<script src="https://d3js.org/d3-quadtree.v1.min.js"></script> | |
<script src="https://d3js.org/d3-timer.v1.min.js"></script> | |
<script src="https://d3js.org/d3-force.v1.min.js"></script> | |
<script> | |
function generateData() { | |
var abcdes = []; | |
for (var i = 0; i < 1000; i++) { | |
id = "A-" + i; | |
abcdes.push({"id": id, | |
"label": id, | |
"state": "A", | |
}); | |
} | |
var fghijs = []; | |
for (var i = 0; i < 1600; i++) { | |
id = "5065" + (''+i).padStart(4, '0'); | |
fghijs.push({"id": id, | |
"label": id, | |
"state": (i%3==0)? "B" : "C", | |
}); | |
} | |
var links = []; | |
var curr_abcde_idx = 0; | |
var curr_fghij_idx = 0; | |
while (true) { | |
var subbox_abcde_num = Math.floor(Math.random() * abcdes.length / 50); | |
var subbox_fghij_num = Math.floor(Math.random() * fghijs.length / 50); | |
if ( subbox_abcde_num + curr_abcde_idx >= abcdes.length | |
|| subbox_fghij_num + curr_fghij_idx >= fghijs.length) { | |
// remaining | |
subbox_abcde_num = abcdes.length - curr_abcde_idx; | |
subbox_fghij_num = fghijs.length - curr_fghij_idx; | |
} | |
var num_links = Math.floor(subbox_abcde_num * subbox_fghij_num | |
* (Math.random() / 2 + 0.25)); | |
console.log(subbox_abcde_num, subbox_fghij_num, num_links); | |
for (var i = 0; i < num_links; i++) { | |
var abcde_idx = curr_abcde_idx + Math.floor(Math.random() * subbox_abcde_num); | |
var fghij_idx = curr_fghij_idx + Math.floor(Math.random() * subbox_fghij_num); | |
links.push({ | |
"source": abcdes[abcde_idx].id, | |
"target": fghijs[fghij_idx].id, | |
"state": (Math.random() > 0.7) ? "D" : "E", | |
//"defghijion": "Link " + i | |
}) | |
} | |
curr_abcde_idx = Math.min(abcdes.length, curr_abcde_idx + subbox_abcde_num); | |
curr_fghij_idx = Math.min(fghijs.length, curr_fghij_idx + subbox_fghij_num); | |
if (curr_abcde_idx >= abcdes.length || curr_fghij_idx >= fghijs.length) { | |
break; | |
} | |
} | |
// add a few random links | |
for (var i = 0; i < 6; i++) { | |
//continue; | |
var abcde_idx = Math.floor(Math.random() * abcdes.length); | |
var fghij_idx = Math.floor(Math.random() * fghijs.length); | |
links.push({ | |
"source": abcdes[abcde_idx].id, | |
"target": fghijs[fghij_idx].id, | |
"state": (Math.random() > 0.7) ? "D" : "E", | |
//"defghijion": "Link " + i, | |
}) | |
} | |
return [abcdes.concat(fghijs), links] | |
} | |
function makeGraph(canvas, nodes, links, setInfo) { | |
var graphctx = { | |
sim : null, | |
nodes : nodes, | |
links : links, | |
zoomTransform: d3.zoomIdentity, | |
}; | |
var context = canvas.getContext("2d"); | |
var width = canvas.width; | |
var height = canvas.height; | |
function drawLink(link) { | |
context.moveTo(link.source.x, link.source.y); | |
context.lineTo(link.target.x, link.target.y); | |
} | |
function drawLinks(state, color) { | |
context.beginPath(); | |
context.strokeStyle = color; | |
graphctx.links | |
.filter(link => link.state == state) | |
.forEach(drawLink); | |
context.stroke(); | |
} | |
function drawNode(node) { | |
context.moveTo(node.x + 3, node.y); | |
context.arc(node.x, node.y, 3, 0, 2 * Math.PI); | |
} | |
function drawNodes(state, color) { | |
context.beginPath(); | |
context.fillStyle = color; | |
graphctx.nodes | |
.filter(node => node.state == state) | |
.forEach(drawNode); | |
context.fill(); | |
//context.strokeStyle = node.color; | |
//context.stroke(); | |
} | |
function ticked() { | |
context.save(); | |
context.clearRect(0, 0, width, height); | |
context.translate(graphctx.zoomTransform.x, graphctx.zoomTransform.y); | |
context.scale(graphctx.zoomTransform.k, graphctx.zoomTransform.k); | |
drawLinks("D", "#00FF00"); | |
drawLinks("E", "#444444"); | |
drawNodes("A", "#00FF00"); | |
drawNodes("B", "#FF00FF"); | |
drawNodes("C", "#444444"); | |
context.beginPath(); | |
context.strokeStyle = "#000000"; | |
context.moveTo(-10, 0); | |
context.lineTo(10, 0); | |
context.moveTo(0, -10); | |
context.lineTo(0, 10); | |
context.stroke(); | |
context.restore(); | |
} | |
var sim = d3.forceSimulation() | |
.nodes(graphctx.nodes) | |
.on("tick.mytick", ticked) | |
.stop(); | |
var steps=600; | |
sim.alphaDecay(1 - Math.pow(sim.alphaMin(), 1 / steps)); | |
sim.velocityDecay(0.5); | |
sim.force("linkforce", | |
d3.forceLink(graphctx.links) | |
// allow target and source to be ids: | |
.id( function(node) { return node.id; } ) | |
.distance(function (link) { return 30; }) | |
//.strength(function strength(link) { | |
// count(node) returns number of connections | |
//return 1 / Math.min(count(link.source), count(link.target)); | |
//return 0.1; | |
//}) | |
); | |
sim.force("center", | |
d3.forceCenter() | |
.x(width/2) | |
.y(height/2) | |
); | |
sim.force("charge", | |
d3.forceManyBody() | |
.strength(function strength() { | |
return -10; | |
}) | |
//.distanceMin() | |
//.distanceMax(100) | |
); | |
sim.force("gravity", | |
d3.forceManyBody() | |
.strength(function strength() { | |
return 10; | |
}) | |
.distanceMin(600) | |
//.distanceMax() | |
); | |
function getNearest(x, y) { | |
return sim.find(graphctx.zoomTransform.invertX(x), | |
graphctx.zoomTransform.invertY(y), | |
30); | |
} | |
function dragstarted() { | |
if (!d3.event.active) sim.alphaTarget(0.3).restart(); | |
//console.log("start subject xy", d3.event.subject.x, d3.event.subject.y, "event xy", //d3.event.x, d3.event.y); | |
d3.event.subject.drag_start_x = d3.event.x; | |
d3.event.subject.drag_start_y = d3.event.y; | |
d3.event.subject.fx = d3.event.subject.x; | |
d3.event.subject.fy = d3.event.subject.y; | |
} | |
function dragged() { | |
d3.event.subject.fx = d3.event.subject.drag_start_x | |
+ (d3.event.x - d3.event.subject.drag_start_x) / graphctx.zoomTransform.k; | |
d3.event.subject.fy = d3.event.subject.drag_start_y | |
+ (d3.event.y - d3.event.subject.drag_start_y) / graphctx.zoomTransform.k; | |
//console.log("event", d3.event.x, d3.event.y, "node", d3.event.subject.fx, d3.event.subject.fy, "transform", graphctx.zoomTransform.x, graphctx.zoomTransform.y, graphctx.zoomTransform.k); | |
} | |
function dragended() { | |
if (!d3.event.active) sim.alphaTarget(0); | |
d3.event.subject.fx = null; | |
d3.event.subject.fy = null; | |
} | |
function dragsubject() { | |
return getNearest(d3.event.x, d3.event.y) | |
} | |
d3.select(canvas) | |
.call(d3.drag() | |
.container(canvas) | |
.subject(dragsubject) | |
.on("start", dragstarted) | |
.on("drag", dragged) | |
.on("end", dragended)); | |
function zoomed(transform) { | |
graphctx.zoomTransform = transform; | |
} | |
d3.select(canvas).call(d3.zoom() | |
.scaleExtent([1 / 8, 4]) | |
.on("zoom", () => zoomed(d3.event.transform)) | |
//.on("wheel.zoom", pan) | |
); | |
d3.select(canvas).on("mousemove.my", function() { | |
var node = getNearest(d3.event.x, d3.event.y); | |
if (node != null) { | |
setInfo(node); | |
} | |
}); | |
graphctx.sim = sim; | |
return graphctx; | |
} | |
function restart() { | |
var [nodesArray, linksArray] = generateData(); | |
var canvas = document.querySelector("canvas#canvas"); | |
var infodiv = document.querySelector("#info"); | |
var setInfo = function(item) { | |
info.textContent = item.id; | |
}; | |
var graphctx = makeGraph(canvas, nodesArray, linksArray, setInfo); | |
graphctx.sim.restart(); | |
return graphctx; | |
} | |
graphctx = restart(); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment