Black Lives Matter. Please consider donating to Black Girls Code today. # Plot different 3d surface/contour plot planes of the same function at different depths in the cartesian domain

Hi everyone,

I’m relatively new to plotly and it looks like an amazing tool to me! As a new user, I’m very excited, although I’ve been running into an issue that I hope very much you could help me with.

In short, I’m using plotly for the post-processing analysis of the vorticity field of my 3d simulations.
I have a cartesian domain and I’d like to plot the result of my function in the XY plan at three different depths. My domain in Z is 0-(-)660km and I want to have these plans visualised in 3d at 0km, -110km and -660km.

• the values of the function at different plans of different depth are different.

problem #1: I can’t get to plot the function, which goes approx from -5 to 10, at different depths. I am also not sure if a surface or a volume plot would be the best choice here.

An example of this where x2-y2 is the 2d plane and z2 to z4 are the different vorticity functions

would be:

fig = go.Figure(data=[go.Surface(
contours = {
“x”: {“show”: True, “start”: 1.5, “end”: 2, “size”: 0.04, “color”:“white”},
“z”: {“show”: True, “start”: 0.5, “end”: 0.8, “size”: 0.05}
},
z=z2, x=x2, y=y2, cmin=-200, cmax=200)])

fig = go.Figure(data=[go.Surface(z=z3, x=x2, y=y2, cmin=-200, cmax=200)])

fig = go.Figure(data=[go.Surface(z=z4, x=x2, y=y2, cmin=-200, cmax=200)])

fig.update_layout(scene =
dict( xaxis = dict(nticks=4, range=[-2000,2000],),
yaxis = dict(nticks=4, range=[-1500,1500],),
zaxis = dict(nticks=4, range=[-660,0],),),
title=‘vorticity’, autosize=False,
width=800, height=700,
margin=dict(l=65, r=50, b=65, t=90)
)
fig.update_layout(scene = dict(
xaxis_title=‘X [km]’,
yaxis_title=‘Y [km]’,
zaxis_title=‘Z [km]’),
)

fig.show()

when I do this they overwrite themself, I’m a bit confused. Thank you very much to who will answer this post and Merry Christmas to everyone! if necessary I can provide a github-binder version of this.

R.

Since in your plots the surfaces at displayed at depth=0, to place them at the desired depth
take z=z3-110, respectively z=z4-660.

Merry Christmas!

Hi,
thank you very much for the hint and the wishes also,

I can now visualise all three plans. However, I can not normalise the colour bar for all the three plans at the same time.

I’d like all to set -200 and 200 as the minimum and maximum values of my colour bar regardless of the depth of the plane.

• Problem one: cmax and cmin work for the surface plane only. If I add cmax-cmin to z3 it gives colours corresponding to the value of -100 ±the values of the function.

• Problem 2: the contour is only visible in the surface plane.

this is my current working script:

``````fig = go.Figure(data=[
go.Surface (
contours = {
"x": {"show": True, "start": 1.5, "end": 2, "size": 0.04, "color":"white"},
"z": {"show": True, "start": 0.5, "end": 0.8, "size": 0.05}
},
z=z2, x=x2, y=y2, opacity=0.9, cmin=-200, cmax=200),

go.Surface(contours = {
"x": {"show": True, "start": 1.5, "end": 2, "size": 0.04, "color":"white"},
"z": {"show": True, "start": 0.5, "end": 0.8, "size": 0.05}
},
z=z3-100, x=x2, y=y2,showscale=False,opacity=0.9,cmin=-200, cmax=200),
go.Surface(contours = {
"x": {"show": True, "start": 1.5, "end": 2, "size": 0.04, "color":"white"},
"z": {"show": True, "start": 0.5, "end": 0.8, "size": 0.05}
},
z=z4-640, x=x2, y=y2,showscale=False,)])

fig.update_layout(scene =
dict(xaxis = dict(nticks=4, range=[-2000,2000],),
yaxis = dict(nticks=4, range=[-1500,1500],),
zaxis = dict(nticks=4, range=[-660,10],),),
title='vorticity', autosize=False,
width=800, height=700,
margin=dict(l=65, r=50, b=65, t=90))

fig.update_layout(scene = dict(
xaxis_title='X [km]',
yaxis_title='Y [km]',
zaxis_title='Z [km]'),
)

fig.show()
``````

@rcarluccio

To get the colormapping of all tree surfaces z-values to a colorscale corresponding to the interval [-200, 200], just remove `cmin`, `cmax` from the definition of the first and the second surface, as well as the `showscale=False`, from the second and third surface, and insert in each surface `coloraxis='coloraxis'`, i.e. define common colormappings for all three surfaces.
Then set the `coloraxis` in `fig.layout`:

``````fig.update_layout(coloraxis = dict(colorscale='Viridis', cmin =-200, cmax=200, colorbar_thickness=25)
``````

In your code pasted above only the z-values of the first, and second surface are mapped to the interval
[-200, 200], because only in their definition is set cmin, cmax.

Hi Empet,

thank you for your reply. Unfortunately, that doesn’t make the trick quite yet.

I want the colour bar to show the value of the function independently by the depth of the plane.
If we do z-depth that the colour bar shows the z-depth value. I, however, need to visualise the true values of the function z translated to the right depth position in the cartesian plane. As for the figure attached the coloraxis function doesn’t make it work yet. Coloraxis helps me to keep the same colour scale but the colorscale has to refer to z* (z1-z3) and not z*-depth values, even if z values are plotted at z*-depth coords.

Below I made a vanilla example that reproduces the issue:

``````import plotly.graph_objects as go
import numpy as np

z1 = np.array([
[8.83,8.89,8.81,8.87,8.9,8.87],
[8.89,8.94,8.85,8.94,8.96,8.92],
[8.84,8.9,8.82,8.92,8.93,8.91],
[8.79,8.85,8.79,8.9,8.94,8.92],
[8.79,8.88,8.81,8.9,8.95,8.92],
[8.8,8.82,8.78,8.91,8.94,8.92],
[8.75,8.78,8.77,8.91,8.95,8.92],
[8.8,8.8,8.77,8.91,8.95,8.94],
[8.74,8.81,8.76,8.93,8.98,8.99],
[8.89,8.99,8.92,9.1,9.13,9.11],
[8.97,8.97,8.91,9.09,9.11,9.11],
[9.04,9.08,9.05,9.25,9.28,9.27],
[9,9.01,9,9.2,9.23,9.2],
[8.99,8.99,8.98,9.18,9.2,9.19],
[8.93,8.97,8.97,9.18,9.2,9.18]
])
z2 = np.array([
[8.83,8.89,8.81,8.87,8.9,8.87],
[8.89,8.94,8.85,8.94,8.96,8.92],
[8.84,8.9,8.82,8.92,8.93,8.91],
[8.79,8.85,8.79,8.9,8.94,8.92],
[8.79,8.88,8.81,8.9,8.95,8.92],
[8.8,8.82,8.78,8.91,8.94,8.92],
[8.75,8.78,8.77,8.91,8.95,8.92],
[8.8,8.5,8.77,8.91,8.95,8.94],
[8.74,8.81,8.76,8.93,4.98,8.99],
[8.89,8.99,8.92,9.1,9.13,9.11],
[8.97,8.97,8.91,9.09,9.11,9.11],
[9.04,9.08,9.05,9.25,9.28,9.27],
[9,9.01,16,9.2,9.23,9.2],
[8.99,8.99,8.98,9.18,9.2,9.19],
[8.93,8.97,8.97,9.18,9.2,9.18]
])
z3 = np.array([
[8.83,8.89,8.81,8.87,8.9,8.87],
[8.89,8.94,8.85,8.94,8.96,8.92],
[8.84,8.9,8.82,8.92,8.93,8.91],
[8.79,8.85,8.79,8.9,4.94,8.92],
[8.79,8.88,8.81,8.9,8.95,8.92],
[8.8,8.82,8.78,8.91,8.94,8.92],
[8.75,8.78,8.77,8.91,8.95,8.92],
[8.8,8.8,8.77,8.91,8.95,8.94],
[8.74,8.81,8.76,8.93,8.98,8.99],
[8.89,8.99,8.92,9.1,9.13,9.11],
[8.97,8.97,8.91,9.09,9.11,9.11],
[9.04,9.08,20.05,9.25,9.28,9.27],
[9,9.01,9,9.2,9.23,9.2],
[8.99,8.99,8.98,9.18,9.2,9.19],
[8.93,8.97,8.97,9.18,9.2,9.18]
])

z2 = z2 -110
z3 = z3 - 660

mycolorscale = [[0, 'magenta'],[0.3, 'violet'], [0.35, 'blue'], [0.5, 'green'],[0.6, 'yellow'],[0.8, 'orange'], [1, 'red']]

fig1 = go.Figure(data=[
go.Surface (
contours = {
"x": {"show": True, "start": 1.5, "end": 2, "size": 0.04, "color":"white"},
"z": {"show": True, "start": 0.5, "end": 0.8, "size": 0.05}
}, coloraxis='coloraxis',

colorbar=dict(
title='Vorticity [Gyr-1]', # title here
titleside='right',
titlefont=dict(
size=14,
family='Arial, sans-serif'),
len=0.8,),

z=z1, opacity=0.9),

go.Surface(contours = {
"x": {"show": True, "start": 1.5, "end": 2, "size": 0.04, "color":"white"},
"z": {"show": True, "start": 0.5, "end": 0.8, "size": 0.05}
},  coloraxis='coloraxis',

z=z2,opacity=0.9, ),
go.Surface(contours = {
"x": {"show": True, "start": 1.5, "end": 2, "size": 0.04, "color":"white"},
"z": {"show": True, "start": 0.5, "end": 0.8, "size": 0.05}
}, coloraxis='coloraxis',

z=z3, opacity=0.9)])

fig1.update_layout(coloraxis = dict(colorscale=mycolorscale, cmin =-320, cmax=320, colorbar_thickness=25))

fig1.update_layout(scene = dict(
xaxis_title='X [km]',
yaxis_title='Y [km]',
zaxis_title='Z [km]'),
)

fig1.show()

``````

The colours displayed shouldn’t go below 3-4, which is the minumum value of the function z.

Thank you very much for your feedback and to everyone who could help me with this.

@rcarluccio It seems that now you are asking a different question, posted on plotly.py, too: “Is it currently possible to create multiple surface/contour plots of a function (W) in the 3D (XYZ) space?”

Your pasted code on plotly.py defines a 3d volume. Plotly is able to define an isosurface in your volume,
https://plot.ly/python/3d-isosurface-plots/
i.e. to carve into the volume to get the set of points that have associated the same value. This is similar to defining a surface of equation F(x, y, z) =somevalue. Your description here and as an issue on plotly.py is not sufficiently clear to understand what role have the arrays denoted by w. To get the Plotly Isosurface working you have to associate a value to each point of your volume, i.e. if X, Y, Z have the shape (m, n, p), another array of the same shape records these associated values : https://plot.ly/~empet/15143.

If you are interested only in plotting a surface as a cover of a volume like in the image below, then please point out the array(s) that define your surface. (in this image the upper surface is not colored, but its z-values can be mapped to a colorscale).

Hi Empet,

Thank you for your prompt reply, I was now able to reproduce a working example of what I am trying to achieve using the isosurface plot. Yes, I have a 3d F(x, y, z) =somevalue and I wanted to plot slices of this function at some certain depths, I could have been clearer in formulating my question.

Is it possible to use Isosurface and contour plots in combination?

This is the current working vanilla example and output:

``````import plotly.graph_objects as go
import numpy as np

mycolorscale = [[0, 'magenta'],[0.3, 'violet'], [0.35, 'blue'], [0.5, 'green'],[0.6, 'yellow'],[0.8, 'orange'], [1, 'red']]

# Create 3D grid
X      =        np.linspace(-2000, 2000,6)
Y      =        np.linspace(-1500, 1500, 6)
Z      =        np.linspace(-660, 0., 6)
x, y, z = np.meshgrid(X, Y, Z, indexing='ij')

assert np.all(x[:,0,0] == X)
assert np.all(y[0,:,0] == Y)
assert np.all(z[0,0,:] == Z)

W= np.array ([[
[ 0,  1,  2, 0,  1,  2],
[ 3,  4,  5, 0,  1,  2],
[ 6,  7,  8, 0,  1,  2],
[ 6,  7,  8, 0,  1,  2],
[0,  1,  2, 0,  1,  2],
[ 3,  4,  5, 0,  1,  2]],

[[ 9, 10, 11, 0,  1,  2],
[12, 13, 14, 0,  1,  2],
[15, 16, 17, 0,  1,  2],
[ 6,  7,  8, 0,  1,  2],
[15, 16, 17, 0,  1,  2],
[ 6,  7,  8, 0,  1,  2]],

[[18, 19, 20, 0,  1,  2],
[21, 22, 23, 0,  1,  2],
[24, 25, 26, 0,  1,  2],
[24, 25, 26, 0,  1,  2],
[24, 25, 26, 0,  1,  2],
[24, 25, 26, 0,  1,  2]],

[ [ 0,  1,  2, 0,  1,  2],
[ 3,  4,  5, 0,  1,  2],
[ 6,  7,  8, 0,  1,  2],
[ 6,  7,  8, 0,  1,  2],
[0,  1,  2, 0,  1,  2],
[ 3,  4,  5, 0,  1,  2]],

[[ 9, 10, 11, 0,  1,  2],
[12, 13, 14, 0,  1,  2],
[15, 16, 17, 0,  1,  2],
[ 6,  7,  8, 0,  1,  2],
[15, 16, 17, 0,  1,  2],
[ 6,  7,  8, 0,  1,  2]],

[[18, 19, 20, 0,  1,  2],
[21, 22, 23, 0,  1,  2],
[24, 25, 26, 0,  1,  2],
[24, 25, 26, 0,  1,  2],
[24, 25, 26, 0,  1,  2],
[24, 25, 26, 0,  1,  2]],

])
fig1 = go.Figure(data=go.Isosurface(
x=x.flatten(),
y=y.flatten(),
z=z.flatten(),
value=W.flatten(),
isomin=0,
isomax=50,
surface_fill=0.4,
coloraxis='coloraxis',
caps=dict(x_show=False, y_show=False),
slices_z=dict(show=True, locations=[0, -110]),
#slices_y=dict(show=True, locations=[-2000]),
))

fig1.update_layout(coloraxis = dict(colorscale=mycolorscale, cmin =-320, cmax=320, colorbar_thickness=25))

fig1.update_layout(
margin=dict(t=0, l=0, b=0), # tight layout
scene_camera_eye=dict(x=1.86, y=0.61, z=0.98))
fig1.show()

``````

Thank you,

The class `go.Isosurface` has an attribute, `contour`: https://plot.ly/python-api-reference/generated/plotly.graph_objects.Isosurface.html.

If you insert in the isosurf definition from this notebook https://plot.ly/~empet/15143:

`contour_show=True, contour_width=4, contour_color='black',`

you’ll see from particular positions of the camera eye (or rotating the isosurface with the mouse) the contours on the surface of equation F(x,y,z) =c, but
if you are defining a slice or more and set contour_show=True (i.e. you add the above contour attributes), the contours are not plotted but they are displayed when you are hovering over the slices: https://plot.ly/~empet/15350.

Thank you for your support and help. Unfortunately, I’m still running into a couple of problems.

I’m post-processing the outputs of my 3D-HR simulations which run on 256 CPUs and have a 3D mesh grid of 256X64X64 elements. In this current case, I’m analysing just the output of one timestep of one simulation. It took me a bit of time but now If you like you can run my code through binder/docker. You can lunch them through the binder icon on my README file on github: https://github.com/rcarluccio/3D-Post_Processing. This link provides you with an interactive interface to run the output of my primary code.

Problems:

1. If I use an Isosurface to plot the whole volume of the function, this becomes i) very computationally expensive with a simplified “vanilla example” of my same 3d grid size already. It works in low resolution with my real data, but as soon as I use my HR grid the plot shows nothing. You can find this script on the binder image at this path 3D_Post_Processing/test-2-01-volume-LR.ipynb

2. A surface plot is a less computationally expensive option. This allows me to evaluate my function only on certain surfaces plans (not on the entire mesh) which is less computationally expensive. Then, I need to reproduce the 3D domain and translate the XY surfaces to the right depth (as from my first question). However, as seen before this changes the values of my function in function-depth and the same colour scale does not fit them all appropriately. You can find this script here on the binder image: 3D_Post_Processing/test-2-01-surfaces-LR.ipynb.

#Update: The lower resolution (LR) scripts work and are available through binder, and have a smaller grid size (32X16X16). The HR are also available online but the notebooks crush as they reach the max memory allowed, but can be run locally.

I’m very much open to suggestions and comments, every feedback at all will be very much appreciated it.

From my previous experments I know that it is almost impossible to visualize an isosurface carved in a HR volume, but you can remedy the drawback pointed out in 2. setting the `surfacecolor` as the real depth of the surface. A surface can be colored according to its z-values, but also according to the values set in `surfacecolor`

Ok, I was able to solve this with the volume plot by modifying my coordinate system in a way the could be framed appropriately by the isosurface plot.

``````
fig1 = go.Figure(data=go.Isosurface(
x=ycoords,
y=zcoords,
z=xcoords,
value=w_z_glob_filtered.flatten(),
opacity=0.6,
coloraxis='coloraxis',
caps=dict(z_show=False,  x_show=False),

))
name = 'vertical is along y+z'
camera = dict(
up=dict(x=0, y=math.cos(angle), z=math.sin(angle)),
eye=dict(x=2, y=0, z=0)
)

fig.update_layout(scene_camera=camera, scene_dragmode='orbit', title=name)
``````

I’d like to ask one final thing. By doing this my coordinate system switched to y, z, x and I need to rotate my camera to visualise this properly in the true x, y, z physical form. I’ve tried to use the up parameter to tilt the camera, but somehow I don’t visualise any changes. Would you have any advice on this?

@rcarluccio

From your isosurface definition it follows that you applied a transformation T to your data,

``````T = np.array([[0, 1, 0],
[0, 0, 1],
[1, 0, 0]])
``````

which is a rotation about the direction represented by the vector v = [1,1,1] (the eigenvector of the eigenvalue 1 of the array T), and of angle theta defined by cos(theta)= (trace(T)-1)/2= -1/2 (this follows from the theory of
3 x 3 orthogonal matrices, i.e. matrices that satisfy T.dot (T.ranspose() =Id_3 (identity matrix), and of determinant equal to 1.

You are applying this transformation to data, but the reference system is fixed, i.e. the rotated volume is represented with respect to the same coordinate system.
In a default definition of your isosurface the camera up vector has the direction (0, 0, 1). To see your isosurface as if it was not rotated you have to apply the same transformation T to the default up vector,
i.e. to compute:

`T.dot([0,0,1]) `

which gives [0,1,0] as the new up vector.

Hence you have to update the layout as follows:

``````fig.update_scenes(camera_up =dict(x=0,y=1, z=0),
camera_eye= dict(x=x_eye, y=y_eye, z=z_eye),
``````

where (x_eye, y_eye, z_eye) is a convenient position for camera_eye to emphasize characteristics of interest in your plot.

Thank you a lot also for the detailed explanation!

1 Like

I have a question. Can help me please?
How can add projection to Isosurface.(similar to https://plotly.com/python/v3/2d-projection-of-3d-surface/)
Or how can add contours to Isosurface. (similar to https://plotly.com/python/3d-surface-plots/)

Hi @hamed,

Its projection on a z-plane of equation, z=constant, for example, makes sense if it is a graph of a simple surface of equation, z=f(x,y). The projection map associates to each point on the surface, of coordinates (x, y, f(x,y)), the point in the plane, of coordinates (x,y, constant) , and colors it with the color of the point on the surface.

An isosurface is theoretically an implicit surface, g(x,y,z)=0, that can have more points of the same (x,y)- coordinates. The simplest one is the sphere `x**2+y**2+z**2=1`. The points (0.4, 0.5, 0.7681145747868608),
(0.4, 0.5, -0.7681145747868608), are both on this sphere but their projection on the plane z=0 is the same: (0.4, 0.5, 0). What color should we assign to this point? The color of the first point on the sphere or the color of the second one?!

To continue this discussion please open a new question because this one is different on that in the sequecne of questions-answers above.

Hi again and tanks for warm consideration.

My isosurface code is as follows
#############################################################
W = f(X,Y,Z)

axis = dict(
showbackground=True,
backgroundcolor=“rgb(230, 230,230)”,
showgrid=False,
zeroline=False,
showline=False)

ztickvals=list(range(0,4))
layout = go.Layout(title=“Projections of a surface onto coordinate planes” ,
autosize=False,
width=500,
height=500,
scene=dict(xaxis=dict(axis, range=[0, 1]),
yaxis=dict(axis, range=[0, 1]),
zaxis=dict(axis , range=[0, 1]),
aspectratio=dict(x=0.1,
y=0.1,
z=0.1)
)
)

data=go.Isosurface(
x=X.flatten(),
y=Y.flatten(),
z=Z.flatten(),
value=W.flatten(),
isomin=-0.5,
isomax=-0.5,
showscale=False,
caps=dict(x_show=False, y_show=False),
slices_z=dict(show=True, locations=[0.5]),
)

fig = go.Figure(data=data,layout=layout)

fig.show()
#################################################

Now, I want add contour plot “f(x ,y ,z = 0)” in xy plan. In fact, I want combine two figures: Isosurface f(x,y,z) in my top code and contour plot “f(x ,y ,z = 0)” which belongs in xy plan. In this case, the contour plot looks like a shadow of the Isosurface.

my two mention figures are similar as follows but the are not combined in one figure here. I want to bottom figure be in xy plan of top figure.

@hamed
On the first page of Plotly forum click in the upper-right corner on New topic. In the dropdown menu, category, select Graphing Library Plotly.py, and write the title `Slice in an isosurface`, because what you are interested in is a slice, not a projection!!! Then I’ll give you an answer there.