My Coding > Numerical simulations > Atoms and Molecules > Tools for molecular modelling > Visualising density cloud with planes in Plotly

Visualising density cloud with planes in Plotly

In the previous example, I've shown, how to calculate randomly distributed dots in accordance with the given density function. Now we can calculate them for 3D space.

Now we have some problems, with how to visualize these results. I will visualize the 3D density, or probability function with Plotly in this example. To show elliptical objects it is very convenient to use three perpendicular slices by the main axes of the object and make these slices semitransparent.

Why Plotly?

Why I'm using Plotly because it will allow me to generate interactive 3D-picture working as an HTML object.

Algorithm

There are two ways of making this kind of presentation. The first way is to make XYZ coordinates for required cross-cutting planes and then calculate values on these planes for further displaying. Another algorithm is to calculate all values in a 3D domain and then extract values corresponding to the planes of crossing. I will use the second algorithm.

Initial definitions


import numpy as np
import plotly.graph_objects as go
import plotly.io as pio

def RDF(x, y, z):
    kx, ky, kz = 1, 2.5, 5
    r = ((x/kx)**2 + (y/ky)**2 + (z/kz)**2)**0.5
    return r**2 * np.exp(-r**2)

Min, Max = -10, 10
N = 50

In this module, I've loaded the required libraries: numpy, plotly.graph and plotly.io.

Then I've defined the function of calculation density (or probability) over 3d space according to the equations:

\[ d = R^2 \times e^{-R^2} \\ where \\ R = \sqrt{(\frac{x}{k_x})^2 + (\frac{y}{k_y})^2 + (\frac{z}{k_z})^2} \\ with k_x = 1; k_y = 2.5; k_z = 5\]

The domain will be defined in the range [-10, 10] with the step of 50.

Calculating values in the domain

To create domain, I will use meshgrid function from numpy.


xr = np.linspace(Min, Max, N) # range over X
yr = np.linspace(Min, Max, N) # range over Y
zr = np.linspace(Min, Max, N) # range over Z
x, y, z = np.meshgrid(xr, yr, zr) # domain with coordinates
rdf = RDF(x, y, z) # calculating values in the domain

It is very important to remember, that in the meshgrid the X and Y are swapped, and it is necessary to mention them in the code, by using them in the proper order. I will use this specific order when I will calculate slices.

Perpendicular planes extracted from the domain by crossing it through the centre:


x_plane = rdf[:, N//2, :] # X is a second coordinate 
y_plane = rdf[N//2, :, :] # Y is a first coordinate
z_plane = rdf[:, :, N//2] # Z is a third coordinate

Displaying planes with plotly

It is straight forward command for displaying planes. Create Surface and pass X, Y and Z planes as coordinates, surface colour values will be taken from x_plane, colorscale will be Hot - you can use any standard colour schemes. The plane will be slightly transparent: opacity=0.7 and finally, by the parameter showscale=False I've disabled the displaying scale on the right side.

This code will be used for every plane.


sp_x = go.Surface(x=x[:, N//2, :], y=y[:, N//2, :], z=z[:, N//2, :],
                 surfacecolor=x_plane,
                 colorscale='Hot', showscale=False, opacity=0.7)
sp_y = go.Surface(x=x[N//2, :, :], y=y[N//2, :, :], z=z[N//2, :, :],
                 surfacecolor=y_plane,
                 colorscale='Hot', showscale=False, opacity=0.7)
sp_z = go.Surface(x=x[:, :, N//2], y=y[:, :, N//2], z=z[:, :, N//2],
                 surfacecolor=z_plane,
                 colorscale='Hot', showscale=False, opacity=0.7)
fig = go.Figure(data=[sp_x, sp_y, sp_z]) # add surfaces to the future

When all surfaces are generated, we need to add them to the figure as a list.

Displaying axes

For better visual presentation we can show axes. Every axis has three elements: the axis itself, the label and the arrow at the end of this axis. We need to create all these objects separately. Axis will be 1.2 times bigger than our domain and the arrow size will be 5% of the axis length.


arr = (Max-Min)*0.05 # size of an arrow
# X axis
fig.add_trace(go.Scatter3d(x=[1.2*Min, 1.2*Max], y=[0,0], z=[0,0],
                          mode='lines', line=dict(color='black', width=2),
                          showlegend=False))
fig.add_trace(go.Scatter3d(x=[1.2*Max], y=[0], z=[0], text=['X Axis'],
                          mode='text', showlegend=False))
fig.add_trace(go.Cone(x=[1.2*Max], y=[0], z=[0], 
                      u=[arr], v=[0], w=[0],
                      colorscale=[[0,'black'], [1,'black']],
                      showscale=False))

it is important to note, that every object creates a legend at the right of the screen, which is a bit annoying and it is better to switch it off, and furthermore in the Cone it is a different parameter (showscale) for switching off the scale. For Scatter3d it is a showlegend parameter. I hope other parameters are self-explanatory.

The same we should do for all axes.

Savig results.

The beauty of plotly - it can save results as an HTML object.

It is possible to save it as an HTML fragment to incorporate into another HTML


html_fragment = fig.to_html(full_html=False)
with open('rdf_frag.html', 'w') as f:
    f.write(html_fragment)

Or to generate full HTML with this interactive picture.


pio.write_html(fig, 'rdf.html', auto_open=True)

That it! The full code is below. If you want to see it in more detail, please watch our video with explanations of the details.

Full working code


import numpy as np
import plotly.graph_objects as go
import plotly.io as pio

def RDF(x, y, z):
    kx, ky, kz = 1, 2.5, 5
    r = ((x/kx)**2 + (y/ky)**2 + (z/kz)**2)**0.5
    return r**2 * np.exp(-r**2)

Min, Max = -10,10
N = 50

xr = np.linspace(Min, Max, N)
yr = np.linspace(Min, Max, N)
zr = np.linspace(Min, Max, N)
x, y, z = np.meshgrid(xr, yr, zr)
rdf = RDF(x, y, z)

x_plane = rdf[:, N//2, :]
y_plane = rdf[N//2, :, :]
z_plane = rdf[:, :, N//2]

surface_plot_x = go.Surface(x=x[:, N//2, :], y=y[:, N//2, :], z=z[:, N//2, :],
                            surfacecolor=x_plane,
                            colorscale='Hot', showscale=False, opacity=0.8)
surface_plot_y = go.Surface(x=x[N//2, :, :], y=y[N//2, :, :], z=z[N//2, :, :],
                            surfacecolor=y_plane,
                            colorscale='Hot', showscale=False, opacity=0.8)
surface_plot_z = go.Surface(x=x[:, :, N//2], y=y[:, :, N//2], z=z[:, :, N//2],
                            surfacecolor=z_plane,
                            colorscale='Hot', showscale=False, opacity=0.8)
                            
fig = go.Figure(data=[surface_plot_x,  surface_plot_y, surface_plot_z])

# Draw an x-axis line
arrow_size = (Max-Min)*0.05
fig.add_trace(go.Scatter3d(x=[1.2*Min, 1.2*Max], y=[0, 0], z=[0, 0],
                           mode='lines', line=dict(color='black', width=2),
                           showlegend=False))
fig.add_trace(go.Scatter3d(x=[1.2*Max], y=[0], z=[0], text=['X Axis'],
                           mode='text', showlegend=False))

fig.add_trace(go.Cone(x=[1.2*Max], y=[0], z=[0], u=[arrow_size], v=[0], w=[0],
                      colorscale=[[0, 'black'], [1, 'black']], showscale=False))

# Y
fig.add_trace(go.Scatter3d(x=[0, 0], y=[1.2*Min, 1.2*Max], z=[0, 0],
                           mode='lines', line=dict(color='black', width=2),
                           showlegend=False))
fig.add_trace(go.Scatter3d(x=[0], y=[1.2*Max], z=[0], text=['Y Axis'],
                           mode='text', showlegend=False))

fig.add_trace(go.Cone(x=[0], y=[1.2*Max], z=[0], u=[0], v=[arrow_size], w=[0],
                      colorscale=[[0, 'black'], [1, 'black']], showscale=False))
# Z
fig.add_trace(go.Scatter3d(x=[0, 0], y=[0, 0], z=[1.2*Min, 1.2*Max],
                           mode='lines', line=dict(color='black', width=2),
                           showlegend=False))
fig.add_trace(go.Scatter3d(x=[0], y=[0], z=[1.2*Max], text=['Z Axis'],
                           mode='text', showlegend=False))

fig.add_trace(go.Cone(x=[0], y=[0], z=[1.2*Max], u=[0], v=[0], w=[arrow_size],
                      colorscale=[[0, 'black'], [1, 'black']], showscale=False))



# Generate HTML fragment
html_fragment = fig.to_html(full_html=False)

# Write the HTML fragment to a file
with open('rdf_plot_fragment.html', 'w') as f:
    f.write(html_fragment)

# Creating interactive output for testing
pio.write_html(fig, 'rdf_plot.html', auto_open=True)

HTML embedding

To insert our calculated object into a big HTML, use the following framework:


<!DOCTYPE html>
<html>
<head>
    <title>Main HTML</title>
</head>
<body>
    <h1>Main HTML Content</h1>
    <!-- Embed the Plotly HTML fragment using an iframe -->
    <iframe src="rdf_frag.html" frameborder="0"></iframe>
    <p>Additional content in the main HTML file.</p>
</body>
</html>

Additionally, if you want to control the size of the inserted object using CSS styles, you can use the style attribute to apply inline CSS. For example:


<iframe src="rdf_plot_fragment.html" style="width: 500px; height: 300px;" frameborder="0"></iframe>

In this case, the style attribute is used to apply CSS rules directly to the <iframe> element. The width property sets the width to 500px, and the height property sets the height to 300px. You can adjust the values according to your design requirements.

Using the width and height attributes or applying inline CSS using the style attribute allows you to control the size of the inserted object and ensure that it appears with the desired dimensions on your web page.


Published: 2023-08-16 03:39:34
Updated: 2023-08-16 23:11:23

Last 10 artitles


9 popular artitles

© 2020 MyCoding.uk -My blog about coding and further learning. This blog was writen with pure Perl and front-end output was performed with TemplateToolkit.