-
Notifications
You must be signed in to change notification settings - Fork 0
/
graphic.py
194 lines (174 loc) · 6.29 KB
/
graphic.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
from typing import Union
from derivative import Derivative
from function import Function
from window import Window
from settings import *
import sys
class Graph:
"""Object allowing to the user to draw math functions.
It can only plot real numbers, which means that complex numbers wont be plot.
"""
def __init__(
self,
window: object,
x_min: Union[int, float],
x_max: Union[int, float],
y_min: Union[int, float],
y_max: Union[int, float],
step: float,
width: int,
height: int,
axes: bool,
functions: list[str],
) -> None:
"""Graph initialisation
:param window: the window object to draw the graph.
:type window: object
:param x_min: the smallest absciss of the graph
:type x_min: Union[int, float]
:param x_max: the largest absciss of the graph
:type x_max: Union[int, float]
:param y_min: the smallest ordinate of the graph
:type y_min: Union[int, float]
:param y_max: the largest ordinate of the graph
:type y_max: Union[int, float]
:param step: this number represent the step between to absciss of the graph.
if the step is 0.1, the next point of the absciss after 0 is 0.1.
:type step: float
:param width: the width of the window (in pixel)
:type width: int
:param height: the height of the window (in pixel)
:type height: int
:param axes: indicate to draw or not the axes
:type axes: bool
:param functions: the list of the functions to draw
:type functions: list[str]
"""
self.x_min = x_min
self.x_max = x_max
self.y_min = y_min
self.y_max = y_max
self.step = step
self.width = width
self.height = height
self.axes = axes
self.functions = functions
self.window = window
self.coefs = self.calculate_coef()
self.colors = ["blue", "red", "green", "yellow", "purple"]
def calculate_points(self) -> list[list[tuple[float, float]]]:
"""Generate a list of points for all functions
from the ``self.x_min`` absciss to the ``self.x_max``
:return: the list of list containing the points
:rtype: list[list[tuple[float, float]]]
"""
points = []
fi = 0
for f in self.functions:
points.append([])
x = self.x_min
while x < self.x_max:
try:
y = f(x)
if isinstance(y, complex):
raise TypeError
a, b = self.coordinate_to_screen((x, y))
points[fi].append((a, b))
except ZeroDivisionError:
pass
except TypeError:
pass
x += self.step
fi += 1
return points
def calculate_coef(self) -> tuple[float, float, float, float]:
"""The (0, 0) of the window (the top left corner)
is not the same (0, 0) for the graph (the center).
This function calculate a, b, c, d coefficient to go
from coordinate of the graph to coordinate of the window,
using x1 = a*x+b, y1 = c*y+d equations.
:return: the coefficients
:rtype: tuple[float, float, float, float]
"""
x1 = self.x_min
y1 = self.y_min
x2 = self.x_max
y2 = self.y_max
a = self.width / (x2 - x1)
b = -a * x1
c = -self.height / (y2 - y1)
d = self.height - c * y1
return a, b, c, d
def coordinate_to_screen(self, point: tuple[float, float]) -> tuple[float, float]:
"""The (0, 0) of the window (the top left corner)
is not the same (0, 0) for the graph (the center).
This function calculate the coordinate of a point on the
window from a point on the graph, using
x1 = a*x+b, y1 = c*y+d equations.
:param point: the point from the graph
:type point: tuple[float, float]
:return: the point on the window
:rtype: tuple[float, float]
"""
x, y = point
a, b, c, d = self.coefs
return a * x + b, c * y + d
def display(self) -> None:
"""display the graph and the axes on the window"""
if self.axes:
self.display_axes()
self.display_graph()
def display_graph(self) -> None:
"""display functions's points of the window"""
points = self.calculate_points()
fi = 0
c = 0
for i in range(len(points)):
if c == len(self.colors):
c = 0
for point in range(len(points[i])):
if point + 1 < len(points[i]):
x1, y1 = points[i][point]
x2, y2 = points[i][point + 1]
window.line(x1, y1, x2, y2, fill=self.colors[c], width=2)
fi += 1
c += 1
def display_axes(self) -> None:
"""display the axes"""
self.display_absciss()
self.display_ordinate()
def display_absciss(self) -> None:
"""display the absciss"""
p1 = (self.x_min, 0)
p2 = (self.x_max, 0)
p1 = self.coordinate_to_screen(p1)
p2 = self.coordinate_to_screen(p2)
self.abscisse = window.line(p1[0], p1[1], p2[0], p2[1])
def display_ordinate(self) -> None:
"""display the ordinate"""
p1 = (0, self.y_min)
p2 = (0, self.y_max)
p1 = self.coordinate_to_screen(p1)
p2 = self.coordinate_to_screen(p2)
self.ordinate = window.line(p1[0], p1[1], p2[0], p2[1])
def parse_functions() -> None:
"""Get function's expression from the command line.
All functions must been written between ''.
To draw a function derivative, add a "d" at the begining of the
function expression
"""
argv = sys.argv
if len(argv) != 1:
for expr in argv[1:]:
if expr[0] == "d":
FUNCTIONS.append(Derivative(expr[1:]))
else:
FUNCTIONS.append(Function(expr))
if __name__ == "__main__":
parse_functions()
graph = Graph(
Window, X_MIN, X_MAX, Y_MIN, Y_MAX, STEP, WIDTH, HEIGHT, AXES, FUNCTIONS
)
window = Window(WIDTH, HEIGHT)
graph.display()
window.mainloop()