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
Les stations-services indépendantes vont-elles disparaître ?

Les stations-services indépendantes vont-elles disparaître ?

mai 9, 2026
« Une application par jour jusqu’à disparition des plaques… » : les conseils d’un médecin pour se débarrasser de l’eczéma

« Une application par jour jusqu’à disparition des plaques… » : les conseils d’un médecin pour se débarrasser de l’eczéma

mai 9, 2026
« L’une des saisies les plus importantes de 2026 » : un vaste réseau de narcotrafic démantelé entre la France et l’Espagne après un an d’enquête

« L’une des saisies les plus importantes de 2026 » : un vaste réseau de narcotrafic démantelé entre la France et l’Espagne après un an d’enquête

mai 8, 2026
Facebook X (Twitter) Instagram
Facebook X (Twitter) Instagram YouTube
Se Connecter
mai 9, 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

les stratégies d’alliances ont-elles fonctionné ? Vérifiez-le avec notre outil

Espace PresseBy Espace Pressemars 24, 2026
Facebook Twitter WhatsApp Copy Link Pinterest LinkedIn Tumblr Email Telegram
les stratégies d’alliances ont-elles fonctionné ? Vérifiez-le avec notre outil

Les résultats du second tour des élections municipales, qui s’est tenu dimanche 22 mars, sont désormais complets. Ils permettent de mesurer l’écart entre les projections théoriques et la réalité des bulletins glissés dans les urnes. Et de constater que les fusions, même lorsqu’elles plaçaient une liste en position dominante, n’ont pas toujours suffi à la faire élire.

A l’issue du premier tour, il était en effet possible de calculer le « potentiel électoral » de chaque liste : une addition des scores des listes qualifiées et, le cas échéant, de celles qu’elles avaient absorbées. Mais les reports de voix n’ont pas toujours eu lieu dans les proportions anticipées. Dans 233 communes de plus de 3 500 habitants, la liste qui semblait la mieux armée pour l’emporter a finalement été battue – 110 de ces listes étaient issues d’une fusion. Dans les villes de plus de 30 000 habitants, 38 listes fusionnées et mieux placées pour l’emporter ont échoué, contre 33 qui ont réussi leur pari.

Voir aussi | Article réservé à nos abonnés La carte définitive des résultats des municipales au second tour

Par exemple, à Brest, la liste fusionnée du Parti socialiste (PS) et de La France insoumise (LFI) a obtenu des suffrages proches des résultats obtenus par les deux listes au premier tour. Le Rassemblement national (RN) a, lui, perdu près de 3 000 voix, tandis que le candidat Les Républicains (LR), Stéphane Roudaut, a fédéré 14 272 électeurs de plus qu’au premier tour – 27 points de plus que son « potentiel électoral » –, ce qui lui a assuré la victoire.

A Nantes, en revanche, la fusion PS-LFI a permis à la maire sortante, Johanna Rolland, de conserver sa place face à la liste LR de Foulques Chombart de Lauwe. Johanna Rolland a récolté environ 8 500 voix de plus qu’au premier tour – les quelque 18 400 voix supplémentaires données à son rival n’ont pas suffi à inverser la tendance.

Le module interactif ci-dessous vous permet de comparer, pour chaque ville ayant organisé un second tour, le potentiel électoral théorique et les résultats officiels du second tour. Vous pouvez filtrer les communes par taille, type d’alliance ou bloc politique.

Comparez les résultats officiels et le potentiel électoral des listes candidates pour le second tour

Afficher les communes de…

+ de 30 000 habitants

– de 30 000 habitants

Filtrer

Liste dominante en échec

Toutes les fusions

Les fusions gagnantes

Les fusions infructueuses

LFI au second tour

Gauche au second tour

Droite au second tour

Ext. droite au second tour

Trier

Population

Plus grand écart potentiel/résultat

${subtitle ? `

${subtitle}

` : « }
`;

grid.appendChild(card);

const chartEl = card.querySelector(« .mun_alliances_card_chart »);
const width = chartEl.offsetWidth || card.offsetWidth;
const isSmall = width < 600;

const margin = {
top: isSmall ? width * 0.02 : width * 0.02,
right: width * 0.01,
bottom: isSmall ? width * 0.01 : width * 0.01,
left: isSmall ? width * 0.001 : width * 0.001
};

const svg = d3.select(chartEl)
.append(« svg »)
.attr(« width », « 100% »)
.attr(« class », « chart »);

let x0 = 0;
const t1Segments = [];
const t1Rows = commune.listes;
for (const row of t1Rows) {
const pct = row.pct_exprimes;
if (!Number.isFinite(pct) || pct <= 0) continue;
const x1 = x0 + pct;
const nuanceMin = row.nuance;
const nuanceLM = row.nuance_lemonde;
const { light, dark, label } = getNuanceStyle(nuanceLM, nuanceMin);
const nuanceCode = String(nuanceLM || nuanceMin || label);
t1Segments.push({
ordre: row.ordre,
nuanceCode,
nuanceLabel: String(label),
pct,
x0,
x1,
light,
dark,
sourceRow: row,
t1Status: row.qualifie,
lib: row.lib ?? «  »,
deptLabel
});
x0 = x1;
}

const missing = Math.max(0, 100 – x0);

if (missing > 0.05) {
t1Segments.push({
nuanceLabel: « Eliminés »,
nuanceCode: « __remainder__ »,
voix: null,
pct: missing,
x0: x0,
x1: 100,
light: « #E2E4E9 »,
dark: « #3C3C3C »,
isRemainder: true,
lib: commune.lib,
deptLabel,
});
}

let x0t2 = 0;
const t2Segments = [];
const fmtDecision = (s) => String(s || «  »).trim();
for (const row of t1Rows) {
const decision = fmtDecision(row.decision_officielle);
if (decision == « Fusion (absorbée) ») continue;
if (decision.startsWith(« Désistement »)) continue;
if (decision.startsWith(« Fusionnable »)) continue;
if (decision === « Ne fusionne pas ») continue;
if (decision === « Maintien ») {
const pct = row.pct_exprimes;
if (!Number.isFinite(pct) || pct <= 0) continue;
const x1 = x0t2 + pct;
const { light, dark, label } = getNuanceStyle(row.nuance_lemonde, row.nuance);
t2Segments.push({
nuance: String(label),
pct,
x0: x0t2,
x1,
light,
dark,
isRemainder: false,
sourceRows: [row],
decision: « Maintien »,
lib: commune.lib,
deptLabel
});
x0t2 = x1;
continue;
}
if (decision === « Fusion (absorbante) ») {
let pct = row.pct_exprimes;
const sourceRows = [row];

const fusionTargets = String(row.n_panneau_absorbe || «  »)
.split(« ; »)
.map((s) => {
const n = parseFR(s);
return Number.isFinite(n) ? String(Math.trunc(n)) : String(s || «  »).trim();
})
.filter(Boolean);
const seen = new Set();
for (const targetPanneau of fusionTargets) {
if (seen.has(targetPanneau)) continue;
seen.add(targetPanneau);
const absorbed = commune.byPanneau.get(targetPanneau);
if (absorbed && Number.isFinite(absorbed.pct_exprimes)) {
pct += absorbed.pct_exprimes;
sourceRows.push(absorbed);
}
}
// if (fusionTargets.length && sourceRows.length === 1) {
// console.log(« Fusion cible introuvable (panneau) », commune.code_circo, row.n_panneau, fusionTargets);
// }
if (!Number.isFinite(pct) || pct <= 0) continue;
const x1 = x0t2 + pct;
const { light, dark } = getNuanceStyle(row.nuance_lemonde, row.nuance);
const fusionLabel = sourceRows
.map((r) => getNuanceStyle(r.nuance_lemonde, r.nuance).label)
.join(« + »);
t2Segments.push({
nuance: fusionLabel,
pct,
x0: x0t2,
x1,
light,
dark,
isRemainder: false,
sourceRows,
decision: « Fusion (absorbante) »,
lib: commune.lib,
deptLabel
});
x0t2 = x1;
continue;
}

}
const aRepartir = Math.max(0, 100 – x0t2);

if (aRepartir > 0.05) {
t2Segments.push({
nuance: « À répartir »,
pct: aRepartir,
x0: x0t2,
x1: Math.min(100, x0t2 + aRepartir),
light: « #E2E4E9 »,
dark: « #3C3C3C »,
isRemainder: true,
decision: « À répartir »,
lib: commune.lib,
deptLabel
});
x0t2 = x0t2 + aRepartir;
}

const remainder = t2Segments.filter(d => d.isRemainder);
const nonRemainder = t2Segments.filter(d => !d.isRemainder);
nonRemainder.sort((a, b) => b.pct – a.pct);

let cursor = 0;
for (const seg of […nonRemainder, …remainder]) {
seg.x0 = cursor;
seg.x1 = cursor + seg.pct;
cursor = seg.x1;
}
t2Segments.length = 0;
t2Segments.push(…nonRemainder, …remainder);

// Tour 2 officiel
let x0t3 = 0;
const t3Segments = [];
for (const row of t1Rows) {
const pct = row.resultat_t2;
if (!Number.isFinite(pct) || pct <= 0) continue;
const x1 = x0t3 + pct;
const { light, dark, label } = getNuanceStyle(row.nuance_lemonde, row.nuance);
t3Segments.push({
pct, x0: x0t3, x1, light, dark,
sourceRow: row,
lib: commune.lib, deptLabel,
isT3: true,
});
x0t3 = x1;
}

const t3Remainder = t3Segments.filter(d => d.isRemainder);
const t3NonRemainder = t3Segments.filter(d => !d.isRemainder);
t3NonRemainder.sort((a, b) => {
const aElu = a.sourceRow?.elu === « O »;
const bElu = b.sourceRow?.elu === « O »;
if (aElu && !bElu) return -1;
if (!aElu && bElu) return 1;
return b.pct – a.pct;
});

// Recalculer les x0/x1 après tri
let cursorT3 = 0;
for (const seg of […t3NonRemainder, …t3Remainder]) {
seg.x0 = cursorT3;
seg.x1 = cursorT3 + seg.pct;
cursorT3 = seg.x1;
}
t3Segments.length = 0;
t3Segments.push(…t3NonRemainder, …t3Remainder);
const hasT3Data = t3Segments.length > 0;
const missingT3 = Math.max(0, 100 – x0t3);
if (hasT3Data && missingT3 > 0.05) {
t3Segments.push({
pct: missingT3, x0: x0t3, x1: 100,
light: « #E2E4E9 », dark: « #3C3C3C »,
isRemainder: true,
lib: commune.lib, deptLabel,
isT3: true,
});
}

const labelW = isSmall ? 78 : 78;
const innerW = width – margin.left – margin.right – labelW;
const barH = 20;
const barY = 4;
const gapY = isSmall ? 18 : 22;
const barY2 = barY + barH + gapY;
const gapY3 = isSmall ? 18 : 22;

const barY3 = barY2 + barH + gapY3;
const neededH = hasT3Data
? margin.top + barY3 + barH + margin.bottom
: margin.top + barY2 + barH + margin.bottom;

svg.attr(« viewBox », [0, 0, width, neededH]);

const x = d3.scaleLinear().domain([0, 100]).range([0, innerW]);
const g = svg
.append(« g »)
.attr(« transform », `translate(${margin.left + labelW},${margin.top})`);

const t1SegByRow = new Map(
t1Segments
.filter(d => d.sourceRow)
.map(d => [d.sourceRow, d])
);

// Gérer les liens

const fusionTargets = t2Segments.filter(d => d.decision === « Fusion (absorbante) » && Array.isArray(d.sourceRows) && d.sourceRows.length);
if (fusionTargets.length) {
const gLinks = g.append(« g »).attr(« class », « t2-sankey-links »).attr(« pointer-events », « none »);

for (const ft of fusionTargets) {
const components = (ft.sourceRows || [])
.map(sr => {
const t1Seg = t1SegByRow.get(sr);
const pct = Number.isFinite(sr?.pct_exprimes) ? sr.pct_exprimes : (t1Seg?.pct ?? 0);
return { sr, t1Seg, pct };
})
.filter(d => d.t1Seg && Number.isFinite(d.pct) && d.pct > 0)
.sort((a, b) => b.pct – a.pct);

const denomUsed = components.reduce((s, c) => s + c.pct, 0) || ft.pct || 1;

const ySourceBottom = barY + barH;
const yTargetTop = barY2;

let accX = 0;
const xTargetBase = x(ft.x0);
const xTargetTotalW = Math.max(0, x(ft.x1) – x(ft.x0));

if (!components.length) continue;
for (const comp of components) {
const share = comp.pct / denomUsed;
const sLeft = x(comp.t1Seg.x0);
const sRight = x(comp.t1Seg.x1);
const tLeft = xTargetBase + accX * xTargetTotalW;
const tRight = tLeft + share * xTargetTotalW;
accX += share;

const strokeColor = isDark ? comp.t1Seg.dark : comp.t1Seg.light;

const pathD =
`M ${sLeft} ${ySourceBottom}
L ${sRight} ${ySourceBottom}
L ${tRight} ${yTargetTop}
L ${tLeft} ${yTargetTop}
Z`;
gLinks.append(« path »)
.attr(« d », pathD)
.attr(« fill », strokeColor)
.attr(« fill-opacity », 0.22)
.attr(« stroke », strokeColor)
.attr(« stroke-opacity », 0.35)
.attr(« stroke-width », 1);
}
}
}
g.append(« text »)
.attr(« class », « bar-label passelect »)
.attr(« x », -8)
.attr(« y », barY + barH / 2)
.attr(« dy », « 0.35em »)
.attr(« text-anchor », « end »)
.text(« Résultats T1 »);

g.selectAll(« rect.t1 »)
.data(t1Segments)
.enter()
.append(« rect »)
.attr(« class », « t1 »)
.attr(« x », (d) => x(d.x0))
.attr(« y », barY)
.attr(« height », barH)
.attr(« width », (d) => Math.max(0, x(d.x1) – x(d.x0)))
.attr(« fill », (d) => (isDark ? d.dark : d.light))
.attr(« stroke », isDark ? « rgba(255,255,255,0.35) » : « rgba(0,0,0,0.18) »)
.attr(« stroke-width », 1)
.attr(« fill-opacity », (d) => (d.t1Status === « Fusionnable » ? isDark ? 0.6 : 0.35 : 1))
.attr(« stroke-dasharray », (d) => (d.t1Status === « Fusionnable » ? « 3 2 » : null))
.attr(« shape-rendering », « crispEdges »)
.attr(« cursor », « pointer »)
.on(« mouseenter », showTooltip)
.on(« mousemove », showTooltip)
.on(« mouseleave », hideTooltip);

t1Segments
.filter(d => d.sourceRow?.decision_officielle?.startsWith(« Désistement »))
.forEach(d => {
const segW = Math.max(0, x(d.x1) – x(d.x0));
const cx = x(d.x0) + segW / 2;
const cy = barY + (barH / 2);
const pictoG = g.append(« g »).attr(« class », « t1-desistement-picto »);

pictoG.append(« text »)
.attr(« x », cx)
.attr(« y », cy)
.text(« × »);
});

g.append(« text »)
.attr(« class », « bar-label passelect »)
.attr(« x », -8)
.attr(« y », barY2 + barH / 2)
.attr(« dy », « 0.35em »)
.attr(« text-anchor », « end »)
.text(« Potentiel électoral »);

g.selectAll(« rect.t2 »)
.data(t2Segments)
.enter()
.append(« rect »)
.attr(« class », « t2 »)
.attr(« x », (d) => x(d.x0))
.attr(« y », barY2)
.attr(« height », barH)
.attr(« width », (d) => Math.max(0, x(d.x1) – x(d.x0)))
.attr(« fill », (d) => (isDark ? d.dark : d.light))
.attr(« fill-opacity », 1)
.attr(« stroke », isDark ? « rgba(255,255,255,0.8) » : « rgba(0,0,0,0.3) »)
.attr(« stroke-width », 1)
.attr(« stroke-linecap », « round »)
.attr(« stroke-linejoin », « round »)
.attr(« stroke-dasharray », « 4 3 »)
.attr(« cursor », « pointer »)
.on(« mouseenter », showTooltip)
.on(« mousemove », showTooltip)
.on(« mouseleave », hideTooltip);

if (hasT3Data) {
g.append(« text »)
.attr(« class », « bar-label passelect »)
.attr(« x », -8)
.attr(« y », barY3 + barH / 2)
.attr(« dy », « 0.35em »)
.attr(« text-anchor », « end »)
.text(« Résultats T2 »);

g.selectAll(« rect.t3 »)
.data(t3Segments)
.enter()
.append(« rect »)
.attr(« class », « t3 »)
.attr(« x », d => x(d.x0))
.attr(« y », barY3)
.attr(« height », barH)
.attr(« width », d => Math.max(0, x(d.x1) – x(d.x0)))
.attr(« fill », d => isDark ? d.dark : d.light)
.attr(« stroke », isDark ? « rgba(255,255,255,0.35) » : « rgba(0,0,0,0.18) »)
.attr(« stroke-width », 1)
.attr(« shape-rendering », « crispEdges »)
.attr(« cursor », « pointer »)
.on(« mouseenter », showTooltip)
.on(« mousemove », showTooltip)
.on(« mouseleave », hideTooltip);

t3Segments
.filter(d => !d.isRemainder && Number.isFinite(d.sourceRow?.diff_potentiel_t2))
.forEach((d, i) => {
const diff = d.sourceRow.diff_potentiel_t2;
const absD = Math.abs(diff);
if (absD < 0.05) return;
const segW = Math.max(0, x(d.x1) – x(d.x0));
if (segW < 14) return; // trop étroit
const cx = x(d.x0) + segW / 2;
const sign = diff > 0 ? « + » : « u2212 »;
const unit = i === 0 ? (absD >= 2 ? « u00A0pts » : « u00A0pt ») : «  »;
const label = `${sign}${absD.toFixed(1).replace(« . », « , »)}${unit}`;
// positif = candidat a fait mieux que le potentiel
const color = diff > 0 ? « #2a9d2a » : « #d9522a »;
g.append(« text »)
.attr(« class », « t3-diff-label passelect »)
.attr(« x », cx)
.attr(« y », barY3 – 2)
.attr(« dy », « -0.15em »)
.attr(« text-anchor », « middle »)
.attr(« fill », color)
.text(label);
});
}
}

function showTooltip(event, d) {
const isT3 = !!d.isT3;
const isT2 = !isT3 && !!d.decision;
const isT1 = !isT2 && !isT3;

const row = d.sourceRow || (Array.isArray(d.sourceRows) ? d.sourceRows[0] : null);
let htmlTooltip = «  »;
const titleCity = d.lib ? `

${d.lib}

` : «  »;

// On a des résultats
if (row) {
const { light, dark, label: nuanceLabel } = getNuanceStyle(row.nuance_lemonde, row.nuance);
const nuanceColor = isDark ? dark : light;
const nuanceColorDark = dark || light;
const civ = row.tete_civ ? `${row.tete_civ} ` : «  »;
const fullName = `${civ}${row.tete_prenom ?? «  »} ${row.tete_nom ?? «  »}`.trim();
let secondLine = «  »;
if (isT1) {
secondLine = `${fmtPct(d.pct)} des suffrages exprimés au premier tour`;
} else if (isT2) {
secondLine = `Potentiel électoral : ${fmtPct(d.pct)}`;
} else if (isT3) {
secondLine = `${fmtPct(d.pct)} des suffrages exprimés au second tour`;
}
// Lignes additionnelles (fusion)
let extraContent = «  »;
const badge = (isT3 && d.sourceRow?.elu === « O »)
? `



`
: «  »;
if (isT3 && Number.isFinite(row.diff_potentiel_t2)) {
const diff = row.diff_potentiel_t2;
const absD = Math.abs(diff);
if (absD >= 0.05) {
const sign = diff > 0 ? « + » : « u2212 »;
const unit = absD >= 2 ? « pts » : « pt »;
const diffColor = diff > 0 ? « #2a9d2a » : « #d9522a »;
const voix = row.diff_potentiel_t2_voix;
const voixStr = Number.isFinite(voix)
? ` (${voix > 0 ? « + » : voix < 0 ? « u2212 » : «  »}${thousands(Math.abs(Math.round(voix)))} voix)`
: «  »;
extraContent = `${sign}${absD.toFixed(1).replace(« . », « , »)}u00A0${unit} ${voixStr} par rapport au potentiel électoral`;
}
}
if (isT1 && d.t1Status === « Fusionnable ») {
extraContent = `Liste éliminée mais fusionnable`;
}
if (isT1 && d.sourceRow?.decision_officielle?.startsWith(« Désistement »)) {
extraContent = `×Désistement au second tour`;
}
if (isT2 && d.decision === « Fusion (absorbante) ») {
const fused = (Array.isArray(d.sourceRows) ? d.sourceRows.slice(1) : []).filter(Boolean);
if (fused.length) {
extraContent = fused.map(r => {
const { light, dark } = getNuanceStyle(r.nuance_lemonde, r.nuance);
const nu2 = getNuanceStyle(r.nuance_lemonde, r.nuance).label;
const civ2 = r.tete_civ ? `${r.tete_civ} ` : «  »;
const name2 = `${civ2}${r.tete_prenom ?? «  »} ${r.tete_nom ?? «  »}`.trim();
const dotColor = isDark ? dark : light;
const dot = ``;
return `${dot}Fusion avec ${name2} (${nu2})`;
}).join(« 
« );
}
}
const extra = extraContent
? `

${extraContent}

`
: «  »;
htmlTooltip = `
${titleCity}

  1. ${fullName} (${nuanceLabel}) ${badge} ${d.sourceRow?.elu === « O » ? d.sourceRow?.tete_civ === « Mme » ? « élue »: « élu » : «  »}

    ${secondLine}

${extra}

`;
} else {
if (isT1) {
htmlTooltip = `${titleCity}

Autres listes éliminéess : ${fmtPct(d.pct)}

`;
}
else if (isT2 && d.isRemainder) {
htmlTooltip = `${titleCity}

À répartir : ${fmtPct(d.pct)}

`;
} else if (isT3 && d.isRemainder) {
// CHELOU
htmlTooltip = `${titleCity}

Autre : ${fmtPct(d.pct)}

`;
} else {
return;
}
}

tooltip
.html(htmlTooltip)
.style(« opacity », 1);
if (window.innerWidth > 600) {
tooltip
.style(« left », (event.pageX – tooltip.node().getBoundingClientRect().width / 2) + « px »)
.style(« top », (event.pageY – tooltip.node().getBoundingClientRect().height – 8) + « px »);
}
}

function hideTooltip() {
tooltip.style(« opacity », 0);
}

const communes = Object.values(resultsByCommune).sort(
(a, b) => (b.population ?? 0) – (a.population ?? 0)
);

communes.forEach((commune, idx) => renderCommune(commune, idx));
loader.classList.add(« hidden »);

const loadMoreBtn = document.getElementById(« mun_alliances_load_more »);

function applyFilter() {
const cards = document.querySelectorAll(« .mun_alliances_card »);

let visibleTotal = 0;
let shownSoFar = 0;
cards.forEach(card => {
let matches = true;
if (activePopFilter === « pop_lt30 »)
matches = matches && card.dataset.popcat === « lt30 »;
else if (activePopFilter === « pop_gt30 »)
matches = matches && card.dataset.popcat === « gt30 »;
if (activeThematic.has(« fusion »))
matches = matches && card.dataset.fusion === « 1 »;
if (activeThematic.has(« fusion_gagnante »))
matches = matches && card.dataset.fusion_gagnante === « 1 »;
if (activeThematic.has(« fusion_infructueuse »))
matches = matches && card.dataset.fusion_infructueuse === « 1 »;
if (activeThematic.has(« gauche »))
matches = matches && card.dataset.gauche === « 1 »;
if (activeThematic.has(« droite »))
matches = matches && card.dataset.droite === « 1 »;
if (activeThematic.has(« extdroite »))
matches = matches && card.dataset.extdroite === « 1 »;
if (activeThematic.has(« lfi »))
matches = matches && card.dataset.lfi === « 1 »;
if (activeThematic.has(« inversion »))
matches = matches && card.dataset.inversion === « 1 »;
if (matches) {
visibleTotal++;
const withinPage = shownSoFar < shownCount;
card.style.display = withinPage ? «  » : « none »;
if (withinPage) shownSoFar++;
} else {
card.style.display = « none »;
}
});
loadMoreBtn.style.display = visibleTotal > shownCount ? «  » : « none »;
}

function sortCommunes() {
const cards = […document.querySelectorAll(« .mun_alliances_card »)];
cards.sort((a, b) => {
const cA = resultsByCommune[a.dataset.code];
const cB = resultsByCommune[b.dataset.code];
if (currentSort === « diff ») {
return (cB?.maxDiffAbs ?? 0) – (cA?.maxDiffAbs ?? 0);
}
return (cB?.population ?? 0) – (cA?.population ?? 0);
});
const grid = document.querySelector(« .mun_alliances_charts_grid »);
cards.forEach(card => grid.appendChild(card));
shownCount = PAGE_SIZE;
applyFilter();
}

const listeComm = communes.map((c) => {
const codeCirco5 = String(c.code_circo ?? «  »).padStart(5, « 0 »);
const dept = codeCirco5.slice(0, 2);
return {
label: { name: `${c.lib} (${dept})` },
data: { code: c.code_circo },
};
});
const func_to_treat_result_donnees = (result) => {
const code = result?.data?.code;
activePopFilter = « pop_gt30 »;
activeThematic.clear();
// Filtres sur +30000
const pillsEls = document.querySelectorAll(« .mun_alliances_filters_pills .legend_bloc »);
pillsEls.forEach(p => {
p.classList.toggle(« legend_bloc__active », p.dataset.filter === « pop_gt30 »);
});
// On remet tout en affiché
applyFilter();
const cards = document.querySelectorAll(« .mun_alliances_card »);
cards.forEach(card => {
card.style.display = card.dataset.code === code ? «  » : « none »;
});
searchActive = true;
loadMoreBtn.innerHTML = `Réinitialiser`;
loadMoreBtn.style.display = «  »;
};
const reset_func = () => {
// const input = document.querySelector(`.lmui-search__bar input`);
// if (input) input.value = «  »;
document.querySelector(« #search_mun_alliances .lmui-search__reset-button »)?.click();

searchActive = false;
activePopFilter = « pop_gt30 »;
activeThematic.clear();
shownCount = PAGE_SIZE;
pills.forEach(p => p.classList.toggle(« legend_bloc__active », p.dataset.filter === « pop_gt30 »));
loadMoreBtn.innerHTML = `Afficher plus de communes

`;
applyFilter();
};
autocomplete_decodeurs(
« search_mun_alliances »,
listeComm,
func_to_treat_result_donnees,
reset_func,
(x) => `${x.label.name}`,
6,
3,
slugify,
« Aucune commune avec ce nom »
);
document.addEventListener(« keydown », function (event) {
if (event.key === « Escape ») reset_func();
});

const pills = document.querySelectorAll(« .mun_alliances_filters_pills .legend_bloc »);
const popFilters = new Set([« pop_lt30 », « pop_gt30 »]);
const thematicFilters = new Set([« fusion », « gauche », « droite », « extdroite », « lfi », « inversion », « fusion_infructueuse », « fusion_gagnante »]);

pills.forEach(pill => {
pill.addEventListener(« click », () => {
const f = pill.dataset.filter;
if (searchActive) {
searchActive = false;
document.querySelector(« #search_mun_alliances .lmui-search__reset-button »)?.click();
loadMoreBtn.innerHTML = `Afficher plus de communes

`;
}
if (popFilters.has(f)) {
// Mutuellement exclusifs : toggle
activePopFilter = activePopFilter === f ? null : f;
shownCount = PAGE_SIZE;
} else if (thematicFilters.has(f)) {
// Toggle indépendant
if (activeThematic.has(f)) activeThematic.delete(f);
else activeThematic.add(f);
shownCount = PAGE_SIZE;
}
pills.forEach(p => {
const pf = p.dataset.filter;
const isActive =
pf === « all » ? (activePopFilter === null && activeThematic.size === 0) :
popFilters.has(pf) ? activePopFilter === pf :
thematicFilters.has(pf) ? activeThematic.has(pf) : false;
p.classList.toggle(« legend_bloc__active », isActive);
});
applyFilter();
});
});

const sortPills = document.querySelectorAll(« #mun_alliances_sort_pills .legend_bloc »);
sortPills.forEach(pill => {
pill.addEventListener(« click », () => {
sortPills.forEach(p => p.classList.remove(« legend_bloc__active »));
pill.classList.add(« legend_bloc__active »);
currentSort = pill.dataset.sort;
sortCommunes();
});
});

applyFilter();

// Watch back to top
const backBtn = document.getElementById(« mun_alliances_backtotop »);
const topEl = document.querySelector(« .mun_alliances_top »);
window.addEventListener(« scroll », () => {
const threshold = topEl
? topEl.getBoundingClientRect().bottom + window.scrollY + 300
: 300;
backBtn.classList.toggle(« visible », window.scrollY > threshold);
});
backBtn.addEventListener(« click », () => {
document.getElementById(« search_mun_alliances »).scrollIntoView({ behavior: « smooth », block: « center » });
document.querySelector(« #search_mun_alliances input »)?.focus();
});

loadMoreBtn.addEventListener(« click », () => {
if (searchActive) {
reset_func();
} else {
shownCount += PAGE_SIZE;
applyFilter();
}
});

}

// Tout lancer une première fois

drawAllAlliances();

Share. Facebook Twitter Pinterest LinkedIn Telegram WhatsApp Email

Articles Liés

Emmanuel Macron répond à Donald Trump, qui accuse la France de ne pas coopérer avec les Etats-unis dans le conflit qui les oppose à l’Iran

Emmanuel Macron répond à Donald Trump, qui accuse la France de ne pas coopérer avec les Etats-unis dans le conflit qui les oppose à l’Iran

Politique avril 1, 2026
Retraites : les résultats de l’Agirc-Arrco toujours en excédent, malgré un net recul

Retraites : les résultats de l’Agirc-Arrco toujours en excédent, malgré un net recul

Politique avril 1, 2026
Présidentielle : chez LR, une tribune répond à une autre au sujet du candidat unique de la droite et du centre

Présidentielle : chez LR, une tribune répond à une autre au sujet du candidat unique de la droite et du centre

Politique avril 1, 2026
La « nouvelle France », une expression banale transformée en grenade dégoupillée

La « nouvelle France », une expression banale transformée en grenade dégoupillée

Politique avril 1, 2026
Marc Lazar, historien : « La grande force du national-populisme est de répondre à une colère sociale par un récit mobilisateur »

Marc Lazar, historien : « La grande force du national-populisme est de répondre à une colère sociale par un récit mobilisateur »

Politique avril 1, 2026
Collectivités : à l’Assemblée, le chantier sensible du millefeuille territorial relancé

Collectivités : à l’Assemblée, le chantier sensible du millefeuille territorial relancé

Politique mars 31, 2026
Paris : l’Hôtel de ville perquisitionné mardi ; le marché pour l’organisation de la commémoration des attentats du 13-Novembre dans le viseur

Paris : l’Hôtel de ville perquisitionné mardi ; le marché pour l’organisation de la commémoration des attentats du 13-Novembre dans le viseur

Politique mars 31, 2026
Municipales à Toulouse : la Ville rose reste bel et bien un bastion de droite

Municipales à Toulouse : la Ville rose reste bel et bien un bastion de droite

Politique mars 31, 2026
David Lisnard, maire de Cannes, quitte LR et se déclare candidat pour la présidentielle de 2027

David Lisnard, maire de Cannes, quitte LR et se déclare candidat pour la présidentielle de 2027

Politique mars 31, 2026

Actualité à la Une

« Une application par jour jusqu’à disparition des plaques… » : les conseils d’un médecin pour se débarrasser de l’eczéma

« Une application par jour jusqu’à disparition des plaques… » : les conseils d’un médecin pour se débarrasser de l’eczéma

mai 9, 2026
« L’une des saisies les plus importantes de 2026 » : un vaste réseau de narcotrafic démantelé entre la France et l’Espagne après un an d’enquête

« L’une des saisies les plus importantes de 2026 » : un vaste réseau de narcotrafic démantelé entre la France et l’Espagne après un an d’enquête

mai 8, 2026
Moyen-Orient, Algérie, Chine… Ce qu’il faut retenir de l’interview de Jean-Luc Mélenchon sur LCI

Moyen-Orient, Algérie, Chine… Ce qu’il faut retenir de l’interview de Jean-Luc Mélenchon sur LCI

mai 8, 2026

Choix de l'éditeur

« Jamais de la vie » : Boualem Sansal annonce qu’il ne quittera finalement pas la France

« Jamais de la vie » : Boualem Sansal annonce qu’il ne quittera finalement pas la France

mai 8, 2026
Rupture amicale : comment savoir si une amitié est terminée ?

Rupture amicale : comment savoir si une amitié est terminée ?

mai 8, 2026
Météo du 8 mai 2026 : Prévisions météo à 21h03

Météo du 8 mai 2026 : Prévisions météo à 21h03

mai 8, 2026
Météo du 8 mai 2026 : Prévisions météo à 21h03

Météo du 8 mai 2026 : Prévisions météo à 19h55

mai 8, 2026
Rapatriement, test, isolement… Ce qui attend les passagers français du bateau touché par un foyer d’hantavirus

Rapatriement, test, isolement… Ce qui attend les passagers français du bateau touché par un foyer d’hantavirus

mai 8, 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?