Trying to get an animation of a rotating arrow in a Jupyter notebook.
Can't get the window size and circle display correct.
I'm trying to get an animation of a rotating arrow in matplotlib. This is part of a jupyter engineering mechanics book I'm building for my students.
The idea of the question is that the animation shows what the two dimensional force balance is of multiple vectors on a node (the black dot in the code).
The animation is based on the following three sources:
1) Drawing a shape
2) Matplotlib animation
3) Arrow animation
get_ipython().run_line_magic('matplotlib', 'inline')
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.patches as patches
from matplotlib import animation, rc
from IPython.display import HTML
from math import degrees,radians,cos,sin,atan,acos,sqrt
# Create figure
fig, ax = plt.subplots()
# Axes labels and title are established
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_ylim(-100,100) #<---- This window size is not displayed
ax.set_xlim(-100,100) #<---- This window size is not displayed
ax.set_aspect('equal', adjustable='box')
#the circle
circle = plt.Circle((0, 0), radius=10, fc='black')
plt.gca().add_patch(circle) #<---- The circle is not displayed
#arrow1 (more arrows will me added)
arrow1x=[]
arrow1y=[]
arrow1dx=[]
arrow1dy=[]
for t in range(1000):
if t <= 250:
arrow1x.append(0)
arrow1y.append(0)
arrow1dx.append(t/250*100)
arrow1dy.append(0)
elif t <= 500:
arrow1x.append(0)
arrow1y.append(0)
arrow1dx.append(100)
arrow1dy.append(0)
elif t <= 750:
arrow1x.append(0)
arrow1y.append(0)
arrow1dx.append(100*cos(radians((t-500)/250*180.)))
arrow1dy.append(100*sin(radians((t-500)/250*180.)))
else:
arrow1x.append(0)
arrow1y.append(0)
arrow1dx.append((100-100*(t-750)/250)*-sin(radians((t-750)/250*180.)))
arrow1dy.append((100-100*(t-750)/250)*-sin(radians((t-750)/250*180.)))
patch = patches.Arrow(arrow1x[0], arrow1y[0], arrow1dx[0], arrow1dy[0])
#the animation (I have no idea how this works:)
def init():
ax.add_patch(patch)
return patch,
def animate(t):
ax.clear()
patch = plt.Arrow(arrow1x[t], arrow1y[t], arrow1dx[t], arrow1dy[t])
ax.add_patch(patch)
return patch,
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=1000, interval=20,
blit=True)
HTML(anim.to_jshtml())
As a result of this code I would like to see a square screen with range (-100 x 100,-100 y 100), the black node and the arrow.
What I'm seeing is a square screen (0 x 1,0 y 1), the rotating arrow, and no black dot.
There is no error output in jupyter which makes this really difficult to follow. Additionally the code takes really long to compile, which is also something that is not desired for a webpage, if this keeps taking so long I think i should look in a pre-compiled image (any tips for that perhaps ?).
Thus for some reason the window size and the dot are not adopted, but as far as I'm seeing the code from the sources is adopted as depicted on the webpages.
You took inappropriate part of "Arrow animation". Since you have static elements on your plot, you don't want to fully clear your ax: you should remove one patch during execution of animate function. Just replace ax.clear() with the next lines:
global patch
ax.patches.remove(patch)
Related
I edited this post in order to ask a more clear question.
Starting from a figure plot generate by Matplotlib like this :
Bitmap data is returned by a function:
from PIL import Image
from matplotlib import pyplot as plt
def fig2rgb ( fig ):
fig.tight_layout()
fig.canvas.draw ( )
pil_fig=Image.frombytes('RGB',
fig.canvas.get_width_height(),
fig.canvas.tostring_rgb() )
return pil_fig
figure = plt.etcetera ...
...
fig_datas=fig2rgb (figure)
I have another rectangular PIL image with definite dimension (height=a and width=b) which can be inclued in PIL figure generated before.
How can we find an available rectangular zone with those finite dimensions in the whitespace of figure plot ?
The final result should be :
My initial hypothesis was to use a function that returns pixel coordinates of white space in figure and try to find x,y in them:
def getWhite_coords(rgb):
white_positions = [[x,y] for x in range(rgb.size[0]) for y in range(rgb.size[1]) \
if rgb.getdata()[x+y*rgb.size[0]] == (255,255,255)]
return np.array(white_positions)
white_space=getWhite_coords(fig_datas)
for y in range(a):
if all([x,y] in white_space for x in range(b)):
...
break
This method has not shown results for my aim.
All this presented above is trying to simulate what legend box does when its argument loc="best" is setted.
Do you have any proposals?
Thanks in advance for the reply
I am trying to draw a scatter plot with a point that moves based on a parameter adjusted by a slider. I need the parameter to be the position in a list of the coordinates. I have it so the scatter plot gets drawn and I can manually move the point by change the position, but when I try to implement the slider it is displayed, but can not be drug to update the plot. Any help would be great. What I have so far is below. Thanks.
%pylab
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import animation
def on_change(val):
p=int(val)/1
def chart():
x=[0,0.5,1]
y=[0,0.5,1]
z=[0,0.5,1]
p=0
fig = pyplot.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot([0,0,0,0,0,1,1,0,0,1,1,1,1,1,1,0],[0,1,1,0,0,0,0,0,1,1,0,0,1,1,1,1],[0,0,1,1,0,0,1,1,1,1,1,0,0,1,0,0],c='black',zorder=10)
ax.scatter(x[p],y[p],z[p],zorder=0)
ax.set_xlabel('Width')
ax.set_ylabel('Depth')
ax.set_zlabel('Height')
slider_ax = plt.axes([0.15, 0.05, 0.7, 0.02])
slider = Slider(slider_ax, "min", 0, 2, valinit=1, color='blue')
pyplot.show()
chart()
You have to keep a reference to the Slider object around or it (and it's call backs) get garbage collected. See long discussion at https://github.com/matplotlib/matplotlib/issues/3105.
The documentation on this has been clarified for 1.4.
I'm trying to accomplish something hopefully pretty basic. Hoping for a few pointers!
I want to draw two circles: one that stays on the screen, and one that iteratively grows larger. For the one that changes, I essentially want to use a loop to draw and erase, so that it will draw a larger circle on each iteration (accomplishing an effect where it appears that a second, animated circle is growing). Below is what I've managed to work out so far. It's drawing the second circle progressively larger with each loop, but not erasing.
import matplotlib as plt
plt.use('TkAgg')
import matplotlib.pyplot as plt
#Initialize a variable
CHANGE = 0.3
#Make static 1st circle
circle1 = plt.Circle((0.5, 0.5), 0.2, color='white')
fig, ax = plt.subplots()
plt.axis('off')
ax.add_artist(circle1)
fig.set_facecolor("black")
#Animate dynamic 2nd circle
def frange(start, stop, step):
i = start
while i < stop:
yield i
i += step
for step in frange(0, .6, .01):
circle2 = plt.Circle((0.5, 0.5), CHANGE, color='gray', fill=False)
ax.add_artist(circle2)
plt.draw()
plt.pause(.001)
#plt.cla( )
CHANGE = CHANGE + step
Note: I played around with adding plt.cla() into the loop. While I was able to get it to erase/redraw circle 2 by doing that, it seems to draw over the original circle 1, versus keeping both visible in the same plot.
~~~~~~~~~~~~~
A) What it should like on each loop:
B) What it's looking like when not erasing:
(Using Python3.6.5 via PycharmCE)
Instead of multiple calls to add_artist and remove(), you should simply call the the Circle class's set_radius method. This will be more performant.
Connecting the dots, based on ImportanceOfBeingEarnest's comment, for any newbies out there like me:
Added the following starred line of code into the loop:
for step in frange(0, .6, .01):
circle2 = plt.Circle((0.5, 0.5), CHANGE, color='gray', fill=False)
ax.add_artist(circle2)
plt.draw()
plt.pause(.001)
**circle2.remove()**
CHANGE = CHANGE + step
This post is similar to this one (Change Marker Shapes in Plotly .js), but I can't seem to get anything to work in python. First off, I am trying to make a multi-line graph (which I have done in both plt and plotly...code below), but being colorblind (which I am) I can't often tell what I am looking in plotly because the markers are always a circle (even though the label is included, it sometimes gets cut off (i.e., when the labels are too long) and I can't figure out what I'm looking at). The plotly/cufflinks graphs are much better in terms of being interactive and since I do a lot of data presentations, this will be my preferred method going forward if I can figure out how to change the markers for each line.
I am using Jupyter Notebook (version: 5.4.0) and Python (version 3.6.4)
Screenshot of the dummy_data file.
dummy_data_screenshot
In matplotlib, I did the following to get the output attached (note the different shape markers):
import matplotlib.pyplot as plt
import matplotlib as mpl ##(version: 2.1.2)
import pandas as pd ##(version: 0.22.0)
import numpy as np ##(version: 1.14.0)
import plotly.graph_objs as go
from plotly.offline import download_plotlyjs,init_notebook_mode,plot,iplot
import cufflinks as cf ##(version: 0.12.1)
init_notebook_mode(connected=True)
cf.go_offline()
%matplotlib notebook
df = pd.read_csv("desktop\dummy_data.csv")
fx = df.groupby(['studyarm', 'visit'])\
['totdiffic_chg'].mean().unstack('studyarm').drop(['02_UNSCH','ZEOS'])
valid_markers = ([item[0] for item in
mpl.markers.MarkerStyle.markers.items() if
item[1] is not 'nothing' and not item[1].startswith('tick')
and not item[1].startswith('caret')])
markers = np.random.choice(valid_markers, df.shape[1], replace=False)
ax = fx.plot(kind = 'line', linestyle='-')
for i, line in enumerate(ax.get_lines()):
line.set_marker(markers[i])
ax.legend(loc='best')
ax.set_xticklabels(df.index, rotation=45)
plt.title('Some Made Up Data')
plt.ylabel('Score', fontsize=14)
plt.autoscale(enable=True, axis='x', tight=True)
plt.tight_layout()
plt_image_dummy_data
I used the code below and it created the graph via plotly/cufflinks:
fx.iplot(kind='line', yTitle='Score', title='Some Made Up Data',
mode=markers, filename='cufflinks/simple-line')
plotly_image_dummy_data
I have searched the web for the last few days and I can see many options to change the marker color, opacity, etc., etc., but I can't seem to figure out a way to automatically and randomly change the shape of the markers OR to manually change each individual line to a separate marker shape.
I am sure this is a simple fix, but I can't figure it out. Any help (or nudge in the right direction) would be very much appreciated.!
You can specify the shape for scatter plots using the symbol property, like bellow:
Scatter(x = ..., y = ..., mode = 'lines+markers',
marker = dict(size = 10, symbol = 1, ...))
For example:
0 gives circles
1 gives squares
3 gives '+' signs
5 gives triangles, etc.
Have a look at the 'symbol' entry in Plotly's doc here: https://plot.ly/python/reference/#box-marker-symbol
I am developing a simple graph visualizer using networkX and Matplotlib in Python. I also have some buttons plotted with text in them. As a whole the design is responsive which means that the graph and the buttons scale when I resize the window. However, the text size remains the same which makes the whole visualizer look very bad when not resized enough. Do you know how I can make the text also responsive?
Thank you in advance!!!
You update the fontsize of a matplotlib.text.Text using text.set_fontsize(). You can use a "resize_event" to call a function that sets a new fontsize. In order to do this with every text in a plot, it might be helpful to define a class that stores initial figure height and fontsizes and updates the fontsizes once the figure is resized, scaled by the new figure height divided by the initial one.
You may then also define a minimal readable fontsize, below which the text should not be resized.
A full example:
import matplotlib.pyplot as plt
import numpy as np
class TextResizer():
def __init__(self, texts, fig=None, minimal=4):
if not fig: fig = plt.gcf()
self.fig=fig
self.texts = texts
self.fontsizes = [t.get_fontsize() for t in self.texts]
_, self.windowheight = fig.get_size_inches()*fig.dpi
self.minimal= minimal
def __call__(self, event=None):
scale = event.height / self.windowheight
for i in range(len(self.texts)):
newsize = np.max([int(self.fontsizes[i]*scale), self.minimal])
self.texts[i].set_fontsize(newsize)
fontsize=11
text = plt.text(0.7, 0.6, "Some text", fontsize=fontsize,
bbox={'facecolor':'skyblue', 'alpha':0.5, 'pad':10})
cid = plt.gcf().canvas.mpl_connect("resize_event", TextResizer([text]))
plt.show()