Skip to content

Commit 8095b72

Browse files
statxcintellikingUbuntu
authored
fix: resolve FigureCanvasTkAgg clipping on Windows HiDPI (matplotlib#31133)
* fix: resolve FigureCanvasTkAgg clipping on Windows HiDPI * Fix lint issues * Fix update_idletasks call in Tk backend * Check for update_idletasks existence before calling * refactor: fix HiDPI clipping for layout-managed FigureCanvasTkAgg by resizing with actual widget dimensions instead of computed ones after DPI changes * fix: matplot error * update docstring --------- Co-authored-by: intelliking <intelliking@users.noreply.github.com> Co-authored-by: Ubuntu <ubuntu@vps-eae40731.vps.ovh.ca>
1 parent a01f57d commit 8095b72

2 files changed

Lines changed: 64 additions & 4 deletions

File tree

lib/matplotlib/backends/_backend_tk.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -270,14 +270,24 @@ def _update_device_pixel_ratio(self, event=None):
270270
elif sys.platform == "linux":
271271
ratio = self._tkcanvas.winfo_fpixels('1i') / 96
272272
if ratio is not None and self._set_device_pixel_ratio(ratio):
273-
# The easiest way to resize the canvas is to resize the canvas
274-
# widget itself, since we implement all the logic for resizing the
275-
# canvas backing store on that event.
273+
# Resize the canvas widget, then explicitly update the figure
274+
# size to match the actual widget dimensions. When the canvas
275+
# is constrained by a geometry manager (pack/grid), <Configure>
276+
# may not fire after configure(), so we handle the resize
277+
# directly — similar to Qt's _update_pixel_ratio approach.
276278
w, h = self.get_width_height(physical=True)
277279
self._tkcanvas.configure(width=w, height=h)
280+
self._resize_figure_for_canvas_size(
281+
self._tkcanvas.winfo_width(),
282+
self._tkcanvas.winfo_height())
278283

279284
def resize(self, event):
280-
width, height = event.width, event.height
285+
self._resize_figure_for_canvas_size(event.width, event.height)
286+
287+
def _resize_figure_for_canvas_size(self, width, height):
288+
"""Update figure size and redraw based on a given canvas size."""
289+
if width <= 0 or height <= 0:
290+
return
281291

282292
# compute desired figure size in inches
283293
dpival = self.figure.dpi

lib/matplotlib/tests/test_backend_tk.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import platform
55
import subprocess
66
import sys
7+
from unittest.mock import patch
78

89
import pytest
910

@@ -280,3 +281,52 @@ def test_figure(master):
280281
foreground="white")
281282
test_figure(root)
282283
print("success")
284+
285+
286+
@_isolated_tk_test(success_count=1)
287+
def test_dpi_change_triggers_resize():
288+
"""
289+
Test that _update_device_pixel_ratio recalculates figure.size_inches
290+
using the actual widget dimensions, so the render size matches the
291+
visible canvas area even when constrained by a layout manager.
292+
See issue #31126.
293+
"""
294+
import tkinter as tk
295+
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
296+
from matplotlib.figure import Figure
297+
298+
root = tk.Tk()
299+
root.geometry("400x300")
300+
root.update_idletasks()
301+
302+
fig = Figure(dpi=100)
303+
fig.add_subplot(111)
304+
305+
canvas = FigureCanvasTkAgg(fig, master=root)
306+
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
307+
canvas.draw()
308+
root.update_idletasks()
309+
310+
actual_w = canvas.get_tk_widget().winfo_width()
311+
actual_h = canvas.get_tk_widget().winfo_height()
312+
assert actual_w > 0 and actual_h > 0
313+
314+
# Simulate a 2x DPI change through _update_device_pixel_ratio.
315+
# Mock the platform-specific DPI query to return ratio=2.0.
316+
with patch.object(sys, 'platform', 'linux'), \
317+
patch.object(canvas._tkcanvas, 'winfo_fpixels',
318+
return_value=192.0):
319+
canvas._update_device_pixel_ratio()
320+
321+
# Verify the render size matches the actual widget size, NOT the
322+
# inflated physical size from get_width_height(physical=True).
323+
size = fig.get_size_inches()
324+
render_w = round(size[0] * fig.dpi)
325+
render_h = round(size[1] * fig.dpi)
326+
assert abs(render_w - actual_w) <= 2, \
327+
f"render width {render_w} != actual width {actual_w}"
328+
assert abs(render_h - actual_h) <= 2, \
329+
f"render height {render_h} != actual height {actual_h}"
330+
331+
print("success")
332+
root.destroy()

0 commit comments

Comments
 (0)