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
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
|
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from configuration import CONFIG
import gestionOMS as oms
import gestion_unites as u
from gestion_donnees import calcule_max_graphique, convertit_jours_vers_python, simplifie_nom
from gestion_erreurs import debug, erreur, warning
from calculs_extrapole import calcule_donnee_extrapolee, calcule_age_extrapole, interpole_lineaire, interpole_lineaire_ordonnee, formate_resultat_donnee, formate_resultat_age, formate_interpole, formate_extrapole
import datetime
from numpy import arange
import matplotlib.pyplot as plt
# Essentiellement, la fonction qui trace la courbe, mais on y trouve également les fonctions d'extrapolation.
# Ainsi que les calculs additionnels.
def cree_figure(conf,l_jours,l_poids,typedonnee,liste_extracalculs, liste_err, enfants_add = []):
""" conf est le dictionnaire de config. l_jours et l_poids les listes des temps (en jours) et de données
(donc pas forcément du poids)
typedonnee est le type de données (voir CONFIG["liste_typedonnees"]
liste_err la liste des erreurs à compléter (voir gestion_erreurs))
Renvoie la figure tracée, et les calculs additionnels sont mis sous forme de chaîne dans la liste
liste_extracalculs
Les enfants en plus sont dans la liste enfants_add. Pour chaque item de la liste, il faut prendre
item[typed] pour avoir accès au nom, ljours, et ldonnees
"""
debug("debut de cree_figure. Config : "+str(conf)+". Nombre d'enfants additionnels : "+str(len(enfants_add)),liste_err)
try:
liste_data_labels_p,liste_data_labels_z = oms.renvoie_liste_labels(conf,CONFIG["liste_data_choisie_p"],CONFIG["liste_data_choisie_z"],liste_err)
except:
erreur("bug avec liste data labels",liste_err)
return ""
######################## Gestion des bornes #############################
# y a-t-il un maxi saisi par l'utilisateur ?
if conf["maxi"] ==0:
# Est-ce qu'on a donné un maxi quand même (car même échelle) ?
if conf["non_sauve"].get("maxi",0) == 0:
jour_maxi = calcule_max_graphique(l_jours)
else:
jour_maxi = conf["non_sauve"]["maxi"]+1
else:
jour_maxi = conf["maxi"]+1
# Si on cherche à extrapoler au-delà
if conf["non_sauve"]["calculextradata_type"] == typedonnee and conf["non_sauve"]["calculextradata_age"]>jour_maxi:
jour_maxi = int(conf["non_sauve"]["calculextradata_age"]) +1
# On s'assure que c'est bien compris dans les bornes
jour_maxi = max(CONFIG["jours_mini_courbe"],min(jour_maxi,CONFIG["jours_maxi_courbe"]))
debug("cree_figure : gestion du jour max : "+str(jour_maxi),liste_err)
##################### Gestion des unités ###############################
# si l'unité n'est pas précisée, ni en "non sauvé" ni par l'utilisateur
if conf["unite"] == "" and conf["non_sauve"].get("unite","") == "":
unite = u.choix_unite(jour_maxi)
debug("Unité non précisée, on choisit "+unite,liste_err)
elif conf["unite"] != "":
unite = conf["unite"]
else:
unite = conf["non_sauve"]["unite"]
##################### Gestion de la prématurité #######################"
prema = int(convertit_jours_vers_python(conf["prematurite"],liste_err))
## Gestion des prémas, deux cas :
# Si agecorrige est oui, alors on veut juste soustraire la valeur de préma
# à toutes les données.
# Si agecorrige est non, alors on veut ajouter la valeur de préma aux courbes de référence.
debug("Prématurité : "+str(prema)+" age corrigé : "+conf["agecorrige"],liste_err)
if prema>0 and conf["agecorrige"] == "oui":
l_jours = [j-prema for j in l_jours]
jour_maxi = jour_maxi - prema
###################### Conversion des unités ###########################""
l_jours_conv = u.convertit_tableau(l_jours,unite,liste_err)
# Attention, comme les jours commencent à partir de 0, faut enlever 1 pour avoir la borne...
age_maxi = u.convertitunite(jour_maxi-1,unite,liste_err)
debug("cree_figure : conversion des unités ok : "+str(l_jours_conv),liste_err)
#####################" Courbes OMS et titre ######################################"
titre = "Courbe de "+typedonnee+" OMS"
if conf["typecourbe"] == "P":
# percentiles
liste_data_labels = liste_data_labels_p
if conf["sexe"] == "M":
fichier_oms = CONFIG["fichiersOMS"][typedonnee]["perc_garcon"]#f_poids_perc_garcon
titre += " (percentiles, garçon)"
elif conf["sexe"] == "F":
fichier_oms = CONFIG["fichiersOMS"][typedonnee]["perc_fille"]
titre += " (percentiles, fille)"
else:
fichier_oms = CONFIG["fichiersOMS"][typedonnee]["perc_mixte"]
titre += " (percentiles)"
elif conf["typecourbe"] == "Z":
liste_data_labels = liste_data_labels_z
if conf["sexe"] == "M":
fichier_oms = CONFIG["fichiersOMS"][typedonnee]["z_garcon"]
titre += " (moyenne et écarts-types, garçon)"
elif conf["sexe"] == "F":
fichier_oms = CONFIG["fichiersOMS"][typedonnee]["z_fille"]
titre += " (moyenne et écarts-types, fille)"
else:
fichier_oms = CONFIG["fichiersOMS"][typedonnee]["z_mixte"]
titre += " (moyenne et écarts-types)"
else:
erreur("Type de courbe invalide"+conf["typecourbe"],liste_err)
return ""
## On finira le titre plus tard quand on aura su qui est concerné
#debug("cree_figure : géré le type de courbe ok. Liste des data labels : "+str(liste_data_labels),liste_err)
debug("Fichier d'où extraire les données : "+fichier_oms,liste_err)
#### On extrait les données des courbes, et on convertit les jours dans l'unité voulues
try:
t = oms.lire_fichier_csv(fichier_oms)
except:
erreur("cree_figure : Impossible d'ouvrir le fichier "+fichier_oms, liste_err)
return ""
debug("cree_figure : Conversion des données OMS à la bonne unité",liste_err)
try:
coljour= (oms.extraire_colonne(t,0,jour_maxi))
if prema>0 and conf["agecorrige"] != "oui":
coljour = [j + prema for j in coljour]
coljour = u.convertit_tableau(coljour,unite,liste_err)
except:
erreur("Problème à la conversion du tableau OMS. jour_maxi = "+str(jour_maxi)+" unite = "+unite,liste_err)
return ""
##################### Création de la figure et du graphique ###################
debug("cree_figure : prête à créer la figure",liste_err)
#### La figure, params
fig = plt.figure(num=None, figsize=(conf["largeur"], conf["hauteur"]), dpi=100, facecolor=conf["couleurs"]["fond"])
plt.rcParams['axes.facecolor'] = conf["couleurs"]["fond"]
plt.rcParams['axes.edgecolor']= conf["couleurs"]["cadretxt"]
plt.rcParams['xtick.color'] = conf["couleurs"]["cadretxt"]
plt.rcParams['ytick.color'] = conf["couleurs"]["cadretxt"]
plt.rcParams['grid.color'] = conf["couleurs"]["grille"]
plt.rcParams['legend.edgecolor'] = conf["couleurs"]["grille"]
ax = plt.axes()
###################### Tracé des différentes courbes
#Tracé des courbes OMS
for (i,label,couleur) in liste_data_labels:
ax.plot(coljour,oms.extraire_colonne(t,i,jour_maxi),label=label,color=couleur)
debug("cree_figure : tracé des courbes OMS ok",liste_err)
### Tracé pour de bon
if l_jours != []:
print(conf["symbole"])
ax.plot(l_jours_conv,l_poids,label=conf["nom"],color=conf["couleurs"]["courbeenfant"],marker=conf["symbole"])
debug("Tracé de la courbe enfant, avec les jours "+str(l_jours_conv),liste_err)
listenoms = []
if conf["nom"] != "": # Ajouter le nom de l'enfant
listenoms.append(conf["nom"])
# if enfants_add != []:
# debug("Il y a des enfants en plus à tracer. Données : "+str(enfants_add), liste_err)
for enfant in enfants_add: # Enfants additionnels éventuels
conf_add, lj, ld = enfant[typedonnee] # On récuère les données
if lj != []: # pas la peine de tracer si y'a rien à tracer
# Ajouter le nom de cet enfant-là
listenoms.append(conf_add["nom"])
# Le mot "courbe" doit être au pluriel du coup !
titre = titre.replace("Courbe ", "Courbes ")
lj_conv = u.convertit_tableau(lj,unite,liste_err)
debug("Tracé de la courbe additionnelle de "+conf_add["nom"]+" config : "+str(conf_add), liste_err)
ax.plot(lj_conv, ld, label=conf_add["nom"], color=conf_add["couleurcourbe"],marker=conf_add["symbole"])
if conf["sexe"] != conf_add["sexe"]:
warning("Attention, tous les enfants n'ont pas le même sexe. La courbe de référence est celle de "+conf["nom"]+" et ne sera pas forcément pertinente pour les autres. Vous pouvez éventuellement essayer la courbe neutre. Remarque : cette alerte s'affichera quand même.", liste_err)
# Si y'a un nom on met "courbe de machin, de bidule, d'alala, de truc"
for i in range(len(listenoms)):
# Mot de liaison : , ou et ?
if i>0 and i!=len(listenoms)-1:
titre += ", "
elif i>0 and i == len(listenoms) -1:
titre+= " et "
else:
titre += " "
# Est-ce que la première lettre est une voyelle ?
lettre = simplifie_nom(listenoms[i]).lower()[0]
if lettre in CONFIG["voyelles"]:
titre += "d'"
else:
titre += "de "
titre += listenoms[i]
if prema>0:
titre+= ", préma de "+conf["prematurite"]
if conf["agecorrige"] == "oui":
titre+=" (courbe en âge corrigé)"
else:
titre+=" (courbe en âge réel, données OMS décalées)"
#### extrapolatios éventuelles
# a-t-on demndé des calculs ?
jextrapole = conf["non_sauve"]["prolongercourbes"] == "oui"
# Est-ce qu'on a demandé un calcul sur cette donnée ?
for calextra in CONFIG["extradata"]:
jextrapole = jextrapole or conf["non_sauve"][calextra+"_type"] == typedonnee
#print(jextrapole)
############################## Là où on extrapole ################################
if jextrapole:
try:
debug("Il faut extrapoler les courbes !", liste_err)
# Prendre l'ensemble des dates "source"
# print(conf["non_sauve"]["nbdataextra"])
if conf["non_sauve"]["nbextradata"] == 0:
sources_extrap = l_jours
sources_extrap_data = l_poids
else:
sources_extrap = l_jours[-conf["non_sauve"]["nbextradata"]:] # les derniers jours
sources_extrap_data = l_poids[-conf["non_sauve"]["nbextradata"]:]
debug("On extrapole sur les jours : "+str(sources_extrap), liste_err)
# On récupère toutes les données extrapolées
dates_extrapole, donnees_extrapole = prolongecourbe(t, sources_extrap, sources_extrap_data, conf["typecourbe"], liste_err)
debug("données extrapolées !", liste_err)
#debug(str(dates_extrapole[0:10])+str(donnees_extrapole[0:10]), liste_err)
# QUe veut-on maintenant sur ces données extrapolées ?
# Afficher la courbe
if conf["non_sauve"]["prolongercourbes"] == "oui":
# On va prendre les extrapolations de la dernière donnée jusqu'à l fin du graphe
debut_extr = int(l_jours[-conf["non_sauve"]["nbextradata"]])
i_debut_extr = dates_extrapole.index(debut_extr)
if jour_maxi >= dates_extrapole[-1]:
i_fin_extr = len(dates_extrapole) -1
else:
i_fin_extr = dates_extrapole.index(jour_maxi)
print("bla", i_debut_extr, i_fin_extr)
# Voilà ce qu'on veut tracer
dates_extrapole_trace = dates_extrapole[i_debut_extr:i_fin_extr+1]
donnees_extrapole_trace = donnees_extrapole[i_debut_extr:i_fin_extr+1]
dates_extrapole_trace = u.convertit_tableau(dates_extrapole_trace,unite,liste_err)
# tracé des données extrapolées
plt.plot(dates_extrapole_trace, donnees_extrapole_trace,color=conf["couleurs"]["cadretxt"], linestyle=(0, (5,7)), marker=None)
debug("Tracé de la courbe extrapolée ok", liste_err)
### Calculer une donnée à l'âge x
if conf["non_sauve"]["calculextradata_type"] == typedonnee:
# On essaie l'interpolation
r = interpole_lineaire(l_jours,l_poids,conf["non_sauve"]["calculextradata_age"], liste_err)
if r==-1:
# ça sera donc une extrapolation
r = calcule_donnee_extrapolee(dates_extrapole, donnees_extrapole, conf["non_sauve"]["calculextradata_age"], liste_err)
message=formate_extrapole(conf["non_sauve"]["nbextradata"])
else:
message=formate_interpole()
texte = formate_resultat_donnee(conf["non_sauve"]["calculextradata_age"], conf["non_sauve"]["calculextradata_date"], r, typedonnee, message, liste_err)
debug("calcul de la donnée extrapolée : "+texte, liste_err)
if texte!="":
liste_extracalculs.append(texte)
#print(liste_extracalculs)
# Ajouter le trait ?
if conf["non_sauve"]["calculextradata_trace"] == "oui":
dessine_guides(conf["non_sauve"]["calculextradata_age"], r, conf["couleurs"]["cadretxt"], unite, ax, liste_err)
### Calculer un âge où on atteint cette donnée
if conf["non_sauve"]["calculextratemps_type"] == typedonnee:
# interpolation
r = interpole_lineaire_ordonnee(l_jours,l_poids,conf["non_sauve"]["calculextratemps_val"], liste_err)
if type(conf["naissance"]) == datetime.date:
rdate = conf["naissance"] + datetime.timedelta(days=r)
else:
rdate = None
if r==-1:
# ça sera donc une extrapolation
r = calcule_age_extrapole(dates_extrapole, donnees_extrapole, conf["non_sauve"]["calculextratemps_val"], liste_err)
if type(conf["naissance"]) == datetime.date:
rdate = conf["naissance"] + datetime.timedelta(days=round(r))
else:
rdate = None
message=formate_extrapole(conf["non_sauve"]["nbextradata"])
else:
message=formate_interpole()
print(r, rdate)
texte = formate_resultat_age(r, rdate, conf["non_sauve"]["calculextratemps_val"], typedonnee, message, liste_err)
#r = calcule_age_extrapole(dates_extrapole, donnees_extrapole, conf["non_sauve"]["calculextratemps_val"], typedonnee, liste_err)
if texte!="":
liste_extracalculs.append(texte)
print(liste_extracalculs)
# Ajouter le trait ?
if conf["non_sauve"]["calculextratemps_trace"]:
dessine_guides(r, conf["non_sauve"]["calculextratemps_val"], conf["couleurs"]["cadretxt"], unite, ax, liste_err)
except:
warning("Des problèmes pour extrapoler...", liste_err)
else:
debug("On ne trace pas de courbe enfant", liste_err)
###################" Gestion de l'échelle #####################
debug("Courbes tracées. Il n'y a plus qu'à gérer l'échelle", liste_err)
### échelle à régler
# On extrait la valeur min et la valeur max des poids des courbes OMS et des données
(colonne_min,_,_) = liste_data_labels[-1]
(colonne_max,_,_) = liste_data_labels[0]
# poids max OMS
poids_min = min(oms.extraire_colonne(t,colonne_min,jour_maxi))
poids_max = max(oms.extraire_colonne(t,colonne_max,jour_maxi))
if l_jours != []:
poids_min = min(min(l_poids),poids_min)
# Pour le poids max, voir la dernière valeur du tableau
i = 0
while i<len(l_jours) and l_jours[i]<jour_maxi:
i=i+1
poids_max = max(max(l_poids[0:i+1]),poids_max)
# On ajuste un peu ces min et max
# min : valeur min -1kg
poids_min = max(0,poids_min-1)
#max : +5%
poids_max = poids_max * 1.05
### Repères additionnels éventuels.
#reperes est une liste qui contient des dictionnaires avec "typed" (type de donnée : âge, etc), "donnee" :
#la donnée (en jours pour l'âge), et "texte": le texte à mettre sur le repère en question.
#Pour tracer des repères verticaux (horizontaux plus tard) sur la courbe.
for rep in conf["liste_reperes"]:
if rep.get("trace", "") == "oui": # SI on veut tracer
agec = u.convertitunite(rep["donnee"], unite, liste_err)
# Tracé de la ligne verticale
ax.vlines(agec, poids_min, poids_max, linestyles="dashed", color=conf["couleurs"]["cadretxt"])
# date à afficher ?
if rep["affichedate"] == "oui" and rep["date"] != "":
ax.text(agec, poids_min,rep["date"]+" ", rotation=90, verticalalignment='top', horizontalalignment='center', color=conf["couleurs"]["cadretxt"], fontstyle="italic")
# Si y'a un texte à afficher
if rep["texte"] != "":
ax.text(agec, poids_min, " "+rep["texte"], rotation=90, verticalalignment='bottom', horizontalalignment='right', color=conf["couleurs"]["cadretxt"])
# Grille custom ?
if conf["non_sauve"]["grilleamelio"] == "oui":
debug("On a choisi la grille plus jolie", liste_err)
pas=u.choix_echelle_data(typedonnee, poids_max)
# data_min_arrondie
minechelle = int(poids_min/pas[0])*pas[0]
debug("pas choisis pour l'échelle en y : "+str(pas), liste_err)
echellemajeure = arange(minechelle, poids_max, pas[0])
if pas[1] >0:
echellemineure = arange(minechelle, poids_max, pas[1])
else:
echellemineure = []
ax.set_yticks(echellemajeure, minor=False)
ax.set_yticks(echellemineure, minor=True)
# échelle en temps
pas=u.choix_echelle_temps(unite, age_maxi)
debug("pas choisis pour l'échelle en x : "+str(pas), liste_err)
echellemajeure = arange(0,age_maxi, pas[0])
if pas[1] >0:
echellemineure = arange(0,age_maxi, pas[1])
else:
echellemineure = []
ax.set_xticks(echellemajeure, minor=False)
ax.set_xticks(echellemineure, minor=True)
################################# Aspect du graphique
debug("On commende la déco du graphique", liste_err)
# La grille
ax.grid(conf["grille"]=="oui")
ax.grid(conf["grille"] == "oui", which="minor", linestyle="--")
plt.xlabel("Âge en "+unite,color=conf["couleurs"]["cadretxt"])
plt.ylabel(typedonnee.capitalize()+" en "+CONFIG["unites_typedonnees"][typedonnee],color=conf["couleurs"]["cadretxt"])
plt.title(titre,color=conf["couleurs"]["cadretxt"])
if l_jours_conv == []:
plt.axis([0,age_maxi, poids_min, poids_max])
else:
plt.axis([min(0,l_jours_conv[0]),age_maxi,poids_min,poids_max])
if conf['legende']=="oui":
legende = plt.legend(loc=conf['positionlegende'])
plt.setp(legende.get_texts(), color=conf["couleurs"]["cadretxt"])
fig.tight_layout()
debug("Fin de cree_figure, tout va bien.", liste_err)
return fig
######################################## Pour extrapoler la courbe
def prolongecourbe(tableauOMS, dates, donnees, typecourbe, liste_err):
""" tableauOMS est le ableau des données OMS. dates et donnees sont les dates (jours)
et les données d'où on extrapole. On calcule toutes les dates comme des sauvages.
On renvoie la liste des jours totale et la liste des data_totales
(tableaux de jours)
typecourbe est P ou Z. Pour P il faut commencer à regarder à l'indice 4, pour Z
à l'indice 1
On renvoie [],[] si pas pu extrapoler. """
# les lignes OMS correspondant aux dates données
lignesoms = [tableauOMS[int(date)] for date in dates]
debug("prolongecourbe : Lignes OMS :"+str(lignesoms)+" valeur de données : "+str(donnees), liste_err)
# Principe : on cherche dans quel intervalle de "colonnes" on se situe.
# On va donc regarder pour chaque donnée entre quels i on se situe,et après
# prendre le plus grand intervalle.
# Numéros de colonnes d'où on part. Pour la fin c'est forcément longueur-1
if typecourbe == "P":
idep=4
else:
idep = 1
liste_indices = []
for k in range(len(dates)):
i= idep
ligne = lignesoms[k]
while i<len(ligne) and ligne[i]<donnees[k]:
i+=1
debug("prolongecourbe : on a trouvé la valeur de i : "+str(i),liste_err)
if i>=len(ligne):
warning("prolongation de courbe : pas réussi... donnée trop haute !", liste_err)
return [],[]
if i==idep:
warning("prolongation de courbe : pas réussi... donnée trop basse !", liste_err)
return [],[]
liste_indices.append(i)
imin=min(liste_indices) -1
imax=max(liste_indices)
debug("Les données se situent dans les indices : "+str(imin)+", "+str(imax),liste_err)
# Maintenant on doit trouver les coeffs : on se situe en coeff * l[imin]+ (1-coeff)*ligne[imax]
# Et faire la moyenne de ces coeffs
total = 0
for k in range(len(dates)):
ligne = lignesoms[k]
donnee = donnees[k]
total += (donnee - ligne[imax])/(ligne[imin] - ligne[imax])
#print(k)
coeff_moyen = total/len(dates)
debug("Coeff moyen calculé : "+str(coeff_moyen), liste_err)
# On utilisera la même chose pour les nouvelle donnee
# extrapolations
nouvdates =oms.extraire_colonne(tableauOMS,0) # On sort tout.
#print(nouvdates)
nouvdonnees = []
for j in nouvdates:
ligne2 = tableauOMS[int(j)]
nouvdonnees.append(coeff_moyen*ligne2[imin]+ (1-coeff_moyen)*ligne2[imax])
return nouvdates,nouvdonnees
def dessine_guides(t, data, couleur, unite, ax, liste_err):
""" dessine deux lignes, horizontales et verticales qui vont "vers" la courbe
jusqu'aux points (t, data). En pointillés et avec un point dessus."""
debug("Début de dessine_guides"+str(t)+", "+str(data), liste_err)
t_conv = u.convertitunite(t,unite,liste_err)
ax.vlines(t_conv, 0, data, colors=couleur, linestyles="dashed")
ax.hlines(data, 0, t_conv, color=couleur, linestyles="dashed")
ax.plot([t_conv], [data], color=couleur, marker="*", ms=13)
|