Atelier Quarto - Exercice 3

Observable JavaScript avec WidgetHTML : tableau, graphe, carte, inline code

L’exemple qui suit est complexe : il va falloir charger un csv avec des données au “format long” et les manipuler avec duckdb et un peu de javascript pour gérer l’interactivité !

La doc duckdb pour ojs

à compléter

Chargement du csv et tableau

Ici les données ont été préparées au “format long”, c’est un format très pratique pour simplement faire des filtres par la suite et pouvoir représenter des “séries” de données.
La donnée est complexe : elle comprend à la queuleuleue les données des quartiers mais aussi les données des quartiers agrégées à la commune, département, région.

Duckdb est intégré à observable javascript, dans cet exemple on ne charge aucune librairie !

Afficher le code
db = DuckDBClient.of({
  donnees_combined : FileAttachment("data/donnees_combined.csv")
})

myData = db.sql`SELECT * FROM donnees_combined`
Inputs.table(myData)

Filtre pour sélectionner les indicateurs tranche d’âge

Un simple filtre permet de sélectionner tous les indicateurs relatifs aux tranches d’âge.

Afficher le code
myDataTrancheAge = db.sql`SELECT * FROM donnees_combined where indicateur ilike 'ind%ans%'`
// Afficher la donnée
Inputs.table(myDataTrancheAge)

Ensuite, OJS contient tous les input de base, voici un exemple avec un slider

Afficher le code
viewof myQuartier = Inputs.range([1, 342], {step: 1, label: "Choisir un quartier"})

On peut lier cet input à un filtre. Dès que l’on change la valeur du input, cela recalcule tous les blocs de code qui contiennent myQuartier.

Afficher le code
myDataQuartier = db.sql`
  SELECT * FROM donnees_combined where id=${myQuartier} and echelle = 'recoquartier' and indicateur ilike 'ind%ans%'
`

// Afficher la donnée
Inputs.table(myDataQuartier)

Et pour avoir un graphe interactif, il suffit de prendre entrée la variable filtrée selon le input.

Afficher le code
Plot.plot({
  marginLeft: 45,
  color: {legend: true },
  marks: [
    Plot.barX(
      myDataQuartier,
        { x: "valeur" , y : "id", fill: "indicateur"}
    )
  ]
})

Exemple plus complet de texte, tableau et graphe

Préparation des données pour le Inputs.select

Au préalable, on prépare des données sur mesure pour notre Input pour choisir un quartier et on formate ça aux petits oignons : selection de colonnes, filtre, pivot.

Afficher le code
dataSelectQ = db.sql`
  SELECT 
      id,
      MIN(CASE WHEN indicateur = 'commune' THEN valeur END) AS commune,
      MIN(CASE WHEN indicateur = 'nom_iris' THEN valeur END) AS nom_iris
  FROM donnees_combined
  WHERE indicateur IN ('commune', 'nom_iris')
  GROUP BY id;
`

Inputs.table(dataSelectQ)

Données quartier avec référent régional, stackBar

Afficher le code
// Le menu déroulant
viewof Q = Inputs.select(dataSelectQ, {label: "Choisir un quartier", format: d => `n° ${d.id}-${d.commune} (${d.nom_iris})` })

Vous avez sélectionner le quartier 🏢 n° situé sur la commune de (IRIS : )

Les données étant au format long, il suffit de filtrer :

  • selon l’identifiant du quartier
  • selon la valeur de l’échelle géographique (avec un ou qui s’écrit || en javascript)
  • selon les libellés des indicateurs que l’on veut représenter
Afficher le code
myDataGrapheStackBar = db.sql`
  SELECT echelle, indicateur, valeur 
  FROM  donnees_combined 
  WHERE 
    indicateur ilike '%ind%ans%'
    and
    (
      (id = ${Q.id} and echelle='recoquartier' )
        or
      echelle = 'multi_recoquartiers_region'
    )
`
/*
myDataGrapheStackBar = myData
                  .filter( aq.escape(d => (d.id== Q.id && d.echelle == 'recoquartier') || d.echelle == 'multi_recoquartiers_region'  ) )
                  .filter( d => op.includes(d.indicateur, '_ans') )
                  .select('echelle','indicateur','valeur')
*/
// Un tableau sur 2 colonnes en faisant un pivot à part (plus compliqué et moins lisible qu'avec arquero...)
myData2columns = db.sql`
  SELECT indicateur,
          MIN(CASE WHEN echelle = 'multi_recoquartiers_region' THEN valeur END) as multi_recoquartiers_region, 
          MIN(CASE WHEN echelle = 'recoquartier' THEN valeur END) as recoquartier
  FROM  donnees_combined 
  WHERE 
    indicateur ilike '%ind%ans%'
    and
    (
      (id = ${Q.id} and echelle='recoquartier' )
        or
      echelle = 'multi_recoquartiers_region'
    )
  group by indicateur
`

Inputs.table(
  myData2columns, {
  header: {
    multi_recoquartiers_region: "Ensemble des RecoQ",
    recoquartier: `RecoQ n° ${Q.id}`
  },
  align: {
    multi_recoquartiers_region: "center",
    recoquartier: "center"
  }
})

La librairie plot permet de créer la plupart des graphes. Très élégante dans sa conception, elle est un peu difficile à prendre en main… mais une fois la logique comprise elle permet de faire des graphes sur mesure complètement responsive design.

Afficher le code
// Des stack bar série               
Plot.plot({
  marginLeft: 45,
  marginBottom: 60,
  marginTop: 60,
  style: {fontSize: "25px"},
  color: {legend: true },
  marks: [
    Plot.barY(
      myDataGrapheStackBar,
        { x : "echelle" ,y: "valeur" , fill: "indicateur", offset: "normalize", tip: true}
    )
  ]
})

Boîtes à moustache + symboles

Afficher le code
myDataBoxPlot = db.sql`
SELECT id, echelle, indicateur, valeur 
  FROM  donnees_combined 
  WHERE 
    indicateur ilike '%ind%ans%'
    and
    echelle='recoquartier'
`
// valeur du quartier sélectionné
myDataBoxPlotQ = db.sql`
SELECT id, echelle, indicateur, valeur
  FROM  donnees_combined 
  WHERE 
    id = ${Q.id}
    and
    indicateur ilike '%ind%ans%'
    and
    echelle='recoquartier'
`

//Inputs.table(myDataBoxPlotQ)

// Comme avec arquero, le graphe en boxplot ne reconnaît pas directement que valeur est un nombre, on lui force la main avec d => parseFloat(d.valeur) au lieu de simplement "valeur"

Plot.plot({
  caption: "Les boîtes à moustache représentent la dispersion des valeurs de l'ensemble des quartiers par tranche d'âge, les symboles représentent les valeurs du quartier sélectionné",
  y: {
    grid: true
  },
  color: {legend:true, style: {fontSize: "14px"}},
  symbol: {legend: true, style: {fontSize: "14px"}},
  style: {fontSize: "14px"},
  marginBottom: 40,
  marginTop: 20,
  marks: [
    // boîte à moustache en série pour réprésenter l'ensemble des quartiers
    Plot.boxY(myDataBoxPlot, {x: "indicateur", y: d => parseFloat(d.valeur), fill:"indicateur"}),

    // et un symbole pour positionner le quartier sélectionné
    Plot.dot(myDataBoxPlotQ, {
      x: "indicateur", 
      y: d => parseFloat(d.valeur), 
      fill: "white", 
      stroke:"black", 
      r: 5, 
      symbol: "indicateur",
      // un canal permet de rajouter des informations sur mesure à partir des données
      channels: {
        quartier: {
          value: "id",
          label: "Quartier n°"
        },
      },
      tip: {
        format: {
          quartier: true,
          id:true,
          indicateur: true,
          y: (d) => `${d*100} %`,
          stroke: false
        }
      }
    })
  ]
})