Skip to content

Instantly share code, notes, and snippets.

@Redchards
Created August 31, 2016 22:24
Show Gist options
  • Select an option

  • Save Redchards/f0b2cd2c3f4669717c8c671b2d4ddb33 to your computer and use it in GitHub Desktop.

Select an option

Save Redchards/f0b2cd2c3f4669717c8c671b2d4ddb33 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="potato.js"></script>
</head>
<body onload="init();">
</body>
</html>
var graphZone;
var ctx;
var axes;
var container;
var points = new Array();
var pointsRef = new Array();
var graphZoneBaseHeight = 200;
var graphZoneBaseWidth = 600;
var test2DSpline = [{x : 1.31, y : -1.94},
{x : 2.89, y : 1.50},
{x : 2.05, y : 1.47},
{x : 1.67, y : 1.40},
{x : 3.12, y : 1.77},
{x : 2.05, y : 0.07},
{x : 0.23, y : 0.77},
{x : 3.04, y : 0.41},
{x : 1.31, y : -1.94}];
var test2DSpline2 = [{x : 2.5, y : 0},
{x : 1.3, y : -0.25},
{x : -0.25, y : 1.3},
{x : 0, y : 2.5},
{x : 0.25, y : 1.3},
{x : -1.3, y : -0.25},
{x : -2.5, y : 0},
{x : -1.3, y : 0.25},
{x : 0.25, y : -1.3},
{x : 0, y : -2.5},
{x : -0.25, y : -1.3},
{x : 1.3, y : 0.25},
{x : 2.5, y : 0}];
function assert(cond, msg) {
if(!cond) {
msg = msg || "Assert failed";
if(typeof Error !== "undefined") {
throw new Error(msg);
}
throw message;
}
}
function ProxyArray() {
//Object.setPrototypeOf(this, Array.prototype);
this.innerArray = new Array();
this.innerArray.__proto__ = ProxyArray.prototype;
return new Proxy(this,
{
set : function(target, property, value) {
target.innerArray[property] = value;
console.log("Set");
}
})
}
ProxyArray.prototype = new Array;
function onHandlersClick() {
alert("clicked !");
}
function init() {
//graphZone = document.getElementById("graph");
//borders = document.createElement("div");
//borders.style.border = "1px dotted grey";
//handlers.addEventListener("click", onHandlersClick);
//borders.style.width = graphZoneBaseWidth + "px";
//borders.style.height = graphZoneBaseHeight + "px";
//borders.style.position = "relative";
//document.body.appendChild(borders);
container = document.createElement("div");
document.body.appendChild(container);
graphZone = document.createElement("canvas");
graphZone.setAttribute("id", "graph");
graphZone.setAttribute("width", graphZoneBaseWidth);
graphZone.setAttribute("height", graphZoneBaseHeight);
graphZone.style.top = "0px";
graphZone.style.left = "0px";
graphZone.style.position = "absolute";
graphZone.style.border = "1px dotted grey";
graphZone.style.float = "left";
container.appendChild(graphZone);
//<canvas id="graph" width="500" height="200"></canvas>
//console.log(handlers.offsetHeight);
ctx = graphZone.getContext("2d");
ctx.strokeStyle = "black";
var topLeftHandler = createHandler();
topLeftHandler.style.top = "0";
topLeftHandler.style.left = "0";
container.appendChild(topLeftHandler);
initGraph();
//plotFunction(Math.cos, -5, 5, 100);
//plotFunction(Math.sin, -5, 5, 100);
//plotFunction(Math.exp, -5, 5, 100);
var addPointButton = document.createElement("div");
addPointButton.style.width = "100px";
addPointButton.style.height = "20px";
addPointButton.style.border = "1px solid black";
addPointButton.style.float = "right";
addPointButton.addEventListener("click", addPointButtonClick);
addPointButton.style.cursor = "pointer";
addPointButton.innerHTML = "Add point";
addPointButton.style.textAlign = "center";
document.body.appendChild(addPointButton);
addPoint(1, 1);
addPoint(1.35, 1.25);
addPoint(2.36, -1.3);
addPoint(3.56, -1.7);
addPoint(4.56, -1.7);
addPoint(4.96, -1.0);
addPoint(5.96, -1.45);
addPoint(-5.96, -1.45);
plot([1, 2, 3, -2], [0, 2, 1, 2]);
draw2DSplinesFrom(test2DSpline2, 20);
/*ctx.moveTo(axes.origin.x, axes.origin.y);
ctx.lineTo(axes.origin.x -1*axes.scale.x, axes.origin.y + 1*axes.scale.y);
ctx.moveTo(axes.origin.x, axes.origin.y);
ctx.lineTo(axes.origin.x +1*axes.scale.x, axes.origin.y + 1*axes.scale.y);
ctx.moveTo(axes.origin.x, axes.origin.y - 1*axes.scale.y);
ctx.lineTo(axes.origin.x -1*axes.scale.x, axes.origin.y - 1*axes.scale.y);
ctx.moveTo(axes.origin.x, axes.origin.y - 1*axes.scale.y);
ctx.lineTo(axes.origin.x +1*axes.scale.x, axes.origin.y - 1*axes.scale.y);
ctx.moveTo(axes.origin.x + 0.5*axes.scale.x, axes.origin.y - 1.5*axes.scale.y);
ctx.arc(axes.origin.x, axes.origin.y - 1.5*axes.scale.y, 1*axes.scale.y/2, 0, 2 * Math.PI, false);
ctx.stroke();*/
//console.log(computeSecondSplineDerivatives(points));
drawSplinesFrom(pointsRef);
}
function initGraph() {
drawAxes();
drawGrid({x:2, y:2});
}
function plotFunction(func, from, to, samples) {
console.log(from, to);
assert(from < to, "'from' should be greater than 'to' !");
var interval = (to - from);
var beginPosition = axes.origin.x + from*axes.scale.x + 0.5;
var endPosition = axes.origin.x + to*axes.scale.x + 0.5;
var scale = interval / samples;
ctx.beginPath();
ctx.moveTo(beginPosition, axes.origin.y - func(from)*axes.scale.y);
//console.log("From : " + from);
for(var i = scale; i < interval; i += scale) {
ctx.lineTo(beginPosition + i*axes.scale.x, axes.origin.y - func(from + i)*axes.scale.y - 0.5);
//console.log((beginPosition) + " : " + (from + i) + " : " + func(from + i));
}
ctx.lineTo(endPosition, axes.origin.y - func(to)*axes.scale.y - 0.5);
ctx.stroke();
}
function plot(x, y) {
assert(x.length == y.length, "x and y arrays should contain the same number of arguments");
var beginXPosition = axes.origin.x + x[0]*axes.scale.x + 0.5;
var beginYPosition = axes.origin.y - y[0]*axes.scale.y - 0.5;
ctx.beginPath();
ctx.moveTo(beginXPosition, beginYPosition);
for(var i = 1; i < x.length; ++i) {
ctx.lineTo(axes.origin.x + x[i]*axes.scale.x + 0.5, axes.origin.y - y[i]*axes.scale.y - 0.5);
}
ctx.stroke();
}
function drawAxes() {
axes = new Object();
axes.origin = new Object();
axes.scale = new Object();
axes.origin.x = graphZone.width * 0.5;
axes.origin.y = graphZone.height * 0.5;
axes.scale.x = 50;
axes.scale.y = 50;
ctx.beginPath();
ctx.moveTo(0.5, axes.origin.y + 0.5);
ctx.lineTo(graphZone.width + 0.5, axes.origin.y + 0.5);
ctx.moveTo(axes.origin.x + 0.5, -0.5);
ctx.lineTo(axes.origin.x + 0.5, graphZone.height - 0.5);
ctx.stroke();
}
function drawGrid(resolution) {
var oldStrokeStyle = new String(ctx.strokeStyle);
var oldLineWidth = new Number(ctx.lineWidth);
ctx.strokeStyle = "grey";
ctx.lineWidth = 0.5;
ctx.beginPath();
var beginXValue = (axes.scale.x / resolution.x);
var beginYValue = (axes.scale.y / resolution.y);
var tmp = parseInt(graphZone.width)/axes.scale.x;
var floored = Math.floor(tmp);
var diffX = (tmp - floored);
//console.log(diffX);
// = parseInt(graphZone.;
if(tmp != floored) {
// DO adjustment
beginXValue = diffX;
//console.log(beginXValue);
}
tmp = parseInt(graphZone.height)/axes.scale.y;
floored = Math.floor(tmp);
var diffY = (tmp - floored);
//console.log(diffY);
if(tmp != floored) {
beginYValue = diffY;
//console.log(beginYValue);
}
for(var i = beginXValue + 0.5; i < graphZone.width; i += axes.scale.x/resolution.x) {
ctx.moveTo(i, 0);
ctx.lineTo(i, graphZone.height);
}
for(var i = Math.floor(beginYValue) - 0.5; i < graphZone.height; i += axes.scale.y/resolution.y) {
ctx.moveTo(0, i);
ctx.lineTo(graphZone.width, i);
}
ctx.stroke();
ctx.strokeStyle = oldStrokeStyle;
ctx.lineWidth = oldLineWidth;
}
function onHandlerDrag(e) {
console.log("drag");
}
function log2DPosition(x, y) {
console.log(x + " : " + y);
}
function startMove(e) {
e = e || window.event;
var origPosX = e.clientX;
var origPosY = e.clientY;
log2DPosition(origPosX, origPosY);
var target = e.target;
var parent = target.parentElement;
var parentOrigHeight = parseInt(graphZone.height);
var parentOrigWidth = parseInt(graphZone.width);
var parentOrigLeft = (parseInt(graphZone.style.left) || 0);
var parentOrigTop = (parseInt(graphZone.style.top) || 0 );
var topPosition = parseInt(target.style.top.replace("px", ""))
|| parseInt(target.style.bottom.replace("px", ""))
|| 0;
var leftPosition = parseInt(target.style.left.replace("px", ""))
|| parseInt(target.style.right.replace("px", ""))
|| 0;
document.onmousemove = function(e) {
e = e || window.event;
var posX = e.clientX;
var posY = e.clientY;
var diffX = posX - origPosX;
var diffY = posY - origPosY;
targetX = diffX + leftPosition;
targetY = diffY + topPosition;
doMove(target, targetX, targetY);
//log2DPosition(parentOrigWidth - diffX + "px", parentOrigHeight - diffY);
graphZone.width = parentOrigWidth - diffX;
graphZone.height = parentOrigHeight - diffY;
log2DPosition(parentOrigWidth)
graphZone.style.left = parentOrigLeft + diffX + "px";
graphZone.style.top = parentOrigTop + diffY + "px";
//console.log("Top : " + parent.style.top);
}
//console.log("left : " + leftPosition + "\ntop : " + topPosition);
}
function stopMove() {
console.log("Unhook event");
document.onmousemove = function(){}
initGraph();
}
function doMove(e, x, y) {
e.style.left = x + "px";
e.style.top = y + "px";
}
function startMovePoint(e) {
e = e || window.event;
var origPosX = e.clientX;
var origPosY = e.clientY;
var target = e.target;
var point = target;
var origPointX = point.x;
var origPointY = point.y;
var topPosition = parseInt(target.style.top.replace("px", "")) || 0;
var leftPosition = parseInt(target.style.left.replace("px", "")) || 0;
document.onmousemove = function(e) {
e = e || window.event;
var diffX = e.clientX - origPosX;
var diffY = e.clientY - origPosY;
target.style.top = topPosition + diffY + "px";
target.style.left = leftPosition + diffX + "px";
point.x = origPointX + diffX/axes.scale.x;
point.y = origPointY - diffY/axes.scale.y;
clearCanvas();
initGraph();
pointsRef.sort(comparePoints);
drawSplinesFrom(pointsRef);
}
document.onmouseup = stopMovePoint;
}
function stopMovePoint(e) {
document.onmousemove = function() {}
}
function clearCanvas() {
ctx.clearRect(0, 0, graphZone.width, graphZone.height);
}
function createHandler() {
var handler = document.createElement("div");
handler.style.border = "1px solid black";
handler.style.position = "absolute";
handler.style.height = "5px";
handler.style.width = "5px";
handler.addEventListener('mousedown', startMove);
handler.addEventListener('mouseup', stopMove);
handler.setAttribute("draggable", "true");
return handler;
}
function addPoint(x, y) {
var point = document.createElement("div");
point.style.top = axes.origin.y - y*axes.scale.y - 3 + "px";
point.style.left = axes.origin.x + x*axes.scale.x - 3 + "px";
console.log(point.style.left);
point.style.border = "1px solid black";
point.style.borderRadius = "100%";
point.style.height = "6px";
point.style.width = "6px";
point.style.position = "absolute";
point.addEventListener("mousedown", startMovePoint);
point.style.cursor = "all-scroll";
//point.id = points.length || "0";
point.x = x;
point.y = y;
pointsRef.push(point);
container.appendChild(point);
points.push({x:x, y:y, htmlElement:point});
pointsRef.sort(comparePoints);
for(var elem of pointsRef) {
console.log(elem);
}
return point;
}
function comparePoints(p1, p2) {
log2DPosition(p1.x, p2.x);
console.log(p1.x > p2.x);
if(p1.x > p2.x) {
return 1;
}
else if(p1.x < p2.x) {
return -1;
}
return 0;
/*if((greater && (parseInt(p1.id) < parseInt(p2.id))) || (!greater && (parseInt(p1.id) > parseInt(p2.id)))) {
var tmp = new String(p1.htmlElement.id);
p1.htmlElement.id = new String(p2.htmlElement.id);
p2.htmlElement.id = tmp;
}
return greater;*/
}
function addPointButtonClick(e) {
x = e.clientX/axes.scale.x - axes.origin.x/axes.scale.x ;
y = axes.origin.y/axes.scale.y - e.clientY/axes.scale.y;
log2DPosition(e.clientX, y)
var newPoint = addPoint(x, y);
startMovePoint({target: newPoint, clientX: e.clientX, clientY: e.clientY});
}
function drawSplinesFrom(points) {
// Should check if sorted first.
// points.sort(comparePoints);
var splines = generateSplineFrom(points);
for(var i = 0; i < points.length - 1; ++i) {
plotFunction(splines[i], points[i].x, points[i + 1].x, 20);
}
}
function euclideanNorm(u) {
return Math.sqrt(u.x*u.x + u.y*u.y);
}
function draw2DSplinesFrom(points, samples) {
var parameters = new Array();
var xPoints = new Array();
var yPoints = new Array();
parameters.push(0);
var xSpline, ySpline;
for(var i = 1; i < points.length; ++i) {
//console.log({x : points[i].x - points[i - 1].x, y : points[i].y * points[i - 1].y})
parameters[i] = parameters[i - 1] + euclideanNorm({x : points[i].x - points[i - 1].x, y : points[i].y - points[i - 1].y});
}
console.log(parameters);
for(var i = 0; i < points.length; ++i) {
xPoints.push({y : points[i].x, x : parameters[i]});
}
for(var i = 0; i < points.length; ++i) {
yPoints.push({y : points[i].y, x : parameters[i]})
}
xSpline = generate2DSplineFrom(xPoints);
ySpline = generate2DSplineFrom(yPoints);
var xCoord = new Array(),
yCoord = new Array();
for(var i = 0; i < parameters.length - 1; ++i) {
var interval = parameters[i + 1] - parameters[i];
var step = interval / samples;
for(var j = 1; j <= samples; ++j) {
//console.log(j*step)
xCoord.push(xSpline[i](parameters[i] + j*step));
yCoord.push(ySpline[i](parameters[i] + j*step));
}
console.log(xCoord, yCoord);
}
plot(xCoord, yCoord);
}
function generate2DSplineFrom(points) {
var splines = new Array();
var curvatures = computeSecondPeriodicSplineDerivatives(points);
var splineGenerator = function(i) {
return {
function(x) {
var hi = (points[i + 1].x - points[i].x);
var A = (curvatures[i + 1] - curvatures[i])/(6*hi);
var B = (curvatures[i]/2);
var C = ((-(hi)*curvatures[i + 1])/6) - (hi)*curvatures[i]/3 + (points[i + 1].y - points[i].y)/(hi);
var xxi = (x - points[i].x);
var res = points[i].y + (xxi)*(C + (xxi)*(B + (xxi)*A));
return res;
}
}
}
for(var i = 0; i < points.length - 1; ++i) {
splines[i] = splineGenerator(i).function;
}
return splines;
}
function generateSplineFrom(points) {
var splines = new Array();
var curvatures = computeSecondSplineDerivatives(points);
var splineGenerator = function(i) {
return {
function(x) {
var hi = (points[i + 1].x - points[i].x);
var A = (curvatures[i + 1] - curvatures[i])/(6*hi);
var B = (curvatures[i]/2);
var C = ((-(hi)*curvatures[i + 1])/6) - (hi)*curvatures[i]/3 + (points[i + 1].y - points[i].y)/(hi);
var xxi = (x - points[i].x);
var res = points[i].y + (xxi)*(C + (xxi)*(B + (xxi)*A));
return res;
}
}
}
for(var i = 0; i < points.length - 1; ++i) {
splines[i] = splineGenerator(i).function;
}
return splines;
}
function evalSpline(splineFunc, from, to, samples) {
var step = (to - from)/samples;
var evalValues = new Array();
for(var i = from; i < to; i += step) {
evalValue.push(splineFunc(i));
}
}
function tridiagonalSolve(a, b, c, d) {
//assert(a.length == d.lenght)
}
function solveTridiagonal(a, b, c, d) {
// Condition implies c.length == d.length
// assert(b.length == (c.length + 1) && b.length == (d.length + 1) && a.length == b.length,
// "The system is not tridiagonal");
//console.log(a, b, c, d)
var results = new Array();
c[0] = c[0]/b[0];
d[0] = d[0]/b[0];
for(var i = 1; i < d.length; ++i) {
//console.log(d.length + " " + c.length)
var div = b[i] - a[i - 1] * c[i - 1];
c[i] = c[i] / (div);
d[i] = (d[i] - (a[i - 1] * d[i - 1])) / div;
//console.log("After " + d[i] + a[i])
}
results[d.length - 1] = d[d.length - 1];
for(var i = d.length - 2; i >= 0; --i) {
//console.log(d.length - 1 - i)
results[i] = d[i] - c[i] * results[i + 1];
}
//console.log(results);
return results;
}
// Solving the tridiagonal system using Thomas algorithm.
function computeSecondSplineDerivatives(points) {
var a = new Array(),
b = new Array(),
c = new Array(),
d = new Array();
var point2 = points[2];
var x2 = (point2 && point2.x) || 0;
var y2 = (point2 && point2.y) || 0;
b.push(2 * (x2 - points[0].x));
c.push(x2 - points[1].x);
d.push(6 * (((y2 - points[1].y) / (x2 - points[1].x))
- ((points[1].y - points[0].y) / (points[1].x - points[0].x))));
var hi, him;
for(var i = 2; i < points.length - 2; ++i) {
hi = (points[i+1].x - points[i].x);
him = (points[i].x - points[i - 1].x);
a.push(points[i].x - points[i - 1].x);
b.push(2*(hi + him));
c.push(hi);
d.push((6*(((points[i + 1].y - points[i].y) / (hi))
- ((points[i].y - points[i - 1].y) / (him)))));
}
var i = points.length - 2;
hi = (points[i+1].x - points[i].x);
him = (points[i].x - points[i - 1].x);
a.push(points[i].x - points[i - 1].x);
b.push(2*(hi + him));
d.push((6*(((points[i + 1].y - points[i].y) / (hi))
- ((points[i].y - points[i - 1].y) / (him)))));
var results = new Array();
var toConcat = solveTridiagonal(a, b, c, d);
results[0] = 0;
results = results.concat(toConcat);
//console.log(toConcat);
results.push(0);
return results;
}
function computeSecondPeriodicSplineDerivatives(points) {
var a = new Array(),
b = new Array(),
c = new Array(),
d1 = new Array(),
d2 = new Array();
var results = new Array();
var point2 = points[2];
var x2 = (point2 && point2.x) || 0;
var y2 = (point2 && point2.y) || 0;
b.push(2 * (x2 - points[0].x));
c.push(x2 - points[1].x);
d1.push(6 * (((y2 - points[1].y) / (x2 - points[1].x))
- ((points[1].y - points[0].y) / (points[1].x - points[0].x))));
d2.push(-(points[points.length - 1].x - points[points.length - 2].x));
var hi, him;
for(var i = 2; i < points.length - 3; ++i) {
hi = (points[i+1].x - points[i].x);
him = (points[i].x - points[i - 1].x);
a.push(points[i].x - points[i - 1].x);
b.push(2*(hi + him));
c.push(hi);
d1.push((6*(((points[i + 1].y - points[i].y) / (hi))
- ((points[i].y - points[i - 1].y) / (him)))));
d2.push(0);
}
var i = points.length - 3;
hi = (points[i+1].x - points[i].x);
him = (points[i].x - points[i - 1].x);
a.push(points[i].x - points[i - 1].x);
b.push(2*(hi + him));
d1.push((6*(((points[i + 1].y - points[i].y) / (hi))
- ((points[i].y - points[i - 1].y) / (him)))));
//d2.push(-(points[0].x - points[1].x));
d2.push(-(points[points.length - 1].x - points[points.length - 2].x));
var firstSolution = solveTridiagonal(a, b, c, d1);
var secondSolution = solveTridiagonal(a, b, c, d2);
++i;
hi = (points[i+1].x - points[i].x);
him = (points[i].x - points[i - 1].x);
xn = (6*(((points[i + 1].y - points[i].y) / (hi)) - ((points[i].y - points[i - 1].y) / (him)))
- hi*firstSolution[0] - him*firstSolution[firstSolution.length - 1]) /
(2*(hi + him) + hi*secondSolution[0] + him*secondSolution[secondSolution.length - 1]);
console.log(xn)
results.push(xn)
for(var i = 1; i < firstSolution.length + 1; ++i) {
results[i] = firstSolution[i - 1] + secondSolution[i - 1]*xn
}
results.push(xn);
results.push(results[1]);
console.log(results.length)
return results;
}
// The old routine to compute second derivative. The new is a bit slower, but cleaner.
function computeSecondSplineDerivativesOld(points) {
// Add check if there is only one point.
var
c = new Array(),
d = new Array();
var results = new Array();
var point2 = points[2];
var x2 = (point2 && point2.x) || 0;
var y2 = (point2 && point2.y) || 0;
var b0 = 2 * (x2 - points[0].x)
c[0] = (x2 - points[1].x)/ b0;
d[0] = 6 * (((y2 - points[1].y) / (x2 - points[1].x))
- ((points[1].y - points[0].y) / (points[1].x - points[0].x))) / b0;
for(var i = 2; i < points.length - 1; ++i) {
var hi = (points[i+1].x - points[i].x);
var him = (points[i].x - points[i - 1].x);
var ai = points[i].x - points[i - 1].x;
var bi = 2*(hi + him);
c[i - 1] = (hi / (bi - ai*c[i - 2]));
d[i - 1] = (6*(((points[i + 1].y - points[i].y) / (hi))
- ((points[i].y - points[i - 1].y) / (him))) - ai*d[i - 2]) / (bi - ai*c[i - 2]);
}
results[0] = 0;
results[points.length - 1] = 0;
results[points.length - 2] = d[points.length - 3] || 0;
for(var i = points.length - 3; i > 0; --i) {
//console.log(i);
results[i] = d[i-1] - c[i-1]*(results[i + 1]);
}
return results;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment