this is the first time i ask a question in here, so i hope i can ask it correctly any feedback on clarity is also appriciated.
I am forced to use matplotlib's plot function in the code i am currently writing, due to the datastructure i am working with. But it does not do well with plotting in loops. What i aim to do, with my plot, is to be able to modify a background window determined by the user, and either accept or reject the output. But as i understand there is some conflict between matplotlibs interactive function and the use of a loop. I am relativly new to python, so the code might not be the prittiest, but useually i gets the job done.
I am however at a complete loss for the particular problem. I have seen similair problems on the internet, which have been solved with plt.pause('timeinterval') but this is not an option in my case. or atleast i cannot see how i can use it, since i want to wait for user input. I have also tried plt.waitforbuttonpress() but this is not very intuetive, i cannot choose which button it should wait for.
I guess a third option is to extract the data from the format hyperspy uses and then make a canvas of my own, which forfills my needs, but this is very tidious for me, due to my lack of experience.
Do anyone have any alternative ways of producing a plot, preferably with matplotlib such that i can achive what i am trying?
By the way, i have also tried turning off interactive mode, and this does not do the trick unfurtunatly.
Some information about the specs: This is running on a windows pc, using jupyterlab and python 3.10
I hope my dilemma is clear.
def set_background(self):
"""
This function is ment to let the user define the background of each element, and then save the background for later use
if working with multiple images of particles with the same composition.
This function could be expanded to have interactive features so background would be clickable.
"""
self.Background_tree = {}
elements_in_sample = deepcopy(self.Data.metadata.Sample['elements'])
Xray_in_sample = self.weighted_Xray_line_list
data_sample = deepcopy(self.Data)
integration_window = 1.3
for element in elements_in_sample:
data_sample.set_elements(elements=[element])
for xray_line in (self.Data.metadata.Sample["xray_lines"]):
if element in xray_line:
data_sample.set_lines([xray_line])
background_points = input('please input the background points seperated by a space').split(' ')
background_window = list(map(float,background_points))
bw = data_sample.estimate_background_windows(background_window)
iw = data_sample.estimate_integration_windows(integration_window)
data_sample.sum().plot(True,bakcground_windows=background_window)
happy = input('are you happy with the result?')
if happy == 'y':
#self.Data.get_lines_intensity(xray_lines=[xray_line], background_windows=bw, integration_windows=iw)
self.Background_tree[element+"_"+xray_line] = bw
import pandas as pd
import numpy as np
import ipywidgets as wg
from ipywidgets import HBox, VBox
import matplotlib.pyplot as plt
from IPython.display import display
%matplotlib widget
a = np.arange(50)
b = np.random.rand(50) + a
c = np.sin(a)
d = np.cos(b)
df = pd.DataFrame({'a': a,
'b': b,
'c': c,
'd': d})
userinput1 = wg.Text(value="", placeholder='Type something', description='x axis')
userinput2 = wg.Text(value="", placeholder='Type something', description='y axis')
buttonplot = wg.Button(description='Plot', disabled=False, button_style='', tooltip='Click me',icon='check')
buttonout = wg.Output()
display(HBox((userinput1, userinput2, buttonplot)))
display(buttonout)
plt.close()
fig, ax = plt.subplots()
def on_click_event(change):
with buttonout:
x = (userinput1.value)
y = (userinput2.value)
ax.plot(df[x], df[y], label=f'{y}')
ax.legend()
buttonplot.on_click(on_click_event)
Output:
After user input and clicking the button:
More user input:
Does it satisfy your need or am I getting further away from your initial question?
Related
I tried to do the tutorial of McKay Johns on YT (reference to the Jupyter Notebook to see the data (https://github.com/mckayjohns/passmap/blob/main/Pass%20map%20tutorial.ipynb).
I understood everything but I wanted to do a little change. I wanted to change plt.plot(...) with:
plt.arrow(df['x'][x],df['y'][x], df['endX'][x] - df['x'][x], df['endY'][x]-df['y'][x],
shape='full', color='green')
But the problem is, I still can't see the arrows. I tried multiple changes but I've failed. So I'd like to ask you in the group.
Below you can see the code.
## Read in the data
df = pd.read_csv('...\Codes\Plotting_Passes\messibetis.csv')
#convert the data to match the mplsoccer statsbomb pitch
#to see how to create the pitch, watch the video here: https://www.youtube.com/watch?v=55k1mCRyd2k
df['x'] = df['x']*1.2
df['y'] = df['y']*.8
df['endX'] = df['endX']*1.2
df['endY'] = df['endY']*.8
# Set Base
fig ,ax = plt.subplots(figsize=(13.5,8))
# Change background color of base
fig.set_facecolor('#22312b')
# Change color of base inside
ax.patch.set_facecolor('#22312b')
#this is how we create the pitch
pitch = Pitch(pitch_type='statsbomb',
pitch_color='#22312b', line_color='#c7d5cc')
# Set the axes to our Base
pitch.draw(ax=ax)
# X-Achsen => 0 to 120
# Y-Achsen => 80 to 0
# Lösung: Y-Achse invertieren:
plt.gca().invert_yaxis()
#use a for loop to plot each pass
for x in range(len(df['x'])):
if df['outcome'][x] == 'Successful':
#plt.plot((df['x'][x],df['endX'][x]),(df['y'][x],df['endY'][x]),color='green')
plt.scatter(df['x'][x],df['y'][x],color='green')
**plt.arrow(df['x'][x],df['y'][x], df['endX'][x] - df['x'][x], df['endY'][x]-df['y'][x],
shape='full', color='green')** # Here is the problem!
if df['outcome'][x] == 'Unsuccessful':
plt.plot((df['x'][x],df['endX'][x]),(df['y'][x],df['endY'][x]),color='red')
plt.scatter(df['x'][x],df['y'][x],color='red')
plt.title('Messi Pass Map vs Real Betis',color='white',size=20)
It always shows:
The problem is that plt.arrow has default values for head_width and head_length, which are too small for your figure. I.e. it is drawing arrows, the arrow heads are just way too tiny to see them (even if you zoom out). E.g. try something as follows:
import pandas as pd
import matplotlib.pyplot as plt
from mplsoccer.pitch import Pitch
df = pd.read_csv('https://raw.githubusercontent.com/mckayjohns/passmap/main/messibetis.csv')
...
# create a dict for the colors to avoid repetitive code
colors = {'Successful':'green', 'Unsuccessful':'red'}
for x in range(len(df['x'])):
plt.scatter(df['x'][x],df['y'][x],color=colors[df.outcome[x]], marker=".")
plt.arrow(df['x'][x],df['y'][x], df['endX'][x] - df['x'][x],
df['endY'][x]-df['y'][x], color=colors[df.outcome[x]],
head_width=1, head_length=1, length_includes_head=True)
# setting `length_includes_head` to `True` ensures that the arrow head is
# *part* of the line, not added on top
plt.title('Messi Pass Map vs Real Betis',color='white',size=20)
Result:
Note that you can also use plt.annotate for this, passing specific props to the parameter arrowprops. E.g.:
import pandas as pd
import matplotlib.pyplot as plt
from mplsoccer.pitch import Pitch
df = pd.read_csv('https://raw.githubusercontent.com/mckayjohns/passmap/main/messibetis.csv')
...
# create a dict for the colors to avoid repetitive code
colors = {'Successful':'green', 'Unsuccessful':'red'}
for x in range(len(df['x'])):
plt.scatter(df['x'][x],df['y'][x],color=colors[df.outcome[x]], marker=".")
props= {'arrowstyle': '-|>,head_width=0.25,head_length=0.5',
'color': colors[df.outcome[x]]}
plt.annotate("", xy=(df['endX'][x],df['endY'][x]),
xytext=(df['x'][x],df['y'][x]), arrowprops=props)
plt.title('Messi Pass Map vs Real Betis',color='white',size=20)
Result (a bit sharper, if you ask me, but maybe some tweaking with params in plt.arrow can also achieve that):
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.
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.
Running jupyter notebook (python)
Plotting using Python Plotnine library
I plot and below the output graphic is annoying "ggplot2: (number)" output
Normally you would put a ; at the end of your notebook cell, but it doesn't seem to supress the annoying output text when i use Plotnine (but it does obviously work for matplotlib, etc)
Any ideas ?
The point is in calling draw() method with semicolon at the end.
Fully working example:
import pandas
from plotnine import *
from random import randint
# 100 random numbers
random_numbers = [randint(1, 100) for p in range(0, 100)]
# Create DataFrame
df = pd.DataFrame({'number': random_numbers})
# Draw plot
(
ggplot(df, aes(x='number')) +
geom_histogram(bins=20, na_rm=True) +
ggtitle('Histogram of random numbers') +
theme_light()
).draw();
The draw(); method didnt't work for me.
However, it did the trick:
warnings.filterwarnings( "ignore", module = "plotnine\..*" )
I've been looking into how to make plots against time on the x axis and have it pretty much sorted, with one strange quirk that makes me wonder whether I've run into a bug or (admittedly much more likely) am doing something I don't really understand.
Simply put, below is a simplified version of my program. If I put this in a .py file and execute it from an interpreter (ipython) I get a figure with an x axis with the year only, "2012", repeated a number of times, like this.
However, if I comment out the line (40) that sets the xticks manually, namely 'plt.xticks(tk)' and then run that exact command in the interpreter immediately after executing the script, it works great and my figure looks like this.
Similarly it also works if I just move that line to be after the savefig command in the script, that's to say to put it at the very end of the file. Of course in both cases only the figure drawn on screen will have the desired axis, and not the saved file. Why can't I set my x axis earlier?
Grateful for any insights, thanks in advance!
import matplotlib.pyplot as plt
import datetime
# define arrays for x, y and errors
x=[16.7,16.8,17.1,17.4]
y=[15,17,14,16]
e=[0.8,1.2,1.1,0.9]
xtn=[]
# convert x to datetime format
for t in x:
hours=int(t)
mins=int((t-int(t))*60)
secs=int(((t-hours)*60-mins)*60)
dt=datetime.datetime(2012,01,01,hours,mins,secs)
xtn.append(date2num(dt))
# set up plot
fig=plt.figure()
ax=fig.add_subplot(1,1,1)
# plot
ax.errorbar(xtn,y,yerr=e,fmt='+',elinewidth=2,capsize=0,color='k',ecolor='k')
# set x axis range
ax.xaxis_date()
t0=date2num(datetime.datetime(2012,01,01,16,35)) # x axis startpoint
t1=date2num(datetime.datetime(2012,01,01,17,35)) # x axis endpoint
plt.xlim(t0,t1)
# manually set xtick values
tk=[]
tk.append(date2num(datetime.datetime(2012,01,01,16,40)))
tk.append(date2num(datetime.datetime(2012,01,01,16,50)))
tk.append(date2num(datetime.datetime(2012,01,01,17,00)))
tk.append(date2num(datetime.datetime(2012,01,01,17,10)))
tk.append(date2num(datetime.datetime(2012,01,01,17,20)))
tk.append(date2num(datetime.datetime(2012,01,01,17,30)))
plt.xticks(tk)
plt.show()
# save to file
plt.savefig('savefile.png')
I don't think you need that call to xaxis_date(); since you are already providing the x-axis data in a format that matplotlib knows how to deal with. I also think there's something slightly wrong with your secs formula.
We can make use of matplotlib's built-in formatters and locators to:
set the major xticks to a regular interval (minutes, hours, days, etc.)
customize the display using a strftime formatting string
It appears that if a formatter is not specified, the default is to display the year; which is what you were seeing.
Try this out:
import datetime as dt
import matplotlib.pyplot as plt
from matplotlib.dates import DateFormatter, MinuteLocator
x = [16.7,16.8,17.1,17.4]
y = [15,17,14,16]
e = [0.8,1.2,1.1,0.9]
xtn = []
for t in x:
h = int(t)
m = int((t-int(t))*60)
xtn.append(dt.datetime.combine(dt.date(2012,1,1), dt.time(h,m)))
def larger_alim( alim ):
''' simple utility function to expand axis limits a bit '''
amin,amax = alim
arng = amax-amin
nmin = amin - 0.1 * arng
nmax = amax + 0.1 * arng
return nmin,nmax
plt.errorbar(xtn,y,yerr=e,fmt='+',elinewidth=2,capsize=0,color='k',ecolor='k')
plt.gca().xaxis.set_major_locator( MinuteLocator(byminute=range(0,60,10)) )
plt.gca().xaxis.set_major_formatter( DateFormatter('%H:%M:%S') )
plt.gca().set_xlim( larger_alim( plt.gca().get_xlim() ) )
plt.show()
Result:
FWIW the utility function larger_alim was originally written for this other question: Is there a way to tell matplotlib to loosen the zoom on the plotted data?