Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: al2o3cr/cubic_graph
base: 79cb189f36
...
head fork: al2o3cr/cubic_graph
compare: 95bfa454fd
  • 4 commits
  • 3 files changed
  • 0 commit comments
  • 1 contributor
Showing with 441 additions and 49 deletions.
  1. +100 −25 app.js
  2. +332 −0 g.line.js
  3. +9 −24 index.html
View
125 app.js
@@ -15,7 +15,7 @@ window.drawGraph = function(r) {
var x = getX(), y = getY(), length = Math.min(x.length, y.length);
- if (length <= 1) {
+ if (length <= 2) {
return;
}
x = _.first(x, length);
@@ -28,30 +28,15 @@ window.drawGraph = function(r) {
x = _.map(points, xCoord);
y = _.map(points, yCoord);
- r.linechart(20, 0, 900, 550, x, y, {axis: '0 0 1 1', axisxstep: niceSteps(x), axisystep: niceSteps(y)});
- r.linechart(20, 0, 900, 550, x, y, {symbol: 'circle', nostroke: true});
-/* var x = [], y = [], y2 = [], y3 = [];
-
- for (var i = 0; i < 1e6; i++) {
- x[i] = i * 10;
- y[i] = (y[i - 1] || 0) + (Math.random() * 7) - 3;
- y2[i] = (y2[i - 1] || 150) + (Math.random() * 7) - 3.5;
- y3[i] = (y3[i - 1] || 300) + (Math.random() * 7) - 4;
- }
-
- var lines = r.linechart(0, 0, 900, 550, [[1, 2, 3, 4, 5, 6, 7],[3.5, 4.5, 5.5, 6.5, 7, 8]], [[12, 32, 23, 15, 17, 27, 22], [10, 20, 30, 25, 15, 28]],
-{ nostroke: false, axis: "0 0 1 1", symbol: "circle", smooth: true }).hoverColumn(function () {
- this.tags = r.set();
-
- for (var i = 0, ii = this.y.length; i < ii; i++) {
- this.tags.push(r.tag(this.x, this.y[i], this.values[i], 160, 10).insertBefore(this).attr([{ fill: "#fff" }, { fill: this.symbols[i].attr("fill") }]));
- }
- }, function () {
- this.tags && this.tags.remove();
- });
-
- lines.symbols.attr({ r: 6 }); */
-
+ var coefs = spline_coef(x, y);
+ var points = spline_plot(x, y, coefs);
+ r.linechart(20, 0, 900, 550, [x, points[0]], [y, points[1]],
+ {
+ axis: '0 0 1 1', axisxstep: niceSteps(x), axisystep: niceSteps(y),
+ symbol: ['circle', ''],
+ nostroke: [true, false]
+ });
+ $('#results').val(coefs.toString());
};
window.getX = function() {
@@ -94,3 +79,93 @@ window.niceSteps = function(a) {
return (steps <= 4 ? Math.floor(5*range/nearest) : steps);
}
+
+window.spline_coef = function(x, y) {
+ // returns the second derivatives needed to calculate a natural cubic spline
+ // through the given points
+ // source: Numerical Mathematics and Computing, 3rd Ed., Cheney & Kincaid, 1994, p. 297
+ //
+ // expects x and y to be equal-length arrays, with x in normal order
+
+ var n = x.length - 1;
+ var h = [], b = [], u = [], v = [], z=[];
+
+ for(var i=0; i<n; i++) {
+ h[i] = x[i+1] - x[i];
+ b[i] = (y[i+1] - y[i])/h[i];
+ }
+
+ u[1] = 2*(h[0]+h[1]);
+ v[1] = 6*(b[1]-b[0]);
+
+ for(var i=2; i<n; i++) {
+ u[i] = 2*(h[i]+h[i-1]) - (h[i-1]*h[i-1])/u[i-1];
+ v[i] = 6*(b[i]-b[i-1]) - (h[i-1]*v[i-1])/u[i-1];
+ }
+
+ z[n] = 0;
+ z[0] = 0;
+ for(var i=n-1; i > 0; i--) {
+ z[i] = (v[i] - h[i]*z[i+1])/u[i];
+ }
+
+ return z;
+}
+
+window.spline_interp = function(x_eval, x, y, coefs) {
+ // evaluate the natural cubic spline given by x, y and coefs at x_eval
+
+ var n = x.length - 1;
+ var i, h, tmp, diff;
+
+ // handle out-of-bounds cases
+ if (x_eval > x[n]) {
+ var h = x[n] - x[n-1];
+ var dy = y[n] - y[n-1];
+ // NOTE: this is tricky, since we're evaluating S'[n-1](x[n])
+ var slope = dy/h + h*coefs[n]/3 + h*coefs[n-1]/6;
+ return slope*(x_eval - x[n]) + y[n];
+ }
+ if (x_eval < x[0]) {
+ var h = x[1] - x[0];
+ var dy = y[1] - y[0];
+ // NOTE: B[0]
+ var slope = dy/h - h*coefs[0]/3 - h*coefs[1]/6
+ return slope*(x_eval - x[0]) + y[0];
+ }
+ // find the appropriate segment
+ for(i=n-1; i >= 0; i--) {
+ diff = x_eval - x[i];
+ if (diff >= 0) {
+ break;
+ }
+ }
+
+ // evaluate it
+ h = x[i+1] - x[i];
+ tmp = coefs[i]/2 + diff*(coefs[i+1]-coefs[i])/(6*h);
+ tmp = diff*tmp - (h/6)*(coefs[i+1] + 2*coefs[i]) + (y[i+1]-y[i])/h;
+ return y[i]+diff*tmp;
+}
+
+window.spline_plot = function(x, y, coefs) {
+ // return an array with an array of x coordinates and an array of y coordinates for the
+ // graph of the spline specified by x, y and coefs
+
+ var points = [[], []];
+ var xmin = _.min(x), xmax = _.max(x), range = xmax - xmin, full_range = 1.2*range;
+ var n = 200;
+ var inc = full_range/n;
+
+ xmin = xmin - 0.1*range;
+ xmax = xmax + 0.1*range;
+
+ for(var i=0; i<n; i++) {
+ var x0 = i*inc + xmin;
+ points[0][i] = x0;
+ points[1][i] = spline_interp(x0, x, y, coefs);
+ }
+
+ return points;
+}
+
View
332 g.line.js
@@ -0,0 +1,332 @@
+/*!
+ * g.Raphael 0.5 - Charting library, based on Raphaël
+ *
+ * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
+ */
+(function () {
+
+ function shrink(values, dim) {
+ var k = values.length / dim,
+ j = 0,
+ l = k,
+ sum = 0,
+ res = [];
+
+ while (j < values.length) {
+ l--;
+
+ if (l < 0) {
+ sum += values[j] * (1 + l);
+ res.push(sum / k);
+ sum = values[j++] * -l;
+ l += k;
+ } else {
+ sum += values[j++];
+ }
+ }
+ return res;
+ }
+
+ function getAnchors(p1x, p1y, p2x, p2y, p3x, p3y) {
+ var l1 = (p2x - p1x) / 2,
+ l2 = (p3x - p2x) / 2,
+ a = Math.atan((p2x - p1x) / Math.abs(p2y - p1y)),
+ b = Math.atan((p3x - p2x) / Math.abs(p2y - p3y));
+
+ a = p1y < p2y ? Math.PI - a : a;
+ b = p3y < p2y ? Math.PI - b : b;
+
+ var alpha = Math.PI / 2 - ((a + b) % (Math.PI * 2)) / 2,
+ dx1 = l1 * Math.sin(alpha + a),
+ dy1 = l1 * Math.cos(alpha + a),
+ dx2 = l2 * Math.sin(alpha + b),
+ dy2 = l2 * Math.cos(alpha + b);
+
+ return {
+ x1: p2x - dx1,
+ y1: p2y + dy1,
+ x2: p2x + dx2,
+ y2: p2y + dy2
+ };
+ }
+
+ function Linechart(paper, x, y, width, height, valuesx, valuesy, opts) {
+
+ var chartinst = this;
+
+ opts = opts || {};
+
+ if (!paper.raphael.is(valuesx[0], "array")) {
+ valuesx = [valuesx];
+ }
+
+ if (!paper.raphael.is(valuesy[0], "array")) {
+ valuesy = [valuesy];
+ }
+
+ var gutter = opts.gutter || 10,
+ len = Math.max(valuesx[0].length, valuesy[0].length),
+ symbol = opts.symbol || "",
+ colors = opts.colors || chartinst.colors,
+ columns = null,
+ dots = null,
+ chart = paper.set(),
+ path = [];
+
+ for (var i = 0, ii = valuesy.length; i < ii; i++) {
+ len = Math.max(len, valuesy[i].length);
+ }
+
+ var shades = paper.set();
+
+ for (i = 0, ii = valuesy.length; i < ii; i++) {
+ if (opts.shade) {
+ var nostroke = Raphael.is(nostroke, "array") ? opts.nostroke[i] : opts.nostroke;
+ shades.push(paper.path().attr({ stroke: "none", fill: colors[i], opacity: nostroke ? 1 : .3 }));
+ }
+
+ if (valuesy[i].length > width - 2 * gutter) {
+ valuesy[i] = shrink(valuesy[i], width - 2 * gutter);
+ len = width - 2 * gutter;
+ }
+
+ if (valuesx[i] && valuesx[i].length > width - 2 * gutter) {
+ valuesx[i] = shrink(valuesx[i], width - 2 * gutter);
+ }
+ }
+
+ var allx = Array.prototype.concat.apply([], valuesx),
+ ally = Array.prototype.concat.apply([], valuesy),
+ xdim = chartinst.snapEnds(Math.min.apply(Math, allx), Math.max.apply(Math, allx), valuesx[0].length - 1),
+ minx = xdim.from,
+ maxx = xdim.to,
+ ydim = chartinst.snapEnds(Math.min.apply(Math, ally), Math.max.apply(Math, ally), valuesy[0].length - 1),
+ miny = ydim.from,
+ maxy = ydim.to,
+ kx = (width - gutter * 2) / ((maxx - minx) || 1),
+ ky = (height - gutter * 2) / ((maxy - miny) || 1);
+
+ var axis = paper.set();
+
+ if (opts.axis) {
+ var ax = (opts.axis + "").split(/[,\s]+/);
+ +ax[0] && axis.push(chartinst.axis(x + gutter, y + gutter, width - 2 * gutter, minx, maxx, opts.axisxstep || Math.floor((width - 2 * gutter) / 20), 2, paper));
+ +ax[1] && axis.push(chartinst.axis(x + width - gutter, y + height - gutter, height - 2 * gutter, miny, maxy, opts.axisystep || Math.floor((height - 2 * gutter) / 20), 3, paper));
+ +ax[2] && axis.push(chartinst.axis(x + gutter, y + height - gutter, width - 2 * gutter, minx, maxx, opts.axisxstep || Math.floor((width - 2 * gutter) / 20), 0, paper));
+ +ax[3] && axis.push(chartinst.axis(x + gutter, y + height - gutter, height - 2 * gutter, miny, maxy, opts.axisystep || Math.floor((height - 2 * gutter) / 20), 1, paper));
+ }
+
+ var lines = paper.set(),
+ symbols = paper.set(),
+ line;
+
+ for (i = 0, ii = valuesy.length; i < ii; i++) {
+ var nostroke = Raphael.is(nostroke, "array") ? opts.nostroke[i] : opts.nostroke;
+
+ if (!nostroke) {
+ lines.push(line = paper.path().attr({
+ stroke: colors[i],
+ "stroke-width": opts.width || 2,
+ "stroke-linejoin": "round",
+ "stroke-linecap": "round",
+ "stroke-dasharray": opts.dash || ""
+ }));
+ }
+
+ var sym = Raphael.is(symbol, "array") ? symbol[i] : symbol,
+ symset = paper.set();
+
+ path = [];
+
+ for (var j = 0, jj = valuesy[i].length; j < jj; j++) {
+ var X = x + gutter + ((valuesx[i] || valuesx[0])[j] - minx) * kx,
+ Y = y + height - gutter - (valuesy[i][j] - miny) * ky;
+
+ (Raphael.is(sym, "array") ? sym[j] : sym) && symset.push(paper[Raphael.is(sym, "array") ? sym[j] : sym](X, Y, (opts.width || 2) * 3).attr({ fill: colors[i], stroke: "none" }));
+
+ if (opts.smooth) {
+ if (j && j != jj - 1) {
+ var X0 = x + gutter + ((valuesx[i] || valuesx[0])[j - 1] - minx) * kx,
+ Y0 = y + height - gutter - (valuesy[i][j - 1] - miny) * ky,
+ X2 = x + gutter + ((valuesx[i] || valuesx[0])[j + 1] - minx) * kx,
+ Y2 = y + height - gutter - (valuesy[i][j + 1] - miny) * ky,
+ a = getAnchors(X0, Y0, X, Y, X2, Y2);
+
+ path = path.concat([a.x1, a.y1, X, Y, a.x2, a.y2]);
+ }
+
+ if (!j) {
+ path = ["M", X, Y, "C", X, Y];
+ }
+ } else {
+ path = path.concat([j ? "L" : "M", X, Y]);
+ }
+ }
+
+ if (opts.smooth) {
+ path = path.concat([X, Y, X, Y]);
+ }
+
+ symbols.push(symset);
+
+ if (opts.shade) {
+ shades[i].attr({ path: path.concat(["L", X, y + height - gutter, "L", x + gutter + ((valuesx[i] || valuesx[0])[0] - minx) * kx, y + height - gutter, "z"]).join(",") });
+ }
+
+ !nostroke && line.attr({ path: path.join(",") });
+ }
+
+ function createColumns(f) {
+ // unite Xs together
+ var Xs = [];
+
+ for (var i = 0, ii = valuesx.length; i < ii; i++) {
+ Xs = Xs.concat(valuesx[i]);
+ }
+
+ Xs.sort();
+ // remove duplicates
+
+ var Xs2 = [],
+ xs = [];
+
+ for (i = 0, ii = Xs.length; i < ii; i++) {
+ Xs[i] != Xs[i - 1] && Xs2.push(Xs[i]) && xs.push(x + gutter + (Xs[i] - minx) * kx);
+ }
+
+ Xs = Xs2;
+ ii = Xs.length;
+
+ var cvrs = f || paper.set();
+
+ for (i = 0; i < ii; i++) {
+ var X = xs[i] - (xs[i] - (xs[i - 1] || x)) / 2,
+ w = ((xs[i + 1] || x + width) - xs[i]) / 2 + (xs[i] - (xs[i - 1] || x)) / 2,
+ C;
+
+ f ? (C = {}) : cvrs.push(C = paper.rect(X - 1, y, Math.max(w + 1, 1), height).attr({ stroke: "none", fill: "#000", opacity: 0 }));
+ C.values = [];
+ C.symbols = paper.set();
+ C.y = [];
+ C.x = xs[i];
+ C.axis = Xs[i];
+
+ for (var j = 0, jj = valuesy.length; j < jj; j++) {
+ Xs2 = valuesx[j] || valuesx[0];
+
+ for (var k = 0, kk = Xs2.length; k < kk; k++) {
+ if (Xs2[k] == Xs[i]) {
+ C.values.push(valuesy[j][k]);
+ C.y.push(y + height - gutter - (valuesy[j][k] - miny) * ky);
+ C.symbols.push(chart.symbols[j][k]);
+ }
+ }
+ }
+
+ f && f.call(C);
+ }
+
+ !f && (columns = cvrs);
+ }
+
+ function createDots(f) {
+ var cvrs = f || paper.set(),
+ C;
+
+ for (var i = 0, ii = valuesy.length; i < ii; i++) {
+ for (var j = 0, jj = valuesy[i].length; j < jj; j++) {
+ var X = x + gutter + ((valuesx[i] || valuesx[0])[j] - minx) * kx,
+ nearX = x + gutter + ((valuesx[i] || valuesx[0])[j ? j - 1 : 1] - minx) * kx,
+ Y = y + height - gutter - (valuesy[i][j] - miny) * ky;
+
+ f ? (C = {}) : cvrs.push(C = paper.circle(X, Y, Math.abs(nearX - X) / 2).attr({ stroke: "none", fill: "#000", opacity: 0 }));
+ C.x = X;
+ C.y = Y;
+ C.value = valuesy[i][j];
+ C.line = chart.lines[i];
+ C.shade = chart.shades[i];
+ C.symbol = chart.symbols[i][j];
+ C.symbols = chart.symbols[i];
+ C.axis = (valuesx[i] || valuesx[0])[j];
+ f && f.call(C);
+ }
+ }
+
+ !f && (dots = cvrs);
+ }
+
+ chart.push(lines, shades, symbols, axis, columns, dots);
+ chart.lines = lines;
+ chart.shades = shades;
+ chart.symbols = symbols;
+ chart.axis = axis;
+
+ chart.hoverColumn = function (fin, fout) {
+ !columns && createColumns();
+ columns.mouseover(fin).mouseout(fout);
+ return this;
+ };
+
+ chart.clickColumn = function (f) {
+ !columns && createColumns();
+ columns.click(f);
+ return this;
+ };
+
+ chart.hrefColumn = function (cols) {
+ var hrefs = paper.raphael.is(arguments[0], "array") ? arguments[0] : arguments;
+
+ if (!(arguments.length - 1) && typeof cols == "object") {
+ for (var x in cols) {
+ for (var i = 0, ii = columns.length; i < ii; i++) if (columns[i].axis == x) {
+ columns[i].attr("href", cols[x]);
+ }
+ }
+ }
+
+ !columns && createColumns();
+
+ for (i = 0, ii = hrefs.length; i < ii; i++) {
+ columns[i] && columns[i].attr("href", hrefs[i]);
+ }
+
+ return this;
+ };
+
+ chart.hover = function (fin, fout) {
+ !dots && createDots();
+ dots.mouseover(fin).mouseout(fout);
+ return this;
+ };
+
+ chart.click = function (f) {
+ !dots && createDots();
+ dots.click(f);
+ return this;
+ };
+
+ chart.each = function (f) {
+ createDots(f);
+ return this;
+ };
+
+ chart.eachColumn = function (f) {
+ createColumns(f);
+ return this;
+ };
+
+ return chart;
+ };
+
+ //inheritance
+ var F = function() {};
+ F.prototype = Raphael.g;
+ Linechart.prototype = new F;
+
+ //public
+ Raphael.fn.linechart = function(x, y, width, height, valuesx, valuesy, opts) {
+ return new Linechart(this, x, y, width, height, valuesx, valuesy, opts);
+ }
+
+})();
View
33 index.html
@@ -5,7 +5,7 @@
<script type="text/javascript" src="./underscore.min.js"></script>
<script type="text/javascript" src="./raphael-min.js"></script>
<script type="text/javascript" src="./g.raphael-min.js"></script>
- <script type="text/javascript" src="./g.line-min.js"></script>
+ <script type="text/javascript" src="./g.line.js"></script>
<script type="text/javascript" src="./app.js"></script>
<style type="text/css">
#holder {
@@ -16,6 +16,7 @@
</style>
</head>
<body>
+ <h1>Natural cubic spline</h1>
<div id="holder"></div>
<div style="float: left">
@@ -40,6 +41,10 @@
<td><input type="text" class="xin"/></td>
<td><input type="text" class="yin"/></td>
</tr>
+ <tr>
+ <td><input type="text" class="xin"/></td>
+ <td><input type="text" class="yin"/></td>
+ </tr>
</tbody>
<tbody>
<tr>
@@ -51,29 +56,9 @@
</table>
</div>
<div style="float: left">
- <h1>Graph</h1>
- <table width="200px">
- <thead>
- <tr>
- <th></th>
- <th align="left">X</th>
- <th align="left">Y</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <th>min</th>
- <td><input type="text" id="xmin"/></td>
- <td><input type="text" id="ymin"/></td>
- </tr>
- <tr>
- <th>max</th>
- <td><input type="text" id="xmax"/></td>
- <td><input type="text" id="ymax"/></td>
- </tr>
- </tbody>
- </table>
-
+ <h1>Results</h1>
+ <textarea id="results">
+ </textarea>
</div>
</body>
</html>

No commit comments for this range

Something went wrong with that request. Please try again.