在不带matplotlib

时间:2019-01-01 22:28:11

标签: python python-3.x tkinter

我正在尝试在tkinter中创建一个时间线以绘制日期时间对象。日期可能相隔数年,但不会太多(最多20个)。我希望该行可以缩放,因此第一个日期在该行的开头,最后一个日期在该行的末尾,并在中间标记日期。

我不需要它做任何花哨的事情,但我需要显示时间间隔之间的距离,而不仅仅是显示标签的有序网格。

虽然matplotlib为此提供了一个很好的解决方案,但我无法使用它,因为它会爆炸文件大小(我将其打包),并且在项目中没有其他用途。我还查看了ttkwidgets timeline,但这是针对时间而不是日期的,并且似乎与我给定的时间不一致。

因此,我认为我需要使用Canvas小部件,并绘制一条可以动态缩放的线。然后,我需要绘制标记,并考虑到缩放任何提示都会有用。

1 个答案:

答案 0 :(得分:1)

好的,这就是我到目前为止所得到的。它不是很完美,但应该能够说明如何根据提交的日期建立动态时间表。

此示例将根据间隔多少天在画布上创建标签。

您将能够单击标签以获取与单击当天保存的笔记。

当您有多个日期而无法在屏幕上全部看到它们时,我提供了一个滚动条。

您还将注意到,您不能两次提交同一天。尽管您可能希望添加一个功能,以使您可以更新该日期的注释。

为此,您将需要pip安装tkcalendar,除非您希望构建自己的日期选择器。但这是很多工作,没有充分的理由。

import tkinter as tk
from tkcalendar import Calendar


class Timeline(tk.Tk):
    def __init__(self):
        super().__init__()
        self.rowconfigure(3, weight=1)
        self.columnconfigure(0, weight=1)
        self.timeline_list = []
        self.timeline_canvas = tk.Canvas(self)
        self.note_textbox = tk.Text(self, height=3)
        self.text_label = tk.Label(self, text='Notes on date: ')
        self.date_button = tk.Button(self, text='Submit new date', command=self.date_selector)
        self.img = tk.PhotoImage(file='1x1.png')

        self.date_button.grid(row=0, column=0)
        self.text_label.grid(row=1, column=0)
        self.note_textbox.grid(row=2, column=0, padx=5, pady=5, sticky="nsew")
        self.timeline_canvas.grid(row=3, column=0, sticky='ew')

        bar = tk.Scrollbar(self, orient='horizontal')
        bar.config(command=self.timeline_canvas.xview)
        bar.grid(row=4, column=0, sticky='ew')

    def on_click(self, event, data="No data!"):
        """You could build a popup menu here that
         is activated on mouse-over or on-click
         I just used print to test the field"""
        print(data)

    def append_canvas(self):
        list_len = len(self.timeline_list)
        if list_len > 1:
            first_date = self.timeline_list[0][0]
            last_date = self.timeline_list[-1][0]
            line_length = last_date - first_date
            self.timeline_list.sort()
            right_side = 50
            self.timeline_canvas.delete('all')
            list_of_dates = []
            for i in range(list_len):
                if i == 0:
                    list_of_dates.append([self.timeline_list[i], 0])
                elif i == list_len-1:
                    list_of_dates.append([self.timeline_list[i], line_length.days])
                else:
                    list_of_dates.append(
                        [self.timeline_list[i], (self.timeline_list[i][0] - self.timeline_list[0][0]).days])

            for ndex, date_item in enumerate(list_of_dates):
                lbl = tk.Label(self.timeline_canvas, text=date_item[0][0], background='gray')
                self.timeline_canvas.create_window((right_side, 25), window=lbl)
                if ndex < len(list_of_dates)-1:
                    right_side += (65 + list_of_dates[ndex+1][1])

                lbl.bind("<Button-1>", lambda event, d=date_item[0][1].strip(): self.on_click(event, d))

    def date_selector(self):
        def work_selection():
            selected_date = cal.selection_get()
            selected_notes = self.note_textbox.get(1.0, 'end')
            match_found = False
            for each_list in self.timeline_list:
                if selected_date == each_list[0]:
                    match_found = True
                    break
            if match_found is False:
                self.timeline_list.append([selected_date, selected_notes])
                self.append_canvas()
            top.destroy()
        top = tk.Toplevel(self)
        cal = Calendar(top, selectmode='day')
        cal.pack(fill="both", expand=True)
        tk.Button(top, text="ok", width=10, command=work_selection).pack()


if __name__ == "__main__":
    Timeline().mainloop()

结果:

enter image description here

如果您有任何疑问,请告诉我。 如果有时间,我会在以后做更多的工作。

更新:

我修改了代码,使其包含在500像素长的行上。 我做了一些测试,以确保一切都正常。

如果发现有问题,我可能会稍后对其进行修改,但现在我认为这应该可以满足您列出的所有需求。

我确实注意到一个可能的问题。如果第一个日期和最后一个日期之间有较大的差距,那么一组日期中的日期实际上会彼此接近。我将尝试找到更好的解决方案,但这是我今天得到的。

更新:

我还添加了与鼠标滚轮配合使用的缩放选项。现在,放大时您将不得不使用滚动条在画布上移动,但是它应该适用于在视觉上接近且重叠的日期。

import tkinter as tk
from tkcalendar import Calendar


class Timeline(tk.Tk):
    def __init__(self):
        super().__init__()
        self.rowconfigure(3, weight=1)
        self.columnconfigure(0, weight=1)
        self.timeline_list = []
        self.line_size = 500
        self.timeline_canvas = tk.Canvas(self)
        self.note_textbox = tk.Text(self, height=3)
        self.text_label = tk.Label(self, text='Notes on date: ')
        self.date_button = tk.Button(self, text='Submit new date', command=self.date_selector)
        self.img = tk.PhotoImage(file='1x1.png')

        self.date_button.grid(row=0, column=0)
        self.text_label.grid(row=1, column=0)
        self.note_textbox.grid(row=2, column=0, padx=5, pady=5, sticky="nsew")
        self.timeline_canvas.grid(row=3, column=0, sticky='ew')

        bar = tk.Scrollbar(self, orient='horizontal')
        bar.config(command=self.timeline_canvas.xview)
        bar.grid(row=4, column=0, sticky='ew')
        self.timeline_canvas.bind_all("<MouseWheel>", self.zoom_in_out)

    def zoom_in_out(self, event):
        if event.delta < 0:
            self.line_size -= 100
        else:
            self.line_size += 100
        self.append_canvas()

    def on_click(self, event=None, date=None, data=None):
        """You could build a popup menu here that
         is activated on mouse-over or on-click
         I just used print to test the field"""
        print(date, data)

    def append_canvas(self):
        list_len = len(self.timeline_list)
        if list_len > 1:
            self.timeline_list.sort()
            first_date = self.timeline_list[0][0]
            last_date = self.timeline_list[-1][0]
            line_length = last_date - first_date
            self.timeline_canvas.delete('all')
            list_of_dates = []
            for i in range(list_len):
                if i == 0:
                    list_of_dates.append([self.timeline_list[i], 0])
                elif i == list_len-1:
                    list_of_dates.append([self.timeline_list[i], line_length.days])
                else:
                    list_of_dates.append(
                        [self.timeline_list[i], (self.timeline_list[i][0] - self.timeline_list[0][0]).days])
            self.timeline_canvas.create_line(50, 50, 550, 50, fill="red", dash=(4, 4))
            for ndex, date_item in enumerate(list_of_dates):
                if ndex == 0:
                    lbl = tk.Label(self.timeline_canvas, text=ndex + 1, background='gray')
                    self.timeline_canvas.create_window((50, 50), window=lbl)
                elif ndex == len(list_of_dates) - 1:
                    lbl = tk.Label(self.timeline_canvas, text=ndex + 1, background='gray')
                    self.timeline_canvas.create_window((self.line_size + 50, 50), window=lbl)
                else:
                    x = (list_of_dates[ndex][1] / list_of_dates[-1][1]) * self.line_size
                    lbl = tk.Label(self.timeline_canvas, text=ndex + 1, background='gray')
                    self.timeline_canvas.create_window((x + 50, 50), window=lbl)
                lbl.bind("<Button-1>", lambda event, d=date_item[0][0], t=date_item[0][1].strip(): self.on_click(event, d, t))

    def date_selector(self):
        def work_selection():
            selected_date = cal.selection_get()
            selected_notes = self.note_textbox.get(1.0, 'end')
            match_found = False
            for each_list in self.timeline_list:
                if selected_date == each_list[0]:
                    match_found = True
                    break
            if match_found is False:
                self.timeline_list.append([selected_date, selected_notes])
                self.append_canvas()
            top.destroy()
        top = tk.Toplevel(self)
        cal = Calendar(top, selectmode='day')
        cal.pack(fill="both", expand=True)
        tk.Button(top, text="ok", width=10, command=work_selection).pack()


if __name__ == "__main__":
    Timeline().mainloop()

新结果:

enter image description here