Public
Edited
Sep 9, 2024
Paused
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Inputs.table(alphabet)
Insert cell
alphabet
Insert cell
Insert cell
Plot.plot({
marks: [
// On utilise la marque barY, une barre, qui s'étend en Y pour
// chaque individu du tableau de données
Plot.barY(
alphabet,
{
x: "letter", // La valeur de la propriété 'letter' en X
y: "frequency", // La valeur de la propriété 'frequency' en Y
fill: "darkblue", // Même couleur pour tous les individus
// On ne spécifie rien pour le tri, donc tri par défaut (alphabétique) de l'axe X
}
),
Plot.ruleY([0]) // On ajoute manuellement une ligne horizontale, à la valeur 0
]
})
Insert cell
Insert cell
Plot.plot({
x: {tickPadding: 6, tickSize: 0},
y: {percent: true},
marks: [
// Deux marques : le trait (ruleX) et le point (dot).
// On trie (propriété 'sort') sur l'axe x avec la valeur rencontrée sur l'axe y
Plot.ruleX(alphabet, {x: "letter", y: "frequency", strokeWidth: 2, sort: { x: "y", reverse: true }}),
Plot.dot(alphabet, {x: "letter", y: "frequency", fill: "currentColor", r: 4, sort: { x: "y", reverse: true }})
]
})
Insert cell
Insert cell
Plot.plot({
color: {
// Le domaine d'entrée de nos valeur (voir plus bas comment cette valeur est calculée)
domain:['Voyelle', 'Consonne'],
// La plage de sortie correspondante (on aurait pu laisser les couleurs par défaut
// comme dans le prochain graphique)
range: ['lightblue', 'lightgreen'],
// On demande explicitement l'affichage de la légende
legend: true,
},
marks: [
// Toujours la marque bar, mais cette fois 'barX', elle s'étend sur l'axe X
Plot.barX(
alphabet,
{
x: "frequency",
y: "letter",
sort: { y: "x", reverse: true }, // En triant sur l'axe 'y', par la valeur rencontrée sur l'axe 'x'
fill: (d) => /[AEIOU]/.test(d.letter) ? 'Voyelle' : 'Consonne', // Avec une couleur différente voyelle/consomne
}
),
Plot.ruleX([0]) // On ajoute une ligne verticale, à la valeur 0
]
})
Insert cell
Insert cell
Insert cell
Plot.plot({
color: {
// On affichage la légend, mais on laisse les couleurs catégorielles par défault
legend: true,
},
marks: [
Plot.barY(
penguins,
// C'est la première transformation que nous rencontrons, groupX,
// on l'utilise pour compter le nombre d'observation dans chaque groupe
Plot.groupX(
// On stocke le compte (count) d'individu dans la propriété 'y'
// qui sera utilisé pour les barres
{ y: "count" },
{ x: "island", fill: "species" },
)
)
]
})
Insert cell
Insert cell
Insert cell
Plot.plot({
x: {axis: null},
y: {grid: true}, // Une grille, sur l'axe Y
color: { legend: true},
marks: [
Plot.barY(
penguins,
// On utilise toujours la transformation 'groupX' pour compter le nombre d'observation dans chaque groupe
Plot.groupX(
{ y: "count" },
{
x: "species", // On veut toujours les espèces sur l'axe X
y: "population", // Le compte de population sur l'axe Y
fill: "species", // Une coloration en fonction de l'espèce
fx: "island", // Et un facettage sur la valeur "island" (i.e répéter le graphique pour chaque island)
},
)
),
Plot.ruleY([0])
]
})
Insert cell
Insert cell
Inputs.table(aapl)
Insert cell
Insert cell
Plot.plot({
marks: [
Plot.lineY(aapl, {x: "Date", y: "Close", stroke: 'blue'}), // Pour une ligne, on définit sa couleur avec 'stroke'
Plot.ruleY([0]), // On ajoute une ligne horizontale, à la valeur 0
]
})
Insert cell
Insert cell
Plot.plot({
marks: [
Plot.ruleY([0]),
Plot.axisX({ticks: "3 months"}), // Un axe X personnalisé
Plot.gridX(), // Une grille, seulement au départ de l'axe X
Plot.line(aapl, {x: "Date", y: "Close"}) // La ligne brisée à ajouter
]
})
Insert cell
Insert cell
Plot.plot({
marks: [
Plot.areaY(aapl, { x: "Date", y: "Close", fill: 'green' }), // Pour une surface, on définit sa couleur avec 'fill'
Plot.ruleY([0]) // On ajoute une ligne horizontale, à la valeur 0
]
})
Insert cell
Insert cell
Insert cell
Plot.plot({
// On peut définir des styles pour tous le graphique
// avec la propriété style
style: {
fontFamily: 'monospace',
backgroundColor: 'rgba(245, 245, 220, 0.4)',
color: 'rgb(12, 5, 12)'
},
// Une autre manière d'afficher une grille, ici sur les 2 axes
grid: true,
marks: [
// Une ligne pour l'axe des abscisse
Plot.ruleY([0]),
// Une ligne pour l'axe des ordonnées
Plot.ruleX([d3.min(aapl, d => d.Date)]),
// On utilise ici une transformation (windowY) qui calcule une fenêtre mobile
// et en dérive des statistiques qui seront utilisées dans le graphique
Plot.lineY(aapl, Plot.windowY({x: "Date", y: "Close", k: 80, reduce: "mean"}))
]
})
Insert cell
Insert cell
Insert cell
Plot.plot({
inset: 6,
width: 1000,
// Il est possible de donner un nom de classe, il sera attribué à l'élément SVG retourné
// Cela peut être utile si vous voulez personnaliser le style du graphique en CSS
className: 'my-plot',
y: { label: "↑ Apple stock price ($)" },
color: { domain: [-1, 0, 1], range: ["#e41a1c", "#000000", "#4daf4a"] },
marks: [
// On utilise cette autre manière de définir la grille, qui nous permet
// de controle sa couleur et son opacité (alors que les méthodes utilisées plus haut
// dans les exemples ne le permettait pas).
Plot.gridX({ stroke: 'white', strokeOpacity: 0.4 }),
Plot.gridY({ stroke: 'white', strokeOpacity: 0.4 }),
// Les fines barres (ici blanche)
// qui montre les valeurs min / max
Plot.ruleX(aapl.slice(-120), {
x: "Date",
y1: "Low",
y2: "High"
}),
// Les blocs de couleurs vertes ou rouges
// qui montrent les valeurs d'ouverture / fermeture
Plot.ruleX(aapl.slice(-120), {
x: "Date",
y1: "Open",
y2: "Close",
stroke: (d) => Math.sign(d.Close - d.Open),
strokeWidth: 4,
strokeLinecap: "round",
}),
]
})
Insert cell
<style>
.my-plot {
background: black;
color: white;
}
.my-plot g[aria-label="y-axis label"] {
font-size: 18px;
}
</style>
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Plot.plot({
marks: [
// C'est le second exemple d'utilisation d'une transformation
// Le premier argument de "bin" décrit comment résumer les données
// le second argument décrit les options propres à la représentation
// Regardez la documentation pour en savoir plus si besoin :
// https://observablehq.com/plot/transforms/bin
// Ici on demande environ 30 bins.
Plot.rectY(olympians, Plot.binX({y: "proportion"}, {x: "weight", tip: true, thresholds: 30})),
Plot.ruleY([0])
]
})
Insert cell
Insert cell
viewof binwidthMethod = Inputs.select(
// Un objet Map, où les clés sont
// affichées dans le selecteur, et où les
// valeurs sont ce qui est retournée
// dans la variable définie avec viewof
new Map([
['Freedman Diaconis', 'freedman-diaconis'],
['Scott', 'scott'],
['Sturges', 'sturges']
]),
{ label: "Bin-width method" }
)
Insert cell
Insert cell
Plot.plot({
marks: [
// Ici, la valeur de thresholds est donc une chaine de caractère,
// au choix enter 'freedman-diaconis', 'scott' et 'sturges'
Plot.rectY(olympians, Plot.binX({y: "proportion"}, {x: "weight", tip: true, thresholds: binwidthMethod})),
Plot.ruleY([0])
]
})
Insert cell
Insert cell
viewof intervalSize = Inputs.range([1, 25], {label: "Interval size", step: 1, value: 10})
Insert cell
Plot.plot({
marks: [
// Notez qu'ici nous n'utilisons donc plus la propriété thresholds
// mais la propriété interval
Plot.rectY(olympians, Plot.binX({y: "proportion"}, {x: "weight", tip: true, interval: intervalSize})),
Plot.ruleY([0])
]
})
Insert cell
Insert cell
Plot.plot({
color: { legend: true },
marks: [
Plot.rectY(olympians, Plot.binX({y: "proportion"}, {x: "weight", tip: true, interval: intervalSize, fill: 'sex'})),
Plot.ruleY([0])
]
})
Insert cell
Insert cell
Plot.plot({
marginLeft: 60,
x: { nice: true },
marks: [
Plot.areaY(
olympians,
Plot.binX(
{ y: "proportion" },
{
x: "weight",
fill: "blue",
stroke: 'blue',
fillOpacity: 0.1,
strokeWidth: 1.5,
thresholds: 30,
curve: "natural"
}
)
),
Plot.ruleY([0]),
]
})
Insert cell
Plot.plot({
marginLeft: 60,
x: { nice: true },
color: { legend: true },
marks: [
Plot.areaY(
olympians,
Plot.binX(
// On utilisee y2 pour éviter d'empiler automatiquement les aires
// Vous pouvez changer y2 en y pour voir la différence
// (attention, les valeurs de l'axe Y ne seront plus les même)
{ y2: "proportion" },
{
x: "weight",
// Si on utilise une variable (ici 'sex') pour fill et/ou stroke,
// (par rapport au graphique précédent), alors la densité est représentée
// de manière différenciée selon la variable 'sex'
fill: "sex",
stroke: "sex",
fillOpacity: 0.1,
strokeWidth: 1.5,
thresholds: 30,
curve: "natural"
}
)
),
Plot.ruleY([0]),
]
})
Insert cell
Insert cell
Insert cell
Plot.plot({
marks: [
Plot.boxX(olympians, {x: "weight"}),
]
})
Insert cell
Insert cell
Plot.plot({
marks: [
// Un 'tick' par individu
Plot.tickX(olympians, {x: "weight"})
]
})
Insert cell
Insert cell
Plot.plot({
r: {range: [0, 14]},
marks: [
Plot.dot(
olympians,
Plot.binX(
// On stocke le compte (count) de chaque bin dans la propriété 'r'
// qui sera utilisé pour dessiner le rayon des cercle
{r: "count"},
{x: "weight"}
)
)
]
})
Insert cell
Insert cell
Plot.plot({
height: 400,
marks: [
Plot.dot(
olympians.slice(0, 1000),
// La transformation 'dodge' (on pourrait dire "esquive" en français)
// calcule une autre dimension (ici en Y) de la position de manière
// à ce que les points soient densément empilés sans se chevaucher
Plot.dodgeY("middle", {x: "weight"}))
]
})
Insert cell
Insert cell
Plot.plot({
height: 400,
color: { legend: true }, // Nécessaire pour avoir l'affichage de la légende
marks: [
Plot.dot(
olympians.slice(0, 1000),
// La transformation 'dodge' (on pourrait dire "esquive" en français)
// calcule une autre dimension (ici en Y) de la position de manière
// à ce que les points soient densément empilés sans se chevaucher
Plot.dodgeY("middle", {x: "weight", fill: 'sex'})
)
]
})
Insert cell
Insert cell
Plot.plot({
y: {
grid: true,
label: "← Women · Men →",
labelAnchor: "center",
tickFormat: Math.abs
},
marks: [
Plot.dot(
olympians.slice(0, 1000),
// La transformation "stack" pour former des empilement, ici sur l'axe Y
// et avec stackY2 pour permettre un meilleur positionnement par rapport
// à l'axe
Plot.stackY2({
x: 'weight',
y: (d) => d.sex === "male" ? 1 : -1, // Position par rapport à l'axe
fill: "sex",
})
),
Plot.ruleY([0])
]
})
Insert cell
Insert cell
Plot.plot({
marginLeft: 100,
marks: [
// Les lignes pointillées, aux valeurs indiquées
Plot.ruleX([40, 60, 80, 100, 120, 140, 160], { stroke: 'grey', strokeDasharray: '5 20', strokeWidth: 0.5 }),
// Par rapport au box plot précédent, on spécifie le nom d'une variable
// catégorielle ('sport'), qui va permettre de décomposer la
// création des box plots pour produire des small multiples
Plot.boxX(olympians, {x: "weight", y: "sport", sort: {y: "x"}}),
]
})
Insert cell
Insert cell
Plot.plot({
marginRight: 100,
// On utilise ici explicitement le concept de facet,
// en spécifiant le jeu de données, l'axe sur lequel produire
// les small multiples ('y') et la variable à utiliser ('sport')
facet: {
data: olympians,
y: "sport",
},
marks: [
Plot.rectY(olympians, Plot.binX({y: "count"}, {x: "weight"})),
Plot.ruleY([0])
]
})
Insert cell
Insert cell
Plot.plot({
height: 400,
marginTop: 0,
marginLeft: 100,
x: {inset: 10, grid: true, label: "weight (kg) →"},
y: {axis: null, inset: 2},
color: {legend: true},
// On fait appel au mécanisme de facet,
// en demandant à décomposer le tracé des points
// selon la variable 'sport', sur l'axe Y
facet: {
data: olympians,
y: "sport",
marginLeft: 50
},
marks: [
// Une marque pour le cadre
Plot.frame({stroke: "#aaa", strokeWidth: 0.5}),
// Une marque pour les points représentants les individus,
// avec un remplissage différent selon les modalité de la variable "sex"
Plot.dot(olympians, {x: "weight", y: "sex", fill: "sex", r: 1})
],
})
Insert cell
Insert cell
// Un scatter plot dans sa forme la plus simple
Plot.plot({
marks: [
Plot.dot(olympians, {x: "height", y: "weight", stroke: 'red'})
]
})
Insert cell
Insert cell
Plot.plot({
grid: true,
inset: 4,
marks: [
Plot.dot(
olympians,
{
x: "height",
y: "weight",
stroke: 'red',
// Seulement les athletes ayant gagné une médaille
filter: d => d.bronze + d.silver + d.gold > 0,
// Avec un tooltip au survol
tip: true,
// Deux champs à ajouter dans le tooltip
channels: {
'Nombre de total de médailles': d => d.bronze + d.silver + d.gold,
'Sport': d => d.sport,
},
}
),
Plot.frame()
]
})
Insert cell
Insert cell
Plot.plot({
height: 3000,
width: 1000,
marginRight: 100,
// On peut utiliser le système de facet pour produire des small multiples
// selon deux variables, ici 'sex' sur l'axe 'x'
// et 'sport' sur l'axe 'y'
facet: {
data: olympians,
y: "sport",
x: "sex",
},
marks: [
Plot.frame(),
Plot.dot(olympians, {x: "height", y: "weight", stroke: 'darkgreen'})
]
})
Insert cell
Insert cell
Plot.plot({
marks: [
// On utilise pour ça la marque "density",
// on peut utiliser une couleur fixe (comme "red") dans le champ stroke
// ou une couleur basée sur la valeur de densité calculée (en mettant "density")
Plot.density(olympians, { x: "height", y: "weight", stroke: "density" })
]
})
Insert cell
Insert cell
Plot.plot({
color: {
scheme: "ylgnbu"
},
marks: [
Plot.hexagon(olympians, Plot.hexbin({fill: "sum"}, {x: "height", y: "weight"}))
]
})
Insert cell
Insert cell
Plot.plot({
marks: [
Plot.dot(penguins, {x: "culmen_length_mm", y: "culmen_depth_mm"}),
// On utilise les mêmes propriétés x et y que pour la marque "dot"
Plot.linearRegressionY(penguins, {x: "culmen_length_mm", y: "culmen_depth_mm"})
]
})
Insert cell
Insert cell
Plot.plot({
inset: 4,
marks: [
// On ajoute aussi
Plot.dot(
penguins,
{
x: "culmen_length_mm",
y: "culmen_depth_mm",
fill: 'species', // On ajoute la propriété 'fill' pour avoir un remplissage différent selon l'espèce
tip: true, // On ajoute la propriété 'tip' pour avoir des tooltips au survol
}
),
Plot.linearRegressionY(
penguins,
{
x: "culmen_length_mm",
y: "culmen_depth_mm",
stroke: 'species', // Pour calculer les régressions linéaires pour chaque espèce
}
),
Plot.frame(),
]
})
Insert cell
Insert cell
Insert cell
Insert cell
{
const months = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'];
return Plot.plot({
subtitle: 'New-York - Température journalière maximale',
marginLeft: 60,
color: {
legend: true, // Si on ne précise pas ça, la légende n'est pas affichée par défaut
label: 'Température maximale', // Si on ne précise pas ça, le label est le nom de la variable
},
marks: [
Plot.cell(weather.slice(-365), {
x: d => d.date.getUTCDate(),
y: d => months[d.date.getUTCMonth()],
// Remplissage selon la variable "temp_max" (la progression de couleur utilisée est ici celle par défaut)
fill: "temp_max",
sort: { y: null } // Si on ne précise pas ça, l'axe est trié par ordre alphabétique des noms de mois
}),
]
})
}
Insert cell
Insert cell
Plot.plot({
x: { round: true },
color: { scheme: "BuRd" },
width: 700,
height: 200,
marks: [
Plot.barX(dataClimate, {
x: "Year",
fill: "Anomaly",
interval: "year", // données annuelles (pour les étiquettes sur l'axe X)
inset: 0, // pas d'espace entre les barres,
tip: true,
channels: {
// On formatte nous même le champ "Year" du tooltip
Year: d => `${d.Year.getUTCFullYear()}`
}
})
],
caption: md`Data source: https://www.ncei.noaa.gov/access/monitoring/climate-at-a-glance/`
})
Insert cell
Insert cell
Plot.plot({
marginLeft: 90,
height: 380,
width: 700,
label: null, // Pour n'afficher ni le label de l'axe X ni le label de l'axe Y
color: {
scheme: "RdYlBu", // Il s'agit d'une palette "divergente" "Red - Yellow - Blue" de ColorBrewer
pivot: 0, // La valeur correspondant au milieu de la palette
legend: true, // Affichage de la légende
label: "correlation",
},
marks: [
Plot.cell(correlations, { x: "a", y: "b", fill: "correlation" }),
Plot.text(correlations, {
x: "a",
y: "b",
text: (d) => d.correlation.toFixed(2),
// Pour avoir une bonne lisibilité, on utilise du texte noir quand la cellule est claire
// et du texte blanc quand la cellule est foncée :
fill: (d) => (Math.abs(d.correlation) > 0.6 ? "white" : "black")
}),
]
})
Insert cell
Insert cell
Plot.plot({
axis: null,
margin: 20,
marginRight: 120,
marks: [
Plot.tree(flare.slice(0, 50), {path: "name", delimiter: "."})
]
})
Insert cell
Insert cell
Plot.plot({
height: 400,
// Il est nécessaire de définir une projection lorsque les données ne le sont pas déjà
// (quand elles sont en coordonnées géographiques longitude / latitude)
// et le "domain", c'est à dire ici l'emprise géographique de la carte à créer
projection: { type: "conic-conformal", domain: quartiersParis },
marks: [
// La marque Plot.geo pour afficher les polygones représentant les quartiers
Plot.geo(quartiersParis, {
stroke: 'black'
})
]
})
Insert cell
Insert cell
Plot.plot({
height: 400,
projection: { type: "conic-conformal", domain: quartiersParis },
// On définit l'échelle de couleurs qui sera utilisé pour le remplissage
color: {
type: "quantile",
n: 6,
scheme: "Oranges", // Il s'agit d'un type de palette présent dans ColorBrewer, et dans Plot
label: "Densité de population (hb / km2)",
legend: true,
tickFormat: d => d.toFixed(),
},
marks: [
Plot.geo(quartiersParis, {
stroke: 'grey',
// Cette fois on remplit selon la valeur de densité de population
fill: (d) => (d.properties.P12_POP / d.properties.surface) * 1000000,
})
]
})
Insert cell
Insert cell
{
const chart = Plot.plot({
color: {
type: 'categorical',
scheme: 'Set3', // Il s'agit d'un type de palette présent dans ColorBrewer, et dans Plot
},
r: { range: [0, 22] },
height: 400,
projection: { type: "conic-conformal", domain: quartiersParis },
marks: [
Plot.geo(quartiersParis, {
stroke: 'darkgrey',
fill: 'rgb(244, 244, 244)'
}),
// La marque 'Plot.dot' pour les cercles proportionnels
Plot.dot(quartiersParis.features, Plot.centroid({
// On colore le point selon le code arrondissement
// (un arrondissement contient plusieurs quartiers)
fill: d => d.properties.c_ar,
stroke: "black",
// On définit le rayon des cercles comme proportionnel à la population
r: (d) => d.properties.P12_POP,
tip: true,
// Pour affichage dans le tooltip :
channels: {
'Nom': d => d.properties.l_qu,
'Population 2012': d => d.properties.P12_POP,
'Code arrondissement': d => d.properties.c_ar,
}
})),
]
});
return html`<div>
${legendRadius(chart.scale("r"), { ticks: 4, label: "Population 2012" })}
${chart}
</div>`;
}
Insert cell
Insert cell
{
const chart = Plot.plot({
r: { range: [0, 22] },
color: {
scheme: 'Reds', // Il s'agit d'un type de palette présent dans ColorBrewer, et dans Plot
legend: true,
type: 'quantile',
n: 6,
label: 'Densité de population (hb/km2)',
tickFormat: '.0f',
},
height: 400,
projection: { type: "conic-conformal", domain: quartiersParis },
marks: [
Plot.geo(quartiersParis, {
stroke: 'darkgrey',
fill: 'rgb(244, 244, 244)'
}),
// La marque 'Plot.dot' pour les cercles proportionnels
Plot.dot(quartiersParis.features, Plot.centroid({
// On colore le point selon la densité de population
fill: d => (d.properties.P12_POP / d.properties.surface) * 1000000,
stroke: "black",
// On définit le rayon des cercles comme proportionnel à la population
r: (d) => d.properties.P12_POP,
tip: true,
// Pour affichage dans le tooltip :
channels: {
'Nom': d => d.properties.l_qu,
'Population 2012': d => d.properties.P12_POP,
'Code arrondissement': d => d.properties.c_ar,
}
})),
]
});
return html`<div>
${legendRadius(chart.scale("r"), { ticks: 4, label: "Population 2012" })}
${chart}
</div>`;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more