Aller au contenu

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

Votre tracé sera ici

Principe général#

L'idée générale pour que cela fonctionne est la suivante :

  1. Insérer dans le fichier markdown un élément html (une <div>) qui accueillera la figure une fois qu'elle sera créée.
  2. Importer le module du thème,vis_network, depuis pyodide.
  3. 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 ou run. Lors de cette étape, le thème charge automatiquement le CDN vis-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 :

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Entrer ou sortir du mode "deux colonnes"
(Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
(Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

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'argument dict_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) :

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Entrer ou sortir du mode "deux colonnes"
(Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
(Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Votre figure

Votre tracé sera ici

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.

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Entrer ou sortir du mode "deux colonnes"
(Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
(Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Votre figure

Votre tracé sera ici