Mercurial > hg > xsplines
changeset 6:d3a25d39a6e2
implement closed x-splines
author | Jordi Gutiérrez Hermoso <jordi@ecometrica.com> |
---|---|
date | Sun, 26 Aug 2018 10:49:31 -0400 |
parents | a9ffd0bcc292 |
children | 35975411a2a1 |
files | splines.html splines.js |
diffstat | 2 files changed, 73 insertions(+), 39 deletions(-) [+] |
line wrap: on
line diff
--- a/splines.html +++ b/splines.html @@ -58,20 +58,23 @@ </head> <body> - <input name="approximateButton" - type="button" - value="Approximate all" - onclick="approximate_all()" /> - <input name="sharplyInterpolateButton" - type="button" - value="Sharply interpolate all" - onclick="sharply_interpolate_all()" /> - <input name="interpolateButton" - type="button" - value="Interpolate all" - onclick="interpolate_all()" /> - <form> + <input name="approximateButton" + type="button" + value="Approximate all" + onclick="approximate_all()" /> + <input name="sharplyInterpolateButton" + type="button" + value="Sharply interpolate all" + onclick="sharply_interpolate_all()" /> + <input name="interpolateButton" + type="button" + value="Interpolate all" + onclick="interpolate_all()" /> + <input name="toggleClosed" + type="button" + value="Toggle open/closed spline" + onclick="toggle_closed()" /> <label for="interpolate">SVG spline type:</label> <select id="interpolate"></select><br> </form>
--- a/splines.js +++ b/splines.js @@ -5,6 +5,8 @@ return [i * width / 5, 50 + Math.random() * (height - 100), 1]; }); +// How many points to plot between nodes +var tstep = 20; var dragged = null, selected = nodes[0]; @@ -63,13 +65,10 @@ d3.select("#slider").call(slider); +var closed_xspline = false; -// How many points to plot between nodes -var tstep = 20; // Parameter space, "time", one "second" per node -var tvec = linspace(0, nodes.length-1, nodes.length*tstep); - -var x = d3.scale.linear().range([0,width]).domain([0,tvec.length]); +var tvec = linspace(0, nodes.length-1, (nodes.length-1)*tstep + 1); var xspline = d3.svg.line() .x(function(d,i){return xspline_at_t(d, 0);}) @@ -82,7 +81,12 @@ function redraw() { svg.select("path.line").attr("d", line); - svg.select("path.xspline").attr("d", xspline); + if (!closed_xspline) { + tvec = linspace(0, nodes.length-1, (nodes.length-1)*tstep + 1); + } else { + tvec = linspace(0, nodes.length, nodes.length*tstep + 1); + } + svg.select("path.xspline").datum(tvec).attr("d", xspline); var circle = svg.selectAll("circle") .data(nodes, function(d) { return d; }); @@ -127,8 +131,6 @@ newcirc.push(1); slider.value(1); nodes.push(selected = dragged = newcirc); - tvec = linspace(0, nodes.length-1, nodes.length*tstep); - svg.select("path.xspline").datum(tvec); redraw(); } @@ -154,8 +156,6 @@ var i = nodes.indexOf(selected); nodes.splice(i, 1); selected = nodes.length ? nodes[i > 0 ? i - 1 : 0] : null; - tvec = linspace(0, nodes.length-1, nodes.length*tstep); - svg.select("path.xspline").datum(tvec); redraw(); break; } @@ -184,25 +184,27 @@ // Non-normalised blending function at node k function fk(k, t) { - // No blending functions outside the range - if(k >= nodes.length || k < 0) { - return 0; + var n = nodes.length; + + if (! closed_xspline) { + // No blending functions outside the range + if(k >= n || k < 0) { + return 0; + } } - // Special case the endpoints - if ( (t == 0 && k == 0) || (k == nodes.length-1 && t == nodes.length-1)) { - return 1; - } + // Determine if we're doing the left or right side var s, tboundary; - // Determine if we're doing the left or right side if (t < k) { - s = nodes[k-1][2]; + var leftk = (n + k - 1)%n; + s = nodes[leftk][2]; tboundary = k - 1 - s; } else { - s = nodes[k+1][2]; + var rightk = (n + k + 1)%n; + s = nodes[rightk][2]; tboundary = k + 1 + s; } @@ -248,20 +250,44 @@ function xspline_at_t(t, XorY) { // Determine which blending functions have influence at this point. - var k1 = Math.floor(t) - 1, k2 = Math.floor(t), k3 = Math.ceil(t), k4 = Math.ceil(t) + 1; + var k1, k2, k3, k4; + var tfloor = Math.floor(t); + var tceil = Math.ceil(t); + if ( t == tfloor) { + // When exactly on a node, only immediate neighbours have + // influence + k1 = t - 1; + k2 = t; + k3 = t + 1; + k4 = null; + } + else { + k1 = tfloor - 1; + k2 = tfloor; + k3 = tceil; + k4 = tceil + 1; + } + + var n = nodes.length; + + // When indexing, it has to be mod n + var k1_idx = (n + k1) % n, + k2_idx = (n + k2) % n, + k3_idx = (n + k3) % n, + k4_idx = k4 ? (n + k4) % n : null; var f1t = fk(k1, t), f2t = fk(k2, t), f3t = fk(k3, t), - f4t = fk(k4, t); + f4t = k4 ? fk(k4, t) : 0; // normalisation factor var denom = f1t + f2t + f3t + f4t; - var p1 = k1 >= 0 ? nodes[k1] : [0, 0], - p2 = k2 >= 0 && k2 < nodes.length ? nodes[k2] : [0, 0], - p3 = k3 >= 0 && k3 < nodes.length ? nodes[k3] : [0, 0], - p4 = k4 < nodes.length ? nodes[k4] : [0, 0]; + var p1 = nodes[k1_idx], + p2 = nodes[k2_idx], + p3 = nodes[k3_idx], + p4 = k4 ? nodes[k4_idx] : [0, 0]; var at_t = (f1t*p1[XorY] + f2t*p2[XorY] + f3t*p3[XorY] + f4t*p4[XorY])/denom; @@ -288,3 +314,8 @@ } redraw(); } + +function toggle_closed() { + closed_xspline = !closed_xspline; + redraw(); +}