-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Isosurface traces #2752
Changes from 4 commits
892e7ed
f6aba79
639c287
6b2dbac
98ad6b0
6b87ed7
1c4ec62
d417e6e
88cad83
c3df068
f7eb951
604a2d9
f761b09
eb5221d
e75e9d7
2ee5f60
c884c89
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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'); |
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' | ||
].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.' | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 I think naming this field There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. About |
||
|
||
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; |
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'); | ||
}; |
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]] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, will do, |
||
// ]; | ||
|
||
|
||
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does |
||
|
||
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; |
There was a problem hiding this comment.
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)