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
= jour_maxi
- prema
80 ###################### Conversion des unités ###########################""
81 l_jours_conv
= u
.convertit_tableau(l_jours
,unite
,liste_err
)
82 # Attention, comme les jours commencent à partir de 0, faut enlever 1 pour avoir la borne...
83 age_maxi
= u
.convertitunite(jour_maxi
-1,unite
,liste_err
)
85 debug("cree_figure : conversion des unités ok : "+str(l_jours_conv
),liste_err
)
87 #####################" Courbes OMS et titre ######################################"
88 titre
= "Courbe de "+typedonnee
+" OMS"
89 if conf
["typecourbe"] == "P":
91 liste_data_labels
= liste_data_labels_p
92 if conf
["sexe"] == "M":
93 fichier_oms
= CONFIG
["fichiersOMS"][typedonnee
]["perc_garcon"]#f_poids_perc_garcon
94 titre
+= " (percentiles, garçon)"
95 elif conf
["sexe"] == "F":
96 fichier_oms
= CONFIG
["fichiersOMS"][typedonnee
]["perc_fille"]
97 titre
+= " (percentiles, fille)"
99 fichier_oms
= CONFIG
["fichiersOMS"][typedonnee
]["perc_mixte"]
100 titre
+= " (percentiles)"
101 elif conf
["typecourbe"] == "Z":
102 liste_data_labels
= liste_data_labels_z
103 if conf
["sexe"] == "M":
104 fichier_oms
= CONFIG
["fichiersOMS"][typedonnee
]["z_garcon"]
105 titre
+= " (moyenne et écarts-types, garçon)"
106 elif conf
["sexe"] == "F":
107 fichier_oms
= CONFIG
["fichiersOMS"][typedonnee
]["z_fille"]
108 titre
+= " (moyenne et écarts-types, fille)"
110 fichier_oms
= CONFIG
["fichiersOMS"][typedonnee
]["z_mixte"]
111 titre
+= " (moyenne et écarts-types)"
113 erreur("Type de courbe invalide"+conf
["typecourbe"],liste_err
)
116 ## On finira le titre plus tard quand on aura su qui est concerné
118 #debug("cree_figure : géré le type de courbe ok. Liste des data labels : "+str(liste_data_labels),liste_err)
119 debug("Fichier d'où extraire les données : "+fichier_oms
,liste_err
)
122 #### On extrait les données des courbes, et on convertit les jours dans l'unité voulues
124 t
= oms
.lire_fichier_csv(fichier_oms
)
126 erreur("cree_figure : Impossible d'ouvrir le fichier "+fichier_oms
, liste_err
)
129 debug("cree_figure : Conversion des données OMS à la bonne unité",liste_err
)
131 coljour
= (oms
.extraire_colonne(t
,0,jour_maxi
))
132 if prema
>0 and conf
["agecorrige"] != "oui":
133 coljour
= [j
+ prema
for j
in coljour
]
134 coljour
= u
.convertit_tableau(coljour
,unite
,liste_err
)
136 erreur("Problème à la conversion du tableau OMS. jour_maxi = "+str(jour_maxi
)+" unite = "+unite
,liste_err
)
139 ##################### Création de la figure et du graphique ###################
140 debug("cree_figure : prête à créer la figure",liste_err
)
141 #### La figure, params
143 fig
= plt
.figure(num
=None, figsize
=(conf
["largeur"], conf
["hauteur"]), dpi
=100, facecolor
=conf
["couleurs"]["fond"])
144 plt
.rcParams
['axes.facecolor'] = conf
["couleurs"]["fond"]
145 plt
.rcParams
['axes.edgecolor']= conf
["couleurs"]["cadretxt"]
146 plt
.rcParams
['xtick.color'] = conf
["couleurs"]["cadretxt"]
147 plt
.rcParams
['ytick.color'] = conf
["couleurs"]["cadretxt"]
148 plt
.rcParams
['grid.color'] = conf
["couleurs"]["grille"]
149 plt
.rcParams
['legend.edgecolor'] = conf
["couleurs"]["grille"]
152 ###################### Tracé des différentes courbes
153 #Tracé des courbes OMS
154 for (i
,label
,couleur
) in liste_data_labels
:
155 ax
.plot(coljour
,oms
.extraire_colonne(t
,i
,jour_maxi
),label
=label
,color
=couleur
)
157 debug("cree_figure : tracé des courbes OMS ok",liste_err
)
159 ### Tracé pour de bon
161 print(conf
["symbole"])
162 ax
.plot(l_jours_conv
,l_poids
,label
=conf
["nom"],color
=conf
["couleurs"]["courbeenfant"],marker
=conf
["symbole"])
163 debug("Tracé de la courbe enfant, avec les jours "+str(l_jours_conv
),liste_err
)
167 if conf
["nom"] != "": # Ajouter le nom de l'enfant
168 listenoms
.append(conf
["nom"])
170 # if enfants_add != []:
171 # debug("Il y a des enfants en plus à tracer. Données : "+str(enfants_add), liste_err)
172 for enfant
in enfants_add
: # Enfants additionnels éventuels
173 conf_add
, lj
, ld
= enfant
[typedonnee
] # On récuère les données
174 if lj
!= []: # pas la peine de tracer si y'a rien à tracer
175 # Ajouter le nom de cet enfant-là
176 listenoms
.append(conf_add
["nom"])
177 # Le mot "courbe" doit être au pluriel du coup !
178 titre
= titre
.replace("Courbe ", "Courbes ")
179 lj_conv
= u
.convertit_tableau(lj
,unite
,liste_err
)
180 debug("Tracé de la courbe additionnelle de "+conf_add
["nom"]+" config : "+str(conf_add
), liste_err
)
181 ax
.plot(lj_conv
, ld
, label
=conf_add
["nom"], color
=conf_add
["couleurcourbe"],marker
=conf_add
["symbole"])
183 if conf
["sexe"] != conf_add
["sexe"]:
184 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
)
187 # Si y'a un nom on met "courbe de machin, de bidule, d'alala, de truc"
188 for i
in range(len(listenoms
)):
189 # Mot de liaison : , ou et ?
190 if i
>0 and i
!=len(listenoms
)-1:
192 elif i
>0 and i
== len(listenoms
) -1:
196 # Est-ce que la première lettre est une voyelle ?
197 lettre
= simplifie_nom(listenoms
[i
]).lower()[0]
198 if lettre
in CONFIG
["voyelles"]:
202 titre
+= listenoms
[i
]
205 titre
+= ", préma de "+conf
["prematurite"]
206 if conf
["agecorrige"] == "oui":
207 titre
+=" (courbe en âge corrigé)"
209 titre
+=" (courbe en âge réel, données OMS décalées)"
211 #### extrapolatios éventuelles
212 # a-t-on demndé des calculs ?
213 jextrapole
= conf
["non_sauve"]["prolongercourbes"] == "oui"
214 # Est-ce qu'on a demandé un calcul sur cette donnée ?
216 for calextra
in CONFIG
["extradata"]:
217 jextrapole
= jextrapole
or conf
["non_sauve"][calextra
+"_type"] == typedonnee
220 ############################## Là où on extrapole ################################
223 debug("Il faut extrapoler les courbes !", liste_err
)
224 # Prendre l'ensemble des dates "source"
225 # print(conf["non_sauve"]["nbdataextra"])
226 if conf
["non_sauve"]["nbextradata"] == 0:
227 sources_extrap
= l_jours
228 sources_extrap_data
= l_poids
230 sources_extrap
= l_jours
[-conf
["non_sauve"]["nbextradata"]:] # les derniers jours
231 sources_extrap_data
= l_poids
[-conf
["non_sauve"]["nbextradata"]:]
233 debug("On extrapole sur les jours : "+str(sources_extrap
), liste_err
)
235 # On récupère toutes les données extrapolées
236 dates_extrapole
, donnees_extrapole
= prolongecourbe(t
, sources_extrap
, sources_extrap_data
, conf
["typecourbe"], liste_err
)
237 debug("données extrapolées !", liste_err
)
238 #debug(str(dates_extrapole[0:10])+str(donnees_extrapole[0:10]), liste_err)
240 # QUe veut-on maintenant sur ces données extrapolées ?
242 if conf
["non_sauve"]["prolongercourbes"] == "oui":
243 # On va prendre les extrapolations de la dernière donnée jusqu'à l fin du graphe
244 debut_extr
= int(l_jours
[-conf
["non_sauve"]["nbextradata"]])
245 i_debut_extr
= dates_extrapole
.index(debut_extr
)
246 if jour_maxi
>= dates_extrapole
[-1]:
247 i_fin_extr
= len(dates_extrapole
) -1
249 i_fin_extr
= dates_extrapole
.index(jour_maxi
)
250 print("bla", i_debut_extr
, i_fin_extr
)
251 # Voilà ce qu'on veut tracer
252 dates_extrapole_trace
= dates_extrapole
[i_debut_extr
:i_fin_extr
+1]
253 donnees_extrapole_trace
= donnees_extrapole
[i_debut_extr
:i_fin_extr
+1]
254 dates_extrapole_trace
= u
.convertit_tableau(dates_extrapole_trace
,unite
,liste_err
)
256 # tracé des données extrapolées
257 plt
.plot(dates_extrapole_trace
, donnees_extrapole_trace
,color
=conf
["couleurs"]["cadretxt"], linestyle
=(0, (5,7)), marker
=None)
258 debug("Tracé de la courbe extrapolée ok", liste_err
)
260 ### Calculer une donnée à l'âge x
261 if conf
["non_sauve"]["calculextradata_type"] == typedonnee
:
262 # On essaie l'interpolation
263 r
= interpole_lineaire(l_jours
,l_poids
,conf
["non_sauve"]["calculextradata_age"], liste_err
)
265 # ça sera donc une extrapolation
266 r
= calcule_donnee_extrapolee(dates_extrapole
, donnees_extrapole
, conf
["non_sauve"]["calculextradata_age"], liste_err
)
267 message
=formate_extrapole(conf
["non_sauve"]["nbextradata"])
269 message
=formate_interpole()
271 texte
= formate_resultat_donnee(conf
["non_sauve"]["calculextradata_age"], conf
["non_sauve"]["calculextradata_date"], r
, typedonnee
, message
, liste_err
)
272 debug("calcul de la donnée extrapolée : "+texte
, liste_err
)
274 liste_extracalculs
.append(texte
)
275 #print(liste_extracalculs)
277 if conf
["non_sauve"]["calculextradata_trace"] == "oui":
278 dessine_guides(conf
["non_sauve"]["calculextradata_age"], r
, conf
["couleurs"]["cadretxt"], unite
, ax
, liste_err
)
280 ### Calculer un âge où on atteint cette donnée
281 if conf
["non_sauve"]["calculextratemps_type"] == typedonnee
:
283 r
= interpole_lineaire_ordonnee(l_jours
,l_poids
,conf
["non_sauve"]["calculextratemps_val"], liste_err
)
284 if type(conf
["naissance"]) == datetime
.date
:
285 rdate
= conf
["naissance"] + datetime
.timedelta(days
=r
)
290 # ça sera donc une extrapolation
291 r
= calcule_age_extrapole(dates_extrapole
, donnees_extrapole
, conf
["non_sauve"]["calculextratemps_val"], liste_err
)
292 if type(conf
["naissance"]) == datetime
.date
:
293 rdate
= conf
["naissance"] + datetime
.timedelta(days
=round(r
))
296 message
=formate_extrapole(conf
["non_sauve"]["nbextradata"])
298 message
=formate_interpole()
300 texte
= formate_resultat_age(r
, rdate
, conf
["non_sauve"]["calculextratemps_val"], typedonnee
, message
, liste_err
)
302 #r = calcule_age_extrapole(dates_extrapole, donnees_extrapole, conf["non_sauve"]["calculextratemps_val"], typedonnee, liste_err)
304 liste_extracalculs
.append(texte
)
305 print(liste_extracalculs
)
307 if conf
["non_sauve"]["calculextratemps_trace"]:
308 dessine_guides(r
, conf
["non_sauve"]["calculextratemps_val"], conf
["couleurs"]["cadretxt"], unite
, ax
, liste_err
)
311 warning("Des problèmes pour extrapoler...", liste_err
)
314 debug("On ne trace pas de courbe enfant", liste_err
)
316 ###################" Gestion de l'échelle #####################
317 debug("Courbes tracées. Il n'y a plus qu'à gérer l'échelle", liste_err
)
320 # On extrait la valeur min et la valeur max des poids des courbes OMS et des données
321 (colonne_min
,_
,_
) = liste_data_labels
[-1]
322 (colonne_max
,_
,_
) = liste_data_labels
[0]
325 poids_min
= min(oms
.extraire_colonne(t
,colonne_min
,jour_maxi
))
326 poids_max
= max(oms
.extraire_colonne(t
,colonne_max
,jour_maxi
))
328 poids_min
= min(min(l_poids
),poids_min
)
329 # Pour le poids max, voir la dernière valeur du tableau
331 while i
<len(l_jours
) and l_jours
[i
]<jour_maxi
:
333 poids_max
= max(max(l_poids
[0:i
+1]),poids_max
)
334 # On ajuste un peu ces min et max
335 # min : valeur min -1kg
336 poids_min
= max(0,poids_min
-1)
338 poids_max
= poids_max
* 1.05
341 ### Repères additionnels éventuels.
342 #reperes est une liste qui contient des dictionnaires avec "typed" (type de donnée : âge, etc), "donnee" :
343 #la donnée (en jours pour l'âge), et "texte": le texte à mettre sur le repère en question.
344 #Pour tracer des repères verticaux (horizontaux plus tard) sur la courbe.
345 for rep
in conf
["liste_reperes"]:
346 if rep
.get("trace", "") == "oui": # SI on veut tracer
347 agec
= u
.convertitunite(rep
["donnee"], unite
, liste_err
)
348 # Tracé de la ligne verticale
349 ax
.vlines(agec
, poids_min
, poids_max
, linestyles
="dashed", color
=conf
["couleurs"]["cadretxt"])
351 if rep
["affichedate"] == "oui" and rep
["date"] != "":
352 ax
.text(agec
, poids_min
,rep
["date"]+" ", rotation
=90, verticalalignment
='top', horizontalalignment
='center', color
=conf
["couleurs"]["cadretxt"], fontstyle
="italic")
353 # Si y'a un texte à afficher
354 if rep
["texte"] != "":
355 ax
.text(agec
, poids_min
, " "+rep
["texte"], rotation
=90, verticalalignment
='bottom', horizontalalignment
='right', color
=conf
["couleurs"]["cadretxt"])
362 if conf
["non_sauve"]["grilleamelio"] == "oui":
363 debug("On a choisi la grille plus jolie", liste_err
)
364 pas
=u
.choix_echelle_data(typedonnee
, poids_max
)
366 minechelle
= int(poids_min
/pas
[0])*pas
[0]
368 debug("pas choisis pour l'échelle en y : "+str(pas
), liste_err
)
369 echellemajeure
= arange(minechelle
, poids_max
, pas
[0])
372 echellemineure
= arange(minechelle
, poids_max
, pas
[1])
376 ax
.set_yticks(echellemajeure
, minor
=False)
377 ax
.set_yticks(echellemineure
, minor
=True)
380 pas
=u
.choix_echelle_temps(unite
, age_maxi
)
381 debug("pas choisis pour l'échelle en x : "+str(pas
), liste_err
)
383 echellemajeure
= arange(0,age_maxi
, pas
[0])
385 echellemineure
= arange(0,age_maxi
, pas
[1])
388 ax
.set_xticks(echellemajeure
, minor
=False)
389 ax
.set_xticks(echellemineure
, minor
=True)
391 ################################# Aspect du graphique
393 debug("On commende la déco du graphique", liste_err
)
396 ax
.grid(conf
["grille"]=="oui")
397 ax
.grid(conf
["grille"] == "oui", which
="minor", linestyle
="--")
400 plt
.xlabel("Âge en "+unite
,color
=conf
["couleurs"]["cadretxt"])
401 plt
.ylabel(typedonnee
.capitalize()+" en "+CONFIG
["unites_typedonnees"][typedonnee
],color
=conf
["couleurs"]["cadretxt"])
402 plt
.title(titre
,color
=conf
["couleurs"]["cadretxt"])
403 if l_jours_conv
== []:
404 plt
.axis([0,age_maxi
, poids_min
, poids_max
])
406 plt
.axis([min(0,l_jours_conv
[0]),age_maxi
,poids_min
,poids_max
])
409 if conf
['legende']=="oui":
410 legende
= plt
.legend(loc
=conf
['positionlegende'])
411 plt
.setp(legende
.get_texts(), color
=conf
["couleurs"]["cadretxt"])
416 debug("Fin de cree_figure, tout va bien.", liste_err
)
422 ######################################## Pour extrapoler la courbe
424 def prolongecourbe(tableauOMS
, dates
, donnees
, typecourbe
, liste_err
):
425 """ tableauOMS est le ableau des données OMS. dates et donnees sont les dates (jours)
426 et les données d'où on extrapole. On calcule toutes les dates comme des sauvages.
427 On renvoie la liste des jours totale et la liste des data_totales
429 typecourbe est P ou Z. Pour P il faut commencer à regarder à l'indice 4, pour Z
431 On renvoie [],[] si pas pu extrapoler. """
432 # les lignes OMS correspondant aux dates données
433 lignesoms
= [tableauOMS
[int(date
)] for date
in dates
]
434 debug("prolongecourbe : Lignes OMS :"+str(lignesoms
)+" valeur de données : "+str(donnees
), liste_err
)
438 # Principe : on cherche dans quel intervalle de "colonnes" on se situe.
439 # On va donc regarder pour chaque donnée entre quels i on se situe,et après
440 # prendre le plus grand intervalle.
441 # Numéros de colonnes d'où on part. Pour la fin c'est forcément longueur-1
442 if typecourbe
== "P":
448 for k
in range(len(dates
)):
451 while i
<len(ligne
) and ligne
[i
]<donnees
[k
]:
453 debug("prolongecourbe : on a trouvé la valeur de i : "+str(i
),liste_err
)
455 warning("prolongation de courbe : pas réussi... donnée trop haute !", liste_err
)
458 warning("prolongation de courbe : pas réussi... donnée trop basse !", liste_err
)
460 liste_indices
.append(i
)
461 imin
=min(liste_indices
) -1
462 imax
=max(liste_indices
)
463 debug("Les données se situent dans les indices : "+str(imin
)+", "+str(imax
),liste_err
)
464 # Maintenant on doit trouver les coeffs : on se situe en coeff * l[imin]+ (1-coeff)*ligne[imax]
465 # Et faire la moyenne de ces coeffs
467 for k
in range(len(dates
)):
470 total
+= (donnee
- ligne
[imax
])/(ligne
[imin
] - ligne
[imax
])
472 coeff_moyen
= total
/len(dates
)
474 debug("Coeff moyen calculé : "+str(coeff_moyen
), liste_err
)
476 # On utilisera la même chose pour les nouvelle donnee
479 nouvdates
=oms
.extraire_colonne(tableauOMS
,0) # On sort tout.
483 ligne2
= tableauOMS
[int(j
)]
484 nouvdonnees
.append(coeff_moyen
*ligne2
[imin
]+ (1-coeff_moyen
)*ligne2
[imax
])
486 return nouvdates
,nouvdonnees
489 def dessine_guides(t
, data
, couleur
, unite
, ax
, liste_err
):
490 """ dessine deux lignes, horizontales et verticales qui vont "vers" la courbe
491 jusqu'aux points (t, data). En pointillés et avec un point dessus."""
492 debug("Début de dessine_guides"+str(t
)+", "+str(data
), liste_err
)
493 t_conv
= u
.convertitunite(t
,unite
,liste_err
)
494 ax
.vlines(t_conv
, 0, data
, colors
=couleur
, linestyles
="dashed")
495 ax
.hlines(data
, 0, t_conv
, color
=couleur
, linestyles
="dashed")
496 ax
.plot([t_conv
], [data
], color
=couleur
, marker
="*", ms
=13)