Skip to content

Instantly share code, notes, and snippets.

@cmgiven
Created August 12, 2016 12:58
Show Gist options
  • Save cmgiven/231f779f9655025f38b5b4b828f3b7b0 to your computer and use it in GitHub Desktop.
Save cmgiven/231f779f9655025f38b5b4b828f3b7b0 to your computer and use it in GitHub Desktop.
Collatz Conjecture
license: mit
height: 960

Block-a-Day #6. Creates a force directed graph of the Collatz conjecture containing all numbers up to 500. See this Numberphile video for more explanation, or the relevant XKCD.

What I Learned: There's still not a way for SVG markers to inherit a color from their parent. First time I used d3.map; works as expected. Also, I learned to search to see if someone had already done it before I create the block (check out this Jason Davies project that tackles the problem in reverse to beautiful effect).

What I'd Do With More Time: A mouseover effect that traces the path to 1 would be useful, as would be control over the animation (start/stop, speed up/slow down).

Block-a-Day

Just what it sounds like. For fifteen days, I will make a D3.js v4 block every single day. Rules:

  1. Ideas over implementation. Do something novel, don't sweat the details.
  2. No more than two hours can be spent on coding (give or take).
  3. Every. Single. Day.

Previously

<!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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment