from data_structure import * import customtkinter as ctk import math class Expression: """manipule les expression sous forme d'arbre""" def __init__(self, val, fils_gauche, fils_droit) -> 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 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""" if self.est_feuille(): if self.val == 'x': return x return float(self.val) if self.val == '+': return self.fils_gauche.evalue(x) + self.fils_droit.evalue(x) if self.val == '*': return self.fils_gauche.evalue(x) * self.fils_droit.evalue(x) if self.val == '/': return self.fils_gauche.evalue(x) / self.fils_droit.evalue(x) if self.val == '^': return self.fils_gauche.evalue(x) ** self.fils_droit.evalue(x) if self.val == '-': return self.fils_gauche.evalue(x) - self.fils_droit.evalue(x) def valeurs_de_fonction(self): """calcul les 100 premieres valeurs""" result = [] for i in range(-100, 100): result.append((i, self.evalue(i))) return result class App(ctk.CTk): def __init__(self) -> None: super().__init__() self.grid_columnconfigure(0, weight=0) self.numbers = [] for i, val in enumerate(["(", ")", "/", "^", 7,8,9,"*", 4, 5, 6, "-", 1, 2, 3, "+", "clear", 0, '.', "exe"]): # add numbers button self.numbers.append(ctk.CTkButton(self, text=val)) if val == "clear": self.numbers[i]._command = self.clear_screen else: self.numbers[i]._command = lambda x=val: self.add_value(x) self.numbers[i].grid(row=5+i//4, column=i%4+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_screen = ctk.CTkCanvas(self.fonction_frame) self.fonction_screen.pack(fill="both", expand=True) self.fonction_entry = ctk.CTkEntry(self.fonction_frame) self.fonction_entry.pack(fill="both", expand=True) # 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=self.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): # 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=4, column=1, rowspan=5, row=0, sticky="nsew") self.numbers[-1]._command = self.calculate else: self.calcul_frame.grid_forget() if name == "Fonction": self.mode = "Fonction" self.fonction_frame.grid(columnspan=4, column=1, row=0, padx=20, pady=20, sticky="nsew") self.fonction_frame.update() self.fonction_screen_width = self.fonction_screen.winfo_width() self.numbers[-1]._command = self.draw_graph self.fonction_screen_height = self.fonction_screen.winfo_height() print(self.fonction_screen_height) else: self.fonction_frame.grid_forget() def change_appearance_mode_event(self, new_appearance_mode): ctk.set_appearance_mode(new_appearance_mode) def add_value(self, value) -> None: if self.mode == 'Fonction': self.fonction_entry.insert("end", value) else: self.calcul_entry.insert("end", value) def clear_screen(self) -> None: if self.mode == "Calcul": self.calcul_screen.configure(text="") else: self.fonction_screen.delete('all') def draw_framing(self, min_x, max_x, min_y, max_y): 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*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') coo_abscisse = self.fonction_screen_height * ordonnee % 20 -6 # décalage modulo 20 par rapport à l'origine nb_abscisse_lines = math.ceil(20*ratio)+1 # nombres de lignes horizontales à tracer for i in range(nb_abscisse_lines): # dessin des lignes verticales if i == int(nb_abscisse_lines * abscisse): # tracer de l'axe des abscisses self.fonction_screen.create_line(0, 0 + i * self.fonction_screen_width/20 - coo_abscisse, self.fonction_screen_width, 0 + i * self.fonction_screen_width/20 - coo_abscisse, fill='red', width=4) text = self.fonction_screen.create_text(15, 0 + i * self.fonction_screen_width/20 - coo_abscisse, 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 - coo_abscisse, 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 - coo_abscisse, self.fonction_screen_width, 0 + i * self.fonction_screen_width/20 - coo_abscisse, fill='red') def calculate(self) -> None: exp = list(self.calcul_entry.get()) exp = inf2npi(exp) result = npi2tree(exp).evalue() self.calcul_screen.configure(text=result) def draw_graph(self): self.fonction_screen.delete('all') self.fonction_points = npi2tree(inf2npi(parse_string_to_list(self.fonction_entry.get()))).valeurs_de_fonction() max_y = max(self.fonction_points,key=lambda item:item[1])[1] min_y = min(self.fonction_points,key=lambda item:item[1])[1] self.draw_framing(-100, 100, min_y, max_y) for x, y in self.fonction_points: image_x = self.fonction_screen_width / 2 + x * (self.fonction_screen_width/len(self.fonction_points)) image_y = self.fonction_screen_height - (y - min_y) * self.fonction_screen_height / (abs(max_y)+abs(min_y)) - 3 self.fonction_screen.create_rectangle(image_x, image_y, image_x, image_y) def parse_string_to_list(text: str) -> list: text = list(text) result = [] elem = "" for char in text: if char.isdigit(): elem += char elif char == 'x': result.append(char) else: if len(elem) != 0: result.append(elem) elem = "" result.append(char) if len(elem) != 0: result.append(elem) elem = "" return result def npi2tree(expr: list) -> Expression: """renvoie l'arbre formé a partir de l'expression donnée""" pile = Pile_chaine() for val in expr: if not val.isdigit() and val != "x": # on inverse pour avoir les nombres dans le bon ordre nombre2, nombre1 = pile.depiler(), pile.depiler() pile.empiler(Expression(val, nombre1, nombre2)) else: pile.empiler(Expression(val, None, None)) 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, '^': 2, '/': 2, '(': 0, ')': 0 } output = [] for val in expr: if val.isdigit() 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() if operator_stack.est_vide(): # test si il y a un astérix avant la parenthèse output.append('*') elif operator_stack.sommet() != '*': output.append('*') else: output.append(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] print(parse_string_to_list('x')) print(inf2npi(parse_string_to_list('x'))) gui = App() gui.mainloop()