Public
Edited
Mar 24
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
a0e32de343898e490aff6a3f4aa42 = FileAttachment("1485a0e3-2de3-4389-8e49-0aff6a3f4aa4(2).jfif").image()
Insert cell
Insert cell
graph1 = FileAttachment("graph1.png").image()
Insert cell
graph2 = FileAttachment("graph 2.png").image()
Insert cell
departement1 = FileAttachment("departements-auvergne-rhone-alpes.geojson").json()
Insert cell
departement2 = FileAttachment("departements-provence-alpes-cote-d-azur.geojson").json()
Insert cell
departement3 = FileAttachment("departements-occitanie.geojson").json()
Insert cell
region1 = FileAttachment("region-auvergne-rhone-alpes@1.geojson").json()
Insert cell
region2 = FileAttachment("region-provence-alpes-cote-d-azur.geojson").json()
Insert cell
region3= FileAttachment("region-occitanie.geojson").json()
Insert cell
site = FileAttachment("site@3.json").json()
Insert cell
rivieres = FileAttachment("rivieres@1.json").json()
Insert cell
graph = FileAttachment("graph@8.csv").csv()
Insert cell
d3Sankey = require.alias({"d3-array": d3, "d3-shape": d3, "d3-sankey": "d3-sankey@0.12.3/dist/d3-sankey.min.js"})("d3-sankey")
Insert cell
// Copyright 2021-2023 Observable, Inc.
// Released under the ISC license.
// https://observablehq.com/@d3/sankey-diagram
function SankeyChart({
nodes, // an iterable of node objects (typically [{id}, …]); implied by links if missing
links // an iterable of link objects (typically [{source, target}, …])
}, {
format = ",", // a function or format specifier for values in titles
align = "justify", // convenience shorthand for nodeAlign
nodeId = d => d.id, // given d in nodes, returns a unique identifier (string)
nodeGroup, // given d in nodes, returns an (ordinal) value for color
nodeGroups, // an array of ordinal values representing the node groups
nodeLabel, // given d in (computed) nodes, text to label the associated rect
nodeTitle = d => `${d.id}\n${format(d.value)}`, // given d in (computed) nodes, hover text
nodeAlign = align, // Sankey node alignment strategy: left, right, justify, center
nodeSort, // comparator function to order nodes
nodeWidth = 15, // width of node rects
nodePadding = 10, // vertical separation between adjacent nodes
nodeLabelPadding = 6, // horizontal separation between node and label
nodeStroke = "currentColor", // stroke around node rects
nodeStrokeWidth, // width of stroke around node rects, in pixels
nodeStrokeOpacity, // opacity of stroke around node rects
nodeStrokeLinejoin, // line join for stroke around node rects
linkSource = ({source}) => source, // given d in links, returns a node identifier string
linkTarget = ({target}) => target, // given d in links, returns a node identifier string
linkValue = ({value}) => value, // given d in links, returns the quantitative value
linkPath = d3Sankey.sankeyLinkHorizontal(), // given d in (computed) links, returns the SVG path
linkTitle = d => `${d.source.id} → ${d.target.id}\n${format(d.value)}`, // given d in (computed) links
linkColor = "source-target", // source, target, source-target, or static color
linkStrokeOpacity = 0.5, // link stroke opacity
linkMixBlendMode = "multiply", // link blending mode
colors = d3.schemeTableau10, // array of colors
width = 640, // outer width, in pixels
height = 400, // outer height, in pixels
marginTop = 5, // top margin, in pixels
marginRight = 1, // right margin, in pixels
marginBottom = 5, // bottom margin, in pixels
marginLeft = 1, // left margin, in pixels
} = {}) {
// Convert nodeAlign from a name to a function (since d3-sankey is not part of core d3).
if (typeof nodeAlign !== "function") nodeAlign = {
left: d3Sankey.sankeyLeft,
right: d3Sankey.sankeyRight,
center: d3Sankey.sankeyCenter
}[nodeAlign] ?? d3Sankey.sankeyJustify;

// Compute values.
const LS = d3.map(links, linkSource).map(intern);
const LT = d3.map(links, linkTarget).map(intern);
const LV = d3.map(links, linkValue);
if (nodes === undefined) nodes = Array.from(d3.union(LS, LT), id => ({id}));
const N = d3.map(nodes, nodeId).map(intern);
const G = nodeGroup == null ? null : d3.map(nodes, nodeGroup).map(intern);

// Replace the input nodes and links with mutable objects for the simulation.
nodes = d3.map(nodes, (_, i) => ({id: N[i]}));
links = d3.map(links, (_, i) => ({source: LS[i], target: LT[i], value: LV[i]}));

// Ignore a group-based linkColor option if no groups are specified.
if (!G && ["source", "target", "source-target"].includes(linkColor)) linkColor = "currentColor";

// Compute default domains.
if (G && nodeGroups === undefined) nodeGroups = G;

// Construct the scales.
const color = nodeGroup == null ? null : d3.scaleOrdinal(nodeGroups, colors);

// Compute the Sankey layout.
d3Sankey.sankey()
.nodeId(({index: i}) => N[i])
.nodeAlign(nodeAlign)
.nodeWidth(nodeWidth)
.nodePadding(nodePadding)
.nodeSort(nodeSort)
.extent([[marginLeft, marginTop], [width - marginRight, height - marginBottom]])
({nodes, links});

// Compute titles and labels using layout nodes, so as to access aggregate values.
if (typeof format !== "function") format = d3.format(format);
const Tl = nodeLabel === undefined ? N : nodeLabel == null ? null : d3.map(nodes, nodeLabel);
const Tt = nodeTitle == null ? null : d3.map(nodes, nodeTitle);
const Lt = linkTitle == null ? null : d3.map(links, linkTitle);

// A unique identifier for clip paths (to avoid conflicts).
const uid = `O-${Math.random().toString(16).slice(2)}`;

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;");

const node = svg.append("g")
.attr("stroke", nodeStroke)
.attr("stroke-width", nodeStrokeWidth)
.attr("stroke-opacity", nodeStrokeOpacity)
.attr("stroke-linejoin", nodeStrokeLinejoin)
.selectAll("rect")
.data(nodes)
.join("rect")
.attr("x", d => d.x0)
.attr("y", d => d.y0)
.attr("height", d => d.y1 - d.y0)
.attr("width", d => d.x1 - d.x0);

if (G) node.attr("fill", ({index: i}) => color(G[i]));
if (Tt) node.append("title").text(({index: i}) => Tt[i]);

const link = svg.append("g")
.attr("fill", "none")
.attr("stroke-opacity", linkStrokeOpacity)
.selectAll("g")
.data(links)
.join("g")
.style("mix-blend-mode", linkMixBlendMode);

if (linkColor === "source-target") link.append("linearGradient")
.attr("id", d => `${uid}-link-${d.index}`)
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", d => d.source.x1)
.attr("x2", d => d.target.x0)
.call(gradient => gradient.append("stop")
.attr("offset", "0%")
.attr("stop-color", ({source: {index: i}}) => color(G[i])))
.call(gradient => gradient.append("stop")
.attr("offset", "100%")
.attr("stop-color", ({target: {index: i}}) => color(G[i])));

link.append("path")
.attr("d", linkPath)
.attr("stroke", linkColor === "source-target" ? ({index: i}) => `url(#${uid}-link-${i})`
: linkColor === "source" ? ({source: {index: i}}) => color(G[i])
: linkColor === "target" ? ({target: {index: i}}) => color(G[i])
: linkColor)
.attr("stroke-width", ({width}) => Math.max(1, width))
.call(Lt ? path => path.append("title").text(({index: i}) => Lt[i]) : () => {});

if (Tl) svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.selectAll("text")
.data(nodes)
.join("text")
.attr("x", d => d.x0 < width / 2 ? d.x1 + nodeLabelPadding : d.x0 - nodeLabelPadding)
.attr("y", d => (d.y1 + d.y0) / 2)
.attr("dy", "0.35em")
.attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end")
.text(({index: i}) => Tl[i]);

function intern(value) {
return value !== null && typeof value === "object" ? value.valueOf() : value;
}

return Object.assign(svg.node(), {scales: {color}});
}
Insert cell
function createMap(list_region, list_departement, rivieres, site, handleSiteClick, graphique) {
const nom_site = document.createElement("div");
nom_site.id = "nom_site";
nom_site.replaceChildren(html`<h4 id="">Evolution du débit total de tous les sites</h4>`);
const chartContainer_scrubber = document.createElement("div");
chartContainer_scrubber.id = "chart-container";
console.log(graphique)
const scrubber = Scrubber(
[...new Set(graph.map(d => d.date))].sort(), // Min to max years in 5-year increments
{ autoplay: false, delay: 750, loop: false }
);
chartContainer_scrubber.replaceChildren(scrubber)
const graph_filter = graphique.filter(d => d.date == scrubber.value)
// Définition du canevas SVG
const width = 400, height = 500;
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height);

// Projection proche de Lambert 93 (conique conforme adaptée à la France)
const projection = d3.geoConicConformal()
.center([4.5, 45]) // Centre sur la France
.scale(8000) // Ajuste la taille
.translate([width / 2, height / 2]); // Centre sur le canevas

const pathGenerator = d3.geoPath().projection(projection);

// Dessiner le fond de carte (départements)
svg.selectAll("path.departement")
.data(list_departement) // Utilisation de liste_region directement
.enter().append("path")
.attr("class", "departement")
.attr("d", pathGenerator)
.attr("stroke", "#aaa")
.attr("fill", "#f0f0f0")
.attr("stroke-width", 1); // Augmenter la largeur du trait

// Dessiner le fond de carte (départements)
svg.selectAll("path.region")
.data(list_region) // Utilisation de liste_region directement
.enter().append("path")
.attr("class", "region")
.attr("d", pathGenerator)
.attr("stroke", "black")
.attr("fill", "none")
.attr("stroke-width", 3); // Augmenter la largeur du trait

// Dessiner les rivières
svg.selectAll("path.riviere")
.data(rivieres.features)
.enter().append("path")
.attr("class", "riviere")
.attr("d", pathGenerator)
.attr("stroke", "blue")
.attr("fill", "none")
.attr("stroke-width", 3)
.on("mouseover", function(event, d) {
// Agrandir la rivière au survol
d3.select(this)
.attr("stroke-width", 10); // Agrandir la ligne de la rivière

// Afficher le nom de la rivière
svg.append("text")
.attr("id", "nomRiviere")
.attr("x", event.offsetX + 10) // Décalage horizontal
.attr("y", event.offsetY - 10) // Décalage vertical
.attr("font-size", 22)
.attr("fill", "black")
.text(d.properties.name);
})
.on("mouseout", function() {
// Rétablir la taille normale
d3.select(this)
.attr("stroke-width", 3);

// Supprimer le texte du nom de la rivière
d3.select("#nomRiviere").remove();
})
.on("mousemove", function(event, d) {
// Mise à jour de la position du texte lors du mouvement de la souris
d3.select("#nomRiviere")
.attr("x", event.offsetX + 10)
.attr("y", event.offsetY - 10);
});
// Conteneur pour le graphique
const chartContainer = document.createElement("div");
chartContainer.id = "chart-container";
console.log(graphique)
chartContainer.replaceChildren(handleSiteClick({ "properties": { "code_site": "" } }, graphique, scrubber.value));

const chartContainer2 = document.createElement("div");
chartContainer2.id = "chart-container";
const initialData = graphique.filter(d => d.date == scrubber.value);

const tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("background", "white")
.style("border", "1px solid black")
.style("border-radius", "5px")
.style("padding", "5px")
.style("display", "none") // Caché par défaut
.style("pointer-events", "none"); // Empêche d'interférer avec la souris
// Créer le graphique Sankey initial
let sankeyChart = SankeyChart({
links: initialData
}, {
nodeGroup: d => d.id.split(/_/)[0], // Take first word for color
nodeAlign: "right", // E.g., d3.sankeyJustify
linkColor: "source-target", // E.g., "source" or "target"
format: (f => d => `${f(d)} m3/s`)(d3.format(",.1~f")),
width: 1200,
height: 450,
});
chartContainer2.replaceChildren(sankeyChart);

// Ajouter un événement sur le scrubber pour mettre à jour le graphique Sankey
scrubber.addEventListener("input", function() {
const selectedDate = scrubber.value;
// Filtrer les données selon la date sélectionnée
const filteredData = graphique.filter(d => d.date == selectedDate);
console.log("scrubber value", scrubber.value)
// Mettre à jour le graphique Sankey avec les nouvelles données
sankeyChart = SankeyChart({
links: filteredData
}, {
nodeGroup: d => d.id.split(/_/)[0], // Take first word for color
nodeAlign: "right", // E.g., d3.sankeyJustify
linkColor: "source-target", // E.g., "source" or "target"
format: (f => d => `${f(d)} m3/s`)(d3.format(",.1~f")),
width: 1200,
height: 450
});
// Mettre à jour le conteneur avec le nouveau Sankey Chart
chartContainer2.replaceChildren(sankeyChart);
console.log("nom_site", nom_site.firstElementChild)
console.log("site_id", nom_site.firstElementChild.id)
chartContainer.replaceChildren(handleSiteClick({ "properties": { "code_site": nom_site.firstElementChild.id }}, graphique, scrubber.value)); // Passe les données du site cliqué à la fonction
});
// Dessiner les points des sites
svg.selectAll("circle")
.data(site.features)
.enter().append("circle")
.attr("cx", d => projection(d.geometry.coordinates)[0])
.attr("cy", d => projection(d.geometry.coordinates)[1])
.attr("r", 6)
.attr("fill", "red")
.attr("stroke", "black")
.attr("stroke-width", 1)
.on("mouseover", function (event, d) {
d3.select(this).attr("fill", "green"); // Change la couleur en vert
tooltip.style("display", "block")
.html(`Site: ${d.properties.source}`);
})
.on("mousemove", function (event) {
tooltip.style("left", (event.pageX - 60) + "px")
.style("top", (event.pageY + 30) + "px");
})
.on("mouseout", function () {
d3.select(this).attr("fill", "red"); // Remet la couleur rouge
tooltip.style("display", "none");
})
.on("click", function(event, d) {
// Appeler une fonction lorsque le site est cliqué
chartContainer.replaceChildren(handleSiteClick(d, graphique, scrubber.value)); // Passe les données du site cliqué à la fonction
nom_site.replaceChildren(html`<h4 id=${d.properties.code_site}>Evolution du débit au niveau de la station de mesure ${d.properties.source}</h4>
`);
});
svg.on("click", function(event, d) {
// Vérifier si le clic a eu lieu sur un cercle ou ailleurs
if (!event.target.closest("circle")) {
chartContainer.replaceChildren(handleSiteClick({ "properties": { "code_site": "" } }, graphique, scrubber.value)); // Passe les données du site cliqué à la fonction
nom_site.replaceChildren(html`<h4 id="">Evolution du débit total de tous les sites</h4>`);
}
});

return html`
<h2>Visualisation du débit sur le Rhône et ses affluents</h2>
<div>${chartContainer_scrubber}</div>
<div style="display: flex; flex-direction: column; gap: 20px;">
<!-- Partie haute : chartContainer2 qui prend toute la largeur -->
<div style="width: 100%;">
${chartContainer2}
</div>

<!-- Partie basse : svg.node() et chartContainer côte à côte -->
<div style="display: flex; gap: 20px;">
<div style="flex: 1;">
<h4>Carte des sites de mesure du Rhône et de ses affluents </h4>
${svg.node()}
</div>
<div style="flex: 1;">
${nom_site}
${chartContainer}
</div>
</div>
</div>

`

// Retourne le noeud SVG pour l'inclure dans le DOM
// return svg.node()
}

Insert cell
// Fonction appelée lors du clic sur un site
function handleSiteClick(siteData, graphe, scrubberValue) {
console.log("Site cliqué:", siteData); // Affiche les informations du site dans la console
const code_site = siteData.properties.code_site
const line_chart = createLineChart(filtre_data(graphe, code_site), "date_parse", "value_parse", scrubberValue)
// Ajoute ici toute logique que tu souhaites exécuter pour le site
return line_chart
}


Insert cell
function createLineChart(data, xColumn, yColumn, scrubberValue) {
// Dimensions du graphique
const margin = { top: 20, right: 50, bottom: 40, left: 80 };
const width = 900 - margin.left - margin.right;
const height = 600 - margin.top - margin.bottom;

// Création de l'élément SVG
const svg = d3.create("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);

// Échelles
const x = d3.scaleTime()
.domain(d3.extent(data, d => d[xColumn]))
.range([margin.left, width - margin.right]);

const y = d3.scaleLinear()
.domain([0, d3.max(data, d => d[yColumn])])
.range([height - margin.bottom, margin.top]);

// Axes
svg.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x))
.append("text")
.attr("x", width / 2)
.attr("y", 35)
.attr("fill", "black")
.attr("text-anchor", "middle")
.text("Temps");

svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
.append("text")
.attr("x", -height / 2)
.attr("y", -55)
.attr("fill", "black")
.attr("text-anchor", "middle")
.attr("transform", "rotate(-90)")
.text("Débit moyen journalier (m3/s)");

// Ligne
const line = d3.line()
.x(d => x(d[xColumn]))
.y(d => y(d[yColumn]));

svg.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", line);

// Ajout d'un div tooltip
const tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("background", "white")
.style("border", "1px solid black")
.style("border-radius", "5px")
.style("padding", "5px")
.style("display", "none") // Caché par défaut
.style("pointer-events", "none"); // Empêche d'interférer avec la souris

// Points
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", d => x(d[xColumn]))
.attr("cy", d => y(d[yColumn]))
.attr("r", d => {
return d[xColumn].toDateString() == new Date(scrubberValue).toDateString() ? 8 : 4;
})
.attr("fill", "red")
.attr("cursor", "pointer")
.on("mouseover", function (event, d) {
d3.select(this).attr("fill", "green");
tooltip.style("display", "block")
.html(`Date: ${d[xColumn].toLocaleDateString()}<br>Valeur: ${d[yColumn].toFixed(1)} m3/s`);
})
.on("mousemove", function (event) {
tooltip.style("left", (event.pageX - 60) + "px")
.style("top", (event.pageY + 30) + "px");
})
.on("mouseout", function () {
d3.select(this).attr("fill", "red");
tooltip.style("display", "none");
});

return svg.node();
}

Insert cell
function filtre_data(dataset, code_site) {
dataset.forEach(d => {
d.date_parse = new Date(d.date);
d.value_parse = parseFloat(d.value);
});
let data_filter;
if (code_site === "") {
// Grouper les données par date et sommer les valeurs
data_filter = Object.values(
dataset.reduce((acc, { date_parse, value_parse }) => {
const key = date_parse;
if (!acc[key]) {
acc[key] = { date_parse: date_parse, value_parse: 0 };
}
acc[key].value_parse += parseFloat(value_parse);
return acc;
}, {})
);
} else {
// Filtrer les données par code_site
data_filter = dataset.filter(d => d.code_site === code_site);
}

// Convertir date en objet Date et value en float
data_filter.forEach(d => {
d.date_parse = new Date(d.date_parse);
d.value_parse = parseFloat(d.value_parse);
});

// Trier par date
data_filter.sort((a, b) => a.date_parse - b.date_parse);
return data_filter;
}

Insert cell
import {howto} from "@d3/example-components"
Insert cell
import {Scrubber} from "@mbostock/scrubber"
Insert cell
// Appel de la fonction avec les données nécessaires
createMap([region1, region2, region3], [departement1, departement2, departement3], rivieres, site, handleSiteClick, graph);
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