2 # -*- coding: utf-8 -*-
3 from configuration
import CONFIG
4 import gestionOMS
as oms
5 import gestion_unites
as u
6 from gestion_donnees
import calcule_max_graphique
, convertit_jours_vers_python
, simplifie_nom
7 from gestion_erreurs
import debug
, erreur
, warning
8 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
11 from numpy
import arange
14 import matplotlib
.pyplot
as plt
16 # Essentiellement, la fonction qui trace la courbe, mais on y trouve également les fonctions d'extrapolation.
17 # Ainsi que les calculs additionnels.
20 def cree_figure(conf
,l_jours
,l_poids
,typedonnee
,liste_extracalculs
, liste_err
, enfants_add
= []):
21 """ conf est le dictionnaire de config. l_jours et l_poids les listes des temps (en jours) et de données
22 (donc pas forcément du poids)
23 typedonnee est le type de données (voir CONFIG["liste_typedonnees"]
24 liste_err la liste des erreurs à compléter (voir gestion_erreurs))
25 Renvoie la figure tracée, et les calculs additionnels sont mis sous forme de chaîne dans la liste
28 Les enfants en plus sont dans la liste enfants_add. Pour chaque item de la liste, il faut prendre
29 item[typed] pour avoir accès au nom, ljours, et ldonnees
32 debug("debut de cree_figure. Config : "+str(conf
)+". Nombre d'enfants additionnels : "+str(len(enfants_add
)),liste_err
)
34 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
)
36 erreur("bug avec liste data labels",liste_err
)
39 ######################## Gestion des bornes #############################
40 # y a-t-il un maxi saisi par l'utilisateur ?
42 # Est-ce qu'on a donné un maxi quand même (car même échelle) ?
43 if conf
["non_sauve"].get("maxi",0) == 0:
44 jour_maxi
= calcule_max_graphique(l_jours
)
46 jour_maxi
= conf
["non_sauve"]["maxi"]+1
48 jour_maxi
= conf
["maxi"]+1
50 # Si on cherche à extrapoler au-delà
51 if conf
["non_sauve"]["calculextradata_type"] == typedonnee
and conf
["non_sauve"]["calculextradata_age"]>jour_maxi
:
52 jour_maxi
= int(conf
["non_sauve"]["calculextradata_age"]) +1
54 # On s'assure que c'est bien compris dans les bornes
55 jour_maxi
= max(CONFIG
["jours_mini_courbe"],min(jour_maxi
,CONFIG
["jours_maxi_courbe"]))
56 debug("cree_figure : gestion du jour max : "+str(jour_maxi
),liste_err
)
58 ##################### Gestion des unités ###############################
59 # si l'unité n'est pas précisée, ni en "non sauvé" ni par l'utilisateur
60 if conf
["unite"] == "" and conf
["non_sauve"].get("unite","") == "":
61 unite
= u
.choix_unite(jour_maxi
)
62 debug("Unité non précisée, on choisit "+unite
,liste_err
)
63 elif conf
["unite"] != "":
66 unite
= conf
["non_sauve"]["unite"]
68 ##################### Gestion de la prématurité #######################"
69 prema
= int(convertit_jours_vers_python(conf
["prematurite"],liste_err
))
70 ## Gestion des prémas, deux cas :
71 # Si agecorrige est oui, alors on veut juste soustraire la valeur de préma
72 # à toutes les données.
73 # Si agecorrige est non, alors on veut ajouter la valeur de préma aux courbes de référence.
74 debug("Prématurité : "+str(prema
)+" age corrigé : "+conf
["agecorrige"],liste_err
)
75 if prema
>0 and conf
["agecorrige"] == "oui":
76 l_jours
= [j
-prema
for j
in l_jours
]
77 jour_maxi
= max(jour_maxi
- prema
, 2)
78 ## Si le jour maxi est <0 on met minimum 1 jour quand même !
81 ###################### Conversion des unités ###########################""
82 l_jours_conv
= u
.convertit_tableau(l_jours
,unite
,liste_err
)
83 # Attention, comme les jours commencent à partir de 0, faut enlever 1 pour avoir la borne...
84 age_maxi
= u
.convertitunite(jour_maxi
-1,unite
,liste_err
)
86 debug("cree_figure : conversion des unités ok : "+str(l_jours_conv
),liste_err
)
88 #####################" Courbes OMS et titre ######################################"
89 titre
= "Courbe de "+typedonnee
+" OMS"
90 if conf
["typecourbe"] == "P":
92 liste_data_labels
= liste_data_labels_p
93 if conf
["sexe"] == "M":
94 fichier_oms
= CONFIG
["fichiersOMS"][typedonnee
]["perc_garcon"]#f_poids_perc_garcon
95 titre
+= " (percentiles, garçon)"
96 elif conf
["sexe"] == "F":
97 fichier_oms
= CONFIG
["fichiersOMS"][typedonnee
]["perc_fille"]
98 titre
+= " (percentiles, fille)"
100 fichier_oms
= CONFIG
["fichiersOMS"][typedonnee
]["perc_mixte"]
101 titre
+= " (percentiles)"
102 elif conf
["typecourbe"] == "Z":
103 liste_data_labels
= liste_data_labels_z
104 if conf
["sexe"] == "M":
105 fichier_oms
= CONFIG
["fichiersOMS"][typedonnee
]["z_garcon"]
106 titre
+= " (moyenne et écarts-types, garçon)"
107 elif conf
["sexe"] == "F":
108 fichier_oms
= CONFIG
["fichiersOMS"][typedonnee
]["z_fille"]
109 titre
+= " (moyenne et écarts-types, fille)"
111 fichier_oms
= CONFIG
["fichiersOMS"][typedonnee
]["z_mixte"]
112 titre
+= " (moyenne et écarts-types)"
114 erreur("Type de courbe invalide"+conf
["typecourbe"],liste_err
)
117 ## On finira le titre plus tard quand on aura su qui est concerné
119 #debug("cree_figure : géré le type de courbe ok. Liste des data labels : "+str(liste_data_labels),liste_err)
120 debug("Fichier d'où extraire les données : "+fichier_oms
,liste_err
)
123 #### On extrait les données des courbes, et on convertit les jours dans l'unité voulues
125 t
= oms
.lire_fichier_csv(fichier_oms
)
127 erreur("cree_figure : Impossible d'ouvrir le fichier "+fichier_oms
, liste_err
)
130 debug("cree_figure : Conversion des données OMS à la bonne unité",liste_err
)
132 coljour
= (oms
.extraire_colonne(t
,0,jour_maxi
))
133 if prema
>0 and conf
["agecorrige"] != "oui":
134 coljour
= [j
+ prema
for j
in coljour
]
135 coljour
= u
.convertit_tableau(coljour
,unite
,liste_err
)
137 erreur("Problème à la conversion du tableau OMS. jour_maxi = "+str(jour_maxi
)+" unite = "+unite
,liste_err
)
140 ##################### Création de la figure et du graphique ###################
141 debug("cree_figure : prête à créer la figure",liste_err
)
142 #### La figure, params
144 fig
= plt
.figure(num
=None, figsize
=(conf
["largeur"], conf
["hauteur"]), dpi
=100, facecolor
=conf
["couleurs"]["fond"])
145 plt
.rcParams
['axes.facecolor'] = conf
["couleurs"]["fond"]
146 plt
.rcParams
['axes.edgecolor']= conf
["couleurs"]["cadretxt"]
147 plt
.rcParams
['xtick.color'] = conf
["couleurs"]["cadretxt"]
148 plt
.rcParams
['ytick.color'] = conf
["couleurs"]["cadretxt"]
149 plt
.rcParams
['grid.color'] = conf
["couleurs"]["grille"]
150 plt
.rcParams
['legend.edgecolor'] = conf
["couleurs"]["grille"]
153 ###################### Tracé des différentes courbes
154 #Tracé des courbes OMS
155 for (i
,label
,couleur
) in liste_data_labels
:
156 ax
.plot(coljour
,oms
.extraire_colonne(t
,i
,jour_maxi
),label
=label
,color
=couleur
)
158 debug("cree_figure : tracé des courbes OMS ok",liste_err
)
160 ### Tracé pour de bon
162 print(conf
["symbole"])
163 ax
.plot(l_jours_conv
,l_poids
,label
=conf
["nom"],color
=conf
["couleurs"]["courbeenfant"],marker
=conf
["symbole"])
164 debug("Tracé de la courbe enfant, avec les jours "+str(l_jours_conv
),liste_err
)
168 if conf
["nom"] != "": # Ajouter le nom de l'enfant
169 listenoms
.append(conf
["nom"])
171 # if enfants_add != []:
172 # debug("Il y a des enfants en plus à tracer. Données : "+str(enfants_add), liste_err)
173 for enfant
in enfants_add
: # Enfants additionnels éventuels
174 conf_add
, lj
, ld
= enfant
[typedonnee
] # On récuère les données
175 if lj
!= []: # pas la peine de tracer si y'a rien à tracer
176 # Ajouter le nom de cet enfant-là
177 listenoms
.append(conf_add
["nom"])
178 # Le mot "courbe" doit être au pluriel du coup !
179 titre
= titre
.replace("Courbe ", "Courbes ")
180 lj_conv
= u
.convertit_tableau(lj
,unite
,liste_err
)
181 debug("Tracé de la courbe additionnelle de "+conf_add
["nom"]+" config : "+str(conf_add
), liste_err
)
182 ax
.plot(lj_conv
, ld
, label
=conf_add
["nom"], color
=conf_add
["couleurcourbe"],marker
=conf_add
["symbole"])
184 if conf
["sexe"] != conf_add
["sexe"]:
185 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
)
188 # Si y'a un nom on met "courbe de machin, de bidule, d'alala, de truc"
189 for i
in range(len(listenoms
)):
190 # Mot de liaison : , ou et ?
191 if i
>0 and i
!=len(listenoms
)-1:
193 elif i
>0 and i
== len(listenoms
) -1:
197 # Est-ce que la première lettre est une voyelle ?
198 lettre
= simplifie_nom(listenoms
[i
]).lower()[0]
199 if lettre
in CONFIG
["voyelles"]:
203 titre
+= listenoms
[i
]
206 titre
+= ", préma de "+conf
["prematurite"]
207 if conf
["agecorrige"] == "oui":
208 titre
+=" (courbe en âge corrigé)"
210 titre
+=" (courbe en âge réel, données OMS décalées)"
212 #### extrapolatios éventuelles
213 # a-t-on demndé des calculs ?
214 jextrapole
= conf
["non_sauve"]["prolongercourbes"] == "oui"
215 # Est-ce qu'on a demandé un calcul sur cette donnée ?
217 for calextra
in CONFIG
["extradata"]:
218 jextrapole
= jextrapole
or conf
["non_sauve"][calextra
+"_type"] == typedonnee
221 ############################## Là où on extrapole ################################
224 debug("Il faut extrapoler les courbes !", liste_err
)
225 # Prendre l'ensemble des dates "source"
226 # print(conf["non_sauve"]["nbdataextra"])
227 if conf
["non_sauve"]["nbextradata"] == 0:
228 sources_extrap
= l_jours
229 sources_extrap_data
= l_poids
231 sources_extrap
= l_jours
[-conf
["non_sauve"]["nbextradata"]:] # les derniers jours
232 sources_extrap_data
= l_poids
[-conf
["non_sauve"]["nbextradata"]:]
234 debug("On extrapole sur les jours : "+str(sources_extrap
), liste_err
)
236 # On récupère toutes les données extrapolées
237 dates_extrapole
, donnees_extrapole
= prolongecourbe(t
, sources_extrap
, sources_extrap_data
, conf
["typecourbe"], liste_err
)
238 debug("données extrapolées !", liste_err
)
239 #debug(str(dates_extrapole[0:10])+str(donnees_extrapole[0:10]), liste_err)
241 # QUe veut-on maintenant sur ces données extrapolées ?
243 if conf
["non_sauve"]["prolongercourbes"] == "oui":
244 # On va prendre les extrapolations de la dernière donnée jusqu'à l fin du graphe
245 debut_extr
= int(l_jours
[-conf
["non_sauve"]["nbextradata"]])
246 i_debut_extr
= dates_extrapole
.index(debut_extr
)
247 if jour_maxi
>= dates_extrapole
[-1]:
248 i_fin_extr
= len(dates_extrapole
) -1
250 i_fin_extr
= dates_extrapole
.index(jour_maxi
)
251 print("bla", i_debut_extr
, i_fin_extr
)
252 # Voilà ce qu'on veut tracer
253 dates_extrapole_trace
= dates_extrapole
[i_debut_extr
:i_fin_extr
+1]
254 donnees_extrapole_trace
= donnees_extrapole
[i_debut_extr
:i_fin_extr
+1]
255 dates_extrapole_trace
= u
.convertit_tableau(dates_extrapole_trace
,unite
,liste_err
)
257 # tracé des données extrapolées
258 plt
.plot(dates_extrapole_trace
, donnees_extrapole_trace
,color
=conf
["couleurs"]["cadretxt"], linestyle
=(0, (5,7)), marker
=None)
259 debug("Tracé de la courbe extrapolée ok", liste_err
)
261 ### Calculer une donnée à l'âge x
262 if conf
["non_sauve"]["calculextradata_type"] == typedonnee
:
263 # On essaie l'interpolation
264 r
= interpole_lineaire(l_jours
,l_poids
,conf
["non_sauve"]["calculextradata_age"], liste_err
)
266 # ça sera donc une extrapolation
267 r
= calcule_donnee_extrapolee(dates_extrapole
, donnees_extrapole
, conf
["non_sauve"]["calculextradata_age"], liste_err
)
268 message
=formate_extrapole(conf
["non_sauve"]["nbextradata"])
270 message
=formate_interpole()
272 texte
= formate_resultat_donnee(conf
["non_sauve"]["calculextradata_age"], conf
["non_sauve"]["calculextradata_date"], r
, typedonnee
, message
, liste_err
)
273 debug("calcul de la donnée extrapolée : "+texte
, liste_err
)
275 liste_extracalculs
.append(texte
)
276 #print(liste_extracalculs)
278 if conf
["non_sauve"]["calculextradata_trace"] == "oui":
279 dessine_guides(conf
["non_sauve"]["calculextradata_age"], r
, conf
["couleurs"]["cadretxt"], unite
, ax
, liste_err
)
281 ### Calculer un âge où on atteint cette donnée
282 if conf
["non_sauve"]["calculextratemps_type"] == typedonnee
:
284 r
= interpole_lineaire_ordonnee(l_jours
,l_poids
,conf
["non_sauve"]["calculextratemps_val"], liste_err
)
285 if type(conf
["naissance"]) == datetime
.date
:
286 rdate
= conf
["naissance"] + datetime
.timedelta(days
=r
)
291 # ça sera donc une extrapolation
292 r
= calcule_age_extrapole(dates_extrapole
, donnees_extrapole
, conf
["non_sauve"]["calculextratemps_val"], liste_err
)
293 if type(conf
["naissance"]) == datetime
.date
:
294 rdate
= conf
["naissance"] + datetime
.timedelta(days
=round(r
))
297 message
=formate_extrapole(conf
["non_sauve"]["nbextradata"])
299 message
=formate_interpole()
301 texte
= formate_resultat_age(r
, rdate
, conf
["non_sauve"]["calculextratemps_val"], typedonnee
, message
, liste_err
)
303 #r = calcule_age_extrapole(dates_extrapole, donnees_extrapole, conf["non_sauve"]["calculextratemps_val"], typedonnee, liste_err)
305 liste_extracalculs
.append(texte
)
306 print(liste_extracalculs
)
308 if conf
["non_sauve"]["calculextratemps_trace"]:
309 dessine_guides(r
, conf
["non_sauve"]["calculextratemps_val"], conf
["couleurs"]["cadretxt"], unite
, ax
, liste_err
)
312 warning("Des problèmes pour extrapoler...", liste_err
)
315 debug("On ne trace pas de courbe enfant", liste_err
)
317 ###################" Gestion de l'échelle #####################
318 debug("Courbes tracées. Il n'y a plus qu'à gérer l'échelle", liste_err
)
321 # On extrait la valeur min et la valeur max des poids des courbes OMS et des données
322 (colonne_min
,_
,_
) = liste_data_labels
[-1]
323 (colonne_max
,_
,_
) = liste_data_labels
[0]
326 poids_min
= min(oms
.extraire_colonne(t
,colonne_min
,jour_maxi
))
327 poids_max
= max(oms
.extraire_colonne(t
,colonne_max
,jour_maxi
))
329 poids_min
= min(min(l_poids
),poids_min
)
330 # Pour le poids max, voir la dernière valeur du tableau
332 while i
<len(l_jours
) and l_jours
[i
]<jour_maxi
:
334 poids_max
= max(max(l_poids
[0:i
+1]),poids_max
)
335 # On ajuste un peu ces min et max
336 # min : valeur min -1kg
337 poids_min
= max(0,poids_min
-1)
339 poids_max
= poids_max
* 1.05
343 ### Repères additionnels éventuels.
344 #reperes est une liste qui contient des dictionnaires avec "typed" (type de donnée : âge, etc), "donnee" :
345 #la donnée (en jours pour l'âge), et "texte": le texte à mettre sur le repère en question.
346 #Pour tracer des repères verticaux (horizontaux plus tard) sur la courbe.
348 debug("Échelle ok. Voir s'il y a des repères supplémentaires : "+str(conf
["liste_reperes"]), liste_err
)
350 for rep
in conf
["liste_reperes"]:
351 if rep
.get("trace", "") == "oui": # SI on veut tracer
352 agec
= u
.convertitunite(rep
["donnee"], unite
, liste_err
)
353 # Tracé de la ligne verticale
354 ax
.vlines(agec
, poids_min
, poids_max
, linestyles
="dashed", color
=conf
["couleurs"]["cadretxt"])
356 if rep
["affichedate"] == "oui" and rep
["date"] != "":
357 ax
.text(agec
, poids_min
,rep
["date"]+" ", rotation
=90, verticalalignment
='top', horizontalalignment
='center', color
=conf
["couleurs"]["cadretxt"], fontstyle
="italic")
358 # Si y'a un texte à afficher
359 if rep
["texte"] != "":
360 ax
.text(agec
, poids_min
, " "+rep
["texte"], rotation
=90, verticalalignment
='bottom', horizontalalignment
='right', color
=conf
["couleurs"]["cadretxt"])
367 if conf
["non_sauve"]["grilleamelio"] == "oui":
368 debug("On a choisi la grille plus jolie", liste_err
)
369 pas
=u
.choix_echelle_data(typedonnee
, poids_max
)
371 minechelle
= int(poids_min
/pas
[0])*pas
[0]
373 debug("pas choisis pour l'échelle en y : "+str(pas
), liste_err
)
374 echellemajeure
= arange(minechelle
, poids_max
, pas
[0])
377 echellemineure
= arange(minechelle
, poids_max
, pas
[1])
381 ax
.set_yticks(echellemajeure
, minor
=False)
382 ax
.set_yticks(echellemineure
, minor
=True)
385 # Le jour minimum n'est pas forcément zéro !
386 age_mini
= min(l_jours
[0], 0)
388 pas
=u
.choix_echelle_temps(unite
, age_maxi
- age_mini
)
389 debug("pas choisis pour l'échelle en x : "+str(pas
), liste_err
)
393 echellemajeure
= arange(age_mini
,age_maxi
, pas
[0])
395 echellemineure
= arange(age_mini
,age_maxi
, pas
[1])
398 ax
.set_xticks(echellemajeure
, minor
=False)
399 ax
.set_xticks(echellemineure
, minor
=True)
401 ################################# Aspect du graphique
403 debug("On commende la déco du graphique", liste_err
)
406 ax
.grid(conf
["grille"]=="oui")
407 ax
.grid(conf
["grille"] == "oui", which
="minor", linestyle
="--")
410 plt
.xlabel("Âge en "+unite
,color
=conf
["couleurs"]["cadretxt"])
411 plt
.ylabel(typedonnee
.capitalize()+" en "+CONFIG
["unites_typedonnees"][typedonnee
],color
=conf
["couleurs"]["cadretxt"])
412 plt
.title(titre
,color
=conf
["couleurs"]["cadretxt"])
413 if l_jours_conv
== []:
414 plt
.axis([0,age_maxi
, poids_min
, poids_max
])
416 plt
.axis([min(0,l_jours_conv
[0]),age_maxi
,poids_min
,poids_max
])
419 if conf
['legende']=="oui":
420 legende
= plt
.legend(loc
=conf
['positionlegende'])
421 plt
.setp(legende
.get_texts(), color
=conf
["couleurs"]["cadretxt"])
426 debug("Fin de cree_figure, tout va bien.", liste_err
)
432 ######################################## Pour extrapoler la courbe
434 def prolongecourbe(tableauOMS
, dates
, donnees
, typecourbe
, liste_err
):
435 """ tableauOMS est le ableau des données OMS. dates et donnees sont les dates (jours)
436 et les données d'où on extrapole. On calcule toutes les dates comme des sauvages.
437 On renvoie la liste des jours totale et la liste des data_totales
439 typecourbe est P ou Z. Pour P il faut commencer à regarder à l'indice 4, pour Z
441 On renvoie [],[] si pas pu extrapoler. """
442 # les lignes OMS correspondant aux dates données
443 lignesoms
= [tableauOMS
[int(date
)] for date
in dates
]
444 debug("prolongecourbe : Lignes OMS :"+str(lignesoms
)+" valeur de données : "+str(donnees
), liste_err
)
448 # Principe : on cherche dans quel intervalle de "colonnes" on se situe.
449 # On va donc regarder pour chaque donnée entre quels i on se situe,et après
450 # prendre le plus grand intervalle.
451 # Numéros de colonnes d'où on part. Pour la fin c'est forcément longueur-1
452 if typecourbe
== "P":
458 for k
in range(len(dates
)):
461 while i
<len(ligne
) and ligne
[i
]<donnees
[k
]:
463 debug("prolongecourbe : on a trouvé la valeur de i : "+str(i
),liste_err
)
465 warning("prolongation de courbe : pas réussi... donnée trop haute !", liste_err
)
468 warning("prolongation de courbe : pas réussi... donnée trop basse !", liste_err
)
470 liste_indices
.append(i
)
471 imin
=min(liste_indices
) -1
472 imax
=max(liste_indices
)
473 debug("Les données se situent dans les indices : "+str(imin
)+", "+str(imax
),liste_err
)
474 # Maintenant on doit trouver les coeffs : on se situe en coeff * l[imin]+ (1-coeff)*ligne[imax]
475 # Et faire la moyenne de ces coeffs
477 for k
in range(len(dates
)):
480 total
+= (donnee
- ligne
[imax
])/(ligne
[imin
] - ligne
[imax
])
482 coeff_moyen
= total
/len(dates
)
484 debug("Coeff moyen calculé : "+str(coeff_moyen
), liste_err
)
486 # On utilisera la même chose pour les nouvelle donnee
489 nouvdates
=oms
.extraire_colonne(tableauOMS
,0) # On sort tout.
493 ligne2
= tableauOMS
[int(j
)]
494 nouvdonnees
.append(coeff_moyen
*ligne2
[imin
]+ (1-coeff_moyen
)*ligne2
[imax
])
496 return nouvdates
,nouvdonnees
499 def dessine_guides(t
, data
, couleur
, unite
, ax
, liste_err
):
500 """ dessine deux lignes, horizontales et verticales qui vont "vers" la courbe
501 jusqu'aux points (t, data). En pointillés et avec un point dessus."""
502 debug("Début de dessine_guides"+str(t
)+", "+str(data
), liste_err
)
503 t_conv
= u
.convertitunite(t
,unite
,liste_err
)
504 ax
.vlines(t_conv
, 0, data
, colors
=couleur
, linestyles
="dashed")
505 ax
.hlines(data
, 0, t_conv
, color
=couleur
, linestyles
="dashed")
506 ax
.plot([t_conv
], [data
], color
=couleur
, marker
="*", ms
=13)