import tkinter as tk from tkinter import filedialog as fd from tkinter import ttk from plotter import plot_preset, plot_points, plot_user from sympy.parsing.sympy_parser import parse_expr from sympy.plotting import plot class PlotType: USER = 1 PRESET = 2 POINT = 3 class GraphMode: LINES = "Линии" DOTS = "Точки" @classmethod def options(cls): return [cls.DOTS, cls.LINES] class PresetType: LINE = "ax + b = 0" PARABOLA = "ax^2 + bx + c = 0" SIN = "a * sin(b * x + c)" COS = "a * cos(b * x + c)" @classmethod def options(cls): return [cls.LINE, cls.PARABOLA, cls.SIN, cls.COS] class PlotTypeFrame(tk.Frame): def __init__(self, master, plot_type_var, on_type_change): super().__init__(master) self.master = master self.plot_type = plot_type_var self.on_type_change = on_type_change self.create_widgets() def create_widgets(self): self.preset_plot = ttk.Radiobutton(self) self.preset_plot["variable"] = self.plot_type self.preset_plot["text"] = "Предустановленный" self.preset_plot["value"] = PlotType.PRESET self.preset_plot["command"] = self.on_type_change self.preset_plot.grid(row=0, column=0, sticky=tk.W) self.user_plot = ttk.Radiobutton(self) self.user_plot["variable"] = self.plot_type self.user_plot["text"] = "Пользовательский" self.user_plot["value"] = PlotType.USER self.user_plot["command"] = self.on_type_change self.user_plot.grid(row=1, column=0, sticky=tk.W) self.point_plot = ttk.Radiobutton(self) self.point_plot["variable"] = self.plot_type self.point_plot["text"] = "По точкам" self.point_plot["value"] = PlotType.POINT self.point_plot["command"] = self.on_type_change self.point_plot.grid(row=2, column=0, sticky=tk.W) self.preset_plot.invoke() class PresetPlotFrame(tk.Frame): def __init__(self, master): super().__init__(master) self.master = master self.create_widgets() def create_widgets(self): # Виджеты для указания значений параметров графиков self.variables = {} self.l_min = tk.Label(self) self.l_min["text"] = "min(x)" self.l_min.grid(row=1, column=0) self.min = tk.Entry(self) self.min.grid(row=1, column=1, sticky=tk.W + tk.E) self.l_max = tk.Label(self) self.l_max["text"] = "max(x)" self.l_max.grid(row=2, column=0) self.max = tk.Entry(self) self.max.grid(row=2, column=1, sticky=tk.W + tk.E) la = tk.Label(self) la["text"] = "a: " a = tk.Entry(self) lb = tk.Label(self) lb["text"] = "b: " b = tk.Entry(self) lc = tk.Label(self) lc["text"] = "c: " c = tk.Entry(self) # В соответствие каждому графику ставятся виджеты его переменных. # None соответствуют все виджеты переменных, чтобы их можно было # скрывать. self.variables[PresetType.LINE] = [(la, a), (lb, b)] self.variables[PresetType.PARABOLA] = [(la, a), (lb, b), (lc, c)] self.variables[PresetType.SIN] = [(la, a), (lb, b), (lc, c)] self.variables[PresetType.COS] = [(la, a), (lb, b), (lc, c)] self.variables[None] = [(la, a), (lb, b), (lc, c)] # Изменения этой переменной будут передаваться в метод type_changed self.preset_type = tk.StringVar() self.preset_type.trace("w", self.type_changed) self.options = ttk.OptionMenu( self, self.preset_type, PresetType.options()[0], *PresetType.options(), ) self.options.grid(row=0, column=0, columnspan=2, rowspan=1, sticky=tk.NSEW) def type_changed(self, *args): new_type = self.preset_type.get() for (label, var) in self.variables[None]: label.grid_forget() var.grid_forget() for i, (label, var) in enumerate(self.variables[new_type], 3): label.grid(row=i, column=0) var.grid(row=i, column=1, sticky=tk.W + tk.E) def grid_size(self): return (4, 2) def show(self): self.grid(row=0, column=0, columnspan=2, rowspan=4, sticky=tk.NSEW) self.grid_columnconfigure(0, weight=1) self.grid_columnconfigure(1, weight=4) def collect_info(self): cur_type = self.preset_type.get() v = self.variables[cur_type] try: min_x = float(self.min.get().strip()) except ValueError: return {"status": "error", "message": "min(x) не является float"} try: max_x = float(self.max.get().strip()) except ValueError: return {"status": "error", "message": "max(x) не является float"} try: a = float(v[0][1].get().strip()) except ValueError: return {"status": "error", "message": "a не является float"} try: b = float(v[1][1].get().strip()) except ValueError: return {"status": "error", "message": "b не является float"} if cur_type == PresetType.PARABOLA: try: c = float(v[2][1].get().strip()) except ValueError: return {"status": "error", "message": "c не является float"} return { "status": "ok", "graph": "preset", "type": "parabola", "min": min_x, "max": max_x, "a": a, "b": b, "c": c } elif cur_type == PresetType.SIN: try: c = float(v[2][1].get().strip()) except ValueError: return {"status": "error", "message": "c не является float"} return { "status": "ok", "graph": "preset", "type": "sin", "min": min_x, "max": max_x, "a": a, "b": b, "c": c } elif cur_type == PresetType.COS: try: c = float(v[2][1].get().strip()) except ValueError: return {"status": "error", "message": "c не является float"} return { "status": "ok", "graph": "preset", "type": "cos", "min": min_x, "max": max_x, "a": a, "b": b, "c": c } elif cur_type == PresetType.LINE: return { "status": "ok", "graph": "preset", "type": "line", "min": min_x, "max": max_x, "a": a, "b": b } class UserPlotFrame(tk.Frame): def __init__(self, master): super().__init__(master) self.master = master self.create_widgets() def create_widgets(self): self.label = tk.Label(self) self.label["text"] = "Введите выражение:" self.label.grid(row=0, column=0, rowspan=1, columnspan=1, sticky=tk.W) self.text = tk.Entry(self) self.text.grid(row=1, column=0, sticky=tk.NSEW) def show(self): self.grid(row=0, column=0, columnspan=1, rowspan=2, sticky=tk.NSEW) grid_conf(self, 2, 1) def collect_info(self): expr_text = self.text.get() try: expr = parse_expr(expr_text) plot(expr, show=False) except: return {"status": "error", "message": "Не удалось обработать выражение"} return {"status": "ok", "graph": "user", "expr": expr} class PointPlotFrame(tk.Frame): def __init__(self, master): super().__init__(master) self.master = master self.show() self.create_widgets() self._path = "" self.mode = "o" def create_widgets(self): self.choose_file = tk.Button(self) self.choose_file["text"] = "Выбрать файл" self.choose_file["command"] = self.open_file self.choose_file.grid(row=0, column=0, columnspan=1, rowspan=1, padx=3, pady=3, sticky=tk.NSEW) self.filepath = tk.Entry(self) self.filepath.grid(row=1, column=0, sticky=tk.NSEW) # Изменения этой переменной будут передаваться в метод type_changed self.mode_var = tk.StringVar() self.mode_var.trace("w", self.mode_changed) self.options = ttk.OptionMenu( self, self.mode_var, GraphMode.DOTS, *GraphMode.options(), ) self.options.grid(row=2, column=0, columnspan=1, rowspan=1, sticky=tk.NSEW) def mode_changed(self, *args): mode = self.mode_var.get() if mode == GraphMode.DOTS: self.mode = "o" elif mode == GraphMode.LINES: self.mode = "r" def open_file(self): file_handle = tk.filedialog.askopenfile() if file_handle is not None: self._path = file_handle.name self.filepath.delete(0, tk.END) self.filepath.insert(0, file_handle.name) def show(self): gr, gc = (2, 1) self.grid(row=0, column=0, columnspan=gc, rowspan=gr, sticky=tk.NSEW) grid_conf(self, gr, gc) def collect_info(self): try: with open(self._path) as f: data = f.read().strip().split() except FileNotFoundError: return {"status": "error", "message": "Файл не найден"} try: points = [] for line in data: x, y = map(float, line.split(',')) points.append((x, y)) except ValueError: return {"status": "error", "message": "Неверный формат данных"} return {"status": "ok", "graph": "point", "mode": self.mode, "points": points} class App(tk.Frame): def __init__(self, master): super().__init__(master) self.grid(row=0, column=0, columnspan=2, rowspan=6, sticky=tk.NSEW) self.create_widgets() def create_widgets(self): # Создаём фреймы с настройкой выбранного типа графика self.cfg_pt = { "row": 0, "column": 1, "rowspan": 5, "columnspan": 1, "padx": 10, "pady": 10, "sticky": tk.NSEW } self.right_frame = ttk.LabelFrame(self) self.right_frame["text"] = "Настройка графика" self.right_frame.grid(**self.cfg_pt) grid_conf(self.right_frame, 5, 1) self.preset_plot_frame = PresetPlotFrame(self.right_frame) self.user_plot_frame = UserPlotFrame(self.right_frame) self.point_plot_frame = PointPlotFrame(self.right_frame) self.pw = { PlotType.PRESET: self.preset_plot_frame, PlotType.USER: self.user_plot_frame, PlotType.POINT: self.point_plot_frame, } # Фрейм с выбором типа графика self.left_frame = ttk.LabelFrame(self) self.left_frame["text"] = "Тип графика" self.left_frame.grid(row=0, column=0, rowspan=5, columnspan=1, padx=10, pady=10, sticky=tk.NSEW) self.plot_type = tk.IntVar() self.w_plot_type = PlotTypeFrame( self.left_frame, self.plot_type, self.change_plot_type ) self.w_plot_type.grid(row=0, column=0, rowspan=5, padx=3, pady=3) ############################# self.change_plot_type() self.submit_btn = tk.Button(self) self.submit_btn["text"] = "Построить график" self.submit_btn["command"] = self.submit self.submit_btn.grid(row=5, column=0, rowspan=1, columnspan=2, padx=10, pady=10, sticky=tk.NSEW) self.error_label = tk.Label(self) self.error_label["fg"] = "#ff0000" def change_plot_type(self): cur_type = self.plot_type.get() for ptype, widget in self.pw.items(): if cur_type == ptype: widget.show() else: widget.grid_forget() def submit(self): cur_type = self.plot_type.get() widget = self.pw[cur_type] info = widget.collect_info() if info["status"] == "error": self.error_label["text"] = info["message"] self.error_label.grid(row=7, column=0, rowspan=1, columnspan=2, padx=10, pady=5, sticky=tk.NSEW) else: self.error_label.grid_forget() if info["graph"] == "preset": plot_preset(info) elif info["graph"] == "point": plot_points(info) elif info["graph"] == "user": plot_user(info) else: print(info) def grid_conf(obj, rows, cols, uniform=False): for i in range(rows): obj.grid_rowconfigure(i, weight=1, uniform=uniform) for i in range(cols): obj.grid_columnconfigure(i, weight=1, uniform=uniform) if __name__ == "__main__": root = tk.Tk() app = App(root) grid_conf(app, 6, 2) grid_conf(root, 6, 2) root.mainloop()