Browse Source

feat: 新增积分信用分析

SongZihuan 3 years ago
parent
commit
eb3e58146e
3 changed files with 424 additions and 15 deletions
  1. 131 1
      tk_ui/admin_event.py
  2. 42 11
      tk_ui/admin_menu.py
  3. 251 3
      tk_ui/admin_program.py

+ 131 - 1
tk_ui/admin_event.py

@@ -1,4 +1,5 @@
 import time
 import time
+import numpy as np
 
 
 from tool.type_ import *
 from tool.type_ import *
 from sql.db import DB
 from sql.db import DB
@@ -563,7 +564,7 @@ class UpdateGarbageCheckEvent(AdminEventBase):
 
 
 class CountThrowTimeEvent(AdminEventBase):
 class CountThrowTimeEvent(AdminEventBase):
     """
     """
-    任务: 按时段-区域统计数据
+    任务: 按时段统计数据
     """
     """
 
 
     def func(self, column: List, get_name: Callable):
     def func(self, column: List, get_name: Callable):
@@ -607,3 +608,132 @@ class CountThrowTimeEvent(AdminEventBase):
             self.station.show_warning("数据分析", "数据获取时发生错误")
             self.station.show_warning("数据分析", "数据获取时发生错误")
         else:
         else:
             self._program.show_result(res[0], res[1])
             self._program.show_result(res[0], res[1])
+
+
+class CountScoreReputationTinyEvent(AdminEventBase):
+    """
+    任务: 统计积分和用户数据
+    """
+
+    def func(self):
+        cur = self._db.search(columns=["ceil(Score / 5) AS ScoreGB",
+                                       "ceil(Reputation / 10) AS ReputationGB",
+                                       "count(UserID) AS count"],
+                              table="user",
+                              group_by=["ScoreGB", "ReputationGB"],
+                              order_by=[("ScoreGB", "DESC"), ("ReputationGB", "DESC")])
+        if cur is None:
+            return None
+        lst = np.zeros((100, 100))
+        for i in range(cur.rowcount):
+            res = cur.fetchone()
+            x = res[0]
+            y = res[1]
+            if x == 100:
+                x = 99  # 边界值
+            if y == 100:
+                y = 99  # 边界值
+            lst[x, y] = int(res[2])
+        return lst
+
+    def __init__(self, gb_station):
+        super().__init__(gb_station)
+        self.thread = None
+        self._program: Optional[admin_program.StatisticsUserTinyProgram] = None
+
+    def start(self, program):
+        self.thread = TkThreading(self.func)
+        self._program = program
+        return self
+
+    def is_end(self) -> bool:
+        return not self.thread.is_alive()
+
+    def done_after_event(self):
+        res: Optional[np.array] = self.thread.wait_event()
+        if res is None:
+            self.station.show_warning("数据分析", "数据获取时发生错误")
+        else:
+            self._program.show_result(res)
+
+
+class CountScoreReputationLargeEvent(AdminEventBase):
+    """
+    任务: 统计积分和用户数据
+    """
+
+    def func(self):
+        cur = self._db.search(columns=["ceil(Score / 50) AS ScoreGB",
+                                       "ceil(Reputation / 100) AS ReputationGB",
+                                       "count(UserID) AS count"],
+                              table="user",
+                              group_by=["ScoreGB", "ReputationGB"],
+                              order_by=[("ScoreGB", "DESC"), ("ReputationGB", "DESC")])
+        if cur is None:
+            return None
+        lst = np.zeros((10, 10))
+        for i in range(cur.rowcount):
+            res = cur.fetchone()
+            x = res[0]
+            y = res[1]
+            if x == 10:
+                x = 9  # 边界值
+            if y == 10:
+                y = 9  # 边界值
+            lst[x, y] = int(res[2])
+        return lst
+
+    def __init__(self, gb_station):
+        super().__init__(gb_station)
+        self.thread = None
+        self._program: Optional[admin_program.StatisticsUserLargeProgram] = None
+
+    def start(self, program):
+        self.thread = TkThreading(self.func)
+        self._program = program
+        return self
+
+    def is_end(self) -> bool:
+        return not self.thread.is_alive()
+
+    def done_after_event(self):
+        res: Optional[np.array] = self.thread.wait_event()
+        if res is None:
+            self.station.show_warning("数据分析", "数据获取时发生错误")
+        else:
+            self._program.show_result(res)
+
+
+class ScoreReputationDistributedEvent(AdminEventBase):
+    """
+    任务: 统计积分或信用分布
+    """
+
+    def func(self, which):
+        cur = self._db.search(columns=[which],
+                              table="user",
+                              order_by=[(which, "DESC")])
+        if cur is None:
+            return None
+        return cur.fetchall()
+
+    def __init__(self, gb_station):
+        super().__init__(gb_station)
+        self.thread = None
+        self._program: Optional[admin_program.StatisticsScoreDistributedProgram] = None
+
+    def start(self, which, program):
+        self.thread = TkThreading(self.func, which)
+        self._program = program
+        return self
+
+    def is_end(self) -> bool:
+        return not self.thread.is_alive()
+
+    def done_after_event(self):
+        res: Optional[np.array] = self.thread.wait_event()
+        if res is None:
+            self.station.show_warning("数据分析", "数据获取时发生错误")
+        else:
+            lst = [int(i[0]) for i in res]
+            self._program.show_result(lst)

+ 42 - 11
tk_ui/admin_menu.py

@@ -48,8 +48,8 @@ class AdminMenu(metaclass=abc.ABCMeta):
 class MainMenu(AdminMenu):
 class MainMenu(AdminMenu):
     def __init__(self, station, win, color):
     def __init__(self, station, win, color):
         super().__init__(station, win, color, "主页")
         super().__init__(station, win, color, "主页")
-        self.btn: List[tk.Button] = [tk.Button(self.frame) for _ in range(5)]
-        self.btn_name = ["创建", "删除", "搜索", "更新", "退出登录"]
+        self.btn: List[tk.Button] = [tk.Button(self.frame) for _ in range(6)]
+        self.btn_name = ["创建", "删除", "搜索", "更新", "数据分析", "退出登录"]
 
 
     def conf_gui(self, color: str, n: int = 1):
     def conf_gui(self, color: str, n: int = 1):
         super().conf_gui(color, n)
         super().conf_gui(color, n)
@@ -57,7 +57,8 @@ class MainMenu(AdminMenu):
         self.btn[1]['command'] = lambda: self.delete_command()
         self.btn[1]['command'] = lambda: self.delete_command()
         self.btn[2]['command'] = lambda: self.search_command()
         self.btn[2]['command'] = lambda: self.search_command()
         self.btn[3]['command'] = lambda: self.update_command()
         self.btn[3]['command'] = lambda: self.update_command()
-        self.btn[4]['command'] = lambda: self.logout_command()
+        self.btn[4]['command'] = lambda: self.statistics_command()
+        self.btn[5]['command'] = lambda: self.logout_command()
 
 
     def create_command(self):
     def create_command(self):
         self.station.to_menu("创建")
         self.station.to_menu("创建")
@@ -71,6 +72,9 @@ class MainMenu(AdminMenu):
     def update_command(self):
     def update_command(self):
         self.station.to_menu("更新")
         self.station.to_menu("更新")
 
 
+    def statistics_command(self):
+        self.station.to_menu("数据分析")
+
     def logout_command(self):
     def logout_command(self):
         self.station.logout()
         self.station.logout()
 
 
@@ -148,7 +152,7 @@ class SearchMenu(AdminMenu):
     def __init__(self, station, win, color):
     def __init__(self, station, win, color):
         super().__init__(station, win, color, "搜索")
         super().__init__(station, win, color, "搜索")
         self.btn: List[tk.Button] = [tk.Button(self.frame) for _ in range(6)]
         self.btn: List[tk.Button] = [tk.Button(self.frame) for _ in range(6)]
-        self.btn_name = ["用户", "高级搜索-用户", "垃圾袋", "高级搜索-垃圾袋", "高级搜索", "数据分析"]
+        self.btn_name = ["用户", "高级搜索-用户", "垃圾袋", "高级搜索-垃圾袋", "高级搜索"]
 
 
     def conf_gui(self, color: str, n: int = 1):
     def conf_gui(self, color: str, n: int = 1):
         super().conf_gui(color, n)
         super().conf_gui(color, n)
@@ -157,7 +161,6 @@ class SearchMenu(AdminMenu):
         self.btn[2]['command'] = lambda: self.garbage_command()
         self.btn[2]['command'] = lambda: self.garbage_command()
         self.btn[3]['command'] = lambda: self.garbage_advanced_command()
         self.btn[3]['command'] = lambda: self.garbage_advanced_command()
         self.btn[4]['command'] = lambda: self.advanced_command()
         self.btn[4]['command'] = lambda: self.advanced_command()
-        self.btn[5]['command'] = lambda: self.statistics_command()
 
 
     def user_command(self):
     def user_command(self):
         self.station.to_program("搜索用户")
         self.station.to_program("搜索用户")
@@ -174,9 +177,6 @@ class SearchMenu(AdminMenu):
     def advanced_command(self):
     def advanced_command(self):
         self.station.to_program("高级搜索")
         self.station.to_program("高级搜索")
 
 
-    def statistics_command(self):
-        self.station.to_menu("数据分析")
-
 
 
 class UpdateMenu(AdminMenu):
 class UpdateMenu(AdminMenu):
     def __init__(self, station, win, color):
     def __init__(self, station, win, color):
@@ -207,16 +207,20 @@ class UpdateMenu(AdminMenu):
 class StatisticsMenu(AdminMenu):
 class StatisticsMenu(AdminMenu):
     def __init__(self, station, win, color):
     def __init__(self, station, win, color):
         super().__init__(station, win, color, "数据分析")
         super().__init__(station, win, color, "数据分析")
-        self.btn: List[tk.Button] = [tk.Button(self.frame) for _ in range(6)]
-        self.btn_name = ["时段分析", "积分信用分析", "失信用户", "通过率"]
+        self.btn: List[tk.Button] = [tk.Button(self.frame) for _ in range(3)]
+        self.btn_name = ["时段分析", "积分信用分析", "通过率"]
 
 
     def conf_gui(self, color: str, n: int = 1):
     def conf_gui(self, color: str, n: int = 1):
         super().conf_gui(color, n)
         super().conf_gui(color, n)
         self.btn[0]['command'] = self.statistics_time_command
         self.btn[0]['command'] = self.statistics_time_command
+        self.btn[1]['command'] = self.statistics_user_command
 
 
     def statistics_time_command(self):
     def statistics_time_command(self):
         self.station.to_menu("时段分析")
         self.station.to_menu("时段分析")
 
 
+    def statistics_user_command(self):
+        self.station.to_menu("积分信用分析")
+
 
 
 class StatisticsTimeMenu(AdminMenu):
 class StatisticsTimeMenu(AdminMenu):
     def __init__(self, station, win, color):
     def __init__(self, station, win, color):
@@ -257,4 +261,31 @@ class StatisticsTimeMenu(AdminMenu):
         self.station.to_program("时段分析-详细分类")
         self.station.to_program("时段分析-详细分类")
 
 
 
 
-all_menu = [MainMenu, CreateMenu, DeleteMenu, SearchMenu, UpdateMenu, StatisticsMenu, StatisticsTimeMenu]
+class StatisticsUserMenu(AdminMenu):
+    def __init__(self, station, win, color):
+        super().__init__(station, win, color, "积分信用分析")
+        self.btn: List[tk.Button] = [tk.Button(self.frame) for _ in range(6)]
+        self.btn_name = ["积分,信用-细致", "积分,信用-大致", "积分分布", "信用分布"]
+
+    def conf_gui(self, color: str, n: int = 1):
+        super().conf_gui(color, n)
+        self.btn[0]['command'] = self.by_tiny
+        self.btn[1]['command'] = self.by_large
+        self.btn[2]['command'] = self.score
+        self.btn[3]['command'] = self.reputation
+
+    def by_tiny(self):
+        self.station.to_program("积分信用分析-细致")
+
+    def by_large(self):
+        self.station.to_program("积分信用分析-大致")
+
+    def score(self):
+        self.station.to_program("积分分布")
+
+    def reputation(self):
+        self.station.to_program("垃圾分类信用分布")
+
+
+all_menu = [MainMenu, CreateMenu, DeleteMenu, SearchMenu, UpdateMenu, StatisticsMenu, StatisticsTimeMenu,
+            StatisticsUserMenu]

+ 251 - 3
tk_ui/admin_program.py

@@ -2,10 +2,11 @@ import abc
 import tkinter as tk
 import tkinter as tk
 import tkinter.ttk as ttk
 import tkinter.ttk as ttk
 from tkinter.filedialog import askdirectory, askopenfilename, asksaveasfilename
 from tkinter.filedialog import askdirectory, askopenfilename, asksaveasfilename
-
+import random
 from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
 from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
 from matplotlib.axes import Axes
 from matplotlib.axes import Axes
 import numpy as np
 import numpy as np
+from matplotlib.colorbar import Colorbar
 from matplotlib.figure import Figure
 from matplotlib.figure import Figure
 
 
 from tool.color import random_color
 from tool.color import random_color
@@ -1964,16 +1965,27 @@ class StatisticsTimeBaseProgram(AdminProgram):
         self.plt.set_xlim(-1, 24)
         self.plt.set_xlim(-1, 24)
         self.plt.set_xticks([i for i in range(0, 24, 2)])
         self.plt.set_xticks([i for i in range(0, 24, 2)])
         self.plt.set_xticklabels([f"{i}h" for i in range(0, 24, 2)])
         self.plt.set_xticklabels([f"{i}h" for i in range(0, 24, 2)])
-        self.plt.set_title(self.program_title)
+        self.plt.set_title(f"{self.program_title}柱状图")
+
         self.canvas.draw()
         self.canvas.draw()
         self.toolbar.update()
         self.toolbar.update()
         self.update_listbox()
         self.update_listbox()
 
 
     def set_disable(self):
     def set_disable(self):
         self.export_btn['state'] = 'disable'
         self.export_btn['state'] = 'disable'
+        self.reset_btn['state'] = 'disable'
+        self.refresh_btn['state'] = 'disable'
+        self.reverse_btn['state'] = 'disable'
+        self.btn_show['state'] = 'disable'
+        self.btn_hide['state'] = 'disable'
 
 
     def reset_disable(self):
     def reset_disable(self):
         self.export_btn['state'] = 'normal'
         self.export_btn['state'] = 'normal'
+        self.reset_btn['state'] = 'normal'
+        self.refresh_btn['state'] = 'normal'
+        self.reverse_btn['state'] = 'normal'
+        self.btn_show['state'] = 'normal'
+        self.btn_hide['state'] = 'normal'
 
 
 
 
 class StatisticsTimeLocProgram(StatisticsTimeBaseProgram):
 class StatisticsTimeLocProgram(StatisticsTimeBaseProgram):
@@ -2115,6 +2127,241 @@ class StatisticsTimeDetailProgram(StatisticsTimeBaseProgram):
                 f'-{GarbageType.GarbageTypeStrList_ch[int(data_2.decode("utf-8"))]}' + f'-{i[4]}')
                 f'-{GarbageType.GarbageTypeStrList_ch[int(data_2.decode("utf-8"))]}' + f'-{i[4]}')
 
 
 
 
+class StatisticsUserBaseProgram(AdminProgram):
+    def __init__(self, station, win, color, title: str):
+        super().__init__(station, win, color, title)
+
+        self.figure_frame = tk.Frame(self.frame)
+        self.figure = Figure(dpi=100)
+        self.plt: Axes = self.figure.add_subplot(111)  # 添加子图:1行1列第1个
+
+        self.canvas = FigureCanvasTkAgg(self.figure, master=self.figure_frame)
+        self.canvas_tk = self.canvas.get_tk_widget()
+        self.toolbar = NavigationToolbar2Tk(self.canvas, self.figure_frame)
+        self.color_bar: Optional[Colorbar] = None
+        self.export_lst: Optional[np.array] = None
+
+        self.export_btn = tk.Button(self.frame)
+        self.refresh_btn = tk.Button(self.frame)
+        self._conf("#abc88b")
+        self.__conf_font()
+
+    def _conf(self, bg_color):
+        self.bg_color = bg_color
+
+    def __conf_font(self, n: int = 1):
+        self.btn_font_size = int(14 * n)
+
+    def conf_gui(self, n: int = 1):
+        self.__conf_font(n)
+        btn_font = make_font(size=self.btn_font_size)
+
+        self.figure_frame['bg'] = self.bg_color
+        self.figure_frame['bd'] = 5
+        self.figure_frame['relief'] = "ridge"
+        self.figure_frame.place(relx=0.00, rely=0.02, relwidth=1, relheight=0.88)
+
+        self.canvas_tk.place(relx=0, rely=0, relwidth=1.0, relheight=0.9)
+        self.toolbar.place(relx=0, rely=0.9, relwidth=1.0, relheight=0.1)
+
+        for btn, text, func, x in zip([self.refresh_btn, self.export_btn],
+                                      ["刷新数据", "导出数据"],
+                                      [self.refresh, self.export],
+                                      [0.34, 0.51]):
+            btn['font'] = btn_font
+            btn['bg'] = Config.tk_btn_bg
+            btn['text'] = text
+            btn['command'] = func
+            btn.place(relx=x, rely=0.91, relwidth=0.15, relheight=0.08)
+
+    def export(self):
+        ...
+
+    def refresh(self, event_class):
+        self.plt.cla()
+        if self.color_bar is not None:
+            self.color_bar.remove()
+        event = event_class(self.station).start(self)
+        self.station.push_event(event)
+
+    def set_disable(self):
+        self.export_btn['state'] = 'disable'
+        self.refresh_btn['state'] = 'disable'
+
+    def reset_disable(self):
+        self.export_btn['state'] = 'normal'
+        self.refresh_btn['state'] = 'normal'
+
+
+class StatisticsUserTinyProgram(StatisticsUserBaseProgram):
+    def __init__(self, station, win, color):
+        super(StatisticsUserTinyProgram, self).__init__(station, win, color, "积分信用分析-细致")
+
+    def show_result(self, lst: np.array):
+        self.export_lst = lst
+        x_label = [f'{i * 10}' for i in range(0, 51, 10)]
+        y_label = [f'{i * 10}' for i in range(0, 101, 20)]
+
+        im = self.plt.pcolormesh(lst, cmap='Blues')  # 用cmap设置配色方案
+
+        self.plt.set_xticks(range(0, 101, 20))  # 设置x轴刻度
+        self.plt.set_yticks(range(0, 101, 20))  # 设置y轴刻度
+        self.plt.set_xticklabels(x_label)  # 设置x轴刻度标签
+        self.plt.set_yticklabels(y_label)  # 设置y轴刻度标签
+        self.plt.set_xlabel("用户积分")  # 设置x轴刻度标签
+        self.plt.set_ylabel("垃圾分类信用")  # 设置y轴刻度标签
+
+        self.color_bar = self.figure.colorbar(im, pad=0.03, ax=self.plt)  # 设置颜色条
+        self.plt.set_title("积分信用分析-细致热图")  # 设置标题以及其位置和字体大小
+
+        self.canvas.draw()
+        self.toolbar.update()
+
+    def export(self):
+        if self.export_lst is None:
+            self.station.show_msg("保存数据", f"没有数据需要保存")
+            return
+
+        path = asksaveasfilename(title='选择CSV文件保存位置', filetypes=[("CSV", ".csv")])
+        if not path.endswith(".csv"):
+            path += ".csv"
+        with open(path, "w") as f:
+            f.write("#, " + ", ".join([f'[{i * 10} {i * 10 + 10}]' for i in range(0, 100, 1)]) + "\n")
+            for i, lst in zip(range(0, 50, 1), self.export_lst):
+                f.write(f"[{i * 10} {i * 10 + 10}], " + ", ".join([f"{a}" for a in lst]) + "\n")
+        self.station.show_msg("保存数据", f"数据导出成功\n保存位置:\n  {path}")
+
+    def to_program(self):
+        self.refresh()
+
+    def refresh(self, _=None):
+        super().refresh(tk_event.CountScoreReputationTinyEvent)
+
+
+class StatisticsUserLargeProgram(StatisticsUserBaseProgram):
+    def __init__(self, station, win, color):
+        super(StatisticsUserLargeProgram, self).__init__(station, win, color, "积分信用分析-大致")
+
+    def show_result(self, lst: np.array):
+        self.export_lst = lst
+        print("HHHH")
+        x_label = [f'{i * 10}' for i in range(0, 51, 10)]
+        y_label = [f'{i * 10}' for i in range(0, 101, 20)]
+
+        im = self.plt.pcolormesh(lst, cmap='Blues')  # 用cmap设置配色方案
+
+        self.plt.set_xticks(range(0, 11, 2))  # 设置x轴刻度
+        self.plt.set_yticks(range(0, 11, 2))  # 设置y轴刻度
+        self.plt.set_xticklabels(x_label)  # 设置x轴刻度标签
+        self.plt.set_yticklabels(y_label)  # 设置y轴刻度标签
+        self.plt.set_xlabel("用户积分")  # 设置x轴刻度标签
+        self.plt.set_ylabel("垃圾分类信用")  # 设置y轴刻度标签
+
+        self.color_bar = self.figure.colorbar(im, pad=0.03, ax=self.plt)  # 设置颜色条
+        self.plt.set_title("积分信用分析-大致热图")  # 设置标题以及其位置和字体大小
+
+        self.canvas.draw()
+        self.toolbar.update()
+
+    def export(self):
+        if self.export_lst is None:
+            self.station.show_msg("保存数据", f"没有数据需要保存")
+            return
+
+        path = asksaveasfilename(title='选择CSV文件保存位置', filetypes=[("CSV", ".csv")])
+        if not path.endswith(".csv"):
+            path += ".csv"
+        with open(path, "w") as f:
+            f.write("#, " + ", ".join([f'[{i * 10} {i * 10 + 100}]' for i in range(0, 100, 10)]) + "\n")
+            for i, lst in zip(range(0, 50, 5), self.export_lst):
+                f.write(f"[{i * 10} {i * 10 + 50}], " + ", ".join([f"{a}" for a in lst]) + "\n")
+        self.station.show_msg("保存数据", f"数据导出成功\n保存位置:\n  {path}")
+
+    def to_program(self):
+        self.refresh()
+
+    def refresh(self, _=None):
+        super().refresh(tk_event.CountScoreReputationLargeEvent)
+
+
+class StatisticsScoreDistributedProgram(StatisticsUserBaseProgram):
+    def __init__(self, station, win, color):
+        super(StatisticsScoreDistributedProgram, self).__init__(station, win, color, "积分分布")
+
+    def show_result(self, lst: np.array):
+        bins = [i for i in range(0, 501, 10)]
+        res = self.plt.hist(lst, bins)
+        self.export_lst = res[0]
+
+        self.plt.set_xlabel("用户积分")  # 设置x轴刻度标签
+        self.plt.set_ylabel("分布")  # 设置x轴刻度标签
+        self.plt.set_title("积分分布直方图")  # 设置标题以及其位置和字体大小
+        self.canvas.draw()
+        self.toolbar.update()
+
+    def export(self):
+        if self.export_lst is None:
+            self.station.show_msg("保存数据", f"没有数据需要保存")
+            return
+
+        path = asksaveasfilename(title='选择CSV文件保存位置', filetypes=[("CSV", ".csv")])
+        if not path.endswith(".csv"):
+            path += ".csv"
+        with open(path, "w") as f:
+            f.write("积分区间," + ", ".join([f'[{i * 10} {i * 10 + 100}]' for i in range(0, 501, 10)]) + "\n")
+            f.write("积分分布," + ", ".join([f'{i}' for i in self.export_lst]) + "\n")
+        self.station.show_msg("保存数据", f"数据导出成功\n保存位置:\n  {path}")
+
+    def to_program(self):
+        self.refresh()
+
+    def refresh(self, _=None):
+        self.plt.cla()
+        if self.color_bar is not None:
+            self.color_bar.remove()
+        event = tk_event.ScoreReputationDistributedEvent(self.station).start("Score", self)
+        self.station.push_event(event)
+
+
+class StatisticsReputationDistributedProgram(StatisticsUserBaseProgram):
+    def __init__(self, station, win, color):
+        super(StatisticsReputationDistributedProgram, self).__init__(station, win, color, "垃圾分类信用分布")
+
+    def show_result(self, lst: np.array):
+        bins = [i for i in range(0, 501, 10)]
+        res = self.plt.hist(lst, bins)
+        self.export_lst = res[0]
+
+        self.plt.set_xlabel("垃圾分类信用")  # 设置x轴刻度标签
+        self.plt.set_ylabel("分布")  # 设置x轴刻度标签
+        self.plt.set_title("垃圾分类信用分布直方图")  # 设置标题以及其位置和字体大小
+        self.canvas.draw()
+        self.toolbar.update()
+
+    def export(self):
+        if self.export_lst is None:
+            self.station.show_msg("保存数据", f"没有数据需要保存")
+            return
+
+        path = asksaveasfilename(title='选择CSV文件保存位置', filetypes=[("CSV", ".csv")])
+        if not path.endswith(".csv"):
+            path += ".csv"
+        with open(path, "w") as f:
+            f.write("信用区间," + ", ".join([f'[{i * 10} {i * 10 + 100}]' for i in range(0, 501, 10)]) + "\n")
+            f.write("信用分布," + ", ".join([f'{i}' for i in self.export_lst]) + "\n")
+        self.station.show_msg("保存数据", f"数据导出成功\n保存位置:\n  {path}")
+
+    def to_program(self):
+        self.refresh()
+
+    def refresh(self, _=None):
+        self.plt.cla()
+        if self.color_bar is not None:
+            self.color_bar.remove()
+        event = tk_event.ScoreReputationDistributedEvent(self.station).start("Reputation", self)
+        self.station.push_event(event)
+
+
 all_program = [WelcomeProgram, CreateNormalUserProgram, CreateManagerUserProgram, CreateAutoNormalUserProgram,
 all_program = [WelcomeProgram, CreateNormalUserProgram, CreateManagerUserProgram, CreateAutoNormalUserProgram,
                CreateGarbageProgram, DeleteUserProgram, DeleteUsersProgram, DeleteGarbageProgram,
                CreateGarbageProgram, DeleteUserProgram, DeleteUsersProgram, DeleteGarbageProgram,
                DeleteGarbageMoreProgram, DeleteAllGarbageProgram, SearchUserProgram, SearchUserAdvancedProgram,
                DeleteGarbageMoreProgram, DeleteAllGarbageProgram, SearchUserProgram, SearchUserAdvancedProgram,
@@ -2123,4 +2370,5 @@ all_program = [WelcomeProgram, CreateNormalUserProgram, CreateManagerUserProgram
                ExportGarbageProgram, ExportUserProgram, CreateUserFromCSVProgram, AboutProgram,
                ExportGarbageProgram, ExportUserProgram, CreateUserFromCSVProgram, AboutProgram,
                StatisticsTimeLocProgram, StatisticsTimeTypeProgram, StatisticsTimeTypeLocProgram,
                StatisticsTimeLocProgram, StatisticsTimeTypeProgram, StatisticsTimeTypeLocProgram,
                StatisticsTimeCheckResultProgram, StatisticsTimeCheckResultAndTypeProgram,
                StatisticsTimeCheckResultProgram, StatisticsTimeCheckResultAndTypeProgram,
-               StatisticsTimeCheckResultAndLocProgram, StatisticsTimeDetailProgram]
+               StatisticsTimeCheckResultAndLocProgram, StatisticsTimeDetailProgram, StatisticsUserTinyProgram,
+               StatisticsUserLargeProgram, StatisticsScoreDistributedProgram, StatisticsReputationDistributedProgram]