diff --git a/main.py b/main.py index a1b20cf..5d7e335 100644 --- a/main.py +++ b/main.py @@ -1,418 +1,9 @@ -import math -import customtkinter as ctk -from data_structure import Pile_chaine +# test si custom tkinter fonction sur le pc +try: + from package.app import Ctk_app + gui = Ctk_app() +except ImportError: + from package.app import Tk_app + gui = Tk_app() - -class Expression: - """manipule les expression sous forme d'arbre""" - def __init__(self, val, fils_gauche=None, fils_droit=None) -> None: - self.val = val - self.fils_gauche = fils_gauche - self.fils_droit = fils_droit - - def __str__(self) -> str: - """renvoie l'expression sous forme infixé""" - if self.est_feuille(): - return str(self.val) - return '('+self.fils_gauche.__str__()+str(self.val)+self.fils_droit.__str__()+')' - - def print_tree(self, level=0): - """ Affiche le noeud joliment""" - if self.fils_gauche is not None: - self.fils_gauche.print_tree(level + 1) - print(' ' * 4 * level + '-> ' + str(self.val)) - if self.fils_droit is not None: - self.fils_droit.print_tree(level + 1) - - def est_feuille(self) -> bool: - """ Renvoie true si le noeud est une feuille""" - if self.fils_droit is None and self.fils_gauche is None: - return True - return False - - def evalue(self, x=0) -> float: - """ Renvoie le résultat de l'expression""" - try: - if self.est_feuille(): - if self.val == 'x': - return x - return float(self.val) - elif self.val == '+': - return self.fils_gauche.evalue(x) + self.fils_droit.evalue(x) - elif self.val == '*': - return self.fils_gauche.evalue(x) * self.fils_droit.evalue(x) - elif self.val == '/': - return self.fils_gauche.evalue(x) / self.fils_droit.evalue(x) - elif self.val == '^': - return self.fils_gauche.evalue(x) ** self.fils_droit.evalue(x) - elif self.val == '-': - return self.fils_gauche.evalue(x) - self.fils_droit.evalue(x) - elif self.val == 'ln': - return math.log(self.fils_gauche.evalue(x)) - elif self.val == '√': - return math.sqrt(self.fils_gauche.evalue(x)) - elif self.val == "cos": - return math.cos(self.fils_gauche.evalue(x)) - elif self.val == "sin": - return math.sin(self.fils_gauche.evalue(x)) - elif self.val == "tan": - return math.tan(self.fils_gauche.evalue(x)) - - except ZeroDivisionError: - raise ZeroDivisionError - - def valeurs_de_fonction(self, start, end): - """ Calcul les valeurs entre start et end""" - result = [] - pas = (end - start) / 1000 - while start <= end: - try: - result.append((start, self.evalue(start))) - except: - pass - start += pas - return result - - -def change_appearance_mode_event(new_appearance_mode: str) -> None: - """Change le thème de sombre à Claire""" - ctk.set_appearance_mode(new_appearance_mode) - - -class App(ctk.CTk): - """Classe pour l'interface graphique""" - def __init__(self) -> None: - super().__init__() - - change_appearance_mode_event('dark') - - self.fonction_screen_height = None - self.fonction_screen_width = None - self.grid_columnconfigure(0, weight=0) - self.grid_columnconfigure((1,2,3,4,5), weight=1) - self.grid_rowconfigure(0, weight=0) - self.grid_rowconfigure((1,2,3,4,5), weight=1) - - # touches de la calculette - - self.keys = [] - for i, val in enumerate(["←", "cos", "sin", "tan", "→", "x", "e", "√", "^", "𝜋", 7, 8, 9, "(", ")", 4, 5, 6, "*", "/", 1, 2, 3, "+", "-", - "clear", 0, ".", "ln", "exe"]): # add numbers button - if type(val) == int: - self.keys.append(ctk.CTkButton(self, text=val, command=lambda x=val: self.add_value(x), fg_color=("gray50","gray20"), text_color="gray90", hover_color=("gray40", "gray30"))) - elif val == "clear": - self.keys.append(ctk.CTkButton(self, text=val, command=self.clear_screen)) - elif val == "←": - self.keys.append(ctk.CTkButton(self, text=val, command=self.move_cursor_left)) - elif val == "→": - self.keys.append(ctk.CTkButton(self, text=val, command=self.move_cursor_right)) - else: - self.keys.append(ctk.CTkButton(self, text=val, command=lambda x=val: self.add_value(x))) - self.keys[i].grid(row=i//5+1, column=i % 5+1, sticky="NSEW", padx=5, pady=5) - - # Calcul Frame - - self.calcul_frame = ctk.CTkFrame(self, corner_radius=0, fg_color="transparent") - - self.calcul_screen = ctk.CTkLabel(self.calcul_frame, text="hello") - self.calcul_screen.pack(fill="both", expand=True) - self.calcul_entry = ctk.CTkEntry(self.calcul_frame) - self.calcul_entry.pack(fill="both", expand=True) - - # Fonction Frame - - self.fonction_frame = ctk.CTkFrame(self, fg_color="transparent") - - self.fonction_bornes_frame = ctk.CTkFrame(self.fonction_frame, corner_radius=None) - - self.fonction_bornes_entry = ctk.CTkEntry(self.fonction_bornes_frame) - self.fonction_bornes_entry.insert(0, "-100,100") - self.fonction_bornes_entry.grid(sticky="ew", row=1, padx=10) - - self.fonction_bornes_text = ctk.CTkLabel(self.fonction_bornes_frame, text="Entrez les bornes de tracé: (min, max)") - self.fonction_bornes_text.grid(sticky="ew", row=0) - - self.fonction_bornes_frame.grid(sticky="ns", column=0, rowspan=2) - - - self.fonction_screen = ctk.CTkCanvas(self.fonction_frame) - self.fonction_screen.grid(sticky="nsew", column=1, row=0) - self.fonction_entry = ctk.CTkEntry(self.fonction_frame) - self.fonction_entry.grid(sticky='ew', column=1, row=1) - - # navigation menu - self.navigation_frame = ctk.CTkFrame(self, corner_radius=0) - self.navigation_frame.grid_rowconfigure(4, weight=1) - self.navigation_frame.grid(row=0, rowspan=10, column=0, sticky="nsew") - - self.titre = ctk.CTkLabel(self.navigation_frame, text="Wx Calculator", compound="left" - , font=ctk.CTkFont(size=15, weight="bold")) - self.titre.grid(row=0, column=0, padx=20, pady=20) - - self.home_button = ctk.CTkButton(self.navigation_frame, corner_radius=0, height=40, border_spacing=10 - , text="Calcul", fg_color="transparent", text_color=("gray10", "gray90") - , hover_color=("gray70", "gray30"), anchor="w" - , command=lambda: self.select_frame_by_name("Calcul")) - self.home_button.grid(row=1, column=0, sticky="ew") - - self.function_button = ctk.CTkButton(self.navigation_frame, corner_radius=0, height=40, border_spacing=10 - , text="Fonction", fg_color="transparent", text_color=("gray10", "gray90") - , hover_color=("gray70", "gray30"), anchor="w" - , command=lambda: self.select_frame_by_name("Fonction")) - self.function_button.grid(row=2, column=0, sticky="ew") - - self.appearance_mode_menu = ctk.CTkOptionMenu(self.navigation_frame, values=["Light", "Dark", "System"] - , command=change_appearance_mode_event) - self.appearance_mode_menu.grid(row=4, column=0, padx=20, pady=20, sticky="s") - self.mode = 'Calcul' - - # select default frame - - self.select_frame_by_name("Calcul") - - def select_frame_by_name(self, name): - """Change de mode : passe de calcul à fonction""" - # set button color for selected button - self.home_button.configure(fg_color=("gray75", "gray25") if name == "Calcul" else "transparent") - self.function_button.configure(fg_color=("gray75", "gray25") if name == "Fonction" else "transparent") - - # show selected frame - if name == "Calcul": - self.mode = "Calcul" - self.calcul_frame.grid(padx=20, pady=20, columnspan=5, column=1, row=0, sticky="nsew") - self.keys[-1]._command = self.calculate - - else: - self.calcul_frame.grid_forget() - if name == "Fonction": - self.mode = "Fonction" - self.fonction_frame.grid(columnspan=5, column=1, row=0, padx=20, pady=20, sticky="ns") - self.fonction_frame.update() - self.fonction_screen_width = self.fonction_screen.winfo_width() - self.keys[-1]._command = self.draw_graph - self.fonction_screen_height = self.fonction_screen.winfo_height() - else: - self.fonction_frame.grid_forget() - - def add_value(self, value) -> None: - """Ajoute le charactère à la suite de l'expression""" - parenthesis = False - if value == "e": - value += "^" - if value == "(": - value += ")" - parenthesis = True - elif value in ["ln", "cos", "sin", "tan", "√"]: - value += "()" - parenthesis = True - if self.mode == 'Fonction': - self.fonction_entry.insert(ctk.INSERT, value) - else: - self.calcul_entry.insert(ctk.INSERT, value) - if parenthesis: - self.move_cursor_left() - - def clear_screen(self) -> None: - """Enlève l'affichage actuel""" - if self.mode == "Calcul": - self.calcul_screen.configure(text="") - self.calcul_entry.delete('0', 'end') - else: - self.fonction_screen.delete('all') - self.fonction_entry.delete(0, 'end') - - def move_cursor_left(self): - """Déplace le curseur à gauche""" - if self.mode == "Calcul": - pos = self.calcul_entry.index(ctk.INSERT) - if pos > 0: - self.calcul_entry.icursor(pos -1) - else: - pos = self.fonction_entry.index(ctk.INSERT) - if pos > 0: - self.fonction_entry.icursor(pos-1) - - def move_cursor_right(self): - """Déplace le curseur à droite""" - if self.mode == "Calcul": - pos = self.calcul_entry.index(ctk.INSERT) - if pos < len(self.calcul_entry.get()): - self.calcul_entry.icursor(pos + 1) - else: - pos = self.fonction_entry.index(ctk.INSERT) - if pos < len(self.fonction_entry.get()): - self.fonction_entry.icursor(pos + 1) - - def draw_framing(self, min_x, max_x, min_y, max_y): - """Dessine le cadrillage du graphique""" - ratio = self.fonction_screen_height / self.fonction_screen_width # longueur par largeur du canvas - - # position du max dans l'interval - abscisse = max_y / (abs(min_y)+abs(max_y)) - ordonnee = max_x / (abs(min_x)+abs(max_x)) - - for i in range(20): # dessin des lignes verticales - if i == int(20 - 20*ordonnee): - self.fonction_screen.create_line(0 + i * self.fonction_screen_width/20, 0 - , 0 + i * self.fonction_screen_width/20, self.fonction_screen_height - , fill='red', width=4) - text = self.fonction_screen.create_text(0 + i * self.fonction_screen_width/20, 10, text=max_y) - rect = self.fonction_screen.create_rectangle(self.fonction_screen.bbox(text), fill="white") - self.fonction_screen.tag_lower(rect, text) - - text = self.fonction_screen.create_text(0 + i * self.fonction_screen_width/20, - self.fonction_screen_height - 10, text=min_y) - rect = self.fonction_screen.create_rectangle(self.fonction_screen.bbox(text), fill="white") - self.fonction_screen.tag_lower(rect, text) - else: - self.fonction_screen.create_line(0 + i * self.fonction_screen_width/20, 0 - , 0 + i * self.fonction_screen_width/20, self.fonction_screen_height - , fill='red') - nb_abscisse_lines = int(20*ratio) # nombre de lignes horizontales à tracer - - for i in range(nb_abscisse_lines): # dessin des lignes horizontales - if i == math.ceil(nb_abscisse_lines * abscisse): # tracer de l'axe des abscisses - self.fonction_screen.create_line(0, 0 + i * self.fonction_screen_width/20 - , self.fonction_screen_width - , 0 + i * self.fonction_screen_width/20, fill='red' - , width=4) - text = self.fonction_screen.create_text(15, 0 + i * self.fonction_screen_width/20 - , text=min_x) - rect = self.fonction_screen.create_rectangle(self.fonction_screen.bbox(text) - , fill="white") - self.fonction_screen.tag_lower(rect, text) - - text = self.fonction_screen.create_text(self.fonction_screen_width - 15 - , 0 + i * self.fonction_screen_width/20 - , text=max_x) - rect = self.fonction_screen.create_rectangle(self.fonction_screen.bbox(text), fill="white") - self.fonction_screen.tag_lower(rect, text) - else: - self.fonction_screen.create_line(0, 0 + i * self.fonction_screen_width/20 - , self.fonction_screen_width - , 0 + i * self.fonction_screen_width/20, fill='red') - - def calculate(self) -> None: - """Calcule dans la partie graphique""" - exp = parse_string_to_list(self.calcul_entry.get()) - exp = npi2tree(inf2npi(exp)) - exp.print_tree() - try: - result = exp.evalue() - except ZeroDivisionError: - result = "ERREUR" - self.calcul_screen.configure(text=result) - - def draw_graph(self): - """Dessine les points du graphique""" - self.fonction_screen.delete('all') - min_x, max_x = map(int, self.fonction_bornes_entry.get().split(',')) - fonction_points = npi2tree(inf2npi(parse_string_to_list(self.fonction_entry.get()))).valeurs_de_fonction(min_x, max_x) - max_y = max(fonction_points, key=lambda item: item[1])[1] - min_y = min(fonction_points, key=lambda item: item[1])[1] - self.draw_framing(min_x, max_x, min_y, max_y) - for i, (x, y) in enumerate(fonction_points): - image_x = i * (self.fonction_screen_width/len(fonction_points)) - image_y = self.fonction_screen_height - (y - min_y) * self.fonction_screen_height / (abs(max_y)+abs(min_y)) - self.fonction_screen.create_rectangle(image_x, image_y, image_x, image_y) - - -def parse_string_to_list(text: str) -> list: - """ Transforme le text en liste d'éléments intelligible par le programme """ - text = list(text) - result = [] - buffer_function = "" - buffer_number = "" - number_first = False # Savoir dans quel ordre sont les chiffres et les lettres - for char in text: - if char == 'e': - result.append(math.e) - elif char == '𝜋': - result.append(math.pi) - elif char.isdigit() or char == ".": - buffer_number += char - if len(buffer_function) == 0: - number_first = True - elif char.isalpha(): - buffer_function += char - else: - if number_first: - result.append(float(buffer_number)) - buffer_number = "" - number_first = False - if len(buffer_function) != 0: - result.append('*') - result.append(buffer_function) - buffer_function = "" - elif char == '(': - result.append('*') - - else: - if len(buffer_function) != 0: - result.append(buffer_function) - buffer_function = "" - if len(buffer_number) != 0: - result.append(float(buffer_number)) - buffer_number = "" - number_first = False - result.append(char) - if len(buffer_number) != 0: - result.append(float(buffer_number)) - if len(buffer_function) != 0: - result.append('*') - result.append(buffer_function) - elif len(buffer_function) != 0: - result.append(buffer_function) - return result - -def npi2tree(expr: list) -> Expression: - """ Renvoie l'arbre formé à partir de l'expression donnée""" - pile = Pile_chaine() - for val in expr: - if not type(val) is float and val != "x": - # on inverse pour avoir les nombres dans le bon ordre - nombre2 = pile.depiler() - if not pile.est_vide(): - pile.empiler(Expression(val, pile.depiler(), nombre2)) - else: - pile.empiler(Expression(val, nombre2)) - else: - pile.empiler(Expression(val)) - return pile.sommet() - - -def inf2npi(expr: list) -> list: - """Transforme une expression infixé en notation polonaise inversée""" - operator_stack = Pile_chaine() - operator_priority = { - '+': 1, - '-': 1, - '*': 2, - '^': 3, - '/': 2, - '(': 0, - ')': 0 - } - output = [] - for val in expr: - if type(val) is float or val == 'x': - output.append(val) - else: - if operator_stack.est_vide() or ( val == '(' or operator_priority[val] > operator_priority[operator_stack.sommet()]): - operator_stack.empiler(val) - else: - while not operator_stack.est_vide(): - if operator_stack.sommet() == '(': - operator_stack.depiler() - else: - output.append(operator_stack.depiler()) - if val != ')': - operator_stack.empiler(val) - while not operator_stack.est_vide(): - output.append(operator_stack.depiler()) - return output - - -# [3, '-', 6, '*', 4, '+', 3] -gui = App() gui.mainloop() diff --git a/package/app.py b/package/app.py new file mode 100644 index 0000000..c9f8ac8 --- /dev/null +++ b/package/app.py @@ -0,0 +1,570 @@ +"""Classe d'application tk et ctk""" + +import math +import customtkinter as ctk +import tkinter as tk +from package.data_structure import Pile_chaine +from package.expression import Expression + + +def change_appearance_mode_event(new_appearance_mode: str) -> None: + """Change le thème de sombre à Claire""" + ctk.set_appearance_mode(new_appearance_mode) + +def parse_string_to_list(text: str) -> list: + """ Transforme le text en liste d'éléments intelligible par le programme """ + text = list(text) + result = [] + buffer_function = "" + buffer_number = "" + number_first = False # Savoir dans quel ordre sont les chiffres et les lettres + for char in text: + if char == 'e': + result.append(math.e) + elif char == '𝜋': + result.append(math.pi) + elif char.isdigit() or char == ".": + buffer_number += char + if len(buffer_function) == 0: + number_first = True + elif char.isalpha(): + buffer_function += char + else: + if number_first: + result.append(float(buffer_number)) + buffer_number = "" + number_first = False + if len(buffer_function) != 0: + result.append('*') + result.append(buffer_function) + buffer_function = "" + elif char == '(': + result.append('*') + + else: + if len(buffer_function) != 0: + result.append(buffer_function) + buffer_function = "" + if len(buffer_number) != 0: + result.append(float(buffer_number)) + buffer_number = "" + number_first = False + result.append(char) + if len(buffer_number) != 0: + result.append(float(buffer_number)) + if len(buffer_function) != 0: + result.append('*') + result.append(buffer_function) + elif len(buffer_function) != 0: + result.append(buffer_function) + return result + +def npi2tree(expr: list) -> Expression: + """ Renvoie l'arbre formé à partir de l'expression donnée""" + pile = Pile_chaine() + for val in expr: + if not type(val) is float and val != "x": + # on inverse pour avoir les nombres dans le bon ordre + nombre2 = pile.depiler() + if not pile.est_vide(): + pile.empiler(Expression(val, pile.depiler(), nombre2)) + else: + pile.empiler(Expression(val, nombre2)) + else: + pile.empiler(Expression(val)) + return pile.sommet() + +def inf2npi(expr: list) -> list: + """Transforme une expression infixé en notation polonaise inversée""" + operator_stack = Pile_chaine() + operator_priority = { + '+': 1, + '-': 1, + '*': 2, + '^': 3, + '/': 2, + '(': 0, + ')': 0 + } + output = [] + for val in expr: + if type(val) is float or val == 'x': + output.append(val) + else: + if operator_stack.est_vide() or ( val == '(' or operator_priority[val] > operator_priority[operator_stack.sommet()]): + operator_stack.empiler(val) + else: + while not operator_stack.est_vide(): + if operator_stack.sommet() == '(': + operator_stack.depiler() + else: + output.append(operator_stack.depiler()) + if val != ')': + operator_stack.empiler(val) + while not operator_stack.est_vide(): + output.append(operator_stack.depiler()) + return output + + +class Ctk_app(ctk.CTk): + """Classe pour l'interface graphique avec custom tkinter""" + def __init__(self) -> None: + super().__init__() + + change_appearance_mode_event('dark') + + self.fonction_screen_height = None + self.fonction_screen_width = None + self.grid_columnconfigure(0, weight=0) + self.grid_columnconfigure((1,2,3,4,5), weight=1) + self.grid_rowconfigure(0, weight=0) + self.grid_rowconfigure((1,2,3,4,5), weight=1) + + # touches de la calculette + + self.keys = [] + for i, val in enumerate(["←", "cos", "sin", "tan", "→", "x", "e", "√", "^", "𝜋", 7, 8, 9, "(", ")", 4, 5, 6, "*", "/", 1, 2, 3, "+", "-", + "clear", 0, ".", "ln", "exe"]): # add numbers button + if type(val) == int: + self.keys.append(ctk.CTkButton(self, text=val, command=lambda x=val: self.add_value(x), fg_color=("gray50","gray20"), text_color="gray90", hover_color=("gray40", "gray30"))) + elif val == "clear": + self.keys.append(ctk.CTkButton(self, text=val, command=self.clear_screen)) + elif val == "←": + self.keys.append(ctk.CTkButton(self, text=val, command=self.move_cursor_left)) + elif val == "→": + self.keys.append(ctk.CTkButton(self, text=val, command=self.move_cursor_right)) + else: + self.keys.append(ctk.CTkButton(self, text=val, command=lambda x=val: self.add_value(x))) + self.keys[i].grid(row=i//5+1, column=i % 5+1, sticky="NSEW", padx=5, pady=5) + + # Calcul Frame + + self.calcul_frame = ctk.CTkFrame(self, corner_radius=0, fg_color="transparent") + + self.calcul_screen = ctk.CTkLabel(self.calcul_frame, text="hello") + self.calcul_screen.pack(fill="both", expand=True) + self.calcul_entry = ctk.CTkEntry(self.calcul_frame) + self.calcul_entry.pack(fill="both", expand=True) + + # Fonction Frame + + self.fonction_frame = ctk.CTkFrame(self, fg_color="transparent") + + self.fonction_bornes_frame = ctk.CTkFrame(self.fonction_frame, corner_radius=None) + + self.fonction_bornes_entry = ctk.CTkEntry(self.fonction_bornes_frame) + self.fonction_bornes_entry.insert(0, "-100,100") + self.fonction_bornes_entry.grid(sticky="ew", row=1, padx=10) + + self.fonction_bornes_text = ctk.CTkLabel(self.fonction_bornes_frame, text="Entrez les bornes de tracé: (min, max)") + self.fonction_bornes_text.grid(sticky="ew", row=0) + + self.fonction_bornes_frame.grid(sticky="ns", column=0, rowspan=2) + + + self.fonction_screen = ctk.CTkCanvas(self.fonction_frame) + self.fonction_screen.grid(sticky="nsew", column=1, row=0) + self.fonction_entry = ctk.CTkEntry(self.fonction_frame) + self.fonction_entry.grid(sticky='ew', column=1, row=1) + + # navigation menu + self.navigation_frame = ctk.CTkFrame(self, corner_radius=0) + self.navigation_frame.grid_rowconfigure(4, weight=1) + self.navigation_frame.grid(row=0, rowspan=10, column=0, sticky="nsew") + + self.titre = ctk.CTkLabel(self.navigation_frame, text="Wx Calculator", compound="left" + , font=ctk.CTkFont(size=15, weight="bold")) + self.titre.grid(row=0, column=0, padx=20, pady=20) + + self.home_button = ctk.CTkButton(self.navigation_frame, corner_radius=0, height=40, border_spacing=10 + , text="Calcul", fg_color="transparent", text_color=("gray10", "gray90") + , hover_color=("gray70", "gray30"), anchor="w" + , command=lambda: self.select_frame_by_name("Calcul")) + self.home_button.grid(row=1, column=0, sticky="ew") + + self.function_button = ctk.CTkButton(self.navigation_frame, corner_radius=0, height=40, border_spacing=10 + , text="Fonction", fg_color="transparent", text_color=("gray10", "gray90") + , hover_color=("gray70", "gray30"), anchor="w" + , command=lambda: self.select_frame_by_name("Fonction")) + self.function_button.grid(row=2, column=0, sticky="ew") + + self.appearance_mode_menu = ctk.CTkOptionMenu(self.navigation_frame, values=["Light", "Dark", "System"] + , command=change_appearance_mode_event) + self.appearance_mode_menu.grid(row=4, column=0, padx=20, pady=20, sticky="s") + self.mode = 'Calcul' + + # select default frame + + self.select_frame_by_name("Calcul") + + def select_frame_by_name(self, name): + """Change de mode : passe de calcul à fonction""" + # set button color for selected button + self.home_button.configure(fg_color=("gray75", "gray25") if name == "Calcul" else "transparent") + self.function_button.configure(fg_color=("gray75", "gray25") if name == "Fonction" else "transparent") + + # show selected frame + if name == "Calcul": + self.mode = "Calcul" + self.calcul_frame.grid(padx=20, pady=20, columnspan=5, column=1, row=0, sticky="nsew") + self.keys[-1]._command = self.calculate + + else: + self.calcul_frame.grid_forget() + if name == "Fonction": + self.mode = "Fonction" + self.fonction_frame.grid(columnspan=5, column=1, row=0, padx=20, pady=20, sticky="ns") + self.fonction_frame.update() + self.fonction_screen_width = self.fonction_screen.winfo_width() + self.keys[-1]._command = self.draw_graph + self.fonction_screen_height = self.fonction_screen.winfo_height() + else: + self.fonction_frame.grid_forget() + + def add_value(self, value) -> None: + """Ajoute le charactère à la suite de l'expression""" + parenthesis = False + if value == "e": + value += "^" + if value == "(": + value += ")" + parenthesis = True + elif value in ["ln", "cos", "sin", "tan", "√"]: + value += "()" + parenthesis = True + if self.mode == 'Fonction': + self.fonction_entry.insert(ctk.INSERT, value) + else: + self.calcul_entry.insert(ctk.INSERT, value) + if parenthesis: + self.move_cursor_left() + + def clear_screen(self) -> None: + """Enlève l'affichage actuel""" + if self.mode == "Calcul": + self.calcul_screen.configure(text="") + self.calcul_entry.delete('0', 'end') + else: + self.fonction_screen.delete('all') + self.fonction_entry.delete(0, 'end') + + def move_cursor_left(self): + """Déplace le curseur à gauche""" + if self.mode == "Calcul": + pos = self.calcul_entry.index(ctk.INSERT) + if pos > 0: + self.calcul_entry.icursor(pos -1) + else: + pos = self.fonction_entry.index(ctk.INSERT) + if pos > 0: + self.fonction_entry.icursor(pos-1) + + def move_cursor_right(self): + """Déplace le curseur à droite""" + if self.mode == "Calcul": + pos = self.calcul_entry.index(ctk.INSERT) + if pos < len(self.calcul_entry.get()): + self.calcul_entry.icursor(pos + 1) + else: + pos = self.fonction_entry.index(ctk.INSERT) + if pos < len(self.fonction_entry.get()): + self.fonction_entry.icursor(pos + 1) + + def draw_framing(self, min_x, max_x, min_y, max_y): + """Dessine le cadrillage du graphique""" + ratio = self.fonction_screen_height / self.fonction_screen_width # longueur par largeur du canvas + + # position du max dans l'interval + abscisse = max_y / (abs(min_y)+abs(max_y)) + ordonnee = max_x / (abs(min_x)+abs(max_x)) + + for i in range(20): # dessin des lignes verticales + if i == int(20 - 20*ordonnee): + self.fonction_screen.create_line(0 + i * self.fonction_screen_width/20, 0 + , 0 + i * self.fonction_screen_width/20, self.fonction_screen_height + , fill='red', width=4) + text = self.fonction_screen.create_text(0 + i * self.fonction_screen_width/20, 10, text=max_y) + rect = self.fonction_screen.create_rectangle(self.fonction_screen.bbox(text), fill="white") + self.fonction_screen.tag_lower(rect, text) + + text = self.fonction_screen.create_text(0 + i * self.fonction_screen_width/20, + self.fonction_screen_height - 10, text=min_y) + rect = self.fonction_screen.create_rectangle(self.fonction_screen.bbox(text), fill="white") + self.fonction_screen.tag_lower(rect, text) + else: + self.fonction_screen.create_line(0 + i * self.fonction_screen_width/20, 0 + , 0 + i * self.fonction_screen_width/20, self.fonction_screen_height + , fill='red') + nb_abscisse_lines = int(20*ratio) # nombre de lignes horizontales à tracer + + for i in range(nb_abscisse_lines): # dessin des lignes horizontales + if i == math.ceil(nb_abscisse_lines * abscisse): # tracer de l'axe des abscisses + self.fonction_screen.create_line(0, 0 + i * self.fonction_screen_width/20 + , self.fonction_screen_width + , 0 + i * self.fonction_screen_width/20, fill='red' + , width=4) + text = self.fonction_screen.create_text(15, 0 + i * self.fonction_screen_width/20 + , text=min_x) + rect = self.fonction_screen.create_rectangle(self.fonction_screen.bbox(text) + , fill="white") + self.fonction_screen.tag_lower(rect, text) + + text = self.fonction_screen.create_text(self.fonction_screen_width - 15 + , 0 + i * self.fonction_screen_width/20 + , text=max_x) + rect = self.fonction_screen.create_rectangle(self.fonction_screen.bbox(text), fill="white") + self.fonction_screen.tag_lower(rect, text) + else: + self.fonction_screen.create_line(0, 0 + i * self.fonction_screen_width/20 + , self.fonction_screen_width + , 0 + i * self.fonction_screen_width/20, fill='red') + + def calculate(self) -> None: + """Calcule dans la partie graphique""" + exp = parse_string_to_list(self.calcul_entry.get()) + exp = npi2tree(inf2npi(exp)) + exp.print_tree() + try: + result = exp.evalue() + except ZeroDivisionError: + result = "ERREUR" + self.calcul_screen.configure(text=result) + + def draw_graph(self): + """Dessine les points du graphique""" + self.fonction_screen.delete('all') + min_x, max_x = map(int, self.fonction_bornes_entry.get().split(',')) + fonction_points = npi2tree(inf2npi(parse_string_to_list(self.fonction_entry.get()))).valeurs_de_fonction(min_x, max_x) + max_y = max(fonction_points, key=lambda item: item[1])[1] + min_y = min(fonction_points, key=lambda item: item[1])[1] + self.draw_framing(min_x, max_x, min_y, max_y) + for i, (x, y) in enumerate(fonction_points): + image_x = i * (self.fonction_screen_width/len(fonction_points)) + image_y = self.fonction_screen_height - (y - min_y) * self.fonction_screen_height / (abs(max_y)+abs(min_y)) + self.fonction_screen.create_rectangle(image_x, image_y, image_x, image_y) + + +class Tk_app(tk.Tk): + """Classe d'application graphique si python < 3.7""" + def __init__(self) -> None: + super().__init__() + + self.fonction_screen_height = None + self.fonction_screen_width = None + + # touches de la calculette + + self.keys = [] + for i, val in enumerate(["←", "cos", "sin", "tan", "→", "x", "e", "√", "^", "𝜋", 7, 8, 9, "(", ")", 4, 5, 6, "*", "/", 1, 2, 3, "+", "-", + "clear", 0, ".", "ln", "exe"]): # add numbers button + if type(val) == int: + self.keys.append(tk.Button(self, text=val, command=lambda x=val: self.add_value(x), background="blue", fg="gray90")) + elif val == "clear": + self.keys.append(tk.Button(self, text=val, command=self.clear_screen)) + elif val == "←": + self.keys.append(tk.Button(self, text=val, command=self.move_cursor_left)) + elif val == "→": + self.keys.append(tk.Button(self, text=val, command=self.move_cursor_right)) + else: + self.keys.append(tk.Button(self, text=val, command=lambda x=val: self.add_value(x))) + self.keys[i].grid(row=i//5+1, column=i % 5+1, sticky="NSEW", padx=5, pady=5) + + # Calcul Frame + + self.calcul_frame = tk.Frame(self, bg="gray30") + + self.calcul_screen = tk.Label(self.calcul_frame, text="hello") + self.calcul_screen.pack(fill="both", expand=True) + self.calcul_entry = tk.Entry(self.calcul_frame, width=50) + self.calcul_entry.pack(fill="both", expand=True) + + # Fonction Frame + + self.fonction_frame = tk.Frame(self, bg="gray30") + + self.fonction_bornes_frame = tk.Frame(self.fonction_frame) + + self.fonction_bornes_entry = tk.Entry(self.fonction_bornes_frame) + self.fonction_bornes_entry.insert(0, "-100,100") + self.fonction_bornes_entry.grid(sticky="ew", row=1, padx=10) + + self.fonction_bornes_text = tk.Label(self.fonction_bornes_frame, text="Entrez les bornes de tracé: (min, max)") + self.fonction_bornes_text.grid(sticky="ew", row=0) + + self.fonction_bornes_frame.grid(sticky="ns", column=0, rowspan=2) + + + self.fonction_screen = tk.Canvas(self.fonction_frame) + self.fonction_screen.grid(sticky="nsew", column=1, row=0) + self.fonction_entry = tk.Entry(self.fonction_frame) + self.fonction_entry.grid(sticky='ew', column=1, row=1) + + # navigation menu + self.navigation_frame = tk.Frame(self, bg="gray50") + self.navigation_frame.grid(row=0, rowspan=10, column=0, sticky="nsew") + + self.titre = tk.Label(self.navigation_frame, text="Wx Calculator", compound="left" + , font=tk.font.Font(size=15, weight="bold")) + self.titre.grid(row=0, column=0, padx=20, pady=20) + + self.home_button = tk.Button(self.navigation_frame, height=3 + , text="Calcul", bg="gray30", fg="gray10", anchor="w" + , command=lambda: self.select_frame_by_name("Calcul")) + self.home_button.grid(row=1, column=0, sticky="ew") + + self.function_button = tk.Button(self.navigation_frame, height=3 + , text="Fonction", bg="gray30", fg="gray10", anchor="w" + , command=lambda: self.select_frame_by_name("Fonction")) + self.function_button.grid(row=2, column=0, sticky="ew") + + self.mode = 'Calcul' + + # select default frame + + self.select_frame_by_name("Calcul") + + def select_frame_by_name(self, name): + """Change de mode : passe de calcul à fonction""" + # set button color for selected button + self.home_button.configure(bg="gray75" if name == "Calcul" else "gray30") + self.function_button.configure(bg="gray75" if name == "Fonction" else "gray30") + + # show selected frame + if name == "Calcul": + self.mode = "Calcul" + self.calcul_frame.grid(padx=20, pady=20, columnspan=5, column=1, row=0, sticky="nsew") + self.keys[-1].configure(command=self.calculate) + + else: + self.calcul_frame.grid_forget() + if name == "Fonction": + self.mode = "Fonction" + self.fonction_frame.grid(columnspan=5, column=1, row=0, padx=20, pady=20, sticky="ns") + self.fonction_frame.update() + self.fonction_screen_width = self.fonction_screen.winfo_width() + self.keys[-1].configure(command=self.draw_graph) + self.fonction_screen_height = self.fonction_screen.winfo_height() + else: + self.fonction_frame.grid_forget() + + def add_value(self, value) -> None: + """Ajoute le charactère à la suite de l'expression""" + parenthesis = False + if value == "e": + value += "^" + if value == "(": + value += ")" + parenthesis = True + elif value in ["ln", "cos", "sin", "tan", "√"]: + value += "()" + parenthesis = True + if self.mode == 'Fonction': + self.fonction_entry.insert(tk.INSERT, value) + else: + self.calcul_entry.insert(tk.INSERT, value) + if parenthesis: + self.move_cursor_left() + + def clear_screen(self) -> None: + """Enlève l'affichage actuel""" + if self.mode == "Calcul": + self.calcul_screen.configure(text="") + self.calcul_entry.delete('0', 'end') + else: + self.fonction_screen.delete('all') + self.fonction_entry.delete(0, 'end') + + def move_cursor_left(self): + """Déplace le curseur à gauche""" + if self.mode == "Calcul": + pos = self.calcul_entry.index(tk.INSERT) + if pos > 0: + self.calcul_entry.icursor(pos -1) + else: + pos = self.fonction_entry.index(tk.INSERT) + if pos > 0: + self.fonction_entry.icursor(pos-1) + + def move_cursor_right(self): + """Déplace le curseur à droite""" + if self.mode == "Calcul": + pos = self.calcul_entry.index(tk.INSERT) + if pos < len(self.calcul_entry.get()): + self.calcul_entry.icursor(pos + 1) + else: + pos = self.fonction_entry.index(tk.INSERT) + if pos < len(self.fonction_entry.get()): + self.fonction_entry.icursor(pos + 1) + + def draw_framing(self, min_x, max_x, min_y, max_y): + """Dessine le cadrillage du graphique""" + ratio = self.fonction_screen_height / self.fonction_screen_width # longueur par largeur du canvas + + # position du max dans l'interval + abscisse = max_y / (abs(min_y)+abs(max_y)) + ordonnee = max_x / (abs(min_x)+abs(max_x)) + + for i in range(20): # dessin des lignes verticales + if i == int(20 - 20*ordonnee): + self.fonction_screen.create_line(0 + i * self.fonction_screen_width/20, 0 + , 0 + i * self.fonction_screen_width/20, self.fonction_screen_height + , fill='red', width=4) + text = self.fonction_screen.create_text(0 + i * self.fonction_screen_width/20, 10, text=max_y) + rect = self.fonction_screen.create_rectangle(self.fonction_screen.bbox(text), fill="white") + self.fonction_screen.tag_lower(rect, text) + + text = self.fonction_screen.create_text(0 + i * self.fonction_screen_width/20, + self.fonction_screen_height - 10, text=min_y) + rect = self.fonction_screen.create_rectangle(self.fonction_screen.bbox(text), fill="white") + self.fonction_screen.tag_lower(rect, text) + else: + self.fonction_screen.create_line(0 + i * self.fonction_screen_width/20, 0 + , 0 + i * self.fonction_screen_width/20, self.fonction_screen_height + , fill='red') + nb_abscisse_lines = int(20*ratio) # nombre de lignes horizontales à tracer + + for i in range(nb_abscisse_lines): # dessin des lignes horizontales + if i == math.ceil(nb_abscisse_lines * abscisse): # tracer de l'axe des abscisses + self.fonction_screen.create_line(0, 0 + i * self.fonction_screen_width/20 + , self.fonction_screen_width + , 0 + i * self.fonction_screen_width/20, fill='red' + , width=4) + text = self.fonction_screen.create_text(15, 0 + i * self.fonction_screen_width/20 + , text=min_x) + rect = self.fonction_screen.create_rectangle(self.fonction_screen.bbox(text) + , fill="white") + self.fonction_screen.tag_lower(rect, text) + + text = self.fonction_screen.create_text(self.fonction_screen_width - 15 + , 0 + i * self.fonction_screen_width/20 + , text=max_x) + rect = self.fonction_screen.create_rectangle(self.fonction_screen.bbox(text), fill="white") + self.fonction_screen.tag_lower(rect, text) + else: + self.fonction_screen.create_line(0, 0 + i * self.fonction_screen_width/20 + , self.fonction_screen_width + , 0 + i * self.fonction_screen_width/20, fill='red') + + def calculate(self) -> None: + """Calcule dans la partie graphique""" + exp = parse_string_to_list(self.calcul_entry.get()) + exp = npi2tree(inf2npi(exp)) + exp.print_tree() + try: + result = exp.evalue() + except ZeroDivisionError: + result = "ERREUR" + self.calcul_screen.configure(text=result) + + def draw_graph(self): + """Dessine les points du graphique""" + self.fonction_screen.delete('all') + min_x, max_x = map(int, self.fonction_bornes_entry.get().split(',')) + fonction_points = npi2tree(inf2npi(parse_string_to_list(self.fonction_entry.get()))).valeurs_de_fonction(min_x, max_x) + max_y = max(fonction_points, key=lambda item: item[1])[1] + min_y = min(fonction_points, key=lambda item: item[1])[1] + self.draw_framing(min_x, max_x, min_y, max_y) + for i, (x, y) in enumerate(fonction_points): + image_x = i * (self.fonction_screen_width/len(fonction_points)) + image_y = self.fonction_screen_height - (y - min_y) * self.fonction_screen_height / (abs(max_y)+abs(min_y)) + self.fonction_screen.create_rectangle(image_x, image_y, image_x, image_y) \ No newline at end of file diff --git a/data_structure.py b/package/data_structure.py similarity index 100% rename from data_structure.py rename to package/data_structure.py diff --git a/package/expression.py b/package/expression.py new file mode 100644 index 0000000..a702d07 --- /dev/null +++ b/package/expression.py @@ -0,0 +1,73 @@ +"""Fichier de classe pour les expressions""" +import math + +class Expression: + """manipule les expression sous forme d'arbre""" + def __init__(self, val, fils_gauche=None, fils_droit=None) -> None: + self.val = val + self.fils_gauche = fils_gauche + self.fils_droit = fils_droit + + def __str__(self) -> str: + """renvoie l'expression sous forme infixé""" + if self.est_feuille(): + return str(self.val) + return '('+self.fils_gauche.__str__()+str(self.val)+self.fils_droit.__str__()+')' + + def print_tree(self, level=0): + """ Affiche le noeud joliment""" + if self.fils_gauche is not None: + self.fils_gauche.print_tree(level + 1) + print(' ' * 4 * level + '-> ' + str(self.val)) + if self.fils_droit is not None: + self.fils_droit.print_tree(level + 1) + + def est_feuille(self) -> bool: + """ Renvoie true si le noeud est une feuille""" + if self.fils_droit is None and self.fils_gauche is None: + return True + return False + + def evalue(self, x=0) -> float: + """ Renvoie le résultat de l'expression""" + try: + if self.est_feuille(): + if self.val == 'x': + return x + return float(self.val) + elif self.val == '+': + return self.fils_gauche.evalue(x) + self.fils_droit.evalue(x) + elif self.val == '*': + return self.fils_gauche.evalue(x) * self.fils_droit.evalue(x) + elif self.val == '/': + return self.fils_gauche.evalue(x) / self.fils_droit.evalue(x) + elif self.val == '^': + return self.fils_gauche.evalue(x) ** self.fils_droit.evalue(x) + elif self.val == '-': + return self.fils_gauche.evalue(x) - self.fils_droit.evalue(x) + elif self.val == 'ln': + return math.log(self.fils_gauche.evalue(x)) + elif self.val == '√': + return math.sqrt(self.fils_gauche.evalue(x)) + elif self.val == "cos": + return math.cos(self.fils_gauche.evalue(x)) + elif self.val == "sin": + return math.sin(self.fils_gauche.evalue(x)) + elif self.val == "tan": + return math.tan(self.fils_gauche.evalue(x)) + + except ZeroDivisionError: + raise ZeroDivisionError + + def valeurs_de_fonction(self, start, end): + """ Calcul les valeurs entre start et end""" + result = [] + pas = (end - start) / 1000 + while start <= end: + try: + result.append((start, self.evalue(start))) + except: + pass + start += pas + return result +