├── .idea
├── .gitignore
├── vcs.xml
├── misc.xml
├── inspectionProfiles
│ └── profiles_settings.xml
├── modules.xml
└── Custom Widgets for CTk.iml
├── CTkDataVisualizingWidgets
├── __init__.py
├── ctk_graph.py
├── ctk_chart.py
└── ctk_calender.py
└── README.md
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/CTkDataVisualizingWidgets/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | CTkDataVisualizingWidgets
3 | """
4 |
5 | from .ctk_calender import CTkCalendar
6 | from .ctk_graph import CTkGraph
7 | from .ctk_chart import CTkChart
8 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/Custom Widgets for CTk.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CTkDataVisualizingWidgets
2 | **Custom data visualizing widgets like calendar, graph and chart.**
3 |
4 | ## CTkCalendar
5 | A calendar widget consists of two parts title_bar and calendar frame. You can customize virtually everything in the widget. There is
6 | also an option to define the current day, if you define today_fg_color
7 |
8 | Basic view on the left and with little customization on the right:
9 |
10 | 
11 |
12 | To use just init the class and pass the `parent` (the only required argument). The parameters starting with title_bar are responsible for
13 | widgets inside the title bar and the ones starting with the calendar are responsible for the frame where the calendar is displayed
14 |
15 | Creating the customized widget on the preview:
16 | ```python
17 | from CTkDataVisualizingWidgets import *
18 | import customtkinter as ctk
19 |
20 | window = ctk.CTk()
21 | window.title("Calendar Widget")
22 | ctk.set_appearance_mode("dark")
23 |
24 | # init calendar
25 | calendar_widget = CTkCalendar(window, width=300, height=210, border_width=3, border_color="white",
26 | fg_color="#020317", title_bar_border_width=3, title_bar_border_color="white",
27 | title_bar_fg_color="#020F43", calendar_fg_color="#020F43", corner_radius=30,
28 | title_bar_corner_radius=10, calendar_corner_radius=10, calendar_border_color="white",
29 | calendar_border_width=3, calendar_label_pad=5,
30 | today_fg_color="white", today_text_color="black")
31 | calendar_widget.pack(side="left", padx=20)
32 |
33 | window.mainloop()
34 | ```
35 |
36 | ## CTkGraph
37 | The graph widgets lets you visualize the list of integer values as a graph. You can customize almost every part, except for corner radius of canvas (where the graph is drawn). You can also set a custom title and define its family, size and color. If you do not define a title
38 | the widget will display only the graph
39 |
40 | Default view on left and with customization on the right:
41 |
42 | 
43 |
44 | To use just define parent and supply with list of integers. Example code:
45 | ```python
46 | from CTkDataVisualizingWidgets import *
47 | import customtkinter as ctk
48 |
49 | window = ctk.CTk()
50 | window.title("Calendar Widget")
51 | ctk.set_appearance_mode("dark")
52 |
53 | values = [49, 76, 61, 65, 51, 24, 9, 29, 53, 24]
54 |
55 | CTkGraph(window, values, width=250, height=200, fg_color="#FF7761", graph_color="#FF7761",
56 | graph_fg_color="#FF5330", title="Screentime", title_font_size=30, corner_radius=20).pack(side="left", padx=20, pady=20)
57 |
58 | window.mainloop()
59 | ```
60 | ## CTkChart
61 | This is widget to create a chart representation of a dict[str, int]. It takes str of the dict as a key and a title
62 | for certain stat and int or float for that matter as the value and draws it on the canvas. There are also
63 | indicators like average and max value.
64 |
65 | You can also set title, if you do not define it, it wont be rendered.
66 |
67 | There are two values with tuple[bool, bool] format:
68 | - stat_info_show: first bool is responsible for drawing the value in the stat, second for drawing title
69 | - show_indicators: first bool is responsible for max value, second for average.
70 |
71 | Default view on the left and with customization on the right:
72 |
73 | 
74 |
75 | Example code to create the widget:
76 |
77 | ```python
78 | from CTkDataVisualizingWidgets import *
79 | import customtkinter as ctk
80 |
81 | window = ctk.CTk()
82 | window.title("Calendar Widget")
83 | ctk.set_appearance_mode("dark")
84 |
85 | value = {'JJ': 5, 'OO': 0, 'WW': 7, 'TT': 3, 'GG': 15, 'FF': 10, 'HH': 1, 'PP': 12, "AA": 4}
86 | CTkChart(window, value, corner_radius=20, fg_color="#032680", stat_color="#1D6FFF", chart_fg_color="#032680",
87 | show_indicators=(False, True), stat_info_show=(False, True), chart_arrow="none", border_width=2,
88 | border_color="white", indicator_line_color="#1942AC", indicator_text_color="#020F43", stat_width=15,
89 | stat_title_color="#1D6FFF", chart_axis_width=3, width=300, height=200).pack(side="left", pady=20, padx=20)
90 |
91 | window.mainloop()
92 | ```
93 |
--------------------------------------------------------------------------------
/CTkDataVisualizingWidgets/ctk_graph.py:
--------------------------------------------------------------------------------
1 | import customtkinter as ctk
2 | from datetime import datetime
3 | import tkinter as tk
4 |
5 | class CTkGraph(ctk.CTkFrame):
6 | """
7 | Widget to display list of integers as a graph. You can customize almost everything, except for the corner radius
8 | of the canvas that draws the graph.
9 |
10 | You can also set a custom title, if you don't do is the Labeled won't be rendered.
11 |
12 | Graph axis vars are responsible for the arrows of the graph, graph line is responsible for the outline of the
13 | graph polygon (the graph is represented with a polygon)
14 | """
15 | def __init__(self, master,
16 | data_values: list,
17 | fg_color="gray17",
18 | graph_fg_color="gray17",
19 | width=200,
20 | height=200,
21 | border_width=None,
22 | border_color=None,
23 | corner_radius=None,
24 | graph_color="gray20",
25 | graph_axis_width=3,
26 | graph_line_width=2,
27 | graph_axis_color="white",
28 | graph_line_color="white",
29 | graph_axis_arrow="last",
30 | title_font_family="Arial",
31 | title_font_size=16,
32 | title_text_color=None,
33 | title=None):
34 |
35 | super().__init__(master=master, fg_color=fg_color, width=width, height=height, corner_radius=corner_radius,
36 | border_color=border_color, border_width=border_width)
37 |
38 | # data
39 | self.data_values = data_values
40 | self.date = self.current_date()
41 | self.date_display = self.date[:]
42 | self.graph_fg_color = graph_fg_color
43 | self.graph_color = graph_color
44 | self.main_canvas = None
45 |
46 | # graph data
47 | self.width = width
48 | self.height = height
49 | self.graph_axis_width = graph_axis_width
50 | self.graph_line_width = graph_line_width
51 | self.graph_axis_color = graph_axis_color
52 | self.graph_line_color = graph_line_color
53 | self.graph_axis_arrow = graph_axis_arrow
54 |
55 | # title data
56 | self.title_font_family = title_font_family
57 | self.title_font_size = title_font_size
58 | self.title_text_color = title_text_color
59 | self.title = title
60 |
61 | # setting up
62 | self.setup_stat()
63 |
64 | def setup_stat(self):
65 | if self.title is not None:
66 | ctk.CTkLabel(self, text=self.title, text_color=self.title_text_color,
67 | font=ctk.CTkFont(self.title_font_family, self.title_font_size)).pack(fill="x", padx=10, pady=10)
68 |
69 | self.main_canvas = ctk.CTkCanvas(self, background=self.graph_fg_color, bd=0, highlightthickness=0,
70 | relief="ridge", width=self.width, height=self.height)
71 |
72 | self.main_canvas.pack(expand=True, fill="both", padx=self._corner_radius//2, pady=self._corner_radius//2)
73 |
74 | self.main_canvas.bind("", lambda event: self.draw_stats(event.width, event.height))
75 |
76 | def draw_stats(self, width, height):
77 | # drawing graph lines
78 | self.main_canvas.delete("all")
79 |
80 | # axis for the graph
81 | self.main_canvas.create_line(width * 0.05, height * 0.95, width * 0.05, height * 0.05, fill=self.graph_axis_color,
82 | width=self.graph_axis_width, arrow=self.graph_axis_arrow)
83 | self.main_canvas.create_line(width * 0.05, height * 0.95, width * 0.95, height * 0.95, fill=self.graph_axis_color,
84 | width=self.graph_axis_width, arrow=self.graph_axis_arrow)
85 |
86 | data_len = len(self.data_values)
87 | max_value = max(self.data_values)
88 | gap = (width - 15) // data_len
89 | coordinates = [(width * 0.05, height * 0.95)]
90 |
91 | for i in range(data_len):
92 | h = height * 0.8 * self.data_values[i] / max_value
93 | coordinates.append((width * 0.05 + gap * i, height * 0.95 - h))
94 |
95 | else:
96 | coordinates.append((width * 0.05 + gap * data_len - gap, height * 0.95))
97 |
98 | self.main_canvas.create_polygon(coordinates, width=self.graph_line_width, fill=self.graph_color,
99 | outline=self.graph_line_color)
100 |
101 | def current_date(self) -> tuple[int, int, int]:
102 | date = str(datetime.now()).split()
103 | year, month, day = date[0].split("-")
104 | return int(day), int(month), int(year)
105 |
--------------------------------------------------------------------------------
/CTkDataVisualizingWidgets/ctk_chart.py:
--------------------------------------------------------------------------------
1 | import customtkinter as ctk
2 | import tkinter as tk
3 |
4 | class CTkChart(ctk.CTkFrame):
5 | """
6 | This is widget to create a chart representation of a dict[str, int]. It takes str of the dict as a key and a title
7 | for certain stat and int or float for that matter as the value and draws it on the canvas. There are also
8 | indicators like average and max value.
9 |
10 | You can also set title, if you do not define it, it wont be rendered.
11 |
12 | There are two values with tuple[bool, bool] format:
13 | * stat_info_show: first bool is responsible for drawing the value in the stat, second for drawing title
14 | * show_indicators: first bool is responsible for max value, second for average.
15 | """
16 | def __init__(self, master,
17 | data: dict,
18 | width=350,
19 | height=250,
20 | fg_color="gray17",
21 | corner_radius=5,
22 | border_width=None,
23 | border_color=None,
24 | chart_fg_color="gray17",
25 | title=None,
26 | title_font_family="Arial",
27 | title_text_color="white",
28 | title_font_size=16,
29 | chart_axis_width=4,
30 | chart_axis_color="white",
31 | chart_arrow="last",
32 | show_indicators: tuple[bool, bool] = (True, True),
33 | indicator_line_color="white",
34 | indicator_text_color="white",
35 | stat_color="white",
36 | stat_width=30,
37 | stat_info_show: tuple[bool, bool] = (True, True),
38 | stat_text_color="gray17",
39 | stat_title_color="white"):
40 | super().__init__(master=master, fg_color=fg_color, corner_radius=corner_radius, border_color=border_color,
41 | border_width=border_width, width=width, height=height)
42 |
43 | # data
44 | self.data = data
45 | self.data_avg, self.data_max = self.format_data()
46 |
47 | # data about chart axis
48 | self.chart_axis_width = chart_axis_width
49 | self.chart_axis_color = chart_axis_color
50 | self.chart_arrow = chart_arrow
51 |
52 | # data about indicators
53 | self.show_indicators: tuple[bool, bool] = show_indicators
54 | self.indicator_line_color = indicator_line_color
55 | self.indicator_text_color = indicator_text_color
56 |
57 | # data about stats
58 | self.stat_color = stat_color
59 | self.stat_width = stat_width
60 | self.stat_info_show = stat_info_show
61 | self.stat_text_color = stat_text_color
62 | self.stat_title_color = stat_title_color
63 |
64 | self.main_canvas = ctk.CTkCanvas(self, background=chart_fg_color, bd=0, highlightthickness=0, relief="ridge",
65 | width=width, height=height)
66 |
67 | if title is not None:
68 | ctk.CTkLabel(self, text=title, font=ctk.CTkFont(title_font_family, title_font_size, "bold"),
69 | text_color=title_text_color).pack(fill="x", expand=True)
70 |
71 | self.main_canvas.pack(expand=True, fill="both", padx=corner_radius//1.5, pady=corner_radius//1.5)
72 | self.main_canvas.bind("", lambda event: self.draw_stats())
73 |
74 | def format_data(self) -> tuple[float, int]:
75 | m = 0.01
76 | s, count = 0, 0.01
77 |
78 | for value in self.data.values():
79 | s += value
80 | count += 1
81 | m = max(m, value)
82 |
83 | return s/count, m
84 |
85 | def draw_stats(self):
86 | # updating canvas and canvas info
87 | self.main_canvas.delete("all")
88 | canvas_height = self.main_canvas.winfo_height()
89 | canvas_width = self.main_canvas.winfo_width()
90 |
91 | # drawing graph axis
92 | self.main_canvas.create_line(0+self.chart_axis_width, canvas_height-self.chart_axis_width-canvas_height*0.15,
93 | canvas_width, canvas_height-self.chart_axis_width-canvas_height*0.15,
94 | capstyle="round", width=self.chart_axis_width, fill=self.chart_axis_color,
95 | arrow=self.chart_arrow)
96 | self.main_canvas.create_line(0 + self.chart_axis_width, canvas_height-self.chart_axis_width-canvas_height*0.15,
97 | 0 + self.chart_axis_width, 0, arrow=self.chart_arrow,
98 | capstyle="round", width=self.chart_axis_width, fill=self.chart_axis_color)
99 |
100 | for index, key in enumerate(self.data.keys()):
101 | self.draw_stat_day(canvas_width*0.01, canvas_height * 0.2, canvas_width * 0.9, canvas_height * 0.55,
102 | index, key)
103 |
104 | # drawing indicator lines
105 | if self.show_indicators[0]:
106 | self.draw_stat_indicator(canvas_width * 0.9, canvas_height * 0.2 - 15, "max")
107 | if self.show_indicators[1]:
108 | avg_height = canvas_height * 0.55 - canvas_height * 0.55 * (self.data_avg / self.data_max)
109 | self.draw_stat_indicator(canvas_width * 0.9, avg_height + canvas_height * 0.2, "avg")
110 |
111 | def draw_stat_day(self, graph_x_offset, graph_y_offset, graph_width, graph_height, index, key):
112 | day_width = graph_width//len(self.data.keys())
113 | day_offset = day_width*0.6
114 |
115 | value = self.data[key]
116 | day_stat_height = value / self.data_max * graph_height
117 |
118 | self.main_canvas.create_line(graph_x_offset + day_width * index + day_offset,
119 | graph_y_offset + graph_height,
120 | graph_x_offset + day_width * index + day_offset,
121 | graph_y_offset + graph_height - day_stat_height,
122 | capstyle="round", fill=self.stat_color, width=self.stat_width)
123 |
124 | if self.stat_info_show[0]:
125 | self.main_canvas.create_text(graph_x_offset + day_width * index + day_offset,
126 | graph_y_offset + graph_height,
127 | text=value, fill=self.stat_text_color,
128 | font=ctk.CTkFont("Arial", 13, "bold"))
129 |
130 | if self.stat_info_show[1]:
131 | self.main_canvas.create_text(graph_x_offset + day_width * index + day_offset,
132 | graph_y_offset + graph_height + 40,
133 | text=key, fill=self.stat_title_color,
134 | font=ctk.CTkFont("Arial", 13, "bold"))
135 |
136 | def draw_stat_indicator(self, x2, y, title):
137 | self.main_canvas.create_line(10, y, x2, y,
138 | dash=[20], fill=self.indicator_line_color, capstyle="round", width=3)
139 | self.main_canvas.create_text(x2+5, y, anchor="w", text=title, fill=self.indicator_text_color,
140 | font=ctk.CTkFont("Arial", 15, "bold"))
141 |
142 |
--------------------------------------------------------------------------------
/CTkDataVisualizingWidgets/ctk_calender.py:
--------------------------------------------------------------------------------
1 | import customtkinter as ctk
2 | import calendar
3 | from datetime import datetime
4 | import tkinter as tk
5 |
6 | class CTkCalendar(ctk.CTkFrame):
7 | """
8 | Calendar widget to display certain month, each day is rendered as Label.
9 |
10 | If you do not define today_fg_color and today_text_color it will be rendered as other days
11 | """
12 | def __init__(self, master,
13 | today_fg_color=None,
14 | today_text_color=None,
15 | width=250,
16 | height=250,
17 | fg_color=None,
18 | corner_radius=8,
19 | border_width=None,
20 | border_color=None,
21 | bg_color="transparent",
22 | background_corner_colors=None,
23 | title_bar_fg_color=None,
24 | title_bar_border_width=None,
25 | title_bar_border_color=None,
26 | title_bar_corner_radius=None,
27 | title_bar_text_color=None,
28 | title_bar_button_fg_color=None,
29 | title_bar_button_hover_color=None,
30 | title_bar_button_text_color=None,
31 | title_bar_button_border_width=None,
32 | title_bar_button_border_color=None,
33 | calendar_fg_color=None,
34 | calendar_border_width=None,
35 | calendar_border_color=None,
36 | calendar_corner_radius=None,
37 | calendar_text_color=None,
38 | calendar_text_fg_color=None,
39 | calendar_label_pad=1):
40 |
41 | super().__init__(master=master,
42 | width=width,
43 | height=height,
44 | fg_color=fg_color,
45 | corner_radius=corner_radius,
46 | border_width=border_width,
47 | border_color=border_color,
48 | bg_color=bg_color,
49 | background_corner_colors=background_corner_colors)
50 |
51 | # data
52 | self.today_text_color = today_text_color
53 | self.today_fg_color = today_fg_color
54 | self.today = self.current_date()
55 | self.day, self.month, self.year = self.today[:]
56 | self.labels_by_date = dict()
57 | self.month_label = ctk.StringVar(value=calendar.month_name[self.month])
58 | self.year_label = ctk.IntVar(value=self.year)
59 |
60 | # data for title bar
61 | self.title_bar_fg_color = title_bar_fg_color
62 | self.title_bar_border_width = title_bar_border_width
63 | self.title_bar_border_color = title_bar_border_color
64 | self.title_bar_text_color = title_bar_text_color
65 | self.title_bar_button_fg_color = title_bar_button_fg_color
66 | self.title_bar_button_hover_color = title_bar_button_hover_color
67 | self.title_bar_button_text_color = title_bar_button_text_color
68 | self.title_bar_button_border_width = title_bar_button_border_width
69 | self.title_bar_button_border_color = title_bar_button_border_color
70 | self.title_bar_corner_radius = title_bar_corner_radius
71 |
72 | # data for calendar frame
73 | self.calendar_fg_color = calendar_fg_color
74 | self.calendar_border_width = calendar_border_width
75 | self.calendar_border_color = calendar_border_color
76 | self.calendar_corner_radius = calendar_corner_radius
77 | self.calendar_text_fg_color = calendar_text_fg_color
78 | self.calendar_text_color = calendar_text_color
79 | self.calendar_label_pad = calendar_label_pad
80 |
81 | # creating header and calendar frames
82 | self.content_frame = ctk.CTkFrame(self, fg_color="transparent", width=width, height=height)
83 | self.content_frame.pack(expand=True, fill="both", padx=corner_radius/3, pady=corner_radius/3)
84 | self.setup_header_frame()
85 | self.create_calendar_frame()
86 |
87 | # setting up the header frame
88 | def setup_header_frame(self):
89 | header_frame = ctk.CTkFrame(self.content_frame, fg_color=self.title_bar_fg_color,
90 | corner_radius=self.title_bar_corner_radius,
91 | border_color=self.title_bar_border_color, border_width=self.title_bar_border_width)
92 |
93 | ctk.CTkButton(header_frame, text="<", width=25, fg_color=self.title_bar_button_fg_color,
94 | hover_color=self.title_bar_button_hover_color, border_color=self.title_bar_button_border_color,
95 | border_width=self.title_bar_button_border_width, font=ctk.CTkFont("Arial", 11, "bold"),
96 | command=lambda: self.change_month(-1)).pack(side="left", padx=10)
97 | ctk.CTkLabel(header_frame, textvariable=self.month_label, font=ctk.CTkFont("Arial", 16, "bold"),
98 | fg_color="transparent").pack(side="left", fill="x", expand=True)
99 | ctk.CTkLabel(header_frame, textvariable=self.year_label, font=ctk.CTkFont("Arial", 16, "bold"),
100 | fg_color="transparent").pack(side="left", fill="x")
101 | ctk.CTkButton(header_frame, text=">", width=25, fg_color=self.title_bar_button_fg_color,
102 | hover_color=self.title_bar_button_hover_color, border_color=self.title_bar_button_border_color,
103 | border_width=self.title_bar_button_border_width, font=ctk.CTkFont("Arial", 11, "bold"),
104 | command=lambda: self.change_month(1)).pack(side="right", padx=10)
105 |
106 | header_frame.place(relx=0.5, rely=0.02, anchor="n", relheight=0.18, relwidth=0.95)
107 |
108 | def create_calendar_frame(self):
109 | # "updating" frames
110 | calendar_frame = ctk.CTkFrame(self.content_frame, fg_color=self.calendar_fg_color,
111 | corner_radius=self.calendar_corner_radius,
112 | border_width=self.calendar_border_width, border_color=self.calendar_border_color)
113 | current_month = calendar.monthcalendar(self.year, self.month)
114 |
115 | # grid
116 | calendar_frame.columnconfigure((0, 1, 2, 3, 4, 5, 6), weight=1, uniform="b")
117 | rows = tuple([i for i in range(len(current_month))])
118 | calendar_frame.rowconfigure(rows, weight=1, uniform="b")
119 |
120 | # labels for days
121 | for row in range(len(current_month)):
122 | for column in range(7):
123 | if current_month[row][column] != 0:
124 | if self.today_fg_color is not None and self.year == self.today[2] and self.month == self.today[1] \
125 | and current_month[row][column] == self.today[0]:
126 | label = ctk.CTkLabel(calendar_frame, text=str(current_month[row][column]), corner_radius=5,
127 | fg_color=self.today_fg_color, font=ctk.CTkFont("Arial", 11),
128 | text_color=self.today_text_color)
129 | else:
130 | label = ctk.CTkLabel(calendar_frame, text=str(current_month[row][column]), corner_radius=5,
131 | fg_color=self.calendar_text_fg_color, font=ctk.CTkFont("Arial", 11),
132 | text_color=self.calendar_text_color)
133 |
134 | self.labels_by_date[(current_month[row][column], self.month, self.year)] = label
135 |
136 | label.grid(row=row, column=column, sticky="nsew", padx=self.calendar_label_pad,
137 | pady=self.calendar_label_pad)
138 |
139 | calendar_frame.place(relx=0.5, rely=0.97, anchor="s", relheight=0.75, relwidth=0.95)
140 |
141 | def change_month(self, amount):
142 | self.month += amount
143 | if self.month < 1:
144 | self.year -= 1
145 | self.month = 12
146 | self.day = 1
147 | elif self.month > 12:
148 | self.year += 1
149 | self.month = 1
150 | self.day = 1
151 |
152 | self.month_label.set(calendar.month_name[self.month])
153 | self.year_label.set(self.year)
154 |
155 | self.create_calendar_frame()
156 |
157 | def current_date(self) -> tuple[int, int, int]:
158 | date = str(datetime.now()).split()
159 | year, month, day = date[0].split("-")
160 | return int(day), int(month), int(year)
161 |
--------------------------------------------------------------------------------