Skip to content

Instantly share code, notes, and snippets.

@gre
Created March 6, 2012 16:33
Show Gist options
  • Save gre/1987311 to your computer and use it in GitHub Desktop.
Save gre/1987311 to your computer and use it in GitHub Desktop.
Algorithm to find the best grid step for chart rendering
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;
}
}
}();
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
}
}
<!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