7

For research data visualisation I'd like to make an animated 3D surface plot in Plotly. The goal is to see the evolution of temperature in a box in function of time. But I don't know how to animate it.

At this moment I only have my plot at a give time. This is my code:

import plotly
import plotly.graph_objects as go
#import plotly.express as px
import pandas as pd
#import numpy as np

#read CSV
z_data = pd.read_csv('data1.csv')# Read data from a csv

fig = go.Figure(data=[go.Surface(z=z_data.values)])

#projection 2D
fig.update_traces(contours_z=dict(show=True, usecolormap=True,
                                  highlightcolor="tomato", project_z=True),
                                  colorscale='portland')

#fig
fig.update_layout(title='data HEATPILES', autosize=False, width=650, height=500, margin=dict(l=0, r=0, b=0, t=0))

#show
plotly.offline.plot(fig)

data1.csv is only this: data1.csv

But I have more data of the point's position in function of time and I would want to make an animated plot, so we could clearly see the evolution on time.

Here is the result at a given time Plot at a given time

I've seen on the plotly documentation that it's possible to make animation with px.scatter and px.line from here, and from there that we can do it with image, so I guess it would be possible with surface plot.

If you could help me do you I would much appreciate ! Thank you for your help,

Theophile

Daunter
  • 71
  • 4

2 Answers2

2

Here is the full code for you:

import pandas as pd
import plotly.graph_objects as go

z_data = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/api_docs/mt_bruno_elevation.csv').values
print(z_data)
z_data2 = z_data * 1.1
z_data3 = z_data * 1.2
z_data4 = z_data * 0.5

z_data_list = []
z_data_list.append(z_data)
z_data_list.append(z_data2)
z_data_list.append(z_data3)
z_data_list.append(z_data4)
z_data_list.append(z_data)
z_data_list.append(z_data2)
z_data_list.append(z_data3)
z_data_list.append(z_data4)

fig = go.Figure(
    data=[go.Surface(z=z_data_list[0])],
    layout=go.Layout(updatemenus=[dict(type="buttons", buttons=[dict(label="Play", method="animate", args=[None])])]),
    frames=[go.Frame(data=[go.Surface(z=k)], name=str(i)) for i, k in enumerate(z_data_list)]
)

fig.update_traces(contours_z=dict(show=True, usecolormap=True, highlightcolor="tomato", project_z=True), colorscale='portland')

fig.update_layout(title='data HEATPILES', autosize=False, width=650, height=500, margin=dict(l=0, r=0, b=0, t=0))

def frame_args(duration):
    return {
            "frame": {"duration": duration},
            "mode": "immediate",
            "fromcurrent": True,
            "transition": {"duration": duration, "easing": "linear"},
        }

sliders = [
            {
                "pad": {"b": 10, "t": 60},
                "len": 0.9,
                "x": 0.1,
                "y": 0,
                "steps": [
                    {
                        "args": [[f.name], frame_args(0)],
                        "label": str(k),
                        "method": "animate",
                    }
                    for k, f in enumerate(fig.frames)
                ],
            }
        ]
    
fig.update_layout(sliders=sliders)
   
import plotly.io as pio

ii = 1
pio.write_html(fig, file="Live3D_"+str(ii)+".html", auto_open=True)
# plotly.offline.plot(fig)
bitbang
  • 1,804
  • 14
  • 18
  • 1
    Thanks! I hope the author accepts the answer. – Schach21 Nov 02 '21 at 21:59
  • I was changing the ```duration``` argument to change the transition speed, but did not do anything. Do you suspect why? Also, how did you come up with that code? I am just asking because in the future I'd like to create my own code. The plotly documentation is kind of hard to find/explore. – Schach21 Nov 04 '21 at 00:14
  • Ok, i will check, i didnt know do you need a play button or a slider that you can change values by hand. i remember slightly but i mixed up and modfy "Simple Play Button" and "Visualizing MRI Volume Slices in Python" examples most probably. check it out -> https://plotly.com/python/animations/ https://plotly.com/python/visualizing-mri-volume-slices/ – bitbang Nov 04 '21 at 00:57
  • Ok. Looking into the animations though, I do see that transition between frames is not smooth. I wonder if it is just the way plotly was built. My experience using animations with matplotlib is very positive, but I like better the aesthetics of plotly. – Schach21 Nov 04 '21 at 01:48
  • 1
    Done, if you want to change the duration of frames in play buttons animation you need to modify "args=[None]" in button definition "buttons=[dict(label="Play", method="animate", args=[None]". The "def frame_args(duration):" s duration parameter adjusts sliders frame duration ""args": [[f.name], frame_args(0)]" which is 0 because we change frames by hand in slider mode. But just lets assume that you have a lot of frame and you want to create a gif, even gifs have a limitation, which is minimum frame duration=20ms – bitbang Nov 04 '21 at 02:07
  • What should I use instead of ```args=[None]```? is the syntax ```args=["duration":50]``` if I want 50ms? The linked you shared talks about possible ```args``` but none of them seems to be the duration of frames or transition. – Schach21 Nov 04 '21 at 02:50
  • Not sure but i did read somewhere Plotly uses d3.js backend. If you want speed, plotly not suggested, check here-> https://docs.juliaplots.org/latest/backends/#[Plotly-/-PlotlyJS](https://github.com/spencerlyon2/PlotlyJS.jl) . Try this "args=[None, {"frame": {"duration": 2000, "redraw": True},"fromcurrent": True, "transition": {"duration": 0}}]" works well with 2000ms :) – bitbang Nov 04 '21 at 03:57
  • Btw OpenGl solves your problem. you can go as low as 1ms frame duration and you are able to use millions of points(i tried with 10million point with immediate respond like zoom or translate). – bitbang Nov 04 '21 at 04:33
0

After a good research I built this code to plot a proper smooth 3D surface plot. Simply put the data_frame into this function. You'll get a proper smoothen surface plot. Incase you face any error, just choose only those features from data_frame which are numerical.

'data_frame = data_frame.select_dtypes(include='number')'

from scipy import interpolate
from mpl_toolkits.mplot3d import axes3d, Axes3D

def surface(data_frame, title=None, title_x=0.5, title_y=0.9):

    X, Y = np.mgrid[-10:10:complex(0,data_frame.shape[0]), 
-10:10:complex(0,data_frame.shape[1])]
    Z = data_frame.values

    xnew, ynew = np.mgrid[-1:1:80j, -1:1:80j]
    tck = interpolate.bisplrep(X, Y, Z, s=0)
    znew = interpolate.bisplev(xnew[:,0], ynew[0,:], tck)

    fig = go.Figure(data=[go.Surface(z=znew)])
    fig.update_layout(template='plotly_dark', 
                      width=800, 
                      height=800,
                      title = title,
                      title_x = title_x, 
                      title_y = title_y
                     )
    return fig