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()
```
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.
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()
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()
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)