Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug | Regression]: dpi of plot linked to screen scaling - plots expand off the screen #21994

Open
rowlesmr opened this issue Dec 17, 2021 · 4 comments

Comments

@rowlesmr
Copy link

rowlesmr commented Dec 17, 2021

Bug summary

Plotting data, and then replotting over the same plot and toolbar results in the plot expanding in size, but only if the screen scaling (as set in the Windows display settings) is set to greater than 100%.

Code for reproduction is a MWE based on my actual program structure.

Code for reproduction

import PySimpleGUI as sg
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import matplotlib.pyplot as plt
import matplotlib as mpl


class PlotData:
    def __init__(self, m_canvas_x, m_canvas_y):
        self.canvas_x = m_canvas_x
        self.canvas_y = m_canvas_y

    def plot(self, x, y, fig):
        dpi = plt.gcf().get_dpi()
        print(f"{dpi=}")

        if fig:
            plt.close(fig)
        fig, ax = plt.subplots(1, 1)
        fig = plt.gcf()
        fig.set_size_inches(self.canvas_x / float(dpi), self.canvas_y / float(dpi))
        print(f"{fig.get_size_inches()=}")
        fig.set_tight_layout(True)
        plt.margins(x=0)

        ax.plot(x, y, label="a legend entry")
        ax.set_xlabel("X ordinate")
        ax.set_ylabel("Y ordinate")
        plt.title("Title", loc="left")

        return fig


# if this next line is there, and you're using matplotlib 3.5.1,
#  the plot grows when you press "plot" multiple times.
#  matplotlib v3.4.3 works fine.
#  What does this line do? If dpi_awarenes is not None, then the following is activated in PySimpleGUI
#         if running_windows():
#             if platform.release() == "7":
#                 ctypes.windll.user32.SetProcessDPIAware()
#             elif platform.release() == "8" or platform.release() == "10":
#                 ctypes.windll.shcore.SetProcessDpiAwareness(1)
sg.set_options(dpi_awareness=True)

canvas_x = 600
canvas_y = 300

plot = PlotData(canvas_x, canvas_y)
figure = None
figure_agg = None

x_data = [1, 2, 3, 4, 5]
y_data = [[2, 6, 4, 7, 9], [7, 3, 7, 3, 5]]


# https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Matplotlib_Embedded_Toolbar.py
# https://github.com/PySimpleGUI/PySimpleGUI/issues/3989#issuecomment-794005240
def draw_figure_w_toolbar(canvas, figure, canvas_toolbar):
    print(f"{canvas.winfo_width()=}, {canvas.winfo_height()=}")
    if canvas.children:
        for child in canvas.winfo_children():
            child.destroy()
    if canvas_toolbar.children:
        for child in canvas_toolbar.winfo_children():
            child.destroy()
    figure_canvas_agg = FigureCanvasTkAgg(figure, master=canvas)
    figure_canvas_agg.draw()
    toolbar = Toolbar(figure_canvas_agg, canvas_toolbar)
    toolbar.update()
    figure_canvas_agg.get_tk_widget().pack(side='right', fill='both', expand=1)
    return figure_canvas_agg


# https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Matplotlib_Embedded_Toolbar.py
class Toolbar(NavigationToolbar2Tk):
    def __init__(self, *args, **kwargs):
        super(Toolbar, self).__init__(*args, **kwargs)


layout = \
    [
        [sg.Button("Plot", key="plot_data"), sg.Button("Exit", key="exit")],
        [sg.Column(layout=[[sg.Canvas(size=(canvas_x, canvas_y), key="single_plot", expand_x=True, expand_y=True)]], pad=(0, 0), expand_x=True, expand_y=True)],
        [sg.Sizer(v_pixels=60), sg.Canvas(key="single_matplotlib_controls")]
    ]


def gui() -> None:
    global figure, figure_agg
    window = sg.Window("Data plotter", layout, finalize=True, resizable=True)
    print(f"{mpl.__version__=}")
    i = 0
    while True:
        event, values = window.read()
        i += 1
        print(f"{i=}")
        if event in (None, "exit"):
            break
        elif event == "plot_data":
            figure = plot.plot(x_data, y_data[i % 2], figure)
            figure_agg = draw_figure_w_toolbar(window["single_plot"].TKCanvas, figure,
                                               window["single_matplotlib_controls"].TKCanvas)


if __name__ == "__main__":
    gui()

Actual outcome

Actual and expected outcomes are both shown here:

https://youtu.be/7c_Rt_BDfnM

Expected outcome

Actual and expected outcomes are both shown here:

https://youtu.be/7c_Rt_BDfnM

Additional information

If the program is made DPI aware (ctypes.windll.shcore.SetProcessDpiAwareness(1), done by sg.set_options(dpi_awareness=True)) then the bad behaviour occurs.
If not, then the behaviour is not seen.

There is no issue seen at all if using 3.4.3

Operating system

Windows 10

Matplotlib Version

3.5.1

Matplotlib Backend

TKAgg

Python version

3.9.4

Jupyter version

No response

Installation

pip

@rowlesmr
Copy link
Author

rowlesmr commented Dec 17, 2021

ah. Possible duplicate of #21875

@QuLogic
Copy link
Member

QuLogic commented Dec 18, 2021

I'm not sure if there's a bug here. If you are embedding in a Tk window, you should not use pyplot but instantiate a Figure yourself, as in this example. Right now you are creating a new window every time, and stealing the figure from it. Matplotlib applies HiDPI scaling based on the window it creates, but it should not do that if it has no window to manage.

@rowlesmr
Copy link
Author

rowlesmr commented Dec 20, 2021

Roger that. Looking at how to change over now.

rowlesmr added a commit to rowlesmr/pdCIFplotter that referenced this issue Dec 20, 2021
Discovered that the pysimplegui demo isn't really the best way of doing things.

Now following https://matplotlib.org/stable/gallery/user_interfaces/embedding_in_tk_sgskip.html as how to do it properly.

see also matplotlib/matplotlib#21994
@rowlesmr
Copy link
Author

rowlesmr commented Dec 20, 2021

The bug persists. As far as I can tell, I've migrated my code over to the way specified in your link.

import PySimpleGUI as sg
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import matplotlib as mpl


class PlotData:
    def __init__(self):
        self.dpi = 100

    def plot(self, x, y, fig):
        if fig:
            fig.clear()
        fig = Figure(figsize=(6, 3), dpi=self.dpi)
        ax = fig.add_subplot()
        fig.set_tight_layout(True)
        ax.margins(x=0)

        ax.plot(x, y, label="a legend entry")
        ax.set_xlabel("X ordinate")
        ax.set_ylabel("Y ordinate")
        ax.set_title("Title", loc="left")

        return fig


# if this next line is there, and you're using matplotlib 3.5.1,
#  the plot grows when you press "plot" multiple times.
#  matplotlib v3.4.3 works fine.
#  What does this line do? If dpi_awarenes is not None, then the following is activated in PySimpleGUI
#         if running_windows():
#             if platform.release() == "7":
#                 ctypes.windll.user32.SetProcessDPIAware()
#             elif platform.release() == "8" or platform.release() == "10":
#                 ctypes.windll.shcore.SetProcessDpiAwareness(1)
sg.set_options(dpi_awareness=True)

canvas_x = 600
canvas_y = 300

plot = PlotData()
figure = None
figure_agg = None

x_data = [1, 2, 3, 4, 5]
y_data = [[2, 6, 4, 7, 9], [7, 3, 7, 3, 5]]


# https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Matplotlib_Embedded_Toolbar.py
# https://github.com/PySimpleGUI/PySimpleGUI/issues/3989#issuecomment-794005240
def draw_figure_w_toolbar(canvas, fig, canvas_toolbar):
    print(f"{canvas.winfo_width()=}, {canvas.winfo_height()=}")
    if canvas.children:
        for child in canvas.winfo_children():
            child.destroy()
    if canvas_toolbar.children:
        for child in canvas_toolbar.winfo_children():
            child.destroy()
    figure_canvas_agg = FigureCanvasTkAgg(fig, master=canvas)
    figure_canvas_agg.draw()
    toolbar = NavigationToolbar2Tk(figure_canvas_agg, canvas_toolbar)
    toolbar.update()
    figure_canvas_agg.get_tk_widget().pack(side='right', fill='both', expand=1)
    return figure_canvas_agg


layout = \
    [
        [sg.Button("Plot", key="plot_data"), sg.Button("Exit", key="exit")],
        [sg.Column(layout=[[sg.Canvas(size=(canvas_x, canvas_y), key="single_plot", expand_x=True, expand_y=True)]], pad=(0, 0), expand_x=True, expand_y=True)],
        [sg.Sizer(v_pixels=60), sg.Canvas(key="single_matplotlib_controls")]
    ]


def gui() -> None:
    global figure, figure_agg
    window = sg.Window("Data plotter", layout, finalize=True, resizable=True)
    print(f"{mpl.__version__=}")
    i = 0
    while True:
        event, values = window.read()
        i += 1
        print(f"{i=}")
        if event in (None, "exit"):
            break
        elif event == "plot_data":
            figure = plot.plot(x_data, y_data[i % 2], figure)
            figure_agg = draw_figure_w_toolbar(window["single_plot"].TKCanvas, figure,
                                               window["single_matplotlib_controls"].TKCanvas)


if __name__ == "__main__":
    gui()

@QuLogic QuLogic modified the milestones: v3.5.2, v3.5.3 Apr 30, 2022
@QuLogic QuLogic modified the milestones: v3.5.3, v3.5.4 Jul 21, 2022
@oscargus oscargus modified the milestones: v3.5.4, v3.6.1 Sep 25, 2022
@QuLogic QuLogic modified the milestones: v3.6.1, v3.6.2 Oct 6, 2022
@QuLogic QuLogic modified the milestones: v3.6.2, v3.6.3 Oct 27, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants