pan buttons + jump to location in matplotlib - python

I am creating a PyQt applicaton with a matplotlib figure containing 4 subplots (screenshot of the figure):
I would like to create 2 buttons "pan left" and "pan right", which will pan the 4 subplots at a selected rate on the x axis.
I would also like to include a button in the GUI - "jump to location", which will read a value from a textbox in the GUI and jump to that location on the x axis - on all 4 subplots.
I haven't found a way to manipulate the existing pan feature in these ways.
Any suggestions would be appreciated. Thanks.

OK,
I ended up using a much simpler solution than I thought I would.
attached here is the code to my solution:
def jump_to_loc(self):
global dataArrays
for j in range(len(dataArrays)):
ax = self.figure.axes[j]
yBoundary = self.figure.yBoundaries[j]
ax.axis([self.jmpToLocSpinner.value(), self.jmpToLocSpinner.value()+100, yBoundary[0], yBoundary[1]])
self.figure.draw()
def pan(self, dir):
global dataArrays
dir_val = 0
if dir == 'left':
dir_val = 10
else:
dir_val = -10
for j in range(len(dataArrays)):
ax = self.figure.axes[j]
yBoundary = self.figure.yBoundaries[j]
x_axis = ax.get_xlim()
ax.axis([x_axis[0] + dir_val, x_axis[1] + dir_val, yBoundary[0], yBoundary[1]])
self.figure.draw()
def pan_left(self):
self.pan('right')
def pan_right(self):
self.pan('left')
I am basically utilizing the 'axis' function in the axes component, in order to set the x axis limits.

Related

How do you set the coordinates of added annotations on a figure when the x axis is categorical?

I have a plot that I have made which has two different categories that is subdvided into three different groups. I have made calculations of the mean and median for each of these groups, but when I try to add annotate the figures with these numbers, they end up printing on top of each other, when I want each figure within the plot to be annotated with its respective mean and median.
So my code to make this plot currently looks like this:
fig = px.violin(CVs,
y="cv %",
x="group",
color="method",
box=True,
points=False,
hover_data=CVs.columns)
for i in CVs['method'].unique():
for j in CVs['group'].unique():
mean, median = np.round(CVs.loc[CVs['method']==i].agg({'cv %':['mean', 'median']}), 2)['cv %'].values
fig.add_annotation(x=j, y=0,
yshift=-65,
text="Mean: {}%".format(mean),
font=dict(size=10),
showarrow=False)
fig.add_annotation(x=j, y=0,
yshift=-75,
text="Median: {}%".format(median),
font=dict(size=10),
showarrow=False)
fig.update_traces(meanline_visible=True)
fig.update_layout(template='plotly_white', yaxis_zeroline=False, height=fig_height, width=fig_width)
iplot(fig)
From what I have read in the documentation (https://plotly.com/python/text-and-annotations/), it seems like you need indicate the coordinates of the added annotation using the parameters x and y.
I have tried to adhere to these parameters by setting y to 0 (since the y axis is numerical), and setting x to the pertinent group along the x axis (which is a categorical). However, as one can tell from the plot above, this doesn't seem to work. I have also tried setting x to a value that increments with each iteration of the for loop, but all the values I have tried (e.g. 1, 10, 0.1) haven't worked, the annotations keep printing on top of each other, just at different places along the x axis.
I want to have one set of annotations under each figure. Does anyone know how I can set this up?
Based on what you used (yshift) to adjust the annotation, I have done the same using xshift to move each of the labels below their respective plot. Note that you have fig_height and fig_width which was not provided, so I let plotly choose the size. You may need to adjust the offset a bit if figure is different. Hope this works.
CVs = px.data.tips() ##Used tips db
CVs.rename(columns={'sex': 'group', 'day':'method', 'total_bill': 'cv %'}, inplace=True) ##Replaced to names you have
CVs = CVs[CVs.method != 'Thur'] ##Removed one as there were 4 days in tips
fig = px.violin(CVs,
y="cv %",
x="group",
color="method",
box=True,
points=False,
hover_data=CVs.columns)
x_shift = -100 ##Start at -100 to the left of the j location
for i in CVs['method'].unique():
for j in CVs['group'].unique():
mean, median = np.round(CVs.loc[CVs['method']==i].agg({'cv %':['mean', 'median']}), 2)['cv %'].values
fig.add_annotation(x=j, y=0,
yshift=-65, xshift = x_shift,
text="Mean: {}%".format(mean),
font=dict(size=10),
showarrow=False)
fig.add_annotation(x=j, y=0,
yshift=-75, xshift = x_shift,
text="Median: {}%".format(median),
font=dict(size=10),
showarrow=False)
x_shift = x_shift + 100 ##After each entry (healthy/sick in your case), add 100
fig.update_traces(meanline_visible=True)
fig.update_layout(template='plotly_white', yaxis_zeroline=False)#, height=fig_height, width=fig_width)
#iplot(fig)
Plot

Problem with LivePlots in Tkinter (Python)

I'm still quite new to Python, so please do not be too harsh with me.
I'm trying to implement a live plot in a GUI via Tkinter.
With the help of some example codes and google I got it working.
Now I want to erase/clear the Old Plot with click on a Button.
However I get a raise value error when trying to do so.
in the init I initialize one Plot like this:
self.x = 0
self.i = 0
self.delta_i = 1
self.xy_data = []
self.figure = pyplot.figure()
self.figure.set_size_inches((12,7), forward=True)
self.subplot = self.figure.add_subplot(221)
self.line,=self.subplot.plot([],[], color="blue")
self.line2, = self.subplot.plot([],[], color="red")
In the actual drawing function (which is executed over and over for a specific amount of time) I'm doing this:
self.Timer = Tdiff
self.FixTemp=TargetTemp
self.ActualTemp =Value1
self.xy_data += [[self.Timer,self.ActualTemp]]
self.xy2_data += [[self.Timer,self.FixTemp]]
self.subplot.lines.remove(self.line)
self.subplot.lines.remove(self.line2)
self.line, = self.subplot.plot(
[row[0] for row in self.xy_data[0::self.delta_i]],
[row[1] for row in self.xy_data[0::self.delta_i]],
color="blue")
self.line2, = self.subplot.plot(
[row[0] for row in self.xy2_data[0::self.delta_i]],
[row[1] for row in self.xy2_data[0::self.delta_i]],
color="red")
In the Button Function I have tried to do:
self.subplot.cla() # But that clears even the Subplot title and x/y labels. As I have in total 3
subplots it would not be very code effective to initialize them again, I think.
self.subplot.lines.remove(self.line) and self.subplot.lines.remove(self.line2) # But I'm getting a rase Value Error
I would be glad if someone could help out here.
Thank you

Continue program after plt.show() command without closing all windows

I know that there are plenty of similar topics in StackOverflow but none of the answers solved my specific problem.
First of all, I am trying plot 2-dimensional data points from different classes with scatter command to the figure. The program uses matplotlib events like button_press_event and motion_notify_event and I assume that right after event commands plt.show() should be used. The main problem is that once some tasks are done with events (i.e. labeling), I want to update the whole figure but plt.show() is blocking the option to continue the program. Actually plt.show() should block the program until the user of program decides to go forward. Is there any solution to control this blocking attribute?
I have tried plt.ion(), plt.ioff(), plt.show(block=False), plt.draw(), plt.pause(0.001), global variables with while loop etc. without success. The only way the program works somehow correctly is when all the figures are closed inside of button_press_event when so called forward condition is met but it is not very user-friendly solution if all figures are closed every time data points are updated.
Here is the the glimpse of the code:
def draw_original_figure(random_sample, random_sample_label, random_sample_image, X_train_chosen, y_train_chosen, images_train_chosen, classes, accuracy, embedding, model, h=0.05):
global points1, im1, s_im1, xybox, x1, y1, fig1, classes1, points_to_plot, y_train_chosen1, accuracy1, random_sample1, embedding1, y_train_chosen1, h1, random_sample_label1, result1
fig1 = plt.gcf()
.
.
.
original_figure_plot()
fig1.canvas.mpl_connect('motion_notify_event', hover)
fig1.canvas.mpl_connect('button_press_event', click)
plt.show()
def hover(event):
# if the mouse is over the scatter points
if points1.contains(event)[0]:
# find out the index within the array from the event
inds = points1.contains(event)[1]["ind"]
ind = inds[0]
# get the figure size
w,h = fig1.get_size_inches()*fig1.dpi
ws = (event.x > w/2.)*-1 + (event.x <= w/2.)
hs = (event.y > h/2.)*-1 + (event.y <= h/2.)
# if event occurs in the top or right quadrant of the figure,
# change the annotation box position relative to mouse.
ab1.xybox = (xybox[0]*ws, xybox[1]*hs)
# make annotation box visible
ab1.set_visible(True)
# place it at the position of the hovered scatter point
ab1.xy =(x1[ind], y1[ind])
# set the image corresponding to that point
im1.set_data(s_im1[ind,:,:])
else:
#if the mouse is not over a scatter point
ab1.set_visible(False)
fig1.canvas.draw_idle()
def click(event):
# if the mouse is over the scatter points
if points1.contains(event)[0]:
# find out the index within the array from the event
inds = points1.contains(event)[1]["ind"]
ind = inds[0]
# if one specific point is chosen
if ind == len(x1)-1:
plt.scatter(x1[ind], y1[ind], s=25, marker='x', c='#556B2F')
q = question(True, ind)
# do nothing
if q == "":
original_figure_plot()
# quit the program
elif q == "q":
exit()
# continue the program without updating labels
elif q == "n":
result1 = copy.deepcopy(y_train_chosen1)
plt.close("all")
# continue the program after labels are updated
else:
result1 = copy.deepcopy(y_train_chosen1)
result1 = np.append(result1, [int(q)], axis=0)
plt.close("all")
else:
# if anyone else point is chosen
plt.scatter(x1[ind], y1[ind], s=8, c='k')
q = question(False, ind)
# do nothing
if q == "":
original_figure_plot()
# quit the program
elif q == "q":
exit()
# update labels
else:
y_train_chosen1[ind] = int(q)
original_figure_plot()
fig1.canvas.draw_idle()
Probably it is better to use for example other libraries like plotly or dash but is it really true that you cannot update figure without closing it if you are using matplotlib events?? I can provide all the project files but I think so that if there is a solution, it should be done inside of these functions.
It took the whole day to find the answer but here it is!
I use now plt.show() in interactive-mode with command plt.ion() and do blocking manually with commands fig.canvas.start_event_loop() and fig.canvas.stop_event_loop(). To be honest, it was surprisingly difficult to find the solution to this problem but the lesson is learned.
matplotlib figure does not continue program flow after close event triggered inside tk app

pyqtgraph plotwidget multiple Y axis plots in wrong area

I have an embedded widget in a QT5 gui connected via PlotWidget.
I am trying to plot 2 streams of live data Voltage (self.p1) & Current (self.p2). Voltage on the left axis & Current on the right. So far I have each data stream associated with its relevant axis.
However my problem is that the Current plot (self.p2) is not in the correct area of the display. This particular plot appears in the upper left hand corner of the widget, it appears before the LHD axis. Its best to view the image to view the problem.
View Me
.
I know the problem lies in the setup function & self.p2 (Current) is being placed in the wrong location but my searching hasn't produced an answer.
Could someone please help?
Code used to generate graph, is called once on start up:
def pg_plot_setup(self): # not working still on left axis
self.p1 = self.graphicsView.plotItem
# x axis
self.p1.setLabel('bottom', 'Time', units='s', color='g', **{'font-size':'12pt'})
self.p1.getAxis('bottom').setPen(pg.mkPen(color='g', width=3))
# Y1 axis
self.p1.setLabel('left', 'Voltage', units='V', color='r', **{'font-size':'12pt'})
self.p1.getAxis('left').setPen(pg.mkPen(color='r', width=3))
self.p2 = pg.ViewBox()
self.p1.showAxis('right')
self.p1.scene().addItem(self.p2)
self.p1.getAxis('right').linkToView(self.p2)
self.p2.setXLink(self.p1)
# Y2 axis
self.p1.setLabel('right', 'Current', units="A", color='c', **{'font-size':'12pt'}) #<font>Ω</font>
self.p1.getAxis('right').setPen(pg.mkPen(color='c', width=3))
and code used to update display, is called via QTimer:
def update_graph_plot(self):
start = time.time()
X = np.asarray(self.graph_X, dtype=np.float32)
Y1 = np.asarray(self.graph_Y1, dtype=np.float32)
Y2 = np.asarray(self.graph_Y2, dtype=np.float32)
pen1=pg.mkPen(color='r',width=1.0)
pen2=pg.mkPen(color='c',width=1.0)
self.p1.plot(X,Y1,pen=pen1, name="V", clear=True)
self.p2.addItem(pg.PlotCurveItem(X,Y2,pen=pen2, name="I"))
I found the answer buried in here MultiplePlotAxes.py
adding this self.p2.setGeometry(self.p1.vb.sceneBoundingRect()) to function 'update_graph_plot' adjusts the size of viewbox each time the scene is updated, but it has to be in the update loop.
or add this self.p1.vb.sigResized.connect(self.updateViews) to function 'pg_plot_setup' as part of the setup, which shall then automatically call this function
def updateViews(self):
self.p2.setGeometry(self.p1.vb.sceneBoundingRect())
to resize viewbox (self.p2) each time self.p1 updates.

how to explicitly plot y axis with python

I used pp.yscale('log') in my python script to plot a figure with y ticks shown in log scale. However, in the figure, the y axis does not appear. Is there any way to explicitly show y axis in python?
...
leg = pp.legend( series_labels, loc='upper right' )
pp.axis([-0.5, x_len-0.5, 0, max_y*1.1])
configurable_xlabel = x_label + '(unit)'
pp.xlabel(configurable_xlabel)
configurable_ylabel = metrics[metric_idx] + '(unit)'
pp.ylabel(configurable_ylabel)
configurable_scaling = 2
xticklabels = []
for idx in xrange(0,x_len):
if idx % configurable_scaling == 0:
xticklabels.append(x_data[idx])
else:
xticklabels.append('');
pp.axes().set_xticks(xrange(0,x_len));
pp.axes().set_xticklabels(xticklabels[0:len(xticklabels)])
pp.yscale('log')
...
I suggested on a duplicate of this question that it might be due to logarithmic scales having no 0 and suggested further that one might be able to move the axis to another number. I have no way to check this but Richard stated that this is a solution. I hope it is :-}

Categories

Resources