Omega
Omega

Reputation: 871

matplotlib chart shrinks tkinter window

I have implemented a matplotlib chart into my tkinter window, but every time I hit 'OK' and add it to the application, it resizes the entire window and also cuts off the axes ticks. I played around with figsize and canvas, but didn't manage to solve it. Does anyone have an explanation for this behaviour?

I have simplified the code so that it runs by clicking OK.

Before clicking OK:

Before

After clicking OK:

After

from tkinter import *
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

class StockApp(Frame):
    def __init__(self, master):
        super(StockApp, self).__init__(master)
        self.place()
        self.widgets()

    def widgets(self):
        # width * height
        root.geometry('500x400')
        root.configure(bg='#FFFFFF')

        # Header
        header_label = Label(text='Stock Prices', font=(
            'Calibri', 22, 'bold'), bg='#FFFFFF')
        header_label.place(x=30, y=15)

        # Enter your API key
        api_key_label = Label(text='API key', font=(
            'Calibri', 10), bg='#FFFFFF')
        api_key_label.place(x=30, y=65)

        self.api_key_field = Entry(
            width=32, font=('Calibri', 10), bg='#F4F4F4')
        self.api_key_field.config(show="*")
        self.api_key_field.place(x=30, y=90)

        # Enter an index
        index_label = Label(text='Stock index', font=(
            'Calibri', 10), bg='#FFFFFF')
        index_label.place(x=280, y=65)

        self.index_field = Entry(width=15, font=('Calibri', 10), bg='#F4F4F4')
        self.index_field.place(x=280, y=90)

        # OK button
        ok_btn = Button(text='OK', command=self.ok, font=(
            'Calibri', 8), bg='#F4F4F4', width=5)
        ok_btn.place(x=400, y=88)

    def call_api(self):
        pass
       
    def format_df(self):

        self.df = pd.DataFrame({'date': ['2020-11-06', '2020-11-07', '2020-11-08', '2020-11-09'], 'adj_close': [200, 210, 205, 215]})
        self.df['date'] = pd.to_datetime(self.df['date'])
        self.df.set_index('date', inplace = True)

    def draw_chart(self):

        #plot data
        fig, ax = plt.subplots(figsize=(2,1), dpi=50)
        self.df.plot(ax=ax)

        # #set major ticks format
        ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))

        ax.spines['top'].set_visible(False)
        ax.spines['right'].set_visible(False)

        ax.get_legend().remove()

        canvas = FigureCanvasTkAgg(fig, master=root)
        canvas.draw() # TK-Drawingarea
        # Do I need to place both?
        canvas.get_tk_widget().place(x = 30, y = 150)
        canvas._tkcanvas.place(x = 30, y = 150)

    def ok(self):
        self.call_api()
        self.format_df()
        self.draw_chart()


if __name__ == "__main__":
    root= Tk()
    app= StockApp(root)
    root.title('Stock prices')
    mainloop()

Upvotes: 3

Views: 879

Answers (2)

Daniel
Daniel

Reputation: 173

I have had the same problem as you. I solved the problem by adding the following:

import ctypes
 
ctypes.windll.shcore.SetProcessDpiAwareness(1)

But keep in mind that this also has an effect on your tkinter window geometries.

Upvotes: 0

Mandias
Mandias

Reputation: 876

This one has been a thorn in my side for a while. I was getting the exact same behavior that you described until I included "ctypes.windll.shcore.SetProcessDpiAwareness(1)". It's a DPI issue, though there was no way within tkinter that I could find to fix it.

Per the Microsoft documentation for SetProcessDpiAwareness(value):

value: The DPI awareness value to set. Possible values are from the PROCESS_DPI_AWARENESS enumeration.

A "value" of 1 (as above) points to:

PROCESS_SYSTEM_DPI_AWARE: System DPI aware. This app does not scale for DPI changes. It will query for the DPI once and use that value for the lifetime of the app. If the DPI changes, the app will not adjust to the new DPI value. It will be automatically scaled up or down by the system when the DPI changes from the system value.

The example code below is based on a simplified version of what you provided and appears to fix the issue:

from tkinter import *
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import ctypes


class StockApp(Frame):
    def __init__(self, master):
        self.master = master
        super(StockApp, self).__init__(master)
        self.place()
        self.widgets()

    def widgets(self):
        # width * height
        root.geometry('500x400')

        # OK button
        ok_btn = Button(text='OK', command=self.ok, bg='#F4F4F4', width=5)
        ok_btn.pack(side=tk.TOP)

    def format_df(self):
        self.df = pd.DataFrame({'date': ['2020-11-06', '2020-11-07', '2020-11-08', '2020-11-09'], 'adj_close': [200, 210, 205, 215]})
        self.df['date'] = pd.to_datetime(self.df['date'])
        self.df.set_index('date', inplace=True)

    def draw_chart(self):
        fig, ax = plt.subplots(figsize=(2, 1), dpi=100)
        self.df.plot(ax=ax)

        canvas = FigureCanvasTkAgg(fig, master=root)
        canvas.draw()
        canvas.get_tk_widget().pack(side=tk.TOP)

    def ok(self):
        self.format_df()
        self.draw_chart()


if __name__ == "__main__":
    ctypes.windll.shcore.SetProcessDpiAwareness(1)
    root= Tk()

    app= StockApp(root)
    root.title('Stock prices')
    mainloop()

Upvotes: 4

Related Questions