|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<title>Kaprekar Routine</title> |
|
<script src="//d3js.org/d3.v3.min.js"></script> |
|
<style type="text/css"> |
|
body { |
|
padding: 0; |
|
margin: 0; |
|
font-family: "Helvetica Neue", Helvetica, sans-serif; |
|
font-size: 1.25em; |
|
} |
|
.axis path { |
|
display: none; |
|
} |
|
.axis line { |
|
stroke: #000; |
|
stroke-width: .25px; |
|
} |
|
rect { |
|
stroke: black; |
|
stroke-width: .25px; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div style="position: absolute; top: 30px; left: 300px;"> |
|
<b>Sort</b> by |
|
<i><a href="#" class="sort-btn" data-sort="n">n</a></i> or |
|
<i><a href="#" class="sort-btn" data-sort="i">iterations</a></i> |
|
</div> |
|
<script type="text/javascript"> |
|
var margin = {top: 100, right: 20, bottom: 0, left: 300}, |
|
width = 960 - margin.left - margin.right, |
|
height = 500 - margin.top - margin.bottom; |
|
|
|
var z = 3, |
|
xBlocks = 100, |
|
yBlocks = 100, |
|
s = .1, |
|
bh = ((s*100)+(z*100)) |
|
|
|
// index of the array is the value |
|
data = d3.range(100 * 100).map(function(n) { return {i: n, o: n, k: kaprekarProcess(n).length } }) |
|
|
|
svg = d3.select("body") |
|
.on("keydown", clearFocused) |
|
.append("svg") |
|
.attr("width", width + margin.left + margin.right) |
|
.attr("height", height + margin.top + margin.bottom) |
|
|
|
chart = svg.append("g") |
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")") |
|
|
|
var color = d3.scale.linear().domain([1, 7]).range([0, 300]) |
|
|
|
var xScale = d3.scale.linear().range([(s*100)+(z*100), 0]).domain([100, 0]) |
|
var yScale = d3.scale.linear().range([0, (s*100)+(z*100)]).domain([100, 0]) |
|
|
|
var axis = d3.svg.axis().ticks(25) |
|
// only show 0, 25, 50, and 100 ticks |
|
.tickFormat(function(d) { return d % 25 == 0 ? d : "" }) |
|
|
|
chart |
|
.append("g") |
|
.attr("class", "y axis") |
|
.attr("transform", "translate(0," + z + ")") |
|
.call(axis.scale(yScale).orient("left")) |
|
.append("text") |
|
.attr("class", "y label") |
|
.attr("text-anchor", "middle") |
|
.attr("transform", "translate(-50,"+ bh/2 +")rotate(-90)") |
|
.text("[n / 100]"); |
|
|
|
chart |
|
.append("g") |
|
.attr("class", "axis") |
|
.attr("transform", "translate(0," + (bh+z) + ")") |
|
.call(axis.scale(xScale).orient("bottom")) |
|
.append("text") |
|
.attr("class", "y label") |
|
.attr("text-anchor", "middle") |
|
.attr("transform", "translate(" + bh/2 + ", 50)") |
|
.text("n mod 100"); |
|
|
|
chart |
|
.append("text") |
|
.attr("class", "desc") |
|
.attr("transform", "translate(" + (bh + margin.right) + ", 0)") |
|
|
|
d3.selectAll(".sort-btn") |
|
.on("click", function(d) { |
|
d3.event.preventDefault(); |
|
sort(this.dataset.sort) |
|
update() |
|
}); |
|
|
|
var matrix = [] |
|
|
|
// draw data the first time |
|
update() |
|
|
|
// teaser - focus a random rect |
|
d3.select("rect:nth-child("+(Math.floor(Math.random() * (100*100)+1))+")").each(highlight) |
|
|
|
function sort(m) { |
|
var dataCopy = [] |
|
|
|
for (var i=0, length = data.length; i < length; i++) { |
|
dataCopy.push({ |
|
i: i, |
|
v: data[i] |
|
}); |
|
} |
|
|
|
if (m == "i") { |
|
dataCopy.sort(function(a, b) { |
|
// stable sort |
|
return a.v.k == b.v.k ? a.v.i - b.v.i : a.v.k - b.v.k |
|
}) |
|
} else { |
|
dataCopy.sort(function(a, b) { return a.v.i - b.v.i }) |
|
} |
|
|
|
for (var i=0, length = dataCopy.length; i < length; i++) { |
|
data[dataCopy[i].i].o = i |
|
} |
|
} |
|
|
|
function transform(d) { |
|
var x = (d.x * z) + (s * d.x), |
|
y = bh - ((d.y * z) + (s * d.y)) |
|
return "translate(" + x + "," + y + ")" + ( d.focused ? "scale(15)rotate(180)" : "") |
|
} |
|
|
|
function update() { |
|
data.forEach(function(d) { |
|
d.x = d.o % xBlocks |
|
d.y = Math.floor(d.o / yBlocks) |
|
}) |
|
|
|
var rects = chart.selectAll("rect") |
|
// so when reordering the dom it keeps the correct data mapping |
|
.data(data, function(d) { return d.i }) |
|
|
|
rects.enter() |
|
.append("rect") |
|
.attr("width", z) |
|
.attr("height", z) |
|
.attr("transform", transform) |
|
.style("fill", function(d) { return d3.hsl(color(d.k), 1, .5) }) |
|
.on("mouseover", highlight) |
|
.on("click", highlight) |
|
|
|
rects |
|
.transition() |
|
.duration(1000) |
|
.attr("transform", transform) |
|
} |
|
|
|
function clearFocused() { |
|
// reset any previously focused rects |
|
d3.select("rect.focused") |
|
.datum(function(d, i) { |
|
d.focused = false |
|
return d |
|
}) |
|
.classed("focused", function(d) { return d.focused }) |
|
.transition() |
|
.delay(250) |
|
.attr("transform", transform) |
|
.each("end", function(d) { |
|
d3.select(this).style("pointer-events", "auto") |
|
}) |
|
|
|
d3.selectAll("text.desc tspan").remove() |
|
} |
|
|
|
function highlight(d, i) { |
|
clearFocused() |
|
|
|
kaprekarProcessText(d.i) |
|
|
|
// so the element has the highest z-index |
|
this.parentNode.appendChild(this); |
|
|
|
// temporarily disable pointer-events so transition isn't triggered on a transitioning element |
|
d3.select(this) |
|
.datum(function(d, i) { |
|
d.focused = true |
|
return d |
|
}) |
|
.style("pointer-events", "none") |
|
.classed("focused", function(d) { return d.focused }) |
|
.transition() |
|
.duration(500) |
|
.attr("transform", transform) |
|
} |
|
|
|
function kaprekarProcessText(d) { |
|
var lines = [] |
|
|
|
var k = kaprekarProcess(d) |
|
lines.push(String(k[0])) |
|
|
|
lines = lines.concat(k.slice(1).map(function(d) { |
|
return d[0] + " - " + d[1] + " = " + d[2] |
|
})) |
|
|
|
var t = d3.select("text.desc") |
|
.selectAll("tspan") |
|
.data(lines) |
|
|
|
t.enter() |
|
.append("tspan") |
|
.style("font-weight", function(d, i) { return i == 0 ? "bold" : "normal" }) |
|
.attr("x", 0) |
|
.attr("dy", 30) |
|
|
|
t.text(String) |
|
|
|
t.exit().remove() |
|
} |
|
|
|
function kaprekarProcess(v) { |
|
var o = [v] |
|
for(var i=1; i <= 8; i++) { |
|
var digits = String(v).split('').map(parseFloat) |
|
// pad with zeros so we have 4 digits |
|
for(var j=digits.length; j < 4; j++) { |
|
digits.push(0) |
|
} |
|
var asc = digits.slice().sort().join('') |
|
var desc = digits.slice().sort().reverse().join('') |
|
v = +desc - +asc |
|
o.push([desc, asc, v]) |
|
if(v == 6174 || v == 0) break |
|
} |
|
return o |
|
} |
|
|
|
</script> |
|
</body> |
|
</html> |