|
|
Contributeurs : C. Poulard, équipe Hydrologie & S. Coulibaly, équipe Hydraulique, autour de tests pour le prototype de Pamhyr version Python (interface de pré et post-traitement pour des codes d'hydraulique).
|
|
|
|
|
|
**But de l'atelier** : manipuler les notions d' **événement** et de **fonction associée** pour rendre un graphique interactif.
|
|
|
|
|
|
**Autres notions manipulées au passage** :
|
|
|
|
|
|
* Python : **comprehension list** : création d'une nouvelle liste par une syntaxe plus performante qu'une boucle for classique (exemple de syntaxe : \[x\*x for x in liste_x\] va créer une liste des carrés des éléments de liste_x) ; **zip** pour parcourir deux ou plusieurs conteneurs (liste, array...) simultanément, càd en prenant à l'itération i l'élément i de chaque conteneur, comme une fermeture éclair parcourt les dents situées de part et d'autre de la fermeture.
|
|
|
* spécifique mpl : modifier certaines caractéristiques d'une courbe existante avec **set_data()** ou **set_xdata() et set_ydata()** ; optionnel : annoter une figure avec **annotate** et le widget **"Textbox"**.
|
|
|
- Python : **comprehension list** : création d'une nouvelle liste par une syntaxe plus performante qu'une boucle for classique (exemple de syntaxe : \[x\*x for x in liste_x\] va créer une liste des carrés des éléments de liste_x) ; **zip** pour parcourir deux ou plusieurs conteneurs (liste, array...) simultanément, càd en prenant à l'itération i l'élément i de chaque conteneur, comme une fermeture éclair parcourt les dents situées de part et d'autre de la fermeture.
|
|
|
- spécifique mpl : modifier certaines caractéristiques d'une courbe existante avec **set_data()** ou **set_xdata() et set_ydata()** ; optionnel : annoter une figure avec **annotate** et le widget **"Textbox"**.
|
|
|
- spécifique mpl : passer un seul argument à plt.plot(un_vecteur): il utilise le vecteur comme vecteur des y et prend pour abscisse le rang.
|
|
|
|
|
|
**Application** : sélectionner sur une courbe le point le plus proche d'un clic de souris
|
|
|
|
... | ... | @@ -71,6 +74,8 @@ fig.canvas.mpl_connect('button_press_event', onclick_ajouter) |
|
|
plt.show()
|
|
|
```
|
|
|
|
|
|
Pour cette action, on peut aussi utiliser la méthode **plt.ginput()** qui permet de définir comme attribut un nombre de points attendus et un délai d'attente max.
|
|
|
|
|
|
### le "clic" sélectionne le point le plus proche
|
|
|
|
|
|
Cette fois, on va calculer la distance du point à chacun des points de la courbe, et on va retenir l'indice de la distance minimale, qui nous servira à aller chercher les bonnes coordonnées dans x et y. Attention, il s'agit ici de la distance "en unités de la courbe", on verra dans l'exemple du picker qu'il vaut mieux travailler en distances "écran", en pixels, pour que le point sélectionné corresponde à l'impression visuelle. Il restera à déplacer la courbe "selection",réduite à un point matérialisé par une grande étoile rouge, aux coordonnées de ce point.
|
... | ... | @@ -240,11 +245,15 @@ De même que précédemment, on va d'abord aller au plus simple, et comprendre l |
|
|
![sélection picker](uploads/510c61180d0c1d51a059871fed23ab1c/selection_picker.png)
|
|
|
_comparaison des points sélectionnés avec chacune des 2 distances par rapport au clic de souris, matérialisé ici par une croix bleue_
|
|
|
|
|
|
Si on a plusieurs courbes, on peut ne lier le Picker qu'à certaines d'entre elles. Si plusieurs sont réceptives au Picker, la fonction liée sera déclenchée une fois pour chacune, ce qui empêche de calculer facilement "le" point le plus proche. Si vous ajoutez simplement une ou plusieurs courbes, l'action va être exécutée une fois par courbe "réceptive" ; pour notre objectif, cela ne permet pas de déterminer "LE" point le plus proche, mais un point par courbe. En plus, comme dans ce code on modifie une annotation vous ne verrez que le résultat pour la dernière courbe... Pour voir tous les résultats il faut une annotation par courbe, on ne donne pas notre code ici car c'est inutilement compliqué.
|
|
|
Notre conclusion est que cette fonction semble intéressante quand il y a une seule courbe ; sinon le plus simple est d'utiliser l'événement MouseEvent, comme ci-dessus. Picker est sans doute adaptée à d'autres usages, comme identifier les courbes qui ont un point proche du clic de souris par exemple.
|
|
|
Description des **attributs du Picker**, d'après la doc :
|
|
|
- mouseevent : avec lui-même ses propres attributs (x, y, x_data, y_data...)
|
|
|
- l'objet sur lequel l'événement a été vu (: avec lui-même ses propres attributs (x, y, x_data, y_data...), soit en langage mpl un "Artist"
|
|
|
- attributs spécifiques à certains "Artists" ; pour Line2D (généré par un plot) et PatchCollection (généré par vlines par exemple) : liste des points de l'Artist situés dans le rayon défini.
|
|
|
|
|
|
Dans un premier temps, on va raisonner avec un seul Artist réceptif, ici une Line2D ; on évoquera ensuite le cas où plusieurs Artists vont répondre au même clic de souris.
|
|
|
|
|
|
### 1. il faut rendre l'objet attentif au clic de souris...
|
|
|
|
|
|
### 1. il faut rendre l'objet, ici une figure, attentif au clic de souris...
|
|
|
|
|
|
```python
|
|
|
fig.canvas.mpl_connect('pick_event', onpick_cp)
|
... | ... | @@ -256,7 +265,7 @@ Toute la figure va maintenant être "attentive" à un éventuel clic de souris, |
|
|
|
|
|
##### But : un clic de souris compare sa position avec les points d'un objet matplotlib ; le plus proche est sélectionné et des informations s'affichent.
|
|
|
|
|
|
##### Principe : cette fonction sera appelée pour chaque "artist" pour lequel le picker a été activé, ici toute la figure, qui ne contient qu'une courbe (de type "Line2D").
|
|
|
##### rappel: cette fonction sera appelée pour chaque "artist" pour lequel le picker a été activé, ici toute la figure, qui ne contient qu'une courbe (de type "Line2D").
|
|
|
|
|
|
L'événement déclencheur a des attributs :
|
|
|
|
... | ... | @@ -328,25 +337,21 @@ textbox_radius.on_submit(submit_radius) |
|
|
|
|
|
On remarque que :
|
|
|
|
|
|
* les points sélectionnés apparaissent former un cercle : ils sont donc sélectionnés dans un cercle "en coordonnées écran". Les distances en x et en y étant distordues, si le rayon était défini en "coordonnées des données" on verrait une ellipse ;
|
|
|
* les points sélectionnés apparaissent former un cercle : ils sont donc sélectionnés dans un cercle "en coordonnées écran" (en "points", dit la doc). Les distances en x et en y étant distordues, si le rayon était défini en "coordonnées des données" on verrait une ellipse ;
|
|
|
* pour une valeur donnée, le rayon "apparent" est le même quel que soit le zoom ou la taille de la vignette. Le rayon correspond donc à une distance "écran", qui dépend uniquement des dimensions de la vignette. Dit autrement, avec les mêmes données de départ et pour un même rayon, on captera moins de points en zoomant ou en agrandissant la taille de la vignette, les deux actions ayant pour effet de diminuer la densité des points tracés.
|
|
|
|
|
|
## Exemple 3: comprendre les arguments pickradius des axes
|
|
|
## Cas des plusieurs Artists réceptifs :
|
|
|
|
|
|
Si on a plusieurs courbes, on peut ne lier le Picker qu'à certaines d'entre elles. Si plusieurs sont réceptives au Picker, la fonction liée sera déclenchée une fois pour chacune, ce qui empêche de calculer facilement "le" point le plus proche. Si vous ajoutez simplement une ou plusieurs courbes, l'action va être exécutée une fois par courbe "réceptive" ; pour notre objectif, cela ne permet pas de déterminer "LE" point le plus proche, mais un point par courbe. En plus, comme dans ce code on modifie une annotation vous ne verrez que le résultat pour la dernière courbe... Pour voir tous les résultats il faut une annotation par courbe, on ne donne pas notre code ici car c'est inutilement compliqué.
|
|
|
Notre conclusion est que cette fonction semble intéressante quand il y a une seule courbe ; sinon le plus simple est d'utiliser l'événement MouseEvent, comme ci-dessus. Picker est sans doute adaptée à d'autres usages, comme identifier les courbes qui ont un point proche du clic de souris par exemple.
|
|
|
|
|
|
## Reste à faire : comprendre les arguments pickradius des axes
|
|
|
|
|
|
La doc présente également les méthodes get_pickradius et set_pickradius sur les Axes, mais sans exemple difficile de comprendre... Me faire si vous avez compris
|
|
|
|
|
|
Axis.get_pickradius()[source]¶
|
|
|
|
|
|
Return the depth of the axis used by the picker.
|
|
|
|
|
|
|
|
|
```python
|
|
|
def submit_radius(val):
|
|
|
"""
|
|
|
si possible, la valeur écrite dans le textbox est convertie en entier
|
|
|
on affecte cette valeur comme nouveau rayon avec set_picker
|
|
|
"""
|
|
|
try:
|
|
|
radius = int(val)
|
|
|
nuage.set_picker(abs(radius))
|
|
|
textbox_radius.text_disp.set_color("blue")
|
|
|
except:
|
|
|
textbox_radius.text_disp.set_color("orange")
|
|
|
textbox_radius.set_val(nuage.get_picker())
|
|
|
|
|
|
nuage, = ax.plot(x, y, '*', c='blue', label="nuage", picker=init_radius, ls='None')
|
|
|
textbox_radius.on_submit(submit_radius)
|
|
|
``` |