Extension:Graph/Tutoriel interactif de Graph 1

This page is a translated version of the page Extension:Graph/Interactive Graph Tutorial and the translation is 100% complete.

Dans ce tutoriel, nous allons créer un graphe interactif qui affichera les taux de fertilité historiques par pays, avec un curseur pour sélectionner l'année, et une carte pour montrer la distribution des taux partout sur la planète. Si vous voulez investiguer le code source de ce graphe uniquement, il est disponible sur une page séparée. Utilisez le bac à sable interactif de Graph pour vos tests. Vous pourriez également être intéressés par la documentation complète de Vega (en anglais).


Déplacez le curseur pour modifier l'année et survolez les pays à l'aide de la souris pour afficher les taux. Essayez !

Dessiner des formes

Nous commençons par dessiner quelques éléments (marks). Le code du graphe est encadré avec la balise <graph mode=interactive>...</graph> même ci celui-ci n'est pas encore interactif. Ouvrez le bac à sable interactif de Graph et copiez y le code suivant sans la balise <graph> pour vous donner une idée.

Astuce : utilisez l'« Editeur de code » pour copier les spécifités du graphe dans l'éditeur Vega pour vous entrainer.

voir le code source
{
  // Nous voulons utiliser Vega 2, et indiquez la taille de l'image
  "version": 2, "width": 300, "height": 80,
  // Définissez la largeur de la zone de remplissage à la même valeur de chaque côté.
  "padding": 12,
  // Par défaut, le fond est transparent
  "background": "#edf1f7",
  "marks": [
    {
      // Tracer une ligne horizontale
      "name": "scrollLine",
      "type": "rule",
      "properties": {
        "enter": {
          "x": {"value": 0},
          "y": {"value": 40},
          "x2": {"value": 300},
          "stroke": {"value": "#000"},
          "strokeWidth": {"value": 2}
        }
      }
    },
    {
      // Tracer une forme de triangle avec un effet de survol (hover)
      // nommer les objets nous permet de les référencer plus tard
      "name": "handle",
      "type": "path",
      "properties": {
        "enter": {
          "x": {"value": 200},
          "y": {"value": 40},
          // la syntaxe du chemin est la même que la balise du chemin SVG
          "path": {"value": "m-5.5,-10l0,20l11.5,-10l-11.5,-10z"},
          "stroke": {"value": "#880"},
          "strokeWidth": {"value": 2.5}
        },
        "update": {"fill": {"value": "#fff"}},
        // Changer la couleur de remplissage de l'objet survolé par la souris
        "hover": {"fill": {"value": "#f00"}}
      }
    }
  ]
}

Signal isDragging

Pour que l'on puisse faire glisser notre objet curseur, il faut d'abord savoir si on a cliqué dessus ou pas. Pour cela, on ajoute un signal qui passe à true quand l'objet est cliqué (isDragging) et une marque textuelle pour afficher (debogage) le résultat :

voir le code source
"signals": [
  {
    "name": "isDragging",
    "init": false,
    "streams": [
      {"type": "@handle:mousedown","expr": "true"},
      {"type": "mouseup","expr": "false"}
    ]
  },
],

// dans "marks"
  {
    "name": "debugIsDragging",
    "type": "text",
    "properties": {
      "enter": {
        "x": {"value": 250},
        "y": {"value": 0},
        "fill": {"value": "black"}
      },
      "update": {"text": {"signal": "isDragging"}}
    }
  }

Signal handlePosition

Maintenant que nous savons quand l'objet est glissé, nous ajoutons un signal mousemove qui ne change de valeur que lorsque le signal isDragging est true. Nous attachons aussi le nouveau signal aux coordonnées « x » du curseur via la section « update » :

voir le code source
// dans "signals"
  {
    "name": "handlePosition",
    "init": 200,
    "streams": [
      {
        "type": "mousemove[isDragging]",
        "expr": "eventX()"
      }
    ]
  }

  // ajouter dans "marks"
  {
    "name": "handle",
    ...
    "update": {
      "x": {"signal": "handlePosition"},
      ...
    },
  },
  {
    "name": "debugHandlePosition",
    "type": "text",
    "properties": {
      "enter": {
        "x": {"value": 250},
        "y": {"value": 14},
        "fill": {"value": "black"}
      },
      "update": {"text": {"signal": "handlePosition"}}
    }
  }

Signal de calibrage de la position du curseur

Avoir la position en pixels du curseur n'est pas très utile - nous préfèrerions plutôt avoir une valeur qui a un sens pour notre graphe, comme l'année. Les échelles (scales) Vega fournissent un mécanisme utile pour convertir nos données (par exemple les années) ainsi que les coordonnées de l'écran, et revenir en arrière (inverser). Dans cette étape, nous ajoutons à yearsScale une échelle linéaire pour les valeurs de l'intervalle 1960..2013, pour qu'elle soit cadrée sur la largeur complète du graphique (cadrage exclu). Nous ajoutons un nouveau signal scaledHandlePosition pour convertir la position X de la souris en valeur significative d'année.

voir le code source
// ajouter la valeur de base de "scales" :
  "scales": [
    {
      "name": "yearsScale",
      "type": "linear",
      "zero": false,
      "domain": [1960, 2013],
      "range": "width"
    }
  ],

// dans "signals" ajouter :
    {
      "name": "scaledHandlePosition",
      "expr": "handlePosition",
      "scale": {"name": "yearsScale","invert": true}
    }

  // ajouter dans "marks"
    {
      "name": "debugScaledHandlePosition",
      "type": "text",
      "properties": {
        "enter": {
          "x": {"value": 250},
          "y": {"value": 28},
          "fill": {"value": "black"}
        },
        "update": {"text": {"signal": "scaledHandlePosition"}}
      }
    }

Mettre à jour la valeur de l'année

Si vous l'avez remarqué, le curseur peut dépasser la largeur du graphique ce qui produit des résultats incohérents. Aussi la valeur à l'échelle n'est pas un nombre entier comme on pourrait s'y attendre de la valeur d'une année. Pour corriger cela, on peut ajouter un signal de post-traitement supplémentaire appelé annéeActuelle (currentYear) pour la convertir en entier et la contraindre dans l'intervalle nécessaire. Nous lui donnons une valeur initiale raisonnable, et relions l'intitulé de l'année yearLabel à la position du curseur sur l'échelle. Notez que la position du curseur doit être comprise à l'intérieur des coordonnées de l'écran pour pouvoir utiliser yearsScale et retrouver la valeur :

voir le code source
    // Nouveau signal :
    {
      "name": "currentYear",
      "init": 2000,
      "expr": "clamp(parseInt(scaledHandlePosition),1960,2013)"
    }

    // Mettre à jour la marque yearLabel :
    // Nouvelle marque pour afficher l'année
    {
      "name": "yearLabel",
      "type": "text",
      "properties": {
        "enter": {
          "x": {"value": 0},
          "y": {"value": 25},
          "fontSize": {"value": 32},
          "fontWeight": {"value": "bold"},
          "fill": {"value": "steelblue"}
        },
        "update": {"text": {"signal": "currentYear"}}
      }
    },

    // Mettre à jour la marque handle :
    {
      "name": "handle",
      "properties": {
        "update": {
          "x": {"scale": "yearsScale","signal": "currentYear"}
        },
      }
    }

Nettoyage final

À présent nous pouvons retirer toutes les marques de débogages. Nous n'avons également plus besoin de séparer les signaux handlePosition et scaledHandlePosition parce que la mise à l'échelle peut avoir lieu dans la même passe :

voir le code source
{
  // We want to use Vega 2, and specify image size
  "version": 2, "width": 300, "height": 80,
  // Set padding to the same value on all sides
  "padding": 12,
  // By default the background is transparent
  "background": "#edf1f7",

  "signals": [
    {
      "name": "isDragging",
      "init": false,
      "streams": [
        {"type": "@handle:mousedown","expr": "true"},
        {"type": "mouseup","expr": "false"}
      ]
    },
    {
      "name": "scaledHandlePosition",
      "streams": [
        {
          "type": "mousemove[isDragging]",
          "expr": "eventX()",
          "scale": {"name": "yearsScale","invert": true}
        }
      ]
    },
    {
      "name": "currentYear",
      "init": 2000,
      "expr": "clamp(parseInt(scaledHandlePosition),1960,2013)"
    }
  ],

  "scales": [
    {
      "name": "yearsScale",
      "type": "linear",
      "zero": false,
      "domain": [1960, 2013],
      "range": "width"
    }
  ],

  "marks": [
    {
      // draw the year label in the upper left corner
      "name": "yearLabel",
      "type": "text",
      "properties": {
        "enter": {
          "x": {"value": 0},
          "y": {"value": 25},
          "fontSize": {"value": 32},
          "fontWeight": {"value": "bold"},
          "fill": {"value": "steelblue"}
        },
        "update": {"text": {"signal": "currentYear"} }
      }
    },
    {
      // Draw a horizontal line
      "name": "scrollLine",
      "type": "rule",
      "properties": {
        "enter": {
          "x": {"value": 0},
          "y": {"value": 40},
          "x2": {"value": 300},
          "stroke": {"value": "#000"},
          "strokeWidth": {"value": 2}
        }
      }
    },
    {
      // Draw a triangle shape with a hover effect
      // naming objects allows us to reference them later
      "name": "handle",
      "type": "path",
      "properties": {
        "enter": {
          "y": {"value": 40},
          // path syntax is the same as SVG's path tag
          "path": {"value": "m-5.5,-10l0,20l11.5,-10l-11.5,-10z"},
          "stroke": {"value": "#880"},
          "strokeWidth": {"value": 2.5}
        },
        "update": {
          "x": {"scale": "yearsScale","signal": "currentYear"},
          "fill": {"value": "#fff"}
        },
        // Change fill color of the object on mouse hover
        "hover": {"fill": {"value": "#f00"} }
      }
    }
  ]
}

Prochaine étape

Veuillez continuer à la partie 2.