|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
circle { fill: #555; } |
|
line.even { |
|
stroke: #6cd; |
|
marker-end: url(#triangle-even); |
|
} |
|
line.odd { |
|
stroke: #e84; |
|
marker-end: url(#triangle-odd); |
|
} |
|
text { |
|
fill: #555; |
|
font-family: sans-serif; |
|
font-size: 10px; |
|
-webkit-user-select: none; |
|
-moz-user-select: none; |
|
-ms-user-select: none; |
|
user-select: none; |
|
cursor: default; |
|
} |
|
</style> |
|
<body> |
|
<svg> |
|
<marker id="triangle-even" |
|
viewBox="0 0 10 10" |
|
refX="13" refY="5" |
|
markerUnits="strokeWidth" |
|
markerWidth="4" markerHeight="4" |
|
orient="auto"> |
|
<path d="M0 0 L10 5 L0 10 z" fill="#6cd" /> |
|
</marker> |
|
<marker id="triangle-odd" |
|
viewBox="0 0 10 10" |
|
refX="13" refY="5" |
|
markerUnits="strokeWidth" |
|
markerWidth="4" markerHeight="4" |
|
orient="auto"> |
|
<path d="M0 0 L10 5 L0 10 z" fill="#e84" /> |
|
</marker> |
|
</svg> |
|
<script src="//d3js.org/d3.v4.min.js"></script> |
|
<script> |
|
var interval = 400 |
|
var maxStartNumber = 500 |
|
var width = 960 |
|
var height = 960 |
|
|
|
var i = 1 |
|
var map = d3.map() |
|
|
|
var simulation = d3.forceSimulation() |
|
.on('tick', ticked) |
|
.alphaTarget(1) |
|
.force('charge', d3.forceManyBody().strength(-10)) |
|
.force('centerX', d3.forceX(width / 2).strength(0.05)) |
|
.force('centerY', d3.forceY(height / 2).strength(0.05)) |
|
.force('link', d3.forceLink().distance(10).strength(0.1)) |
|
|
|
var drag = d3.drag() |
|
.on('start', dragStarted) |
|
.on('drag', dragged) |
|
.on('end', dragEnded) |
|
|
|
var svg = d3.select('svg') |
|
.attr('width', width) |
|
.attr('height', height) |
|
|
|
var linkLayer = svg.append('g') |
|
var nodeLayer = svg.append('g') |
|
|
|
var links = [] |
|
var nodes = [] |
|
|
|
addNode({ n: 1, fx: width / 2, fy: height / 2 }) |
|
|
|
function addNode(node, prevNode) { |
|
node.x = prevNode ? prevNode.x : width / 2 |
|
node.y = prevNode ? prevNode.y : height / 2 |
|
|
|
nodes.push(node) |
|
map.set(node.n, node) |
|
|
|
var next = node.n % 2 === 0 ? node.n / 2 : node.n * 3 + 1 |
|
var nextNode = map.get(next) |
|
|
|
if (prevNode) { addLink(prevNode, node) } |
|
if (nextNode) { addLink(node, nextNode) } |
|
|
|
var nodeG = nodeLayer.append('g') |
|
.datum(node) |
|
.attr('class', 'node') |
|
|
|
nodeG.append('circle') |
|
.attr('r', node.n === 1 ? 3 : 2) |
|
|
|
nodeG.append('text') |
|
.attr('x', 2) |
|
.attr('y', -2) |
|
.text(node.n) |
|
|
|
if (node.n !== 1) { nodeG.call(drag) } |
|
|
|
simulation.nodes(nodes) |
|
simulation.force('link').links(links) |
|
|
|
if (nextNode || node.n === 1) { |
|
d3.timeout(findNext, interval) |
|
} else { |
|
d3.timeout(function () { addNode({ n: next }, node) }, interval) |
|
} |
|
} |
|
|
|
function addLink(source, target) { |
|
var link = { source: source, target: target } |
|
links.push(link) |
|
linkLayer.append('line') |
|
.datum(link) |
|
.attr('class', source.n % 2 === 0 ? 'even' : 'odd') |
|
} |
|
|
|
function findNext() { |
|
while (map.has(i)) { i++ } |
|
if (i > maxStartNumber) { simulation.alphaTarget(0); return } |
|
addNode({ n: i }) |
|
} |
|
|
|
function ticked() { |
|
nodeLayer.selectAll('.node') |
|
.attr('transform', function (d) { return 'translate(' + d.x + ',' + d.y + ')' }) |
|
|
|
linkLayer.selectAll('line') |
|
.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 }) |
|
} |
|
|
|
function dragStarted(d) { |
|
if (simulation.alphaTarget() !== 1) { simulation.alphaTarget(0.5).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 (simulation.alphaTarget() !== 1) { simulation.alphaTarget(0) } |
|
d.fx = null |
|
d.fy = null |
|
} |
|
|
|
</script> |
|
</body> |