Tkinter DPI bug depends on how import is entered?

Question:

I have a weird question. In essence: why does the way I enter the pyplot import line into iPython influence my plot?!

Try this (on Windows 10):

  • Use a high-DPI main monitor set to 200%
  • Start a fresh iPython console
  • Enter the three blocks of code below one by one, using any of these ways for any block:
    1. just typing, OR
    2. pasting: pressing Ctrl+V, OR
    3. pasting: clicking the right mouse button, OR
    4. pressing Up to get it from history
  • To execute each block, just press Enter
  • Check whether the whole axis is visible
  • Close iPython and try again

The minimal reproducible example:

import matplotlib as mpl
mpl.use('TkAgg')

import matplotlib.pyplot as plt

from ctypes import windll
windll.shcore.SetProcessDpiAwareness(2)
fig = plt.figure()
w = fig.canvas.manager.window
print(w.winfo_screenwidth(), w.winfo_screenheight())
w.wm_geometry('1600x800+60+0')
fig.canvas.flush_events()
plt.plot(1, 'x')
plt.show(block=False)

Now the result depends on how the first two blocks have been entered. If one or both of them used option 1 or 2, an incorrect monitor size is reported (1920×1080), but I do get a ‘correct’ plot (with tiny fonts):
options 1 and 2

If both import lines have been entered using option 3 or 4 however, the reported monitor size is correct (3840×2160), but I get incorrectly sized/zoomed plots (with normal font size):
options 3 and 4

The behavior depends on how import lines (or the ‘run script’ they are in) are entered!? Not how they are run, that’s just Enter.

Any idea what causes this? Or how to fix it? Other than remembering to always do Ctrl+V instead of history or right click…
Is this a bug I can report somewhere?

Changing the window size manually a bit afterwards makes the axis fit inside the window. But I would like to script it. And the manual resize does not fix all differences: options 1 and 2 keep using tiny fonts.

Explicitly resetting SetProcessDpiAwareness(0) prevents the issue (1 and 2 keep it): monitor reported as 1920×1080, axes fit inside figure, but a larger window and normal size fonts.

Specifying dpi=192 (or something) explicitly with plt.figure() does not help or change anything.

The used backend is the TkAgg Windows default. The exact same happens with TkCairo. The WxAgg and QtAgg alternatives work ok, using window.SetClientSize(1600, 800) resp. manager.resize(1600, 800) (they work like SetProcessDpiAwareness(0)). So I guess the issue is specific to tkinter.

Using python.exe (instead of ipython.exe) always shows the incorrect version. AFAICS I have not changed the High DPI compatibility settings for iPython.

Asked By: Michel de Ruiter

||

Answers:

Almost all differences are gone when I do this:

fig.dpi = windll.user32.GetDpiForWindow(w.winfo_id()) / 0.96

This leaves w.winfo_screenwidth/height() halved and small tool buttons when using options 1 and 2. But the window is 1600 pixels wide as specified (758 resp. 728 high, due to the button bar), fonts are readable (200% as configured in Windows) and the axes fit inside the figure.

I must remember not to use dpi= with plt.figure(), because that messes things up again.

I’m still curious where the difference comes from, but this way I can use the default Tk backend without issues.

Answered By: Michel de Ruiter
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.