Skip to content

Instantly share code, notes, and snippets.

@kmxz
Created January 11, 2015 19:13
Show Gist options
  • Save kmxz/8a10690effc5656f8e7e to your computer and use it in GitHub Desktop.
Save kmxz/8a10690effc5656f8e7e to your computer and use it in GitHub Desktop.
Naive function plotter
<html>
<head>
<link rel="stylesheet" type="text/css" href="http://bootswatch.com/paper/bootstrap.min.css" />
<style type="text/css">
.template {
display: none;
}
</style>
<script type="text/javascript">
var add, addListeners, conf, getColor, getGray, init, initconfAndSetListeners, mathWrapX, maxOfArray, minOfArray, randShift, reAssign, reRender, toArray;
toArray = function(input) {
return Array.prototype.slice.call(input);
};
conf = {};
randShift = Math.random();
mathWrapX = function(exp) {
return new Function("x", "with(Math) { return (" + exp + ");}");
};
getColor = function(angle) {
return "hsl(" + ((angle + randShift % 1) * 360) + ", 80%, 50%)";
};
getGray = function(shade) {
return "hsl(" + randShift + ", 0%, " + 100 * shade + "%)";
};
reAssign = function() {
var i, te, valid;
valid = toArray(document.querySelectorAll(".entry")).filter(function(te) {
if (!te.querySelector("input").value.trim().length) {
te.parentNode.removeChild(te);
return false;
}
return true;
});
for (i = 0; i < valid.length; i++) {
te = valid[i];
te.querySelector(".input-group-addon").style.color = getColor(i / valid.length);
}
add();
console.log("Reassigned...");
};
maxOfArray = function(arr) {
return arr.reduce((function(a, b) {
return Math.max(a, b);
}), Number.NEGATIVE_INFINITY);
};
minOfArray = function(arr) {
return arr.reduce((function(a, b) {
return Math.min(a, b);
}), Number.POSITIVE_INFINITY);
};
reRender = function() {
var allEntries, allFunc, canvas, ctx, i, i2x, j, labelPos, labelText, lastError, max, min, oldCanvas, padding, realHeight, realWidth, xMarks, xs, y2j, yMarks, ys;
lastError = null;
padding = conf["padding"];
canvas = document.createElement("canvas");
canvas.width = conf["width"];
canvas.height = conf["height"];
realWidth = conf["width"] - padding * 2;
realHeight = conf["height"] - padding * 2;
allEntries = toArray(document.querySelectorAll(".entry"));
allEntries.pop();
allFunc = allEntries.map(function(te) {
return mathWrapX(te.querySelector("input").value);
});
ctx = canvas.getContext("2d");
xs = [];
i2x = function(i) {
return i * (conf["max"] - conf["min"]) / realWidth + conf["min"];
};
for (i = 0; i < realWidth; i++) {
xs.push(i2x(i));
}
ys = xs.map(function(x) {
return allFunc.map(function(func) {
var y;
y = 0;
try {
y = func(x);
} catch (_) {
lastError = "Parse Error!";
}
if (isNaN(y) || !isFinite(y)) {
lastError = "Illegal return value " + y;
}
return y;
});
});
max = maxOfArray(ys.map(maxOfArray));
min = minOfArray(ys.map(minOfArray));
y2j = function(y) {
return realHeight * (max - y) / (max - min) + padding;
};
ctx.font = "normal normal 12px sans-serif";
ctx.fillStyle = getGray(0.5);
ctx.textBaseline = "bottom";
ctx.textAlign = "right";
ctx.strokeStyle = getGray(0.9);
yMarks = Math.floor(realHeight / padding / 1.5);
for (i = 0; i <= yMarks; i++) {
labelText = (i / yMarks) * (max - min) + min;
labelPos = y2j(labelText);
ctx.fillText(labelText.toPrecision(3), padding, labelPos, padding);
ctx.beginPath();
ctx.moveTo(padding, labelPos);
ctx.lineTo(realWidth + padding, labelPos);
ctx.stroke();
}
ctx.textBaseline = "top";
ctx.textAlign = "left";
xMarks = Math.floor(realWidth / padding / 1.5);
for (i = 0; i <= xMarks; i++) {
labelPos = i / xMarks * realWidth;
labelText = i2x(labelPos);
labelPos += padding;
ctx.fillText(labelText.toPrecision(3), labelPos, padding + realHeight, padding);
ctx.beginPath();
ctx.moveTo(labelPos, padding + realHeight);
ctx.lineTo(labelPos, padding);
ctx.stroke();
}
for (j = 0; j < allFunc.length; j++) {
ctx.strokeStyle = getColor(j / allFunc.length);
ctx.beginPath();
ctx.moveTo(padding, y2j(ys[0][j]));
for (i = 1; i < realWidth; i++) {
ctx.lineTo(i + padding, y2j(ys[i][j]));
}
ctx.stroke();
}
if (lastError) {
ctx.font = "normal normal 24px sans-serif";
ctx.fillStyle = "#000";
ctx.textBaseline = "middle";
ctx.textAlign = "center";
ctx.fillText(lastError, conf["width"] / 2, conf["height"] / 2);
}
oldCanvas = document.querySelector("canvas");
oldCanvas.parentNode.replaceChild(canvas, oldCanvas);
console.log("Rerendered...");
};
addListeners = function(container) {
input = container.querySelector("input");
input.innerHTML = '';
input.addEventListener("keyup", reAssign);
input.addEventListener("change", reRender);
};
add = function() {
var cloned, e;
e = document.querySelector(".template");
cloned = e.cloneNode(true);
cloned.classList.remove('template');
cloned.classList.add('entry');
addListeners(cloned);
e.parentNode.appendChild(cloned);
return cloned;
};
initconfAndSetListeners = function(key) {
var el;
el = document.getElementById(key);
conf[key] = parseFloat(el.value);
el.addEventListener("change", function() {
var evaled;
evaled = parseFloat(el.value);
if (!isNaN(evaled)) {
conf[key] = evaled;
reRender();
} else {
el.value = conf[key];
}
});
};
init = function() {
add().querySelector('input').value = 'x * sin(x)';
add().querySelector('input').value = 'x > 2 * PI ? x * cos(x) : x';
initconfAndSetListeners("width");
initconfAndSetListeners("height");
initconfAndSetListeners("min");
initconfAndSetListeners("max");
initconfAndSetListeners("padding");
reAssign();
reRender();
document.querySelector(".container").style.display = "block";
console.log("Initialized...");
};
document.addEventListener("DOMContentLoaded", init);
</script>
<script type="text/javascript" src="http://coffeescript.org/extras/coffee-script.js"></script>
</head>
<body>
<div class="container" style="display: none">
<div class="page-header">
<h1>Naive function plotter</h1>
</div>
<div class="thumbnail">
<canvas></canvas>
</div>
<div class="panel panel-default">
<div class="panel-heading">Range</div>
<div class="panel-body">
<div class="input-group">
<span class="input-group-addon">minimum: </span>
<input type="text" class="form-control" value="0" id="min" />
</div>
<div class="input-group">
<span class="input-group-addon">maximum: </span>
<input type="text" class="form-control" value="10" id="max" />
</div>
<div class="input-group">
<span class="input-group-addon">width: </span>
<input type="text" class="form-control" value="640" id="width" />
</div>
<div class="input-group">
<span class="input-group-addon">height: </span>
<input type="text" class="form-control" value="480" id="height" />
</div>
<div class="input-group">
<span class="input-group-addon">padding: </span>
<input type="text" class="form-control" value="40" id="padding" />
</div>
</div>
<div class="panel-footer">Dimensions include paddings.</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">Entries</div>
<div class="panel-body">
<div class="input-group template">
<span class="input-group-addon">function of x: </span>
<input type="text" class="form-control" />
</div>
</div>
</div>
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment