How to animate a plot in python using the VisVis package? - python

I am trying to animate a plot using visvis.
This is the example code they have:
import visvis as vv
# read image
ims = [vv.imread('astronaut.png')]
# make list of images: decrease red channel in subsequent images
for i in range(9):
im = ims[i].copy()
im[:,:,0] = im[:,:,0]*0.9
ims.append(im)
# create figure, axes, and data container object
a = vv.gca()
m = vv.MotionDataContainer(a)
# create textures, loading them into opengl memory, and insert into container.
for im in ims:
t = vv.imshow(im)
t.parent = m
and I added:
app = vv.use()
app.Run()
This worked. But I needed to animate a plot, not an image, so I tried doing this:
import visvis as vv
from visvis.functions import getframe
# create figure, axes, and data container object
a = vv.gca()
m = vv.MotionDataContainer(a, interval=100)
for i in range(3):
vv.plot([0, 2+i*10], [0, 2+i*10])
f = getframe(a)
t = vv.imshow(f)
t.parent = m
a.SetLimits(rangeX=[-2, 25], rangeY=[-2, 25])
app = vv.use()
app.Run()
The axes are being initialized very big, that is why I am using set limits, and the output is not animated. I am getting only the last frame so a line from (0,0) to (22, 22).
Does anyone know a way of doing this with visvis?

It turns out adding the frame as a child of MotionDataContainer was not the way to go. The function vv.plot returns an instance of the class Line, and one should add the line as a child. If anyone is having the same problem, I could write a more detailed answer.
EDIT Adding a more detailed answer as requested:
To animate a plot made of lines, one must simply add the lines as children of MotionDataContainer. Taking my example in the question above, one would write:
import visvis as vv
# create figure, axes, and data container object
a = vv.gca()
m = vv.MotionDataContainer(a, interval=100)
for i in range(3):
line = vv.plot([0, 2+i*10], [0, 2+i*10])
line.parent = m
app = vv.use()
app.Run()
In my special case, I even needed to animate multiple lines being drawn at the same time.
To do this, I ended up defining a new class that, like MotionDataContainer, also inherits from MotionMixin, and change the class attribute delta which specifies how many objects should be made visible at the same time. For that, one has to also rewrite the function _SetMotionIndex.
(See visvis official source code: https://github.com/almarklein/visvis/blob/master/wobjects/motion.py)
Disclaimer: Concerning the animation of multiple objects, I have no idea if this is the intended use or if this is the easiest solution, but this is what worked for me.

Related

plt.show() causes program to hang even after window is closed

I'm working on a program that displays drawings, with the option of animating the drawing to show which order the lines should be drawn in. When I used plt.show() to display the drawing as a still image, everything works as expected: the code pauses, and then resumes again as soon as the popup window is closed. However, when I use the same function to display an animated drawing, the code remains frozen even after I close the popup window. The only way to get it unstuck is to fully restart the python shell - it doesn't respond to KeyboardInterrupt.
UPDATE:
When I set repeat to False in the FuncAnimation call, it behaves slightly differently. If I close the popup window while the animation is running, the glitch happens, locking up my program. However, if I close the popup after the animation has finished, the program continues as intended. It seems like the glitch here has something to do with closing the window before the animation is done.
UPDATE 2:
For some reason, replacing all of the plt.plot() calls in the animate_pattern function with ax.plot() fixes the issue. I have no idea why this works, because as far as I know the two functions do the same thing. However, the problem is solved.
Below is the code for the module that handles the animation. Some notes:
Normally, I create the animations by calling plot_animated() from a different module. However, the bug happens whether or not I create the animation that way or do it through the code in this module's if name == main statement.
convert_to_points() is a function from the main module that turns the data it's given into a list of x-values and a list of y-values to be plotted.
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation,PillowWriter
from functools import partial
from os.path import isfile
import json
end_marker = [None]
smooth = 40
def animate_pattern(f,anim_data):
x_anim,y_anim,scale = anim_data
global end_marker
# starting point
if x_anim[f] is None:
end_marker = plt.plot(x_anim[1],y_anim[1],marker='o',ms=1.8*scale,mew=0.4*scale,mec="black",c="#ff6bff")
# segment going into a point
if f%smooth == 1:
plt.plot(x_anim[f-1:f+1],y_anim[f-1:f+1],c="#ff6bff",lw=scale)
plt.plot(x_anim[f],y_anim[f],marker='o',ms=1.8*scale,mew=0.4*scale,mec="black",c="#ff6bff")
# segment coming out of a point
elif f%smooth in (2,4):
plt.plot(x_anim[f-1:f+1],y_anim[f-1:f+1],c="#ff6bff",lw=scale)
plt.plot(x_anim[f-f%smooth+1],y_anim[f-f%smooth+1],marker='o',ms=1.8*scale,mew=0.4*scale,mec="black",c="#ff6bff")
# all other segments
else:
plt.plot(x_anim[f-1:f+1],y_anim[f-1:f+1],c="#ff6bff",lw=scale)
# marker for current endpoint of animated line
if x_anim[f]:
end_marker[0].remove()
end_marker = plt.plot(x_anim[f],y_anim[f],marker='h',ms=2.4*scale,mew=0.5*scale,mec="#547dd6",c="#6bc9e8")
def init_pattern(plot_data,settings):
x_vals,y_vals,scale = plot_data[:3]
# clear the canvas
plt.cla()
plt.gca().axis("off")
# draw the full pattern in the background
for i in range(len(x_vals)-1):
plt.plot(x_vals[i:i+2],y_vals[i:i+2],color=settings["monochrome_color"],lw=scale)
plt.plot(x_vals[i],y_vals[i],'ko',ms=2*scale)
plt.plot(x_vals[-1],y_vals[-1],'ko',ms=2*scale)
def anim_interpolate(plot_data):
x_vals,y_vals,scale = plot_data[:3]
x_anim,y_anim = [None],[None]
# create five interpolated points after each point
for i in range(len(x_vals)-1):
x_dist = x_vals[i+1] - x_vals[i]
y_dist = y_vals[i+1] - y_vals[i]
x_anim += [x_vals[i]+x_dist*(1/smooth)*j for j in range(smooth)]
y_anim += [y_vals[i]+y_dist*(1/smooth)*j for j in range(smooth)]
# add the last point
x_anim.append(x_vals[-1])
y_anim.append(y_vals[-1])
return x_anim,y_anim,scale
def plot_animated(plot_data,settings,):
# convert basic pointlist into special version for animating
anim_data = anim_interpolate(plot_data)
# create animation object by repeatedly invoking animate_pattern()
ani = FuncAnimation(plt.gcf(),
func=animate_pattern,
fargs=[anim_data],
frames=len(anim_data[0]),
init_func=partial(init_pattern,plot_data,settings),
interval=1000/smooth,
repeat=True)
return ani
if __name__ == "__main__":
with open("settings.json",mode="r") as file:
settings = json.load(file)
from hex_draw import convert_to_points
print("Displaying test animation...")
plot_data = convert_to_points("qeewdweddw","northeast",settings)
ax = plt.figure(figsize=(4,4)).add_axes([0,0,1,1])
ax.set_aspect("equal")
ani = plot_animated(plot_data,settings)
plt.show()

Adding icon for node shape using networkx and pyvis (python)

I am new to netwrokx and pyvis and am making a small network to display the different shapes possible for each node. I managed to use all the shapes except for icons. I searched a lot but I couldn't find anything useful and the examples available did not work with my code I would appreciate it if anyone could help me figure this out.
here is my code:
import networkx as nx
import xlrd #used to access the external excel file
import pyvis
from pyvis.network import Network
import pandas as pd
import textwrap
df = pd.read_csv("Visualizer\Data\EECS2311\shapes.csv",encoding='cp1252')
G=nx.Graph()
nodes = []
p1 = df['person1']
p2 = df['person2']
p3 = df['person3']
p4 = df['person4']
p5 = df['person5']
p6 = df['person6']
p7 = df['person7']
p8 = df['person8']
p9 = df['person9']
p10 = df['person10']
p11 = df['person11']
p12 = df['person12']
p13 = df['person13']
p14 = df['person14']
data = zip(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14)
for e in data:
person1 = e[0]
G.add_node(person1, shape="ellipse")
person2 = e[1]
G.add_node(person2, shape="circle")
person3 = e[2]
G.add_node(person3, shape="database")
person4 = e[3]
G.add_node(person4, shape="box")
person5 = e[4]
G.add_node(person5, shape="text")
person6 = e[5]
G.add_node(person6, shape="image", image="https://image.shutterstock.com/image-vector/hello-funny-person-simple-cartoon-260nw-1311467669.jpg")
person7 = e[6]
G.add_node(person7, shape="circularImage", image="https://image.shutterstock.com/image-vector/hello-funny-person-simple-cartoon-260nw-1311467669.jpg")
person8 = e[7]
G.add_node(person8, shape="diamond")
person9 = e[8]
G.add_node(person9, shape="dot")
person10 = e[9]
G.add_node(person10, shape="star")
person11 = e[10]
G.add_node(person11, shape="triangle")
person12 = e[11]
G.add_node(person12, shape="triangleDown")
person13 = e[12]
G.add_node(person13, shape="square")
person14 = e[13]
G.add_node(person14, shape="icon", icon="https://image.shutterstock.com/image-vector/hello-funny-person-simple-cartoon-260nw-1311467669.jpg")
nodes.append((person1, person2))
nodes.append((person2, person3))
nodes.append((person3, person4))
nodes.append((person4, person5))
nodes.append((person5, person6))
nodes.append((person6, person7))
nodes.append((person7, person8))
nodes.append((person8, person9))
nodes.append((person9, person10))
nodes.append((person10, person11))
nodes.append((person11, person12))
nodes.append((person12, person13))
nodes.append((person13, person14))
options = {
"layout": {
"hierarchical": {
"enabled": True,
"levelSeparation": 300,
"nodeSpacing": 165,
"treeSpacing": 305,
"direction": "LR"
}
},
"physics": {
"hierarchicalRepulsion": {
"centralGravity": 0,
"nodeDistance": 110,
},
"minVelocity": 0.75,
"solver": "hierarchicalRepulsion"
}
}
G.add_edges_from(nodes)
G2 = Network(height="800px", width="100%", bgcolor="#222222", font_color="white", select_menu=True, filter_menu=True, directed=True)
G2.from_nx(G)
G2.options = options
neighbor_map = G2.get_adj_list()
for node in G2.nodes:
node["value"] = len(neighbor_map[node["id"]])
#to wrap long labels:
id_string = node["label"]
width = 20
wrapped_strings = textwrap.wrap(id_string, width)
wrapped_id ="";
for line in wrapped_strings:
wrapped_id = textwrap.fill(id_string, width)
node["label"] = wrapped_id
#G2.show_buttons()
G2.show("shapes.html")
and here is my .csv file:
person1,person2,person3,person4,person5,person6,person7,person8,person9,person10,person11,person12,person13,person14
ellipse, circle, database,box,text,image, circularImage,diamond,dot,star,triangle,triangleDown,square,icon
"ellipse shape displays label inside the shape. To use this simply set shape =""ellipse""","circle shape displays label inside the shape. To use this simply set shape =""circle""","database shape displays label inside the shape. To use this simply set shape =""database""","box shape displays label inside the shape. To use this simply set shape =""box""","only displays text. To use this simply set shape =""text""","image displays a image with label outside. To use set shape=""image"", image=""url"". Note: requires link to image","circularImage displays a circular image with label outside. To use set shape="" circularImage"", image=""url"". Note: requires link to image","diamond shape displays label outside the shape. To use this simply set shape =""diamond""","dot shape displays label outside the shape. To use this simply set shape =""dot""","star shape displays label outside the shape. To use this simply set shape =""star""","triangle shape displays label outside the shape. To use this simply set shape =""triangle""","triangleDown shape displays label outside the shape. To use this simply set shape =""triangleDown""","square shape displays label outside the shape. To use this simply set shape =""square""","icon displays a circular image with label outside. To use set shape="" icon"", image=""url"". Note: requires link to image"
ps. forgive the heading for the csv file :)
This doesn't answer your question, I just want to help you shrink your code so you can debug it more easily.
Use the DataFrame directly
You're doing a ton of extra work to get at your data, assigning to temporary variables, then zipping them together. They are already together! To loop over the things in row 0 of the DataFrame try this:
for item in df.loc[0]:
print(item)
There's also a function in NetworkX, nx.from_pandas_dataframe(), that will create a network directly from a DataFrame... but you can only add edge attributes with that, not node attributes.
Then again...
Maybe dn't even use a DataFrame
Pandas is a convenient way to load CSVs, but your data isn't all that well-suited to this data structure. A dict would be better. It's a kind of mapping, in your case from node names to a node attribute.
Fortunately, there's a fairly easy way to get a dict from your DataFrame:
df.T.to_dict()[0]
This 'transposes' the DataFrame (turns the rows into columns) then turns the result into a dict. Then the [0] gives you the only column in the data.
This way you can avoid needing to repeat all your data (the mapping from person number to symbol) in your code.
Then again...
Maybe don't even use a dictionary
Any time you are mapping from a continuous set of numbers to some other objects (like person1, person2, etc) you might as well just use a list. Everything is indexed by position, which is basically what you have already. So you could just store your data like ['ellipse', 'circle', 'dot'] etc.
Then again...
Maybe don't even store the data
It turns out all these symbols are already defined in matplotlib. Have a look at:
from matplotlib.lines import Line2D
Line2D.markers
It's a dictionary of all the markers! If you want to try all of them, then you can just use these, no need to define anything.
Use zip to add your edges
zip is great for combining two or more lists, or combining a list with itself but with some offset. You can step over the nodes and make edges like so:
nodes = list(G.nodes)
for u, v in zip(nodes, nodes[1:]):
G.add_edge(u, v)
General advice
Try to avoid using tools like pandas just to load data. In my experience, it often introduces a bunch of complexity you don't need.
Get something small and simple working before making it more complex, e.g. with URLs of images.
You can store dictionaries easily as JSON text files. Check out the json module.
Again, sorry for not directly answering your question. But I feel like all this should help get your code down to something that is much easier to debug.

How to I make a PyQtGraph scrolling graph clear the previous line within a loop

I wish to plot some data from an array with multiple columns, and would like each column to be a different line on the same scrolling graph. As there are many columns, I think it would make sense to plot them within a loop. I'd also like to plot a second scrolling graph with a single line.
I can get the single line graph to scroll correctly, but the graph containing the multiple lines over-plots from the updated array without clearing the previous lines.
How do I get the lines to clear within the for loop. I thought that setData, might do the clearing. Do I have to have a pg.QtGui.QApplication.processEvents() or something similar within the loop? I tried to add that call but had it no effect.
My code:
#Based on example from PyQtGraph documentation
import numpy as np
import pyqtgraph as pg
win = pg.GraphicsLayoutWidget(show=True)
win.setWindowTitle('pyqtgraph example: Scrolling Plots')
timer = pg.QtCore.QTimer()
plot_1 = win.addPlot()
plot_2 = win.addPlot()
data1 = np.random.normal(size=(300))
curve1 = plot_1.plot(data1)
data_2d = np.random.normal(size=(3,300))
def update_plot():
global data1, data_2d
data1[:-1] = data1[1:]
data1[-1] = np.random.normal()
curve1.setData(data1)
for idx, n in enumerate(data_2d):
n[:-1] = n[1:]
n[-1] = np.random.normal()
curve2 = plot_2.plot(n,pen=(idx))
curve2.setData(n)
#pg.QtGui.QApplication.processEvents() #Does nothing
timer = pg.QtCore.QTimer()
timer.timeout.connect(update_plot)
timer.start(50)
if __name__ == '__main__':
pg.exec()
You could clear the plot of all curves each time with .clear(), but that wouldn't be very performant. A better solution would be to keep all the curve objects around and call setData on them each time, like you're doing with the single-curve plot. E.g.
curves_2d = [plot_2.plot(pen=idx) for idx, n in enumerate(data_2d)]
# ... in update_plot
curves_2d[idx].setData(n)

Why does my code add an extra frame in front of my animation?

I've been coding a Python program that will take a list from a different program, take specific values from that list, and add them to a 2D list. This subsequently creates an animation, with one frame per sub-list within the 2D list. However, when it's animated (using Celluloid), an extra frame is added in front of it that displays a graph of every sub-list at once, which disrupts the animation.
The code I'm using is this:
#Imports the relevant parts of the external modules
from matplotlib import pyplot as plt
from celluloid import Camera
from main import projectileInfo, tickRate
displacements = [] #A 2D list of all displacements
for i in projectileInfo:
displacements.append([i[1], i[0]])
print(displacements)
fig = plt.figure()
camera = Camera(fig)
for j in displacements:
x = [j[0]]
y = [j[1]]
plt.plot(x,y, color = '000000', marker = '.') #Plots the data as black points
camera.snap()
ani = camera.animate(interval = 1000*tickRate, repeat = False)
plt.show()
The issue doesn't come up if I specify values for the animation within the code itself (e.g. displacements = [[1,1], [2,2], [3,3]], but it does if projectileInfo is specified within the program.
For reference, examples for projectileInfo and tickRate are provided:
projectileInfo = [[0, 0, 2.944881550342992, 0.5724269861296344, -1.0092420948384448, -0.03813290516155552, -12.052760210752101, -0.08473978924790115], [0.2944881550342992, 0.05724269861296344, 1.739605529267782, 0.5639530072048443, -0.35217721337929575, -0.03701225346578069, -10.592616029731769, -0.0822494521461793], [0.4684487079610774, 0.11363799933344787, 0.6803439262946049, 0.5557280619902264, -0.05386624698009846, -0.03594051938005718, -9.929702771066887, -0.0798678208445715], [0.5364831005905379, 0.1692108055324705, -0.3126263508120839, 0.5477412799057693, 0.011373937998969578, -0.0349148868178283, -9.784724582224511,
-0.07758863737295177], [0.5052204655093295, 0.22398493352304744, -1.291098809034535, 0.5399824161684741, 0.19398969267459468, -0.03393274001211678, -9.378911794056458, -0.07540608891581507], [0.376110584605876, 0.27798317513989484, -2.2289899884401807, 0.5324418072768926, 0.5781971273919331, -0.03299164661811001, -8.525117494684594, -0.07331477026246667], [0.1532115857618579, 0.3312273558675841, -3.0815017379086402, 0.5251103302506459, 1.1050566133054158, -0.0320893424586703, -7.354318637099077, -0.07130964990815623], [-0.15493858802900617, 0.3837383888926487, -3.816933601618548, 0.5179793652598302, 1.6954652941177968, -0.031223717732420407, -6.042299346404897, -0.06938603940537869], [-0.536631948190861, 0.43553632541863174, -4.421163536259038, 0.5110407613192923, 2.2747457012945764, -0.030392804526055698, -4.75500955267872, -0.0675395656134571], [-0.9787483018167649, 0.486640401550561, -4.89666449152691, 0.5042868047579466, 2.7903609807178054, -0.029594765491590475, -3.6091978206270996, -0.06576614553686772], [-1.4684147509694558, 0.5370690820263556, -5.25758427358962, 0.4977101902042599,
3.216860139839751, -0.02882788356578406, -2.6614219114672206, -0.06406196347952013], [-1.9941731783284178, 0.5868401010467816, -5.523726464736342, 0.49130399385630785, 3.5507821034099836, -0.028090552623374627, -1.9193731035333705, -0.062423450274165834], [-2.546545824802052, 0.6359705004324124, -5.715663775089679, 0.48506164882889125, 3.801833041871401, -0.027381268968280633, -1.3614821291746655, -0.06084726437395696], [-3.11811220231102, 0.6844766653153016, -5.851811988007145, 0.47897692239149553, 3.985110999814779, -0.026698623577869795, -0.9541977781893805, -0.05933027461748843]]
tickRate = 0.05
I haven't managed to reproduce the problem in any test program, not even within the same virtual environment as the original code.

Modifying matplotlib checkbutton

I wrote a code to display live feed of analog data. The code uses pyfirmata to define pins and pull readings. I've set the funcanimation to pull all 12 channels when the port is open. Currently, matplotlib checkbutton is used to show/hide live feed of the channels.
I'd like to manipulate the matplotlib checkbutton so that only the channels that are checked are actually read instead of just being hidden.
The matplotlib widget module is a little too sophisticated for me to break down to a level where I can modify it. What I'd like to do is write a true/false status on each index depending on its visibility then put a nested if statements in the funcanimation to read only the visible lines. I'd appreciate if anyone could share me a sample code to allow me to do that.
Here is a segment of my code:
##check buttons
lines = [ln0, ln1, ln2, ln3, ln4, ln5, ln6, ln7, ln8, ln9, ln10, ln11]
labels = [str(ln0.get_label()) for ln0 in lines]
visibility = [ln0.get_visible() for ln0 in lines]
check = CheckButtons(ax1, labels, visibility)
for i, c in enumerate(colour):
check.labels[i].set_color(c)
def func(label):
index = labels.index(label)
lines[index].set_visible(not lines[index].get_visible())
check.on_clicked(func)
## define pins
a0 = due.get_pin('a:0:i')
a1 = due.get_pin('a:1:i')
a2 = due.get_pin('a:2:i')
a3 = ...
##funcanimation
def rt(i):
t.append(datetime.now())
if due.is_open == True:
T0.append(round(a0.read()*3.3/0.005, 1))
T1.append(round(a1.read()*3.3/0.005, 1))
...
Here is the graph and checkbuttons when run:
click here
Thanks,
I figured it out. There is a get_status function embedded in the matplotlib widget which returns a tuple of trues and falses to indicate the status of check buttons. I used this to write a nested if statements in the funcanimation so that only checked ones are read.

Categories

Resources