Search code examples
pythonpython-3.xmatplotlibtkinter

Scrollbar in tkinter is not working with canvas


I am new to the python and tkinter, I want to create Dashboard using tkinter. I wrote the below code to add the graphs in tkinter using the Matplotlib. As I have 4 graphs and not able to fit all, so added a horizontal scroll bar but somehow it is not working. Please find the below code.

import tkinter as tk
from tkinter import ttk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
import numpy as np

def calculate_gap(sum_of_demand, allocated, capacity):
    if sum_of_demand - allocated < 0:
        sum_of_not_demanded = abs(sum_of_demand - allocated)
    elif capacity - sum_of_demand > 0:
        sum_of_not_demanded = capacity - sum_of_demand
    else:
        sum_of_not_demanded = 0

    if capacity - allocated > 0:
        spare_capacity = capacity - allocated
    else:
        spare_capacity = 0

    if sum_of_demand - capacity > 0:
        skill_gap = sum_of_demand - capacity
    else:
        skill_gap = 0

    return sum_of_not_demanded, spare_capacity, skill_gap


def h_scroll(*args):
    print("In scroll")
    graph1_canvas.get_tk_widget().xview(*args)
    graph2_canvas.get_tk_widget().xview(*args)
    graph3_canvas.get_tk_widget().xview(*args)
    graph4_canvas.get_tk_widget().xview(*args)


def test_gap():
    sum_of_demand = 256
    allocated = 73
    capacity = 66
    sum_of_not_demanded, spare_capacity, skill_gap = calculate_gap(sum_of_demand, allocated, capacity)

    width = 0.35
    below = np.array([sum_of_demand, allocated, capacity])
    above = np.array([sum_of_not_demanded, spare_capacity, skill_gap])

    weight_counts = {
        "Below": np.array([sum_of_demand, allocated, capacity]),
        "Above": np.array([sum_of_not_demanded, spare_capacity, skill_gap])
    }
    title = ['Demand', 'Allocated', 'Capacity']

    bottom = np.zeros(3)
    for boolean, weight_count in weight_counts.items():
        p = graph4_ax.bar(title, weight_count, width, label=boolean, bottom=bottom)
        bottom += weight_count

    graph4_canvas.draw()
    # canvas1.get_tk_widget().pack(side="left",fill="both",expand=True)
    graph4_canvas.get_tk_widget().pack(fill="both", expand=True, side="left")
    graph4_canvas.get_tk_widget().config(scrollregion=graph4_canvas.get_tk_widget().bbox("all"))
    graph4_canvas.get_tk_widget().config(xscrollcommand=horizontal_scrollbar.set,
                                         scrollregion=graph4_canvas.get_tk_widget().bbox("all"))
    # test_canvas.get_tk_widget().config(xscrollcommand=horizontal_scrollbar.set)
    # test_canvas.get_tk_widget().bind("<<Key>>", pop_up)


def graph_three():
    sum_of_demand = 256
    allocated = 73
    capacity = 66

    sum_of_not_demanded, spare_capacity, skill_gap = calculate_gap(sum_of_demand, allocated, capacity)

    if sum_of_demand - capacity > 0:
        skill_gap = sum_of_demand - capacity
    else:
        skill_gap = 0

    width = 0.1

    weight_counts = {
        "Below": np.array([sum_of_demand, allocated, capacity]),
        "Above": np.array([sum_of_not_demanded, spare_capacity, skill_gap])
    }
    title = ['Demand', 'Allocated', 'Capacity']

    bottom = np.zeros(3)
    for boolean, weight_count in weight_counts.items():
        p = graph_ax.bar(title, weight_count, width, label=boolean, bottom=bottom)
        bottom += weight_count

    graph3_canvas.draw()
    # canvas1.get_tk_widget().pack(side="left",fill="both",expand=True)
    graph3_canvas.get_tk_widget().pack(fill="both", expand=True, side="left")
    graph3_canvas.get_tk_widget().config(scrollregion=graph3_canvas.get_tk_widget().bbox("all"))
    graph3_canvas.get_tk_widget().config(xscrollcommand=horizontal_scrollbar.set,
                                         scrollregion=graph3_canvas.get_tk_widget().bbox("all"))
    # mobile_canvas.get_tk_widget().config(xscrollcommand=horizontal_scrollbar.set)


def graph_two():
    sum_of_demand = 830
    allocated = 313
    capacity = 339
    sum_of_not_demanded, spare_capacity, skill_gap = calculate_gap(sum_of_demand, allocated, capacity)

    width = 0.35

    weight_counts = {
        "Below": np.array([sum_of_demand, allocated, capacity]),
        "Above": np.array([sum_of_not_demanded, spare_capacity, skill_gap])
    }
    title = ['Demand', 'Allocated', 'Capacity']

    bottom = np.zeros(3)
    for boolean, weight_count in weight_counts.items():
        p = graph2_ax.bar(title, width, label=boolean, bottom=bottom)
        bottom += weight_count

    graph2_canvas.draw()
    # canvas1.get_tk_widget().pack(side="left",fill="both",expand=True)
    graph2_canvas.get_tk_widget().pack(fill="both", expand=True, side="left")
    graph2_canvas.get_tk_widget().config(scrollregion=graph2_canvas.get_tk_widget().bbox("all"))
    graph2_canvas.get_tk_widget().config(xscrollcommand=horizontal_scrollbar.set,
                                         scrollregion=graph2_canvas.get_tk_widget().bbox("all"))
    # be_canvas.get_tk_widget().config(xscrollcommand=horizontal_scrollbar.set)


def graph_one():
    sum_of_demand = 1631
    allocated = 778
    capacity = 795
    sum_of_not_demanded, spare_capacity, skill_gap = calculate_gap(sum_of_demand, allocated, capacity)

    width = 0.35

    weight_counts = {
        "Below": np.array([sum_of_demand, allocated, capacity]),
        "Above": np.array([sum_of_not_demanded, spare_capacity, skill_gap])
    }
    title = ['Demand', 'Allocated', 'Capacity']

    bottom = np.zeros(3)
    for boolean, weight_count in weight_counts.items():
        p = graph1_ax.bar(title, weight_count, width, label=boolean, bottom=bottom)
        bottom += weight_count

    graph1_canvas.draw()
    graph1_canvas.get_tk_widget().config(scrollregion=graph1_canvas.get_tk_widget().bbox("all"))
    graph1_canvas.get_tk_widget().config(xscrollcommand=horizontal_scrollbar.set,
                                         scrollregion=graph1_canvas.get_tk_widget().bbox("all"))

    # horizontal_scrollbar.config(command=h_scroll())


root = tk.Tk()

# Main Frame
frame = ttk.Frame(root)
frame.pack()
# Data Frame
graph_frame = ttk.Labelframe(frame, text="Skill Gap")
graph_frame.grid(side = tk.RIGHT, expand = True, fill = tk.BOTH)

horizontal_scrollbar = ttk.Scrollbar(graph_frame, orient=tk.HORIZONTAL, command=h_scroll)
horizontal_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)

# Canvas
graph1_fig, graph1_ax = plt.subplots(dpi=100)
graph1_canvas = FigureCanvasTkAgg(graph1_fig, graph_frame)
horizontal_scrollbar.config(command=graph1_canvas.get_tk_widget().xview)
graph1_canvas.get_tk_widget().config(yscrollcommand=horizontal_scrollbar.set)
# Canvas
graph2_fig, graph2_ax = plt.subplots(dpi=100)
graph2_canvas = FigureCanvasTkAgg(graph2_fig, graph_frame)

# Canvas
graph3_fig, graph_ax = plt.subplots(dpi=100)
graph3_canvas = FigureCanvasTkAgg(graph3_fig, graph_frame)

#  Canvas
graph4_fig, graph4_ax = plt.subplots(dpi=100)
graph4_canvas = FigureCanvasTkAgg(graph4_fig, graph_frame)

graph_one()
graph_two()
graph_three()
test_gap()
horizontal_scrollbar.config(command=h_scroll)

tk.mainloop()

I am expecting horizontal scroll bar to be work. And able to scroll though all the graphs.


Solution

  • Tkitner can scroll only Canvas, Listbox and Text but not Frame

    If you want to scroll all elements then you have to put them in one tkinter.Frame and this frame put on tkinter.Canvas and use tkinter.Scrollbar to scroll this frame on canvas.

    I took one of my code from answer to your previous question

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

    and I added scrolled cavnas

    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(event): 
        dashboard_canvas.configure(scrollregion=dashboard_canvas.bbox('all'))
    
    # --- main ---
    
    x = [1, 2, 3, 4]
    y = [1, 2, 3, 4]
    AS = [10 / 2 ** 0]
    
    # ---
    
    root = tk.Tk()
    root.geometry("1000x1000")
    root.title("eggs")
    
    # ---
    
    # canvas
    dashboard_canvas = tk.Canvas(root)#, bg='#00c000')  # background color only to test its size
    dashboard_inner_frame = tk.Frame(dashboard_canvas)
    inner_frame_id = dashboard_canvas.create_window((0,0), window=dashboard_inner_frame, anchor='nw')
    dashboard_canvas.pack(fill='both', expand=True)
    
    # scrollbar
    dasboard_scrollbar_x = tk.Scrollbar(root, orient='horizontal')
    dasboard_scrollbar_x.pack(fill='x')
    
    # join widgets 
    dashboard_canvas.configure(xscrollcommand=dasboard_scrollbar_x.set)
    dasboard_scrollbar_x['command'] = dashboard_canvas.xview
    
    # resize scrollregion on canvas when plots will have size (and it will be after starting program and drawing plots)
    dashboard_inner_frame.bind('<Configure>', resize)
    
    #---
    
    # put on dashboard_inner_frame
    frame_top = tk.Frame(dashboard_inner_frame, width=2000)
    frame_top.pack(fill='both', expand=True)
    
    plots = []
    
    for index in range(10):
        fig = Figure(dpi=100)
        ax = fig.add_subplot(111)
        ax.plot(x, y)
        fig.suptitle(f"Plot {index+1}")
    
        canvas = FigureCanvasTkAgg(fig, master=frame_top)
        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()
    

    Version with code from current question

    import tkinter as tk
    from tkinter import ttk
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
    import matplotlib.pyplot as plt
    import numpy as np
    
    
    # --- functions ---
    
    def resize(event): 
        dashboard_canvas.configure(scrollregion=dashboard_canvas.bbox('all'))
    
    
    def calculate_gap(sum_of_demand, allocated, capacity):
        if sum_of_demand - allocated < 0:
            sum_of_not_demanded = abs(sum_of_demand - allocated)
        elif capacity - sum_of_demand > 0:
            sum_of_not_demanded = capacity - sum_of_demand
        else:
            sum_of_not_demanded = 0
    
        if capacity - allocated > 0:
            spare_capacity = capacity - allocated
        else:
            spare_capacity = 0
    
        if sum_of_demand - capacity > 0:
            skill_gap = sum_of_demand - capacity
        else:
            skill_gap = 0
    
        return sum_of_not_demanded, spare_capacity, skill_gap
    
    
    def h_scroll(*args):
        print("In scroll")
        graph1_canvas.get_tk_widget().xview(*args)
        graph2_canvas.get_tk_widget().xview(*args)
        graph3_canvas.get_tk_widget().xview(*args)
        graph4_canvas.get_tk_widget().xview(*args)
    
    
    def test_gap():
        sum_of_demand = 256
        allocated = 73
        capacity = 66
        sum_of_not_demanded, spare_capacity, skill_gap = calculate_gap(sum_of_demand, allocated, capacity)
    
        width = 0.35
        below = np.array([sum_of_demand, allocated, capacity])
        above = np.array([sum_of_not_demanded, spare_capacity, skill_gap])
    
        weight_counts = {
            "Below": np.array([sum_of_demand, allocated, capacity]),
            "Above": np.array([sum_of_not_demanded, spare_capacity, skill_gap])
        }
        title = ['Demand', 'Allocated', 'Capacity']
    
        bottom = np.zeros(3)
        for boolean, weight_count in weight_counts.items():
            p = graph4_ax.bar(title, weight_count, width, label=boolean, bottom=bottom)
            bottom += weight_count
    
        graph4_canvas.draw()
        # canvas1.get_tk_widget().pack(side="left",fill="both",expand=True)
        graph4_canvas.get_tk_widget().pack(fill="both", expand=True, side="left")
        graph4_canvas.get_tk_widget().config(scrollregion=graph4_canvas.get_tk_widget().bbox("all"))
        # test_canvas.get_tk_widget().config(xscrollcommand=horizontal_scrollbar.set)
        # test_canvas.get_tk_widget().bind("<<Key>>", pop_up)
    
    
    def graph_three():
        sum_of_demand = 256
        allocated = 73
        capacity = 66
    
        sum_of_not_demanded, spare_capacity, skill_gap = calculate_gap(sum_of_demand, allocated, capacity)
    
        if sum_of_demand - capacity > 0:
            skill_gap = sum_of_demand - capacity
        else:
            skill_gap = 0
    
        width = 0.1
    
        weight_counts = {
            "Below": np.array([sum_of_demand, allocated, capacity]),
            "Above": np.array([sum_of_not_demanded, spare_capacity, skill_gap])
        }
        title = ['Demand', 'Allocated', 'Capacity']
    
        bottom = np.zeros(3)
        for boolean, weight_count in weight_counts.items():
            p = graph_ax.bar(title, weight_count, width, label=boolean, bottom=bottom)
            bottom += weight_count
    
        graph3_canvas.draw()
        # canvas1.get_tk_widget().pack(side="left",fill="both",expand=True)
        graph3_canvas.get_tk_widget().pack(fill="both", expand=True, side="left")
        graph3_canvas.get_tk_widget().config(scrollregion=graph3_canvas.get_tk_widget().bbox("all"))
        # mobile_canvas.get_tk_widget().config(xscrollcommand=horizontal_scrollbar.set)
    
    
    def graph_two():
        sum_of_demand = 830
        allocated = 313
        capacity = 339
        sum_of_not_demanded, spare_capacity, skill_gap = calculate_gap(sum_of_demand, allocated, capacity)
    
        width = 0.35
    
        weight_counts = {
            "Below": np.array([sum_of_demand, allocated, capacity]),
            "Above": np.array([sum_of_not_demanded, spare_capacity, skill_gap])
        }
        title = ['Demand', 'Allocated', 'Capacity']
    
        bottom = np.zeros(3)
        for boolean, weight_count in weight_counts.items():
            p = graph2_ax.bar(title, width, label=boolean, bottom=bottom)
            bottom += weight_count
    
        graph2_canvas.draw()
        # canvas1.get_tk_widget().pack(side="left",fill="both",expand=True)
        graph2_canvas.get_tk_widget().pack(fill="both", expand=True, side="left")
        graph2_canvas.get_tk_widget().config(scrollregion=graph2_canvas.get_tk_widget().bbox("all"))
        # be_canvas.get_tk_widget().config(xscrollcommand=horizontal_scrollbar.set)
    
    
    def graph_one():
        sum_of_demand = 1631
        allocated = 778
        capacity = 795
        sum_of_not_demanded, spare_capacity, skill_gap = calculate_gap(sum_of_demand, allocated, capacity)
    
        width = 0.35
    
        weight_counts = {
            "Below": np.array([sum_of_demand, allocated, capacity]),
            "Above": np.array([sum_of_not_demanded, spare_capacity, skill_gap])
        }
        title = ['Demand', 'Allocated', 'Capacity']
    
        bottom = np.zeros(3)
        for boolean, weight_count in weight_counts.items():
            p = graph1_ax.bar(title, weight_count, width, label=boolean, bottom=bottom)
            bottom += weight_count
    
        graph1_canvas.draw()
        graph1_canvas.get_tk_widget().config(scrollregion=graph1_canvas.get_tk_widget().bbox("all"))
    
        # horizontal_scrollbar.config(command=h_scroll())
    
    
    root = tk.Tk()
    
    # Main Frame
    frame = ttk.Frame(root)
    frame.pack(expand=True, fill='both')
    
    # Data Frame
    graph_frame = ttk.Labelframe(frame, text="Skill Gap")
    graph_frame.pack(expand=True, fill='both')
    
    # ---
    
    # canvas
    dashboard_canvas = tk.Canvas(graph_frame)#, bg='#00c000')  # background color only to test its size
    dashboard_inner_frame = tk.Frame(dashboard_canvas)
    inner_frame_id = dashboard_canvas.create_window((0,0), window=dashboard_inner_frame, anchor='nw')
    dashboard_canvas.pack(fill='both', expand=True)
    
    # scrollbar
    dasboard_scrollbar_x = tk.Scrollbar(graph_frame, orient='horizontal')
    dasboard_scrollbar_x.pack(fill='x')
    
    # join widgets 
    dashboard_canvas.configure(xscrollcommand=dasboard_scrollbar_x.set)
    dasboard_scrollbar_x['command'] = dashboard_canvas.xview
    
    # resize scrollregion on canvas when plots will have size (and it will be after starting program and drawing plots)
    dashboard_inner_frame.bind('<Configure>', resize)
    
    #-----
    
    # Canvas
    graph1_fig, graph1_ax = plt.subplots(dpi=100)
    graph1_canvas = FigureCanvasTkAgg(graph1_fig, dashboard_inner_frame)
    
    # Canvas
    graph2_fig, graph2_ax = plt.subplots(dpi=100)
    graph2_canvas = FigureCanvasTkAgg(graph2_fig, dashboard_inner_frame)
    
    # Canvas
    graph3_fig, graph_ax = plt.subplots(dpi=100)
    graph3_canvas = FigureCanvasTkAgg(graph3_fig, dashboard_inner_frame)
    
    #  Canvas
    graph4_fig, graph4_ax = plt.subplots(dpi=100)
    graph4_canvas = FigureCanvasTkAgg(graph4_fig, dashboard_inner_frame)
    
    graph_one()
    graph_two()
    graph_three()
    test_gap()
    
    tk.mainloop()
    

    I remove your scrollbar and I put all FigureCanvasTkAgg in the same deskboard_inner_frame and I put deskboard_canvas and deskboard_scrollbar in your label_frame.

    To make it more controlled you can keep both desckboard_cavnas and deskboard_scrollbarin one frame (e.g.deskboardordeskboard_outer_frame) and then you have to put only one element in label_frame`


    Code is long and it hard to explain - so maybe use tool like Meld to see differences between your version and my version.