Browse Source

fonction cos, sin ln

master
BARRAUX Arthur 2 years ago
parent
commit
45d34ccee0
  1. 328
      main.py

328
main.py

@ -1,10 +1,11 @@
from data_structure import *
import customtkinter as ctk
import math
import customtkinter as ctk
from data_structure import Pile_chaine
class Expression:
"""manipule les expression sous forme d'arbre"""
def __init__(self, val, fils_gauche, fils_droit) -> None:
def __init__(self, val, fils_gauche=None, fils_droit=None) -> None:
self.val = val
self.fils_gauche = fils_gauche
self.fils_droit = fils_droit
@ -15,51 +16,97 @@ class Expression:
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"""
""" 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"""
""" Renvoie le résultat de l'expression"""
try:
if self.est_feuille():
if self.val == 'x':
return x
return float(self.val)
if self.val == '+':
elif self.val == '+':
return self.fils_gauche.evalue(x) + self.fils_droit.evalue(x)
if self.val == '*':
elif self.val == '*':
return self.fils_gauche.evalue(x) * self.fils_droit.evalue(x)
if self.val == '/':
elif self.val == '/':
return self.fils_gauche.evalue(x) / self.fils_droit.evalue(x)
if self.val == '^':
elif self.val == '^':
return self.fils_gauche.evalue(x) ** self.fils_droit.evalue(x)
if self.val == '-':
elif self.val == '-':
return self.fils_gauche.evalue(x) - self.fils_droit.evalue(x)
def valeurs_de_fonction(self):
"""calcul les 100 premieres valeurs"""
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 = []
for i in range(-100, 100):
for i in range(start, end+1):
try:
result.append((i, self.evalue(i)))
except:
pass
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__()
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
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.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)
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
@ -75,40 +122,42 @@ class App(ctk.CTk):
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_screen.grid(sticky="nsew", column=1, row=0)
self.fonction_entry = ctk.CTkEntry(self.fonction_frame)
self.fonction_entry.pack(fill="both", expand=True)
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 = 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 = 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 = 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 = 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")
@ -116,128 +165,211 @@ class App(ctk.CTk):
# 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
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=4, column=1, row=0, padx=20, pady=20, sticky="nsew")
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.numbers[-1]._command = self.draw_graph
self.keys[-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:
"""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("end", value)
self.fonction_entry.insert(ctk.INSERT, value)
else:
self.calcul_entry.insert("end", value)
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*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)
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)
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
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 == 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.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 - coo_abscisse, text=max_x)
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 - coo_abscisse, self.fonction_screen_width, 0 + i * self.fonction_screen_width/20 - coo_abscisse, fill='red')
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:
exp = list(self.calcul_entry.get())
exp = inf2npi(exp)
result = npi2tree(exp).evalue()
"""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')
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
fonction_points = npi2tree(inf2npi(parse_string_to_list(self.fonction_entry.get()))).valeurs_de_fonction(-100, 100)
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(-1000, 1000, min_y, max_y)
for x, y in fonction_points:
image_x = self.fonction_screen_width / 2 + x * (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 = []
elem = ""
buffer_function = ""
buffer_number = ""
number_first = False # Savoir dans quel ordre sont les chiffres et les lettres
for char in text:
if char.isdigit():
elem += char
elif char == 'x':
result.append(char)
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():
print(char)
buffer_function += char
else:
if len(elem) != 0:
result.append(elem)
elem = ""
result.append(char)
if len(elem) != 0:
result.append(elem)
elem = ""
if number_first:
print("number", char)
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 == '(':
print("()")
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)
print(result)
return result
def npi2tree(expr: list) -> Expression:
"""renvoie l'arbre formé a partir de l'expression donnée"""
""" Renvoie l'arbre formé à partir de l'expression donnée"""
pile = Pile_chaine()
for val in expr:
if not val.isdigit() and val != "x":
if not type(val) is float 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))
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, None, None))
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()
@ -245,14 +377,14 @@ def inf2npi(expr: list) -> list:
'+': 1,
'-': 1,
'*': 2,
'^': 2,
'^': 3,
'/': 2,
'(': 0,
')': 0
}
output = []
for val in expr:
if val.isdigit() or val == 'x':
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()]):
@ -261,22 +393,16 @@ def inf2npi(expr: list) -> list:
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())
print(output)
return output
# [3, '-', 6, '*', 4, '+', 3]
print(parse_string_to_list('x'))
print(inf2npi(parse_string_to_list('x')))
gui = App()
gui.mainloop()
Loading…
Cancel
Save