vis.js - Network
Le thème propose l'intégration de vis-network
, via un module python vis_network
à importer.
Graphe interactif tracé avec vis.Network
Principe général#
L'idée générale pour que cela fonctionne est la suivante :
- Insérer dans le fichier markdown un élément html (une
<div>
) qui accueillera la figure une fois qu'elle sera créée. - Importer le module du thème,
vis_network
, depuis pyodide. - Construire le graphe avec l'objet
vis_network.Network
.
- L'étape 1 est facilitée par l'utilisation de la macro
{{ figure(...) }}
. - L'étape 2 est faite par l'utilisateur via un fichier python associé à une macro
IDE
,terminal
,py_btn
ourun
. Lors de cette étape, le thème charge automatiquement le CDNvis-network
. - L'étape 3 est facilitée par l'utilisation d'objets python de la classe
Network
qui permettent de faire le lien entre le code python et les fonctionnalités JavaScript, tout en gérant au passage le cycle de vie des objets et des évènements rajoutés au graphe.
Il est vivement recommandé de lire la documentation vis-network, la présente page expliquant principalement comment articuler le thème/pyodide avec la bibliothèque JavaScript, sans détailler toutes les fonctionnalités de l'outil.
Exemple simple#
Le graphe ci-dessus est construit avec le code suivant :
Le module vis_network
#
La classe python Network
#
Elle permet de construire le graphe en convertissant automatiquement les structures de données python en objets compatibles avec l'objet vis.Network
JavaScript. L'interface diffère légèrement de la classe d'origine en JavaScript, notamment en ce qui concerne le constructeur et les méthodes gérant les évènements ajoutés par l'utilisateur.
Les appels aux méthodes qui ne sont pas définies sur la classe python sont automatiquement redirigés vers les méthodes de l'objet JS d'origine (un objet JsProxy
, accessible si besoin via la propriété network
de l'objet python).
Lorsqu'une méthode est définie sur les objets python Network
, la méthode originale ne devrait jamais être utilisée directement : ces méthodes permettent de gérer pour l'utilisateur les conversions de fonctions et les cycles de vie des différents proxies nécessaires pour un usage de js.vis.Network
depuis Pyodide.
Conversion de structures de données python vers JS
Pour assurer le bon fonctionnement de l'objet JS vis.Network
, les structures de données passées en arguments depuis python/pyodide doivent être converties de manière appropriée.
Le module python vis_network
propose une fonction dédiée, to_js(obj)
, qui réalise la conversion des objets structurés simples (dict
, list
, ...) de manière appropriée. La conversion est recursive.
- La fonction
vis_network.to_js
est équivalente àpyodide.ffi.to_js
, mais utilise par défaut l'argumentdict_converter=js.Object.fromEntries
. - Cette fonction ne gère pas la conversion en objets
js.vis.DataSet
: cela reste à la charge de l'utilisateur.
Voir l'utilisation directe des objets JS pour un exemple.
class NetWork:
"""
PMT class wrapper around a JS vis.Network object.
Accessed with `vis_network.Network`.
If you need to work directly with the original JS `vis.Network` class, you can get access
its JsProxy through `vis_network.Network.JS_NETWORK`. Note that you'll have to convert
all the data structures appropriately on your own (see module level help).
Use self.network to access the original JS instance (as a pyodide.ffi.JsProxy).
For contracts consistency, methods that are defined on the python object must be used,
instead of calling directly the version on the JsProxy, self.network.
"""
JS_NETWORK: JsProxy = js.vis.Network # The original JS class
network: JsProxy # JsProxy of the original JS vis.Network instance
figure_id: str # Html id of the container holding the graph.
def __init__(self, figure_id:str=None, data:dict=None, options:dict=None):
"""
Create a Network object, which is a wrapper around the original JS vis.Network object.
@@figure_id: Id of the figure/div in which the Network must be drawn. If `None`,
the current default value of the `figure` macro is used.
@data: A python dict of nodes and edges (both as lists of dicts). The python
instance will handle the conversions to the proper JS data structures
for the user.
@options: A python dict, with any of the JS available options. Also converted
automatically to a proper JS object.
"""
def on(self, event_name:str, cbk:Callable) -> int:
"""
Create an event handler from a python function for the NetWork, and return the
id number representing the function handler, so that it is possible to remove
that specific listener through `self.off(event_name, listener_id)`.
WARNING: the interface differs from the original vis.Network.on(...) method: if
you plan on removing manually the event listener later, you'll have to store the
int value returned by this vis_network.Network.on(...) call so that the Pyodide
proxy callback can be properly handled.
"""
def off(self, event_name:str=None, listener_id:int=None):
"""
Remove event listeners for the given event, with the given function id.
Note this method applies only to event handlers that have been created through the
python `vis_network.Network.on(...)` method.
@event_name: If None (then, @listener_id must also be None), remove all the listeners
of the Network.
@listener_id: Id of the listener to remove (as provided as output of `self.on(...)`).
If `None`, remove all listeners related to the @event_name.
@throws: ValueError if arguments are not valid.
WARNING: This interface differs from the original JS `vis.Network.off` method.
If you need to remove other listeners, use the underlying JS object's method:
`vis_network.Network.network.off(...)`
"""
def once(self, event_name:str, cbk:Callable):
"""
Equivalent to the original JS method, handling extra logic related to Pyodide.
"""
def destroy(self):
"""
Equivalent to the original JS method, handling extra logic related to Pyodide.
"""
to_js
#
Cette fonction est équivalente à pyodide.ffi.to_js
, à ceci près qu'elle utilise par défaut le paramètre dict_converter=js.Object.fromEntries
, qui transforme les dict
Python en Object
JavaScript, plutôt qu'en Map
.
def to_js(py_object, *args, **kwargs):
"""
Automatically convert a python data structure to the equivalent JsProxy (recursively)
useable with the JS `vis.Network` instances (meaning: it sets the `dict_converter`
argument to `js.Object.fromEntries` for you).
NOTE: Try to avoid assigning the created JsProxy objects: this could increase memory
leak troubles.
"""
target
#
Cette fonction n'a d'utilité que pour une personne souhaitant créer un objet JS vis.Network
directement, en retrouvant donc le comportement canonique de PMT concernant l'utilisation de la macro figure
.
voir l'utilisation directe des objets JS pour un exemple.
def target(figure_id:str=None):
"""
Extract from the DOM the html tag with the id @figure_id.
If @figure_id is `None`, the default value for the equivalent argument of the
`figure(...)` macro is used instead.
"""
Évènements#
Il est possible d'ajouter des évènements aux graphes, en plus de ceux présents par défaut.
La documentation de vis.Network
présente dans ce tableau les arguments passés en argument des gestionnaires d'évènements pour les différents cas possibles.
Afin de limiter les fuites de mémoires, il est conseillé d'utiliser les méthodes on
, off
et/ou once
des objets python vis_network.Network
, qui vont gérer les cycles de vie des proxies nécessaires pour utiliser des fonctions python en tant que gestionnaires d'évènements.
L'interface des méthodes on
et off
en python diffère légèrement de celle en JS
Méthode | Python | Javascript |
---|---|---|
on |
(str, Callable) -> int |
(string, Function) -> undefined |
off |
(str|None, int|None) -> None |
(string?, Function?) -> undefined |
La méthode on
renvoie l'identifiant du proxy servant à la souscription.
C'est cet entier qu'il faut ensuite passer à la méthode off
si l'on souhaite supprimer spécifiquement ce gestionnaire d'évènement sans supprimer les autres.
Voici un exemple simple montrant comment ajouter puis supprimer (via le terminal) un évènement (noter au passage que si l'id de la figure n'est pas fourni dans le constructeur, les arguments data
et options
doivent être des arguments nommés) :
# Tests
(insensible à la casse)(Ctrl+I)
(Alt+: ; Ctrl pour inverser les colonnes)
(Esc)
Votre figure
Gestion du feedback & des erreurs#
La problématique est exactement la même que pour les animations p5
:
- Les animations tournent en dehors des exécutions classiques liées à PMT.
- Les erreurs et les
print
sont affichés dans la console du navigateur. terminal_message
affiche dans le dernier terminal actif.
Utilisation directe des objets JS#
Voici à titre informatif comment il faudrait procéder pour travailler depuis pyodide directement avec la bibliothèque JS vis.Network
.
Le but ici est d'illustrer les techniques à employer, mais garder en tête que tout évènement mis en place directement via l'interface JS ne sera pas géré correctement en ce qui concerne les fuites de mémoire.
Travail directement avec l'interface JS
Le code ci-dessous recrée le même graphe que l'exemple en haut de la page.
# Tests
(insensible à la casse)(Ctrl+I)
(Alt+: ; Ctrl pour inverser les colonnes)
(Esc)
Votre figure
# Tests
(insensible à la casse)(Ctrl+I)
(Alt+: ; Ctrl pour inverser les colonnes)
(Esc)