import tkinter as tk
from tkinter import ttk
import random
import datetime
import calendar
class SalesChartApp:
def __init__(self, root):
self.root = root
self.root.title("年度销售额统计")
self.root.geometry("900x600")
# 初始化数据存储
self.current_year = datetime.datetime.now().year
self.sales_data = {year: [0] * 12 for year in range(self.current_year - 2, self.current_year + 1)}
# 创建控制面板
control_frame = ttk.Frame(root, padding=10)
control_frame.pack(fill=tk.X)
# 年份选择
ttk.Label(control_frame, text="选择年份:").grid(row=0, column=0, padx=5)
self.year_var = tk.IntVar(value=self.current_year)
years = list(range(self.current_year - 2, self.current_year + 1))
self.year_combo = ttk.Combobox(
control_frame,
textvariable=self.year_var,
values=years,
state="readonly",
width=8
)
self.year_combo.grid(row=0, column=1, padx=5)
self.year_combo.bind("<<ComboboxSelected>>", self.update_chart)
# 自动更新按钮
self.auto_update_var = tk.BooleanVar(value=True)
auto_update_btn = ttk.Checkbutton(
control_frame,
text="自动年度更新",
variable=self.auto_update_var
)
auto_update_btn.grid(row=0, column=2, padx=20)
# 添加模拟数据按钮
ttk.Button(
control_frame,
text="生成模拟数据",
command=self.generate_data
).grid(row=0, column=3, padx=5)
# 创建图表画布
self.chart_canvas = tk.Canvas(root, bg="white", height=500)
self.chart_canvas.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)
# 初始化数据并绘制图表
self.generate_data()
self.update_chart()
# 设置年度自动更新
self.schedule_annual_update()
def generate_data(self):
"""生成模拟销售数据"""
for year in self.sales_data:
self.sales_data[year] = [random.randint(8000, 20000) for _ in range(12)]
def update_chart(self, event=None):
"""更新图表显示"""
selected_year = self.year_var.get()
self.draw_bar_chart(selected_year)
def draw_bar_chart(self, year):
"""在画布上绘制条形图"""
self.chart_canvas.delete("all")
data = self.sales_data[year]
# 获取画布尺寸
canvas_width = self.chart_canvas.winfo_width()
canvas_height = self.chart_canvas.winfo_height()
if canvas_width <= 1 or canvas_height <= 1:
return # 防止初始化时尺寸错误
# 图表参数
margin = 60
chart_width = canvas_width - 2 * margin
chart_height = canvas_height - 2 * margin
bar_width = chart_width / 15
gap = bar_width / 2
# 计算最大值用于缩放
max_value = max(data) * 1.1
# 绘制坐标轴
self.chart_canvas.create_line(margin, margin, margin, canvas_height - margin, width=2) # Y轴
self.chart_canvas.create_line(margin, canvas_height - margin, canvas_width - margin, canvas_height - margin, width=2) # X轴
# 绘制标题
title = f"{year}年各月销售额统计(单位:元)"
self.chart_canvas.create_text(canvas_width/2, 20, text=title, font=("Arial", 14, "bold"))
# 绘制Y轴刻度和标签
for i in range(6):
y = canvas_height - margin - (i * chart_height / 5)
value = int(max_value * i / 5)
self.chart_canvas.create_line(margin - 5, y, margin, y, width=1)
self.chart_canvas.create_text(margin - 10, y, text=f"{value:,}", anchor=tk.E, font=("Arial", 9))
# 绘制条形和标签
for i, month in enumerate(calendar.month_abbr[1:]): # 跳过空值
# 计算条形位置
x1 = margin + gap + i * (bar_width + gap)
x2 = x1 + bar_width
y1 = canvas_height - margin
y2 = canvas_height - margin - (data[i] / max_value) * chart_height
# 绘制条形
bar_color = "#4e79a7" # 蓝色
self.chart_canvas.create_rectangle(x1, y1, x2, y2, fill=bar_color, outline="black")
# 显示月份
self.chart_canvas.create_text(
(x1 + x2) / 2,
canvas_height - margin + 15,
text=month,
font=("Arial", 10)
)
# 显示销售额数值
self.chart_canvas.create_text(
(x1 + x2) / 2,
y2 - 15,
text=f"{data[i]:,}",
font=("Arial", 9),
fill="white"
)
# 添加悬停效果
tag_name = f"bar_{i}"
self.chart_canvas.create_rectangle(
x1, y1, x2, y2,
fill="",
outline="",
tags=(tag_name,)
)
self.chart_canvas.tag_bind(
tag_name,
"<Enter>",
lambda e, val=data[i], month=month: self.show_tooltip(e, val, month)
)
self.chart_canvas.tag_bind(tag_name, "<Leave>", self.hide_tooltip)
def show_tooltip(self, event, value, month):
"""显示悬停提示"""
self.tooltip = tk.Toplevel(self.root)
self.tooltip.wm_overrideredirect(True)
self.tooltip.wm_geometry(f"+{event.x_root+10}+{event.y_root+10}")
label = tk.Label(
self.tooltip,
text=f"{month}: ¥{value:,}",
bg="lightyellow",
relief="solid",
borderwidth=1
)
label.pack()
def hide_tooltip(self, event):
"""隐藏悬停提示"""
if hasattr(self, 'tooltip') and self.tooltip:
self.tooltip.destroy()
def schedule_annual_update(self):
"""安排年度自动更新任务"""
now = datetime.datetime.now()
# 计算到明年1月1日的时间(毫秒)
next_year = now.year + 1
next_january = datetime.datetime(next_year, 1, 1)
delay_ms = int((next_january - now).total_seconds() * 1000)
# 安排更新任务
self.root.after(delay_ms, self.annual_update)
def annual_update(self):
"""年度自动更新"""
if self.auto_update_var.get():
# 更新年份范围
self.current_year += 1
new_years = list(range(self.current_year - 2, self.current_year + 1))
# 添加新一年的数据
self.sales_data[self.current_year] = [random.randint(8000, 20000) for _ in range(12)]
# 移除旧数据(保留最近3年)
for year in list(self.sales_data.keys()):
if year < self.current_year - 2:
del self.sales_data[year]
# 更新下拉框
self.year_combo['values'] = new_years
self.year_var.set(self.current_year)
# 更新图表
self.update_chart()
# 重新安排下一次更新
self.schedule_annual_update()
if __name__ == "__main__":
root = tk.Tk()
app = SalesChartApp(root)
root.mainloop()
加上填写数据