How to export/save an animated bubble chart made with plotly? - python

How to export/save an animated bubble chart made with plotly? (For instance, the one was made following the link below) I would like to have it in a gift or some format with better resolution. Thank you in advance.
https://www.kaggle.com/aashita/guide-to-animated-bubble-charts-using-plotly

There is no possibility to do this in plotly. Please use gifmaker and save every step in the animation as a single picture to later combine them to a gif. Follow this source. Further explanation on how to create animations is provided by plotly here.
The basic way to go would be to integrate this logic into the animation process of your plotly code:
import ImageSequence
import Image
import gifmaker
sequence = []
im = Image.open(....)
# im is your original image
frames = [frame.copy() for frame in ImageSequence.Iterator(im)]
# write GIF animation
fp = open("out.gif", "wb")
gifmaker.makedelta(fp, frames)
fp.close()
If you could provide your actual code, it would be possible to provide a more detailled answer to your question. :)

Another possibility is to use the gif library, which works with matplolib, altair and plotly, and is really straight forward. In this case you would not use a plotly animation. Instead, you define a function that returns a plotly fig and construct a list of figs to pass to gif as an argument.
Your code would look something like this:
import random
import plotly.graph_objects as go
import pandas as pd
import gif
# Pandas DataFrame with random data
df = pd.DataFrame({
't': list(range(10)) * 10,
'x': [random.randint(0, 100) for _ in range(100)],
'y': [random.randint(0, 100) for _ in range(100)]
})
# Gif function definition
#gif.frame
def plot(i):
d = df[df['t'] == i]
fig = go.Figure()
fig.add_trace(go.Scatter(
x=d["x"],
y=d["y"],
mode="markers"
))
fig.update_layout(width=500, height=300)
return fig
# Construct list of frames
frames = []
for i in range(10):
frame = plot(i)
frames.append(frame)
# Save gif from frames with a specific duration for each frame in ms
gif.save(frames, 'example.gif', duration=100)

Building on answers already provided. This generates an animated GIF from an plotly figure that has frames (is animated)
start by generating some test data
generate an animated figure using plotly express
create an image for each frame in plotly figure
finally generate animated GIF from list of images
import plotly.express as px
import pandas as pd
import numpy as np
import io
import PIL
r = np.random.RandomState(42)
# sample data
df = pd.DataFrame(
{
"step": np.repeat(np.arange(0, 8), 10),
"x": np.tile(np.linspace(0, 9, 10), 8),
"y": r.uniform(0, 5, 80),
}
)
# smaple plotly animated figure
fig = px.bar(df, x="x", y="y", animation_frame="step")
# generate images for each step in animation
frames = []
for s, fr in enumerate(fig.frames):
# set main traces to appropriate traces within plotly frame
fig.update(data=fr.data)
# move slider to correct place
fig.layout.sliders[0].update(active=s)
# generate image of current state
frames.append(PIL.Image.open(io.BytesIO(fig.to_image(format="png"))))
# create animated GIF
frames[0].save(
"test.gif",
save_all=True,
append_images=frames[1:],
optimize=True,
duration=500,
loop=0,
)

Related

How to plot multiple animations in Matplolib for 2 different processes

In a measurement chain, each instrument embedded in various measurement loops will record a CSV and I want to monitor the live plots in separate figures i.e figure 1 for instrument1 , figure 2 for instrument2...etc. I try to implement animations but nothing out. csv is continuously generating data.
I first generate data in a CSV then i try to plot 2 animations in parallel:I get the figure 2 animated but the first is frozen. any help appreciated.
import pandas as pd
from matplotlib import pyplot as plt
from matplotlib import animation
# making figures
def makeFigure():
df = pd.read_csv('data.csv')
data = pd.DataFrame(df)
x = data['current']
y1 = data['resistance']
y2 = data['voltage']
fig=plt.figure()
ax=fig.add_subplot(1,1,1)
# # Plot 1 set of data
dataset =ax.plot(x,y1)
return fig,ax,dataset
# Frame rendering function
def renderFrame(i, dataset):
df = pd.read_csv('data.csv')
data = pd.DataFrame(df)
x = data['current']
y1 = data['resistance']
y2 = data['voltage']
# Plot data
plt.cla()
dataset, =ax.plot(x,y2)
return dataset
# Make the figures
figcomps1=makeFigure()
figcomps2=makeFigure()
# List of Animation objects for tracking
anim = []
# Animate the figures
for figcomps in [figcomps1,figcomps2]:
fig,ax,dataset = figcomps
anim.append(animation.FuncAnimation(fig,renderFrame,fargs=[dataset]))
# plt.gcf()
plt.show()
```

How to assign variable fig for animation function in Python 3

I want to make an animation of multiple plots whose rendering evolves in time.
The files that I need are under the format, for example for one :
DD0043/DD0043. So I use the trick : f'{43:04}' to fill the zeros leading for each file (the files go from DD0000/DD0000 to DD0922/DD0922.
Here the script, warning, the plot is done with yt-project tool :
import yt
import os, sys
import numpy as np
from matplotlib.animation import FuncAnimation
from matplotlib import rc_context
from matplotlib import pyplot as plt
# animate must accept an integer frame number. We use the frame number
# to identify which dataset in the time series we want to load
def animate(i):
plot._switch_ds(array_data[i])
# Number of files
numFiles = int(os.popen('ls -dl DD* | wc -l').read())
# Array for each data directory
array_data = np.array(numFiles)
for i in range(numFiles):
data = yt.load('DD'+str(f'{i:04}')+'/DD'+str(f'{i:04}'))
sc = yt.create_scene(data, lens_type='perspective')
source = sc[0]
source.set_field('density')
source.set_log(True)
# Set up the camera parameters: focus, width, resolution, and image orientation
sc.camera.focus = ds.domain_center
sc.camera.resolution = 1024
sc.camera.north_vector = [0, 0, 1]
sc.camera.position = [1.7, 1.7, 1.7]
# You may need to adjust the alpha values to get an image with good contrast.
# For the annotate_domain call, the fourth value in the color tuple is the
# alpha value.
sc.annotate_axes(alpha=.02)
sc.annotate_domain(ds, color=[1, 1, 1, .01])
text_string = "T = {} Gyr".format(float(array_data[i].current_time.to('Gyr')))
fig = plt.figure()
animation = FuncAnimation(fig, animate, frames=numFiles)
# Override matplotlib's defaults to get a nicer looking font
with rc_context({'mathtext.fontset': 'stix'}):
animation.save('animation.mp4')
But at the execution, I get the following error :
923
Traceback (most recent call last):
File "vol-annotated.py", line 52, in <module>
animation.save('animation.mp4')
File "/Users/fab/Library/Python/3.7/lib/python/site-packages/matplotlib/animation.py", line 1135, in save
anim._init_draw()
File "/Users/fab/Library/Python/3.7/lib/python/site-packages/matplotlib/animation.py", line 1743, in _init_draw
self._draw_frame(next(self.new_frame_seq()))
StopIteration
I don't know if I do the things correctly, especially for the variable fig that I initialize with :
fig = plt.figure()
Actually, I am trying to adapt to my case this script which creates a movie :
make animation
i.e :
import yt
from matplotlib.animation import FuncAnimation
from matplotlib import rc_context
ts = yt.load('GasSloshingLowRes/sloshing_low_res_hdf5_plt_cnt_*')
plot = yt.SlicePlot(ts[0], 'z', 'density')
plot.set_zlim('density', 8e-29, 3e-26)
fig = plot.plots['density'].figure
# animate must accept an integer frame number. We use the frame number
# to identify which dataset in the time series we want to load
def animate(i):
ds = ts[i]
plot._switch_ds(ds)
animation = FuncAnimation(fig, animate, frames=len(ts))
# Override matplotlib's defaults to get a nicer looking font
with rc_context({'mathtext.fontset': 'stix'}):
animation.save('animation.mp4')
UPDATE 1: I didn't find a way to use animation.save correctly to generate an animation: always this issue about the fig variable.
But I managed to generate all the images corresponding for each one to an output file DDxxxx/DDxxxx. I have proceeded like this:
import yt
import os, sys
import numpy as np
from matplotlib.animation import FuncAnimation
from matplotlib import rc_context
# Number of files
numFiles = int(os.popen('ls -dl DD* | wc -l').read())
# Loop to load input files
ts = []
for j in range(numFiles):
ts = np.append(ts, yt.load('DD'+str(f'{j:04}')+'/DD'+str(f'{j:04}')))
plot = yt.SlicePlot(ts[0], 'z', 'density')
plot.set_zlim('density', 8e-29, 3e-26)
# create plotting figure
fig = plot.plots['density'].figure
# animate must accept an integer frame number. We use the frame number
# to identify which dataset in the time series we want to load
def animate(i):
ds = ts[i]
sc = yt.create_scene(ds, lens_type='perspective')
source = sc[0]
source.set_field('density')
source.set_log(True)
# Set up the camera parameters: focus, width, resolution, and image orientation
sc.camera.focus = ds.domain_center
sc.camera.resolution = 1024
sc.camera.north_vector = [0, 0, 1]
sc.camera.position = [1.7, 1.7, 1.7]
# You may need to adjust the alpha values to get an image with good contrast.
# For the annotate_domain call, the fourth value in the color tuple is the
# alpha value.
sc.annotate_axes(alpha=.02)
sc.annotate_domain(ds, color=[1, 1, 1, .01])
text_string = "T = {} Gyr".format(float(ds.current_time.to('Gyr')))
## Here the scene needs to be painted into my figure / plot.
sc.save('rendering_'+str(i)+'.png')
animation = FuncAnimation(fig, animate, frames=numFiles)
# Override matplotlib's defaults to get a nicer looking font
with rc_context({'mathtext.fontset': 'stix'}):
animation.save('animation.mp4')
If I open a single .png, I get a correct image representing a 3D scene.
Unfortunately, the animation function is not working, I get just a 2D heatmap plot showing the density projected: I would like to get an animation of the 3D scene figures (rendering_xxx.png).
It seems that I have to use ffmpeg to generate this animation from the multiple .png image, excepted if I find a way to know how to use Python FuncAnimation function (included in yt library ? or in Python by default ?).
UPDATE 2: here an example of figure (a frame actually) of animation I would like to get (this is a figure which represents gas density inside a box, i.e. in 3D) :
Unfortunately, #NightTrain's script produces this kind of plot :
As you can see, I don't understand why I get a 2D heatmap with NightTrain's solution instead of a 3D scene.
Moreover, there is no animation in this 2D heatmap, the movie displays always this same figure.
UPDATE3 : the last solution suggested by #Night train produces the following error :
Traceback (most recent call last):
File "plot_3D_enzo_with_animation_LAST.py", line 30, in <module>
plot = yt.SlicePlot(ts[0], 'z', 'density')
File "/Users/henry/Library/Python/3.7/lib/python/site-packages/yt/data_objects/time_series.py", line 201, in __getitem__
o = self._pre_outputs[key]
IndexError: list index out of range
I don't understand why this error occurs.
If you could provide more information it would be easier to help. I fixed your code and it is running now.
You also forgot to use the text_string variable.
Since the array_data variable isn't used I removed it.
import yt
import os, sys
import numpy as np
from matplotlib.animation import FuncAnimation
from matplotlib import rc_context
from matplotlib import pyplot as plt
import pathlib
import glob
base_path = "enzo_tiny_cosmology"
paths = sorted(glob.glob(base_path + "/DD*/DD[0-9][0-9][0-9][0-9]"))
# paths = [x.joinpath(x.name).as_posix() for x in sorted(pathlib.Path(base_path).glob("DD*"))]
# Array for each data directory
# array_data = np.zeros(len(paths))
# array_data = [None for x in range(len(paths))]
ts = yt.load(paths)
# ts = yt.load(base_path + "/DD*/DD[0-9][0-9][0-9][0-9]")
# print(ts.outputs)
plot = yt.SlicePlot(ts[0], 'z', 'density')
fig = plot.plots['density'].figure
# animate must accept an integer frame number. We use the frame number
# to identify which dataset in the time series we want to load
def animate(i):
data = ts[i]
sc = yt.create_scene(data, lens_type='perspective')
source = sc[0]
source.set_field('density')
source.set_log(True)
# Set up the camera parameters: focus, width, resolution, and image orientation
sc.camera.focus = data.domain_center
sc.camera.resolution = 1024
sc.camera.north_vector = [0, 0, 1]
sc.camera.position = [1.7, 1.7, 1.7]
# You may need to adjust the alpha values to get an image with good contrast.
# For the annotate_domain call, the fourth value in the color tuple is the
# alpha value.
sc.annotate_axes(alpha=.02)
sc.annotate_domain(data, color=[1, 1, 1, .01])
text_string = "T = {} Gyr".format(float(data.current_time.to('Gyr')))
plot._switch_ds(data)
animation = FuncAnimation(fig, animate, frames = len(paths))
# Override matplotlib's defaults to get a nicer looking font
with rc_context({'mathtext.fontset': 'stix'}):
animation.save('animation.mp4')
Instead of counting the lines of ls -dlyou might want to use a python solution. which also lets you use the paths directly without contructing them later. You can use either pathlib or the os module.
import pathlib
import glob
base_path = "enzo_tiny_cosmology"
paths = sorted(glob.glob(base_path + "/DD*/DD[0-9][0-9][0-9][0-9]"))
paths = [x.joinpath(x.name).as_posix() for x in sorted(pathlib.Path(base_path).glob("DD*"))]
For testing I downloaded these datasets:
curl -sSO https://yt-project.org/data/enzo_tiny_cosmology.tar.gz
tar xzf enzo_tiny_cosmology.tar.gz
curl -sSO https://yt-project.org/data/GasSloshingLowRes.tar.gz
tar xzf GasSloshingLowRes.tar.gz
UPDATE:
If you want to save the rendered scenes as video you could e.g. use imageio or opencv:
import yt, glob, imageio
# animate must accept an integer frame number. We use the frame number
# to identify which dataset in the time series we want to load
def animate(data):
sc = yt.create_scene(data, lens_type='perspective')
source = sc[0]
source.set_field('density')
source.set_log(True)
# Set up the camera parameters: focus, width, resolution, and image orientation
sc.camera.focus = data.domain_center
sc.camera.resolution = 1024
sc.camera.north_vector = [0, 0, 1]
sc.camera.position = [1.7, 1.7, 1.7]
# You may need to adjust the alpha values to get an image with good contrast.
# For the annotate_domain call, the fourth value in the color tuple is the
# alpha value.
sc.annotate_axes(alpha=.02)
sc.annotate_domain(data, color=[1, 1, 1, .01])
plot._switch_ds(data)
sc.save(f'rendering_{i:04d}.png')
return sc.render()
paths = sorted(glob.glob("/DD*/DD[0-9][0-9][0-9][0-9]"))
ts = yt.load(paths)
plot = yt.SlicePlot(ts[0], 'z', 'density')
plot.set_zlim('density', 8e-29, 3e-26)
vid_writer = imageio.get_writer("animation.mp4", fps = 10)
for frame in ts:
rendered_image = animate(frame)
vid_writer.append_data(rendered_image)
vid_writer.close()
There are some issues that I can see right away.
The animate function refers to a plot variable that is not defined.
array_data = np.array(numFiles) will result in the number of files in a one-item numpy array. Probably not intended and will cause that array_data[i] fails for i>=1.
array_data is not filled with data afterwards, either.
I don't see any plotting being done. fig = plt.figure() will only provide you with an empty figure.
So, with that I'll restructure your code a bit:
import yt
import os, sys
import numpy as np
from matplotlib.animation import FuncAnimation
from matplotlib import rc_context
from matplotlib import pyplot as plt
# Number of files
numFiles = int(os.popen('ls -dl DD* | wc -l').read())
# create plotting figure
fig = plt.figure()
# animate must accept an integer frame number. We use the frame number
# to identify which dataset in the time series we want to load
def animate(i):
data = yt.load('DD'+str(f'{i:04}')+'/DD'+str(f'{i:04}'))
sc = yt.create_scene(data, lens_type='perspective')
source = sc[0]
source.set_field('density')
source.set_log(True)
# Set up the camera parameters: focus, width, resolution, and image orientation
sc.camera.focus = ds.domain_center
sc.camera.resolution = 1024
sc.camera.north_vector = [0, 0, 1]
sc.camera.position = [1.7, 1.7, 1.7]
# You may need to adjust the alpha values to get an image with good contrast.
# For the annotate_domain call, the fourth value in the color tuple is the
# alpha value.
sc.annotate_axes(alpha=.02)
sc.annotate_domain(ds, color=[1, 1, 1, .01])
text_string = "T = {} Gyr".format(float(data.current_time.to('Gyr')))
## Here the scene needs to be painted into your figure / plot.
animation = FuncAnimation(fig, animate, frames=numFiles)
# Override matplotlib's defaults to get a nicer looking font
with rc_context({'mathtext.fontset': 'stix'}):
animation.save('animation.mp4')
However, I also see in the example that yt supports loading several files at once:
ts = yt.load('GasSloshingLowRes/sloshing_low_res_hdf5_plt_cnt_*') so you might want to consider that as well.
I'm well aware that this is not a running example, but I hope this will help you tracking this down.

How can a plot a map with folium and place a bar graph next to the map in python?

I have a dataframe and I have been creating a interactive bar graph with widgets to show different characteristics of the dataframe and I am only missing to show the location with the latitud and longitud in the dataframe but i dont know how to show those two with a single visualization i know hot to plot the markers with the latitud and longitud in a separate visulization but how can I plot them side to side?
a bit old, but we now have a solution for this using ipywidgets
from io import BytesIO
import matplotlib.pyplot as plt
import folium
from IPython.display import display
fig, ax = plt.subplots(1)
# do your figure work here
mp = folium.Map()
# do your folium map work here
# save the image to a file handler in memory
tmpfile = BytesIO()
fig.savefig(tmpfile, format='png')
# close the figure to avoid this being re-rendered automatically by jupyter
plt.close()
# display the image and map converting them to widgets
display(widget.HBox([
widget.Image(value=tmpfile.getvalue(), format='png', width='50%'),
widget.HTML(value=mp._repr_html_(), layout=widget.Layout(width='50%', height='97%', margin='3% 0 0 0'))
]))
You need to save your plotted graphs to images and then:
import os
from folium.plugins import FloatImage
img = ('your_path_to_image')
f_map = folium.Map ([# lat long from df])
image = (img, bottom = 40,left =65).add_to(f_map)
f_map.save()

Plotly: How to make a 3D stacked histogram?

I have several histograms that I succeded to plot using plotly like this:
fig.add_trace(go.Histogram(x=np.array(data[key]), name=self.labels[i]))
I would like to create something like this 3D stacked histogram but with the difference that each 2D histogram inside is a true histogram and not just a hardcoded line (my data is of the form [0.5 0.4 0.5 0.7 0.4] so using Histogram directly is very convenient)
Note that what I am asking is not similar to this and therefore also not the same as this. In the matplotlib example, the data is presented directly in a 2D array so the histogram is the 3rd dimension. In my case, I wanted to feed a function with many already computed histograms.
The snippet below takes care of both binning and formatting of the figure so that it appears as a stacked 3D chart using multiple traces of go.Scatter3D and np.Histogram.
The input is a dataframe with random numbers using np.random.normal(50, 5, size=(300, 4))
We can talk more about the other details if this is something you can use:
Plot 1: Angle 1
Plot 2: Angle 2
Complete code:
# imports
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import plotly.io as pio
pio.renderers.default = 'browser'
# data
np.random.seed(123)
df = pd.DataFrame(np.random.normal(50, 5, size=(300, 4)), columns=list('ABCD'))
# plotly setup
fig=go.Figure()
# data binning and traces
for i, col in enumerate(df.columns):
a0=np.histogram(df[col], bins=10, density=False)[0].tolist()
a0=np.repeat(a0,2).tolist()
a0.insert(0,0)
a0.pop()
a1=np.histogram(df[col], bins=10, density=False)[1].tolist()
a1=np.repeat(a1,2)
fig.add_traces(go.Scatter3d(x=[i]*len(a0), y=a1, z=a0,
mode='lines',
name=col
)
)
fig.show()
Unfortunately you can't use go.Histogram in a 3D space so you should use an alternative way. I used go.Scatter3d and I wanted to use the option to fill line doc but there is an evident bug see
import numpy as np
import plotly.graph_objs as go
# random mat
m = 6
n = 5
mat = np.random.uniform(size=(m,n)).round(1)
# we want to have the number repeated
mat = mat.repeat(2).reshape(m, n*2)
# and finally plot
x = np.arange(2*n)
y = np.ones(2*n)
fig = go.Figure()
for i in range(m):
fig.add_trace(go.Scatter3d(x=x,
y=y*i,
z=mat[i,:],
mode="lines",
# surfaceaxis=1 # bug
)
)
fig.show()

How to use RangetoolLink with holoviews in an Overlayed plot

I am trying to use the holoviews Rangetool link in a holoviews Overlayed plot. But unable to achieve the range linking to work. Is it possible to achieve this.?
Based on these links example 1 and example 2 I tried the options with an overlayed plot instead of a single curve plot. But this didn't work. Below I provided an example with a similar dummy data.
import pandas as pd
import holoviews as hv
from holoviews import opts
import numpy as np
from holoviews.plotting.links import RangeToolLink
hv.extension('bokeh')
# Genrate Random Data
def randomDataGenerator(noOfSampleDataSets):
for i in range(noOfSampleDataSets):
res = np.random.randn(1000).cumsum()
yield res
# Overlay Plots
overlaid_plot = hv.Overlay([hv.Curve(data)
.opts(width=800, height=600, axiswise=True, default_tools=[])
for data in randomDataGenerator(5)])
# Adjust Source Height
source = overlaid_plot.opts(height=200)
# adjust target plot attributes
target = source.opts(clone=True, width=800, labelled=['y'],)
# Link source and target
rtlink = RangeToolLink(source, target)
# Compose and plot.
(target + source).cols(1).opts(merge_tools=False)
I expect that the source plot will show up with a range tool as shown in the example and be able to select a range in it which should select the same data points in the target plot.
Following code works in my case. I slightly refactored the code. But the logic is still the same. So if we have a an overlaid plot, link one of the curves in the overlayed plot works fine with all the remaining curves.
Following code works in a jupyter notebook. Its not tested in other environment.
import holoviews as hv
import numpy as np
hv.extension('bokeh')
from holoviews.plotting.links import RangeToolLink
# Genrate Random Data
def randomDataGenerator(noOfSampleDataSets):
for i in range(noOfSampleDataSets):
res = np.random.randn(1000).cumsum()
yield res
#generate all curves
def getCurves(n):
for data in randomDataGenerator(n):
curve = hv.Curve(data)
yield curve
source_curves, target_curves = [], []
for curve in getCurves(10):
# Without relabel, the curve somehow shares the ranging properties. opts with clone=True doesn't help either.
src = curve.relabel('').opts(width=800, height=200, yaxis=None, default_tools=[])
tgt = curve.opts(width=800, labelled=['y'], toolbar='disable')
source_curves.append(src)
target_curves.append(tgt)
# link RangeTool for the first curves in the list.
RangeToolLink(source_curves[0],target_curves[0])
#Overlay the source and target curves
overlaid_plot_src = hv.Overlay(source_curves).relabel('Source')
overlaid_plot_tgt = hv.Overlay(target_curves).relabel('Target').opts(height=600)
# layout the plot and render
layout = (overlaid_plot_tgt + overlaid_plot_src).cols(1)
layout.opts(merge_tools=False,shared_axes=False)

Categories

Resources