Skip to content

Instantly share code, notes, and snippets.

@mgold
Last active September 18, 2015 14:30
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mgold/f549505c467167d97e1f to your computer and use it in GitHub Desktop.
Save mgold/f549505c467167d97e1f to your computer and use it in GitHub Desktop.
The 2-adic Numbers

This block is an animation of forming the 2-adic numbers in a fractal-like process. Starting with zero and one, we alternate two steps. First, we visually scale down the existing plot, which doesn't actually change anything. Then, we subdivide each number. The left half stays the same, and to the right half we add an increasing power of two, one more than the previously largest number in the list.

The 2-adic distance metric, which is shift invariant, considers numbers to be closer if they were formed by a more recent subdivision, which is shown visually as similar colors. Analytically, two numbers are close if their difference is divisible by a high power of two. So while 16 is close to 0, 32 is closer, and 64 is closer still.

The integers expand to the right of the number line (or the left of the numeral). But the 2-adics expand into themselves: each subdivision grows in the same space that was once zero to one. These numbers dart back and forth in a contained space; you can hold infinity in the palm of your hand.

Inspired by this video.

<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<style>
.axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
text {
font-family: avenir, sans-serif;
font-size: 11px;
}
</style>
</head>
<body>
<script>
var zeroHeight = 4;
var margin = {top: 12, right: 10, bottom: 30+zeroHeight, left: 25};
var width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scale.ordinal()
.domain([0,1])
.rangeBands([0, width]);
var y = d3.scale.linear()
.domain([0,1])
.range([height, 0]);
var colorRange =
["#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#31a354", "#74c476", "#a1d99b", "#c7e9c0",
"#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#843c39", "#ad494a", "#d6616b", "#e7969c"]
var colors = function(i){
var denom = Math.pow(2, 4-iteration)
return colorRange[Math.floor(i*denom)]
}
var maxIter = 4;
var iteration = 0;
var values = [0];
var yAxis = d3.svg.axis()
.orient("left")
.ticks(1)
.scale(y);
var xAxis = d3.svg.axis()
.orient("bottom")
.ticks(1)
.scale(x);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height+zeroHeight) + ")")
.call(xAxis);
var transDur = 1200
var shrink = function(){
y.domain([0, Math.pow(2, iteration+1)-1])
svg.select(".y.axis")
.transition().duration(transDur)
.call(yAxis.ticks(Math.min(32, y.domain()[1])));
var bars = svg.selectAll("rect.bar")
.transition().duration(transDur)
.attr("y", y)
.attr("height", function(d){ return height - y(d) + zeroHeight})
setTimeout(subdivide, 1.1*transDur)
}
var subdivide = function(){
var rise = Math.pow(2, iteration);
iteration++;
values = values.map(function(d){
return [ d, d+rise ];
}).reduce(function(a, b) { // flatten the pairs into a single list
return a.concat(b);
});
x.domain(d3.range(Math.pow(2, iteration)));
svg.select(".x.axis")
.transition().duration(transDur)
.call(xAxis.ticks(Math.min(20,x.domain()[1])));
var bars = svg.selectAll("rect.bar")
.data(values, function(d){return d})
bars.enter()
.append("rect")
.attr("class", "bar")
.attr("x", function(d,i){return x(i)})
.each(function(d,i){
var value = iteration === 1 ? d : values[i-1];
d3.select(this)
.attr("y", y(value))
.attr("height", height - y(value) + zeroHeight)
})
.style("fill", function(d,i){
if (iteration === 1){
return colors(i);
}else{
return colors(i - i%2); // start with neightbor's color
}
})
.transition().duration(transDur)
.attr("y", y)
.attr("height", function(d){ return height - y(d) + zeroHeight})
.style("fill", function(d,i){
return colors(i);
})
bars.attr("width", Math.ceil(x.rangeBand()));
if (iteration <= maxIter){
setTimeout(shrink, 1.1*transDur)
}else{
setTimeout(finalize, 1.1*transDur)
}
}
var finalize = function(){
svg.selectAll()
.data(values)
.enter()
.append("text")
.text(function(d){return d})
.attr("transform", function(d, i){return "translate("+(x(i)+x.rangeBand()/2)+","+(y(d)-3)+")"})
.style("opacity", 0)
.style("text-anchor", "middle")
.transition().duration(transDur)
.style("opacity", 1)
}
subdivide();
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment