Documentation

  1. Accueil
  2. Docs
  3. Documentation
  4. Principales fonctionnalit...
  5. Les routines et graphique...
  6. Modèles de graphiques Vega-Lite

Modèles de graphiques Vega-Lite

LODEX s’appuie sur Vega-Lite, une grammaire de visualisation déclarative, pour générer la plupart de ses graphiques. Grâce à ce langage, il est possible de personnaliser les visualisations proposées par LODEX, mais aussi de créer des représentations plus avancées adaptées à la structure de vos données.

Graphique en aires superposées Graphiques combinés Diagramme à barres empilées Graphique en cercles imbriqués
Diagramme à barres colorées par catégories Donut labellisé Carte proportionnelle non hiérarchique Graphique en courbe

Qu’il s’agisse de modifier les couleurs, les polices, les axes, les légendes, les infobulles ou les étiquettes, de combiner plusieurs représentations ou encore de construire un nouveau type de graphique, Vega-Lite permet d’aller au-delà des paramètres proposés directement dans l’interface.

Cette section rassemble des modèles de graphiques Vega-Lite prêts à être adaptés et réutilisés dans LODEX. Chaque modèle part d’un besoin de visualisation concret et présente la configuration nécessaire pour obtenir le résultat souhaité.

Certains modèles permettent de personnaliser un graphique existant, tandis que d’autres proposent des visualisations plus élaborées, comme des graphiques combinés, des barres empilées, des aires superposées ou des cercles imbriqués. Lorsque cela est nécessaire, la préparation des données à l’aide d’une routine LODEX est également expliquée.

Aucune connaissance approfondie de Vega-Lite n’est nécessaire pour utiliser ces modèles. Les scripts proposés peuvent être copiés puis adaptés à vos propres données en suivant les indications fournies pour chaque modèle. Ils permettent également de se familiariser progressivement avec la structure d’une spécification Vega-Lite.

Activer le Mode avancé

Pour accéder à la configuration complète d’un graphique, commencez par activer le Mode avancé dans les paramètres du format. Ce mode permet d’éditer directement la spécification Vega-Lite du graphique et de personnaliser son apparence ainsi que son comportement.

Activation du mode avancé dans les paramètres d'un graphique LODEX

Tous les modèles présentés dans cette section nécessitent que le Mode avancé soit activé.

💡 Astuce
L’activation du Mode avancé ne modifie pas le fonctionnement du graphique. Elle permet simplement d’accéder à sa spécification Vega-Lite. Vous pouvez revenir à tout moment à la configuration précédente en désactivant ce mode.

Diagramme à barres colorées par catégories

Diagramme à barres colorées représentant les différentes voies d'accès

Le diagramme à barres est particulièrement adapté pour comparer les effectifs de plusieurs catégories. L’utilisation d’une couleur différente pour chaque catégorie permet de les distinguer rapidement et de faciliter la lecture du graphique.

Dans cet exemple, le graphique permet de visualiser la répartition des publications d’un corpus selon leur voie d’accès à la science ouverte : Diamond, Gold, Green, Bronze, Hybrid ou Closed.

Configurer la routine

Dans cet exemple, les données sont préparées à l’aide de la routine distinct-by.

La routine est appliquée au champ contenant les voies d’accès. Elle permet de compter le nombre de publications correspondant à chaque valeur du champ.

Configuration de la routine distinct-by appliquée aux voies d'accès

Elle produit automatiquement une structure de données contenant deux champs :

{
  "_id": "Gold",
  "value": 577
}
  • _id : la catégorie à représenter, ici la voie d’accès.
  • value : le nombre de publications correspondant à cette catégorie.

Cette structure est directement exploitable par Vega-Lite pour représenter chaque catégorie sous la forme d’une barre dont la hauteur correspond à son nombre de publications.

Créer le graphique

Le script suivant attribue une couleur différente à chaque voie d’accès. Il calcule également la part représentée par chaque catégorie dans l’ensemble des publications. Cette valeur est stockée dans le champ Taux et affichée dans l’infobulle sous l’intitulé Taux de publication.

{
  "transform": [
    {
      "window": [
        {
          "op": "sum",
          "field": "value",
          "as": "NbPublisPerYear"
        }
      ],
      "frame": [
        null,
        null
      ]
    },
    {
      "calculate": "round(datum.value*1000/datum.NbPublisPerYear)/1000",
      "as": "Taux"
    }
  ],
  "layer": [
    {
      "mark": {
        "type": "bar"
      },
      "encoding": {
        "x": {
          "field": "_id",
          "type": "nominal",
          "axis": {
            "labelAngle": 0,
            "tickMinStep": 1
          },
          "title": "",
          "sort": [
            "Diamond",
            "Gold",
            "Green",
            "Bronze",
            "Hybrid",
            "Closed"
          ]
        },
        "y": {
          "field": "value",
          "type": "quantitative",
          "axis": {
            "labelAngle": 0,
            "tickMinStep": 1
          },
          "scale": {
            "type": "linear"
          },
          "title": ""
        },
        "color": {
          "field": "_id",
          "scale": {
            "domain": [
              "Diamond",
              "Gold",
              "Green",
              "Bronze",
              "Hybrid",
              "Closed"
            ],
            "range": [
              "#00bfff",
              "#f9bc01",
              "#228B22",
              "#c97522",
              "#ffff67",
              "#7f7f7f"
            ]
          },
          "type": "nominal",
          "legend": null
        },
        "tooltip": [
          {
            "field": "_id",
            "type": "nominal",
            "title": "Voie d'accès"
          },
          {
            "field": "value",
            "type": "quantitative",
            "title": "Nb publis"
          },
          {
            "field": "Taux",
            "title": "Taux de publication",
            "format": ".1%",
            "type": "quantitative"
          }
        ],
        "size": {
          "value": 50
        }
      }
    },
    {
      "mark": {
        "type": "text",
        "color": "black",
        "fontSize": 10,
        "fontWeight": "bold",
        "baseline": "bottom",
        "align": "center",
        "dx": 0
      },
      "encoding": {
        "x": {
          "field": "_id",
          "type": "nominal",
          "title": "",
          "sort": [
            "diamond",
            "gold",
            "green",
            "bronze",
            "hybrid",
            "closed"
          ]
        },
        "y": {
          "field": "value",
          "type": "quantitative",
          "axis": null
        },
        "text": {
          "field": "value",
          "type": "quantitative"
        }
      }
    }
  ],
  "padding": {
    "left": 0,
    "right": 0,
    "top": 0,
    "bottom": 0
  },
  "config": {
    "view": {
      "strokeWidth": 0
    }
  },
  "width": 600,
  "height": 300
}

Adapter le graphique à vos données

Cette recette peut être utilisée avec d’autres catégories. Les valeurs et les couleurs associées sont définies dans les propriétés domain et range :

"scale": {
  "domain": [
    "Diamond",
    "Gold",
    "Green",
    "Bronze",
    "Hybrid",
    "Closed"
  ],
  "range": [
    "#00bfff",
    "#f9bc01",
    "#228B22",
    "#c97522",
    "#ffff67",
    "#7f7f7f"
  ]
}

Chaque valeur du tableau domain correspond à la couleur située à la même position dans le tableau range. Vous pouvez donc remplacer les catégories et les couleurs par celles adaptées à vos propres données.

Pensez également à adapter l’ordre des catégories défini dans la propriété sort :

"sort": [
  "Diamond",
  "Gold",
  "Green",
  "Bronze",
  "Hybrid",
  "Closed"
]
À savoir : cette recette n’est pas limitée aux voies d’accès à la science ouverte. Elle peut être utilisée avec tout résultat de routine distinct-by produisant des champs _id et value. Vous pouvez ainsi représenter des types de documents, des disciplines, des établissements, des pays ou toute autre catégorie présente dans votre jeu de données.

Graphiques combinés : diagramme à barres et graphique en courbes

Graphique combinant un diagramme à barres et un graphique en courbes

Les graphiques combinés permettent de représenter plusieurs informations dans une même visualisation en associant différents types de représentations.

Dans cet exemple, un diagramme à barres indique le nombre total de publications par année, tandis que plusieurs courbes permettent de visualiser leur répartition selon leur voie d’accès à la science ouverte.

Configurer la routine

Dans cet exemple, les données sont préparées à l’aide de la routine pairing-with.

La routine est appliquée à deux champs :

  • Champ source : l’année de publication ;
  • Champ cible : la voie d’accès à la science ouverte.

Configuration de la routine pairing-with appliquée aux années et aux voies d'accès

La routine produit une structure de données contenant trois champs :

{
  "source": "2023",
  "target": "Gold",
  "weight": 42
}
  • source : la première dimension, ici l’année de publication ;
  • target : la seconde dimension, ici la voie d’accès ;
  • weight : le nombre de publications correspondant à cette combinaison.

Cette structure permet de représenter simultanément le nombre total de publications par année et leur répartition selon les différentes voies d’accès.

Créer le graphique

Le script suivant combine plusieurs couches Vega-Lite. Les barres représentent le nombre de publications par année, tandis que les courbes et leurs points représentent les différentes voies d’accès.

{
  "background": "transparent",
  "padding": 40,
  "width": "container",
  "height": "container",
  "autosize": {
    "type": "fit",
    "contains": "padding"
  },
  "transform": [
    {
      "window": [
        {
          "op": "sum",
          "field": "weight",
          "as": "NbDocsParAnnnées"
        }
      ],
      "groupby": [
        "source"
      ],
      "frame": [
        null,
        null
      ]
    }
  ],
  "layer": [
    {
      "mark": {
        "type": "bar"
      },
      "encoding": {
        "x": {
          "field": "source",
          "type": "nominal",
          "axis": {
            "labelAngle": 0,
            "labelOverlap": true,
            "tickMinStep": 1
          },
          "title": "",
          "sort": null
        },
        "y": {
          "field": "weight",
          "type": "quantitative",
          "axis": {
            "labelAngle": 0,
            "tickMinStep": 1
          },
          "scale": {
            "type": "linear"
          },
          "title": "",
          "sort": null
        },
        "color": {
          "value": "#1f77b4"
        },
        "tooltip": [
          {
            "field": "source",
            "type": "nominal",
            "title": "Années"
          },
          {
            "field": "NbDocsParAnnnées",
            "type": "quantitative",
            "title": "Nombre de publications"
          }
        ],
        "size": {
          "value": 60
        }
      }
    },
    {
      "mark": {
        "type": "line",
        "point": true
      },
      "encoding": {
        "x": {
          "field": "source",
          "type": "nominal",
          "axis": {
            "labelAngle": 0,
            "labelOverlap": true
          },
          "title": "Années",
          "sort": "x"
        },
        "y": {
          "field": "weight",
          "type": "quantitative",
          "axis": {
            "labelAngle": 0
          },
          "scale": {
            "type": "linear"
          },
          "title": "Nombre de documents"
        },
        "color": {
          "field": "target",
          "title": "Voie d'accès",
          "scale": {
            "domain": [
              "Diamond",
              "Gold",
              "Green",
              "Bronze",
              "Hybrid",
              "Closed"
            ],
            "range": [
              "#00bfff",
              "#f9bc01",
              "#228B22",
              "#c97522",
              "#ffff67",
              "#7f7f7f"
            ]
          },
          "type": "nominal"
        },
        "data": {
          "name": "values"
        },
        "tooltip": [
          {
            "field": "NbDocsParAnnnées",
            "title": "N Docs de l'année",
            "type": "quantitative"
          },
          {
            "field": "target",
            "type": "nominal",
            "title": "Voie d'accès"
          },
          {
            "field": "weight",
            "type": "quantitative",
            "title": "Nb Docs Année / Statut OA"
          }
        ],
        "mark": {
          "type": "text",
          "color": "darkblue",
          "fontSize": 10,
          "fontWeight": "bold",
          "baseline": "bottom",
          "align": "center",
          "sort": "target",
          "dx": 0,
          "dy": -5
        },
        "encoding": {
          "x": {
            "field": "source",
            "type": "nominal",
            "title": "",
            "sort": "x"
          },
          "y": {
            "line": {
              "field": "target",
              "type": "quantitative"
            }
          }
        }
      }
    },
    {
      "mark": {
        "type": "text",
        "color": "black",
        "fontSize": 20,
        "fontWeight": "bold",
        "baseline": "bottom",
        "align": "center",
        "dx": 0,
        "dy": -5
      },
      "encoding": {
        "x": {
          "field": "source",
          "type": "nominal",
          "title": "",
          "sort": null
        },
        "y": {
          "field": "NbDocsParAnnnées",
          "type": "quantitative",
          "axis": {
            "labelAngle": 0,
            "tickMinStep": 1
          },
          "scale": {
            "type": "linear"
          },
          "sort": null
        },
        "text": {
          "field": "NbDocsParAnnnées",
          "type": "quantitative"
        }
      }
    }
  ]
}

Adapter le graphique à vos données

Ce modèle peut être adapté en modifiant plusieurs propriétés du script.

Modifier les couleurs

Les catégories et les couleurs des différentes courbes sont définies dans les propriétés domain et range :

"scale": {
  "domain": [
    "Diamond",
    "Gold",
    "Green",
    "Bronze",
    "Hybrid",
    "Closed"
  ],
  "range": [
    "#00bfff",
    "#f9bc01",
    "#228B22",
    "#c97522",
    "#ffff67",
    "#7f7f7f"
  ]
}

Chaque valeur du tableau domain correspond à la couleur située à la même position dans le tableau range.

Modifier la taille des éléments

La largeur des barres peut être modifiée en adaptant la propriété size :

"size": {
  "value": 60
}

La taille des textes peut quant à elle être modifiée à l’aide de la propriété fontSize des marques de type text.

Remplacer les courbes par des points

Il est également possible de supprimer les lignes reliant les points afin d’obtenir un graphique combinant un diagramme à barres et un nuage de points.

Pour cela, remplacez :

"mark": {
  "type": "line",
  "point": true
}

par :

"mark": {
  "type": "circle"
}

Vous obtiendrez alors une variante de type bar chart + scatterplot :

Graphique combinant un diagramme à barres et un nuage de points

À savoir : ce modèle n’est pas limité aux années et aux voies d’accès à la science ouverte. Il peut être adapté à tout résultat de routine pairing-with produisant des champs source, target et weight.

Donut

Donut représentant les voies d'accès à la science ouverte

Le donut permet de représenter la répartition de plusieurs catégories au sein d’un ensemble. L’affichage direct des valeurs sur le graphique facilite la lecture en évitant de devoir consulter uniquement les infobulles.

Dans cet exemple, le graphique permet de visualiser la répartition des publications d’un corpus selon leur voie d’accès à la science ouverte. Chaque voie d’accès est représentée par une couleur différente et le nombre de publications correspondant est affiché directement autour du donut.

Configurer la routine

Dans cet exemple, les données sont préparées à l’aide de la routine distinct-by.

La routine est appliquée au champ contenant les voies d’accès. Elle permet de compter le nombre de publications correspondant à chaque valeur du champ.

Configuration de la routine distinct-by appliquée aux voies d'accès

Elle produit une structure de données contenant deux champs :

{
  "_id": "gold",
  "value": 577
}
  • _id : la catégorie à représenter, ici la voie d’accès ;
  • value : le nombre de publications correspondant à cette catégorie.

Cette structure est directement exploitable par Vega-Lite pour déterminer la taille de chaque portion du donut.

Créer le graphique

Le script suivant construit le donut à partir des valeurs produites par la routine. Il calcule également la proportion représentée par chaque catégorie afin de l’afficher dans l’infobulle sous l’intitulé Taux de publication.

{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "width": "container",
  "height": 450,
  "transform": [
    {
      "joinaggregate": [
        {
          "op": "sum",
          "field": "value",
          "as": "total"
        }
      ]
    },
    {
      "calculate": "datum.value / datum.total",
      "as": "proportion"
    },
    {
      "window": [
        {
          "op": "rank",
          "as": "rank"
        }
      ],
      "sort": [
        {
          "field": "value",
          "order": "descending"
        }
      ]
    },
    {
      "calculate": "datum.rank - 1",
      "as": "rank_minus_1"
    },
    {
      "calculate": "format(datum.value, ',.0f')",
      "as": "texte"
    }
  ],
  "layer": [
    {
      "mark": {
        "type": "arc",
        "innerRadius": 130
      },
      "encoding": {
        "theta": {
          "field": "value",
          "type": "quantitative",
          "stack": true
        },
        "color": {
          "field": "_id",
          "type": "nominal",
          "legend": {
            "orient": "right",
            "title": "Voie d'accès",
            "columns": 1
          },
          "scale": {
            "domain": [
              "gold",
              "unknown",
              "bronze",
              "hybrid",
              "closed"
            ],
            "range": [
              "#FFC40C",
              "#C0C0C0",
              "#CD7F32",
              "#EC5800",
              "#E23D28"
            ]
          }
        },
        "tooltip": [
          {
            "field": "_id",
            "type": "nominal",
            "title": "Voie d'accès"
          },
          {
            "field": "value",
            "type": "quantitative",
            "title": "Nb publis",
            "format": ",.0f"
          },
          {
            "field": "proportion",
            "type": "quantitative",
            "title": "Taux de publication",
            "format": ".1%"
          }
        ],
        "order": {
          "field": "rank_minus_1"
        }
      }
    },
    {
      "mark": {
        "type": "text",
        "fill": "black",
        "radius": 250,
        "fontSize": 14
      },
      "encoding": {
        "theta": {
          "field": "value",
          "type": "quantitative",
          "stack": true
        },
        "text": {
          "field": "texte"
        },
        "color": {
          "field": "_id",
          "type": "nominal"
        },
        "order": {
          "field": "rank_minus_1"
        }
      }
    }
  ],
  "view": {
    "stroke": null
  },
  "background": "transparent"
}

Adapter le graphique à vos données

Les catégories et les couleurs qui leur sont associées sont définies dans les propriétés domain et range :

"scale": {
  "domain": [
    "gold",
    "unknown",
    "bronze",
    "hybrid",
    "closed"
  ],
  "range": [
    "#FFC40C",
    "#C0C0C0",
    "#CD7F32",
    "#EC5800",
    "#E23D28"
  ]
}

Chaque valeur du tableau domain correspond à la couleur située à la même position dans le tableau range. Ces valeurs peuvent être remplacées par les catégories et les couleurs correspondant à votre propre jeu de données.

Modifier la taille du donut

La taille de l’espace central du donut est définie par la propriété innerRadius :

"innerRadius": 130

Une valeur plus élevée agrandit le trou central, tandis qu’une valeur plus faible rapproche le graphique d’un diagramme circulaire classique.

Modifier l’affichage des valeurs

Les valeurs affichées autour du donut sont positionnées à l’aide de la propriété radius de la marque de type text :

"radius": 250,
"fontSize": 14

Vous pouvez modifier radius pour rapprocher ou éloigner les valeurs du centre du graphique et fontSize pour ajuster leur taille.

À savoir : cette recette peut être utilisée avec tout résultat de routine distinct-by produisant des champs _id et value. Elle n’est donc pas limitée aux voies d’accès à la science ouverte.

Graphique en cercles imbriqués

Graphique en cercles imbriqués représentant deux niveaux de catégories

Le graphique en cercles imbriqués (nested ou concentric circles) permet de représenter deux niveaux de catégories dans une même visualisation. Le cercle intérieur présente les catégories principales, tandis que le cercle extérieur détaille les sous-catégories qui leur sont associées.

Par exemple, il peut être utilisé pour représenter la part de publications en Open Access ou non, puis détailler les différentes voies d’ouverture : Diamond, Gold, Green, Bronze, Hybrid ou Closed.

Configurer la routine

Dans cet exemple, les données sont préparées à l’aide de la routine pairing-with.

La routine prend en entrée deux champs :

Configuration de la routine pairing-with pour le graphique en cercles imbriqués

  • Champ source : Open Access, avec par exemple les valeurs Oui et Non.
  • Champ cible : Voie d’ouverture, avec par exemple les valeurs Diamond, Gold, Green, Bronze, Hybrid et Closed.

Elle produit automatiquement une structure de données contenant trois champs :

{
  "source": "Oui",
  "target": "Diamond",
  "weight": 120
}
  • source : la catégorie principale, représentée dans le cercle intérieur.
  • target : la sous-catégorie, représentée dans le cercle extérieur.
  • weight : le nombre de documents correspondant à chaque couple source/target.

Un résultat de routine peut par exemple contenir les données suivantes :

[
  {
    "source": "Oui",
    "target": "Diamond",
    "weight": 120
  },
  {
    "source": "Oui",
    "target": "Gold",
    "weight": 80
  },
  {
    "source": "Oui",
    "target": "Green",
    "weight": 50
  },
  {
    "source": "Oui",
    "target": "Bronze",
    "weight": 30
  },
  {
    "source": "Oui",
    "target": "Hybrid",
    "weight": 20
  },
  {
    "source": "Non",
    "target": "Closed",
    "weight": 70
  }
]

Dans cet exemple, les valeurs Diamond, Gold, Green, Bronze et Hybrid sont rattachées à Oui, tandis que Closed est rattaché à Non.

Cette relation est importante : elle permet d’aligner les portions des deux cercles. La somme des sous-catégories appartenant à une même valeur de source détermine automatiquement la taille de cette catégorie dans le cercle intérieur.

Créer le graphique

Le script suivant construit les deux cercles à partir des relations entre les catégories source et target. La taille des portions est déterminée par le champ weight.

{
  "$schema": "https://vega.github.io/schema/vega-lite/v6.json",
  "background": "transparent",
  "width": "container",
  "height": "container",
  "padding": 20,
  "transform": [
    {
      "calculate": "toNumber(datum.weight)",
      "as": "value"
    },
    {
      "filter": "isValid(datum.value) && datum.value > 0"
    },
    {
      "joinaggregate": [
        {
          "op": "sum",
          "field": "value",
          "as": "grandTotal"
        }
      ]
    },
    {
      "calculate": "datum.value / datum.grandTotal",
      "as": "targetPercent"
    },
    {
      "calculate": "format(datum.targetPercent, '.1%')",
      "as": "targetPercentLabel"
    },
    {
      "calculate": "datum.targetPercent >= 0.04 ? datum.target : ''",
      "as": "targetVisibleLabel"
    },
    {
      "calculate": "datum.targetPercent >= 0.04 ? format(datum.targetPercent, '.1%') : ''",
      "as": "targetVisiblePercent"
    },
    {
      "calculate": "datum.source === 'Oui' ? 1 : datum.source === 'Non' ? 2 : 99",
      "as": "sourceOrder"
    },
    {
      "calculate": "datum.target === 'Diamond' ? 1 : datum.target === 'Gold' ? 2 : datum.target === 'Green' ? 3 : datum.target === 'Bronze' ? 4 : datum.target === 'Hybrid' ? 5 : datum.target === 'Closed' ? 6 : 99",
      "as": "targetOrder"
    }
  ],
  "layer": [
    {
      "transform": [
        {
          "aggregate": [
            {
              "op": "sum",
              "field": "value",
              "as": "sourceTotal"
            },
            {
              "op": "max",
              "field": "grandTotal",
              "as": "grandTotal"
            },
            {
              "op": "min",
              "field": "sourceOrder",
              "as": "sourceOrder"
            }
          ],
          "groupby": [
            "source"
          ]
        },
        {
          "calculate": "datum.sourceTotal / datum.grandTotal",
          "as": "sourcePercent"
        },
        {
          "calculate": "format(datum.sourcePercent, '.1%')",
          "as": "sourcePercentLabel"
        }
      ],
      "mark": {
        "type": "arc",
        "radius": 135,
        "radius2": 80,
        "stroke": "white",
        "strokeWidth": 3,
        "cornerRadius": 3
      },
      "encoding": {
        "theta": {
          "field": "sourceTotal",
          "type": "quantitative",
          "stack": true
        },
        "color": {
          "field": "source",
          "type": "nominal",
          "legend": null,
          "scale": {
            "domain": [
              "Oui",
              "Non"
            ],
            "range": [
              "#4C956C",
              "#E23D28"
            ]
          }
        },
        "order": {
          "field": "sourceOrder",
          "type": "quantitative",
          "sort": "ascending"
        },
        "tooltip": [
          {
            "field": "source",
            "type": "nominal",
            "title": "Accès ouvert"
          },
          {
            "field": "sourceTotal",
            "type": "quantitative",
            "title": "Nombre",
            "format": ",d"
          },
          {
            "field": "sourcePercent",
            "type": "quantitative",
            "title": "Part",
            "format": ".1%"
          }
        ]
      }
    },
    {
      "transform": [
        {
          "aggregate": [
            {
              "op": "sum",
              "field": "value",
              "as": "sourceTotal"
            },
            {
              "op": "max",
              "field": "grandTotal",
              "as": "grandTotal"
            },
            {
              "op": "min",
              "field": "sourceOrder",
              "as": "sourceOrder"
            }
          ],
          "groupby": [
            "source"
          ]
        },
        {
          "calculate": "datum.sourceTotal / datum.grandTotal",
          "as": "sourcePercent"
        },
        {
          "calculate": "format(datum.sourcePercent, '.1%')",
          "as": "sourcePercentLabel"
        }
      ],
      "mark": {
        "type": "text",
        "radius": 107,
        "dy": -8,
        "fontSize": 15,
        "fontWeight": "bold",
        "color": "white"
      },
      "encoding": {
        "theta": {
          "field": "sourceTotal",
          "type": "quantitative",
          "stack": true
        },
        "order": {
          "field": "sourceOrder",
          "type": "quantitative",
          "sort": "ascending"
        },
        "text": {
          "field": "source",
          "type": "nominal"
        }
      }
    },
    {
      "transform": [
        {
          "aggregate": [
            {
              "op": "sum",
              "field": "value",
              "as": "sourceTotal"
            },
            {
              "op": "max",
              "field": "grandTotal",
              "as": "grandTotal"
            },
            {
              "op": "min",
              "field": "sourceOrder",
              "as": "sourceOrder"
            }
          ],
          "groupby": [
            "source"
          ]
        },
        {
          "calculate": "datum.sourceTotal / datum.grandTotal",
          "as": "sourcePercent"
        },
        {
          "calculate": "format(datum.sourcePercent, '.1%')",
          "as": "sourcePercentLabel"
        }
      ],
      "mark": {
        "type": "text",
        "radius": 107,
        "dy": 10,
        "fontSize": 13,
        "fontWeight": "bold",
        "color": "white"
      },
      "encoding": {
        "theta": {
          "field": "sourceTotal",
          "type": "quantitative",
          "stack": true
        },
        "order": {
          "field": "sourceOrder",
          "type": "quantitative",
          "sort": "ascending"
        },
        "text": {
          "field": "sourcePercentLabel",
          "type": "nominal"
        }
      }
    },
    {
      "mark": {
        "type": "arc",
        "radius": 200,
        "radius2": 145,
        "stroke": "white",
        "strokeWidth": 3,
        "cornerRadius": 3
      },
      "encoding": {
        "theta": {
          "field": "value",
          "type": "quantitative",
          "stack": true
        },
        "color": {
          "field": "target",
          "type": "nominal",
          "legend": null,
          "scale": {
            "domain": [
              "Diamond",
              "Gold",
              "Green",
              "Bronze",
              "Hybrid",
              "Closed"
            ],
            "range": [
              "#00BFFF",
              "#FFC40C",
              "#4CBB17",
              "#CD7F32",
              "#EC5800",
              "#E23D28"
            ]
          }
        },
        "order": {
          "field": "targetOrder",
          "type": "quantitative",
          "sort": "ascending"
        },
        "tooltip": [
          {
            "field": "source",
            "type": "nominal",
            "title": "Accès ouvert"
          },
          {
            "field": "target",
            "type": "nominal",
            "title": "Voie"
          },
          {
            "field": "value",
            "type": "quantitative",
            "title": "Nombre",
            "format": ",d"
          },
          {
            "field": "targetPercent",
            "type": "quantitative",
            "title": "Part",
            "format": ".1%"
          }
        ]
      }
    },
    {
      "mark": {
        "type": "text",
        "radius": 172,
        "dy": -8,
        "fontSize": 12,
        "fontWeight": "bold",
        "color": "#111"
      },
      "encoding": {
        "theta": {
          "field": "value",
          "type": "quantitative",
          "stack": true
        },
        "order": {
          "field": "targetOrder",
          "type": "quantitative",
          "sort": "ascending"
        },
        "text": {
          "field": "targetVisibleLabel",
          "type": "nominal"
        }
      }
    },
    {
      "mark": {
        "type": "text",
        "radius": 172,
        "dy": 9,
        "fontSize": 12,
        "fontWeight": "bold",
        "color": "#111"
      },
      "encoding": {
        "theta": {
          "field": "value",
          "type": "quantitative",
          "stack": true
        },
        "order": {
          "field": "targetOrder",
          "type": "quantitative",
          "sort": "ascending"
        },
        "text": {
          "field": "targetVisiblePercent",
          "type": "nominal"
        }
      }
    },
    {
      "transform": [
        {
          "aggregate": [
            {
              "op": "sum",
              "field": "value",
              "as": "total"
            }
          ]
        }
      ],
      "mark": {
        "type": "text",
        "fontSize": 26,
        "fontWeight": "bold",
        "color": "#00284B"
      },
      "encoding": {
        "text": {
          "field": "total",
          "type": "quantitative",
          "format": ",d"
        }
      }
    },
    {
      "mark": {
        "type": "text",
        "fontSize": 13,
        "dy": 25,
        "color": "#555"
      },
      "encoding": {
        "text": {
          "datum": "publications"
        }
      }
    }
  ],
  "resolve": {
    "scale": {
      "color": "independent"
    }
  },
  "config": {
    "view": {
      "stroke": null
    }
  }
}

Adapter le graphique à vos données

Pour utiliser cette recette avec d’autres données, certaines parties du script doivent être adaptées.

L’ordre des catégories principales est défini ici :

{
  "calculate": "datum.source === 'Oui' ? 1 : datum.source === 'Non' ? 2 : 99",
  "as": "sourceOrder"
}

Remplacez Oui et Non par les valeurs présentes dans votre champ source.

L’ordre des sous-catégories est défini ici :

{
  "calculate": "datum.target === 'Diamond' ? 1 : datum.target === 'Gold' ? 2 : datum.target === 'Green' ? 3 : datum.target === 'Bronze' ? 4 : datum.target === 'Hybrid' ? 5 : datum.target === 'Closed' ? 6 : 99",
  "as": "targetOrder"
}

Remplacez ces valeurs par celles présentes dans votre champ target.

Attention : les sous-catégories appartenant à une même catégorie principale doivent être placées les unes à la suite des autres dans targetOrder. L’ordre des groupes doit également correspondre à celui défini dans sourceOrder. C’est ce qui permet de conserver l’alignement entre les deux cercles.

Les couleurs du cercle intérieur sont définies dans :

"scale": {
  "domain": [
    "Oui",
    "Non"
  ],
  "range": [
    "#4C956C",
    "#E23D28"
  ]
}

Les couleurs du cercle extérieur sont définies dans :

"scale": {
  "domain": [
    "Diamond",
    "Gold",
    "Green",
    "Bronze",
    "Hybrid",
    "Closed"
  ],
  "range": [
    "#00BFFF",
    "#FFC40C",
    "#4CBB17",
    "#CD7F32",
    "#EC5800",
    "#E23D28"
  ]
}

Chaque valeur du tableau domain correspond à la couleur située à la même position dans le tableau range.

Affichage des pourcentages

Les pourcentages sont calculés automatiquement à partir du champ weight. Ils sont affichés directement dans les cercles ainsi que dans les infobulles.

Pour éviter de surcharger les petites portions, les libellés du cercle extérieur sont masqués lorsqu’ils représentent moins de 4 % du total :

{
  "calculate": "datum.targetPercent >= 0.04 ? datum.target : ''",
  "as": "targetVisibleLabel"
},
{
  "calculate": "datum.targetPercent >= 0.04 ? format(datum.targetPercent, '.1%') : ''",
  "as": "targetVisiblePercent"
}

Vous pouvez modifier ce seuil. Par exemple, utilisez 0.02 pour afficher les catégories représentant au moins 2 % du total ou 0.05 pour un seuil de 5 %.

À savoir : les petites catégories ne sont pas supprimées du graphique. Seul leur texte est masqué. Elles restent ainsi prises en compte dans le calcul des angles et leurs informations restent accessibles au survol. Il est important de conserver ce fonctionnement : filtrer directement les petites catégories dans les couches de texte décalerait les libellés par rapport aux portions du graphique.

Modifier le texte central

Le total est calculé automatiquement. Le texte affiché sous celui-ci est défini ici :

"text": {
  "datum": "publications"
}

Remplacez publications par le type de ressources représenté, par exemple documents, projets, ressources ou établissements.

À savoir : cette recette n’est pas limitée aux données de science ouverte. Elle fonctionne avec n’importe quel résultat de routine pairing-with produisant des champs source, target et weight, dès lors que les valeurs de target peuvent être rattachées à des catégories source. Elle peut par exemple être utilisée pour représenter des types de documents et leurs sous-types, des catégories de projets et leurs statuts, des disciplines et leurs sous-disciplines ou toute autre organisation hiérarchique à deux niveaux.

Diagramme à barres empilées

Diagramme à barres empilées représentant les voies d'accès à la science ouverte par année

Le diagramme à barres empilées permet de représenter simultanément l’évolution d’un ensemble et sa répartition entre plusieurs catégories.

Dans cet exemple, chaque barre représente le nombre de publications d’une année et les différentes portions qui la composent correspondent aux voies d’accès à la science ouverte : Diamond, Gold, Green, Bronze, Hybrid ou Closed.

Configurer la routine

Dans cet exemple, les données sont préparées à l’aide de la routine pairing-with.

La routine prend en entrée deux champs :

  • Champ source : l’année de publication ;
  • Champ cible : la voie d’accès à la science ouverte.

Configuration de la routine pairing-with appliquée aux années et aux voies d'accès

Elle produit automatiquement une structure de données contenant trois champs :

{
  "source": "2023",
  "target": "Gold",
  "weight": 42
}
  • source : la première dimension, ici l’année de publication ;
  • target : la seconde dimension, ici la voie d’accès ;
  • weight : le nombre de publications correspondant à cette combinaison.

Cette structure permet à Vega-Lite de construire une barre pour chaque année et de la subdiviser en fonction des différentes valeurs de target.

Créer le graphique

Le script suivant construit le diagramme à barres empilées à partir des champs source, target et weight.

Il calcule également le nombre total de publications pour chaque année ainsi que la part représentée par chaque voie d’accès. Cette dernière est stockée dans le champ Taux et affichée dans l’infobulle sous l’intitulé % Publication.

{
  "background": "transparent",
  "width": "container",
  "height": "container",
  "autosize": {
    "type": "fit",
    "contains": "padding"
  },
  "transform": [
    {
      "window": [
        {
          "op": "sum",
          "field": "weight",
          "as": "NbDocsParAnnnées"
        }
      ],
      "groupby": [
        "source"
      ],
      "frame": [
        null,
        null
      ]
    },
    {
      "calculate": "round(datum.weight*1000/datum.NbDocsParAnnnées)/1000",
      "as": "Taux"
    }
  ],
  "layer": [
    {
      "mark": {
        "type": "bar"
      },
      "encoding": {
        "x": {
          "field": "source",
          "type": "nominal",
          "axis": {
            "labelAngle": 0,
            "labelOverlap": true
          },
          "title": "Années",
          "sort": "x"
        },
        "y": {
          "field": "weight",
          "type": "quantitative",
          "axis": {
            "labelAngle": 0
          },
          "scale": {
            "type": "linear"
          },
          "title": "Nombre de documents"
        },
        "color": {
          "field": "target",
          "title": "Voie d'accès",
          "scale": {
            "domain": [
              "Diamond",
              "Gold",
              "Green",
              "Bronze",
              "Hybrid",
              "Closed"
            ],
            "range": [
              "#00bfff",
              "#f9bc01",
              "#228B22",
              "#c97522",
              "#ffff67",
              "#7f7f7f"
            ]
          }
        },
        "tooltip": [
          {
            "field": "NbDocsParAnnnées",
            "title": "N Docs de l'année",
            "type": "quantitative"
          },
          {
            "field": "target",
            "type": "nominal",
            "title": "Voie d'accès"
          },
          {
            "field": "weight",
            "type": "quantitative",
            "title": "Nb Docs Année / Statut OA"
          },
          {
            "field": "Taux",
            "title": "% Publication",
            "format": ".1%",
            "type": "quantitative"
          }
        ],
        "size": {
          "value": 60
        },
        "mark": {
          "type": "text",
          "color": "darkblue",
          "fontSize": 20,
          "fontWeight": "bold",
          "baseline": "bottom",
          "align": "center",
          "sort": "target",
          "dx": 0,
          "dy": -5
        },
        "encoding": {
          "x": {
            "field": "source",
            "type": "nominal",
            "title": "",
            "sort": "x"
          },
          "y": {
            "line": {
              "field": "target",
              "type": "quantitative"
            }
          }
        }
      }
    },
    {
      "mark": {
        "type": "text",
        "color": "black",
        "fontSize": 20,
        "fontWeight": "bold",
        "baseline": "bottom",
        "align": "center",
        "dx": 0,
        "dy": -5
      },
      "encoding": {
        "x": {
          "field": "source",
          "type": "nominal",
          "title": "",
          "sort": null
        },
        "y": {
          "field": "NbDocsParAnnnées",
          "type": "quantitative",
          "axis": {
            "labelAngle": 0,
            "tickMinStep": 1
          },
          "scale": {
            "type": "linear"
          },
          "sort": null
        },
        "text": {
          "field": "NbDocsParAnnnées",
          "type": "quantitative"
        }
      }
    }
  ],
  "data": {
    "name": "values"
  }
}

Adapter le graphique à vos données

Pour utiliser cette recette avec d’autres données, adaptez les champs utilisés par la routine pairing-with. Le champ source détermine les différentes barres du graphique, tandis que le champ target détermine les catégories empilées à l’intérieur de chaque barre.

Modifier les catégories et les couleurs

Les catégories et les couleurs qui leur sont associées sont définies dans les propriétés domain et range :

"scale": {
  "domain": [
    "Diamond",
    "Gold",
    "Green",
    "Bronze",
    "Hybrid",
    "Closed"
  ],
  "range": [
    "#00bfff",
    "#f9bc01",
    "#228B22",
    "#c97522",
    "#ffff67",
    "#7f7f7f"
  ]
}

Chaque valeur du tableau domain correspond à la couleur située à la même position dans le tableau range. Remplacez ces valeurs par les catégories et les couleurs correspondant à votre propre jeu de données.

Modifier les axes

Le champ utilisé sur l’axe horizontal est défini ici :

"x": {
  "field": "source",
  "type": "nominal",
  "title": "Années"
}

Le titre Années peut être remplacé par l’intitulé correspondant à votre champ source.

L’axe vertical représente les effectifs contenus dans le champ weight :

"y": {
  "field": "weight",
  "type": "quantitative",
  "title": "Nombre de documents"
}

Vous pouvez également adapter le titre Nombre de documents au type de ressources représenté.

Modifier les informations affichées au survol

Les informations présentes dans l’infobulle sont définies dans la propriété tooltip. Dans cet exemple, elle affiche le nombre total de documents de l’année, la voie d’accès, le nombre de documents correspondant à cette voie et sa part dans l’ensemble des publications de l’année.

Les intitulés peuvent être adaptés en modifiant les propriétés title.

Modifier la largeur des barres

La largeur des barres est définie par la propriété size :

"size": {
  "value": 60
}

Augmentez cette valeur pour obtenir des barres plus larges ou diminuez-la pour les rendre plus étroites.

À savoir : cette recette n’est pas limitée aux années et aux voies d’accès à la science ouverte. Elle peut être utilisée avec tout résultat de routine pairing-with produisant des champs source, target et weight. Elle permet notamment de comparer la composition de plusieurs groupes, périodes ou catégories.

Graphique en courbe

Graphique en courbe représentant une évolution au cours du temps

Le graphique en courbe est particulièrement adapté pour représenter une évolution au cours du temps ou comparer plusieurs séries de données sur un même axe. Par exemple, il permet de visualiser le nombre de publications par année selon leur statut Open Access.

Configurer la routine

Dans cet exemple, les données sont préparées à l’aide de la routine pairing-with.

La routine prend en entrée deux champs :

  • Champ source : Année de publication.
  • Champ cible : Open Access.

Configuration de la routine pairing-with appliquée à l'année de publication et au statut Open Access

Elle produit automatiquement une structure de données contenant trois champs :

{
  "source": "2018",
  "target": "Ouvert",
  "weight": 42
}
  • source : les valeurs affichées sur l’axe des abscisses, ici les années de publication.
  • target : les différentes séries représentées par une courbe, ici les statuts Open Access.
  • weight : le nombre de documents correspondant à chaque couple source/target.

Cette structure est directement exploitable par Vega-Lite pour tracer une courbe par valeur du champ target.

Créer le graphique

Le script suivant trace une courbe pour chaque valeur du champ target. Les valeurs du champ source sont placées sur l’axe horizontal et les effectifs contenus dans weight sur l’axe vertical.

{
  "background": "transparent",
  "padding": 40,
  "width": "container",
  "height": 400,
  "autosize": {
    "type": "fit",
    "contains": "padding"
  },
  "transform": [
    {
      "filter": "datum.source != null && datum.source != '' && datum.target != null && datum.target != '' && datum.weight > 0"
    }
  ],
  "mark": {
    "type": "line",
    "point": {
      "filled": true,
      "size": 40
    },
    "strokeWidth": 3
  },
  "encoding": {
    "x": {
      "field": "source",
      "type": "ordinal",
      "sort": "ascending",
      "title": "Nom de l'axe des abscisses",
      "axis": {
        "labelAngle": 0,
        "labelOverlap": true
      }
    },
    "y": {
      "aggregate": "sum",
      "field": "weight",
      "type": "quantitative",
      "title": "Nom de l'axe des ordonnées",
      "axis": {
        "format": "d",
        "tickMinStep": 1
      }
    },
    "color": {
      "field": "target",
      "type": "nominal",
      "title": "Légende"
    },
    "tooltip": [
      {
        "field": "source",
        "type": "ordinal",
        "title": "Axe X"
      },
      {
        "field": "target",
        "type": "nominal",
        "title": "Courbe / Ressource"
      },
      {
        "aggregate": "sum",
        "field": "weight",
        "type": "quantitative",
        "title": "Nombre de documents"
      }
    ]
  }
}

Adapter le graphique à vos données

Pour utiliser cette recette avec d’autres données, adaptez tout d’abord les champs utilisés par la routine pairing-with. Le champ source correspond aux valeurs placées sur l’axe horizontal, tandis que chaque valeur du champ target produit une série distincte.

Modifier le titre des axes

Le titre de l’axe horizontal est défini dans :

"x": {
  "field": "source",
  "type": "ordinal",
  "title": "Nom de l'axe des abscisses"
}

Remplacez Nom de l'axe des abscisses par l’intitulé correspondant à votre champ source, par exemple Année de publication.

Le titre de l’axe vertical est défini dans :

"y": {
  "aggregate": "sum",
  "field": "weight",
  "type": "quantitative",
  "title": "Nom de l'axe des ordonnées"
}

Remplacez Nom de l'axe des ordonnées par le type de valeur représenté, par exemple Nombre de publications.

Modifier le titre de la légende

La légende correspond aux différentes valeurs du champ target. Son titre est défini ici :

"color": {
  "field": "target",
  "type": "nominal",
  "title": "Légende"
}

Vous pouvez remplacer Légende par un intitulé adapté à vos données, par exemple Statut Open Access.

Modifier l’apparence des courbes

L’épaisseur des courbes est définie par la propriété strokeWidth :

"strokeWidth": 3

La taille des points affichés sur les courbes est définie ici :

"point": {
  "filled": true,
  "size": 40
}

Vous pouvez augmenter ou diminuer ces valeurs pour adapter l’affichage du graphique.

Modifier les informations affichées au survol

Les informations présentes dans l’infobulle sont définies dans la propriété tooltip. Dans cet exemple, elle affiche la valeur de l’axe horizontal, la série correspondante et le nombre de documents.

Les intitulés peuvent être adaptés en modifiant les propriétés title.

À savoir : cette recette n’est pas limitée aux années de publication et au statut Open Access. Elle fonctionne avec n’importe quel résultat de routine pairing-with produisant des champs source, target et weight. Vous pouvez par exemple représenter une évolution par type de document, discipline, laboratoire, pays ou toute autre catégorie adaptée à votre jeu de données.

Cette recette est issue d’une proposition d’une utilisatrice de LODEX. Merci à elle pour ce partage.

Graphique en aires superposées

Graphique en aires superposées représentant l'évolution du nombre de thèses selon leur statut

Le graphique en aires superposées permet de représenter l’évolution de plusieurs séries sur un même axe tout en donnant une représentation visuelle de leur importance respective.

Contrairement à un graphique en aires empilées, les différentes séries ne sont pas additionnées les unes aux autres : elles sont dessinées sur le même espace et se superposent. L’utilisation de couleurs, de transparence et de styles de lignes différents permet de les distinguer.

Dans cet exemple, le graphique représente l’évolution du nombre de thèses selon leur statut. Les thèses Soutenue sont représentées par une aire bleue et les thèses En cours par une aire violette superposée.

Configurer la routine

Dans cet exemple, les données sont préparées à l’aide de la routine cross-by.

Deux champs sont croisés :

  • Statut de la thèse : par exemple Soutenue ou En cours.
  • Année de référence : l’année utilisée pour représenter l’évolution dans le temps.

La routine produit une structure de données contenant trois champs :

{
  "source": "Soutenue",
  "target": "2023",
  "weight": 50
}
  • source : la série à représenter, ici le statut de la thèse ;
  • target : la valeur placée sur l’axe horizontal, ici l’année de référence ;
  • weight : le nombre de ressources correspondant à chaque couple source/target.

Cette structure permet à Vega-Lite de tracer une aire différente pour chacune des valeurs du champ source.

Créer le graphique

Le script suivant construit deux aires superposées à partir des champs source, target et weight. Chaque série possède son propre style afin de faciliter leur comparaison : couleur, dégradé, ligne, points et transparence.

{
  "$schema": "https://vega.github.io/schema/vega-lite/v6.json",
  "background": "transparent",
  "padding": 35,
  "width": "container",
  "height": "container",
  "autosize": {
    "type": "fit",
    "contains": "padding"
  },
  "transform": [
    {
      "filter": "datum.target != null && datum.target != '' && datum.target != '?'"
    },
    {
      "calculate": "toNumber(datum.target)",
      "as": "year"
    }
  ],
  "encoding": {
    "x": {
      "field": "year",
      "type": "ordinal",
      "title": "Année de référence",
      "sort": "ascending",
      "axis": {
        "labelAngle": 0,
        "labelFontSize": 14,
        "labelFontWeight": "bold",
        "titleFontSize": 16,
        "titleFontWeight": "bold",
        "titlePadding": 15
      }
    },
    "y": {
      "field": "weight",
      "type": "quantitative",
      "title": "Nombre de thèses",
      "axis": {
        "grid": true,
        "labelFontSize": 14,
        "titleFontSize": 16,
        "titleFontWeight": "bold",
        "titlePadding": 15
      }
    },
    "tooltip": [
      {
        "field": "source",
        "type": "nominal",
        "title": "Statut"
      },
      {
        "field": "year",
        "type": "ordinal",
        "title": "Année"
      },
      {
        "field": "weight",
        "type": "quantitative",
        "title": "Nombre de thèses"
      }
    ]
  },
  "layer": [
    {
      "transform": [
        {
          "filter": "datum.source == 'Soutenue'"
        }
      ],
      "mark": {
        "type": "area",
        "interpolate": "monotone",
        "point": {
          "filled": true,
          "size": 75,
          "fill": "#ffeb6e",
          "stroke": "#00284b",
          "strokeWidth": 2
        },
        "line": {
          "color": "#00284b",
          "strokeWidth": 3
        },
        "color": {
          "x1": 1,
          "y1": 1,
          "x2": 1,
          "y2": 0,
          "gradient": "linear",
          "stops": [
            {
              "offset": 0,
              "color": "white"
            },
            {
              "offset": 1,
              "color": "#0798d6"
            }
          ]
        }
      }
    },
    {
      "transform": [
        {
          "filter": "datum.source == 'En cours'"
        }
      ],
      "mark": {
        "type": "area",
        "interpolate": "monotone",
        "point": {
          "filled": true,
          "size": 75,
          "fill": "white",
          "stroke": "#6941eb",
          "strokeWidth": 2
        },
        "line": {
          "color": "#6941eb",
          "strokeWidth": 3,
          "strokeDash": [
            7,
            5
          ]
        },
        "color": {
          "x1": 1,
          "y1": 1,
          "x2": 1,
          "y2": 0,
          "gradient": "linear",
          "stops": [
            {
              "offset": 0,
              "color": "white"
            },
            {
              "offset": 1,
              "color": "#6941eb"
            }
          ]
        },
        "opacity": 0.35
      }
    },
    {
      "mark": {
        "type": "point",
        "opacity": 0
      },
      "encoding": {
        "color": {
          "field": "source",
          "type": "nominal",
          "title": "Statut de la thèse",
          "scale": {
            "domain": [
              "Soutenue",
              "En cours"
            ],
            "range": [
              "#0798d6",
              "#6941eb"
            ]
          },
          "legend": {
            "orient": "top",
            "direction": "horizontal",
            "titleFontSize": 14,
            "titleFontWeight": "bold",
            "labelFontSize": 13,
            "symbolType": "circle",
            "symbolSize": 120
          }
        }
      }
    }
  ]
}

Adapter le graphique à vos données

Pour réutiliser cette recette avec d’autres données, les principales modifications concernent les valeurs du champ source, les axes et l’apparence de chacune des séries.

Choisir les séries à représenter

Chaque aire est créée dans une couche distincte et sélectionnée à l’aide d’un filtre.

La première aire correspond ici aux thèses soutenues :

"transform": [
  {
    "filter": "datum.source == 'Soutenue'"
  }
]

La seconde correspond aux thèses en cours :

"transform": [
  {
    "filter": "datum.source == 'En cours'"
  }
]

Remplacez Soutenue et En cours par les valeurs du champ source que vous souhaitez comparer.

À savoir : seules les valeurs explicitement sélectionnées par ces filtres sont représentées par une aire. Les autres valeurs éventuellement présentes dans le champ source ne seront donc pas affichées.

Adapter l’axe temporel

Les valeurs du champ target correspondent ici à des années. Le script commence par écarter les valeurs absentes ou non exploitables :

{
  "filter": "datum.target != null && datum.target != '' && datum.target != '?'"
}

Elles sont ensuite converties en nombres dans un nouveau champ year :

{
  "calculate": "toNumber(datum.target)",
  "as": "year"
}

Ce champ est ensuite utilisé sur l’axe horizontal :

"x": {
  "field": "year",
  "type": "ordinal",
  "title": "Année de référence",
  "sort": "ascending"
}

Si votre champ target contient également des années, vous pouvez conserver ce fonctionnement et simplement adapter le titre de l’axe.

Modifier le titre des axes

Le titre de l’axe horizontal est défini ici :

"title": "Année de référence"

Celui de l’axe vertical est défini ici :

"title": "Nombre de thèses"

Remplacez ces intitulés par ceux correspondant à vos données.

Modifier les couleurs et les dégradés

Chaque aire peut utiliser son propre dégradé. Pour la série Soutenue, il est défini ici :

"color": {
  "x1": 1,
  "y1": 1,
  "x2": 1,
  "y2": 0,
  "gradient": "linear",
  "stops": [
    {
      "offset": 0,
      "color": "white"
    },
    {
      "offset": 1,
      "color": "#0798d6"
    }
  ]
}

La seconde série utilise le même principe avec la couleur #6941eb.

Vous pouvez remplacer ces couleurs par celles de votre choix. Les deux valeurs définies dans stops déterminent les extrémités du dégradé.

Modifier l’apparence des lignes

La ligne de la série Soutenue est continue :

"line": {
  "color": "#00284b",
  "strokeWidth": 3
}

Celle de la série En cours est en pointillés :

"line": {
  "color": "#6941eb",
  "strokeWidth": 3,
  "strokeDash": [
    7,
    5
  ]
}

La propriété strokeWidth permet de modifier l’épaisseur de la ligne. La propriété strokeDash permet de créer une ligne discontinue.

Modifier les points

Des points sont affichés sur les valeurs de chaque série. Leur apparence est définie dans la propriété point.

Pour la série Soutenue, par exemple :

"point": {
  "filled": true,
  "size": 75,
  "fill": "#ffeb6e",
  "stroke": "#00284b",
  "strokeWidth": 2
}
  • size détermine la taille du point ;
  • fill détermine sa couleur intérieure ;
  • stroke détermine la couleur de son contour ;
  • strokeWidth détermine l’épaisseur du contour.

Modifier la transparence

Afin que les différentes aires restent visibles lorsqu’elles se superposent, la série En cours utilise une transparence :

"opacity": 0.35

Une valeur proche de 0 rend l’aire plus transparente, tandis qu’une valeur proche de 1 la rend plus opaque.

Modifier la forme des courbes

Les deux séries utilisent l’interpolation monotone :

"interpolate": "monotone"

Cette interpolation permet d’obtenir des transitions arrondies entre les différents points.

Modifier la légende

La légende est créée à partir du champ source. Les valeurs et les couleurs associées sont définies ici :

"scale": {
  "domain": [
    "Soutenue",
    "En cours"
  ],
  "range": [
    "#0798d6",
    "#6941eb"
  ]
}

Si vous modifiez les séries représentées, adaptez également les valeurs du tableau domain et les couleurs correspondantes dans range.

Le titre de la légende peut être modifié ici :

"title": "Statut de la thèse"

Modifier les informations affichées au survol

Les infobulles affichent actuellement le statut, l’année et le nombre de thèses :

"tooltip": [
  {
    "field": "source",
    "type": "nominal",
    "title": "Statut"
  },
  {
    "field": "year",
    "type": "ordinal",
    "title": "Année"
  },
  {
    "field": "weight",
    "type": "quantitative",
    "title": "Nombre de thèses"
  }
]

Vous pouvez modifier les propriétés title pour adapter les intitulés au contenu de votre graphique.

À savoir : cette recette n’est pas limitée aux statuts de thèses. Elle peut être utilisée avec tout résultat produisant des champs source, target et weight afin de comparer l’évolution de plusieurs séries sur un même axe. Elle peut par exemple servir à comparer des types de documents, des catégories de projets, des statuts, des établissements ou des disciplines au cours du temps.

Formatage français des dates et des nombres

Vega-Lite permet de convertir l’affichage des dates et des nombres en français en s’appuyant sur une locale adaptée.

En configurant correctement cette locale et en l’utilisant avec les formats appropriés, il est notamment possible d’afficher :

  • les mois et les jours en français ;
  • des dates lisibles, par exemple 01 janvier 2025 ;
  • des nombres conformes aux conventions françaises, par exemple 11 951.

Trois éléments interviennent dans ce formatage : la locale, le type du champ et le format appliqué à son affichage.

Définir une locale française

La première étape consiste à définir une locale. Celle-ci décrit les règles linguistiques et typographiques utilisées lors du formatage, comme les séparateurs numériques ou les noms des mois et des jours.

Dans Vega-Lite, la locale se définit dans la section config.locale de la spécification :

{
  "config": {
    "locale": {
      "number": {
        "decimal": ",",
        "thousands": " ",
        "grouping": [3],
        "currency": ["", " €"],
        "percent": " %"
      },
      "time": {
        "days": [
          "dimanche",
          "lundi",
          "mardi",
          "mercredi",
          "jeudi",
          "vendredi",
          "samedi"
        ],
        "shortDays": [
          "dim.",
          "lun.",
          "mar.",
          "mer.",
          "jeu.",
          "ven.",
          "sam."
        ],
        "months": [
          "janvier",
          "février",
          "mars",
          "avril",
          "mai",
          "juin",
          "juillet",
          "août",
          "septembre",
          "octobre",
          "novembre",
          "décembre"
        ],
        "shortMonths": [
          "janv.",
          "févr.",
          "mars",
          "avr.",
          "mai",
          "juin",
          "juil.",
          "août",
          "sept.",
          "oct.",
          "nov.",
          "déc."
        ]
      }
    }
  }
}

Cette configuration indique à Vega-Lite :

  • quel caractère utiliser comme séparateur décimal et comme séparateur de milliers ;
  • comment afficher les pourcentages et les monnaies ;
  • quels libellés utiliser pour les jours et les mois, en version longue ou abrégée.
À savoir : la locale ne déclenche pas à elle seule l’affichage en français. Elle définit comment les valeurs doivent être formatées. Le type du champ et la propriété format déterminent ensuite où ce formatage doit être appliqué.

Définir correctement le type des données

Pour appliquer le formatage approprié, Vega-Lite doit savoir comment interpréter les données.

Deux types sont particulièrement importants :

  • temporal pour les dates et les heures ;
  • quantitative pour les valeurs numériques.

Dates : utiliser le type temporal

Un champ contenant une date doit être déclaré avec le type temporal :

{
  "field": "date",
  "type": "temporal"
}

Nombres : utiliser le type quantitative

De la même manière, un champ numérique doit être déclaré avec le type quantitative :

{
  "field": "value",
  "type": "quantitative"
}

La locale définit donc les règles de présentation, tandis que le type indique à Vega-Lite quel moteur de formatage utiliser.

Appliquer un format

Une fois la locale définie et le type du champ correctement renseigné, la propriété format permet de préciser la manière dont la valeur doit être affichée.

Formater une date

Les dates utilisent une syntaxe issue de d3-time-format. Pour afficher une date sous la forme 01 janvier 2025, utilisez par exemple :

{
  "axis": {
    "format": "%d %B %Y"
  }
}

Dans ce format :

  • %d correspond au jour du mois sur deux chiffres ;
  • %B correspond au nom du mois en toutes lettres ;
  • %Y correspond à l’année sur quatre chiffres.

Ainsi :

2025-01-01 → 01 janvier 2025

Le nom janvier provient directement de la liste définie dans config.locale.time.months.

Formater un nombre

Le format des nombres utilise la syntaxe de d3-format. Pour afficher un entier avec un séparateur de milliers :

{
  "format": ",d"
}

Dans ce format :

  • , active le groupement des milliers ;
  • d indique que la valeur doit être affichée comme un entier.

Le caractère utilisé comme séparateur de milliers est ensuite déterminé par la locale française :

1250000 → 1 250 000

Exemple : afficher des dates en français

Exemple de graphique LODEX avec des dates affichées en français

Dans cet exemple, les valeurs du champ _id sont converties en dates, puis affichées avec le nom du mois en français.

{
  "background": "transparent",
  "mark": {
    "type": "bar"
  },
  "transform": [
    {
      "calculate": "toDate(datum._id)",
      "as": "date"
    }
  ],
  "encoding": {
    "x": {
      "field": "date",
      "type": "temporal",
      "title": "",
      "axis": {
        "labelAngle": 0,
        "format": "%d %B %Y"
      }
    },
    "y": {
      "field": "value",
      "type": "quantitative",
      "title": "",
      "axis": {
        "labelAngle": 0,
        "format": ",d"
      },
      "scale": {
        "type": "linear"
      }
    },
    "tooltip": [
      {
        "field": "date",
        "type": "temporal",
        "title": "Date",
        "format": "%d %B %Y"
      },
      {
        "field": "value",
        "type": "quantitative",
        "title": "Nombre",
        "format": ",d"
      }
    ]
  },
  "padding": 18,
  "width": "container",
  "height": "container",
  "autosize": {
    "type": "fit",
    "contains": "padding"
  },
  "config": {
    "view": {
      "strokeWidth": 0
    },
    "locale": {
      "number": {
        "decimal": ",",
        "thousands": " ",
        "grouping": [3],
        "currency": ["", " €"],
        "percent": " %"
      },
      "time": {
        "dateTime": "%A %e %B %Y, %X",
        "date": "%d/%m/%Y",
        "time": "%H:%M:%S",
        "periods": ["AM", "PM"],
        "days": [
          "dimanche",
          "lundi",
          "mardi",
          "mercredi",
          "jeudi",
          "vendredi",
          "samedi"
        ],
        "shortDays": [
          "dim.",
          "lun.",
          "mar.",
          "mer.",
          "jeu.",
          "ven.",
          "sam."
        ],
        "months": [
          "janvier",
          "février",
          "mars",
          "avril",
          "mai",
          "juin",
          "juillet",
          "août",
          "septembre",
          "octobre",
          "novembre",
          "décembre"
        ],
        "shortMonths": [
          "janv.",
          "févr.",
          "mars",
          "avr.",
          "mai",
          "juin",
          "juil.",
          "août",
          "sept.",
          "oct.",
          "nov.",
          "déc."
        ]
      }
    }
  }
}

Trois éléments sont particulièrement importants dans cet exemple :

  • toDate(datum._id) convertit la valeur d’origine en date exploitable par Vega-Lite ;
  • "type": "temporal" indique qu’il s’agit d’une donnée temporelle ;
  • "format": "%d %B %Y" applique le format souhaité en utilisant les mois définis dans la locale française.
À savoir : le format doit être appliqué séparément aux différents emplacements où la date est affichée. Dans cet exemple, il est donc présent à la fois dans axis et dans tooltip.

Exemple : afficher des nombres au format français

Exemple de graphique LODEX avec des nombres affichés selon les conventions françaises

Dans cet exemple, les valeurs numériques sont affichées avec un séparateur de milliers conforme à la locale française.

{
  "params": [
    {
      "name": "highlight",
      "select": {
        "type": "point",
        "on": "pointerover"
      }
    },
    {
      "name": "select",
      "select": "point",
      "value": null
    }
  ],
  "background": "transparent",
  "mark": {
    "type": "bar",
    "cursor": "pointer",
    "stroke": "black"
  },
  "encoding": {
    "x": {
      "field": "value",
      "type": "quantitative",
      "axis": {
        "labelAngle": 0,
        "tickMinStep": 1,
        "format": ",d"
      },
      "scale": {
        "type": "linear"
      },
      "title": "",
      "sort": null
    },
    "y": {
      "field": "_id",
      "type": "nominal",
      "axis": {
        "labelAngle": 0,
        "tickMinStep": 1
      },
      "title": "",
      "sort": null
    },
    "color": {
      "field": "_id",
      "scale": {
        "range": ["#AB8EE2"]
      },
      "type": "nominal",
      "legend": null
    },
    "fillOpacity": {
      "condition": [
        {
          "param": "select",
          "value": 1
        },
        {
          "param": "highlight",
          "empty": false,
          "value": 1
        }
      ],
      "value": 0.3
    },
    "strokeWidth": {
      "condition": [
        {
          "param": "select",
          "empty": false,
          "value": 2
        },
        {
          "param": "highlight",
          "empty": false,
          "value": 1
        }
      ],
      "value": 0
    },
    "tooltip": [
      {
        "field": "_id",
        "type": "nominal",
        "title": "Catégorie"
      },
      {
        "field": "value",
        "type": "quantitative",
        "title": "Valeur",
        "format": ",d"
      }
    ]
  },
  "padding": 18,
  "width": "container",
  "height": {
    "step": "30"
  },
  "autosize": {
    "type": "fit",
    "contains": "padding"
  },
  "config": {
    "locale": {
      "number": {
        "decimal": ",",
        "thousands": " ",
        "grouping": [3],
        "currency": ["", " €"],
        "percent": " %"
      }
    },
    "view": {
      "strokeWidth": 0
    }
  }
}

Ici, le champ value est déclaré en quantitative et le format ,d est appliqué à l’axe :

"axis": {
  "labelAngle": 0,
  "tickMinStep": 1,
  "format": ",d"
}

Le même format est également appliqué dans l’infobulle :

{
  "field": "value",
  "type": "quantitative",
  "title": "Valeur",
  "format": ",d"
}
À savoir : la configuration de la locale peut être réutilisée dans n’importe quel graphique Vega-Lite. Il n’est donc pas nécessaire d’utiliser les graphiques présentés dans ces exemples : vous pouvez intégrer les blocs config.locale et les propriétés format dans vos propres visualisations.

Ajouter un sélecteur à un graphique Vega-Lite

Selecteur VegaLite

Un graphique peut contenir plusieurs catégories de données que l’on souhaite pouvoir afficher séparément, sans avoir à créer plusieurs visualisations.

Vega-Lite permet d’ajouter un sélecteur afin que l’utilisateur choisisse les données qu’il souhaite afficher.

Dans cet exemple, nous partons d’un diagramme en barres et souhaitons proposer trois choix :

  • positif
  • négatif
  • ni positif ni négatif

Nous partons de ce diagramme en barres classique :


{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "background": "transparent",
  "transform": [
    {"filter": "datum._id != 'non concerné'"},
    {"joinaggregate": [{"op": "sum", "field": "value", "as": "total"}]},
    {"calculate": "datum.value / datum.total * 100", "as": "pourcentage"}
  ],
  "layer": [
    {
      "mark": {"type": "bar"},
      "encoding": {
        "x": {
          "field": "value",
          "type": "quantitative",
          "axis": {"labelAngle": 0, "tickMinStep": 1},
          "scale": {"type": "linear"},
          "title": "",
          "sort": null
        },
        "y": {
          "field": "_id",
          "type": "nominal",
          "axis": {"labelAngle": 0, "tickMinStep": 1},
          "title": "",
          "sort": null
        },
        "color": {
          "field": "_id",
          "scale": {
            "range": ["#d7191c", "#fdae61", "#ffffbf", "#abdda4", "#2b83ba"]
          },
          "type": "nominal",
          "legend": null
        },
        "tooltip": [
          {"field": "_id", "type": "nominal", "title": "Catégorie"},
          {"field": "value", "type": "quantitative", "title": "Valeur"},
          {"field": "total", "type": "quantitative", "title": "Sur"},
          {
            "field": "pourcentage",
            "type": "quantitative",
            "format": ".0f",
            "title": "Pourcentage (%)"
          }
        ]
      }
    },
    {
      "mark": {
        "type": "text",
        "color": "black",
        "fontSize": 10,
        "fontWeight": "bold",
        "baseline": "middle",
        "align": "left",
        "dx": 4,
        "dy": 0
      },
      "encoding": {
        "x": {
          "field": "value",
          "type": "quantitative",
          "axis": null,
          "sort": null
        },
        "y": {"field": "_id", "type": "nominal", "title": "", "sort": null},
        "text": {"field": "value", "type": "quantitative"}
      }
    }
  ],
  "padding": 18,
  "config": {"view": {"strokeWidth": 0}},
  "width": "container",
  "height": {"step": 20},
  "autosize": {"type": "fit", "contains": "padding"},
  "data": {
    "values": [
      {
        "_id": "programme positif",
        "value": 28
      },
      {
        "_id": "international positif",
        "value": 18
      },
      {
        "_id": "financement positif",
        "value": 12
      },
      {
        "_id": "reconnaissance positif",
        "value": 8
      },

      {
        "_id": "programme négatif",
        "value": 15
      },
      {
        "_id": "international négatif",
        "value": 10
      },
      {
        "_id": "financement négatif",
        "value": 21
      },
      {
        "_id": "reconnaissance négatif",
        "value": 14
      },

      {
        "_id": "programme ni positif ni négatif",
        "value": 40
      },
      {
        "_id": "international ni positif ni négatif",
        "value": 32
      },
      {
        "_id": "financement ni positif ni négatif",
        "value": 25
      },
      {
        "_id": "reconnaissance ni positif ni négatif",
        "value": 16
      }
    ]
  }
}

ℹ️Le principe présenté ici n’est toutefois pas spécifique aux diagrammes en barres. Le sélecteur agit sur les données avant leur représentation et peut donc être utilisé avec d’autres types de graphiques Vega-Lite.

Préparer les données utilisées par le graphique

Pour pouvoir utiliser le sélecteur, chaque valeur doit contenir à la fois :

  • le libellé qui sera utilisé dans le graphique
  • la catégorie qui permettra de filtrer les données.

Une solution consiste à regrouper ces deux informations dans une même chaîne en utilisant un séparateur.

Dans notre exemple, nous utilisons :

programme : positif
programme : négatif
international : positif
international : négatif
financement : ni positif ni négatif

La structure est donc toujours la même :

libellé : catégorie

La partie située avant : correspond au libellé qui sera utilisé par le graphique.
La partie située après : correspond à la catégorie utilisée par le sélecteur.

Préparer ses données
Selon la structure du corpus d’origine, il peut être nécessaire de transformer les données avant de créer le graphique.
Par exemple, une valeur telle que Financement Négatif peut être transformée en
Financement : négatif.

Cette préparation permet ensuite à Vega-Lite de distinguer facilement le libellé de la catégorie utilisée par le sélecteur.

Un exemple de script Lodash permettant de réaliser cette transformation est disponible ici :

Structurer une chaîne de caractères pour distinguer un libellé et une catégorie

Après application de l’enrichissement, puis de la routine, les données transmises au graphique ont cette forme :

[
  {
    "_id": "programme : positif",
    "value": 28
  },
  {
    "_id": "international : positif",
    "value": 18
  },
  {
    "_id": "programme : négatif",
    "value": 15
  },
  {
    "_id": "international : négatif",
    "value": 10
  }
]

La propriété _id contient les deux informations nécessaires tandis que value contient le nombre d’occurrences calculé par la routine.

Ajouter le sélecteur

Le sélecteur est déclaré dans la propriété params de la spécification Vega-Lite.

"params": [
  {
    "name": "selectcategorie",
    "value": "positif",
    "bind": {
      "input": "select",
      "options": [
        "positif",
        "négatif",
        "ni positif ni négatif"
      ],
      "name": "Afficher : "
    }
  }
]

Ce bloc crée une liste déroulante permettant de sélectionner la catégorie à afficher.

Nommer le sélecteur

"name": "selectcategorie"

La propriété name définit le nom interne du paramètre.

Ce nom sera ensuite utilisé dans le filtre afin de récupérer la valeur actuellement choisie par l’utilisateur.

Définir la valeur sélectionnée par défaut

"value": "positif"

La propriété value définit la valeur sélectionnée lors du premier affichage du graphique.

Dans cet exemple, le graphique affiche donc initialement les données appartenant à la catégorie positif.

Créer une liste déroulante

"bind": {
  "input": "select"
}

La propriété bind permet d’associer le paramètre à un élément d’interface.

Avec "input": "select", Vega-Lite crée une liste déroulante.

Définir les valeurs proposées

"options": [
  "positif",
  "négatif",
  "ni positif ni négatif"
]

La propriété options contient les différentes valeurs proposées dans le sélecteur.

Ces valeurs doivent correspondre exactement aux catégories présentes dans les données.

À modifier
Adaptez la propriété options aux catégories présentes dans votre propre corpus.

Par exemple :

"options": [
  "article",
  "ouvrage",
  "chapitre"
]

ou :

"options": [
  "2024",
  "2025",
  "2026"
]

Séparer le libellé et la catégorie dans Vega-Lite

Les données reçues par Vega-Lite contiennent actuellement une chaîne unique :

international : positif

Nous devons récupérer séparément :

libelle    → international
categorie  → positif

Pour cela, deux transformations sont ajoutées :

{
  "calculate": "split(datum._id, ' : ')[0]",
  "as": "libelle"
},
{
  "calculate": "split(datum._id, ' : ')[1]",
  "as": "categorie"
}

La fonction split découpe la chaîne en utilisant : comme séparateur.

Ainsi :

international : positif

est découpé conceptuellement de cette manière :

["international", "positif"]

Le premier élément est récupéré avec [0] et enregistré dans un nouveau champ appelé libelle.

Le second élément est récupéré avec [1] et enregistré dans un nouveau champ appelé categorie.

international : positif
       │             │
       ▼             ▼
    libelle       categorie
       │             │
       ▼             ▼
international      positif

À modifier
Si vous utilisez un autre séparateur dans vos données, remplacez ' : ' dans les deux expressions split.

Relier le sélecteur aux données

Nous disposons maintenant :

  • d’un champ categorie dans les données ;
  • d’un paramètre selectcategorie contenant le choix effectué par l’utilisateur.

Il suffit alors de comparer les deux valeurs avec un filtre :

{
  "filter": "datum.categorie == selectcategorie"
}

Pour chaque donnée, Vega-Lite compare donc :

datum.categorie

avec :

selectcategorie

Si l’utilisateur choisit négatif, seules les données dont la catégorie vaut négatif sont conservées.

programme : positif       ✕
international : positif   ✕

programme : négatif       ✓
international : négatif   ✓
financement : négatif     ✓

Le graphique est automatiquement mis à jour lorsque l’utilisateur modifie la valeur du sélecteur.

Utiliser le nouveau libellé dans le graphique

Dans le graphique initial, la propriété _id pouvait être utilisée directement comme libellé.

Mais _id contient désormais :

programme : positif

Nous souhaitons uniquement afficher :

programme

Il faut donc utiliser le nouveau champ libelle dans l’encodage du graphique :

"y": {
  "field": "libelle",
  "type": "nominal",
  "axis": {
    "labelAngle": 0,
    "tickMinStep": 1
  },
  "title": "",
  "sort": null
}

Le même principe peut être appliqué partout où le libellé doit être utilisé, par exemple pour la couleur :

"color": {
  "field": "libelle",
  "scale": {
    "range": [
      "#d7191c",
      "#fdae61",
      "#ffffbf",
      "#abdda4",
      "#2b83ba"
    ]
  },
  "type": "nominal",
  "legend": null
}

Adapter l’infobulle

Les deux informations peuvent également être affichées séparément dans l’infobulle.

"tooltip": [
  {
    "field": "libelle",
    "type": "nominal",
    "title": "Libellé"
  },
  {
    "field": "categorie",
    "type": "nominal",
    "title": "Catégorie"
  },
  {
    "field": "value",
    "type": "quantitative",
    "title": "Valeur"
  },
  {
    "field": "total",
    "type": "quantitative",
    "title": "Sur"
  },
  {
    "field": "pourcentage",
    "type": "quantitative",
    "format": ".0f",
    "title": "Pourcentage (%)"
  }
]

Pour une donnée telle que :

financement : négatif

l’infobulle peut ainsi présenter séparément le libellé, la catégorie et la valeur.

Calculer le total après le filtrage

Dans notre exemple, le graphique calcule également un total et un pourcentage.

Le bloc complet des transformations est :

"transform": [
  {
    "calculate": "split(datum._id, ' : ')[0]",
    "as": "libelle"
  },
  {
    "calculate": "split(datum._id, ' : ')[1]",
    "as": "categorie"
  },
  {
    "filter": "datum.categorie == selectcategorie"
  },
  {
    "joinaggregate": [
      {
        "op": "sum",
        "field": "value",
        "as": "total"
      }
    ]
  },
  {
    "calculate": "datum.value / datum.total * 100",
    "as": "pourcentage"
  }
]

L’ordre des transformations est important.

Séparation du libellé et de la catégorie
                    ↓
          choix dans le sélecteur
                    ↓
              filtrage
                    ↓
      calcul du total sélectionné
                    ↓
        calcul des pourcentages
                    ↓
         affichage du graphique

Le filtre est appliqué avant le calcul du total.

Si l’utilisateur sélectionne positif, le total est donc calculé uniquement à partir des valeurs positives.
S’il sélectionne négatif, le total est recalculé à partir des valeurs négatives.

Le code complet du graphique avec sélecteur


{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "background": "transparent",

  "params": [
    {
      "name": "selectcategorie",
      "value": "positif",
      "bind": {
        "input": "select",
        "options": [
          "positif",
          "négatif",
          "ni positif ni négatif"
        ],
        "name": "Afficher : "
      }
    }
  ],

  "transform": [
    {
      "calculate": "split(datum._id, ' : ')[0]",
      "as": "libelle"
    },
    {
      "calculate": "split(datum._id, ' : ')[1]",
      "as": "categorie"
    },
    {
      "filter": "datum.categorie == selectcategorie"
    },
    {
      "joinaggregate": [
        {
          "op": "sum",
          "field": "value",
          "as": "total"
        }
      ]
    },
    {
      "calculate": "datum.value / datum.total * 100",
      "as": "pourcentage"
    }
  ],

  "layer": [
    {
      "mark": {
        "type": "bar"
      },
      "encoding": {
        "x": {
          "field": "value",
          "type": "quantitative",
          "axis": {
            "labelAngle": 0,
            "tickMinStep": 1
          },
          "scale": {
            "type": "linear"
          },
          "title": "",
          "sort": null
        },
        "y": {
          "field": "libelle",
          "type": "nominal",
          "axis": {
            "labelAngle": 0,
            "tickMinStep": 1
          },
          "title": "",
          "sort": null
        },
        "color": {
          "field": "libelle",
          "scale": {
            "range": [
              "#d7191c",
              "#fdae61",
              "#ffffbf",
              "#abdda4",
              "#2b83ba"
            ]
          },
          "type": "nominal",
          "legend": null
        },
        "tooltip": [
          {
            "field": "libelle",
            "type": "nominal",
            "title": "Catégorie"
          },
          {
            "field": "categorie",
            "type": "nominal",
            "title": "Catégorie"
          },
          {
            "field": "value",
            "type": "quantitative",
            "title": "Valeur"
          },
          {
            "field": "total",
            "type": "quantitative",
            "title": "Sur"
          },
          {
            "field": "pourcentage",
            "type": "quantitative",
            "format": ".0f",
            "title": "Pourcentage (%)"
          }
        ]
      }
    },

    {
      "mark": {
        "type": "text",
        "color": "black",
        "fontSize": 10,
        "fontWeight": "bold",
        "baseline": "middle",
        "align": "left",
        "dx": 4,
        "dy": 0
      },
      "encoding": {
        "x": {
          "field": "value",
          "type": "quantitative",
          "axis": null,
          "sort": null
        },
        "y": {
          "field": "libelle",
          "type": "nominal",
          "title": "",
          "sort": null
        },
        "text": {
          "field": "value",
          "type": "quantitative"
        }
      }
    }
  ],

  "padding": 18,

  "config": {
    "view": {
      "strokeWidth": 0
    }
  },

  "width": "container",

  "height": {
    "step": 20
  },

  "autosize": {
    "type": "fit",
    "contains": "padding"
  },

  "data": {
    "values": [
      {
        "_id": "programme : positif",
        "value": 28
      },
      {
        "_id": "international : positif",
        "value": 18
      },
      {
        "_id": "financement : positif",
        "value": 12
      },
      {
        "_id": "reconnaissance : positif",
        "value": 8
      },

      {
        "_id": "programme : négatif",
        "value": 15
      },
      {
        "_id": "international : négatif",
        "value": 10
      },
      {
        "_id": "financement : négatif",
        "value": 21
      },
      {
        "_id": "reconnaissance : négatif",
        "value": 14
      },

      {
        "_id": "programme : ni positif ni négatif",
        "value": 40
      },
      {
        "_id": "international : ni positif ni négatif",
        "value": 32
      },
      {
        "_id": "financement : ni positif ni négatif",
        "value": 25
      },
      {
        "_id": "reconnaissance : ni positif ni négatif",
        "value": 16
      }
    ]
  }
}

Utiliser un sélecteur avec d’autres types de graphiques

Dans cet exemple, le sélecteur est utilisé avec un diagramme en barres, mais son fonctionnement n’est pas lié à ce type de représentation.

Le sélecteur intervient dans les transformations Vega-Lite :

{
  "filter": "datum.categorie == selectcategorie"
}

Le filtrage est donc effectué sur les données avant leur représentation graphique.

Données
   ↓
Sélection d'une catégorie
   ↓
Filtrage des données
   ↓
Transformations éventuelles
   ↓
Représentation graphique

Le même mécanisme peut ainsi être utilisé avec différents types de visualisations Vega-Lite : courbes, graphiques en aires, nuages de points, donuts ou encore graphiques combinés.

Par exemple, des données structurées ainsi :

2024 : France
2025 : France
2026 : France

2024 : Allemagne
2025 : Allemagne
2026 : Allemagne

pourraient utiliser la première partie comme information représentée sur l’axe temporel et la seconde comme catégorie permettant de sélectionner le pays.

Le principe reste le même :

split(...)[0] → information utilisée par le graphique
split(...)[1] → information utilisée par le sélecteur

Seuls les champs utilisés dans les propriétés encoding doivent ensuite être adaptés au type de représentation choisi.

À retenir
Le sélecteur est indépendant du type de graphique. Il filtre les données transmises à la visualisation. Une fois le filtre appliqué, Vega-Lite construit le graphique avec les seules données correspondant au choix effectué par l’utilisateur.

Les trois modifications essentielles

Pour ajouter un sélecteur à un graphique existant, trois modifications principales sont nécessaires.

1. Ajouter le sélecteur

"params": [
  {
    "name": "selectcategorie",
    "value": "positif",
    "bind": {
      "input": "select",
      "options": [
        "positif",
        "négatif",
        "ni positif ni négatif"
      ],
      "name": "Afficher : "
    }
  }
]

2. Créer les champs et appliquer le filtre

{
  "calculate": "split(datum._id, ' : ')[0]",
  "as": "libelle"
},
{
  "calculate": "split(datum._id, ' : ')[1]",
  "as": "categorie"
},
{
  "filter": "datum.categorie == selectcategorie"
}

3. Utiliser le nouveau champ pour l’affichage

Dans les différentes parties du graphique qui doivent afficher le libellé, utilisez :

"field": "libelle"

à la place de :

"field": "_id"

Principe général

Cette méthode ne se limite pas aux catégories positif et négatif.

Elle repose simplement sur des données structurées selon le modèle :

libellé : catégorie

Par exemple :

Article scientifique : français
Article scientifique : anglais

Université de Lorraine : 2025
Université de Lorraine : 2026

Publication : ouverte
Publication : fermée

Dans tous les cas, Vega-Lite peut récupérer séparément les deux informations :

split(...)[0] → information utilisée dans le graphique
split(...)[1] → information utilisée par le sélecteur

À retenir
Lorsque plusieurs informations sont regroupées dans une chaîne structurée, par exemple libellé : catégorie, la fonction split permet de les séparer directement dans Vega-Lite. L’une peut alors servir à construire la visualisation et l’autre à filtrer les données grâce à un sélecteur.

Comment pouvons-nous aider ?