Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3707441

Browse files
authored
Merge pull request #20321 from QuLogic/gtk4
Add a GTK4 backend.
2 parents ce7e2e0 + 04f17f8 commit 3707441

27 files changed

+1312
-212
lines changed

.flake8

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,12 @@ per-file-ignores =
123123
examples/ticks_and_spines/date_concise_formatter.py: E402
124124
examples/user_interfaces/embedding_in_gtk3_panzoom_sgskip.py: E402
125125
examples/user_interfaces/embedding_in_gtk3_sgskip.py: E402
126-
examples/user_interfaces/gtk_spreadsheet_sgskip.py: E402
126+
examples/user_interfaces/embedding_in_gtk4_panzoom_sgskip.py: E402
127+
examples/user_interfaces/embedding_in_gtk4_sgskip.py: E402
128+
examples/user_interfaces/gtk3_spreadsheet_sgskip.py: E402
129+
examples/user_interfaces/gtk4_spreadsheet_sgskip.py: E402
127130
examples/user_interfaces/mpl_with_glade3_sgskip.py: E402
128-
examples/user_interfaces/pylab_with_gtk_sgskip.py: E402
131+
examples/user_interfaces/pylab_with_gtk3_sgskip.py: E402
132+
examples/user_interfaces/pylab_with_gtk4_sgskip.py: E402
129133
examples/user_interfaces/toolmanager_sgskip.py: E402
130134
examples/userdemo/pgf_preamble_sgskip.py: E402

doc/api/backend_gtk4_api.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
**NOTE** These backends are not documented here, to avoid adding a dependency
2+
to building the docs.
3+
4+
.. redirect-from:: /api/backend_gtk4agg_api
5+
.. redirect-from:: /api/backend_gtk4cairo_api
6+
7+
8+
:mod:`matplotlib.backends.backend_gtk4agg`
9+
==========================================
10+
11+
.. module:: matplotlib.backends.backend_gtk4agg
12+
13+
:mod:`matplotlib.backends.backend_gtk4cairo`
14+
============================================
15+
16+
.. module:: matplotlib.backends.backend_gtk4cairo

doc/api/index_backend_api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
backend_agg_api.rst
1111
backend_cairo_api.rst
1212
backend_gtk3_api.rst
13+
backend_gtk4_api.rst
1314
backend_nbagg_api.rst
1415
backend_pdf_api.rst
1516
backend_pgf_api.rst

doc/devel/dependencies.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ and the capabilities they provide.
4242

4343
* Tk_ (>= 8.4, != 8.6.0 or 8.6.1) [#]_: for the Tk-based backends.
4444
* PyQt6_ (>= 6.1), PySide6_, PyQt5_, or PySide2_: for the Qt-based backends.
45-
* PyGObject_: for the GTK3-based backends [#]_.
45+
* PyGObject_: for the GTK-based backends [#]_.
4646
* wxPython_ (>= 4) [#]_: for the wx-based backends.
47-
* pycairo_ (>= 1.11.0) or cairocffi_ (>= 0.8): for the GTK3 and/or cairo-based
47+
* pycairo_ (>= 1.11.0) or cairocffi_ (>= 0.8): for the GTK and/or cairo-based
4848
backends.
4949
* Tornado_: for the WebAgg backend.
5050

examples/user_interfaces/embedding_in_gtk3_panzoom_sgskip.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
win = Gtk.Window()
2121
win.connect("delete-event", Gtk.main_quit)
2222
win.set_default_size(400, 300)
23-
win.set_title("Embedding in GTK")
23+
win.set_title("Embedding in GTK3")
2424

2525
fig = Figure(figsize=(5, 4), dpi=100)
2626
ax = fig.add_subplot(1, 1, 1)

examples/user_interfaces/embedding_in_gtk3_sgskip.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
win = Gtk.Window()
2020
win.connect("delete-event", Gtk.main_quit)
2121
win.set_default_size(400, 300)
22-
win.set_title("Embedding in GTK")
22+
win.set_title("Embedding in GTK3")
2323

2424
fig = Figure(figsize=(5, 4), dpi=100)
2525
ax = fig.add_subplot()
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""
2+
===========================================
3+
Embedding in GTK4 with a navigation toolbar
4+
===========================================
5+
6+
Demonstrate NavigationToolbar with GTK4 accessed via pygobject.
7+
"""
8+
9+
import gi
10+
gi.require_version('Gtk', '4.0')
11+
from gi.repository import Gtk
12+
13+
from matplotlib.backends.backend_gtk4 import (
14+
NavigationToolbar2GTK4 as NavigationToolbar)
15+
from matplotlib.backends.backend_gtk4agg import (
16+
FigureCanvasGTK4Agg as FigureCanvas)
17+
from matplotlib.figure import Figure
18+
import numpy as np
19+
20+
21+
def on_activate(app):
22+
win = Gtk.ApplicationWindow(application=app)
23+
win.set_default_size(400, 300)
24+
win.set_title("Embedding in GTK4")
25+
26+
fig = Figure(figsize=(5, 4), dpi=100)
27+
ax = fig.add_subplot(1, 1, 1)
28+
t = np.arange(0.0, 3.0, 0.01)
29+
s = np.sin(2*np.pi*t)
30+
ax.plot(t, s)
31+
32+
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
33+
win.set_child(vbox)
34+
35+
# Add canvas to vbox
36+
canvas = FigureCanvas(fig) # a Gtk.DrawingArea
37+
canvas.set_hexpand(True)
38+
canvas.set_vexpand(True)
39+
vbox.append(canvas)
40+
41+
# Create toolbar
42+
toolbar = NavigationToolbar(canvas, win)
43+
vbox.append(toolbar)
44+
45+
win.show()
46+
47+
48+
app = Gtk.Application(
49+
application_id='org.matplotlib.examples.EmbeddingInGTK4PanZoom')
50+
app.connect('activate', on_activate)
51+
app.run(None)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""
2+
=================
3+
Embedding in GTK4
4+
=================
5+
6+
Demonstrate adding a FigureCanvasGTK4Agg widget to a Gtk.ScrolledWindow using
7+
GTK4 accessed via pygobject.
8+
"""
9+
10+
import gi
11+
gi.require_version('Gtk', '4.0')
12+
from gi.repository import Gtk
13+
14+
from matplotlib.backends.backend_gtk4agg import (
15+
FigureCanvasGTK4Agg as FigureCanvas)
16+
from matplotlib.figure import Figure
17+
import numpy as np
18+
19+
20+
def on_activate(app):
21+
win = Gtk.ApplicationWindow(application=app)
22+
win.set_default_size(400, 300)
23+
win.set_title("Embedding in GTK4")
24+
25+
fig = Figure(figsize=(5, 4), dpi=100)
26+
ax = fig.add_subplot()
27+
t = np.arange(0.0, 3.0, 0.01)
28+
s = np.sin(2*np.pi*t)
29+
ax.plot(t, s)
30+
31+
# A scrolled margin goes outside the scrollbars and viewport.
32+
sw = Gtk.ScrolledWindow(margin_top=10, margin_bottom=10,
33+
margin_start=10, margin_end=10)
34+
win.set_child(sw)
35+
36+
canvas = FigureCanvas(fig) # a Gtk.DrawingArea
37+
canvas.set_size_request(800, 600)
38+
sw.set_child(canvas)
39+
40+
win.show()
41+
42+
43+
app = Gtk.Application(application_id='org.matplotlib.examples.EmbeddingInGTK4')
44+
app.connect('activate', on_activate)
45+
app.run(None)

examples/user_interfaces/gtk_spreadsheet_sgskip.py renamed to examples/user_interfaces/gtk3_spreadsheet_sgskip.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""
2-
===============
3-
GTK Spreadsheet
4-
===============
2+
================
3+
GTK3 Spreadsheet
4+
================
55
66
Example of embedding Matplotlib in an application and interacting with a
77
treeview to store data. Double click on an entry to update plot data.
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
"""
2+
================
3+
GTK4 Spreadsheet
4+
================
5+
6+
Example of embedding Matplotlib in an application and interacting with a
7+
treeview to store data. Double click on an entry to update plot data.
8+
"""
9+
10+
import gi
11+
gi.require_version('Gtk', '4.0')
12+
gi.require_version('Gdk', '4.0')
13+
from gi.repository import Gtk
14+
15+
from matplotlib.backends.backend_gtk4agg import FigureCanvas # or gtk4cairo.
16+
17+
from numpy.random import random
18+
from matplotlib.figure import Figure
19+
20+
21+
class DataManager(Gtk.ApplicationWindow):
22+
num_rows, num_cols = 20, 10
23+
24+
data = random((num_rows, num_cols))
25+
26+
def __init__(self, *args, **kwargs):
27+
super().__init__(*args, **kwargs)
28+
self.set_default_size(600, 600)
29+
30+
self.set_title('GtkListStore demo')
31+
32+
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False,
33+
spacing=8)
34+
self.set_child(vbox)
35+
36+
label = Gtk.Label(label='Double click a row to plot the data')
37+
vbox.append(label)
38+
39+
sw = Gtk.ScrolledWindow()
40+
sw.set_has_frame(True)
41+
sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
42+
sw.set_hexpand(True)
43+
sw.set_vexpand(True)
44+
vbox.append(sw)
45+
46+
model = self.create_model()
47+
self.treeview = Gtk.TreeView(model=model)
48+
self.treeview.connect('row-activated', self.plot_row)
49+
sw.set_child(self.treeview)
50+
51+
# Matplotlib stuff
52+
fig = Figure(figsize=(6, 4), constrained_layout=True)
53+
54+
self.canvas = FigureCanvas(fig) # a Gtk.DrawingArea
55+
self.canvas.set_hexpand(True)
56+
self.canvas.set_vexpand(True)
57+
vbox.append(self.canvas)
58+
ax = fig.add_subplot()
59+
self.line, = ax.plot(self.data[0, :], 'go') # plot the first row
60+
61+
self.add_columns()
62+
63+
def plot_row(self, treeview, path, view_column):
64+
ind, = path # get the index into data
65+
points = self.data[ind, :]
66+
self.line.set_ydata(points)
67+
self.canvas.draw()
68+
69+
def add_columns(self):
70+
for i in range(self.num_cols):
71+
column = Gtk.TreeViewColumn(str(i), Gtk.CellRendererText(), text=i)
72+
self.treeview.append_column(column)
73+
74+
def create_model(self):
75+
types = [float] * self.num_cols
76+
store = Gtk.ListStore(*types)
77+
for row in self.data:
78+
# Gtk.ListStore.append is broken in PyGObject, so insert manually.
79+
it = store.insert(-1)
80+
store.set(it, {i: val for i, val in enumerate(row)})
81+
return store
82+
83+
84+
def on_activate(app):
85+
manager = DataManager(application=app)
86+
manager.show()
87+
88+
89+
app = Gtk.Application(application_id='org.matplotlib.examples.GTK4Spreadsheet')
90+
app.connect('activate', on_activate)
91+
app.run()

examples/user_interfaces/pylab_with_gtk_sgskip.py renamed to examples/user_interfaces/pylab_with_gtk3_sgskip.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""
2-
===============
3-
pyplot with GTK
4-
===============
2+
================
3+
pyplot with GTK3
4+
================
55
66
An example of how to use pyplot to manage your figure windows, but modify the
77
GUI by accessing the underlying GTK widgets.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""
2+
================
3+
pyplot with GTK4
4+
================
5+
6+
An example of how to use pyplot to manage your figure windows, but modify the
7+
GUI by accessing the underlying GTK widgets.
8+
"""
9+
10+
import matplotlib
11+
matplotlib.use('GTK4Agg') # or 'GTK4Cairo'
12+
import matplotlib.pyplot as plt
13+
14+
import gi
15+
gi.require_version('Gtk', '4.0')
16+
from gi.repository import Gtk
17+
18+
19+
fig, ax = plt.subplots()
20+
ax.plot([1, 2, 3], 'ro-', label='easy as 1 2 3')
21+
ax.plot([1, 4, 9], 'gs--', label='easy as 1 2 3 squared')
22+
ax.legend()
23+
24+
manager = fig.canvas.manager
25+
# you can access the window or vbox attributes this way
26+
toolbar = manager.toolbar
27+
vbox = manager.vbox
28+
29+
# now let's add a button to the toolbar
30+
button = Gtk.Button(label='Click me')
31+
button.connect('clicked', lambda button: print('hi mom'))
32+
button.set_tooltip_text('Click me for fun and profit')
33+
toolbar.append(button)
34+
35+
# now let's add a widget to the vbox
36+
label = Gtk.Label()
37+
label.set_markup('Drag mouse over axes for position')
38+
vbox.insert_child_after(label, fig.canvas)
39+
40+
41+
def update(event):
42+
if event.xdata is None:
43+
label.set_markup('Drag mouse over axes for position')
44+
else:
45+
label.set_markup(
46+
f'<span color="#ef0000">x,y=({event.xdata}, {event.ydata})</span>')
47+
48+
49+
fig.canvas.mpl_connect('motion_notify_event', update)
50+
51+
plt.show()

lib/matplotlib/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,8 +1098,8 @@ def use(backend, *, force=True):
10981098
backend names, which are case-insensitive:
10991099
11001100
- interactive backends:
1101-
GTK3Agg, GTK3Cairo, MacOSX, nbAgg, QtAgg, QtCairo,
1102-
TkAgg, TkCairo, WebAgg, WX, WXAgg, WXCairo, Qt5Agg, Qt5Cairo
1101+
GTK3Agg, GTK3Cairo, GTK4Agg, GTK4Cairo, MacOSX, nbAgg, QtAgg,
1102+
QtCairo, TkAgg, TkCairo, WebAgg, WX, WXAgg, WXCairo, Qt5Agg, Qt5Cairo
11031103
11041104
- non-interactive backends:
11051105
agg, cairo, pdf, pgf, ps, svg, template

lib/matplotlib/backend_bases.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ def _safe_pyplot_import():
101101
backend_mapping = {
102102
'qt': 'qtagg',
103103
'gtk3': 'gtk3agg',
104+
'gtk4': 'gtk4agg',
104105
'wx': 'wxagg',
105106
'tk': 'tkagg',
106107
'macosx': 'macosx',
@@ -1656,7 +1657,7 @@ class FigureCanvasBase:
16561657
A high-level figure instance.
16571658
"""
16581659

1659-
# Set to one of {"qt", "gtk3", "wx", "tk", "macosx"} if an
1660+
# Set to one of {"qt", "gtk3", "gtk4", "wx", "tk", "macosx"} if an
16601661
# interactive framework is required, or None otherwise.
16611662
required_interactive_framework = None
16621663

@@ -1732,7 +1733,7 @@ def _fix_ipython_backend2gui(cls):
17321733
# don't break on our side.
17331734
return
17341735
rif = getattr(cls, "required_interactive_framework", None)
1735-
backend2gui_rif = {"qt": "qt", "gtk3": "gtk3",
1736+
backend2gui_rif = {"qt": "qt", "gtk3": "gtk3", "gtk4": "gtk4",
17361737
"wx": "wx", "macosx": "osx"}.get(rif)
17371738
if backend2gui_rif:
17381739
if _is_non_interactive_terminal_ipython(ip):

0 commit comments

Comments
 (0)