summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app.py45
-rw-r--r--data/FAQ_data.txt19
-rw-r--r--data/changelog_data.txt14
-rw-r--r--gestion_donnees.py177
-rw-r--r--static/outilspage.js26
-rw-r--r--static/requetes.js51
-rw-r--r--static/style.css13
-rw-r--r--templates/base.html2
-rw-r--r--templates/changelog.html3
-rw-r--r--templates/faq.html2
-rw-r--r--templates/index.html110
11 files changed, 325 insertions, 137 deletions
diff --git a/app.py b/app.py
index 7baef86..0d2054b 100644
--- a/app.py
+++ b/app.py
@@ -27,8 +27,8 @@ def index():
27 ## charger les donneés dans le formulaire 27 ## charger les donneés dans le formulaire
28 fichier = flask.request.files['fichier_donnees'] 28 fichier = flask.request.files['fichier_donnees']
29 29
30 val_form = fichier_texte_vers_configdonnees(fichier,liste_err) 30 val_form = fichier_json_vers_configdonnees(fichier,liste_err)
31 31 #return str(val_form['nb_data'])
32 return flask.render_template("index.html",err=liste_err[1],valform=val_form) 32 return flask.render_template("index.html",err=liste_err[1],valform=val_form)
33 33
34@app.route('/apropos') 34@app.route('/apropos')
@@ -46,9 +46,10 @@ def courbe_image(ext):
46 config = gere_configuration(data,liste_err) 46 config = gere_configuration(data,liste_err)
47 l_jours,l_poids = gere_donneespoids(data,config["naissance"],liste_err) 47 l_jours,l_poids = gere_donneespoids(data,config["naissance"],liste_err)
48 48
49 # préparer l'export 49 texte = donnees_vers_json(l_jours,l_poids,config)
50 texte = configuration_vers_texte(config) 50
51 texte += donnees_poids_vers_texte(l_jours,l_poids) 51 # noter le nom de l'enfant pour l'export
52 nomenfant = simplifie_nom(config['nom'])
52 53
53 # créer la figure 54 # créer la figure
54 try: 55 try:
@@ -67,12 +68,14 @@ def courbe_image(ext):
67 reponse = flask.jsonify({ "result":result, 68 reponse = flask.jsonify({ "result":result,
68 "messages": liste_err[1], 69 "messages": liste_err[1],
69 "image": base64.b64encode(output.getvalue()).decode("ascii"), 70 "image": base64.b64encode(output.getvalue()).decode("ascii"),
70 "export_txt": texte}) 71 "export_txt": texte,
72 "nomenfant": nomenfant})
71 else: 73 else:
72 reponse = flask.jsonify({ "result":result, 74 reponse = flask.jsonify({ "result":result,
73 "messages": liste_err[1]+liste_err[0], 75 "messages": liste_err[1]+liste_err[0],
74 "image": "", 76 "image": "",
75 "export_txt": texte}) 77 "export_txt": texte,
78 "nomenfant": nomenfant})
76 return reponse 79 return reponse
77 #return flask.Response(base64.b64encode(output.getvalue()), mimetype='text/plain') 80 #return flask.Response(base64.b64encode(output.getvalue()), mimetype='text/plain')
78 elif ext == "png" and result == "success": 81 elif ext == "png" and result == "success":
@@ -84,19 +87,21 @@ def courbe_image(ext):
84 87
85 #return flask.Response(base64.b64encode(output.getvalue()), mimetype='text/plain') 88 #return flask.Response(base64.b64encode(output.getvalue()), mimetype='text/plain')
86 89
87@app.route("/export_donnees",methods=['POST']) 90#@app.route("/export_donnees",methods=['POST'])
88def export_donnees(): 91#def export_donnees():
89 # exporte les données au format texte 92# # exporte les données au format texte
90 liste_err = initialise_erreurs() 93# liste_err = initialise_erreurs()
91 data = flask.request.form 94# data = flask.request.form
92 95#
93 config = gere_configuration(data,liste_err) 96# config = gere_configuration(data,liste_err)
94 l_jours,l_poids = gere_donneespoids(data,config["naissance"],liste_err) 97# l_jours,l_poids = gere_donneespoids(data,config["naissance"],liste_err)
95 98#
96 texte = configuration_vers_texte(config) 99# texte = configuration_vers_texte(config)
97 texte += donnees_poids_vers_texte(l_jours,l_poids) 100# texte += donnees_poids_vers_texte(l_jours,l_poids)
98 101#
99 return flask.Response(texte,mimetype="text/plain") 102# #texte = donnees_vers_json(l_jours,l_poids,config)
103# #return flask.Response(texte,mimetype="application/json")
104# return flask.Response(texte,mimetype="text/plain")
100 105
101@app.route("/faq") 106@app.route("/faq")
102def faq(): 107def faq():
diff --git a/data/FAQ_data.txt b/data/FAQ_data.txt
index fe2f5a5..2313fa8 100644
--- a/data/FAQ_data.txt
+++ b/data/FAQ_data.txt
@@ -1,10 +1,12 @@
1"Utilisation","Comment tracer la courbe ?","Remplissez le formulaire (vous pouvez laisser par défaut tous les champs qui ne vous « parlent » pas), puis cliquez sur ""Je veux la courbe !"". La courbe s'affiche plus bas. Si vous modifiez les données du formulaire, vous pouvez re-cliquer sur ce bouton pour la mettre à jour. Et c'est tout !" 1"Utilisation","Comment tracer la courbe ?","Remplissez le formulaire (vous pouvez laisser par défaut tous les champs qui ne vous « parlent » pas), puis cliquez sur ""Je veux la courbe !"". La courbe s'affiche plus bas. Si vous modifiez les données du formulaire, vous pouvez re-cliquer sur ce bouton pour la mettre à jour. Et c'est tout ! Vous pouvez également télécharger la courbe en cliquant sur le bouton juste en dessous."
2 2
3"Utilisation","Comment sauvegarder mes données pour une prochaine fois ?","L'outil ne propose pas de sauvegarde en ligne, car il ne garde aucune donnée personnelle. Quand vous demandez à tracer la courbe, il s'affiche en bas un champ de texte. Si vous copiez-collez ce contenu et le gardez dans un fichier, vous n'aurez pas à re-saisir les données. Utilisez la partie ""Importer le fichier de données"" puis ""Charger les données"" : les champs seront alors pré-remplis et vous pourrez les modifier ou ajouter des données (par exemple une pesée récente)." 3"Utilisation","Comment sauvegarder mes données pour une prochaine fois ?","L'outil ne propose pas de sauvegarde en ligne, car il ne garde aucune donnée personnelle. Vous pouvez télécharger vos propres données (dans un format adapté) en cliquant sur « Télécharger les données ». Enregistrez ce fichier quelque part. Pour les réutiliser, utilisez la partie ""Importer le fichier de données"" puis ""Charger les données"" : les champs seront alors pré-remplis et vous pourrez les modifier ou ajouter des données (par exemple une pesée récente)."
4 4
5"Utilisation","Je ne comprends pas comment saisir les données âge/poids !","Pour chaque pesée, vous avez le choix entre donner son âge, ou donner la date de la pesée. Pour la date de la pesée, il faut que vous ayiez rempli la date de naissance (sinon l'outil ne peut pas calculer l'âge !). Pour l'âge, vous pouvez indiquer en années, mois, semaines, jours, et mélanger plusieurs unités. Par exemple 2m3j pour «&nbsp;2 mois 3 jours&nbsp;». Ne remplissez qu'un des deux champs : âge ou date. Puis saisissez le poids en kilogrammes dans la colonne de droite. <br> 5"Utilisation","Je ne comprends pas comment saisir les données âge/poids !","Pour chaque pesée, vous avez le choix entre donner son âge, ou donner la date de la pesée. Pour la date de la pesée, il faut que vous ayiez rempli la date de naissance (sinon l'outil ne peut pas calculer l'âge !). Pour l'âge, vous pouvez indiquer en années, mois, semaines, jours, et mélanger plusieurs unités. Par exemple 2m3j pour «&nbsp;2 mois 3 jours&nbsp;». Ne remplissez qu'un des deux champs : âge ou date. Puis saisissez le poids en kilogrammes dans la colonne de droite. <br>
6Si vous manquez de lignes, vous pouvez cliquer sur ""Ajouter des lignes"" pour avoir plus de champs de données. Il n'y a pas de problème à laisser des champs vides, ils seront simplement ignorés." 6Si vous manquez de lignes, vous pouvez cliquer sur ""Ajouter des lignes"" pour avoir plus de champs de données. Il n'y a pas de problème à laisser des champs vides, ils seront simplement ignorés."
7 7
8"Utilisation","Une erreur s'affiche en bas de ma courbe, je ne comprends pas.","Si le message d'erreur n'est pas clair pour vous, c'est qu'il est probablement dû à un souci interne. Si vous le pouvez, contactez l'administratrice, en citant le message d'erreur et en donnant les données qui ont amené à cette erreur (fichier de données, ou données saisies). Merci d'avance ! :)"
9
8"Courbe","À quoi sert ce site ?","Il sert à tracer la courbe de poids des bébés et jeunes enfants en fonction de leur âge, comme sur le carnet de santé, et la compare aux courbes de références de l'Organisation Mondiale de la Santé." 10"Courbe","À quoi sert ce site ?","Il sert à tracer la courbe de poids des bébés et jeunes enfants en fonction de leur âge, comme sur le carnet de santé, et la compare aux courbes de références de l'Organisation Mondiale de la Santé."
9 11
10"Courbe","Pourquoi des courbes OMS et pas des courbes françaises ?","Les particularités de ces données sont les suivantes : bébés et enfants choisis dans différents pays du monde (et non dans un seul pays), dans des familles raisonnablement aisées (pour éviter les problèmes liés à la malnutrition), et des bébés allaités. Plus d'infos <a href=""https://www.who.int/childgrowth/standards/technical_report/en/"">ici</a> (en anglais). L'idée générale était de donner une «&nbsp;référence&nbsp;» de croissance de l'enfant de l'espèce humaine. Cela ne veut pas dire que les courbes du carnet de santé français sont mauvaises (si vous avez celles datant d'avant 2018, elles sont un peu anciennes par contre), de fait, elles sont très peu différentes. Pour les autres pays je ne sais pas, je n'ai pas comparé." 12"Courbe","Pourquoi des courbes OMS et pas des courbes françaises ?","Les particularités de ces données sont les suivantes : bébés et enfants choisis dans différents pays du monde (et non dans un seul pays), dans des familles raisonnablement aisées (pour éviter les problèmes liés à la malnutrition), et des bébés allaités. Plus d'infos <a href=""https://www.who.int/childgrowth/standards/technical_report/en/"">ici</a> (en anglais). L'idée générale était de donner une «&nbsp;référence&nbsp;» de croissance de l'enfant de l'espèce humaine. Cela ne veut pas dire que les courbes du carnet de santé français sont mauvaises (si vous avez celles datant d'avant 2018, elles sont un peu anciennes par contre), de fait, elles sont très peu différentes. Pour les autres pays je ne sais pas, je n'ai pas comparé."
@@ -16,10 +18,21 @@ Si vous manquez de lignes, vous pouvez cliquer sur ""Ajouter des lignes"" pour a
16"Courbe","Pourquoi faire des courbes de référence différenciées selon le sexe ?","Il se trouve que l'évolution de poids diffère selon ce critère. S'il s'agissait d'une simple différence de poids en moyenne, une courbe mixte serait adaptée (puisqu'on s'intéresse à l'allure de la courbe et non au poids absolu), mais les courbes sont légèrement différentes.<br> 18"Courbe","Pourquoi faire des courbes de référence différenciées selon le sexe ?","Il se trouve que l'évolution de poids diffère selon ce critère. S'il s'agissait d'une simple différence de poids en moyenne, une courbe mixte serait adaptée (puisqu'on s'intéresse à l'allure de la courbe et non au poids absolu), mais les courbes sont légèrement différentes.<br>
17Vous trouverez <a href=""static/courbe_oms_gf_1an.png"">ici</a> et <a href=""static/courbe_oms_gf_5ans.png"">là</a> deux graphiques où on voit les courbes de poids garçons et filles superposées." 19Vous trouverez <a href=""static/courbe_oms_gf_1an.png"">ici</a> et <a href=""static/courbe_oms_gf_5ans.png"">là</a> deux graphiques où on voit les courbes de poids garçons et filles superposées."
18 20
19"Courbe","Pourquoi une courbe de «&nbsp;neutre&nbsp;» ? Et c'est quoi ?","Pourquoi pas ? C'est simplement une courbe moyennée des courbes garçon/fille. Ce n'est absolument pas une référence réelle, mais ça ne coûtait rien d'essayer. Je n'ai aucune connaissance dans les données de croissance des enfants intersexes, et s'il existe pour ces enfants des courbes de référence, peut-être que celle-ci aurait du sens, peut-être pas. Elle permet accessoirement de générer une courbe «&nbsp;acceptable&nbsp;» même si on n'a pas spécifié sexe masculin/féminin. Au pire vous pouvez considérer que c'est une «&nbsp;courbe jouet&nbsp;»." 21"Courbe","Pourquoi une courbe de «&nbsp;neutre&nbsp;» ? Et c'est quoi ?","Pourquoi pas ? C'est simplement une courbe moyennée des courbes garçon/fille. Ce n'est absolument pas une vraie référence, mais ça ne coûtait rien d'essayer. Je n'ai aucune connaissance dans les données de croissance des enfants intersexes, et s'il existe pour ces enfants des courbes de référence, peut-être que celle-ci aurait du sens, peut-être pas. Elle permet accessoirement de générer une courbe «&nbsp;acceptable&nbsp;» même si on n'a pas spécifié sexe masculin/féminin. Au pire vous pouvez considérer que c'est une «&nbsp;courbe jouet&nbsp;»."
22
23"Courbe","C'est quoi la différence entre la courbe percentiles et moyenne/écarts-types ?","Ce sont deux manières de comparer son enfant à la «&nbsp;référence&nbsp;». Si vous ne savez pas la différence entre une moyenne et une médiane, retenez juste que ce sont des calculs un peu différents, mais cela ne change pas l'allure de la courbe, si votre enfant «&nbsp;suit&nbsp;» un couloir sur un type de courbe ce sera pareil sur l'autre. Vous pouvez au pire choisir celle que vous préférez. ;)
24<br>Pour les curieuses et les curieux :
25<ul>
26<li>Médiane et percentiles : si votre enfant est (par exemple) au 30e percentile, cela signifie que 29% des enfants sont plus petits que lui, et 31% sont plus grands. Cela donne une idée de où se situe l'enfant par rapport aux autres. <a href=""https://fr.wikipedia.org/wiki/Centile"">Plus d'infos (maths)</a></li>
27<li>Moyenne et écart-types : la moyenne est obtenue en faisant la somme et en divisant par le nombre d'enfants. L'écart-type (noté &sigma; ou z) est une sorte d'«&nbsp;écart moyen&nbsp;» à la moyenne. On regarde donc généralement la moyenne, la moyenne + 1&sigma;, moyenne +2&sigma;, moyenne -1 &sigma;, etc. <a href=""https://fr.wikipedia.org/wiki/%C3%89cart_type"">Plus d'infos (maths)</a></li>
28</ul>
29Si la répartition des données est «&nbsp;bonne&nbsp;» (on parle de gaussienne entre matheuses et matheux), alors la moyenne correspond à la médiane, la moyenne + 1&sigma; correspond environ au 84e percentile, la moyenne -1&sigma; correspond au 16e percentile. Pour ces données, il semble que ce soit assez proche d'une gaussienne."
20 30
21"Divers","Pourquoi cet outil ?","Tout a commencé sur le <a href=""https://forum.lllfrance.org/"">forum de La Leche League</a>, où de nombreuses mamans (et parfois quelques papas) viennent poser des questions sur l'allaitement. Souvent, se pose la question de la prise de poids du bébé, qui est un bon indicateur de si le bébé reçoit assez de lait. Le poids dans l'absolu étant peu pertinent, il est utile de «&nbsp;tracer&nbsp;» la courbe de poids. Il n'existe actuellement que peu d'outils qui permettent de le faire (l'OMS en fournit un qui ne fonctionne que sous windows), en voici un." 31"Divers","Pourquoi cet outil ?","Tout a commencé sur le <a href=""https://forum.lllfrance.org/"">forum de La Leche League</a>, où de nombreuses mamans (et parfois quelques papas) viennent poser des questions sur l'allaitement. Souvent, se pose la question de la prise de poids du bébé, qui est un bon indicateur de si le bébé reçoit assez de lait. Le poids dans l'absolu étant peu pertinent, il est utile de «&nbsp;tracer&nbsp;» la courbe de poids. Il n'existe actuellement que peu d'outils qui permettent de le faire (l'OMS en fournit un qui ne fonctionne que sous windows), en voici un."
22 32
23"Divers","Mais qui es tu donc ?","Je suis une de ces nombreuses mamans qui s'est posée un jour (et un autre jour aussi) la question de la prise de poids de son enfant. Adepte de solutions libres et multi-plateforme, je n'ai rien trouvée qui me permette de tracer cette courbe, que ce soit pour mon enfant ou celui des autres. Je suis par ailleurs férue de programmation (et je l'enseigne d'ailleurs), et donc... voilà." 33"Divers","Mais qui es tu donc ?","Je suis une de ces nombreuses mamans qui s'est posée un jour (et un autre jour aussi) la question de la prise de poids de son enfant. Adepte de solutions libres et multi-plateforme, je n'ai rien trouvée qui me permette de tracer cette courbe, que ce soit pour mon enfant ou celui des autres. Je suis par ailleurs férue de programmation (et je l'enseigne d'ailleurs), et donc... voilà."
24 34
25"Technique","Quelle est la technologie utilisée derrière ?","Il s'agit de <a href=""https://flask.palletsprojects.com/en/1.1.x/"">flask</a>, un petit framework de développement web en <a href=""https://www.python.org/"">python</a>. Il y a une petite dose de <a href=""https://developer.mozilla.org/fr/docs/Web/JavaScript"">JavaScript</a> pour assaisonner le tout, et voilà." 35"Technique","Quelle est la technologie utilisée derrière ?","Il s'agit de <a href=""https://flask.palletsprojects.com/en/1.1.x/"">flask</a>, un petit framework de développement web en <a href=""https://www.python.org/"">python</a>. Il y a une petite dose de <a href=""https://developer.mozilla.org/fr/docs/Web/JavaScript"">JavaScript</a> pour assaisonner le tout, et voilà."
36
37"Technique","J'ai trouvé un affreux bug !","N'hésitez pas à me contacter avec le plus de détails possibles à ce sujet. Par exemple quelles données, quelles manipulations vous ont amené.e à ce bug. Vous pouvez aussi préciser votre navigateur. Plus vous me fournissez d'informations, plus j'ai de chances de résoudre le bug !"
38
diff --git a/data/changelog_data.txt b/data/changelog_data.txt
index e5b81a0..f939444 100644
--- a/data/changelog_data.txt
+++ b/data/changelog_data.txt
@@ -1,3 +1,15 @@
1"Version 0.4","18/06/2020","<ul>
2<li>Mis certaines sections en masquées par défaut, on peut cliquer pour les afficher</li>
3<li>Encore des ajouts dans la FAQ</li>
4<li>Si je continue comme ça je vais arriver à une version 1.0 avant d'avoir fini !</li>
5</ul>"
6
7"Version 0.3","17/06/2020","<ul>
8<li>Le format d'export est maintenant du json (vos anciennes sauvegardes ne marchent plus, désolée !)</li>
9<li>Bouton de téléchargement des données et de la courbe, avec un nom sympa en plus («&nbsp;courbe_nomenfant.png&nbsp;» par exemple)</li>
10<li>Le texte ""explicite"" d'export est maintenant caché par défaut (on peut quand même l'avoir en cliquant dessus)</li>
11</ul>"
12
1"Version 0.2","16/06/2020","<ul> 13"Version 0.2","16/06/2020","<ul>
2<li>Mise en place du changelog (sans blague)</li> 14<li>Mise en place du changelog (sans blague)</li>
3<li>Courbe ""sexe neutre"" expérimentale</li> 15<li>Courbe ""sexe neutre"" expérimentale</li>
@@ -6,7 +18,7 @@
6<li>Mise à jour de la FAQ</li> 18<li>Mise à jour de la FAQ</li>
7</ul>" 19</ul>"
8 20
9"Version 0.1","14/06/2020","Mise en place du site web, en cours de développement. 21"Version 0.1","14/06/2020","<p>Mise en place du site web, en cours de développement.</p>
10<ul> 22<ul>
11<li>Tracé de la courbe</li> 23<li>Tracé de la courbe</li>
12<li>Export/import des données</li> 24<li>Export/import des données</li>
diff --git a/gestion_donnees.py b/gestion_donnees.py
index 71c9623..a2d0a1e 100644
--- a/gestion_donnees.py
+++ b/gestion_donnees.py
@@ -4,13 +4,15 @@
4from configuration import * 4from configuration import *
5from gestion_erreurs import * 5from gestion_erreurs import *
6import datetime 6import datetime
7import json
8import unidecode
7 9
8 10
9############ Fonctions de conversion 11############ Fonctions de conversion
10 12
11def convertit_jours_vers_python(chaine,liste_err): 13def convertit_jours_vers_python(chaine,liste_err):
12 """ convertit une chaine de type 1a 3m 1s 10j en jours 14 """ convertit une chaine de type 1a 3m 1s 10j en jours
13 Renvoie un nombre de jours en entiers. 15 Renvoie un nombre de jours en float
14 Si un des caractères n'est ni un nombre, ni une lettre "autorisée" ni une espace, 16 Si un des caractères n'est ni un nombre, ni une lettre "autorisée" ni une espace,
15 on affiche un warning et on ignore ce caractère 17 on affiche un warning et on ignore ce caractère
16 """ 18 """
@@ -41,19 +43,19 @@ def convertit_jours_vers_python(chaine,liste_err):
41 warning("convertit_jour_vers_python : caractère invalide : "+lettre,liste_err) 43 warning("convertit_jour_vers_python : caractère invalide : "+lettre,liste_err)
42 # à la fin s'il reste qqch on le garde dans les jours 44 # à la fin s'il reste qqch on le garde dans les jours
43 if chainenombre != "": 45 if chainenombre != "":
44 agejour += int(chainenombre) 46 agejours += int(chainenombre)
45 if agejours<0: 47 if agejours<0:
46 warning("L'âge est négatif !",liste_err) 48 warning("L'âge est négatif !",liste_err)
47 agejours = 0 49 agejours = 0
48 return round(agejours) 50 return agejours
49 51
50def convertit_age_vers_texte(nombre): 52def convertit_age_vers_texte(nombre):
51 """ convertit un nombre de jours en un truc plus lisible en mois, années, jours 53 """ convertit un nombre de jours en un truc plus lisible en mois, années, jours
52 et renvoie une chaîne sous la forme 3a2m1j par exemple""" 54 et renvoie une chaîne sous la forme 3a2m1j par exemple"""
53 annees = int(nombre / jours_dans_annee) 55 annees = int(nombre / jours_dans_annee)
54 restant = nombre - round(annees*jours_dans_annee) 56 restant = nombre - annees*jours_dans_annee
55 mois = int(restant/jours_dans_mois) 57 mois = int(restant/jours_dans_mois)
56 jours= nombre - round(mois*jours_dans_mois + annees*jours_dans_annee) 58 jours= round(nombre - mois*jours_dans_mois - annees*jours_dans_annee)
57 59
58 chaine = "" 60 chaine = ""
59 if annees >0: 61 if annees >0:
@@ -64,6 +66,18 @@ def convertit_age_vers_texte(nombre):
64 chaine += str(jours)+"j" 66 chaine += str(jours)+"j"
65 return chaine 67 return chaine
66########################## 68##########################
69
70
71def simplifie_nom(chaine):
72 """ simplifie le nom chaine afin d'en faire une extension
73 pour le nom du fichier. Met tout en minuscules et vire les caractères spéciaux
74 et max 15 caractères"""
75 chaine2 = ""
76 for l in chaine:
77 if l.isalpha():
78 chaine2+=l
79 chaine2 = unidecode.unidecode(chaine2)
80 return chaine2[:15]
67 81
68def convertit_poids_vers_python(chaine,liste_err): 82def convertit_poids_vers_python(chaine,liste_err):
69 """ convertit une chaine vers un float qui est le poids. 83 """ convertit une chaine vers un float qui est le poids.
@@ -82,9 +96,9 @@ def convertit_poids_vers_python(chaine,liste_err):
82 poids = 0 96 poids = 0
83 return poids 97 return poids
84 98
85def convertit_poids_vers_texte(poids): 99#def convertit_poids_vers_texte(poids):
86 """ convertit un poids vers du texte. Rien à dire là pour l'instant """ 100# """ convertit un poids vers du texte. Rien à dire là pour l'instant """
87 return str(poids) 101# return str(poids)
88 102
89######################### 103#########################
90 104
@@ -101,7 +115,7 @@ def convertit_date_vers_python(chaine,liste_err):
101 date = datetime.date(int(liste[0]),int(liste[1]),int(liste[2])) 115 date = datetime.date(int(liste[0]),int(liste[1]),int(liste[2]))
102 except: 116 except:
103 date = "" 117 date = ""
104 warning("Impossible de lire la date "+chaine) 118 warning("Impossible de lire la date "+chaine,liste_err)
105 return date 119 return date
106 120
107def convertit_date_vers_texte(date): 121def convertit_date_vers_texte(date):
@@ -127,7 +141,7 @@ def delta_date(date1,datenaissance):
127 141
128 142
129def gere_configuration(data,liste_err): 143def gere_configuration(data,liste_err):
130 """ prend en argument le dictionnaire de requête (configuratio imparfaite), et 144 """ prend en argument le dictionnaire de requête (configuration imparfaite), et
131 construit le dictionnaire de configuration qui va bien. 145 construit le dictionnaire de configuration qui va bien.
132 Vérifie que chaque entrée est cohérente évidemment.""" 146 Vérifie que chaque entrée est cohérente évidemment."""
133 configuration = {} 147 configuration = {}
@@ -176,7 +190,7 @@ def gere_configuration(data,liste_err):
176 configuration["maxi"] = convertit_jours_vers_python(maxi,liste_err) 190 configuration["maxi"] = convertit_jours_vers_python(maxi,liste_err)
177 191
178 # dimensions du graphique 192 # dimensions du graphique
179 largeur = data.get("largeur") 193 largeur = data.get("largeur","")
180 if largeur == "": 194 if largeur == "":
181 largeur = largeur_graphique 195 largeur = largeur_graphique
182 else: 196 else:
@@ -193,7 +207,7 @@ def gere_configuration(data,liste_err):
193 warning("Largeur trop petite !",liste_err) 207 warning("Largeur trop petite !",liste_err)
194 configuration["largeur"] = largeur 208 configuration["largeur"] = largeur
195 209
196 hauteur = data.get("hauteur") 210 hauteur = data.get("hauteur","")
197 if hauteur == "": 211 if hauteur == "":
198 hauteur = hauteur_graphique 212 hauteur = hauteur_graphique
199 else: 213 else:
@@ -225,21 +239,19 @@ def gere_configuration(data,liste_err):
225 positionlegende = "upper left" 239 positionlegende = "upper left"
226 configuration["positionlegende"] = positionlegende 240 configuration["positionlegende"] = positionlegende
227 241
228
229
230 return configuration 242 return configuration
231 243
232def configuration_vers_texte(config): 244#def configuration_vers_texte(config):
233 """ exporte le texte associé à une configuration 245# """ exporte le texte associé à une configuration
234 on dumpe simplement sauf pour maxi """ 246# on dumpe simplement sauf pour maxi """
235 texte = "# Section configuration\n" 247# texte = "# Section configuration\n"
236 for (cle,val) in config.items(): 248# for (cle,val) in config.items():
237 if cle != "maxi": 249# if cle != "maxi":
238 texte+= cle + "=" + str(val) + "\n" 250# texte+= cle + "=" + str(val) + "\n"
239 else: 251# else:
240 texte+= cle + "=" + convertit_age_vers_texte(val)+"\n" 252# texte+= cle + "=" + convertit_age_vers_texte(val)+"\n"
241 texte +="\n" 253# texte +="\n"
242 return texte 254# return texte
243 255
244 256
245def gere_donneespoids(data,naissance,liste_err): 257def gere_donneespoids(data,naissance,liste_err):
@@ -279,46 +291,87 @@ def gere_donneespoids(data,naissance,liste_err):
279 return (l_jours,l_poids) 291 return (l_jours,l_poids)
280 292
281 293
282def donnees_poids_vers_texte(l_jours,l_poids): 294#def donnees_poids_vers_texte(l_jours,l_poids):
283 """ retourne le texte correspondant aux données de poids """ 295# """ retourne le texte correspondant aux données de poids """
284 texte = "# Section données\n" 296# texte = "# Section données\n"
285 297#
286 for i in range(len(l_poids)): 298# for i in range(len(l_poids)):
287 texte +=convertit_age_vers_texte(l_jours[i])+","+convertit_poids_vers_texte(l_poids[i])+"\n" 299# texte +=convertit_age_vers_texte(l_jours[i])+","+convertit_poids_vers_texte(l_poids[i])+"\n"
300#
301# texte+="\n"
302# return texte
303
304
305
306
307
308def donnees_vers_json(l_jours,l_poids,config):
309 """ retourne le json à renvoyer"""
310 gros_dico = config.copy()
311 l_jours2 = [convertit_age_vers_texte(d) for d in l_jours]
312 gros_dico["data_j"] = l_jours2
313 gros_dico["data_p"] = l_poids
314 # gérer la date de naissance
315 if gros_dico.get("naissance","") != "":
316 gros_dico["naissance"] = convertit_date_vers_texte(gros_dico["naissance"])
317 # gérer l'age maxi
318 gros_dico["maxi"] = convertit_age_vers_texte(gros_dico["maxi"])
288 319
289 texte+="\n" 320 return json.dumps(gros_dico, indent=2,ensure_ascii=False )
290 return texte
291 321
322#def fichier_texte_vers_configdonnees(fichier,liste_err):
323# """ prend le texte importé et l'exporte vers configuration et données
324# sous forme de valeurs du formulaire """
325#
326# valform = {}
327# indice_formulaire = 0 # l'indice du formulaire pour les données : age_1, date_1, poids_1 etc
328# num_ligne = 0
329# lignes = fichier.readlines()
330# for ligne in lignes:
331# num_ligne +=1
332# ligne = str(ligne,"utf8")
333# ligne = ligne.rstrip("\n")
334# if ligne != "" and ligne[0] != "#" and not(ligne.isspace()): # les lignes commençant par # sont des commentaires
335# # On essaie de partitionner pour voir
336# (var,egal,val) = ligne.partition("=")
337# if egal == "=": # c'est une ligne de config
338# valform[var] = val
339# else:
340# (age,virgule,poids) = ligne.partition(",") # On partitionne avec ,
341# if virgule == ",":
342# # c'est une ligne de data
343# valform["age_"+str(indice_formulaire)] = age
344# valform["poids_"+str(indice_formulaire)] = poids
345# indice_formulaire +=1
346#
347# else:
348# warning("La ligne "+str(num_ligne)+" n'est pas reconnue et sera ignorée : <"+ligne+">",liste_err)
349#
350# #le nb max du formulaire
351# valform["nb_data"] = max(indice_formulaire +2,nombre_lignes_form)
352#
353# return valform
292 354
293def fichier_texte_vers_configdonnees(fichier,liste_err): 355def fichier_json_vers_configdonnees(fichier,liste_err):
294 """ prend le texte importé et l'exporte vers configuration et données 356 """ prend le json importé et l'exporte vers les valeurs du formulaire """
295 sous forme de valeurs du formulaire """ 357 chaine = fichier.read()
358 valform = json.loads(chaine)
359 # Il faut maintenant récupérer les l_jours et l_poids puis les remettre
360 # sous forme de age_i et poids_i
361 l_jours= valform.get("data_j",[])
362 l_poids=valform.get("data_p",[])
363 if len(l_poids) != len(l_jours):
364 warning("Lecture du json : les données sont incohérentes (listes de taille différentes et/ou pb de lecture")
365 long = min(len(l_jours),len(l_poids))
366 else:
367 long = len(l_jours)
368 for i in range(long):
369 valform["age_"+str(i)] = l_jours[i]
370 valform["poids_"+str(i)] = l_poids[i]
371
372 valform["nb_data"] = max(long +2,nombre_lignes_form)
296 373
297 valform = {} 374 return valform
298 indice_formulaire = 0 # l'indice du formulaire pour les données : age_1, date_1, poids_1 etc
299 num_ligne = 0
300 lignes = fichier.readlines()
301 for ligne in lignes:
302 num_ligne +=1
303 ligne = str(ligne,"utf8")
304 ligne = ligne.rstrip("\n")
305 if ligne != "" and ligne[0] != "#" and not(ligne.isspace()): # les lignes commençant par # sont des commentaires
306 # On essaie de partitionner pour voir
307 (var,egal,val) = ligne.partition("=")
308 if egal == "=": # c'est une ligne de config
309 valform[var] = val
310 else:
311 (age,virgule,poids) = ligne.partition(",") # On partitionne avec ,
312 if virgule == ",":
313 # c'est une ligne de data
314 valform["age_"+str(indice_formulaire)] = age
315 valform["poids_"+str(indice_formulaire)] = poids
316 indice_formulaire +=1
317
318 else:
319 warning("La ligne "+str(num_ligne)+" n'est pas reconnue et sera ignorée : <"+ligne+">",liste_err)
320 375
321 #le nb max du formulaire
322 valform["nb_data"] = indice_formulaire +2
323 376
324 return valform 377 \ No newline at end of file
diff --git a/static/outilspage.js b/static/outilspage.js
index de87963..8da9e88 100644
--- a/static/outilspage.js
+++ b/static/outilspage.js
@@ -23,3 +23,29 @@ function ajoutelignes()
23 } 23 }
24 24
25} 25}
26
27// Affichage de la textarea "export"
28function affiche_export()
29{
30 document.getElementById("export").style.display = "block" ;
31
32}
33
34function affiche_cache(id,elemcourant)
35{
36 // affiche et/ou cache l'élément id, tout en changeant le this
37 // en afficher/masquer
38 elem = document.getElementById(id)
39 if(elem.style.display == "block")
40 {
41 elem.style.display = "none";
42 elemcourant.innerHTML = "Afficher" ;
43 }
44 else
45 {
46 elem.style.display = "block" ;
47 elemcourant.innerHTML = "Masquer" ;
48
49 }
50
51}
diff --git a/static/requetes.js b/static/requetes.js
index 072e880..b7e719a 100644
--- a/static/requetes.js
+++ b/static/requetes.js
@@ -9,21 +9,34 @@ function appelle_image()
9 requete.onreadystatechange = function() 9 requete.onreadystatechange = function()
10 { 10 {
11 if (this.readyState == 4 && this.status == 200) { 11 if (this.readyState == 4 && this.status == 200) {
12 // On nettoie
13 nettoie_erreurs()
14
12 // on récupère les différents champs de la réponse 15 // on récupère les différents champs de la réponse
13 var result = this.response.result ; 16 var result = this.response.result ;
14 var image = this.response.image ; 17 var image = this.response.image ;
15 var liste_warnings = this.response.messages 18 var liste_warnings = this.response.messages
16 var texte = this.response.export_txt; 19 var texte = this.response.export_txt;
20 var nomenfant = this.response.nomenfant ;
21
17 // on affiche l'export des données 22 // on affiche l'export des données
18 document.getElementById('export').innerHTML = texte; 23 document.getElementById('export').innerHTML = texte;
19 document.getElementById('sectionexport').style.display = "block"; 24 document.getElementById('sectionexport').style.display = "block";
25
26
27 var boutondl = document.getElementById("export_dl") ;
28 boutondl.setAttribute('onclick',"download_file('donnees_"+nomenfant+".json', 'application/json;charset=utf-8','"+encodeURIComponent(texte) +"')")
29
20 30
21 if(result == "success") 31 if(result == "success")
22 { 32 {
23 // On affiche l'image 33 // On affiche l'image
24 document.getElementById('courbe').src = 'data:image/png;base64,'+(image); 34 document.getElementById('courbe').src = 'data:image/png;base64,'+(image);
25 document.getElementById('sectioncourbe').style.display = "block"; 35 document.getElementById('sectioncourbe').style.display = "block";
26 36
37 //document.getElementById("courbe_dl").setAttribute('href', 'data:image/png;base64,' + image);
38 boutondl = document.getElementById("courbe_dl") ;
39 boutondl.setAttribute('onclick',"download_file('courbe_"+nomenfant+".png', 'image/png;base64','"+image +"')")
27 40
28 // Si y'a eu des warnings, faut les afficher 41 // Si y'a eu des warnings, faut les afficher
29 if(liste_warnings.length != 0) 42 if(liste_warnings.length != 0)
@@ -41,7 +54,7 @@ function appelle_image()
41 } 54 }
42 else{ // si la génération de l'image a merdé 55 else{ // si la génération de l'image a merdé
43 56
44 // afficher la liste des warnings 57 // afficher la liste des erreurs
45 var elem_div = document.getElementById('courbe_erreurs') ; 58 var elem_div = document.getElementById('courbe_erreurs') ;
46 elem_div.style.display = "block" ; 59 elem_div.style.display = "block" ;
47 var ul = elem_div.children[1] ; 60 var ul = elem_div.children[1] ;
@@ -58,3 +71,35 @@ function appelle_image()
58 requete.open("POST","courbe/b64",true) 71 requete.open("POST","courbe/b64",true)
59 requete.send(formData) 72 requete.send(formData)
60} 73}
74
75function nettoie_erreurs()
76{
77 // fonction qui nettoie les erreurs affichées sur la page
78 // vider les warnings
79 var elem_div = document.getElementById('courbe_warnings') ;
80 elem_div.style.display = "none" ;
81 var ul = elem_div.children[1] ;
82 ul.innerHTML = "";
83
84 // vider les erreurs
85 elem_div = document.getElementById('courbe_erreurs') ;
86 elem_div.style.display = "none" ;
87 ul = elem_div.children[1] ;
88 ul.innerHTML = "" ;
89
90}
91
92
93function download_file(filename,mimetype,data) {
94 var element = document.createElement('a');
95 element.setAttribute('href', 'data:'+mimetype+',' + data);
96 element.setAttribute('download', filename);
97
98 element.style.display = 'none';
99 document.body.appendChild(element);
100
101 element.click();
102
103 document.body.removeChild(element);
104
105}
diff --git a/static/style.css b/static/style.css
index 3113832..58fdd24 100644
--- a/static/style.css
+++ b/static/style.css
@@ -13,6 +13,7 @@ body {
13#export { 13#export {
14 width: 25em; 14 width: 25em;
15 height: 20em; 15 height: 20em;
16 display:none;
16} 17}
17 18
18#courbe_warnings { 19#courbe_warnings {
@@ -22,3 +23,15 @@ body {
22#courbe_erreurs { 23#courbe_erreurs {
23 display: none; 24 display: none;
24} 25}
26
27#import_donnees, #pref_graphique{
28 display: none;
29}
30
31.bouton {
32 text-decoration: underline;
33}
34
35.bouton:hover {
36 cursor:pointer;
37}
diff --git a/templates/base.html b/templates/base.html
index 9fa5263..694266c 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -33,7 +33,7 @@
33 33
34 <nav><a href="/">Accueil et saisie des données</a> | 34 <nav><a href="/">Accueil et saisie des données</a> |
35 <a href="/apropos">À propos</a> | 35 <a href="/apropos">À propos</a> |
36 <a href="/faq">Foire Aux Questions</a> | 36 <a href="/faq">FAQ</a> |
37 <a href="/changelog">Changelog</a> 37 <a href="/changelog">Changelog</a>
38 </nav> 38 </nav>
39 39
diff --git a/templates/changelog.html b/templates/changelog.html
index 24447f6..7ba96cd 100644
--- a/templates/changelog.html
+++ b/templates/changelog.html
@@ -7,7 +7,8 @@
7<h3>{{ ligne[0] }}</h3> 7<h3>{{ ligne[0] }}</h3>
8<div class="date">Le {{ ligne[1] }}</div> 8<div class="date">Le {{ ligne[1] }}</div>
9 9
10<p>{{ ligne[2]|safe }}</p> 10<div class="contenu_changelog">
11{{ ligne[2]|safe }}
11</div> 12</div>
12{% endfor %} 13{% endfor %}
13 14
diff --git a/templates/faq.html b/templates/faq.html
index 301f5d7..162f68d 100644
--- a/templates/faq.html
+++ b/templates/faq.html
@@ -1,6 +1,6 @@
1{% extends "base.html" %} 1{% extends "base.html" %}
2{% block contenu %} 2{% block contenu %}
3<h2>Foire Aux Questions</h2> 3<h2>Foire Aux Questions (FAQ)</h2>
4 4
5<div id="sommaire"> 5<div id="sommaire">
6<ul>{% for cat in lcateg %} 6<ul>{% for cat in lcateg %}
diff --git a/templates/index.html b/templates/index.html
index 23a9d36..60693d4 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -4,57 +4,42 @@
4<hr> 4<hr>
5 5
6 6
7<h2>Données du graphique</h2> 7<h2>Données de l'enfant</h2>
8 8
9<h3>Importer un fichier </h3>
10<div class="bouton" onclick="affiche_cache('import_donnees',this)">Afficher</div>
11<div id="import_donnees">
9 <form action="/" method="post" enctype="multipart/form-data"> 12 <form action="/" method="post" enctype="multipart/form-data">
13
14
15
10 <label for="fichier_donnees">Importer le fichier de données</label> 16 <label for="fichier_donnees">Importer le fichier de données</label>
11 <input type="file" name="fichier_donnees"> 17 <input type="file" name="fichier_donnees">
12 <input type="submit" name="valider_fichier" value="Charger les données"> 18 <input type="submit" name="valider_fichier" value="Charger les données">
13</form> 19</form>
14 20</div>
21<hr>
15 22
16<form id="donnees_enfant"> 23<form id="donnees_enfant">
17 24
18 25<div>
26<input type="reset" value="Effacer les données du formulaire">
27</div>
19 28
20<h3>Informations sur l'enfant</h3> 29<h3>Informations sur l'enfant</h3>
21<ul> 30<ul>
22<li><label>Nom de l'enfant :</label> <input type="text" name="nom" value="{{ valform.nom }}"></li> 31<li><label>Nom de l'enfant&nbsp;:</label> <input type="text" name="nom" value="{{ valform.nom }}"></li>
23<li><label>Sexe :</label> <input type="radio" name="sexe" value="F" {%if valform.sexe == "F" %}checked{% endif %}> féminin 32<li><label>Sexe&nbsp;:</label> <input type="radio" name="sexe" value="F" {%if valform.sexe == "F" %}checked{% endif %}> féminin
24| <input type="radio" name="sexe" value="M" {%if valform.sexe == "M"%} checked {% endif %}> masculin | <input type="radio" name="sexe" value="N" {%if valform.sexe == "N"%} checked {% endif %}> neutre (expérimental)</li> 33| <input type="radio" name="sexe" value="M" {%if valform.sexe == "M"%} checked {% endif %}> masculin | <input type="radio" name="sexe" value="N" {%if valform.sexe == "N"%} checked {% endif %}> neutre (expérimental)</li>
25<li><label>Date de naissance : </label> <input type="date" name="naissance" value="{{ valform.naissance }}"></li> 34<li><label>Date de naissance&nbsp;: </label> <input type="date" name="naissance" value="{{ valform.naissance }}"></li>
26</ul> 35</ul>
27 36
28 37
29<h3>Préférences du graphique</h3>
30<ul>
31 <li><label>Type de courbe :</label> <input type="radio" name="typecourbe" value="P"
32 {% if valform.typecourbe == "P" or valform.typecourbe is not defined %} checked {% endif %}> Percentiles | <input type="radio" name="typecourbe" value="Z"
33 {% if valform.typecourbe == "Z" %} checked {% endif %}> Moyenne et écarts-type</li>
34 <li><label>Grille : </label><input type="checkbox" name="grille" {%if valform.grille == "oui" or valform.grille is not defined %} checked {% endif %}></li>
35 <li><label>Unité : </label>
36 <select name="unite">
37 <option value="" {% if valform.typecourbe == "" or valform is not defined %} selected {%endif %}>Par défaut</option>
38 {% for unite in ['jours','semaines','mois','années'] %}
39 <option value="{{ unite }}"{% if valform.unite == unite %} selected {% endif %} >{{ unite }}</option>
40 {% endfor %}
41 </select></li>
42 <li><label>valeur maximum du graphique (facultatif, syntaxe similaire à l'âge, voir plus bas) </label><input type="text" name="maxi" value="{{ valform.maxi }}"></li>
43 <li>Dimensions du graphique : <label>largeur : </label><input type="text" name="largeur" value="{{ valform.largeur }}">
44 <label>hauteur : </label><input type="text" name="hauteur" value="{{ valform.hauteur }}"></li>
45 <li><label>Légende : </label><input type="checkbox" name="legende" {% if valform.legende == 'oui' %} checked{% endif %}>
46Position : <select name="positionlegende">
47{%for (pos,posnom) in [('upper left','Haut gauche'),('upper right','Haut Droite'),('lower left','Bas gauche'),('lower right','Bas droite')] %}
48<option value="{{ pos }}">{{ posnom }}</option>
49{% endfor %}
50</select>
51 </li>
52</ul>
53 38
54 39
55 40
56<h3>Données de poids</h3> 41<h3>Données de poids</h3>
57<p>Syntaxe pour l'âge : utiliser j, s, m, a comme des "unités" (jours, semaines, mois, années). Vous pouvez mixer les unités, par exemple "3a2m5j" pour 3 ans, 2 mois et 5 jours. Les espaces sont ignorées, et il faut saisir des nombres entiers.</p> 42<p>Syntaxe pour l'âge&nbsp;: utiliser j, s, m, a comme des "unités" (jours, semaines, mois, années). Vous pouvez mixer les unités, par exemple "3a2m5j" pour 3 ans, 2 mois et 5 jours. Les espaces sont ignorées, et il faut saisir des nombres entiers.</p>
58<p>Saisir le poids en kilogrammes (par exemple "2.62" ou "2,62" pour 2 kilogrammes et 620 grammes).</p> 43<p>Saisir le poids en kilogrammes (par exemple "2.62" ou "2,62" pour 2 kilogrammes et 620 grammes).</p>
59<p>Il faut saisir la date ou l'âge. Si les deux sont saisis, seul l'âge comptera.</p> 44<p>Il faut saisir la date ou l'âge. Si les deux sont saisis, seul l'âge comptera.</p>
60 45
@@ -72,35 +57,70 @@ Position : <select name="positionlegende">
72{% endfor %} 57{% endfor %}
73 58
74</table> 59</table>
60<p class="bouton" onclick="ajoutelignes()">Cliquer ici pour ajouter des lignes</p>
61
62
63
64<h3>Préférences du graphique</h3>
65<div class="bouton" onclick="affiche_cache('pref_graphique',this)">Afficher</div>
66
67<ul id="pref_graphique">
68 <li><label>Type de courbe&nbsp;:</label> <input type="radio" name="typecourbe" value="P"
69 {% if valform.typecourbe == "P" or valform.typecourbe is not defined %} checked {% endif %}> Percentiles | <input type="radio" name="typecourbe" value="Z"
70 {% if valform.typecourbe == "Z" %} checked {% endif %}> Moyenne et écarts-type</li>
71 <li><label>Grille&nbsp;: </label><input type="checkbox" name="grille" {%if valform.grille == "oui" or valform.grille is not defined %} checked {% endif %}></li>
72 <li><label>Unité&nbsp;: </label>
73 <select name="unite">
74 <option value="" {% if valform.typecourbe == "" or valform is not defined %} selected {%endif %}>Par défaut</option>
75 {% for unite in ['jours','semaines','mois','années'] %}
76 <option value="{{ unite }}"{% if valform.unite == unite %} selected {% endif %} >{{ unite }}</option>
77 {% endfor %}
78 </select></li>
79 <li><label>valeur maximum du graphique (facultatif, syntaxe similaire à l'âge, voir plus bas) </label><input type="text" name="maxi" value="{{ valform.maxi }}"></li>
80 <li>Dimensions du graphique&nbsp;: <label>largeur&nbsp;: </label><input type="text" name="largeur" value="{{ valform.largeur }}">
81 <label>hauteur&nbsp;: </label><input type="text" name="hauteur" value="{{ valform.hauteur }}"></li>
82 <li><label>Légende&nbsp;: </label><input type="checkbox" name="legende" {% if valform.legende == 'oui' %} checked{% endif %}>
83Position&nbsp;: <select name="positionlegende">
84{%for (pos,posnom) in [('upper left','Haut gauche'),('upper right','Haut Droite'),('lower left','Bas gauche'),('lower right','Bas droite')] %}
85<option value="{{ pos }}">{{ posnom }}</option>
86{% endfor %}
87</select>
88 </li>
89</ul>
90
91
75</form> 92</form>
76<button onclick="ajoutelignes()">Ajouter des lignes</button>
77 93
78<hr> 94<hr>
79 95
80<button onclick="appelle_image()">Je veux la courbe !</button> 96<button onclick="appelle_image()">Je veux la courbe !</button>
81 97
82
83<div id="sectioncourbe">
84<hr> 98<hr>
85<h2>Courbe</h2> 99<div id="sectioncourbe">
86<img id="courbe"> 100
101 <h2>Courbe</h2>
102 <img id="courbe">
103 <div>
104 <button id="courbe_dl">Télécharger la courbe</button>
105 </div>
87</div> 106</div>
88<div id="courbe_warnings"> 107<div id="courbe_warnings">
89<p><strong>Alerte :</strong> la courbe a eu quelques soucis à se générer. Voici la liste des erreurs.</p> 108 <p><strong>Alerte&nbsp;:</strong> la courbe a eu quelques soucis à se générer. Voici la liste des erreurs.</p>
90<ul></ul> 109 <ul></ul>
91</div> 110</div>
92<div id="courbe_erreurs"> 111<div id="courbe_erreurs">
93<p><strong>Alerte :</strong> La courbe n'a pas pu être générée. Vérifiez les données saisies, ou contactez l'administratrice. Erreurs : </p> 112 <p><strong>Alerte&nbsp;:</strong> La courbe n'a pas pu être générée. Vérifiez les données saisies, ou contactez l'administratrice. Erreurs&nbsp;: </p>
94<ul></ul> 113 <ul></ul>
95</div> 114</div>
96 115
97<div id="sectionexport"> 116<div id="sectionexport">
98<h2>Export des données</h2> 117 <h2>Export des données</h2>
99<p>Vous trouverez ci-dessous les données exportées au format texte. Il peut être utile de les copier/coller 118 <p>Vous pouvez télécharger les données afin de ne pas avoir à les re-saisir la prochaine fois.</p>
100quelque part et de les sauvegarder, comme ça la prochaine fois vous n'aurez pas à ressaisir tout à la main.</p>
101 119
102<textarea readonly id="export"> 120 <div><button id="export_dl">Télécharger les données</button></div>
103</textarea> 121 <p>Si vous n'arrivez pas à télécharger les données, <a href='#export' onclick="affiche_export()">cliquez ici</a> pour les voir en texte clair&nbsp;: il vous suffira de les copier/coller dans un fichier texte.</p>
122 <textarea readonly id="export">
123 </textarea>
104 124
105</div> 125</div>
106 126