Androïd et D3

Objectifs

Ce travail pratique à comme double-objectif de vous aider à découvrir les activités Androïd et l’environnement D3 :

  • relier des activités par intention explicite,
  • relier des activités par intention implicite,
  • exécuter des tâches en arrière-plan, et
  • découvrir la bibliothèque D3.

Rajouter une activité à intention explicite

Nous allons reprendre notre application de la séance précédente. Rappelons que notre convertisseur de monnaie demande à l’utilisateur de saisir le taux d’échange entre deux monnaies. C’est un peu simpliste comme application.

On peut imaginer des meilleurs interfaces avec des meilleurs interactions, mais faisons simple ici : rajoutons un bouton à coté du champs de texte pour le taux d’échange qui permettra, lorsque l’utilisateur le clique, de charger le taux d’échange sur internet.

Lorsque l’utilisateur clique sur ce bouton, votre convertisseur devrait lancer une deuxième activité dans votre application qui aurait une interface qui permet de choisir parmi plusieurs monnaies (e.g. dollars américains, yuans chinois, dinars tunisiens, …). Pour l’instant, vous allez câbler ces taux en dur. À vous de choisir une interface adapté.

Rajouter une activité à intention implicite

Nous voulons ensuite étendre notre convertisseur de monnaie pour afficher l’historique des taux d’échange. Pour faire cela, rajouter encore un bouton à votre interface à un endroit adapté (vous êtes libre de choisir comment le faire et à quelle activité). Un clic sur ce bouton devrait lancer un navigateur web qui va ouvrir une page web dans votre compte. Choisissez un bon nom pour ce fichier, par exemple, historique-monnaies, et créer un dossier avec ce nom dans votre compte. Dans ce dossier, rajouter un fichier index.html, un dossier js, un dossier css, et un dossier data.

À cette stade, nous ne développons plus dans Androïd mais dans le web. Notre application web comprendra quatre choses :

  • les données qu’on veut visualiser, qu’on mettra dans le dossier data.
  • notre code D3, écrit en JavaScript, qui décrira comment charger et afficher les données. On le mettra dans le dossier js (pour JavaScript).
  • les styles d’affichage css, qui décrivent e.g. la couleurs des éléments de notre visualisation, et
  • un document HTML qui reliera le tout.

Mettons dans notre index.html le squelette de notre visualisation :

    <html>
    <head>
        <meta charset="utf-8">
        <title>Historique des monnaies</title>
    </head>
    <body>
    </body>
    </html>

Ceci nous une une page vide avec comme titre dans le navigateur Historique des monnaies. Vérifions cela en ouvrant la page dans un navigateur web.

Charger D3

Ce document est assez ennuyeux, rajoutons alors un peu de contenus. Avec D3, nous rajoutons ces contenus en code, avec du JavaScript. Rajoutons alors la bibliothèque D3 à notre projet. Téléchargez la bibliothèque D3 et l’extrayez dans le dossier js/. On devrait voir une structure comme ceci :

  • historique-monnaies
    • css/
    • data/
    • index.html
    • js/
      • d3
        • d3.js
        • d3.min.js
        • LICENSE

Il faut ensuite dire à notre projet de charger cette bibliothèque. Rajoutons la ligne suivante aux en-têtes (<head>) de notre document.

    <script type="text/javascript" src="js/d3/d3.js"></script>

Il faut aussi rajout un dossier où on mettra notre code. Créez un fichier js/historique.js, puis chargez-le en rajoutant au _corps (<body>) de notre document HTML :

    <script type="text/javascript" src="js/historique.js"></script>

Pour vérifier que tout marche correctement, on peut rajouter la ligne suivante à notre historique.js:

    alert("Hello, Money!");

Lorsqu’on charge la page, on devrait voir cet alerte. Si ceci n’est pas le cas, vérifiez que vous avez bien mis la balise <script> pour D3 dans les en-têtes de votre document HTML et celle de votre historique.js dans le corps du document.

Si ça ne marche toujours pas, voyons quoi d’autre pourrait être le problème. Ouvrez les outils de développeur du navigateur. Dans Chrome, faites Afficher > Options pour les développeurs > Outils de développement, puis cliquez le bouton Console. En Safari, ouvrez les préférences, puis allez dans l’onglet Avancées. Assurez que la case Afficher le menu Développement dans la barre des menus est cochée. Puis faites Développement > Afficher le console JavaScript. S’il y a des erreurs dans votre code, elle devraient s’afficher ici.

Certains navigateurs, comme Chrome, sont très strictes sur l’exécution de JavaScript, même dans une page locale. Il faudrait peut-être accéder au projet via un serveur web. Pour faire cela, nous allons démarrer un serveur web sur la machine local en utilisant un module de la bibliothèque standard de python. Ouvrez un terminal et cd dans votre dossier projet. Puis lances l’une des commandes suivantes (selon la version de Python sur votre machine) pour démarrer un serveur web au port 8080 :

    python -m SimpleHTTPServer 8080
    python -m http.server 8080

Vous devrez désormais pouvoir accéder à votre projet en allant à http://localhost:8080. Avertissement: lancez cette commande seulement depuis votre dossier projet, car il donnerait accès à tous les fichiers de là où vous l’avez lancé.

Félicitations ! Vous avez un squelette vide d’un projet ! Supprimez l’appel alert() pour que ça ne nous rende pas fou.

Créer un canevas vide

Jusque là, nous avons créé un document qui charge D3 et notre fichier JavaScript. On doit créer un canevas où on peut dessiner notre visualisation. Pour faire cela, nous allons utiliser SVG, un format vecteur avec un syntaxe comme le HTML. Au plus simple, un document SVG vide est juste <svg></svg>. Créons un canevas video pour notre dessin, d’une taille 600 fois 600 pixels :

    var w = 600;
    var h = 600;
    
    //Create SVG element
    var svg = d3.select("body")
                .append("svg")
                    .attr("width", w)
                    .attr("height", h);

Regardons un peu plus ce que ça fait : les deux première lignes sont plutôt directes : on crée deux variables globales : w et h, qui stockent les dimensions de notre canevas. C’est la suite qui est plus compliquée. Dans un premier temps, on utilise la variable globale d3 pour interagir avec la bibliothèque D3.

D3 utilise le principe de sélecteurs (un peu comme le même principe dans jQuery, si vous le connaissez). Ainsi, lorsqu’on écrit d3.select("body"), D3 parcourra le DOM et rendra tout élément correspondant. Dans ce cas, tous les éléments <body> dont un document bien formé n’a qu’un seul. Alors, le DOM pour un document à corps vide :

    <body></body>

deviendrait :

    <body>
        <svg></svg>
    </body>

Remarquons aussi qu’on utilise des commandes enchainées, en écrivant quelque chose comme d3.select().append().attr().attr(). Cette formulation marche car le résultat de la méthode .append() est l’élément nouvellement créé, dont on affecte son attribut width à la valeur w (600) avec la méthode attr(). D3 utilise ce mécanisme d’enchainement pour ses méthodes, où un méthode qui rendrait void rende l’objet sur lequel on agit. Ainsi, le résultat de .attr() est l’élément SVG dont on modifie l’attribut.

Après cette selection, notre document ressemble à :

    <body>
        <svg width="600" height="600"></svg>
    </body>

Si vous regardez votre index.html, vous pourrez être excusé de penser que rien ne marche, car rien n’a changé : aucune nouvelle ligne n’a été rajouté au fichier. Rappelez que, dans D3, nous modifions programmatiquement le DOM. Lorsque le navigateur charge le document, il crée sa représentation interne (le DOM) de ce qui est dans le .html. Puis, notre JavaScript agit sur cette représentation interne du document. Elle est entièrement éphémère : le document sur le disque n’est pas modifié.

Charger les données

Now lets take a look at [the data we’re going to use][data]. Open them in your preferred text editor or in your favorite spreadsheet program. Notice that this a file containing a data table in a tab-separated format. What are the attributes of the data file? Do you notice anything interesting about the data?

The first thing we need to decide is how to store our data. Generally D3 stores data as a list of values. Here, each value will be a row in our table, which we will store as a JavaScript dictionary, or object. Each entry should have a postal code, INSEE code, place name, longitude, latitude, population, and population density. We will need to define a loader that will fetch each row of our TSV file and convert it into a JavaScript object.

Let’s go ahead and read the data into D3. D3 provides various importers to handle data in different formats, such as csv, tsv, json, etc. For more information, take a look at the [D3 API][]. Since our data consists of tab-separated values, we are going to use the tsv loader.

What we want to do is write a function that loads the data from a file or URL, parses the data and stores it in memory, and then sets up the visualization. This is what we’re going to do, but we can’t do that all at once. What happens if the data file is really big, or if we’re loading it from a slow server? The interface would become unresponsive, or blocked, until it finished loading and processing all of that data.

Instead, D3 loaders generally run in the background. That means that our call to d3.tsv("data/france.tsv") will return immediately, before the data have finished loading. This is what is known as asynchronous or event-driven programming. We’re going to tell D3 to load our data, and then give it a function to call when it has finished. This will look something like

     d3.tsv("data/france.tsv")
       .get(function(error, rows) {
           // Handle error or set up visualization here
       });

The first line tells D3 that we want to load TSV data from data/france.tsv. The .get() method tells it to start loading and that, when it is done, it should call a function. That function has two parameters, error, and rows. This code in our function is not executed right now, but it will be at some point in the future.

If you’re curious about what’s going on here, we’re creating an anonymous function and using it at a parameter to the .get() method. When the get() method finished loading its data in the background, it will then call this anonymous function. #}