Utilisation de WebCola et de D3.js pour créer une mise en page hiérarchique

Une bonne présentation est essentielle pour découvrir la structure inhérente aux données connectées. Les dispositions hiérarchiques sont généralement utilisées pour afficher les relations parent-enfant entre les nœuds du graphique. L'implémentation D3.js standard de la disposition hiérarchique ne peut pas être utilisée dans le cas de données réelles dans lesquelles un nœud enfant peut avoir plusieurs nœuds parents. Nous utilisons WebCola et D3.js pour dessiner des graphes hiérarchiques à l'aide d'une spécification déclarative des contraintes en quelques lignes de code.

Le monde réel est richement interconnecté et je pense que nos interfaces utilisateurs devraient imiter ces connexions. Les connexions dans un réseau transmettent des informations. Un coup d'œil rapide sur un réseau, s'il est bien représenté, peut révéler des structures inhérentes qui ne sont pas visibles superficiellement. Étant donné la nature hautement connectée des données de nos clients, chez Alyne, nous avons décidé d'investir du temps dans une visualisation évolutive qui représenterait les données et les connexions entre elles de manière intuitive.

L'un des cas d'utilisation qui l'exploitera le plus chez Alyne est notre bibliothèque d'objets. Nous permettons à nos clients de reproduire leurs actifs numériques et physiques dans la bibliothèque d'objets de notre application. Généralement, ces actifs sont de nature hiérarchique, et nous avons identifié que le diagramme de réseau peut être un bon outil pour permettre aux clients d'avoir une vue d'ensemble de leur paysage d'objets.

Visualisation du réseau

L'une des façons les plus simples et les plus puissantes de visualiser les réseaux est d'utiliser un réseau orienté par la force. Les réseaux orientés par la force réorganisent les nœuds et les arêtes de manière à réduire au minimum le chevauchement des nœuds et les intersections des arêtes. Les réseaux orientés par la force sont une bonne représentation des réseaux car ils permettent de démêler des schémas communs tels que les grappes de nœuds, les nœuds à haut degré, les composantes connectées, etc. mais ils ne peuvent pas représenter les informations hiérarchiques de manière directe.

Il existe plusieurs excellentes bibliothèques, telles que D3, pour calculer les informations relatives à la position des réseaux.

Dans ce billet, nous nous concentrons particulièrement sur la représentation hiérarchique des données. D3 fournit une mise en page hiérarchique prête à l'emploi pour calculer les positions des nœuds de manière hiérarchique. Cependant, la mise en page hiérarchique fonctionne sur les données qui sont dans un format arborescent (structure parent-enfant implicite), avec une contrainte selon laquelle un nœud ne doit pas avoir plus d'un parent. Par conséquent, nous ne pouvons pas utiliser d3-hierarchy pour créer des présentations arborescentes afin d'indiquer des informations hiérarchiques lorsque la structure des données ressemble à un graphe.

Nous pouvons résoudre ce problème en tirant parti de la mise en page dirigée par la force D3 afin d'obtenir des nœuds qui ne se chevauchent pas et en imposant des contraintes supplémentaires sur les positions géométriques des nœuds afin d'obtenir une mise en page hiérarchique. Cela peut être réalisé purement en D3, mais il est un peu fastidieux d'écrire des contraintes en JavaScript.

Il existe un moyen plus simple : WebCola.

WebCola nous offre un moyen de spécifier les informations de mise en page en termes de contraintes géométriques. En outre, il génère dynamiquement des contraintes pour la disposition des flux, les nœuds qui ne se chevauchent pas, etc. afin de désencombrer le diagramme de réseau. J'aime WebCola parce qu'il est simple et flexible à utiliser.

Commençons par un simple réseau d3 force layout avec les données suivantes. Comme vous pouvez le constater, il n'y a pas de structure parent-enfant inhérente à ces données. Par conséquent, pour organiser les nœuds de manière hiérarchique, nous introduisons une propriété de niveau par nœud ; elle indique la position d'un nœud dans une hiérarchie.

Nous allons afficher une mise en page orientée vers la force de vanille pour les données suivantes en utilisant la bibliothèque de mise en page de force D3.

const nodes = [
  { id: 1, level: 1 }, { id: 2, level: 2 },
  { id: 3, level: 2 }, { id: 4, level: 2 },
  { id: 5, level: 3 }, { id: 6, level: 3 },
  { id: 7, level: 3 }, { id: 8, level: 3 }, 
  { id: 9, level: 3 }, { id: 10, level: 3 },
  { id: 10, level: 3 }, { id: 11, level: 4 },
  { id: 12, level: 4 }, { id: 13, level: 4 },
  { id: 14, level: 4 }, { id: 15, level: 4 },
  { id: 16, level: 4 }
];
const links = [
  { start: 1, end: 2 },
  { start: 1, end: 3 },
  { start: 1, end: 4 },
  { start: 2, end: 5 },
  { start: 2, end: 6 },
  { start: 3, end: 7 },
  { start: 3, end: 8 },
  { start: 3, end: 9 },
  { start: 4, end: 5 },
  { start: 4, end: 9 },
  { start: 4, end: 10 },
  { source: 5, target: 11 },
  { source: 5, target: 12 },
  { source: 7, target: 13 },
  { source: 8, target: 14 },
  { source: 10, target: 15 },
  { source: 10, target: 16 },
  { source: 9, target: 16 },
];

Par souci d'observabilité, dans la visualisation ci-dessus, nous affichons les nœuds ayant la même level avec la même couleur, et nous ajustons le rayon du nœud en fonction de l'attribut level c'est-à-dire plus le level plus le rayon est petit.

Comme vous pouvez le voir dans le diagramme, la mise en page dirigée par la force génère uniquement la contrainte pour les nœuds qui ne se chevauchent pas à l'aide de diverses forces. Mais il n'y a pas de moyen facile dans D3, hors de la boîte, d'organiser les données hiérarchiquement sur la base de la propriété exogène du nœud.

Nous pouvons tirer parti de WebCola pour ajouter des contraintes géométriques afin d'aligner les nœuds à différents niveaux en fonction de leur propriété de niveau.

Contraintes de WebCola

Il existe différentes catégories de contraintes dans WebCola, telles que l'alignement, le regroupement, les contraintes d'égalité, etc. Nous allons utiliser les contraintes d'alignement et d'inégalité pour aligner les nœuds au même niveau le long de l'axe des ordonnées. Ces contraintes peuvent être spécifiées en JSON comme suit :

{
  "type": "alignment",
  "axis": "y",
  "offsets": [
    { "node": 1, "offset": 0 },
    { "node": 2, "offset": 0 },
    { "node": 3, "offset": 0 }
  ]
}

Cette contrainte spécifiée alignerait le nœud avec les indices 1, 2 et 3 le long de l'axe y, c'est-à-dire horizontalement. offset désigne le déplacement du centre du nœud, généralement utilisé dans le cas de nœuds de taille variable.

Essayons maintenant d'organiser notre réseau sous forme d'arbre. Nous avons besoin de deux types de contraintes, comme suit :

  1. Contrainte d'alignement - Nous voulons que les nœuds ayant la même propriété de niveau aient la même y coordonner
  2. Contrainte d'égalité - Les nœuds de même niveau doivent avoir une marge à gauche et à droite pour une meilleure lisibilité.

Ainsi, dans notre exemple, nous regroupons les nœuds par attribut de niveau et, pour chaque nœud du même groupe, nous spécifions une contrainte d'alignement le long de l'axe des ordonnées. En outre, dans chaque groupe, nous attribuons une contrainte d'égalité positionnelle le long de l'axe x avec un écart de 50 pixels ; cela nous aide à désencombrer les nœuds situés au même niveau. La valeur de l'écart peut être dérivée dynamiquement en fonction de la taille du nœud, mais il s'agit d'une décision spécifique au rendu.

const constraints = [];
const { nodes, links } = loadData();
const groups = _.groupBy(nodes, "level");

for (const level of Object.keys(groups)) {
  const nodeGroup = groups[level];
  const constraint = {
    type: "alignment",
    axis: "y",
    offsets: [],
  };
  let prevNodeId = -1;
  for (const node of nodeGroup) {
    constraint.offsets.push({
      node: _.findIndex(nodes, (d) => d.id === node.id),
      offset: 0,
    });

    if (prevNodeId !== -1) {
      constraints.push({
        axis: "x",
        left: _.findIndex(nodes, (d) => d.id === prevNodeId),
        right: _.findIndex(nodes, (d) => d.id === node.id),
        gap: 50,
      });
    }

    prevNodeId = node.id;
  }

  constraints.push(constraint);
}

d3Cola
  .nodes(nodes)
  .links(links)
  .constraints(constraints)
  .flowLayout("y", 80)
  .linkDistance(50)
  .symmetricDiffLinkLengths(40)
  .avoidOverlaps(true)
  .on("tick", ticked)
  .start(10, 40, 50);

Les contraintes basées sur l'attribut de niveau du nœud sont des contraintes définies par l'utilisateur. En plus de ces contraintes définies par l'utilisateur, WebCola génère dynamiquement des contraintes pour déterminer la longueur des liens (linkDistance, flowLayout), les contraintes de non-chevauchement (avoidOverlaps), etc.

Nous effectuons une simulation dans laquelle nous appliquons des contraintes générées dynamiquement pendant 10 itérations, des contraintes définies par l'utilisateur pendant 40 itérations et les deux ensembles de contraintes ensemble pendant 50 itérations. Ces nombres peuvent être modifiés de manière heuristique en fonction du nombre de nœuds et de contraintes dans la simulation.

Grâce à ces contraintes, nous obtenons une disposition arborescente en utilisant la simulation dirigée par la force, bien que nous ne disposions pas d'une structure de données semblable à celle d'un arbre. Vous pouvez voir que les nœuds ayant la même valeur de niveau ont la même couleur et sont situés à la même hauteur. De plus, notre visualisation est stable lorsque plusieurs nœuds ont plusieurs parents.

Ceci n'est qu'un aperçu de la façon dont WebCola peut être utilisé pour appliquer différentes dispositions au diagramme de réseau en quelques lignes de code. Je vous recommande de consulter SetCola qui est un langage de haut niveau pour créer des contraintes WebCola et avec quelques lignes de code, vous pouvez créer des graphismes complexes.

[content_block id=53166]

Nous sommes là pour vous aider

Contactez-nous et nous répondrons à toutes vos questions sur la façon dont Mitratech contribue à votre succès.

Nous contacter