Prise en main des visualisations Vega dans Kibana

La syntaxe déclarative Vega est un outil puissant pour visualiser vos données. Nouveauté dans Kibana 6.2 : vous pouvez désormais construire des visualisations riches Vega et Vega-Lite avec vos données Elasticsearch. Il est temps pour nous de découvrir le langage Vega au travers de quelques exemples simples.

Pour commencer, ouvrez l’éditeur Vega, un outil pratique pour tester le langage Vega "brut" (c’est-à-dire sans personnalisations Elasticsearch). Copiez le code ci-dessous. Le texte "Hello Vega!" s’affiche dans le panneau de droite.

Text Mark Example

{
  "$schema": "https://vega.github.io/schema/vega/v3.json",
  "width": 100, "height": 30,
  "background": "#eef2e8",
  "padding": 5,
  "marks": [
    {
      "type": "text",
      "encode": {
        "update": {
          "text":     { "value": "Hello Vega!" },
          "align":    { "value": "center"},
          "baseline": { "value": "middle"},
          "stroke":   { "value": "#A32299" },
          "angle":    { "value": 15 },
          "x":        { "signal": "width/2" },
          "y":        { "signal": "height/2" }
        }
      }
    }
  ]
}

Le bloc de repères est une série de primitives de dessin, p. ex. texte, lignes et rectangles. Chaque repère dispose d’un grand nombre de paramètres indiqués dans l’ensemble d’encodage. Chaque paramètre est défini soit sur une constante (valeur), soit sur le résultat d’un calcul (signal) au stade de "mise à jour". Pour le repère de texte, nous définissons la chaîne de texte, nous veillons à ce que le texte soit correctement positionné par rapport aux coordonnées indiquées, dans le bon sens, et nous en définissons la couleur. Les coordonnées x et y sont calculées en fonction de la largeur et de la hauteur du graphe, en positionnant le texte au milieu. Il existe bien d’autres paramètres de repères de texte. Il y a également un graphe de démonstration interactif pour tester différentes valeurs de paramètres pour le repère de texte.

Expliquons quelques éléments : $schema correspond tout simplement à l’ID de la version du moteur Vega requise ; Background permet d’avoir un graphe qui n’est pas transparent ; width et height définissent la taille du quadrillage du dessin initial. La taille du graphe final peut changer dans certains cas, selon le contenu et les options d’autodimensionnement. Remarque : La valeur autosize par défaut dans Kibana est fit, et non pas pad. De ce fait, les éléments height et width sont facultatifs. Le paramètre padding permet d’ajouter de l’espace autour du graphe, en plus de définir la largeur et la hauteur.

Graphe axé sur les données

Prêt pour la prochaine étape ? Nous allons maintenant dessiner un graphe axé sur les données à l’aide du repère de rectangle. La section des données permet d’utiliser plusieurs sources de données, qu’elles soient codées en dur ou qu’elles se présentent sous forme d’URL. Dans Kibana, vous pouvez aussi utiliser des requêtes Elasticsearch directes. Notre table de données vals comprend quatre lignes et deux colonnes (category et count). La colonne category permet de positionner la barre sur l’axe x, tandis que la colonne count définit la hauteur de la barre. Remarque : La valeur 0 pour la coordonnée y se trouve tout en haut. Plus la barre descend, plus la valeur augmente.

Rect Mark Example

{
  "$schema":"https://vega.github.io/schema/vega/v3.json",
  "width": 300, "height": 100,
  "data": [ {
    "name": "vals",
    "values": [
      {"category": 50,  "count": 30},
      {"category": 100, "count": 80},
      {"category": 150, "count": 10},
      {"category": 200, "count": 50}
    ]
  } ],
  "marks": [ {
    "type": "rect",
    "from": { "data": "vals" },
    "encode": {
      "update": {
        "x":     {"field": "category"},
        "width": {"value": 30},
        "y":     {"field": "count"},
        "y2":    {"value": 0}
      }
    }
  } ]
}

Le repère rect indique que la table vals est la source de données. Le repère est dessiné une fois par valeur de donnée source (connu aussi comme ligne de table ou datum). À la différence du graphe précédent, les paramètres x et y ne sont pas codés en dur : ils proviennent des champs du datum.

Montée en charge

La montée en charge est l’un des concepts les plus importants de Vega, mais aussi l’un des plus délicats. Dans les exemples précédents, les coordonnées des pixels à l’écran étaient codées en dur dans les données. Même si cela facilite les choses, il est rare que les données réelles arrivent sous cette forme. À la place, les données sources se présentent sous leurs propres unités (p. ex. nombre d’événements), et c’est au graphe d’effectuer la montée en charge des valeurs sources en pixels selon la taille de graphe souhaitée.

Dans cet exemple, c’est la montée en charge linéaire qui est utilisée. Il s’agit essentiellement d’une fonction mathématique servant à convertir une valeur du domaine des données sources (ici, les valeurs count de 1 000 à 8000, y compris count=0) selon la plage souhaitée (ici, la hauteur du graphe, de 0 à 99). La fonction de montée en charge yscale permet d’ajouter "scale": "yscale" aux paramètres y et y2 afin de convertir les valeurs count en coordonnées à l’écran (0 devient 99, et 8 000 - la plus grande valeur des données sources - devient 0). Remarque : Le paramètre de plage height est un cas spécial car il fait en sorte que la valeur 0 apparaisse en bas du graphe.

Rect Mark with Scaling Example

{
  "$schema":"https://vega.github.io/schema/vega/v3.json",
  "width": 400, "height": 100,
  "data": [ {
    "name": "vals",
    "values": [
      {"category": 50,  "count": 3000},
      {"category": 100, "count": 8000},
      {"category": 150, "count": 1000},
      {"category": 200, "count": 5000}
    ]
  } ],
 "scales": [
    {
      "name": "yscale",
      "type": "linear",
      "zero": true,
      "domain": {"data": "vals", "field": "count"},
      "range": "height"
    }
  ],
  "marks": [ {
    "type": "rect",
    "from": { "data": "vals" },
    "encode": {
      "update": {
        "x":     {"field": "category"},
        "width": {"value": 30},
        "y":     {"scale": "yscale", "field": "count"},
        "y2":    {"scale": "yscale", "value": 0}
      }
    }
  } ]
}

Montée en charge de bande

Pour notre tutoriel, nous aurons besoin d’un autre type de montée en charge Vega 15+ : une montée en charge de bande. Ce type de montée en charge sert lorsque nous disposons d’un ensemble de valeurs (p. ex. des catégories) à représenter sous forme de bandes, occupant chacune la même largeur proportionnelle sur la largeur totale du graphe. Dans le cas présent, la montée en charge de bande attribue à chacune des quatre catégories uniques la même largeur proportionnelle (environ 400/4, moins 5 % de couche entre les barres et aux extrémités). {"scale": "xscale", "band": 1} obtient 100 % de la largeur des bandes pour le paramètre width du repère.

Rect Mark with Band Scaling Example

{
  "$schema":"https://vega.github.io/schema/vega/v3.json",
  "width": 400, "height": 100,
  "data": [ {
    "name": "vals",
    "values": [
      {"category": "Oranges", "count": 3000},
      {"category": "Pears",   "count": 8000},
      {"category": "Apples",  "count": 1000},
      {"category": "Peaches", "count": 5000}
    ]
  } ],
 "scales": [
    {
      "name": "yscale",
      "type": "linear",
      "zero": true,
      "domain": {"data": "vals", "field": "count"},
      "range": "height"
    },
    {
      "name": "xscale",
      "type": "band",
      "domain": {"data": "vals", "field": "category"},
      "range": "width",
      "padding": 0.05
    }
  ],
  "marks": [ {
    "type": "rect",
    "from": { "data": "vals" },
    "encode": {
      "update": {
        "x":     {"scale": "xscale", "field": "category"},
        "width": {"scale": "xscale", "band": 1},
        "y":     {"scale": "yscale", "field": "count"},
        "y2":    {"scale": "yscale", "value": 0}
      }
    }
  } ]
}

Axes

Un graphe type ne serait pas complet sans balises d’axe. La définition des axes applique les mêmes montées en charge que celles indiquées précédemment. Pour les ajouter, rien de plus simple : il suffit de désigner la montée en charge par son nom et d’en préciser le côté de positionnement. Ajoutez le code suivant en tant qu’élément de niveau supérieur dans le dernier exemple de code.

Rect Mark with Axes Example

  "axes": [
    {"scale": "yscale", "orient": "left"},
    {"scale": "xscale", "orient": "bottom"}
  ],

Remarque : La taille du graphique a automatiquement augmenté pour s’adapter à ces axes. Si vous souhaitez que le graphe conserve sa taille d’origine, ajoutez "autosize": "fit" dans la spécification.

Transformations des données et conditions

Pour pouvoir utiliser les données dans le dessin, il faut généralement les manipuler auparavant. Pour cela, Vega propose différentes transformations. Commençons par la transformation de formule la plus courante pour ajouter un champ de valeur count aléatoire à chaque datum source de façon dynamique. Dans ce graphe, nous allons également nous servir de la fonction de couleur de remplissage de la barre : les valeurs inférieures à 333 seront indiquées en rouge, celles comprises entre 333 et 666, en jaune, et celles supérieures à 666, en vert. Remarque : Il est possible d’utiliser une montée en charge à la place, en mappant le domaine des données sources à l’ensemble de couleurs ou à un spectre de couleurs.

Random Data Example

{
  "$schema":"https://vega.github.io/schema/vega/v3.json",
  "width": 400, "height": 200,
  "data": [ {
    "name": "vals",
    "values": [
      {"category": "Oranges"},
      {"category": "Pears"},
      {"category": "Apples"},
      {"category": "Peaches"},
      {"category": "Bananas"},
      {"category": "Grapes"}
    ],
    "transform": [
      {"type": "formula", "as": "count", "expr": "random()*1000"}
    ]
  } ],
 "scales": [
    {
      "name": "yscale",
      "type": "linear",
      "zero": true,
      "domain": {"data": "vals", "field": "count"},
      "range": "height"
    },
    {
      "name": "xscale",
      "type": "band",
      "domain": {"data": "vals", "field": "category"},
      "range": "width",
      "padding": 0.05
    }
  ],
  "axes": [
    {"scale": "yscale", "orient": "left"},
    {"scale": "xscale", "orient": "bottom"}
  ],
  "marks": [ {
    "type": "rect",
    "from": { "data": "vals" },
    "encode": {
      "update": {
        "x":     {"scale": "xscale", "field": "category"},
        "width": {"scale": "xscale", "band": 1},
        "y":     {"scale": "yscale", "field": "count"},
        "y2":    {"scale": "yscale", "value": 0},
        "fill":  [
          {"test": "datum.count < 333", "value": "red"},
          {"test": "datum.count < 666", "value": "yellow"},
          {"value": "green"}
        ]
      }
    }
  } ]
}

Données dynamiques avec Elasticsearch et Kibana

Maintenant que vous disposez des connaissances de base, passons aux choses sérieuses. Nous allons créer un graphe linéaire basé sur le temps en nous servant de données Elasticsearch générées de façon aléatoire. La procédure est similaire à celle que nous avons vue initialement lors de la création d’un graphe Vega dans Kibana. La seule différence, c’est que nous allons nous servir du langage Vega au lieu des paramètres par défaut Kibana de Vega-Lite (la version simplifiée de niveau supérieur de Vega).

Dans cet exemple, nous allons coder les données en dur avec values, au lieu d’émettre la requête réelle avec url. Ainsi, nous pourrons continuer nos essais dans l’éditeur Vega qui ne prend pas en charge les requêtes Elasticsearch Kibana. Le graphe deviendra totalement dynamique dans Kibana si vous remplacez values par la section url comme indiqué ci-dessous.

Notre requête recense le nombre de documents par intervalle de temps, en se servant de la plage de temps et des filtres de contexte sélectionnés par l’utilisateur du tableau de bord. Consultez comment interroger Elasticsearch à partir de Kibana pour en savoir plus.

   "url": {
      "%context%": true,
      "%timefield%": "@timestamp",
      "index": "_all",
      "body": {
        "aggs": {
          "time_buckets": {
            "date_histogram": {
              "field": "@timestamp",
              "interval": {"%autointerval%": true},
              "extended_bounds": {
                "min": {"%timefilter%": "min"},
                "max": {"%timefilter%": "max"}
              },
              "min_doc_count": 0
            }
          }
        },
        "size": 0
      }

Voici le résultat que nous obtenons lorsque nous l’exécutons (nous avons supprimé certains champs non pertinents dans un souci de concision) :

     "aggregations": {
        "time_buckets": {
          "buckets": [
            {"key": 1528061400000, "doc_count": 1},
            {"key": 1528063200000, "doc_count": 45},
            {"key": 1528065000000, "doc_count": 49},
            {"key": 1528066800000, "doc_count": 17},
...

Comme vous pouvez le constater, les données réelles dont nous avons besoin se trouvent dans la série aggregations.time_buckets.buckets. Nous pouvons indiquer à Vega de se concentrer uniquement sur cette série en indiquant "format": {"property": "aggregations.time_buckets.buckets"} dans la définition des données.

Notre axe x n’est plus basé sur les catégories, mais sur le temps : le champ key est exprimé sous forme d’heure UNIX que Vega peut utiliser directement. Nous changeons donc le type xscale en temps, et nous ajustons tous les champs pour qu’ils utilisent key et doc_count. Nous devons également modifier le type de repère en line, et inclure uniquement les canaux de paramètres x et y. Voilà ! Vous avez désormais un graphe linéaire ! Si vous le souhaitez, vous pouvez personnaliser les balises de l’axe x avec les paramètresformat, labelAngle et tickCount.

Elasticsearch search result example

{
  "$schema": "https://vega.github.io/schema/vega/v3.json",
  "width": 400, "height": 200,
  "data": [
    {
      "name": "vals",
      "values": {
        "aggregations": {
          "time_buckets": {
            "buckets": [
              {"key": 1528063200000, "doc_count": 45},
              {"key": 1528065000000, "doc_count": 49},
              {"key": 1528068600000, "doc_count": 32},
              {"key": 1528072200000, "doc_count": 12},
              {"key": 1528074000000, "doc_count": 10},
              {"key": 1528075800000, "doc_count": 10},
              {"key": 1528083000000, "doc_count": 2},
              {"key": 1528088400000, "doc_count": 3},
              {"key": 1528092000000, "doc_count": 9},
              {"key": 1528093800000, "doc_count": 15},
              {"key": 1528095600000, "doc_count": 13},
              {"key": 1528097400000, "doc_count": 19},
              {"key": 1528099200000, "doc_count": 33},
              {"key": 1528101000000, "doc_count": 20},
              {"key": 1528102800000, "doc_count": 55},
              {"key": 1528104600000, "doc_count": 68},
              {"key": 1528108200000, "doc_count": 70},
              {"key": 1528110000000, "doc_count": 108},
              {"key": 1528113600000, "doc_count": 151},
              {"key": 1528117200000, "doc_count": 206},
              {"key": 1528122600000, "doc_count": 258},
              {"key": 1528129800000, "doc_count": 250},
              {"key": 1528135200000, "doc_count": 220}
            ]
          }
        }
      },
      "format": {"property": "aggregations.time_buckets.buckets"}
    }
  ],
  "scales": [
    {
      "name": "yscale",
      "type": "linear",
      "zero": true,
      "domain": {"data": "vals", "field": "doc_count"},
      "range": "height"
    },
    {
      "name": "xscale",
      "type": "time",
      "domain": {"data": "vals", "field": "key"},
      "range": "width"
    }
  ],
  "axes": [
    {"scale": "yscale", "orient": "left"},
    {"scale": "xscale", "orient": "bottom"}
  ],
  "marks": [
    {
      "type": "line",
      "from": {"data": "vals"},
      "encode": {
        "update": {
          "x": {"scale": "xscale", "field": "key"},
          "y": {"scale": "yscale", "field": "doc_count"}
        }
      }
    }
  ]
}

Gardez un œil sur notre blog pour lire d’autres articles sur Vega. Parmi les thèmes abordés prochainement : comment gérer les résultats Elasticsearch, notamment les agrégations et les données imbriquées.

Liens utiles

Précédents articles sur Vega