Skip to content

Isosurface traces #2752

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

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Plotly.register([
require('./mesh3d'),
require('./cone'),
require('./streamtube'),
require('./isosurface'),

require('./scattergeo'),
require('./choropleth'),
Expand Down
11 changes: 11 additions & 0 deletions lib/isosurface.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Copyright 2012-2018, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

module.exports = require('../src/traces/isosurface');
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"es6-promise": "^3.0.2",
"fast-isnumeric": "^1.1.1",
"font-atlas-sdf": "^1.3.3",
"gl-isosurface3d": "https://github.com/gl-vis/gl-isosurface3d.git",
"gl-cone3d": "^1.1.0",
"gl-contour2d": "^1.1.4",
"gl-error3d": "^1.0.7",
Expand Down
125 changes: 125 additions & 0 deletions src/traces/isosurface/attributes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/**
* Copyright 2012-2018, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var colorAttrs = require('../../components/colorscale/color_attributes');
var colorscaleAttrs = require('../../components/colorscale/attributes');
var colorbarAttrs = require('../../components/colorbar/attributes');
var mesh3dAttrs = require('../mesh3d/attributes');
var baseAttrs = require('../../plots/attributes');

var extendFlat = require('../../lib/extend').extendFlat;

var attrs = {
x: {
valType: 'data_array',
role: 'info',
editType: 'calc+clearAxisTypes',
description: [
'Sets the x coordinates of the isosurface'
Copy link
Collaborator

Choose a reason for hiding this comment

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

perhaps "Sets the x coordinates of the volume data" (from which the isosurface will be calculated)

].join(' ')
},
y: {
valType: 'data_array',
role: 'info',
editType: 'calc+clearAxisTypes',
description: [
'Sets the y coordinates of the isosurface'
].join(' ')
},
z: {
valType: 'data_array',
role: 'info',
editType: 'calc+clearAxisTypes',
description: [
'Sets the z coordinates of the isosurface'
].join(' ')
},

u: {
valType: 'data_array',
editType: 'calc',
description: 'Sets the intensity values of the isosurface.'
},

imin: {
valType: 'number',
editType: 'calc',
description: 'Sets the minimum iso bound of the isosurface.'
},

imax: {
valType: 'number',
editType: 'calc',
description: 'Sets the maximum iso bound of the isosurface.'
},
Copy link
Collaborator

Choose a reason for hiding this comment

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

imax/imin is OK if we rename u to intensity... but if it turns to v or value these should be vmax/vmin. I presume that if you only provide one of these, the other one is treated as +/-Infinity for the purpose of constructing the isocaps?

Copy link
Contributor

Choose a reason for hiding this comment

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

I remember us having the same concerns about intensity in surface traces. We ended up naming that attribute surfacecolor.

I think naming this field u or v would confusing as it is an array of scalars, not a component of a vector field. I like value, but maybe volume would better (that's how MATLAB seems to call this field)?

Copy link
Contributor

Choose a reason for hiding this comment

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

About imin and imax, MATLAB calls it isovalue. Wouldn't isomin and isomax be better?


smoothnormals: {
valType: 'boolean',
editType: 'calc',
description: ''
},

singlemesh: {
valType: 'boolean',
editType: 'calc',
description: ''
},

isocaps: {
valType: 'boolean',
editType: 'calc',
description: ''
},

boundmin: {
valType: 'data_array',
editType: 'calc',
description: ''
},

boundmax: {
valType: 'data_array',
editType: 'calc',
description: ''
},

text: {
valType: 'string',
role: 'info',
dflt: '',
arrayOk: true,
editType: 'calc',
description: [
'Sets the text elements associated with the isosurface points.',
'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,',
'these elements will be seen in the hover labels.'
].join(' ')
}
};

extendFlat(attrs, colorAttrs('', 'calc', true), {
showscale: colorscaleAttrs.showscale,
colorbar: colorbarAttrs
});
delete attrs.color;

var fromMesh3d = ['opacity', 'lightposition', 'lighting'];

fromMesh3d.forEach(function(k) {
attrs[k] = mesh3dAttrs[k];
});

attrs.hoverinfo = extendFlat({}, baseAttrs.hoverinfo, {
editType: 'calc',
flags: ['x', 'y', 'z', 'intensity', 'text', 'name'],
dflt: 'x+y+z+intensity+text+name'
});

module.exports = attrs;
30 changes: 30 additions & 0 deletions src/traces/isosurface/calc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Copyright 2012-2018, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var colorscaleCalc = require('../../components/colorscale/calc');

module.exports = function calc(gd, trace) {
var u = trace.u;
var len = u.length;
var normMax = -Infinity;
var normMin = Infinity;

for(var i = 0; i < len; i++) {
var uu = u[i];
var norm = uu;

normMax = Math.max(normMax, norm);
normMin = Math.min(normMin, norm);
}

trace._normMax = normMax;

colorscaleCalc(trace, [normMin, normMax], '', 'c');
};
205 changes: 205 additions & 0 deletions src/traces/isosurface/convert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
/**
* Copyright 2012-2018, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/*
Usage example:

var width = 64
var height = 64
var depth = 64

var xs = []
var ys = []
var zs = []

var data = new Uint16Array(width*height*depth)
for (var z=0; z<depth; z++)
for (var y=0; y<height; y++)
for (var x=0; x<width; x++) {
xs.push(x/width);
ys.push(y/height);
zs.push(z/depth);
var value = 1500 + 500 * (
Math.sin(2 * 2*Math.PI*(z/depth-0.5)) +
Math.cos(3 * 2*Math.PI*(x/width-0.5)) +
Math.sin(4 * 2*Math.PI*(y/height-0.5))
);
data[z*height*width + y*width + x] = value
}

Plotly.newPlot(gd, [{
type: 'isosurface',

x: xs,
y: ys,
z: zs,

u: data,

imin: 1600,
imax: 2000,
cmin: 1500,
cmax: 2000,

smoothnormals: true,
isocaps: true,

singlemesh: true,

colorscale: 'Portland',
capscolorscale: 'Jet'
}], {
scene: {
xaxis: {range: [0, 1]},
yaxis: {range: [0, 1]},
zaxis: {range: [0, 1]}
}
})

*/
'use strict';

var isosurfacePlot = require('gl-isosurface3d');

var simpleMap = require('../../lib').simpleMap;
var parseColorScale = require('../../lib/gl_format_color').parseColorScale;

function Isosurface(scene, uid) {
this.scene = scene;
this.uid = uid;
this.mesh = null;
this.data = null;
}

var proto = Isosurface.prototype;

proto.handlePick = function(selection) {
if(selection.object === this.mesh) {
var selectIndex = selection.index = selection.data.index;

selection.traceCoordinate = [
selection.data.position[0],
selection.data.position[1],
selection.data.position[2],
selection.data.intensity
];

var text = this.data.text;
if(Array.isArray(text) && text[selectIndex] !== undefined) {
selection.textLabel = text[selectIndex];
} else if(text) {
selection.textLabel = text;
}

return true;
}
};

var axisName2scaleIndex = {xaxis: 0, yaxis: 1, zaxis: 2};

function getSequence(src) {
var xs = [src[0]];
for(var i = 1, last = xs[0]; i < src.length; i++) {
var p = src[i];
if(p >= last) {
if(p > last) {
xs.push(p);
}
last = p;
} else {
break;
}
}
return xs;
}

function convert(scene, trace) {
var sceneLayout = scene.fullSceneLayout;
var dataScale = scene.dataScale;
var isosurfaceOpts = {};

function toDataCoords(arr, axisName) {
var ax = sceneLayout[axisName];
var scale = dataScale[axisName2scaleIndex[axisName]];
return simpleMap(arr, function(v) { return ax.d2l(v) * scale; });
}

var xs = getSequence(trace.x);
var ys = getSequence(trace.y);
var zs = getSequence(trace.z);

isosurfaceOpts.dimensions = [xs.length, ys.length, zs.length];
isosurfaceOpts.meshgrid = [
toDataCoords(xs, 'xaxis'),
toDataCoords(ys, 'yaxis'),
toDataCoords(zs, 'zaxis')
];

// var bounds = [
// isosurfaceOpts.boundmin || [xs[0], ys[0], zs[0]],
// isosurfaceOpts.boundmax || [xs[xs.length - 1], ys[ys.length - 1], zs[zs.length - 1]]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh I see, these were to be the (x,y,z) limits... yeah, I still think it's better to make 6 separate attributes just since setting a single bound is (I think) the most common use case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, will do, (x|y|z)(min|max)

// ];


isosurfaceOpts.values = trace.u;

isosurfaceOpts.colormap = parseColorScale(trace.colorscale);
// isosurfaceOpts.capsColormap = parseColorScale(trace.capscolorscale);
isosurfaceOpts.vertexIntensityBounds = [trace.cmin, trace.cmax];
isosurfaceOpts.isoBounds = [trace.imin, trace.imax];

isosurfaceOpts.isoCaps = trace.isocaps;
isosurfaceOpts.singleMesh = trace.singlemesh === undefined ? true : trace.singlemesh;
Copy link
Collaborator

Choose a reason for hiding this comment

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

What does singlemesh do? BTW logic like this belongs as attributes.singlemesh.dlft = true.


var bounds = [[0, 0, 0], isosurfaceOpts.dimensions];

var meshData = isosurfacePlot(isosurfaceOpts, bounds);

// pass gl-mesh3d lighting attributes
var lp = trace.lightposition;
meshData.lightPosition = [lp.x, lp.y, lp.z];
meshData.ambient = trace.lighting.ambient;
meshData.diffuse = trace.lighting.diffuse;
meshData.specular = trace.lighting.specular;
meshData.roughness = trace.lighting.roughness;
meshData.fresnel = trace.lighting.fresnel;
meshData.opacity = trace.opacity;

return meshData;
}

proto.update = function(data) {
this.data = data;

var meshData = convert(this.scene, data);
this.mesh.update(meshData);
};

proto.dispose = function() {
this.scene.glplot.remove(this.mesh);
this.mesh.dispose();
};

function createIsosurfaceTrace(scene, data) {
var gl = scene.glplot.gl;

var meshData = convert(scene, data);
var mesh = isosurfacePlot.createTriMesh(gl, meshData);

var isosurface = new Isosurface(scene, data.uid);
isosurface.mesh = mesh;
isosurface.data = data;
isosurface.meshData = meshData;
mesh._trace = isosurface;

scene.glplot.add(mesh);

return isosurface;
}

module.exports = createIsosurfaceTrace;
Loading