Représentation de données arborescentes à l’aide de D3.js

Introduction

Le but de cet article est de mettre en oeuvre la librairie D3.js pour représenter des données sous forme d’arborescence.

Données

Les données que je souhaite exploiter dans cet exemple sont stockées dans une base de données. La table "MenuItem" contient les éléments suivants :

Le champ "PARENT_ITEM" contient le "ID_MENU_ITEM" d'un élément de menu d'un ordre supérieur.

Cette simple table permet de stocker des menus sous forme arborescente.

Extraction

Etant donné que ces données sont déjà largement utilisées dans des projets Java, je vais écrire un petit programme de test en réutilisant au maximum les routines existantes. Mon but est d'obtenir un fichier JSON pour que D3.js puisse le représenter .

Maven

Dans les projets de production, Maven a été massivement utilisé pour lier les différents librairies et sous-projets. Je vais donc aussi utiliser Maven pour mon projet de test et ainsi importer facilement les librairies dont j'ai besoin et surtout l'accès aux données via hibernate qui va largement me simplifier la vie.

pom.xml

Ce fichier me permet de lier les librairies dont j'ai besoin :

  • JUnit : Pour les tests unitaires.
  • NavigationEntity : Librairie "maison" contenant les entités hibernate en rapport avec les données qui permettent à l'utilisateur de "naviguer" dans le programme.
  • Guice : Outil d'injection de dépendances.
  • Mysql-Connector : Pour accéder à mes données de test
  • XStream : Outils de sérialisation d'objets
  • Jettison : Librairie qui permet à XStream de sérialiser les données au format JSON
 
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
    <groupId>ch.conceptforge.group</groupId>
    <artifactId>json_d3</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
 
    <name>json_d3</name>
    <url>http://maven.apache.org</url>
 
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
 
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>NavigationEntity</artifactId>
            <version>2.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.google.inject</groupId>
            <artifactId>guice</artifactId>
            <version>3.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.inject.extensions</groupId>
            <artifactId>guice-persist</artifactId>
            <version>3.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.14</version>
        </dependency> 
        <dependency>
            <groupId>com.thoughtworks.xstream</groupId>
            <artifactId>xstream</artifactId>
            <version>1.4</version>
        </dependency>
        <dependency>
            <groupId>org.codehaus.jettison</groupId>
            <artifactId>jettison</artifactId>
            <version>1.3.2</version>
        </dependency>        
    </dependencies>
</project>

Hibernate

Je ne vais pas entrer trop dans les détails, car c'est trop spécifique à la librairie maison "NavigationEntity". Celle-ci utilise Hibernate et Guice pour se connecter à la base de données et extraire les entités sous forme d'objets. Ceci nécessite de créer un fichier "persistence.xml" et de le configurer pour pouvoir utiliser la classe "MenuItem" qui encapsule les données présentées précédemment dans des objets.

Guice

De même que pour hibernate, cette partie est propre à la librairie maison "NavigationEntity", donc je ne vais pas entrer trop dans les détails. Je vais simplement préciser qu'il est nécessaire de construire une classe qui implémente l'interface "Module" de guice. Pour plus d'informations à ce propos, je vous invite à consulter les documents suivants :

Programme principal

package ch.conceptforge.paquet;
 
import ch.conceptforge.paquet.MenuItemTree;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver;
import com.thoughtworks.xstream.io.json.JsonWriter;
import java.io.Writer;
import javax.persistence.EntityManager;
 
public class App
{
 
    private static EntityManager createEntityManager() {
        Injector injector = Guice.createInjector(new GuiceModule());
        return injector.getInstance(EntityManager.class);
    }
 
    private static XStream createXStream() {
        XStream xstream = new XStream(new JsonHierarchicalStreamDriver() {
 
            @Override
            public HierarchicalStreamWriter createWriter(Writer writer) {
                return new JsonWriter(writer, JsonWriter.DROP_ROOT_MODE);
            }
        });
 
        return xstream;
    }
 
    public static void main( String[] args )
    {
        // Liaison de l'entityManager à la classe MenuItem
        MenuItemTree.setEm(createEntityManager());
 
        // Construction de l'arborescence
        MenuItemTree mit = new MenuItemTree();
 
        // Exportation de l'arbre en JSON sur la console
        System.out.println(createXStream().toXML(mit));
    }
}

La partie la plus importante de ce petit programme de test est la construction de l'instance de "MenuItemTree". Le code qui précède cette instanciation permet d’accéder aux entités hibernate. Le passage de l'entityManager par une méthode statique n'est pas élégant, mais, s'agissant d'un petit programme de test, je ne vais pas y remédier. Le code qui suit l'instanciation de "MenuItemTree" permet de sérialiser cet objet. Il suffira ensuite de mettre cette chaine de caractères dans un fichier texte et de la lier à D3.js.

MenuItemTree

Cette classe va s'occuper d'interroger la base de données et de construire l’arborescence d'objets. Dans un esprit de réutilisabilité, j'ai découpé cette classe en deux niveaux d'abstraction.

Tree

Cette classe contient la structure de base d'un arbre qui pourra être utilisé par D3.js

package ch.conceptforge.paquet.tree;
 
import java.util.ArrayList;
import java.util.List;
 
public class Tree {
 
    private String name;
    private List children = null;
 
    public Tree() {
 
    }        
 
    public Tree(String name) {
        this.name = name;
    }
 
    protected void addChild(Tree tree) {
        if (children == null) {
            children = new ArrayList();
        }
        children.add(tree);
    }
 
    public List getChildren() {
        return children;
    }
}

MenuItemTree

Cette classe contient l'implémentation concrète de la structure arborescente pour mes MenuItems.

package ch.ultrasoft.cortex.tree;
 
import com.ultrasoft.server.core.entity.navigation.MenuItem;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Query;
 
/**
 *
 * @author silenus
 */
public class MenuItemTree extends Tree {
 
    private static EntityManager em;        
 
    public static EntityManager getEm() {
        return em;
    }
 
    public static void setEm(EntityManager em) {
        MenuItemTree.em = em;
    }           
 
    public MenuItemTree() {
        super();
        parseItems(em.createQuery("from MenuItem where parentItem = null"));
    }
 
    public MenuItemTree(int id, String name) {
        super(name);
        parseItems(em.createQuery("from MenuItem where parentItem = " + id));
    }
 
    private void parseItems(Query q) {
        for (MenuItem mi : (List) q.getResultList()) {
            addChild(new MenuItemTree(mi.getId(),mi.getText()));
        }
    }
}

JSON

Comme expliqué précédemment, ce petit programme affiche sur la console les données au format json. Voici un extrait :

{
  "children": [
    {
      "name": "Stammdaten",
      "children": [
        {
          "name": "Adressetyp"
        },
        {
          "name": "Bauart"
        },
        {
          "name": "Betriebstyp"
...
        }
      ]
    }
  ]
}

La totalité des données est stockée dans le fichier test2.json

D3.js

A partir des nombreux exemples d'utilisation de cette librairie, voici quelques propositions d'affichage pour mon arborescence.

Le code html n'est pas particulièrement soigné, je souhaitais dans cet article me concentrer sur le rendu visuel des différentes variantes. Au final, une seule sera choisie pour être implémentée dans une application. C'est à ce moment-là que le code sera repris en profondeur.

One comment on “Représentation de données arborescentes à l’aide de D3.js

  1. Pingback: Essai d’utilisation de la bibliothèque « KineticJS » dans un projet GWT (Google Web Toolkit) » ConceptForge, le Blog

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

*

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>