-
Notifications
You must be signed in to change notification settings - Fork 0
/
Info-GUIGrafo.html
418 lines (372 loc) · 26.3 KB
/
Info-GUIGrafo.html
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
<!DOCTYPE html>
<html class="centro">
<head>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<p style="display: none; background:linear-gradient(skyBlue,rgb(0, 80, 255)) padding-box text;-webkit-text-fill-color: transparent;" id="futureTitle">GUI Grafo</p>
<body class="contenuto">
<font family="Verdana" size="6">
<p id="titolo">
Come è nata l'idea
</p>
</font>
<font family="Verdana">
<p>
Iniziando un corso di informatica, a scuola, in testa girava l'idea di creare un programma capace di creare dei grafi e
mi piaceva il fatto di visualizzare su gui il cammino più breve tra due nodi usando l' <span id="codice">Algoritmo di Dijkstra</span>.
</p>
</font>
<br>
<font family="Verdana" size="6">
<p id="titolo">
Fase di Sviluppo
</p>
</font>
<font family="Verdana">
<p>
Sono partito dell'idea di creare un programma molto simile al <a href="Info-SoftwareCAD.html" class="passami-sopra">Software CAD</a> con l'unica cosa che bisognava aggiungere qualche figura in più
quando si creano linee, meglio dire <span id="codice">Archi</span>. <br>
Ogni Arco che si crea vengono creati due nodi, se premo su un nodo l'arco parte da quel nodo e finisce su un altro. Ogni arco ha il suo peso e di default viene calcolata la distanza cartesiana dal centro dei due nodi e viene divisa per 15, successivamente viene inserita in una casella di testo.<br>
Premendo il <span id="codice">tasto destro</span> sull'Arco, viene aperto un piccolo menù in cui viene specificato:
<ul>
<li>
<span id="codice">Direzione</span> che indica il nodo di partenza e di destinazione
</li>
<li>
<span id="codice">Peso</span> dell'arco, che se premuto sopra si può cambiare
</li>
<li>
<span id="codice">Bidirezionale</span> appare un tick (✔) se l'arco è bidirezionale, altrimenti non comparirà nulla, se premuto l'arco viene considerato bidirezionale
</li>
</ul>
Si possono poi fare diverse cose, come trovare la <span id="codice">distanza minima</span> tra due nodi usando l'<span id="codice">Algoritmo di Dijkstra</span> e
in caso di errore <span id="codice">cancellare</span> l'<span id="codice">Arco</span>, il programma poi capirà quali nodi dovrà cancellare.
</p>
</font>
<font family="Verdana" size="6">
<p id="titolo">
Commento sul Codice
</p>
</font>
<font>
<h2>- Classe Nodo</h2>
<code>
<ol>
<li>
class Nodo:
</li>
<li>
<span id="spazio"></span> def __init__(self, coords: list[tuple], n:int, line_id: int):
</li>
<li>
<span id="spazio" style="--spaziatura: 2"></span>self.__coords = coords
</li>
<li>
<span id="spazio" style="--spaziatura: 2"></span>self.__number = n
</li>
<li>
<span id="spazio" style="--spaziatura: 2"></span>self.__line_id = line_id
</li>
<li>
<br>
</li>
<li>
<span id="spazio"></span>def getCoords(self):
</li>
<li>
<span id="spazio" style="--spaziatura: 2"></span>return self.__coords
</li>
<li>
<br>
</li>
<li>
<span id="spazio"></span>def getNumber(self):
</li>
<li>
<span id="spazio" style="--spaziatura: 2"></span>return self.__number
</li>
<li>
<br>
</li>
<li>
<span id="spazio"></span>def getId(self):
</li>
<li>
<span id="spazio" style="--spaziatura: 2"></span>return self.__line_id
</li>
</ol>
</code>
Questo è come vede il programma i <span id="codice">Nodi</span> dei grafi. Composto da:
<ul>
<li>
<span id="codice">Coordinate</span> del centro del nodo
</li>
<li>
<span id="codice">Numero</span> del nodo
</li>
<li>
<span id="codice">ID </span> della linea del canvas
</li>
</ul>
Gli attributi per convenzione li ho dichiarati privati, in python non avendo una <span id="codice">keyword</span> che specifica
esplicitamente l'<span id="codice">ambito di visibilità</span> si usano i <span id="codice">doppi underscore</span> per indicare
un attributo/metodo privato. <br>
Per il resto non c'è nulla di particolare e il codice parla solo.
</font>
<br>
<br>
<font>
<h2>- Classe Arco</h2>
<code>
<ol>
<li>class Arco:</li>
<li><span id="spazio"></span> def __init__(self, coords: list[tuple], nodes: list[Nodo], height:int, line_id:int, bidirectional = False,**kargs):</li>
<li><span id="spazio" style="--spaziatura: 2"></span>self.__coords = coords</li>
<li><span id="spazio" style="--spaziatura: 2"></span>self.__nodes = nodes</li>
<li><span id="spazio" style="--spaziatura: 2"></span>self.__height = height</li>
<li><span id="spazio" style="--spaziatura: 2"></span>self.__line_id = line_id</li>
<li><span id="spazio" style="--spaziatura: 2"></span>self.__bidirectional = bidirectional</li>
<li></li>
<li><span id="spazio" style="--spaziatura: 2"></span>self.__kargs = kargs</li>
<li></li>
<li><span id="spazio"></span>def getCoords(self):</li>
<li><span id="spazio" style="--spaziatura: 2"></span>return self.__coords</li>
<li></li>
<li><span id="spazio"></span>def getNodes(self):</li>
<li><span id="spazio" style="--spaziatura: 2"></span>return self.__nodes</li>
<li></li>
<li><span id="spazio"></span>def getHeight(self):</li>
<li><span id="spazio" style="--spaziatura: 2"></span>return self.__height</li>
<li></li>
<li><span id="spazio"></span>def getId(self):</li>
<li><span id="spazio" style="--spaziatura: 2"></span>return self.__line_id</li>
<li></li>
<li><span id="spazio"></span>def getBidirectional(self) -> bool:</li>
<li><span id="spazio" style="--spaziatura: 2"></span>return self.__bidirectional</li>
<li></li>
<li><span id="spazio"></span>def getKargs(self):</li>
<li><span id="spazio" style="--spaziatura: 2"></span>return self.__kargs</li>
<li></li>
<li><span id="spazio"></span>def getScheme(self, withHeight = True):</li>
<li><span id="spazio" style="--spaziatura: 2"></span>s = ""</li>
<li><span id="spazio" style="--spaziatura: 2"></span>s += " ".join([str(n.getNumber()) for n in self.__nodes])</li>
<li></li>
<li><span id="spazio" style="--spaziatura: 2"></span>if withHeight:</li>
<li><span id="spazio" style="--spaziatura: 3"></span>s += f" {self.__height}"</li>
<li></li>
<li><span id="spazio" style="--spaziatura: 2"></span>if self.__bidirectional:</li>
<li><span id="spazio" style="--spaziatura: 3"></span>s1 = s.split(" ")</li>
<li><span id="spazio" style="--spaziatura: 3"></span>s1[0], s1[1] = s1[1], s1[0]</li>
<li></li>
<li><span id="spazio" style="--spaziatura: 3"></span>s1 = " ".join(s1)</li>
<li></li>
<li><span id="spazio" style="--spaziatura: 3"></span>s += f"\n{s1}"</li>
<li></li>
<li><span id="spazio" style="--spaziatura: 2"></span>return s.strip()</li>
<li></li>
<li><span id="spazio"></span>def getTotalInfo(self):</li>
<li><span id="spazio" style="--spaziatura: 2"></span>s = self.__coords.copy()</li>
<li></li>
<li><span id="spazio" style="--spaziatura: 2"></span>s.append({})</li>
<li></li>
<li><span id="spazio" style="--spaziatura: 2"></span>if self.__bidirectional:</li>
<li><span id="spazio" style="--spaziatura: 3"></span>s[-1]["BIDIREZIONALE"] = 1</li>
<li></li>
<li><span id="spazio" style="--spaziatura: 2"></span>s[-1]["PESO"] = self.__height</li>
<li></li>
<li><span id="spazio" style="--spaziatura: 2"></span>return s</li>
<li></li>
<li></li>
<li><span id="spazio"></span># ------------------------------------------ #</li>
<li></li>
<li><span id="spazio"></span>def toggleBidirectional(self):</li>
<li><span id="spazio" style="--spaziatura: 2"></span>self.__bidirectional = not self.__bidirectional</li>
<li></li>
<li><span id="spazio"></span>def setBidirectional(self, value):</li>
<li><span id="spazio" style="--spaziatura: 2"></span>self.__bidirectional = value</li>
<li></li>
<li><span id="spazio"></span>def setHeight(self, value):</li>
<li><span id="spazio" style="--spaziatura: 2"></span>self.__height = value</li>
</ol>
</code>
Questo è come vede il programma l'<span id="codice">Arco</span>, composto da:
<ul>
<li>
<span id="codice">coords</span> in cui specifica le coordinate dell'arco
</li>
<li>
<span id="codice">nodes</span> è l'array formato da due nodi, quello di partenza e di destinazione
</li>
<li>
<span id="codice">height</span> indica il peso dell'arco
</li>
<li>
<span id="codice">line_id</span> è l'identificativo dell'arco nel canvas
</li>
<li>
<span id="codice">bidirectional</span> di default impostato su False, indica se l'arco è bidirezionale o meno
</li>
<li>
<span id="codice">**kargs</span>, sono degli argomenti aggiuntivi, marcati da una <span id="codice">chiave</span> (key)
</li>
</ul>
Sui metodi <span id="codice">get</span> non c'è molto da dire in quanto hanno un semplice return. C'è da aggiungere qualcosa
su <span id="codice">getScheme</span> & <span id="codice">getTotalInfo</span>:
<ul>
<li>
Il metodo <span id="codice">getScheme</span> ritorna sotto forma di stringa il nodo di partenza, di fine e il peso. <br>
Il parametro <span id="codice">withHeight</span>, impostato di default su <span id="codice">True</span>, mi aggiunge o meno
il peso alla fine.
<br>
<br>
Su riga <span id="codice">31</span>, <span id="codice">s += " ".join([str(n.getNumber()) for n in self.__nodes])</span>, prendo l'array dei
<span id="codice">Nodi</span> e vado a richiamare il metodo <span id="codice">getNumber</span> in modo da ottenere il numero del nodo, e se bidirezionale
mi va a copiare lo schema precedente e va aggiungere gli stessi nodi, ma invertiti. <br>
Infine il <span id="codice">return s.strip()</span>, ritorna lo schema strippato, senza spazi in eccesso.
</li>
<br>
<li>
Il metodo <span id="codice">getTotalInfo</span>, ritorna un array composo dalle
<span id="codice">coordinate cartesiane</span> dell'arco, seguito da un dizionario in cui si distinguono, il <span id="codice">peso</span>
e il fatto di essere <span id="codice">bidirezionale</span>, che se impostato è 1, altrimenti non c'è.
</li>
</ul>
Successivamente abbiamo i metodi <span id="codice">set</span> e uno particolare, <span id="codice">toggle</span>.
I metodi <span id="codice">set</span> impostano il valore il valore del <span id="codice">peso</span>(<span id="codice">setHeight</span>) e il
valore del <span id="codice">bidirezionale</span> (<span id="codice">setBidirectional</span>), solo che ancora non l'ho usato. <br>
Il metodo che uso è il <span id="codice">toggleBidirectional</span> in cui mi inverte il valore di <span id="codice">self.__bidirectional</span>.
</font>
<br>
<br>
<font family="Verdana">
<h2>- Recezione Errore</h2>
<code>
<ol>
<li>def logFunzione(func):</li>
<li><span id='spazio'></span>def wrapper(*args):</li>
<li><span id='spazio' style='--spaziatura: 2'></span>try:</li>
<li><span id='spazio' style='--spaziatura: 3'></span>func(*args)</li>
<li><span id='spazio' style='--spaziatura: 2'></span>except:</li>
<li><span id='spazio' style='--spaziatura: 3'></span>err = traceback.format_exc()</li>
<li><span id='spazio' style='--spaziatura: 3'></span>ora = time.strftime("%d-%m-%Y | %H:%M", time.localtime())</li>
<li><span id='spazio' style='--spaziatura: 3'></span></li>
<li><span id='spazio' style='--spaziatura: 3'></span>messagebox.showerror("Notifica di Servizio", "Si è verificato un errore. Chiedere più informazioni al proprietario del software")</li>
<li><span id='spazio' style='--spaziatura: 3'></span></li>
<li><span id='spazio' style='--spaziatura: 3'></span>with open("log.txt", "a") as file:</li>
<li><span id='spazio' style='--spaziatura: 4'></span>file.write(f"[{ora}] =>{err}\n{'-'*130}\n")</li>
<li><span id='spazio' style='--spaziatura: 3'></span>file.close()</li>
<li><span id='spazio'></span></li>
<li><span id='spazio'></span>return wrapper</li>
<li></li>
<li># ------------------------------------------ #</li>
<li></li>
<li>class GUI(ctk.CTk):</li>
<li><span id='spazio'></span>....</li>
<li><span id='spazio'></span>@logFunzione</li>
<li><span id='spazio'></span>def setPoint(self, event): </li>
<li><span id='spazio' style='--spaziatura: 2'></span>...</li>
</ol>
</code>
Per controllare gli eventuali errori che si verificano durante l'eseguzione del programma, senza riscrivere molto codice
ho introdotto un <a href="https://www.programmareinpython.it/video-corso-python-intermedio/12-i-decoratori/" target="_blank" id="codice">decoratore</a>. <br>
Usando il blocco <span id="codice">try-except</span> riesco a trovare gli errori che che avvengono nella funzione, in questo caso una essenziale perché riguarda
il posizionamento di <span id="codice">Nodi</span> & <span id="codice">Archi</span>, per trovare poi l'errore utilizzo il <span id="codice">traceback</span> che mi fornisce
dettagli sull'errore e poi viene salvato nel file <span id="codice">log.txt</span>. <br>
Quando si verifica un errore appare un messaggio, che dice di segnalarmelo, ho programmato una funzione segreta che mi permette di accedere al file dei <span id="codice">log</span>,
per accederci bisogna fare <span id="codice">tasto destro</span> sul bottone <span id="codice">Cancella Tutto</span> e si aprirà una finestra in cui ci sarà il contenuto del file di <span id="codice">log</span>.
</font>
<br>
<br>
<font family="Verdana">
<h2>- Creazione Arco</h2>
<code>
<ol>
<li>self.archi += 1</li>
<li>temp = self.temp_coods.copy()</li>
<li>pm = [(temp[0][0] + temp[1][0])/2, (temp[0][1] + temp[1][1])/2] </li>
<li></li>
<li>try: angle = math.degrees(math.atan2((temp[0][1] - temp[1][1]), (temp[0][0] - temp[1][0])))</li>
<li>except: angle = 90</li>
<li></li>
<li>lenght = math.sqrt((temp[0][0] - temp[1][0])**2 + (temp[0][1] - temp[1][1])**2)</li>
<li>lenght /= 2</li>
<li>lenght -= 10 </li>
<li></li>
<li>temp = list(map(lambda x: list(x), temp))</li>
<li>temp[0][0] = lenght*cos(angle) + pm[0] # x</li>
<li>temp[0][1] = lenght*sin(angle) + pm[1] # y</li>
<li></li>
<li>temp[1][0] = lenght*cos(angle+180) + pm[0] # x </li>
<li>temp[1][1] = lenght*sin(angle+180) + pm[1] # y</li>
<li></li>
<li>line = self.canvas.create_line(temp, fill="white", arrow="last", arrowshape=(7.0, 11.0, 6.0), activewidth=3, width=2)</li>
</ol>
</code>
Per iniziare incremento il contatore degli archi <span id="codice">self.archi += 1</span>. <br>
Successivamente mi copio le coordinate in una variabile <span id="codice">temp</span>. <br>
L'obiettivo è accorciare la linea di 10 punti, perché se lasciata di default oltre dare fastidio all'occhio, il disegno non è corretto. <br>
Ora, utilizzando un po' di goniometria, considero la linea come il diametro di un cerchio con lighezza tale a quella della linea. <br>
Calcolandomi il punto medio (<span id="codice">pm</span>), lo considero come centro della mia circonferenza, mi trovo la pendenza della linea (<span id="codice">angle</span>) e
la sua lunghezza (<span id="codice">lenght</span>). <br>
Visto che la lunghezza la considero come il diametro, la divido per 2, in modo da ottenere il <span id="codice">raggio</span> della mia circonferenza, e da li gli tolgo 10 punti, dato che serve più corta. <br>
Adesso per sapere le nuove coordinate dei punti utilizzo la <a href="https://edulab.unitn.it/wp-content/uploads/nfs/DiCoMat/curve/book_curve.pdf#subsection.1.3.1" target="_blank" id="codice">parametrizzazione di una circonferenza</a>, usando
come raggio, la nuova lunghezza <span id="codice">lenght</span> e come angolo <span id="codice">angle</span>. Faccio una precisazione sulle ultime righe dove esce come argomento del seno e coseno <span id="codice">angle+180</span>, è una regola
degli <a href="https://www.andreaminini.org/matematica/trigonometria/angoli-associati-alfa-e-pi-greco-piu-alfa" target="_blank" id="codice">angoli associati</a> e, <span id="codice">pm[0]</span> e <span id="codice">pm[1]</span> sono le coordinate <span id="codice">x</span> e <span id="codice">y</span>
del centro della circonferenza. <br>
Infine creo la linea (<span id="codice">line</span>) usando la lista di coordinate <span id="codice">temp</span>.
<br>
<br>
<h2>- Riconoscimento Click</h3>
<code>
<ol>
<li>if self.permitted_coods[0][0]:</li>
<li><span id='spazio'></span>self.nodi += 1</li>
<li><span id='spazio'></span>nodo1 = self.canvas.create_oval((x1,y1),(x2,y2), fill="#202123", outline="white", activewidth=2)</li>
<li><span id='spazio'></span>text_nodo1 = self.canvas.create_text(self.temp_coods[0], text=str(self.nodi), activefill="red", fill="white", state="disabled")</li>
<li><span id='spazio'></span></li>
<li><span id='spazio'></span>ogg_nodo1 = Nodo(self.temp_coods[0], self.nodi, nodo1)</li>
<li><span id='spazio'></span>self.canvas.itemconfig(nodo1, tags=([pickle.dumps(ogg_nodo1)]))</li>
<li><span id='spazio'></span></li>
<li><span id='spazio'></span>if event.num == 2:</li>
<li><span id='spazio' style='--spaziatura: 2'></span>x,y = self.temp_coods[0]</li>
<li><span id='spazio' style='--spaziatura: 2'></span>self.dict_nodes[f"{x} {y}"] = [(x,y), pickle.dumps(ogg_nodo1)]</li>
<li><span id='spazio'></span></li>
<li>else:</li>
<li><span id='spazio'></span>ogg_nodo1 = pickle.loads(self.permitted_coods[0][1])</li>
</ol>
</code>
<span id="codice">self.permitted_coods</span> è una matrice 2x2 in cui dice lo stato attuale nell'esatto punto in cui ho premuto, il primo elemento è un <span id="codice">booleano</span>
in cui mi dice se è uno spazio vuoto o meno, ciò lo determina usando i <span id="codice">tags</span>, se c'è una determianta condizione usando quel tag, sa se è uno spazio vuoto o meno.
Il secondo elemento è il tag stesso. <br>
Se lo spazio è vuoto (<span id="codice">if self.permitted_coods[0][0]</span>) incrementa di 1 il counter dei nodi, <span id="codice">self.nodi += 1</span> e crea la parte visiva del nodo (<span id="codice">nodo1</span>) rappresentandolo come un cerchio e il testo del numero del nodo.
Poi viene creato un <span id="codice">oggetto</span> di tipo <span id="codice">Nodo</span>, il costruttore accetta:
<ul>
<li>
<span id="codice">Coordinate</span> del centro del nodo
</li>
<li>
<span id="codice">Numero</span> del nodo (che il quel momento viene rappresentato da <span id="codice">self.nodi</span>)
</li>
<li>
<span id="codice">ID</span> del disegno del cerchio (<span id="codice">nodo1</span>), nel suo <span id="codice">tag</span> sarà racchiuso l'<span id="codice">oggetto</span> interpretato da <span id="codice">pickle</span>
</li>
</ul>
Dove si vede <span id="codice">if event.num == 2</span> è un "metodo" per quando si deve caricare un file sul programma, quindi, senza creare un altra funzione che faccia la stessa identica cosa, all'evento gli modifico il parametro <span id="codice">num</span>
e lo imposto uguale a 2. <br>
In un evento <span id="codice">num</span> è il numero del pulsante del mouse
<ul>
<li><span id="codice">1</span> tasto sinistro del mouse</li>
<li><span id="codice">2</span> tasto centrale del mouse</li>
<li><span id="codice">3</span> tasto destro del mouse</li>
</ul>
tuttavia quando si chiama la funzione per quando si preme il tasto sinistro del mouse, il parametro <span id="codice">num</span> è sempre 1, impostandolo quindi a 2 capisce che si tratta di un caricamento del file e non di un suo utilizzo normale,
prende le coordinate dall'array <span id="codice">self.temp_coods</span> e lo inserire in un dizionario (conosciuto come <span id="codice">dict</span>) che ha come <span id="codice">key</span> le coordinate sottoforma di stringa e come
<span id="codice">value</span> le coordinate insieme all'<span id="codice">oggetto Nodo</span> interpretato da <span id="codice">pickle</span>.
Quando invece rileva qualcosa recupera l'oggetto contenuto in <span id="codice">self.permitted_coods[0][1]</span> utilizzando <span id="codice">pickle</span>. <br>
</font>
<br>
<script src="script.js"></script>
<button id="tornaindietro" class="bottonetornaindietro" onclick="location.pathname = '/'">Torna Indietro</button>
</body>
</html>