You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:Tkinter中Canvas关联水平滚动条失效问题

Tkinter嵌入Matplotlib图表水平滚动失效问题解决

问题描述

我是Python及Tkinter新手,计划用Tkinter搭建仪表盘,通过Matplotlib将4个图表嵌入界面。但图表无法全部显示,添加水平滚动条后无法正常工作,希望实现滚动条正常查看所有图表,原代码如下:

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()
    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"))


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()
    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"))


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()
    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"))


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"))


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()

问题分析

  1. 无统一滚动容器:直接将图表canvas打包到graph_frame,滚动条无法同时控制多个图表的水平滚动
  2. 图表绘制错误:graph_two函数中bar方法参数错误,将width作为数据传入,导致图表显示异常
  3. 滚动绑定混乱:滚动条的command多次重复设置,未关联到统一的滚动容器
  4. 滚动范围设置时机错误:未在所有图表绘制完成后统一计算滚动区域

修正后的代码

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 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
    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()
    graph4_canvas.get_tk_widget().pack(fill="both", expand=True, side="left", padx=5, pady=5)


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)

    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 = graph3_ax.bar(title, weight_count, width, label=boolean, bottom=bottom)
        bottom += weight_count

    graph3_canvas.draw()
    graph3_canvas.get_tk_widget().pack(fill="both", expand=True, side="left", padx=5, pady=5)


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)
    # 修复bar方法参数错误:将width替换为weight_count
    for boolean, weight_count in weight_counts.items():
        p = graph2_ax.bar(title, weight_count, width, label=boolean, bottom=bottom)
        bottom += weight_count

    graph2_canvas.draw()
    graph2_canvas.get_tk_widget().pack(fill="both", expand=True, side="left", padx=5, pady=5)


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().pack(fill="both", expand=True, side="left", padx=5, pady=5)


root = tk.Tk()
root.title("Skill Gap Dashboard")

# Main Frame
frame = ttk.Frame(root)
frame.pack(fill=tk.BOTH, expand=True)

# Data Frame
graph_frame = ttk.Labelframe(frame, text="Skill Gap")
graph_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

# 滚动容器:创建canvas和内部frame
scroll_canvas = tk.Canvas(graph_frame)
inner_frame = ttk.Frame(scroll_canvas)

# 水平滚动条
horizontal_scrollbar = ttk.Scrollbar(graph_frame, orient=tk.HORIZONTAL, command=scroll_canvas.xview)
scroll_canvas.configure(xscrollcommand=horizontal_scrollbar.set)

# 布局滚动组件
horizontal_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)
scroll_canvas.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
# 将内部frame嵌入canvas
canvas_window = scroll_canvas.create_window((0, 0), window=inner_frame, anchor=tk.NW)

# 绑定内部frame大小变化事件,更新canvas滚动范围
def update_scroll_region(event):
    scroll_canvas.configure(scrollregion=scroll_canvas.bbox("all"))
inner_frame.bind("<Configure>", update_scroll_region)

# 创建图表:将图表的父容器改为inner_frame
graph1_fig, graph1_ax = plt.subplots(dpi=100)
graph1_canvas = FigureCanvasTkAgg(graph1_fig, inner_frame)

graph2_fig, graph2_ax = plt.subplots(dpi=100)
graph2_canvas = FigureCanvasTkAgg(graph2_fig, inner_frame)

graph3_fig, graph3_ax = plt.subplots(dpi=100)
graph3_canvas = FigureCanvasTkAgg(graph3_fig, inner_frame)

graph4_fig, graph4_ax = plt.subplots(dpi=100)
graph4_canvas = FigureCanvasTkAgg(graph4_fig, inner_frame)

# 绘制所有图表
graph_one()
graph_two()
graph_three()
test_gap()

tk.mainloop()

关键修改说明

  1. 统一滚动容器:新增scroll_canvas作为滚动载体,所有图表放入inner_frame后嵌入该canvas,滚动条通过控制scroll_canvas.xview实现统一滚动
  2. 修复图表绘制错误:修正graph_two中bar方法的参数,将错误传入的width替换为实际数据weight_count
  3. 优化布局逻辑:所有图表统一打包到inner_frame,横向排列并添加间距
  4. 自动更新滚动范围:绑定inner_frame的大小变化事件,实时更新scroll_canvas的滚动区域,确保滚动范围覆盖所有图表

内容的提问来源于stack exchange,提问作者Chandrakanth Reddy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 21:22:32