Alors que les derniers bureaux de vote ont fermé à 20 heures, les résultats du premier tour des élections municipales commencent à tomber au compte-gouttes en cette soirée du dimanche 15 mars.

Qui fait la course en tête dans votre commune ? Quelles performances pour les personnalités en lice ? Quels résultats dans les villes à grand enjeu ? Voici une carte pour découvrir les résultats au fil du dépouillement.

N’hésitez pas à l’actualiser au cours de la soirée avant de consulter les résultats dans les plus grandes communes, qui doivent arriver plus tard en raison d’une fermeture tardive des bureaux de vote.

Les listes arrivées en tête ou élues au premier tour

${elmt.getAttribute(« data-tt »)}

`;
document.body.appendChild(tt_fixe);

// Positionnement du tooltip
if (window.innerWidth > 500) {
const rect = elmt.getBoundingClientRect();
tt_fixe.style.top = rect.top – 14 + window.scrollY – tt_fixe.offsetHeight + « px »;
tt_fixe.style.left = rect.left + rect.width / 2 – tt_fixe.offsetWidth / 2 + 2 + « px »;
}

// Gestionnaire pour fermer le tooltip
tt_fixe.querySelector(« .fermer-tooltip »).addEventListener(« click », function () {
removeCurrentTooltip();
reset_func();
d3.selectAll(« #dec_mun circle.bubulle »).classed(« opacified », false);
d3.selectAll(« .dec_nuance »).classed(« choisi », false);
d3.selectAll(« .dec_nuance.latotale »).classed(« choisi », true);
});

currentTooltip = tt_fixe;
return tt_fixe;
}

// Sélectionne les éléments
let tt_fixe_elmts = getA(selector);

// Ajoute les événements de clic
forEach(tt_fixe_elmts, function (elmt) {
elmt.addEventListener(« click », function (e) {
e.stopPropagation();
showTooltip(this);
});
});

// Fonction pour afficher le tooltip manuellement
function showTooltipManually(elmt) {
forEach(tt_fixe_elmts, function (e) {
e.classList.remove(« selected-commune »);
});
elmt.classList.add(« selected-commune »);
// on poireaute un peu
setTimeout(() => showTooltip(elmt), 25);
}

// Retourne les fonctions utiles
return {
showTooltipManually,
showTooltip,
};
}

function showHoverTooltip(elmt) {
removeHoverTooltip();
const tt = document.createElement(« div »);
tt.classList.add(« minitt », « tooltipbrt »);
tt.innerHTML = elmt.getAttribute(« data-tt ») +  »;
document.body.appendChild(tt);

const rect = elmt.getBoundingClientRect();
tt.style.top = rect.top – 14 + window.scrollY – tt.offsetHeight + « px »;
tt.style.left = rect.left + 2 + rect.width / 2 – tt.offsetWidth / 2 + « px »;
}

function removeHoverTooltip() {
const existing = document.querySelector(« .minitt »);
if (existing) existing.remove();
}

function dessinerBubulles(langue) {
// on modifie quelques textes en dur
if (langue != « fr ») {
d3.select(« #search_mun input »).attr(« placeholder », « Search for a town or a city »);
}

// Variables globales
const largeur = document.getElementById(« dec_mun »).offsetWidth;
const mobileDec = document.getElementById(« contenant_carte »).offsetWidth < 600;
const ratioMun = mobileDec ? 1.4 : 1.3;
const hauteur = largeur * ratioMun;
const is_dark = document.querySelector(« html »).getAttribute(« data-color-mode ») === « dark »;

// Initialisation du SVG
const svg = d3
.select(« #dec_mun »)
.append(« svg »)
.attr(« width », « 100% »)
.attr(« viewBox », `${largeur * -0.025} ${largeur * ratioMun * -0.05} ${largeur} ${largeur * ratioMun}`),
dec_dept = svg.append(« g »),
dec_ligne = svg.append(« g »),
dec_communes = svg.append(« g »),
dec_textes = svg.append(« g »);

// Fonction pour formater les nombres
function milliers(num) {
let resultat = 0;
if (num < 100) {
// Formatage avec deux décimales pour les nombres < 100
resultat = num.toFixed(2).replace(« . », « , »);
} else if (num < 1000) {
resultat = num;
} else if (num < 1000000) {
resultat = String((num / 1000).toFixed(3)).replace(« . »,  » « );
} else if (num >= 1000000 && num < 1000000000) {
resultat =
String((num / 1000000).toFixed(1))
.replace(« . », « , »)
.replace(« ,0 », «  ») +
 » million » +
(num >= 2000000 ? « s » : «  »);
} else if (num >= 1000000000) {
resultat =
String((num / 1000000000).toFixed(1))
.replace(« . », « , »)
.replace(« ,0 », «  ») +
 » milliard » +
(num >= 2000000000 ? « s » : «  »);
}
return String(resultat).replace(« . », « , »).replace(« ,00 », «  »);
}

function thousands(num) {
if (num >= 1000000) {
const millions = (num / 1000000).toFixed(1);
return `${millions} million`;
} else if (num < 100 && num % 1 !== 0) {
return num.toFixed(2);
}

// Sinon, formate avec des virgules
return num.toString().replace(/B(?=(d{3})+(?!d))/g, « , »);
}

// Chargement des données
Promise.all([d3.json(« //assets-decodeurs.lemonde.fr/decodeurs/assets/municipales2026/brt_3500.json »), d3.csv(« //assets-decodeurs.lemonde.fr/decodeurs/assets/municipales2026/commplus3500.csv »), d3.json(« //assets-decodeurs.lemonde.fr/decodeurs/municipales_2026_snippets/municipales/exports/2026/T1/listes_en_tete.json »), d3.csv(« //assets-decodeurs.lemonde.fr/sheets/wAn2GJcj3n7zw3Ivl7sP8tBTw6KHCg_3917.csv »), d3.csv(« //assets-decodeurs.lemonde.fr/sheets/wAn2GJcj3n7zw3Ivl7sP8tBTw6KHCg_3918.csv »), d3.csv(« //assets-decodeurs.lemonde.fr/sheets/wAn2GJcj3n7zw3Ivl7sP8tBTw6KHCg_3985.csv »)]).then(
function ([geoData, popData, resultData, nuancesMin, nuancesLM, blocsData]) {
const dicoPop = {},
listeComm = [],
listeBlocs = [],
listeNuancesLM = [];

let decompteBlocs = {},
decompteNuances = {},
decompteTours = {},
totalBubulles = 0,
resultBubulles = 0,
totalPop = 0,
resultPop = 0;

// si on a des résultats
if (resultData.length > 0) {
// quelques textes fixes
document.querySelector(« #titre_tetesliste »).innerHTML = textes_dec.leg_tetesliste + asterisque;
document.querySelector(« #titre_nuanceliste »).innerHTML = textes_dec.leg_blocs;
} else {
document.querySelector(« #titre_tetesliste »).innerHTML = textes_dec.pasresultat;
d3.selectAll(« #titre_nuanceliste, #dec_deuxieme_para »).style(« display », « none »);
}

// mes dicos

const resultDict = resultData.reduce((acc, current) => {
acc[current.code_circo] = current;
return acc;
}, {}),
dicoMin = nuancesMin.reduce((acc, e) => {
e.n = +e.n;
acc[e.code] = e;
return acc;
}, {}),
dicoLM = nuancesLM.reduce((acc, e) => {
e.n = +e.n;
acc[e.code] = e;
return acc;
}, {}),
dicoBloc = blocsData.reduce((acc, e) => {
e.n = +e.n;
acc[e.bloc] = e;
return acc;
}, {});

function listerLesListes(circonscription, maxListes) {
if (!maxListes) {
maxListes = 10;
}

var html= »

    « ;

    // Vérifier chaque liste jusqu’à la liste 10
    for (let i = 1; i <= maxListes; i++) {
    const voix = circonscription[`liste_${i}_voix`];

    // Si les voix existent, ajouter les informations de la liste
    if (voix != null) {
    // On vérifie si voix n’est pas null
    const d = {
    nuance: circonscription[`liste_${i}_nuance`] ? circonscription[`liste_${i}_nuance`] : « LDIV »,
    nuance_lemonde: circonscription[`liste_${i}_nuance_lemonde`] ? circonscription[`liste_${i}_nuance_lemonde`] : null,
    voix: voix,
    elus: circonscription[`liste_${i}_elus`],
    qualifie: circonscription[`liste_${i}_qualifie`],
    tete: circonscription[`liste_${i}_tete`],
    };

    const prct = (+d.voix / +circonscription.mentions_exprimes) * 100;
    // Utilisation de la nuance pour le style, même si elle peut être nulle
    var couleur = dicoMin[d.nuance] ? dicoMin[d.nuance].couleur : «  »,
    couleur_dark = dicoMin[d.nuance]?.couleur_dark ?? «  »;

    // la couleur de la nuance LM d’abord
    if (dicoLM[d.nuance_lemonde]) {
    var couleur = dicoLM[d.nuance_lemonde] ? dicoLM[d.nuance_lemonde].couleur : «  »,
    couleur_dark = dicoLM[d.nuance_lemonde]?.couleur_dark ?? «  »;
    }

    // Couleur par défaut si nuance est nulle
    var nomLong = dicoMin[d.nuance] ? dicoMin[d.nuance].nom_long : « Liste inconnue »;
    var nomCourt = dicoMin[d.nuance] ? dicoMin[d.nuance].nom_court : « DIV »;
    if (langue == « en ») {
    var nomLong = dicoMin[d.nuance] ? dicoMin[d.nuance].nom_long_en : « Unkown list »;
    var nomCourt = dicoMin[d.nuance] ? dicoMin[d.nuance].nom_court_en : « MISC. »;
    }
    var classQualif = «  »,
    texteElu = «  »,
    rabe_nuance_lm = «  »,
    badge = «  »;

    if (d.qualifie) {
    classQualif =  » municipalesResults__resultItem__nom–enabled »;
    }
    // si on a une liste élue au premier tour
    if (circonscription.status == « Elue » && i == 1) {
    classQualif =  » municipalesResults__resultItem__nom–enabled »;
    texteElu = extraireTitre(d.tete) +  » « ;
    badge= » ‘;
    }
    tetedeliste = d.tete;
    if (langue == « en ») {
    tetedeliste = d.tete.replace(« M. « , «  »).replace(« Mme « , «  »);
    }

    // on vérifie si on n’a pas une tête de liste nuancée par LM,
    // et on l’ajoute au besoin
    if (d.nuance_lemonde) {
    if (d.nuance_lemonde != d.nuance) {
    if (langue == « fr ») {
    rabe_nuance_lm = « ,  » + textes_dec.tetedeliste +  »  » + dicoLM[d.nuance_lemonde].nom_court;
    } else {
    rabe_nuance_lm = dicoLM[d.nuance_lemonde] ? dicoLM[d.nuance_lemonde].nom_court_en : «  »;
    }
    }
    }

    // Composition du HTML
    html += ‘

  1. ‘;
    html +=  »;
    // la liste
    html += ‘
    ‘;
    html += ‘
    ‘;
    html += ‘

    ‘ + tetedeliste + texteElu + badge;
    if (langue == « fr ») {
    html +=  » ( » + nomCourt + rabe_nuance_lm + « ) « ;
    } else {
    html +=  » ( » + (rabe_nuance_lm ? rabe_nuance_lm : nomCourt) + « ) « ;
    }
    html += « 

    « ;
    html += ‘

    ‘ + (langue == « fr » ? milliers(prct) : thousands(prct)) + textes_dec.prct +  » –  » + (langue == « fr » ? milliers(d.voix) : thousands(d.voix)) +  »  » + textes_dec.voix + « 

    « ;
    html += « 

    « ;
    html += « 

    « ;
    html += « 

  2. « ;
    }
    }
    html += « 

« ;
return html;
}

// Remplir le dictionnaire de population
popData.forEach((row) => (dicoPop[row.code_insee] = +row[« PMUN »]));

// Paramètres pour les cercles
const minPop = 3000,
maxPop = d3.max(popData, (d) => +d[« PMUN »]),
valeursCercles = mobileDec ? [1, largeur / 38] : [1.5, largeur / 36],
unite = valeursCercles[1],
echelleRayon = d3.scaleSqrt().domain([minPop, maxPop]).range([valeursCercles[0], valeursCercles[1]]).clamp(true);

// Extraction des points et départements
const points = topojson.feature(geoData, geoData.objects.p_com),
departements = topojson.feature(geoData, geoData.objects.a_dept);

// Projection et path
const projection = d3
.geoIdentity()
.reflectY(true)
.fitSize([largeur * 0.95, largeur * ratioMun * 0.9], points),
path = d3.geoPath().projection(projection);

// Dessiner les départements/la Fronce
// dec_dept.selectAll(« .departement »).data(departements.features).enter().append(« path »).attr(« class », « departement »).attr(« d », path);

dec_dept
.append(« path »)
.attr(« class », « departement »)
.attr(« d », path(topojson.merge(geoData, geoData.objects.a_dept.geometries)));

// la légende
const legende = svg
.append(« g »)
.attr(« class », « legende passelect »)
.attr(« transform », « translate( » + largeur * 0.075 + « ,  » + hauteur * 0.075 + « ) »);

const valeursLeg = mobileDec ? [200000, 2000000] : [100000, 700000, 2000000],
xLabel = largeur * 0.08;

legende
.selectAll(« circle.leg_stroke »)
.data(valeursLeg)
.join(« circle »)
.attr(« class », « leg_stroke »)
.attr(« cx », 0)
.attr(« cy », (d) => -echelleRayon(d))
.attr(« r », (d) => echelleRayon(d))
.style(« fill », « none »);

legende
.selectAll(« line.leg_stroke »)
.data(valeursLeg)
.join(« line »)
.attr(« class », « leg_stroke »)
.attr(« x1 », 0)
.attr(« x2 », xLabel)
.attr(« y1 », (d) => -echelleRayon(d) * 2)
.attr(« y2 », (d, i) => -echelleRayon(d) * 2)
.style(« stroke-dasharray », « 2,2 »);

legende
.selectAll(« text.texte_leg »)
.data(valeursLeg)
.join(« text »)
.attr(« class », « texte_leg »)
.attr(« x », xLabel * 1.1)
.attr(« y », (d) => 3 + -echelleRayon(d) * 2)
.text(function (d) {
if (langue == « fr ») {
return milliers(d) + (d >= 1000000 ?  » d’hab. » : «  »);
} else {
return thousands(d) + (d >= 1000000 ?  » inhab. » : «  »);
}
});

// Préparation des données pour les cercles
// + la liste pour le menu déroulant
const circlesData = points.features
.filter((d, i) => dicoPop[d.properties.c] !== undefined /*&& i < 600*/)
.map((d) => {
listeComm.push({
label: { name: d.properties.l +  » ( » + d.properties.d + « ) » },
data: { name: d.properties.l, dept: d.properties.d, c: d.properties.c },
});

const pop = dicoPop[d.properties.c] || 0;
var radiusTemp = 1;
// variables
if (pop) {
radiusTemp = echelleRayon(pop);
}
cXtemp = projection(d.geometry.coordinates)[0];
cYtemp = projection(d.geometry.coordinates)[1];
if (dicoPosition[d.properties.c]) {
tempProj = projection([dicoPosition[d.properties.c][0], dicoPosition[d.properties.c][1]]);
cXtemp = tempProj[0];
cYtemp = tempProj[1];
}

return {
cx: cXtemp,
cy: cYtemp,
r: radiusTemp,
l: d.properties.l,
d: d.properties.d,
id: d.properties.c,
p: dicoPop[d.properties.c],
};
});

const simulation = d3
.forceSimulation(circlesData)
.force(« x », d3.forceX((d) => d.cx).strength(0.5))
.force(« y », d3.forceY((d) => d.cy).strength(0.5))
.force(« collide », d3.forceCollide((d) => d.r + 0.2).strength(0.5))
.on(« tick », ticked)
.on(« end », () => {
simulation.stop();
});

// Dessin des cercles
const circles = dec_communes
.selectAll(« circle »)
.data(circlesData)
.enter()
.append(« circle »)
.attr(« class », function (d, i) {
if (resultDict[d.id]) {
resultBubulles++;
resultPop += d.p;
}
totalBubulles++;
totalPop += d.p;
return resultDict[d.id] ? « bubulle resultat » : « bubulle sansresultat »;
})
.attr(« data-insee », (d) => d.id)
.attr(« r », (d) => d.r)
.attr(« data-tt », function (d) {
if (resultDict[d.id]) {
e = resultDict[d.id];
let html= »

 » + d.l +  » ( » + d.d + « )

« ;
if (e) {
html += listerLesListes(e, 5);
} else {
html += ‘

‘ + textes_dec.pasresultat + « 

« ;
}
html +=  »;
html += ‘

‘ + textes_dec.pop_mun +  »  » + (langue == « fr » ? milliers(d.p) : thousands(d.p)) +  »  » + textes_dec.hab + « 

« ;
return html;
} else {
let html= »

 » + d.l +  » ( » + d.d + « )

« ;
html += « 

 » + textes_dec.noresult + « 

« ;
html +=  »;
html += ‘

‘ + textes_dec.pop_mun +  »  » + (langue == « fr » ? milliers(d.p) : thousands(d.p)) +  »  » + textes_dec.hab + « 

« ;
return html;
}
})
.attr(« data-bloc », function (d) {
if (resultDict[d.id]) {
if (dicoLM[resultDict[d.id].liste_1_nuance]) {
if (dicoLM[resultDict[d.id].liste_1_nuance].bloc) {
// on remplit le décompte
if (!decompteBlocs[dicoLM[resultDict[d.id].liste_1_nuance].bloc]) {
decompteBlocs[dicoLM[resultDict[d.id].liste_1_nuance].bloc] = 0;
}
decompteBlocs[dicoLM[resultDict[d.id].liste_1_nuance].bloc]++;
return dicoLM[resultDict[d.id].liste_1_nuance].bloc;
}
} else if (dicoMin[resultDict[d.id].liste_1_nuance]) {
if (dicoMin[resultDict[d.id].liste_1_nuance].bloc) {
// on remplit le décompte ministère
if (!decompteBlocs[dicoMin[resultDict[d.id].liste_1_nuance].bloc]) {
decompteBlocs[dicoMin[resultDict[d.id].liste_1_nuance].bloc] = 0;
}
decompteBlocs[dicoMin[resultDict[d.id].liste_1_nuance].bloc]++;
return dicoMin[resultDict[d.id].liste_1_nuance].bloc;
}
}
}
})
.attr(« data-nuance-lm », function (d) {
if (resultDict[d.id] && resultDict[d.id].liste_1_nuance_lemonde) {
// on remplit le décompte LM
if (!decompteNuances[resultDict[d.id].liste_1_nuance_lemonde]) {
decompteNuances[resultDict[d.id].liste_1_nuance_lemonde] = 0;
}
decompteNuances[resultDict[d.id].liste_1_nuance_lemonde]++;
return resultDict[d.id].liste_1_nuance_lemonde;
} else if (resultDict[d.id] && resultDict[d.id].liste_1_nuance) {
if (!decompteNuances[resultDict[d.id].liste_1_nuance]) {
decompteNuances[resultDict[d.id].liste_1_nuance] = 0;
}
decompteNuances[resultDict[d.id].liste_1_nuance]++;
return resultDict[d.id].liste_1_nuance;
}
})
.attr(« data-status », function (d) {
if (resultDict[d.id]) {
// je fais le compte
if (!decompteTours[resultDict[d.id].status]) {
decompteTours[resultDict[d.id].status] = 0;
}
decompteTours[resultDict[d.id].status]++;
// je remplis
return resultDict[d.id] ? resultDict[d.id].status : «  »;
}
})
.attr(« style », function (d) {
if (resultDict[d.id]) {
// je stocke les blocs depuis les nuances LM
if (dicoLM[resultDict[d.id].liste_1_nuance]) {
if (listeBlocs.indexOf(dicoLM[resultDict[d.id].liste_1_nuance].bloc) == -1) {
listeBlocs.push(dicoLM[resultDict[d.id].liste_1_nuance].bloc);
}
} else if (dicoMin[resultDict[d.id].liste_1_nuance]) {
// les blocs depuis les nuances ministère
if (listeBlocs.indexOf(dicoMin[resultDict[d.id].liste_1_nuance].bloc) == -1) {
listeBlocs.push(dicoMin[resultDict[d.id].liste_1_nuance].bloc);
}
} else {
console.warn(« ouille, pas de nauance « intérieur » », d.id, resultDict[d.id].liste_1_nuance);
}
if (resultDict[d.id].liste_1_nuance_lemonde) {
if (listeNuancesLM.indexOf(resultDict[d.id].liste_1_nuance_lemonde) == -1) {
listeNuancesLM.push(resultDict[d.id].liste_1_nuance_lemonde);
}
}
if (dicoLM[resultDict[d.id].liste_1_nuance_lemonde]) {
styleCercle = « –color:  » + dicoLM[resultDict[d.id].liste_1_nuance_lemonde].couleur + « ; –dark-color:  » + dicoLM[resultDict[d.id].liste_1_nuance_lemonde].couleur_dark + « ;–stroke-color: » + d3.rgb(dicoLM[resultDict[d.id].liste_1_nuance_lemonde].couleur).darker(1) + « ;–stroke-dark-color: » + d3.rgb(dicoLM[resultDict[d.id].liste_1_nuance_lemonde].couleur_dark).darker(1) + « ; »;
} else if (dicoMin[resultDict[d.id].liste_1_nuance]) {
styleCercle = « –color:  » + dicoMin[resultDict[d.id].liste_1_nuance].couleur + « ; –dark-color:  » + dicoMin[resultDict[d.id].liste_1_nuance].couleur_dark + « ;–stroke-color: » + d3.rgb(dicoMin[resultDict[d.id].liste_1_nuance].couleur).darker(1) + « ;–stroke-dark-color: » + d3.rgb(dicoMin[resultDict[d.id].liste_1_nuance].couleur_dark).darker(1) + « ; »;
}
return styleCercle;
} else {
return « –color: #323232; –dark-color: #A4A4A4;–stroke-color:#636363;–stroke-dark-color:#cdcdcd; »;
}
})
.style(« stroke-width », (d) => (resultDict[d.id] ? 0 : 0.25))
.on(« mouseover », function () {
d3.select(this).style(« stroke-width », 1.5);
if (!mobileDec) {
showHoverTooltip(this);
}
})
.on(« mouseout », function () {
d3.select(this).style(« stroke-width », (d) => (resultDict[d.id] ? 0 : 0.25));
removeHoverTooltip();
});

// j’ajoute le texte pour les DROM
if (langue == « fr ») {
var listeDrom = [
// ligne du bas
{ nom: « S.-Pierre-et-M. », nom_court: « SPM », hauteur: 0.93, largeur: 0.2 },
{ nom: « Nouvelle-Calédonie », nom_court: « N.-Calédonie », hauteur: 0.93, largeur: 0.39 },
{ nom: « Polynésie fr. », nom_court: « Poly. fr. », hauteur: 0.93, largeur: 0.57 },
{ nom: « Mayotte », nom_court: « Mayotte », hauteur: 0.93, largeur: 0.74 },
// ligne du haut ligne
{ nom: « Guadeloupe », nom_court: « Guadeloupe », hauteur: 0.73, largeur: 0.2 },
{ nom: « Martinique », nom_court: « Martinique », hauteur: 0.73, largeur: 0.37 },
{ nom: « Guyane », nom_court: « Guyane », hauteur: 0.73, largeur: 0.55 },
{ nom: « La Réunion », nom_court: « Réunion », hauteur: 0.73, largeur: 0.74 },
];
} else {
var listeDrom = [
// ligne du bas
{ nom: « St. Pierre and M. », nom_court: « SPM », hauteur: 0.93, largeur: 0.2 },
{ nom: « New Caledonia », nom_court: « N-Caledonia », hauteur: 0.93, largeur: 0.39 },
{ nom: « French Polynesia », nom_court: « Fr Polynesia », hauteur: 0.93, largeur: 0.57 },
{ nom: « Mayotte », nom_court: « Mayotte », hauteur: 0.93, largeur: 0.74 },
// ligne du haut ligne
{ nom: « Guadeloupe », nom_court: « Guadeloupe », hauteur: 0.73, largeur: 0.2 },
{ nom: « Martinique », nom_court: « Martinique », hauteur: 0.73, largeur: 0.37 },
{ nom: « French Guiana », nom_court: « Fr Guiana », hauteur: 0.73, largeur: 0.55 },
{ nom: « La Réunion », nom_court: « Réunion », hauteur: 0.73, largeur: 0.74 },
];
}

dec_textes
.selectAll(« text.ile »)
.data(listeDrom)
.enter()
.append(« text »)
.attr(« class », « ile passelect »)
.attr(« x », (d) => largeur * d.largeur)
.attr(« y », (d) => hauteur * (mobileDec ? d.hauteur – 0.02 : d.hauteur))
.text((d) => (mobileDec ? d.nom_court : d.nom));

// après la création des bubulles, on peut remplir
// le premier para avec quelques infos au long
if (langue == « fr » && resultData.length > 0) {
document.querySelector(« #dec_premier_para »).innerHTML =
« Cette carte de France présente les résultats du premier tour des municipales dans  » +
milliers(resultBubulles) +
 » commune » +
(resultData.length >= 2 ? « s » : «  ») +
 » française » +
(resultData.length >= 2 ? « s » : «  ») +
 » représentant  » +
milliers(resultPop) +
(resultPop < 1000000 ?  »  » :  » d’ ») +
« habitants ; cela représente  » +
milliers((resultBubulles / totalBubulles) * 100) +
 » % de communes dépouillées sur  » +
milliers(totalBubulles) +
« . Parmi elles,  » +
milliers(decompteTours[« Elue »]) +
 » ont déjà une liste élue avec plus de 50 % des voix au premier tour,  » +
milliers(decompteTours[« Non elue »]) +
 » devront organiser un second tour le dimanche 22 mars, soit  » +
milliers((decompteTours[« Non elue »] / (decompteTours[« Non elue »] + decompteTours[« Elue »])) * 100) +
 » % de celles dont nous avons les résultats. »;
} else {
document.getElementById(« dec_premier_para »).remove();
}

/*██ /██ /██
| ██ /██__/ | ██
| ██ /██████ /██████ /██████ /███████ /███████ /██████
| ██ /██__ ██ /██__ ██ /██__ ██| ██__ ██ /██__ ██ /██__ ██
| ██| ████████| ██ ██| ████████| ██ ██| ██ | ██| ████████
| ██| ██_____/| ██ | ██| ██_____/| ██ | ██| ██ | ██| ██_____/
| ██| ███████| ███████| ███████| ██ | ██| ███████| ███████
|__/ _______/ ____ ██ _______/|__/ |__/ _______/ _______/
/██ ██
| ██████/ *INTERACTIVE
_____*/

if (resultData.length > 0) {
var legendeBlocs= »

 » + textes_dec.toutafficher + « 

« ;

listeBlocs.sort((a, b) => {
const valA = dicoBloc[a] ? dicoBloc[a].n : Infinity;
const valB = dicoBloc[b] ? dicoBloc[b].n : Infinity;
return valA – valB;
});

for (const [i, d] of listeBlocs.entries()) {
if (dicoBloc[d]) {
e = dicoBloc[d];
if (decompteBlocs[e.bloc] >= minLegende) {
legendeBlocs += ‘

‘ + (langue == « fr » ? e.bloc : e.bloc_en) + ‘ ‘ + decompteBlocs[e.bloc] + « 

« ;
}
}
}

legendeCouleurs = d3.select(« #dec_leg_min »).html(legendeBlocs);

// on va faire aussi une légende avec les nuances LM
var legendeNuancesLM = ‘

‘ + textes_dec.toutafficher + « 

« ;

listeNuancesLM.sort((a, b) => {
const valA = dicoLM[a] ? dicoLM[a].n : Infinity;
const valB = dicoLM[b] ? dicoLM[b].n : Infinity;
return valA – valB;
});

for (const [i, d] of listeNuancesLM.entries()) {
if (dicoLM[d]) {
e = dicoLM[d];
if (decompteNuances[e.code] >= minLegende) {
legendeNuancesLM += ‘

‘ + (langue == « fr » ? e.nom_court : e.nom_court_en) + ‘ ‘ + decompteNuances[e.code] + « 

« ;
}
}
}

legendeCouleursLM = d3.select(« #dec_leg_lm »).html(legendeNuancesLM);

var leg_tours= »

 » + textes_dec.toutafficher + « 

« ;
leg_tours += ‘

‘ + textes_dec.liste_elue +  »  » + (langue == « fr » ? milliers(decompteTours[« Elue »]) : thousands(decompteTours[« Elue »])) + « 

« ;
leg_tours += ‘

‘ + textes_dec.deuxiemetour +  »  » + (langue == « fr » ? milliers(decompteTours[« Non elue »]) : thousands(decompteTours[« Non elue »])) + « 

« ;

var legendeStatus = d3.select(« #dec_leg_elu »).html(leg_tours);

// Variables pour stocker les sélections
var selectedStatus = « tout »;
var selectedBloc = « tout »;
var selectedNuanceLM = « tout »;

// Fonction de filtrage combiné
function filterCircles() {
svg.selectAll(« circle.bubulle »).classed(« opacified », function () {
var currentStatus = d3.select(this).attr(« data-status »);
var currentNuance = d3.select(this).attr(« data-bloc »);
var currentNuanceLM = d3.select(this).attr(« data-nuance-lm »);

// Condition pour vérifier si le cercle doit être affiché
var isVisible = (currentStatus === selectedStatus || selectedStatus === « tout ») && (currentNuance === selectedBloc || selectedBloc === « tout ») && (currentNuanceLM === selectedNuanceLM || selectedNuanceLM === « tout »);

return !isVisible;
});
}

// Interaction pour le status
legendeStatus.selectAll(« .dec_status »).on(« click », function () {
d3.selectAll(« .tooltipbrt »).remove();
d3.selectAll(« #dec_leg_elu .dec_status »).classed(« choisi », false);
d3.select(this).classed(« choisi », true);

selectedStatus = d3.select(this).attr(« data-status »);
filterCircles();
});

// Interaction pour les nuances
legendeCouleurs.selectAll(« .dec_nuance_min »).on(« click », function () {
d3.selectAll(« .tooltipbrt »).remove();
d3.selectAll(« .dec_nuance_min »).classed(« choisi », false);
d3.select(this).classed(« choisi », true);

selectedBloc = d3.select(this).attr(« data-bloc »);
selectedNuanceLM = « tout »;
d3.selectAll(« .dec_nuance_lm »).classed(« choisi », false);
d3.selectAll(« .dec_nuance_lm.latotale »).classed(« choisi », true);
d3.select(this).classed(« choisi », true);
filterCircles();
});

// Interaction pour nuance LM
legendeCouleursLM.selectAll(« .dec_nuance_lm »).on(« click », function () {
d3.selectAll(« .tooltipbrt »).remove();
d3.selectAll(« #dec_leg_lm .dec_nuance_lm »).classed(« choisi », false);
d3.select(this).classed(« choisi », true);

selectedNuanceLM = d3.select(this).attr(« data-nuance-lm »);
selectedBloc = « tout »;
d3.selectAll(« .dec_nuance_min »).classed(« choisi », false);
d3.selectAll(« .dec_nuance_min.latotale »).classed(« choisi », true);
filterCircles();
});
}

// fin de la gestion de la légende interactive

// Mise à jour des positions des cercles
function ticked() {
circles.attr(« cx », (d) => d.x).attr(« cy », (d) => d.y);
}

// Initialisation du tooltip
const { showTooltipManually, showTooltip } = initTooltipBrt(« #dec_mun .bubulle »);

// la fonction qui traite les résultats
const func_to_treat_result_donnees = (result) => {
setTimeout(() => d3.selectAll(« #dec_mun circle.bubulle »).classed(« opacified », (d) => d.id !== result.data.c), 25);

const selectedCircle = document.querySelector(`#dec_mun circle.bubulle[data-insee= »${result.data.c} »]`);
if (selectedCircle) {
showTooltipManually(selectedCircle);
selectedCircle.scrollIntoView({ behavior: « smooth », block: « center » });
}
};

// Initialisation de l’autocomplete
if (langue == « fr ») {
autocomplete_decodeurs(« search_mun », listeComm, func_to_treat_result_donnees, reset_func, (x) => `${x.label.name}`, 6, 3, slugify, « Aucune commune avec ce nom »);
} else {
autocomplete_decodeurs(« search_mun », listeComm, func_to_treat_result_donnees, reset_func, (x) => `${x.label.name}`, 6, 3, slugify, « No town or city with that name »);
}
// quand on tape échap
document.addEventListener(« keydown », function (event) {
if (event.key === « Escape ») {
reset_func();
const circles = getA(« #dec_mun circle.bubulle »);

forEach(circles, function (circle) {
circle.classList.remove(« selected-commune »);
});
d3.selectAll(« .tooltipbrt »).remove();
}
});

// Gestionnaire pour fermer le tooltip en cliquant ailleurs
document.addEventListener(« click », function (e) {
if (!e.target.closest(« #dec_mun circle ») && !e.target.closest(« #search_mun »)) {
const circles = getA(« #dec_mun circle.bubulle »);
forEach(circles, function (circle) {
circle.classList.remove(« selected-commune »);
});
}
});
},
);
}

// Fonction pour réinitialiser la carte
const reset_func = () => {
document.querySelector(« #search_mun input »).value = «  »;
d3.selectAll(« #dec_mun circle.bubulle »).classed(« opacified », false);
d3.select(« #search_mun .lmui-search__button »).style(« visibility », « visible »);
d3.select(« #search_mun .lmui-search__reset-button »).style(« visibility », « hidden »);
// on remet à zéro les filtres
d3.selectAll(« .dec_nuance »).classed(« choisi », false);
d3.selectAll(« .dec_nuance.latotale »).classed(« choisi », true);
var selectedStatus = « tout »;
var selectedBloc = « tout »;
var selectedNuanceLM = « tout »;
};

dessinerBubulles(langue);

Les résultats des principaux partis

–>
`;

// Legende blocs
blocs.forEach((bloc) => {
const colors = blocColors[bloc] || {};
const blocName = langue == « fr-FR » ? bloc : blocColors[bloc].bloc_en;
const couleur = isDark
? (colors.couleur_dark || colors.couleur || ‘#888’)
: (colors.couleur || colors.couleur_dark || ‘#888’);
toFill += `

${blocName}

`;
});
toFill += `

${textsMap[`color_note_${currentBloc}`]}

${textsMap.precision_methodo}

`;
htmlContainer.innerHTML = toFill;
}

function inittooltipdecodeurs(selector) {
let currentTooltip = null;
function removeCurrentTooltip() {
if (currentTooltip) {
currentTooltip.remove();
currentTooltip = null;
}
}
function showTooltip(elmt) {
removeCurrentTooltip();

let ttFixe = document.createElement(« div »);
ttFixe.classList.add(« tooltipdecodeurs »);
ttFixe.innerHTML = elmt.getAttribute(« data-tt ») + « ;

document.body.appendChild(ttFixe);

// Ne pas appliquer de positionnement en JavaScript en mobile
if (!isMobile) {
const rect = elmt.getBoundingClientRect();
ttFixe.style.top = rect.top – 14 + window.scrollY – ttFixe.offsetHeight + « px »;
ttFixe.style.left = rect.left + rect.width / 2 – ttFixe.offsetWidth / 2 + 2 + « px »;
}

// Ajouter un gestionnaire pour fermer le tooltip en cliquant sur la croix en mobile
ttFixe.addEventListener(« click », function (e) {
if (e.target.tagName === « DIV » && e.target.classList.contains(« tooltipdecodeurs ») && window.innerWidth <= 600) {
removeCurrentTooltip();
reset_func();
}
});

currentTooltip = ttFixe;
return ttFixe;
}

// Sélectionne les éléments
let ttFixe_elmts = getA(selector);

// Ajoute les événements
forEach(ttFixe_elmts, function (elmt) {
elmt.addEventListener(« mouseover », function () {
showTooltip(this);
});

elmt.addEventListener(« mouseout », function () {
// Ne supprime pas le tooltip si on a cliqué sur une commune
if (!this.classList.contains(« selected-commune »)) {
removeCurrentTooltip();
}
});
});

// Retourne la fonction pour afficher le tooltip manuellement
return function (elmt) {
// Marque le cercle comme sélectionné
forEach(ttFixe_elmts, function (e) {
e.classList.remove(« selected-commune »);
});
elmt.classList.add(« selected-commune »);

return showTooltip(elmt);
};
}

const formatNb = (number, locale= »fr-FR », round = 2, style= »decimal ») => {
return `${number.toLocaleString(locale, { maximumFractionDigits: round, style })}`;
};

async function drawMap(langue) {

// Variables globales

const largeur = document.getElementById(`dec_mun_prct_${snippetId}`).offsetWidth;
const isMobile = largeur < 600;
const ratioMun = isMobile ? 1.4 : 1.2;
const hauteur = largeur * ratioMun;
const isDark = document.querySelector(« html »).getAttribute(« data-color-mode ») === « dark »;

// Initialisation du SVG

const svg = d3
.select(`#dec_mun_prct_${snippetId}`)
.append(« svg »)
.attr(« width », « 100% »)
.attr(« viewBox », `${largeur * -0.025} ${largeur * ratioMun * -0.05} ${largeur} ${largeur * ratioMun}`);

const decDept = svg.append(« g »);
const decCommunes = svg.append(« g »);
const decTextes = svg.append(« g »);

// Données

const geoData = await d3.json(« //assets-decodeurs.lemonde.fr/decodeurs/assets/municipales2026/brt_3500.json »);
const popData = await d3.csv(« //assets-decodeurs.lemonde.fr/decodeurs/assets/municipales2026/commplus3500.csv »);

const dicoPop = {};
const listeComm = [];
function agregerResultats(datasetsArray) {
const dictGlobal = {};
(datasetsArray || []).forEach(dataset => {
if (!dataset?.listes_communes) return;
const nuanceCode = dataset.code;

dataset.listes_communes.forEach(current => {
if (!current || current.code_circo == null) return;
const id = current.code_circo;
const statusCommune = current.status;
if (!dictGlobal[id]) {
dictGlobal[id] = {
…current,
voix: 0,
status: null,
listes: [],
detailListes: [],
};
}
const isEnAttente = statusCommune === « En attente »;
if (isEnAttente) {
if (dictGlobal[id].status !== « disponible ») {
dictGlobal[id].status = « en_attente »;
}
return;
}
// Résultats disponibles : agrégation bloc
const voixListe = +current.voix || 0;
dictGlobal[id].voix += voixListe;
dictGlobal[id].status = « disponible »;
if (nuanceCode && !dictGlobal[id].listes.includes(nuanceCode)) {
dictGlobal[id].listes.push(nuanceCode);
}
// Détail par liste pour cette commune
const exprimés = current.mentions?.exprimes;
const hasExprimes = exprimés != null && +exprimés > 0;
const prctListe = hasExprimes && voixListe > 0
? (voixListe / +exprimés) * 100
: 0;
const tete = current.tete || {};
const eluPremierTour = current.ordre === 1 && current.pourvu === « T1 »;
dictGlobal[id].detailListes.push({
nuance: nuanceCode,
nuance_lemonde: current.nuance_lemonde ?? null,
voix: voixListe,
prct: prctListe,
tete: {
nom: tete.nom || null,
prenom: tete.prenom || null,
civ: tete.civ || null,
},
eluPremierTour,
});
});
});
// Calcul du % global bloc par commune
Object.values(dictGlobal).forEach(commune => {
const exprimés = commune.mentions?.exprimes;
const hasExprimes = exprimés != null && +exprimés > 0;
if (commune.status === « disponible » && commune.voix != null && hasExprimes) {
commune.prct = (+commune.voix / +exprimés) * 100;
} else if (commune.status === « en_attente ») {
commune.prct = null;
} else {
commune.prct = 0;
}
});
return dictGlobal;
}

// Application de l’agrégation
const datasets = (dataByBloc[currentBloc] || [])
.map(d => d.nuanceData)
.filter(Boolean);

const result2026Dict = agregerResultats(datasets);

// console.log(« ——result2026Dict », result2026Dict);

const allPrctValues = [
…Object.values(result2026Dict).map(d => d.prct),
];

const maxPrct = typeof d3.max(allPrctValues) == ‘undefined’ || d3.max(allPrctValues) < 10 ? 10 : d3.max(allPrctValues);

// Adapter la couleur en fonction du bloc
function getBlocColorMax(bloc) {
const colors = blocColors[bloc];
const couleur = colors?.couleur;
const couleurDark = colors?.couleur_dark;
if (!couleur && !couleurDark) return colorMax;
return isDark ? (d3.rgb(couleurDark).darker(1) || d3.rgb(couleur).darker(1)) : (d3.rgb(couleur).darker(1) || d3.rgb(couleurDark).darker(1));
}

let colorScale = d3.scaleSequential(
d3.interpolate(colorMin, getBlocColorMax(currentBloc))
).domain([0, maxPrct]);

// Création de la légende colorée
const legendWidth = isMobile ? largeur * 0.7 : largeur * 0.5;
const legendHeight = isMobile ? 10 : 12;
const legendMargin = { top: isMobile ? 18 : 15, right: isMobile ? 40 : 20, bottom: 18, left: isMobile ? 12 : 20 };

const legendSvg = d3.select(« .legend_color »)
.append(« svg »)
.attr(« width », « 100% »)
.attr(« height », legendHeight + legendMargin.top + legendMargin.bottom);

const legendContainerEl = document.querySelector(`.dec_mun_prct_container[data-attr=${snippetId}] .legend_color`);
const legendContainerWidth = legendContainerEl ? legendContainerEl.offsetWidth : largeur;

const legendRoot = legendSvg.append(« g »)
.attr(« transform », `translate(${(legendContainerWidth / 2) – (legendWidth / 2)}, ${legendMargin.top})`);

const legendScore = legendRoot.append(« g »)
.attr(« id », « legend_score »);

// dégradé score
const gradientId = `score-gradient-${snippetId}`;
const gradient = legendSvg.append(« defs »)
.append(« linearGradient »)
.attr(« id », gradientId)
.attr(« x1 », « 0% »)
.attr(« x2 », « 100% »);

// Ajout des stops de couleur
const numStops = 10;
for (let i = 0; i <= numStops; i++) {
const value = (i / numStops) * maxPrct;
gradient.append(« stop »)
.attr(« offset », `${(i / numStops) * 100}%`)
.attr(« stop-color », colorScale(value));
}

// Barre de dégradé
legendScore.append(« rect »)
.attr(« class », « legend_color_bar »)
.attr(« width », legendWidth)
.attr(« height », legendHeight)
.style(« fill », `url(#${gradientId})`);

// Labels min et max
const legendScale = d3.scaleLinear()
.domain([0, maxPrct])
.range([0, legendWidth]);

const legendAxis = d3.axisBottom(legendScale)
.ticks(6)
.tickFormat((d) => Math.round(d) +  » % »);

legendScore.append(« g »)
.attr(« class », « legend_color_axis »)
.attr(« transform », `translate(0, ${legendHeight})`)
.call(legendAxis)
.call(g => g.select(« .domain »).remove());

// Titre de la légende
legendScore.append(« text »)
.attr(« class », « legend_color_title »)
.attr(« x », legendWidth / 2)
.attr(« y », -5)
.attr(« text-anchor », « middle »)
.text(`${textsMap.titre_legende_score} ${textsMap.prct}`);

function makeTooltipContent(d) {
const result2026 = d.value2026;
const statusBloc = d.statusBloc || « aucune_liste »;
const detailBloc = d.detailBloc || [];

let html = `

${d.l} (${d.d})

`;
if (statusBloc === « en_attente ») {
html += `

${textsMap.en_attente}

`;
} else if (statusBloc === « aucune_liste ») {
html += `

${textsMap.aucune_liste_bloc}

`;
} else {
html += `

${result2026 != null ? `${formatNb(result2026, langue, 1)}${textsMap.suffrages} ${currentBloc}` : textsMap.pasresultat}

`;
}
if (statusBloc === « disponible » && detailBloc.length > 0) {
html += « ;
html += ‘

    ‘;
    detailBloc.forEach((l) => {
    const colors = nuanceCouleurs[l.nuance] ?? blocColors[currentBloc] ?? {};
    const couleur = colors.couleur || « #888″;
    const couleur_dark = colors.couleur_dark || couleur;
    const nomComplet = [l.tete?.civ, l.tete?.prenom, l.tete?.nom].filter(Boolean).join( » « );
    const prctStr = l.prct != null ? `${formatNb(l.prct, langue, 1)}${langue === « fr-FR » ?  » % » : « % »}` : «  »;
    const voixStr = `${formatNb(l.voix, langue, 0)} ${textsMap.voix}`;
    const classQualif =  » municipalesResults__resultItem__nom–enabled »;
    const badge = l.eluPremierTour
    ? ``
    : «  »;
    html += ‘
  1. ‘;
    html += « ;
    html += ‘
    ‘;

    let libelleNuance = nuanceNomCourt[l.nuance] ?? l.nuance ?? « – »;
    if (l.nuance_lemonde && l.nuance_lemonde !== l.nuance) {
    const nomCourtLM = nuanceNomCourt[l.nuance_lemonde] ?? l.nuance_lemonde;
    const teteDeListeLabel = textsMap.tete_de_liste ?? « tête de liste »;
    libelleNuance += « ,  » + teteDeListeLabel +  »  » + nomCourtLM;
    }
    html += `

    ${nomComplet} ${badge} (${libelleNuance})

    `;

    html += `

    ${prctStr} – ${voixStr}

    `;
    html += « 

  2. « ;
    });
    html += « 

« ;
}
html += `

${textsMap.pop_mun} ${formatNb(d.p, langue, 0)} ${textsMap.hab}

`;
return html;
}

// Remplir le dictionnaire de population
popData.forEach((row) => (dicoPop[row.code_insee] = +row[« PMUN »]));

// Paramètres pour les cercles
const minPop = 3000;
const maxPop = d3.max(popData, (d) => +d[« PMUN »]);
const exceptPLM = [« 75056 », « 13055 », « 69123 »];

const valeursCercles = isMobile ? [1, largeur / 38] : [1.5, largeur / 36];
// Échelle pour le rayon des cercles
const echelleRayon = d3
.scaleSqrt()
.domain([minPop, maxPop])
.range([valeursCercles[0], valeursCercles[1]])
.clamp(true);
// Extraction des points et départements
const points = topojson.feature(geoData, geoData.objects.p_com);
const departements = topojson.feature(geoData, geoData.objects.a_dept);

// Projection et path
const projection = d3
.geoIdentity()
.reflectY(true)
.fitSize([largeur * 0.95, largeur * ratioMun * 0.9], points);
const path = d3.geoPath().projection(projection);

// Dessin des départements
decDept.append(« path »).attr(« class », « departement »).attr(« d », path(topojson.merge(geoData, geoData.objects.a_dept.geometries)));

// la légende taille des cercles
const legende = svg
.append(« g »)
.attr(« class », « legende »)
.attr(« transform », « translate( » + largeur * 0.075 + « ,  » + hauteur * 0.075 + « ) »);

const valeursLeg = isMobile ? [200000, 2000000] : [100000, 700000, 2000000];
const xLabel = largeur * 0.08;

legende
.selectAll(« circle.leg_stroke »)
.data(valeursLeg)
.join(« circle »)
.attr(« class », « leg_stroke »)
.attr(« cx », 0)
.attr(« cy », (d) => -echelleRayon(d))
.attr(« r », (d) => echelleRayon(d))
.style(« fill », « none »);

legende
.selectAll(« line.leg_stroke »)
.data(valeursLeg)
.join(« line »)
.attr(« class », « leg_stroke »)
.attr(« x1 », 0)
.attr(« x2 », xLabel)
.attr(« y1 », (d) => -echelleRayon(d) * 2)
.attr(« y2 », (d, i) => -echelleRayon(d) * 2)
.style(« stroke-dasharray », « 2,2 »);

legende
.selectAll(« text.texte_leg »)
.data(valeursLeg)
.join(« text »)
.attr(« class », « texte_leg »)
.attr(« x », xLabel * 1.1)
.attr(« y », (d) => 3 + -echelleRayon(d) * 2)
.text(function (d) {
if (d >= 1000000) {
const millions = Math.round(d / 1000000);
if (langue === « fr-FR ») {
return `${millions} million${millions > 1 ? « s » : «  »} d’hab.`;
}
return `${millions} million inhab.`;
}
return formatNb(d, langue, 0);
});

// Préparation des données pour les cercles
const circlesData = points.features
.filter((d) => dicoPop[d.properties.c] !== undefined)
.map((d) => {
listeComm.push({
label: { name: d.properties.l +  » ( » + d.properties.d + « ) » },
data: { name: d.properties.l, dept: d.properties.d, c: d.properties.c },
});

const id = d.properties.c;
const pop = dicoPop[id] || 0;
let radiusTemp = pop ? echelleRayon(pop) : 1;

let cXtemp = projection(d.geometry.coordinates)[0];
let cYtemp = projection(d.geometry.coordinates)[1];
if (dicoPosition[id]) {
const tempProj = projection([dicoPosition[id][0], dicoPosition[id][1]]);
cXtemp = tempProj[0];
cYtemp = tempProj[1];
}

const resultEntry = result2026Dict[id];
const value2026 = resultEntry ? resultEntry.prct : null;
const statusBloc = resultEntry?.status ?? « aucune_liste »;
const listesBloc = resultEntry?.listes ?? [];
const detailBloc = resultEntry?.detailListes ?? [];

return {
cx: cXtemp,
cy: cYtemp,
r: radiusTemp,
l: d.properties.l,
d: d.properties.d,
id,
p: dicoPop[id],
value2026,
statusBloc,
listesBloc,
detailBloc,
};
});

// Dessin des cercles
const circles = decCommunes
.selectAll(« circle »)
.data(circlesData)
.enter()
.append(« circle »)
.attr(« data-insee », d => d.id)
.attr(« class », « bubulle »)
.attr(« r », d => d.r)
.attr(« data-tt », d => makeTooltipContent(d))
.attr(« fill », d => {
const value = d.value2026;
if (value === null) return `var(–prct-no-result)`;
return colorScale(value);
})
.attr(« stroke », d => {
const value = d.value2026;
return value === null
? `var(–prct-no-result-stroke)`
: d3.rgb(
colorScale(value)
).darker(1);
})
.style(« stroke-width », d =>
d.value2026 === null ? 0 : 0.25
)
.on(« mouseover », function () {
d3.select(this).style(« stroke-width », 1.5);
// terre brûlée, on vire la classe .opacified partout
d3.selectAll(`#dec_mun_prct_${snippetId} circle.bubulle`).classed(« opacified », false);
})
.on(« mouseout », function () {
d3.select(this).style(« stroke-width », (d) => (d.value2026 === null ? 0 : 0.25));
});

decTextes
.selectAll(« text.ile »)
.data(textDrom)
.enter()
.append(« text »)
.attr(« class », « ile »)
.attr(« x », (d) => largeur * d.largeur)
.attr(« y », (d) => hauteur * (isMobile ? d.hauteur – 0.02 : d.hauteur))
.text((d) => (isMobile ? d.nom_court : d.nom));

// Mise à jour des positions des cercles
function ticked() {
circles.attr(« cx », (d) => d.x).attr(« cy », (d) => d.y);
}

const simulation = d3
.forceSimulation(circlesData)
.force(« x », d3.forceX((d) => d.cx).strength(0.3))
.force(« y », d3.forceY((d) => d.cy).strength(0.3))
.force(« collide », d3.forceCollide((d) => d.r + 0.2).strength(0.5))
.on(« tick », ticked)
.on(« end », () => {
simulation.stop();
});

// Watch bloc update
const blocDivs = document.querySelectorAll(`.dec_mun_prct_container[data-attr=${snippetId}] .legend_bloc`);
blocDivs.forEach(bloc => {
bloc.addEventListener(‘click’, () => {
const blocClicked = bloc.dataset.bloc;
blocDivs.forEach(bloc => {
bloc.classList.remove(‘legend_bloc__active’);
});
bloc.classList.add(‘legend_bloc__active’);
switchBloc(blocClicked);
});
});

function switchBloc(bloc) {
currentBloc = bloc;
const datasets = (dataByBloc[bloc] || []).map(d => d.nuanceData).filter(Boolean);
const newResultDict = agregerResultats(datasets);
circlesData.forEach(d => {
const entry = newResultDict[d.id];
d.value2026 = entry ? entry.prct : null;
d.statusBloc = entry?.status ?? « aucune_liste »;
d.listesBloc = entry?.listes ?? [];
d.detailBloc = entry?.detailListes ?? [];
});
circles.attr(« data-tt », d => makeTooltipContent(d));
updateLegend(newResultDict, bloc);
updateMap();
}

function updateLegend(result2026Dict, bloc) {
const allPrctValues = Object.values(result2026Dict).map(d => d.prct);
const newMaxPrct = typeof d3.max(allPrctValues) == ‘undefined’ || d3.max(allPrctValues) < 10 ? 10 : d3.max(allPrctValues);

const blocColorMax = getBlocColorMax(bloc);

// Nouvelle échelle avec la couleur du bloc
colorScale = d3.scaleSequential(
d3.interpolate(colorMin, blocColorMax)
).domain([0, newMaxPrct]);

// Mettre à jour les stops du dégradé
legendSvg.select(`#${gradientId}`)
.selectAll(« stop »)
.attr(« stop-color », (d, i) => colorScale((i / numStops) * newMaxPrct));
legendScale.domain([0, newMaxPrct]);
legendRoot.select(« .legend_color_axis »)
.call(legendAxis)
.call(g => g.select(« .domain »).remove());
legendRoot.select(« .legend_color_title »)
.text(`${textsMap.titre_legende_score} ${textsMap.prct}`);

// color note
const noteEl = document.querySelector(
`.dec_mun_prct_container[data-attr=${snippetId}] .legend_color_note`
);
if (!noteEl) return;
const key = `color_note_${bloc}`;
noteEl.innerHTML = textsMap[key] || «  »;
}

// update cercles
function updateMap() {
circles
.on(« mouseover », function () {
d3.select(this).style(« stroke-width », 1.5);
d3.selectAll(`#dec_mun_prct_${snippetId} circle.bubulle`).classed(« opacified », false);
})
.on(« mouseout », function () {
d3.select(this).style(« stroke-width », (d) => (d.value2026 === null ? 0 : 0.25));
})
.transition()
.duration(600)
.attr(« fill », d => {
const value = d.value2026;
if (value === null) return `var(–prct-no-result)`;
return colorScale(value);
})
.attr(« stroke », d => {
const value = d.value2026;
return value === null
? `var(–prct-no-result-stroke)`
: d3.rgb(
colorScale(value)
).darker(1);
})
.style(« stroke-width », (d) => (d.value2026 === null ? 0 : 0.25));
}

// Fonction pour traiter la sélection d’une commune
const func_to_treat_result_donnees = (result) => {
// Désopacifie tous les cercles
d3.selectAll(`#dec_mun_prct_${snippetId} circle.bubulle`).classed(« opacified », false);
// Opacifie tous les cercles sauf celui sélectionné
d3.selectAll(`#dec_mun_prct_${snippetId} circle.bubulle`)
.filter((d) => d.id !== result.data.c)
.classed(« opacified », true);

// Trouve le cercle correspondant
const selectedCircle = document.querySelector(`#dec_mun_prct_${snippetId} circle[data-insee= »${result.data.c} »]`);
if (selectedCircle) {
showTooltipManually(selectedCircle);
}
};

// Initialisation de l’autocomplete
if (langue == « fr-FR ») {
autocomplete_decodeurs(`search_mun_prct_${snippetId}`, listeComm, func_to_treat_result_donnees, reset_func, (x) => `${x.label.name}`, 6, 3, slugify, « Aucune commune avec ce nom »);
} else {
autocomplete_decodeurs(`search_mun_prct_${snippetId}`, listeComm, func_to_treat_result_donnees, reset_func, (x) => `${x.label.name}`, 6, 3, slugify, « No town or city with that name »);
}
// quand on tape échap
document.addEventListener(« keydown », function (event) {
if (event.key === « Escape ») {
reset_func();
const circles = getA(`#dec_mun_prct_${snippetId} circle.bubulle`);

forEach(circles, function (circle) {
circle.classList.remove(« selected-commune »);
});
d3.selectAll(« .tooltipdecodeurs »).remove();
}
});

// Initialisation du tooltip
const showTooltipManually = inittooltipdecodeurs(`#dec_mun_prct_${snippetId} .bubulle`);

// Gestionnaire pour fermer le tooltip en cliquant ailleurs
document.addEventListener(« click », function (e) {
if (!e.target.closest(`#dec_mun_prct_${snippetId} circle`) && !e.target.closest(`#search_mun_prct_${snippetId}`)) {
const circles = getA(`#dec_mun_prct_${snippetId} circle.bubulle`);
forEach(circles, function (circle) {
circle.classList.remove(« selected-commune »);
});
}
});
}
// Fonction pour réinitialiser la carte
const reset_func = () => {
document.querySelector(`#search_mun_prct_${snippetId} input`).value = «  »;
d3.selectAll(`#dec_mun_prct_${snippetId} circle.bubulle`).classed(« opacified », false);
};

// Start
fillHtml();
drawMap(langue);
}

initPercentMap();

Share.
Exit mobile version