How to embed a matplotlib plot with annotation inside Gtk3 window - python-3.x

I trying a lot to place a matplotlib plot (that also uses annotation) to place inside gtk3 window. If there is no annotation, I can place the plot easily inside gtk3. But, I have messed up with how to do it while using annotation. I have tried this without much progress.
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from numpy import arange, pi, random, linspace
import matplotlib.cm as cm
from matplotlib.backends.backend_gtk3cairo import FigureCanvasGTK3Cairo as FigureCanvas
#WINDOW to embede matplotlib
x = np.random.rand(15)
y = np.random.rand(15)
p_window = Gtk.Window()
p_window.set_default_size(750,500)
p_header = Gtk.HeaderBar()
p_window.set_titlebar(p_header)
p_header.set_subtitle("Periodic Table")
p_header.set_title("column")
p_header.set_show_close_button(True)
c = np.random.randint(1,50,size=120)
norm = plt.Normalize(1,4)
cmap = plt.cm.RdYlGn
fig,ax = plt.subplots()
sc = plt.scatter(x,y)
plt.plot(x,y)
annot = ax.annotate("", xy=(0,0), xytext=(20,20),textcoords="offset points",
bbox=dict(boxstyle="round", fc="w"),
arrowprops=dict(arrowstyle="->"))
annot.set_visible(False)
def update_annot(ind):
pos = sc.get_offsets()[ind["ind"][0]]
annot.xy = pos
namel = "foo"
vall = "bar"
text = "{}, {}".format(namel[2:-2], vall[1:-1])
annot.set_text(text)
# annot.get_bbox_patch().set_facecolor(cmap(norm(c[ind["ind"][0]])))
annot.get_bbox_patch().set_alpha(0.4)
def hover(event):
vis = annot.get_visible()
if event.inaxes == ax:
cont, ind = sc.contains(event)
if cont:
update_annot(ind)
annot.set_visible(True)
fig.canvas.draw_idle()
else:
if vis:
annot.set_visible(False)
fig.canvas.draw_idle()
fig.canvas.mpl_connect("motion_notify_event", hover)
plt.show()
sw = Gtk.ScrolledWindow()
p_window.add(sw)
canvas = FigureCanvas(fig)
canvas.set_size_request(400,400)
sw.add_with_viewport(canvas)
p_window.show_all()
Gtk.main()
After removing pyplt
I have remove pyplt dependency as suggested, and got this:
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
import numpy as np
# import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from numpy import arange, pi, random, linspace
import matplotlib.cm as cm
from matplotlib.backends.backend_gtk3cairo import FigureCanvasGTK3Cairo as FigureCanvas
#WINDOW to embede matplotlib
x = np.random.rand(15)
y = np.random.rand(15)
p_window = Gtk.Window()
p_window.set_default_size(750,500)
p_header = Gtk.HeaderBar()
p_window.set_titlebar(p_header)
p_header.set_subtitle("Periodic Table")
p_header.set_title("column")
p_header.set_show_close_button(True)
c = np.random.randint(1,50,size=120)
fig = Figure(figsize=(10,6), dpi=100)
ax = fig.add_subplot(111)
ax.set_ylabel("column")
ax.set_xlabel("Z")
sc=ax.plot(x,y, "r-o")
print(type(fig))
annot = ax.annotate("", xy=(0,0), xytext=(20,20),textcoords="offset points",
bbox=dict(boxstyle="round", fc="w"),
arrowprops=dict(arrowstyle="->"))
annot.set_visible(False)
def update_annot(ind):
pos = sc.get_offsets()[ind["ind"][0]]
annot.xy = pos
namel = "foo"
vall = "bar"
text = "{}, {}".format(namel[2:-2], vall[1:-1])
print(text)
annot.set_text(text)
# annot.get_bbox_patch().set_facecolor(cmap(norm(c[ind["ind"][0]])))
annot.get_bbox_patch().set_alpha(0.4)
def hover(event):
vis = annot.get_visible()
if event.inaxes == ax:
cont, ind = sc.contains(event)
if cont:
update_annot(ind)
annot.set_visible(True)
fig.canvas.draw_idle()
else:
if vis:
annot.set_visible(False)
fig.canvas.draw_idle()
sw = Gtk.ScrolledWindow()
p_window.add(sw)
canvas = FigureCanvas(fig)
canvas.set_size_request(400,400)
fig.canvas.mpl_connect("motion_notify_event", hover)
sw.add_with_viewport(canvas)
p_window.show_all()
Gtk.main()
which is giving me error:
python3 pop.py
Traceback (most recent call last):
File "/usr/lib64/python3.6/site-packages/matplotlib/backends/backend_gtk3.py", line 268, in motion_notify_event
FigureCanvasBase.motion_notify_event(self, x, y, guiEvent=event)
File "/usr/lib64/python3.6/site-packages/matplotlib/backend_bases.py", line 1958, in motion_notify_event
self.callbacks.process(s, event)
File "/usr/lib64/python3.6/site-packages/matplotlib/cbook.py", line 549, in process
proxy(*args, **kwargs)
File "/usr/lib64/python3.6/site-packages/matplotlib/cbook.py", line 416, in __call__
return mtd(*args, **kwargs)
File "pop.py", line 51, in hover
cont, ind = sc.contains(event)
AttributeError: 'list' object has no attribute 'contains'

Related

Matplotlib Scatter plot interactivity not working

Until this morning I was able to display labels information when hovering the dots on a scatter plot.
Now, if I run the following code it does not display any error but the interactivity is not working and it looks like mplconnect or mlpcursors are completely ignored.
I've tried the same code under windows and Fedora.
Not understanding what's going on.
from matplotlib.pyplot import figure, show
import numpy as npy
from numpy.random import rand
x, y, c, s = rand(4, 100)
def onpick3(event):
ind = event.ind
print('onpick3 scatter:', ind, npy.take(x, ind), npy.take(y, ind))
fig = figure()
ax1 = fig.add_subplot(111)
col = ax1.scatter(x, y, 100*s, c, picker=True)
#fig.savefig('pscoll.eps')
fig.canvas.mpl_connect('pick_event', onpick3)
show()
Or
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(1)
x = np.random.rand(15)
y = np.random.rand(15)
names = np.array(list("ABCDEFGHIJKLMNO"))
c = np.random.randint(1,5,size=15)
norm = plt.Normalize(1,4)
cmap = plt.cm.RdYlGn
fig,ax = plt.subplots()
sc = plt.scatter(x,y,c=c, s=100, cmap=cmap, norm=norm)
annot = ax.annotate("", xy=(0,0), xytext=(20,20),textcoords="offset points",
bbox=dict(boxstyle="round", fc="w"),
arrowprops=dict(arrowstyle="->"))
annot.set_visible(False)
def update_annot(ind):
pos = sc.get_offsets()[ind["ind"][0]]
annot.xy = pos
text = "{}, {}".format(" ".join(list(map(str,ind["ind"]))),
" ".join([names[n] for n in ind["ind"]]))
annot.set_text(text)
annot.get_bbox_patch().set_facecolor(cmap(norm(c[ind["ind"][0]])))
annot.get_bbox_patch().set_alpha(0.4)
def hover(event):
vis = annot.get_visible()
if event.inaxes == ax:
cont, ind = sc.contains(event)
if cont:
update_annot(ind)
annot.set_visible(True)
fig.canvas.draw_idle()
else:
if vis:
annot.set_visible(False)
fig.canvas.draw_idle()
fig.canvas.mpl_connect("motion_notify_event", hover)
plt.show()
This is not my code, I've copied and pasted it from a website but the behavior is the same.
Plotly express solves the problem
import plotly.express as px
alpha = data[data['Ticker']==focus].V1
gamma = data[data['Ticker']==focus].V2
fig = px.scatter(data, x='V1', y='V2', color=Colors.Market_Cap, hover_data=["Ticker"] )
fig.add_shape(type="circle",
xref="x", yref="y",
x0=int(alpha-3), y0=int(gamma-3), x1=int(alpha+3), y1=int(gamma+3),
line_color="LightSeaGreen",
)
fig.show()

How to connect matplotlib cursor mouse_move object with slider value?

I have a figure where 2 axhlines move with mouse movement. I want to put a slider at the bottom where it will change the range of y-axis values covered by these axhlines.
enter image description here
I tried the following code. Problem is that the value of the slider changes but the mouse event object does not update.
Thanks
%matplotlib notebook
import pandas as pd
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
from matplotlib.widgets import Slider
np.random.seed(12345)
df = pd.DataFrame([np.random.normal(32000,200000,3650),
np.random.normal(43000,100000,3650),
np.random.normal(43500,140000,3650),
np.random.normal(48000,70000,3650)],
index=[1992,1993,1994,1995])
df=df.T
data=df.describe().T
data['error']=df.sem()
data['error_range']=df.sem()*1.96
fig, ax = plt.subplots()
def plot_bar(x,y,error,title,alpha_level=0.7):
ax.bar(x,y, yerr=error,
align='center', alpha=alpha_level,
error_kw=dict(ecolor='black', elinewidth=1, capsize=5, capthick=1))
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.set_title(title)
ax.xaxis.set_major_locator(ticker.FixedLocator(data.index))
ax.yaxis.set_major_formatter(ticker.StrMethodFormatter('{x:,.0f}'))
ax.set_ylim([-5000,55000])
ax.set_xlim([1990.5,1995.5])
ax.spines['bottom'].set_position(('data',0))
ax.spines['left'].set_position(('data',1991.4))
plt.tight_layout()
return (ax, ax.get_children()[1:5])
ax, barlist=plot_bar(x=data.index,y=data['mean'],error=data['error_range'],title='Even Harder Option', alpha_level=0.6)
fig.subplots_adjust(bottom=0.1)
axcolor = 'lightgoldenrodyellow'
range_slider = plt.axes([0.2, 0.05, 0.65, 0.03], facecolor=axcolor)
slider = Slider(range_slider, 'Range', 0, 55000, valinit=10000, valstep=100)
def update(val):
slider.val = slider.val
slider.on_changed(update)
class Cursor(object):
_df=None
_bl=None
def __init__(self, ax,data_F, bars, slider):
#global slider
self._df=data_F
self._bl=bars
self.ax = ax
self.lx1 = ax.axhline(color='b')
self.lx2 = ax.axhline(color='b')
self.text1 = ax.text(1990.55, y, '%d' %45,bbox=dict(fc='white',ec='k'), fontsize='x-small')
self.text2 = ax.text(1990.55, y, '%d' %45,bbox=dict(fc='white',ec='k'), fontsize='x-small')
self._sl = slider.val
def mouse_move(self, event):
if not event.inaxes:
return
x, y = event.xdata, event.ydata
r = self._sl
y1 , y2 = y+r/2 , y-r/2
#self.lx1.set_ydata(y)
self.lx1.set_ydata(y+r/2)
self.lx2.set_ydata(y-r/2)
for i in range(4):
#shade = cmap(norm((data['mean'.values[i]-event.ydata)/df_std.values[i]))
prob1=stats.norm.cdf(y1,self._df['mean'].values[i],self._df['error'].values[i])
prob2=stats.norm.cdf(y2,self._df['mean'].values[i],self._df['error'].values[i])
shade = cmap(prob1-prob2)
self._bl[i].set_color(shade)
self.text1.set_text('%d' %y1)
self.text1.set_position((1990.55, y1))
self.text2.set_text('%d' %y2)
self.text2.set_position((1990.55, y2))
plt.draw()
cursor = Cursor(ax, data,barlist, slider)
#plt.connect('range_change', cursor.update)
plt.connect('motion_notify_event', cursor.mouse_move)

Using matplotlib checkbuttons in PyQT5

Is it possible to use matplotlib checkbuttons in a plot embedded in PyQT5? The code is below, the plot works and it is embedded in a PyQT window but the checkbuttons do not add or remove the series as they should. Code works fine when taken out of PyQT.
import numpy as np
import sys
from matplotlib.widgets import CheckButtons
from PyQt5.QtWidgets import QDialog, QApplication, QPushButton, QVBoxLayout
from PyQt5 import QtCore
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
class Window(QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.figure = plt.figure()
t = np.arange(0.0, 2.0, 0.01)
s0 = np.sin(2*np.pi*t)
s1 = np.sin(4*np.pi*t)
s2 = np.sin(6*np.pi*t)
self.figure, ax = plt.subplots()
l0, = ax.plot(t, s0, visible=False, lw=2, color='k', label='2 Hz')
l1, = ax.plot(t, s1, lw=2, color='r', label='4 Hz')
l2, = ax.plot(t, s2, lw=2, color='g', label='6 Hz')
plt.subplots_adjust(left=0.2)
self.lines = [l0, l1, l2]
# Make checkbuttons with all plotted lines with correct visibility
rax = plt.axes([0.05, 0.4, 0.1, 0.15])
self.labels = [str(line.get_label()) for line in self.lines]
visibility = [line.get_visible() for line in self.lines]
check = CheckButtons(rax, self.labels, visibility)
check.on_clicked(self.b)
## print('showing')
## plt.show()
self.canvas = FigureCanvas(self.figure)
layout = QVBoxLayout()
## layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
# layout.addWidget(self.button)
self.setLayout(layout)
self.canvas.draw()
## print('done')
## plt.show()
def b(self,label):
index = self.labels.index(label)
self.lines[index].set_visible(not self.lines[index].get_visible())
plt.draw()
if __name__ == '__main__':
app = QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
If you are going to use matplotlib with Qt then you should not use pyplot but the Figure that is set on the canvas. Also "check" must be a member of the class.
Considering the above, the solution is:
from PyQt5.QtWidgets import QApplication, QDialog, QVBoxLayout
import numpy as np
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.figure import Figure
from matplotlib.widgets import CheckButtons
class Window(QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.canvas = FigureCanvas(Figure(figsize=(5, 3)))
t = np.arange(0.0, 2.0, 0.01)
s0 = np.sin(2 * np.pi * t)
s1 = np.sin(4 * np.pi * t)
s2 = np.sin(6 * np.pi * t)
ax = self.canvas.figure.subplots()
(l0,) = ax.plot(t, s0, visible=False, lw=2, color="k", label="2 Hz")
(l1,) = ax.plot(t, s1, lw=2, color="r", label="4 Hz")
(l2,) = ax.plot(t, s2, lw=2, color="g", label="6 Hz")
self.canvas.figure.subplots_adjust(left=0.2)
self.lines = [l0, l1, l2]
rax = self.canvas.figure.add_axes([0.05, 0.4, 0.1, 0.15])
self.labels = [str(line.get_label()) for line in self.lines]
visibility = [line.get_visible() for line in self.lines]
self.check = CheckButtons(rax, self.labels, visibility)
self.check.on_clicked(self.on_clicked)
lay = QVBoxLayout(self)
lay.addWidget(self.canvas)
self.resize(640, 480)
def on_clicked(self, label):
index = self.labels.index(label)
self.lines[index].set_visible(not self.lines[index].get_visible())
self.canvas.draw()
def main():
import sys
app = QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

show label on hover over vertically spanned area in matplotlib

when I hover over spanned region, labels are being showed only along the sides of spanned area but not through out the area.
I want the label to be viewed in the whole area when I hover in it. How can I implement this logic?
import matplotlib.pyplot as plt
import mplcursors
plt.axvspan(2,3,gid='yes',alpha=0.3,label = 'y')
mplcursors.cursor(hover=True).connect(
"add", lambda sel: sel.annotation.set_text(sel.artist.get_label()))
plt.show()
I don't know why mplcursors does not work in the code from the question; but here is how to show an annotation upon hovering an axes span (without mplcursors):
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
span = ax.axvspan(2,3,gid='yes',alpha=0.3,label = 'y')
annot = ax.annotate("Y", xy=(0,0), xytext=(20,20), textcoords="offset points",
bbox=dict(boxstyle="round", fc="w"),
arrowprops=dict(arrowstyle="->"))
annot.set_visible(False)
def hover(event):
vis = annot.get_visible()
if event.inaxes == ax:
cont, ind = span.contains(event)
if cont:
annot.xy = (event.xdata, event.ydata)
annot.set_visible(True)
fig.canvas.draw_idle()
else:
if vis:
annot.set_visible(False)
fig.canvas.draw_idle()
fig.canvas.mpl_connect("motion_notify_event", hover)
plt.show()

How to display multicursor on a QTabWidget?

The multicursor example
The question is : If I want the plot to be displayed on a tab of the QTabWidget,how to make the MultiCursor works?
# -*- coding: utf-8 -*-
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import matplotlib
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
import numpy as np
import sys
from matplotlib.gridspec import GridSpec
from matplotlib.widgets import MultiCursor
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
class MainWindow(QMainWindow):
def __init__(self):
super().__init__(flags=Qt.Window)
self.setFont(QFont("Microsoft YaHei", 10, QFont.Normal))
self.setMinimumSize(1550, 950)
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
centralwidget = QWidget(flags=Qt.Widget)
centralwidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.setCentralWidget(centralwidget)
self.tabview = QTabWidget()
self.tabview.currentChanged.connect(self.onchange)
self.chart_widget0 = QWidget()
self.chart_widget1 = QWidget()
self.dc0 = my_Canvas(self.chart_widget0, width=20, height=8, dpi=100)
self.dc1 = my_Canvas(self.chart_widget1, width=20, height=8, dpi=100)
self.tabview.addTab(self.dc0, "MultiCursor")
self.tabview.addTab(self.dc1, "Cursor")
toplayout = QHBoxLayout()
toplayout.addWidget(self.tabview)
centralwidget.setLayout(toplayout)
def onchange(self,i):
if i == 0:
self.dc0.update_figure()
elif i == 1:
self.dc1.update_figure()
class my_Canvas(FigureCanvas):
def __init__(self, parent=None, width=10, height=7, dpi=100):
self.fig = plt.figure(figsize=(width, height), dpi=dpi)
gs = GridSpec(2, 1, height_ratios=[3, 1])
self.axes1 = plt.subplot(gs[0])
self.axes2 = plt.subplot(gs[1])
self.compute_initial_figure()
FigureCanvas.__init__(self, self.fig)
self.setParent(parent)
def compute_initial_figure(self):
self.axes1.cla()
self.axes2.cla()
def update_figure(self):
t = np.arange(0.0, 2.0, 0.01)
s1 = np.sin(2*np.pi*t)
s2 = np.sin(4*np.pi*t)
self.axes1.plot(t, s1)
self.axes2.plot(t, s2)
multi = MultiCursor(self.fig.canvas, (self.axes1, self.axes2), color='r', lw=1)
self.draw()
if __name__ == '__main__':
app = QApplication(sys.argv)
w1 = MainWindow()
w1.show()
sys.exit(app.exec_())
How to modify the code to make the MultiCursor works, and could I control the display of the cursor by key or mousebutton click?
Further more, how to display the coordinate with the cursor?
As the Multicursor documentation tells us,
For the cursor to remain responsive you must keep a reference to it.
The easiest way is to make it a class variable,
self.multi = MultiCursor(...)

Resources