Skip to content

Commit 7164f68

Browse files
committed
Work out carpet interpolation
1 parent cba32e9 commit 7164f68

22 files changed

+1514
-248
lines changed

src/traces/carpet/evaluate_cubic.js renamed to lib/scattercarpet.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@
88

99
'use strict';
1010

11-
module.exports = function (f1, f2, f3, f4, t)
11+
module.exports = require('../src/traces/scattercarpet');
+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/**
2+
* Copyright 2012-2016, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
10+
'use strict';
11+
12+
// generalized Catmull-Rom splines, per
13+
// http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf
14+
var CatmullRomExp = 0.5;
15+
function makeControlPoints(p0, p1, p2, smoothness) {
16+
var d1x = p0[0] - p1[0],
17+
d1y = p0[1] - p1[1],
18+
d2x = p2[0] - p1[0],
19+
d2y = p2[1] - p1[1],
20+
d1a = Math.pow(d1x * d1x + d1y * d1y, CatmullRomExp / 2),
21+
d2a = Math.pow(d2x * d2x + d2y * d2y, CatmullRomExp / 2),
22+
numx = (d2a * d2a * d1x - d1a * d1a * d2x) * smoothness,
23+
numy = (d2a * d2a * d1y - d1a * d1a * d2y) * smoothness,
24+
denom1 = 3 * d2a * (d1a + d2a),
25+
denom2 = 3 * d1a * (d1a + d2a);
26+
var dxL = p1[0] + (denom1 && numx / denom1);
27+
var dyL = p1[1] + (denom1 && numy / denom1);
28+
var dxU = p1[0] - (denom2 && numx / denom2);
29+
var dyU = p1[1] - (denom2 && numy / denom2);
30+
31+
return [[dxL, dyL], [dxU, dyU]];
32+
}
33+
34+
/*
35+
* Computes centripetal catmull-rom control points. These shouldn't be confused
36+
* with tangents, which are slightly different. Specifically, the length of the
37+
* tangent vector is
38+
*
39+
* degree * (control point - reference point)
40+
*
41+
* In other words, it really doesn't make too much of a difference whether we
42+
* work with tangents or control points.
43+
*/
44+
module.exports.computeControlPoints = function (pts, smoothness) {
45+
var tangents = [];
46+
for(i = 1; i < pts.length - 1; i++) {
47+
tangents.push(makeControlPoints(pts[i - 1], pts[i], pts[i + 1], smoothness));
48+
}
49+
return tangents;
50+
}
51+
52+
module.exports.evaluateSpline = function evaluateSpline (t, pts, controlpts, smoothness) {
53+
var n = pts.length;
54+
if (n < 3) {
55+
var x = (1 - t) * pts[0][0] + t * pts[1][0];
56+
var y = (1 - t) * pts[0][1] + t * pts[1][1];
57+
return [x, y];
58+
} else {
59+
// Compute the controlpts *once* at the beginning, and store them:
60+
var a, b, c, d, p0, p1, p2, p3, i, t, ot;
61+
param = Math.max(0, Math.min(n - 1, param));
62+
i = Math.min(Math.floor(param), n - 2);
63+
t = param - i;
64+
ot = 1 - t;
65+
66+
p0 = pts[i];
67+
p3 = pts[i + 1];
68+
69+
if (i === 0 || i === n - 2) {
70+
// Evaluate the quadratic first and last segments;
71+
p1 = i === 0 ? controlpts[i][0] : controlpts[i - 1][1];
72+
a = ot * ot;
73+
b = 2 * ot * t;
74+
c = t * t;
75+
return [
76+
a * p0[0] + b * p1[0] + c * p3[0],
77+
a * p0[1] + b * p1[1] + c * p3[1]
78+
];
79+
} else {
80+
// Evaluate internal cubic spline segments:
81+
p1 = controlpts[i - 1][1];
82+
p2 = controlpts[i][0];
83+
p3 = pts[i + 1];
84+
85+
a = ot * ot * ot;
86+
b = 3 * ot * ot * t;
87+
c = 3 * ot * t * t;
88+
d = t * t * t;
89+
90+
return [
91+
a * p0[0] + b * p1[0] + c * p2[0] + d * p3[0],
92+
a * p0[1] + b * p1[1] + c * p2[1] + d * p3[1]
93+
];
94+
}
95+
}
96+
}
97+

src/traces/carpet/calc.js

+10-150
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ var arrayMinmax = require('./array_minmax');
1414
var search = require('../../lib/search').findBin;
1515
var computeControlPoints = require('./compute_control_points');
1616
var map2dArray = require('./map_2d_array');
17-
var evaluateControlPoints = require('./evaluate_control_points');
17+
var createSplineEvaluator = require('./create_spline_evaluator');
18+
var setConvert = require('./set_convert');
19+
var calcGridlines = require('./calc_gridlines');
1820

1921
module.exports = function calc(gd, trace) {
2022
var xa = Axes.getFromId(gd, trace.xaxis || 'x'),
@@ -30,7 +32,7 @@ module.exports = function calc(gd, trace) {
3032
if(trace._cheater) {
3133
var avals = aax.cheatertype === 'index' ? a.length : a
3234
var bvals = bax.cheatertype === 'index' ? b.length : b;
33-
xdata = cheaterBasis(avals, bvals, trace.cheaterslope);
35+
trace.x = xdata = cheaterBasis(avals, bvals, trace.cheaterslope);
3436
} else {
3537
xdata = trace.x;
3638
}
@@ -61,162 +63,20 @@ module.exports = function calc(gd, trace) {
6163
trace.xp = map2dArray(trace.xp, x, xa.c2p);
6264
trace.yp = map2dArray(trace.yp, y, ya.c2p);
6365

64-
// Next compute the control points in whichever directions are necessary:
65-
var result = computeControlPoints(trace.xctrl, trace.yctrl, x, y, aax.smoothing, bax.smoothing);
66-
trace.xctrl = result[0];
67-
trace.yctrl = result[1];
66+
// Create conversions from one coordinate system to another:
67+
setConvert(trace, xa, ya);
6868

69+
trace._agrid = calcGridlines(trace, 'a', 'b', xa, ya);
70+
trace._bgrid = calcGridlines(trace, 'b', 'a', xa, ya);
6971

70-
console.log('evaluateControlPoints(:', evaluateControlPoints([trace.xctrl, trace.yctrl], 0.5, 0.5));
71-
72-
73-
//trace.ab2c = getConvert(x, y, a, b, aax.smoothing, bax.smoothing);
74-
75-
// This is just a transposed function that will be useful in making
76-
// the computations a/b-agnostic:
77-
//trace.ba2c = function (b, a) { return trace.ab2c(a, b); }
78-
79-
//var agrid = makeGridLines(a, aax);
80-
//var bgrid = makeGridLines(b, bax);
81-
82-
//trace.aaxis._tickinfo = agrid;
83-
//trace.baxis._tickinfo = bgrid;
84-
85-
var cd0 = {
72+
return [{
8673
x: x,
8774
y: y,
8875
a: a,
8976
b: b
90-
};
91-
92-
return [cd0];
77+
}];
9378
};
9479

95-
96-
97-
/*
98-
* Interpolate in the b-direction along constant-a lines
99-
* x, y: 2D data arrays to be interpolated
100-
* aidx: index of the a gridline along which to evaluate
101-
* bidx0: lower-index of the b cell in which to interpolate
102-
* tb: an interpolant in [0, 1]
103-
*/
104-
/*function interpolateConstA (x, y, aidx, bidx0, tb, bsmoothing) {
105-
if (bsmoothing) {
106-
return [
107-
x[aidx][bidx0] * (1 - tb) + x[aidx][bidx0 + 1] * tb,
108-
y[aidx][bidx0] * (1 - tb) + y[aidx][bidx0 + 1] * tb
109-
];
110-
} else {
111-
return [
112-
x[aidx][bidx0] * (1 - tb) + x[aidx][bidx0 + 1] * tb,
113-
y[aidx][bidx0] * (1 - tb) + y[aidx][bidx0 + 1] * tb
114-
];
115-
}
116-
}*/
117-
118-
/*
119-
* Interpolate in the a and b directions
120-
* x, y: 2D data arrays to be interpolated
121-
* aidx0: lower index of the a cell in which to interpolatel
122-
* bidx0: lower index of the b cell in which to interpolate
123-
* ta: an interpolant for the a direction in [0, 1]
124-
* tb: an interpolant for the b direction in [0, 1]
125-
*/
126-
/*function interpolateAB (x, y, aidx0, ta, bidx0, tb, asmoothing, bsmoothing) {
127-
if (asmoothing) {
128-
var xy0 = interpolateConstA(x, y, aidx0, bidx0, tb, bsmoothing);
129-
var xy1 = interpolateConstA(x, y, aidx0 + 1, bidx0, tb, bsmoothing);
130-
131-
return [
132-
xy0[0] * (1 - ta) + xy1[0] * ta,
133-
xy0[1] * (1 - ta) + xy1[1] * ta
134-
];
135-
} else {
136-
var xy0 = interpolateConstA(x, y, aidx0, bidx0, tb, bsmoothing);
137-
var xy1 = interpolateConstA(x, y, aidx0 + 1, bidx0, tb, bsmoothing);
138-
139-
return [
140-
xy0[0] * (1 - ta) + xy1[0] * ta,
141-
xy0[1] * (1 - ta) + xy1[1] * ta
142-
];
143-
}
144-
}*/
145-
146-
/*
147-
* Return a function that converts a/b coordinates to x/y values
148-
*/
149-
/*function getConvert (x, y, a, b, asmoothing, bsmoothing) {
150-
return function (aval, bval) {
151-
// Get the lower-indices of the box in which the point falls:
152-
var aidx = Math.min(search(aval, a), a.length - 2);
153-
var bidx = Math.min(search(bval, b), b.length - 2);
154-
155-
var ta = (aval - a[aidx]) / (a[aidx + 1] - a[aidx]);
156-
var tb = (bval - b[bidx]) / (b[bidx + 1] - b[bidx]);
157-
158-
return interpolateAB(x, y, aidx, ta, bidx, tb, asmoothing, bsmoothing);
159-
};
160-
}*/
161-
162-
/*
163-
* Interpret the tick properties to return indices, interpolants,
164-
* and values of grid lines
165-
*
166-
* Output is:
167-
* ilower: lower index of the cell in which the gridline lies
168-
* interpolant: fractional distance of this gridline to the next cell (in [0, 1])
169-
* values: value of the axis coordinate at each respective gridline
170-
*/
171-
/*function makeGridLines (data, axis) {
172-
var gridlines = [];
173-
var t = [];
174-
var i0 = [];
175-
var i1 = [];
176-
var values = [];
177-
switch(axis.tickmode) {
178-
case 'array':
179-
var start = axis.arraytick0 % axis.arraydtick;
180-
var step = axis.arraydtick;
181-
var end = data.length;
182-
// This could be optimized, but we'll convert this to a/b space and then do
183-
// the interpolation in
184-
for (var i = start; i < end; i += step) {
185-
// If it's the end point, we'll use the end of the previous range to
186-
// avoid going out of bounds:
187-
var endshift = i >= end - 1 ? 1 : 0;
188-
189-
gridlines.push({
190-
param: i,
191-
idx: i - endshift,
192-
tInterp: endshift,
193-
value: data[i]
194-
});
195-
}
196-
break;
197-
case 'linear':
198-
var v1 = getLinspaceStartingPoint(data[0], axis.tick0, axis.dtick);
199-
var n = Math.ceil((data[data.length - 1] - v1) / axis.dtick);
200-
201-
// This could be optimized, but we'll convert this to a/b space and then do
202-
// the interpolation in
203-
for (var i = 0; i < n; i++) {
204-
var val = v1 + axis.dtick * i;
205-
var idx = Math.min(search(val, data), data.length - 2);
206-
gridlines.push({
207-
param: (val - data[0]) / (data[data.length - 1] - data[0]) * data.length,
208-
idx: idx,
209-
tInterp: (val - data[idx]) / (data[idx + 1] - data[idx]),
210-
value: val
211-
});
212-
}
213-
}
214-
215-
console.log('gridlines:', gridlines);
216-
217-
return gridlines;
218-
}*/
219-
22080
/*
22181
* Given a data range from starting at x1, this function computes the first
22282
* point distributed along x0 + n * dx that lies within the range.

0 commit comments

Comments
 (0)