Close Menu
Le Méridien
  • Actualités
  • Monde
  • Politique
  • Police
  • Société
  • Education
  • Entreprise
  • Justice
  • Culture
  • Sciences et Tech
  • Plus
    • Environnement
    • Communiqué de Presse
    • Les Tendances
What's Hot
Edouard Philippe : le maire du Havre, en tête du premier tour des municipales, en bonne voie pour sécuriser sa candidature présidentielle

Edouard Philippe : le maire du Havre, en tête du premier tour des municipales, en bonne voie pour sécuriser sa candidature présidentielle

mars 15, 2026
RDC : une attaque des rebelles ADF liés à organisation Etat islamique contre des sites miniers fait plusieurs morts

RDC : une attaque des rebelles ADF liés à organisation Etat islamique contre des sites miniers fait plusieurs morts

mars 15, 2026
En Hongrie, les partisans de Viktor Orban et l’opposition mesurent leurs forces dans les rues de Budapest avant les législatives

En Hongrie, les partisans de Viktor Orban et l’opposition mesurent leurs forces dans les rues de Budapest avant les législatives

mars 15, 2026
Facebook X (Twitter) Instagram
Facebook X (Twitter) Instagram YouTube
Se Connecter
mars 15, 2026
Le Méridien
Histoires Web Bulletin
  • Actualités
  • Monde
  • Politique
  • Police
  • Société
  • Education
  • Entreprise
  • Justice
  • Culture
  • Sciences et Tech
  • Plus
    • Environnement
    • Communiqué de Presse
    • Les Tendances
Le Méridien
Home»Politique
Politique

La carte des résultats des municipales au premier tour en temps réel

Espace PresseBy Espace Pressemars 15, 2026
Facebook Twitter WhatsApp Copy Link Pinterest LinkedIn Tumblr Email Telegram
La carte des résultats des municipales au premier tour en temps réel

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.

Lire aussi | EN DIRECT, résultats des municipales 2026 : Louis Aliot réélu à Perpignan dès le premier tour, selon les estimations ; Edouard Philippe en tête au Havre

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. Facebook Twitter Pinterest LinkedIn Telegram WhatsApp Email

Articles Liés

Edouard Philippe : le maire du Havre, en tête du premier tour des municipales, en bonne voie pour sécuriser sa candidature présidentielle

Edouard Philippe : le maire du Havre, en tête du premier tour des municipales, en bonne voie pour sécuriser sa candidature présidentielle

Politique mars 15, 2026
Ericka Bareigts réélue dès le premier tour des élections municipales à Saint-Denis, chef-lieu de La Réunion

Ericka Bareigts réélue dès le premier tour des élections municipales à Saint-Denis, chef-lieu de La Réunion

Politique mars 15, 2026
Elections municipales 2026 : l’abstention n’a jamais été aussi forte, hors crise sanitaire

Elections municipales 2026 : l’abstention n’a jamais été aussi forte, hors crise sanitaire

Politique mars 15, 2026
« J’arrive très bien à vivre en me détachant de la politique »

« J’arrive très bien à vivre en me détachant de la politique »

Politique mars 15, 2026
« ça se passe beaucoup mieux que prévu » avec le nouveau mode de scrutin

« ça se passe beaucoup mieux que prévu » avec le nouveau mode de scrutin

Politique mars 15, 2026
Municipales 2026 : 48,90 % de participation à 17 heures, en hausse par rapport aux élections de 2020

Municipales 2026 : 48,90 % de participation à 17 heures, en hausse par rapport aux élections de 2020

Politique mars 15, 2026
Municipales 2026 : 48,90 % de participation à 17 heures, en hausse par rapport aux élections de 2020

Municipales 2026 : 19,37 % de participation à midi, en légère hausse par rapport aux élections de 2020

Politique mars 15, 2026
Municipales en Polynésie : le « tavana », bien plus qu’un maire

Municipales en Polynésie : le « tavana », bien plus qu’un maire

Politique mars 15, 2026
« Démolir les droits constitutionnels et les juges qui en assurent la protection, c’est démolir la figure du citoyen et la démocratie »

« Démolir les droits constitutionnels et les juges qui en assurent la protection, c’est démolir la figure du citoyen et la démocratie »

Politique mars 15, 2026

Actualité à la Une

RDC : une attaque des rebelles ADF liés à organisation Etat islamique contre des sites miniers fait plusieurs morts

RDC : une attaque des rebelles ADF liés à organisation Etat islamique contre des sites miniers fait plusieurs morts

mars 15, 2026
En Hongrie, les partisans de Viktor Orban et l’opposition mesurent leurs forces dans les rues de Budapest avant les législatives

En Hongrie, les partisans de Viktor Orban et l’opposition mesurent leurs forces dans les rues de Budapest avant les législatives

mars 15, 2026
Résultats municipales 2026 à Nantes : Johanna Rolland en tête du 1ᵉʳ tour, talonnée par la droite

Résultats municipales 2026 à Nantes : Johanna Rolland en tête du 1ᵉʳ tour, talonnée par la droite

mars 15, 2026

Choix de l'éditeur

Ericka Bareigts réélue dès le premier tour des élections municipales à Saint-Denis, chef-lieu de La Réunion

Ericka Bareigts réélue dès le premier tour des élections municipales à Saint-Denis, chef-lieu de La Réunion

mars 15, 2026
Guerre au Moyen-Orient : des éclats de missiles iraniens ont fait trois blessés et endommagé plusieurs sites israéliens

Guerre au Moyen-Orient : des éclats de missiles iraniens ont fait trois blessés et endommagé plusieurs sites israéliens

mars 15, 2026
l’armée israélienne dit avoir encore « des milliers de cibles » à frapper en Iran

l’armée israélienne dit avoir encore « des milliers de cibles » à frapper en Iran

mars 15, 2026
Résultats municipales 2026 à Pau : François Bayrou en tête au 1ᵉʳ tour

Résultats municipales 2026 à Pau : François Bayrou en tête au 1ᵉʳ tour

mars 15, 2026
suivez le tapis rouge de la 98ᵉ cérémonie des récompenses du cinéma américain et posez vos questions sur les nominations

suivez le tapis rouge de la 98ᵉ cérémonie des récompenses du cinéma américain et posez vos questions sur les nominations

mars 15, 2026
Facebook X (Twitter) Pinterest TikTok Instagram
2026 © Le Méridien. Tous droits réservés.
  • Politique de Confidentialité
  • Termes et Conditions
  • Contacter

Type above and press Enter to search. Press Esc to cancel.

Sign In or Register

Welcome Back!

Login to your account below.

Lost password?