Skip to content

Handle streamtube coordinates in string format and handle different data orders in isosurface and volume #4431

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Dec 30, 2019
69 changes: 47 additions & 22 deletions src/traces/streamtube/calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ module.exports = function calc(gd, trace) {
var slen = 0;
var startx, starty, startz;
if(trace.starts) {
startx = trace.starts.x || [];
starty = trace.starts.y || [];
startz = trace.starts.z || [];
startx = filter(trace.starts.x || []);
starty = filter(trace.starts.y || []);
startz = filter(trace.starts.z || []);
slen = Math.min(startx.length, starty.length, startz.length);
}
trace._startsX = startx || [];
trace._startsY = starty || [];
trace._startsZ = startz || [];

var normMax = 0;
var normMin = Infinity;
Expand Down Expand Up @@ -61,13 +64,18 @@ module.exports = function calc(gd, trace) {
var filledX;
var filledY;
var filledZ;
var firstX;
var firstY;
var firstZ;
var firstX, lastX;
var firstY, lastY;
var firstZ, lastZ;
if(len) {
firstX = x[0];
firstY = y[0];
firstZ = z[0];
firstX = +x[0];
firstY = +y[0];
firstZ = +z[0];
}
if(len > 1) {
lastX = +x[len - 1];
lastY = +y[len - 1];
lastZ = +z[len - 1];
}

for(i = 0; i < len; i++) {
Expand All @@ -80,15 +88,15 @@ module.exports = function calc(gd, trace) {
zMax = Math.max(zMax, z[i]);
zMin = Math.min(zMin, z[i]);

if(!filledX && x[i] !== firstX) {
if(!filledX && (+x[i]) !== firstX) {
filledX = true;
gridFill += 'x';
}
if(!filledY && y[i] !== firstY) {
if(!filledY && (+y[i]) !== firstY) {
filledY = true;
gridFill += 'y';
}
if(!filledZ && z[i] !== firstZ) {
if(!filledZ && (+z[i]) !== firstZ) {
filledZ = true;
gridFill += 'z';
}
Expand All @@ -98,13 +106,13 @@ module.exports = function calc(gd, trace) {
if(!filledY) gridFill += 'y';
if(!filledZ) gridFill += 'z';

var Xs = distinctVals(trace.x.slice(0, len));
var Ys = distinctVals(trace.y.slice(0, len));
var Zs = distinctVals(trace.z.slice(0, len));
var Xs = distinctVals(filter(trace.x, len));
var Ys = distinctVals(filter(trace.y, len));
var Zs = distinctVals(filter(trace.z, len));

gridFill = gridFill.replace('x', (x[0] > x[len - 1] ? '-' : '+') + 'x');
gridFill = gridFill.replace('y', (y[0] > y[len - 1] ? '-' : '+') + 'y');
gridFill = gridFill.replace('z', (z[0] > z[len - 1] ? '-' : '+') + 'z');
gridFill = gridFill.replace('x', (firstX > lastX ? '-' : '+') + 'x');
gridFill = gridFill.replace('y', (firstY > lastY ? '-' : '+') + 'y');
gridFill = gridFill.replace('z', (firstZ > lastZ ? '-' : '+') + 'z');

var empty = function() {
len = 0;
Expand All @@ -118,7 +126,7 @@ module.exports = function calc(gd, trace) {

var getArray = function(c) { return c === 'x' ? x : c === 'y' ? y : z; };
var getVals = function(c) { return c === 'x' ? Xs : c === 'y' ? Ys : Zs; };
var getDir = function(c) { return (c[len - 1] < c[0]) ? -1 : 1; };
var getDir = function(c) { return lessThan(c[len - 1], c[0]) ? -1 : 1; };

var arrK = getArray(gridFill[1]);
var arrJ = getArray(gridFill[3]);
Expand Down Expand Up @@ -146,9 +154,9 @@ module.exports = function calc(gd, trace) {
var q100 = getIndex(i + 1, j, k);

if(
!(arrK[q000] * dirK < arrK[q001] * dirK) ||
!(arrJ[q000] * dirJ < arrJ[q010] * dirJ) ||
!(arrI[q000] * dirI < arrI[q100] * dirI)
!lessThan(arrK[q000] * dirK, arrK[q001] * dirK) ||
!lessThan(arrJ[q000] * dirJ, arrJ[q010] * dirJ) ||
!lessThan(arrI[q000] * dirI, arrI[q100] * dirI)
) {
arbitrary = true;
}
Expand Down Expand Up @@ -194,3 +202,20 @@ module.exports = function calc(gd, trace) {
function distinctVals(col) {
return Lib.distinctVals(col).vals;
}

function filter(arr, len) {
if(len === undefined) len = arr.length;

// no need for casting typed arrays to numbers
if(Lib.isTypedArray(arr)) return arr.slice(0, len);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to use arr.subarray() for typed arrays, as .slice isn't supported in older browsers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call.
Wondering if we need to slice the array at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slice (or subarry) is needed for distictVals to work correctly.
But it may be better not to slice if the length is correct.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it may be better not to slice if the length is correct.

that could be a nice optimisation for typed arrays, but for regular arrays we'll need to slice in general to cast numerical-strings items w/o mutating.


var values = [];
for(var i = 0; i < len; i++) {
values[i] = +arr[i];
}
return values;
}

function lessThan(a, b) {
return +a < +b;
}
6 changes: 3 additions & 3 deletions src/traces/streamtube/convert.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ function convert(scene, trace) {
var slen = trace._slen;
if(slen) {
tubeOpts.startingPositions = zip3(
toDataCoords(trace.starts.x.slice(0, slen), 'xaxis'),
toDataCoords(trace.starts.y.slice(0, slen), 'yaxis'),
toDataCoords(trace.starts.z.slice(0, slen), 'zaxis')
toDataCoords(trace._startsX, 'xaxis'),
toDataCoords(trace._startsY, 'yaxis'),
toDataCoords(trace._startsZ, 'zaxis')
);
} else {
// Default starting positions:
Expand Down
122 changes: 77 additions & 45 deletions test/jasmine/tests/streamtube_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,55 +246,87 @@ describe('Test streamtube interactions', function() {
.then(done);
});

it('@gl should work with negative grid steps', function(done) {
var x = [];
var y = [];
var z = [];
var u = [];
var v = [];
var w = [];

for(var i = 0; i < 3; i++) {
for(var j = 0; j < 4; j++) {
for(var k = 0; k < 5; k++) {
x.push(-i);
y.push(-j);
z.push(-k);
u.push(1);
v.push(1);
w.push(1);
[ // list of directions
'number',
'string'
].forEach(function(format) {
[ // list of directions
[-1, -1, -1],
[-1, -1, 1],
[-1, 1, -1],
[1, -1, -1],
[1, 1, -1],
[1, -1, 1],
[-1, 1, 1],
[1, 1, 1]
].forEach(function(dir) {
it('@gl should work with grid steps: ' + dir + ' and values in ' + format + ' format.', function(done) {
var x = [];
var y = [];
var z = [];
var u = [];
var v = [];
var w = [];

for(var i = 0; i < 3; i++) {
for(var j = 0; j < 4; j++) {
for(var k = 0; k < 5; k++) {
var newU = 1;
var newV = 1;
var newW = 1;
var newX = i * dir[0];
var newY = j * dir[1];
var newZ = k * dir[2];

if(format === 'string') {
newU = String(newU);
newV = String(newV);
newW = String(newW);
newX = String(newX);
newY = String(newY);
newZ = String(newZ);
}

u.push(newU);
v.push(newV);
w.push(newW);
x.push(newX);
y.push(newY);
z.push(newZ);
}
}
}
}
}

var fig = {
data: [{
type: 'streamtube',
x: x,
y: y,
z: z,
u: u,
v: v,
w: w
}]
};

function _assert(msg, exp) {
var scene = gd._fullLayout.scene._scene;
var objs = scene.glplot.objects;
expect(objs.length).toBe(1, 'one gl-vis object - ' + msg);
expect(exp.positionsLength).toBe(objs[0].positions.length, 'positions length - ' + msg);
expect(exp.cellsLength).toBe(objs[0].cells.length, 'cells length - ' + msg);
}
var fig = {
data: [{
type: 'streamtube',
x: x,
y: y,
z: z,
u: u,
v: v,
w: w
}]
};

function _assert(msg, exp) {
var scene = gd._fullLayout.scene._scene;
var objs = scene.glplot.objects;
expect(objs.length).toBe(1, 'one gl-vis object - ' + msg);
expect(exp.positionsLength).toBe(objs[0].positions.length, 'positions length - ' + msg);
expect(exp.cellsLength).toBe(objs[0].cells.length, 'cells length - ' + msg);
}

Plotly.plot(gd, fig).then(function() {
_assert('with negative steps', {
positionsLength: 6336,
cellsLength: 2112
Plotly.plot(gd, fig).then(function() {
_assert('lengths', {
positionsLength: 6336,
cellsLength: 2112
});
})
.catch(failTest)
.then(done);
});
})
.catch(failTest)
.then(done);
});
});

it('@gl should return blank mesh grid if encountered arbitrary coordinates', function(done) {
Expand Down