Created
March 6, 2012 16:33
-
-
Save gre/1987311 to your computer and use it in GitHub Desktop.
Algorithm to find the best grid step for chart rendering
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
var GridUtils = function() { | |
var log10 = Math.log(10.) | |
function powOf10 (n) { | |
return Math.floor(Math.log(n)/log10) | |
} | |
return { | |
findNiceRoundStep: function (delta, preferedStep) { | |
var n = delta / preferedStep; | |
var p = powOf10(n); | |
var p10 = Math.pow(10, p); | |
var digit = n/p10; | |
if(digit<1.5) | |
digit = 1; | |
else if(digit<3.5) | |
digit = 2; | |
else if(digit < 7.5) | |
digit = 5; | |
else { | |
p += 1; | |
p10 = Math.pow(10, p); | |
digit = 1; | |
} | |
return digit * p10; | |
} | |
} | |
}(); |
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
object Scale { | |
lazy val log10 = math.log(10.) | |
def powOf10 (n: Double): Int = math.floor(math.log(n)/log10).toInt | |
def findNiceRoundStep (delta: Double, preferedStep: Double) = { | |
val n = delta / preferedStep | |
var p = powOf10(n).toDouble | |
var p10 = math.pow(10, p) | |
var digit = n/p10 | |
if(digit<1.5) | |
digit = 1. | |
else if(digit<3.5) | |
digit = 2. | |
else if(digit < 7.5) | |
digit = 5. | |
else { | |
p += 1. | |
p10 = math.pow(10, p) | |
digit = 1. | |
} | |
digit * p10 | |
} | |
} |
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
<!DOCTYPE HTML> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Algorithm to find the best grid step for chart rendering</title> | |
<style type="text/css"> | |
#wrapper { | |
margin: 0 auto; | |
width: 500px; | |
} | |
#info { | |
margin-top: 10px; | |
clear: both; | |
} | |
p { | |
padding: 0; | |
margin: 0; | |
} | |
footer { | |
margin-top: 10px; | |
padding-top: 10px; | |
text-align: center; | |
} | |
footer a { | |
color: #09f; | |
} | |
dl { | |
display: block; | |
margin: 0; | |
padding: 0; | |
} | |
dt { | |
padding: 0; | |
margin: 0; | |
font-weight: bold; | |
} | |
dt:after { | |
content: ': '; | |
} | |
div.controls { | |
float: left; | |
margin: 0 10px; | |
} | |
div.controls a { | |
display: block; | |
border-radius: 20px; | |
padding: 2px 6px; | |
width: 10px; | |
text-decoration: none; | |
background: black; | |
color: white; | |
text-align: center; | |
font-size: 18px; | |
font-weight: bold; | |
} | |
div.controls a:hover { | |
background: #0c0; | |
} | |
#grid { | |
float: left; | |
} | |
#info { | |
margin: 20px 0; | |
text-align: right; | |
} | |
#info, footer { | |
clear: both; | |
} | |
</style> | |
<script type="text/javascript" src="GridUtils.js"></script> | |
</head> | |
<body> | |
<div id="wrapper"> | |
<div class="controls"> | |
<a id="splits_plus" href="#">+</a> | |
<div>splits</div> | |
<a id="splits_minus" href="#">-</a> | |
</div> | |
<div class="controls"> | |
<a id="scale_plus" href="#">+</a> | |
<div>scale</div> | |
<a id="scale_minus" href="#">-</a> | |
</div> | |
<canvas id="grid" width="350" height="50"></canvas> | |
<div id="info"> | |
<p>Range: <strong>[<span id="rangeMin"></span>, <span id="rangeMax"></span>]</strong></p> | |
<p>Try to split by: <strong id="splits"></strong></p> | |
</div> | |
<footer id="footer"> | |
<a href="http://blog.greweb.fr/?p=1091">Read the article</a> - | |
<a href="http://gist.github.com/gre">Gist</a> | |
</footer> | |
</div> | |
<script type="text/javascript">(function(){ | |
window.requestAnimFrame = (function(){ | |
return window.requestAnimationFrame || | |
window.webkitRequestAnimationFrame || | |
window.mozRequestAnimationFrame || | |
window.oRequestAnimationFrame || | |
window.msRequestAnimationFrame || | |
function( callback ){ | |
window.setTimeout(callback, 1000 / 60); | |
}; | |
})(); | |
if (window.parent != window) { | |
var node = document.getElementById("footer"); | |
node.style.display = "none"; | |
} | |
(function(){ | |
var canvas = document.getElementById("grid"); | |
var ctx = canvas.getContext("2d"); | |
var MARGIN = 20; | |
// state variables | |
var tStart = +new Date(); | |
var splits, n, max, step; | |
function scale (x) { | |
return Math.round(Math.exp(x) * 1000) / 1000; | |
} | |
function setSplits (s) { | |
splits = s; | |
document.getElementById("splits").innerHTML = splits; | |
} | |
function setN (v) { | |
n = Math.max(0, v); | |
max = scale(n); | |
step = GridUtils.findNiceRoundStep(max, splits); | |
document.getElementById("rangeMin").innerHTML = 0; | |
document.getElementById("rangeMax").innerHTML = max; | |
} | |
setSplits(6); | |
setN(4); | |
document.getElementById("scale_plus").addEventListener("click", function (e) { | |
e.preventDefault(); | |
setN(n+1); | |
}); | |
document.getElementById("scale_minus").addEventListener("click", function (e) { | |
e.preventDefault(); | |
setN(n-1); | |
}); | |
document.getElementById("splits_plus").addEventListener("click", function (e) { | |
e.preventDefault(); | |
setSplits(splits+1); | |
setN(n); | |
}); | |
document.getElementById("splits_minus").addEventListener("click", function (e) { | |
e.preventDefault(); | |
setSplits(splits-1); | |
setN(n); | |
}); | |
function putPoint(px) { | |
var w = canvas.width, h = canvas.height; | |
var x = MARGIN + (px*(w-2*MARGIN)/max); | |
var y = Math.floor(h/2); | |
ctx.fillStyle = "red"; | |
ctx.beginPath(); | |
ctx.arc(x, y+2, 2, 0, Math.PI*2); | |
ctx.fill(); | |
ctx.fillText(""+px, x, y); | |
} | |
function setup () { | |
} | |
var lastn, lastsplits; | |
function render () { | |
if (lastn !== n || lastsplits !== splits) { | |
lastn = n; | |
lastsplits = splits; | |
var w = canvas.width, h = canvas.height; | |
ctx.clearRect(0,0,w,h); | |
ctx.strokeStyle = "black"; | |
ctx.fillStyle = "black"; | |
ctx.beginPath(); | |
ctx.moveTo(0, h-1); | |
ctx.lineTo(w, h-1); | |
ctx.stroke(); | |
var x, y; | |
ctx.textBaseline = "bottom"; | |
ctx.textAlign = "center"; | |
var s = Math.floor(max/step); | |
var stepX = (w-2*MARGIN)/(max/step); | |
for (var i = 0; i <= s; ++i) { | |
var x = MARGIN+i*stepX; | |
var xText = (""+Math.round(i*step*100)/100); | |
ctx.beginPath(); | |
ctx.moveTo(x, h-1); | |
ctx.lineTo(x, h-6); | |
ctx.stroke(); | |
ctx.fillText(xText, x, h-10); | |
} | |
putPoint(max); | |
putPoint(Math.floor(10*Math.random()*(max*0.8))/10); | |
} | |
} | |
setup(); | |
(function loop () { | |
requestAnimFrame(function() { | |
loop(); | |
render(); | |
}, canvas); | |
}()); | |
}()); | |
}()); | |
</script> | |
<script type="text/javascript"> | |
var _gaq = _gaq || []; | |
_gaq.push(['_setAccount', 'UA-9919624-4']); | |
_gaq.push(['_trackPageview']); | |
(function() { | |
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; | |
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; | |
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); | |
})(); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment