Создание шкалы времени в tkinter без matplotlib - PullRequest
0 голосов
/ 02 января 2019

Я пытаюсь создать временную шкалу в tkinter для построения объектов даты и времени.Даты могут быть годами друг от друга, но их не будет много (максимум 20).Я бы хотел, чтобы линия масштабировалась, поэтому первая дата находится в начале строки, а последняя - в конце строки с маркерами для дат между ними.

Мне это не нужночтобы сделать что-то причудливое, но мне нужно показать расстояние между промежутками времени, а не просто упорядоченную сетку меток.

Хотя у matplotlib есть отличное решение для этого, я не могу использовать его, так как он взрываетсяразмер файла (я упаковываю это), и у меня нет другого использования для этого в проекте.Я также посмотрел на временную шкалу ttkwidgets , но она ориентирована на время, а не на даты, и, похоже, не работает с теми временами, которые я ей дал.

Таким образом, я 'Я думаю, что мне нужно использовать виджет Canvas и нарисовать линию, которая масштабируется динамически.Затем мне нужно будет построить маркеры, которые учитывают, что было бы полезно масштабировать любые подсказки.

1 Ответ

0 голосов
/ 04 января 2019

Хорошо, вот что я получил.Он не идеален, но он должен быть в состоянии показать, как можно построить динамическую временную шкалу на основе отправленных дат.

В этом примере будут создаваться метки на холсте, разнесенном на основе количества дней между ними.

Вы сможете нажимать на ярлыки, чтобы получить заметки, которые были сохранены за день, на который вы нажали.

Я предоставил полосу прокрутки для случаев, когда у вас много дат и вы их не видитевсе на экране.

Вы также заметите, что не можете отправить один и тот же день дважды.Хотя вы можете добавить функцию, которая позволит вам обновлять заметки на эту дату.

Для этого вам потребуется 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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...