pythonmatplotlibtkinter

Not able to show more than 4-5 bar charts using matplotlib


I am trying to display 4-5 Matplotlib bar chart in a Tkinter window. But some how only 3 are fitting inside the frame. Please find the below code.

import tkinter as tk
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.figure import Figure
import matplotlib.
# --- main ---

x = [1, 2, 3, 4]
y = [1, 2, 3, 4]
AS = [10 / 2 ** 0]

# ---

root = tk.Tk()
root.geometry("1000x1000")
root.title("eggs")

# ---

frame_top = tk.Frame(root)
frame_top.pack(fill='both', expand=True)

fig = Figure(dpi=100)  # figsize=(10, 6),
fig.add_subplot(111).plot(x, y)
# fig.add_subplot(111).plot(AS)

canvas = FigureCanvasTkAgg(fig, master=frame_top)  # A tk.DrawingArea.
canvas.draw()
canvas.get_tk_widget().pack(side="left",fill='both', expand=True)

fig1 = Figure(dpi=100)  # figsize=(10, 6),
fig1.add_subplot(111).plot(x, y)
# fig.add_subplot(111).plot(AS)

canvas1 = FigureCanvasTkAgg(fig1, master=frame_top)  # A tk.DrawingArea.
canvas1.draw()
canvas1.get_tk_widget().pack(side="left",fill='both', expand=True)

fig2 = Figure(dpi=100)  # figsize=(10, 6),
fig2.add_subplot(111).plot(x, y)
# fig.add_subplot(111).plot(AS)

canvas2 = FigureCanvasTkAgg(fig2, master=frame_top)  # A tk.DrawingArea.
canvas2.draw()
canvas2.get_tk_widget().pack(side="left",fill='both', expand=True)


fig3 = Figure(dpi=100)  # figsize=(10, 6),
fig3.add_subplot(111).plot(x, y)
# fig.add_subplot(111).plot(AS)

canvas3 = FigureCanvasTkAgg(fig3, master=frame_top)  # A tk.DrawingArea.
canvas3.draw()
canvas3.get_tk_widget().pack(side="left",fill='both', expand=True)
# toolbar = NavigationToolbar2Tk(canvas, frame_top)
# toolbar.update()

# tool = tk.Button(toolbar, text="my tool")
# tool.pack(side='left')#, fill='x', expand=True)


root.mainloop()

I tried to use the horizontal scroll bar, but somehow it is also not working. If there are any other suggestions, or and any other way to show multiple bar charts inside a frame.


Solution

  • On my Linux helps when I assign any value (event 1) to all canvas' width

    canvas.get_tk_widget()['width'] = 1
    canvas1.get_tk_widget()['width'] = 1
    canvas2.get_tk_widget()['width'] = 1
    canvas3.get_tk_widget()['width'] = 1
    

    Minimal working code with 10 plots

    import tkinter as tk
    from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
    from matplotlib.figure import Figure
    import matplotlib.pyplot as plt
    
    # --- main ---
    
    x = [1, 2, 3, 4]
    y = [1, 2, 3, 4]
    AS = [10 / 2 ** 0]
    
    # ---
    
    root = tk.Tk()
    root.geometry("1000x1000")
    root.title("eggs")
    
    # ---
    
    frame_top = tk.Frame(root)
    frame_top.pack(fill='both', expand=True)
    
    plots = []
    
    for _ in range(10):
        fig = Figure(dpi=100)  # figsize=(10, 6),
        fig.add_subplot(111).plot(x, y)
        # fig.add_subplot(111).plot(AS)
    
        canvas = FigureCanvasTkAgg(fig, master=frame_top)  # A tk.DrawingArea.
        canvas.draw()
        canvas.get_tk_widget().pack(side="left", fill='both', expand=True)
        canvas.get_tk_widget()['width'] = 1
        plots.append({'fig': fig, 'canvas': canvas})
    
    root.mainloop()
    

    Result:

    enter image description here

    But it not so useful because all plots are small.


    It also works when I put all plots on one canvas

    for i in range(10):
        fig.add_subplot(1, 10, i+1).plot(x, y)
    

    But it may not be useful if you plan to add toolbars or move/close some plots.

    Full working code:

    import tkinter as tk
    from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
    from matplotlib.figure import Figure
    import matplotlib.pyplot as plt
    
    # --- main ---
    
    x = [1, 2, 3, 4]
    y = [1, 2, 3, 4]
    AS = [10 / 2 ** 0]
    
    # ---
    
    root = tk.Tk()
    root.geometry("1000x1000")
    root.title("eggs")
    
    # ---
    
    frame_top = tk.Frame(root)
    frame_top.pack(fill='both', expand=True)
    
    plots = []
    
    fig = Figure(dpi=100)  # figsize=(10, 6),
    canvas = FigureCanvasTkAgg(fig, master=frame_top)  # A tk.DrawingArea.
    canvas.draw()
    canvas.get_tk_widget().pack(side="left", fill='both', expand=True)
    
    for i in range(10):
        line = fig.add_subplot(1, 10, i+1).plot(x, y)
        plots.append(line)
    
    root.mainloop()
    

    enter image description here


    Using grid() I had to set correct value for width and height - so I used event <Configure> to do it when it create (or resize) window.

    But with grid() I can put it in many rows.

    import tkinter as tk
    from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
    from matplotlib.figure import Figure
    import matplotlib.pyplot as plt
    
    # --- functions ---
    
    def resize_widgets(event):
        #print(dir(event))
        print(event.widget, event.width/5, event.height/2)
    
        for row in range(2):
            for col in range(5):
                widget = plots[(row,col)]['canvas'].get_tk_widget()
                widget['width'] = event.width / 5 
                widget['height'] = event.height / 2 
            
        
    # --- main ---
    
    x = [1, 2, 3, 4]
    y = [1, 2, 3, 4]
    AS = [10 / 2 ** 0]
    
    # ---
    
    root = tk.Tk()
    root.geometry("1000x1000")
    root.title("eggs")
    
    # ---
    
    frame_top = tk.Frame(root)
    frame_top.pack(fill='both', expand=True)
    
    plots = {}
    
    for row in range(2):
        for col in range(5):
            fig = Figure(dpi=100)  # figsize=(10, 6),
            fig.add_subplot(111).plot(x, y)
            #fig.add_subplot(212).plot(x, AS)
    
            canvas = FigureCanvasTkAgg(fig, master=frame_top)  # A tk.DrawingArea.
            canvas.draw()
            canvas.get_tk_widget().grid(row=row, column=col)
            #canvas.get_tk_widget()['width'] = 200
            #canvas.get_tk_widget()['height'] = 500
            
            plots[(row,col)] = {'fig': fig, 'canvas': canvas}
    
    frame_top.bind('<Configure>', resize_widgets)
    
    root.mainloop()
    

    enter image description here


    Works also when I set weight=1 to every rows and columns in grid() - and it doesn't need <Configure> - so it seems the simplest method.

    for row in range(2):
        frame_top.rowconfigure(row, weight=1)
    
    for col in range(5):
        frame_top.columnconfigure(col, weight=1)
    

    Full working code:

    import tkinter as tk
    from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
    from matplotlib.figure import Figure
    import matplotlib.pyplot as plt
    
    # --- main ---
    
    x = [1, 2, 3, 4]
    y = [1, 2, 3, 4]
    AS = [10 / 2 ** 0]
    
    # ---
    
    root = tk.Tk()
    root.geometry("1000x1000")
    root.title("eggs")
    
    # ---
    
    frame_top = tk.Frame(root)
    frame_top.pack(fill='both', expand=True)
    
    plots = {}
    
    for row in range(2):
        frame_top.rowconfigure(row, weight=1)
    
    for col in range(5):
        frame_top.columnconfigure(col, weight=2)
    
    frame_top.columnconfigure(0, weight=1) # resize first column
    frame_top.columnconfigure(4, weight=1) # resize last column
    
    for row in range(2):
        for col in range(5):
            fig = Figure(dpi=100)  # figsize=(10, 6),
            fig.add_subplot(111).plot(x, y)
            #fig.add_subplot(212).plot(x, AS)
    
            canvas = FigureCanvasTkAgg(fig, master=frame_top)  # A tk.DrawingArea.
            canvas.draw()
            canvas.get_tk_widget().grid(row=row, column=col)
            
            plots[(row,col)] = {'fig': fig, 'canvas': canvas}
    
    root.mainloop()
    

    It allows to set different sizes using proportions - e.g. first and last column 2 times wider.

    for col in range(5):
        frame_top.columnconfigure(col, weight=2)
    
    frame_top.columnconfigure(0, weight=1)
    frame_top.columnconfigure(4, weight=1)
    

    enter image description here


    Other solution could be putting Frame on tkinter.Canvas and use `Scrollers to move it on canvas - this way plot can be bigger then windows size. But this needs more work - e.g my class ScrolledFrame on GitHub.