Ejemplo n.º 1
0
class WeekeryApp(Tk):
    def __init__(self):
        super().__init__()
        self.canvas_show = 'pie'  # or 'sleep'

        self.withdraw()
        splash = Splash(self)
        splash.pgb['maximum'] = 5

        import matplotlib
        import math
        self.math = math
        import matplotlib.pyplot as plt
        self.plt = plt
        from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
        from tkinter.ttk import Progressbar, Style

        splash.pgb['value'] = 1
        splash.label.image = splash.gif1
        splash.update()

        plt.style.use('ggplot')

        matplotlib.rcParams['font.family'] = 'SimHei'
        self.Set3 = plt.cm.Set3(range(10))
        self.Paired = plt.cm.Paired(range(10))

        splash.pgb['value'] = 2
        splash.label.image = splash.gif1
        splash.update()

        # +++++++++++++++
        # +  GUI_setup  +
        # +++++++++++++++
        from tkinter import Frame, Text
        from tkinter import ttk
        from tkinter.ttk import Button
        self.title('Weekery')
        #Style().theme_use('clam')
        '''
        root
        |- Progressbar.bottom
        |- Frame_Left
        |  |- Frame_btn_left.top
        |  |  |- left(btn{d,w,m,y})
        |  |  |- right(btn{sleep,freq_pic,freq_bar})
        |  |  |- Frame_btn_mid
        |  |     |- <.left
        |  |     |- +.left
        |  |     |- >.right
        |  |     |- -.right
        |  |     |- calendar.middle
        |  |     |- label_date
        |  |- fig_up.top
        |  |- fig_down.top
        |- Frame_Right
           |- Frame_btn_right
           |  |- btn_setting.right
           |  |- btn_reload.right
           |- Text.bottom.top expand=both
        '''
        # ====== Frames ======
        self.frame_left = Frame(self)

        self.frame_right = Frame(self)

        self.frame_btn_left = Frame(self.frame_left)
        self.frame_btn_left.config(bg='white')
        self.frame_btn_mid = Frame(self.frame_btn_left)

        self.frame_btn_right = Frame(self.frame_right)
        self.frame_btn_right.config(bg='white')

        # ====== Buttons ======
        ttk.Style().configure("TButton", background='white')
        ttk.Style().configure("symbol.TButton", font=(20))
        #ttk.Style().configure("TButton", foreground='white')
        self.btn_days = Button(self.frame_btn_left, text='日', command=self.days, width=3)
        self.btn_days.config(state='disable') #bg='white', 
        self.btn_weeks = Button(self.frame_btn_left, text='周', command=self.weeks, width=3)
        #self.btn_weeks.config(bg='white')
        self.btn_months = Button(self.frame_btn_left, text='月', command=self.months, width=3)
        #self.btn_months.config(bg='white')
        self.btn_years = Button(self.frame_btn_left, text='年', command=self.years, width=3)
        #self.btn_years.config(bg='white')
        self.btn_switch_freq_pie = Button(self.frame_btn_left, text='饼图', command=self.pie, width=6)
        #self.btn_switch_freq_pie.config(bg='white')
        self.btn_switch_sleep = Button(self.frame_btn_left, text='睡眠', command=self.sleep, width=6)
        #self.btn_switch_sleep.config(bg='white')
        self.btn_switch_freq_bar = Button(self.frame_btn_left, text='词频', command=self.bar, width=6)
        #self.btn_switch_freq_bar.config(bg='white')

        self.btn_previous = Button(self.frame_btn_mid, text='◀', style='symbol.TButton', command=self.previous, width=2)
        #self.btn_previous.config(bg='white')
        self.btn_backward = Button(self.frame_btn_mid, text='▶', style='symbol.TButton',command=self.backward, width=2)
        #self.btn_backward.config(bg='white')
        self.btn_calendar = Button(self.frame_btn_mid, text="▦", style='symbol.TButton',command=self.ask_selected_date, width=2)
        #self.btn_calendar.config(bg='white')
        self.btn_plus = Button(self.frame_btn_mid, text='+', style='symbol.TButton', command=self.plus, width=2)
        #self.btn_plus.config(bg='white')
        self.btn_minus = Button(self.frame_btn_mid, text='-',style='symbol.TButton', command=self.minus, width=2)
        #self.btn_minus.config(bg='white')

        self.btn_reload = Button(self.frame_btn_right, text='重载', command=self.reload, width=6)
        #self.btn_reload.config(bg='white')
        self.btn_settings = Button(self.frame_btn_right, text='设置', command=self.settings, width=6)
        #self.btn_settings.config(bg='white')

        # ====== Others ======
        self.fig_up = plt.figure(figsize=(7, 3))
        self.fig_down = plt.figure(figsize=(7, 3))
        self.canvas_up = FigureCanvasTkAgg(self.fig_up, master=self.frame_left)
        self.canvas_down = FigureCanvasTkAgg(self.fig_down, master=self.frame_left)

        self.pgb = Progressbar(self, orient='horizontal', length=1000, mode='determinate')

        self.notes = Text(self.frame_right, width=50)
        self.notes.config(bg='azure')
        
        self.label_date = Label(self.frame_btn_mid, text='加载中...', width=15)

        splash.pgb['value'] = 3
        splash.label.image = splash.gif1
        splash.update()

        # ++++++++++++++++++
        # +  GUI Packing   +
        # ++++++++++++++++++
        # level-1
        self.pgb.pack(side='bottom', fill='both')

        # level-1
        self.frame_left.pack(side='left', fill='both', expand='YES')
        # # level-2
        self.frame_btn_left.pack(side='top', fill='both')
        self.canvas_up.get_tk_widget().pack(side='top', fill='both', expand='YES')
        self.canvas_down.get_tk_widget().pack(side='top', fill='both', expand='YES')
        # # # level-3
        self.btn_days.pack(side='left')
        self.btn_weeks.pack(side='left')
        self.btn_months.pack(side='left')
        self.btn_years.pack(side='left')
        self.btn_switch_freq_bar.pack(side='right')
        self.btn_switch_freq_pie.pack(side='right')
        self.btn_switch_sleep.pack(side='right')
        self.frame_btn_mid.pack(side='top')
        # # # # level-4
        self.btn_previous.pack(side='left')
        self.btn_minus.pack(side='left')
        self.btn_backward.pack(side='right')
        self.btn_plus.pack(side='right')
        self.btn_calendar.pack(side='left')
        self.label_date.pack(side='right')

        # level-1
        self.frame_right.pack(side='left', fill='both', expand='YES')
        # # level-2
        self.frame_btn_right.pack(side='top', fill='both')
        # # # level-3
        self.btn_settings.pack(side='right')
        self.btn_reload.pack(side='right')
        # # level-2
        self.notes.pack(side='top', fill='both', expand='YES')

        splash.pgb['value'] = 4
        splash.label.image = splash.gif1
        splash.update()

        # ++++++++++++++++++
        # +  Import class  +
        # ++++++++++++++++++
        import sqlite3
        from config import Config
        from controls import Controls
        from load_data import wiz_week_index, read_data
        splash.pgb['value'] = 5
        splash.label.image = splash.gif1
        splash.update()
        splash.destroy()
        # ============= Show Main GUI ==============
        self.protocol('WM_DELETE_WINDOW', self.close_window)
        self.wm_state('zoomed')  # maximize windows
        self.deiconify()

        self.cfg = Config(self)
        
        # user choose to cancle in configuration
        if self.cfg.cancel:
            return
            
        self.db_path = self.cfg.cache_dir + '/weekery.db'
        self.conn = sqlite3.connect(self.db_path)

        self.id_filenames, self.id_dates = wiz_week_index(self.cfg)

        if self.cfg.last_read == 20160000:
            read_data(self, self.cfg, self.pgb, self.id_dates, self.id_filenames, 'all')
        else:
            read_data(self, self.cfg, self.pgb, self.id_dates, self.id_filenames, dialog=False)

        self.controls = Controls(self.conn)
        self.conn.commit()

        self.colors = {v: (int(f[4:7])/255, int(f[9:12])/255, int(f[14:17])/255, 1) for f, v in self.cfg.color_kind.items()}
        self._paint()

    def ask_selected_date(self):
        select_calendar = CalendarPopup()
        self.wait_window(select_calendar)
        if select_calendar.selected_days:
            self.controls.y = int(select_calendar.selected_days.year)
            self.controls.m = int(select_calendar.selected_days.month)
            self.controls.w = int(select_calendar.selected_days.strftime("%W"))
            self.controls.d = int(select_calendar.selected_days.strftime("%d"))
            self.controls.date_range()
            self.controls.query_data()
            self._paint()

        return select_calendar.selected_days

    def reload(self):
        reload_option = ReloadOption()
        self.wait_window(reload_option)
        if reload_option.reload_mode:
            if reload_option.reload_mode == '全部重载':
                read_data(self, self.cfg, self.pgb, self.id_dates, self.id_filenames, 'all', dialog=False)
                showinfo('提示', '全部数据重载完成!')
                self.weeks()
            elif reload_option.reload_mode == '最近一周':
                read_data(self, self.cfg, self.pgb, self.id_dates, self.id_filenames, 1, dialog=False)
                showinfo('提示', '最近一周数据重载完成!')
                self.weeks()
            elif reload_option.reload_mode == '最近一个月':
                read_data(self, self.cfg, self.pgb, self.id_dates, self.id_filenames, 4, dialog=False)
                showinfo('提示', '最近一个月数据重载完成!')
                self.weeks()
            elif reload_option.reload_mode == '最近三个月':
                read_data(self, self.cfg, self.pgb, self.id_dates, self.id_filenames, 12, dialog=False)
                showinfo('提示', '最近三个月数据重载完成!')
                self.weeks()
            elif reload_option.reload_mode == '最近半年':
                read_data(self, self.cfg, self.pgb, self.id_dates, self.id_filenames, 26, dialog=False)
                showinfo('提示', '最近半年数据重载完成!')
                self.weeks()
            elif reload_option.reload_mode == '最近一年':
                read_data(self, self.cfg, self.pgb, self.id_dates, self.id_filenames, 52, dialog=False)
                showinfo('提示', '最近一年数据重载完成!')
                self.weeks()
            else:
                pass

    def days(self):
        self.btn_days.config(state="disable")
        self.btn_weeks.config(state="normal")
        self.btn_months.config(state="normal")
        self.btn_years.config(state="normal")

        self.controls.days()
        self._paint()

    def weeks(self):
        self.btn_days.config(state="normal")
        self.btn_weeks.config(state="disable")
        self.btn_months.config(state="normal")
        self.btn_years.config(state="normal")

        self.controls.weeks()
        self._paint()

    def months(self):
        self.btn_days.config(state="normal")
        self.btn_weeks.config(state="normal")
        self.btn_months.config(state="disable")
        self.btn_years.config(state="normal")

        self.controls.months()
        self._paint()

    def years(self):
        self.btn_days.config(state="normal")
        self.btn_weeks.config(state="normal")
        self.btn_months.config(state="normal")
        self.btn_years.config(state="disable")

        self.controls.years()
        self._paint()

    def previous(self):
        self.controls.previous()
        self._paint()

    def backward(self):
        self.controls.backward()
        self._paint()

    def plus(self):
        self.controls.plus()
        self._paint()

    def minus(self):
        self.controls.minus()
        self._paint()
    
    def pie(self):
        self.canvas_show = 'pie'
        self._paint()

    def bar(self):
        self.canvas_show = 'bar'
        self._paint()

    def sleep(self):
        self.canvas_show = 'sleep'
        self._paint()
    
    def _paint(self, pie_num=9):
        kinds = self.controls.kinds
        sleep_condition = self.controls.sleep_condition
        frequency = self.controls.frequency
        notes = self.controls.notes

        # paint canvas_up
        self.fig_up.clear()
        axs = self.fig_up.add_subplot(111)
        ax1 = kinds.plot(kind='bar', ax=axs, legend=False, color=[self.colors['fun'],
                                                                  self.colors['rest'],
                                                                  self.colors['work'],
                                                                  self.colors['compel'],
                                                                  self.colors['useless'],
                                                                  self.colors['sleep']])
        ax1.set_ylabel('小时')
        ax1.xaxis.grid()
        ax1.set_xticklabels(kinds.index, rotation=0)
        # box = ax1.get_position()
        # ax1.set_position([box.x0, box.y0, box.width * 0.95, box.height])
        ax1.legend(('尽情娱乐','休息放松','火力全开','强迫工作','无效工作','睡眠小憩'),
                   loc='lower left', ncol=6, bbox_to_anchor=(0, 1.02, 1, 0.2), mode='expand')  # 0.96, 0.7, 
        self.fig_up.tight_layout()
        self.fig_up.canvas.draw()

        # paint canvas_down
        if self.canvas_show == 'pie':
            self.fig_down.clear()
            sum_ax = self.fig_down.add_subplot(1, 2, 1)
            last_ax = self.fig_down.add_subplot(1, 2, 2)
            if list(frequency.keys()) == ['Summary']:
                sum_ax.set_title('暂无数据')
                last_ax.set_title('╮( · ω · )╭怪我咯')
            else:
                # Summary Pie Chart
                sum_key = list(frequency.keys())[0]
                # calculate top pie_num keywords
                labels, count = self._find_top(frequency[sum_key], pie_num)
                up_pie = sum_ax.pie(count, labels=labels, labeldistance=0.5, 
                                    pctdistance=0.85, autopct=self.make_autopct(count), 
                                    shadow=False, startangle=0, colors=self.Set3)
                for tx in up_pie[1]:
                    x,y = tx.get_position()
                    rot = int(self.math.degrees(self.math.atan2(y, x)))
                    tx.set_rotation(rot+180 if rot < -90 else rot-180 if rot > 90 else rot)
                    tx.set_va('center')
                    tx.set_ha('center')
                my_circle_up = self.plt.Circle((0,0), 0.7, color='white')
                sum_ax.add_artist(my_circle_up)
                sum_ax.axis('equal')
                #sum_ax.set_xlabel('(Top 9)')
                sum_ax.set_title(sum_key)
                
                # Current Pie Chart
                last_key = list(frequency.keys())[1]
                labels, count = self._find_top(frequency[last_key], pie_num)
                down_pie = last_ax.pie(count, labels=labels, labeldistance=0.5, 
                                       pctdistance=0.85,autopct=self.make_autopct(count), 
                                       shadow=False, startangle=0, colors=self.Paired)
                for tx in down_pie[1]:
                    x,y = tx.get_position()
                    rot = int(self.math.degrees(self.math.atan2(y, x)))
                    tx.set_rotation(rot+180 if rot < -90 else rot-180 if rot > 90 else rot)
                    tx.set_va('center')
                    tx.set_ha('center')
                my_circle_down = self.plt.Circle((0,0), 0.7, color='white')
                last_ax.add_artist(my_circle_down)
                last_ax.axis('equal')
                #last_ax.set_xlabel('(Top 9)')
                last_ax.set_title(last_key)
                
            self.fig_down.tight_layout()
            self.fig_down.canvas.draw()

        elif self.canvas_show == 'bar':
            showinfo('啊偶', '功能开发中')

        elif self.canvas_show == 'sleep':
            self.fig_down.clear()
            axes2 = self.fig_down.add_subplot(111)

            if not (sleep_condition.isnull().all()['sleep_st'] or sleep_condition.isnull().all()['sleep_ed']):
                sl = len(sleep_condition.index)
                up = sleep_condition.dropna().values.max()
                down = sleep_condition.dropna().values.min()
                mean_st = sleep_condition.mean()['sleep_st']
                mean_ed = sleep_condition.mean()['sleep_ed']

                axes2.axhline(y=mean_st, linewidth=1, color='r')
                axes2.axhline(y=mean_ed, linewidth=1, color='g')

                axes2.text(0, mean_st - 0.3, '入睡:' + self._decimal_to_str(mean_st), color='r')
                axes2.text(0, mean_ed + 0.5, '起床:' + self._decimal_to_str(mean_ed), color='g')

                axes2.set_xlim(0, sl + 1)
                axes2.set_ylim(down - 1, up + 1)

                axes2.set_yticks(list(range(int(math.floor(down)), int(math.ceil(up + 1)))))
                ticks = axes2.get_yticks()
                axes2.set_yticklabels([str(i) + ':00' if i >= 0 else str(24 + i) + ':00' for i in ticks])
            else:
                sl = len(sleep_condition.index)

            axes2.set_xticks(list(range(1, sl + 1)))
            axes2.set_xticklabels(list(sleep_condition.index))

            patches = []
            for i, sid in enumerate(sleep_condition.index):
                st = sleep_condition.loc[sid]['sleep_st']
                ed = sleep_condition.loc[sid]['sleep_ed']
                div = ed - st
                if div == np.nan:
                    continue
                fancy_box = mpatches.FancyBboxPatch([i + 1, st], 0.1, div, color='yellow')
                axes2.text(i + 0.95, st + div / 2, str(round(div, 1)) + 'h')
                patches.append(fancy_box)

            collection = PatchCollection(patches, facecolors='gray', alpha=0.6)
            axes2.add_collection(collection)
            axes2.invert_yaxis()
            #axes2.set_ylabel('Time')

            self.fig_down.tight_layout()
            self.fig_down.canvas.draw()

        # refresh note board
        title = notes[0]

        if title is not None:
            contents = eval(notes[1])

            self.notes.delete(1.0, 'end')
            self.notes.insert('insert', title + '\n', 'Title')

            for key, value in contents.items():
                if '【' in key:
                    self.notes.insert('insert', key + '\n', 'Heading')
                    try:
                        self.notes.insert('insert', eval("u'" + value + "'") + '\n', 'Text')
                    except SyntaxError:
                        self.notes.insert('insert', value + '\n', 'Text')
                else:
                    self.notes.insert('insert', key + ':', 'Subtitle')
                    self.notes.insert('insert', value + '\n', 'Subtitle')

            self.notes.tag_config('Title', foreground='blue', justify="center", font=25)
            self.notes.tag_config('Subtitle', foreground='gray', justify="center", font=25)
            self.notes.tag_config('Heading', foreground='black', justify="left", font=17)
            self.notes.tag_config('Text', foreground='gray', justify="left", font=15)
        # refresh label_date
        date_str = f'{self.controls.y}年{self.controls.m}月{self.controls.d}日' 
        self.label_date.config(text=date_str)
        
    @staticmethod
    def _find_top(key_list, top_num, debug=False):
        '''
        key_list = [['k1', 'k2', ...],[7,6,...]]
        return label_list, count_list
        '''
        if debug: print('\n\n',key_list)
        labels = key_list[0].copy()
        counts = key_list[1].copy()
        if debug: print(labels, counts)
        
        other_counts = 0
        if 'Others' in  labels:
            others_id = labels.index('Others')
            other_counts = counts[others_id]
            labels.pop(others_id)
            counts.pop(others_id)
            
        if debug: print(labels, '\n',counts, '\n',other_counts)
        
        frequency = Counter({k:v for k,v in zip(labels, counts)})
        if debug: print(frequency)
        
        frequen = dict(frequency.most_common(top_num))
        total = sum(frequency.values()) + other_counts
        frequen['其他'] = total - sum(frequen.values())
        if debug: print(frequen)
        
        label_list = list(frequen.keys())
        count_list = list(frequen.values())
        
        return label_list, count_list
        
    @staticmethod
    def settings():
        showinfo('提示', '开发中,敬请期待')

    @staticmethod
    def close_window():
        ans = askyesno('提示', '确认退出?')
        if ans:
            sys.exit()
        else:
            return

    @staticmethod
    def _decimal_to_str(t):
        std = datetime(2000, 1, 1, 0, 0)
        delta = timedelta(hours=t)
        
        return (std + delta).strftime("%H:%M")

    @staticmethod
    def make_autopct(values):
        def my_autopct(pct):
            total = sum(values)
            #val = int(round(pct * total / 100.0))
            val = pct * total / 100.0
            # return '{p:1.1f}%({v:1.1f}h)'.format(p=pct, v=val/2)
            return '{v:1.1f}h'.format(p=pct, v=val / 2)
        return my_autopct