Animated 3D Surface Plots with Plotly - python

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.
Here is some example of the animation: https://plotly.com/python/#animations
Here is some example of the 3D surface plot: https://plotly.com/python/3d-surface-plots
If you could help me do you I would much appreciate !
Thank you for your help,
Theophile

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)

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

Related

Is there an easy way to add a secondary y-axis to a Plotly plot without the need of providing a second array?

I want to have two y-axes for the same time series, an axis with the absolute number as values, and an axis with the percentages as values. Draft schematics of the desired outcome
I don't see a way of doing it without 2 traces. It does mean each trace can be switched on / off in legend so that hover gives interactivity.
import numpy as np
import pandas as pd
import plotly.graph_objects as go
x = pd.date_range("14-feb-2022", freq="1H", periods=400)
y = np.sort(np.random.pareto(2, 400))[::-1]
go.Figure(
[
go.Scatter(x=x, y=y, name="abs"),
go.Scatter(
x=x,
y=y / y.max(),
name="% max",
yaxis="y2",
),
],
layout={
"yaxis2": {
"side": "right",
"tickformat": ",.0%",
},
"yaxis": {"overlaying": "y2"},
"legend": {"orientation": "h", "xanchor": "center", "x": 0.5},
},
)

Add ff.create_quiver to go.Scattermapbox as a trace in Plotly Python

I'm trying to add wind vectors to my Plotly map. This is a simplified version of the code:
import plotly.graph_objects as go
import plotly.figure_factory as ff
fig = go.Figure(go.Scattermapbox(
mode = "markers",
lon = df['lon'],
lat = df['lat'],
marker = {'size': 5, 'color':'black'},
x, y = np.meshgrid(np.arange(0,2,.2), np.arange(0,2,.2))
u = np.cos(x) * y
v = np.sin(x) * y
vec_field = ff.create_quiver(x, y, u, v)
fig.add_traces(data = vec_field.data[0])
fig.update_layout(
margin={"l": 0, "r": 0, "t": 15, "b": 0},
mapbox={
"style": "carto-positron",
"zoom": 5,
"center": {
"lon": df['lon'].mean(),
"lat": df['lat'].mean(),
},
},
)
However, the plot generated is not what I'm looking for. The map ends up overlaying the quiver plot, so I can't see the arrows at all. Is there any way to rectify this, such that the arrows are shown clearly above the map?
using standard traces and mapbox traces in same figure is problematic
thinking laterally, you can build a quiver from geometry and use https://shapely.readthedocs.io/en/stable/manual.html#affine-transformations to set size and direction of vector
import math
import geopandas as gpd
import pandas as pd
import plotly.express as px
import shapely.geometry
import numpy as np
from shapely.affinity import affine_transform as T
from shapely.affinity import rotate as R
# some geometry for an arrow
a = shapely.wkt.loads(
"POLYGON ((-0.6227064947841563 1.890841205238906, -0.3426264166591566 2.156169330238906, -0.07960493228415656 2.129731830238906, 1.952059130215843 0.022985736488906, -0.2085619635341561 -2.182924419761094, -0.6397611822841562 -1.872877544761094, -0.6636088385341563 -1.606053326011095, 0.5862935052158434 -0.400158794761094, -2.312440869784157 -0.3993228572610942, -2.526870557284156 -0.1848931697610945, -2.517313916659156 0.2315384708639062, -2.312440869784157 0.3990052677389059, 0.5862935052158434 0.399841205238906, -0.6363314947841564 1.565763080238906, -0.6227064947841563 1.890841205238906))"
)
gdf = (
gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
.set_crs("EPSG:4326")
)
# generate some data that has GPS co-ordinates, direction and magnitude of wind
df_wind = pd.concat(
[
pd.DataFrame(
{
"lat": np.linspace(*b[[0, 2]], 20),
"lon": np.linspace(*b[[1, 3]], 20),
"d": np.random.uniform(0, math.pi *2, 20),
"s": np.linspace(0.3, 1.5, 20),
}
)
for b in [gdf.sample(2)["geometry"].total_bounds for _ in range(5)]
]
).reset_index(drop=True)
# scatter points
t = (
px.scatter_mapbox(df_wind, lat="lon", lon="lat")
.update_layout(mapbox={"style": "carto-positron"})
.data
)
# wind direction and strength
px.choropleth_mapbox(
df_wind,
geojson=gpd.GeoSeries(
df_wind.loc[:, ["lat", "lon", "d", "s"]].apply(
lambda r: R(
T(a, [r["s"], 0, 0, r["s"], r["lat"], r["lon"]]),
r["d"],
origin=(r["lat"], r["lon"]),
use_radians=True,
),
axis=1,
)
).__geo_interface__,
locations=df_wind.index,
color="d",
).add_traces(t).update_layout(mapbox={"style": "carto-positron", "zoom": 1}, margin={"l":0,"r":0,"t":0,"b":0})

matplotlib to plotly plot conversion

I wanted to create an interactive plot with matplotlib in google colab. It seems like a complex task so I want a little help to convert this piece of code which is in matplotlib to Plotly.
close = df['A']
fig = plt.figure(figsize = (15,5))
plt.plot(close, color='r', lw=2.)
plt.plot(close, '^', markersize=10, color='m', label = 'signal X', markevery = df_x)
plt.plot(close, 'v', markersize=10, color='k', label = 'signal Y', markevery = df_y)
plt.title('Turtle Agent: total gains %f, total investment %f%%'%(df_A, df_B))
plt.legend()
plt.show()
using sample data from plotly OHLC examples https://plotly.com/python/ohlc-charts/
create a line trace
add scatter traces based on filters of data frame with required formatting. This is done as a list comprehension, could be done as inline code
import pandas as pd
import numpy as np
import plotly.express as px
df = pd.read_csv(
"https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv"
)
df["Date"] = pd.to_datetime(df["Date"])
# make data set more useful for demonstrating this plot
df.loc[df.sample((len(df)//8)*7).index, "direction"] = np.nan
px.line(df, x="Date", y="AAPL.Close").update_traces(line_color="red").add_traces(
[
px.scatter(
df.loc[df["direction"].eq(filter)], x="Date", y="AAPL.Close"
)
.update_traces(marker=fmt)
.data[0]
for filter, fmt in zip(
["Increasing", "Decreasing"],
[
{"color": "black", "symbol": "triangle-up", "size": 10},
{"color": "blue", "symbol": "triangle-down", "size": 10},
],
)
]
)

How to create a heatmap animation with plotly express?

I have a list of square matrices, M[t], where t ranges from 0 to N and I wish to create an animated heatplot using plotly.express. The entries in each row/column correspond to a list,
a=['a1','a2',...'aN']
The plotly documentation on animation is fairly sparse and focuses on just scatterplots and barplots
https://plotly.com/python/animations/
A question similar to mine was posted at
How to animate a heatmap in Plotly
However, the user is working in a Jupyter notebook. I'm simply using Python 3.7 with IDLE on a Mac (OS 10.15.4)
I know how to create a basic animation using matplotlib or seaborn, but I like the built-in start/stop buttons that come with plotly express. Here's one approach I use, but I'm sure there are more efficient ways using matplotlib.animation:
import numpy as np
import matplotlib.pyplot as plt
#50 matrices, each of size 4-by-4.
N = 50
M = np.random.random((50, 4,4))
#Desired labels for heatmap--not sure where to put.
labels=['a','b','c','d']
fig, ax = plt.subplots()
for t in range(50):
ax.cla()
ax.imshow(M[t])
ax.set_title("frame {}".format(t))
plt.pause(0.1)
Does this work for you?
import numpy as np
import plotly.graph_objs as go
N = 50
M = np.random.random((N, 10, 10))
fig = go.Figure(
data=[go.Heatmap(z=M[0])],
layout=go.Layout(
title="Frame 0",
updatemenus=[dict(
type="buttons",
buttons=[dict(label="Play",
method="animate",
args=[None])])]
),
frames=[go.Frame(data=[go.Heatmap(z=M[i])],
layout=go.Layout(title_text=f"Frame {i}"))
for i in range(1, N)]
)
fig.show()
UPDATE In case you need to add a Pause button
fig = go.Figure(
data=[go.Heatmap(z=M[0])],
layout=go.Layout(
title="Frame 0",
title_x=0.5,
updatemenus=[dict(
type="buttons",
buttons=[dict(label="Play",
method="animate",
args=[None]),
dict(label="Pause",
method="animate",
args=[None,
{"frame": {"duration": 0, "redraw": False},
"mode": "immediate",
"transition": {"duration": 0}}],
)])]
),
frames=[go.Frame(data=[go.Heatmap(z=M[i])],
layout=go.Layout(title_text=f"Frame {i}"))
for i in range(1, N)]
)
fig.show()

How to plot GEBCO bathymetry data with python?

I'm trying to plot bathymetry data(from GEBCO database). I am trying to plot lat, long and elevation using seaborn python. Data is in the netCDF format. I managed to load data correctly using xarray.
Here is the code snippet.
from netCDF4 import Dataset
import xarray as xr
ds = Dataset('gebco_2019.nc')
lats = ds.variables['lat'][:]
lons = ds.variables['lon'][:]
tid = ds.variables['tid'][:]
import plotly.graph_objects as go
fig = go.Figure(go.Surface(
contours = {
"x": {"show": True, "size": 0.04, "color":"white"},
"z": {"show": True, "size": 0.05}
},
x = ds.variables['lat'],
y = ds.variables['lon'],
z = [
ds.variables['tid']
]))
fig.show()
try to plot 3D plot using python but getting this error.
arrays must all be same length
I managed to get 2-D plot.
I had used the another method to check if the netcdf file is okay(convert to csv). I had plotted using plotly.
import plotly.graph_objects as go
import pandas as pd
import numpy as np
# Read data from a csv
z_data = pd.read_csv('bathy_bedford.csv')
z = z_data.values
sh_0, sh_1 = z.shape
x, y = np.linspace(44.66875, 44.74791667, sh_0), np.linspace(-63.69791667, -63.52708333, sh_1)
fig = go.Figure(data=[go.Surface(z=z, x=x, y=y)])
fig.update_traces(contours_z=dict(show=True, usecolormap=True,
highlightcolor="limegreen", project_z=True))
fig.update_layout(title='Bedford Basin Elevation',xaxis_title="Latitude",
yaxis_title="Longitude",autosize=False,
width=900, height=900,
margin=dict(l=65, r=50, b=65, t=90))
fig.update_layout(scene = dict(
xaxis_title='Latitude',
yaxis_title='Longitude',
zaxis_title='Elevation'),
margin=dict(r=20, b=10, l=10, t=10))
# fig.update_layout(color='Elevation')
fig.update_layout(coloraxis_colorbar=dict(
title="Elevation",
thicknessmode="pixels", thickness=50,
lenmode="pixels", len=200,
yanchor="top", y=1,
ticks="outside", ticksuffix="",
dtick=5
))
fig.show()
Here is the output image:

Categories

Resources