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 alliances avec LFI ont-elles vraiment fait perdre la gauche ?

les alliances avec LFI ont-elles vraiment fait perdre la gauche ?

mars 24, 2026
La mutuelle Just mise en demeure par le gendarme des assureurs pour des « défaillances » de remboursement

La mutuelle Just mise en demeure par le gendarme des assureurs pour des « défaillances » de remboursement

mars 24, 2026
« A Paris, les mêmes électeurs ont souvent voté pour les listes patronnées par Horizons au conseil d’arrondissement, et pour Emmanuel Grégoire à la mairie »

« A Paris, les mêmes électeurs ont souvent voté pour les listes patronnées par Horizons au conseil d’arrondissement, et pour Emmanuel Grégoire à la mairie »

mars 24, 2026
Facebook X (Twitter) Instagram
Facebook X (Twitter) Instagram YouTube
Se Connecter
mars 24, 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

« A Paris, les mêmes électeurs ont souvent voté pour les listes patronnées par Horizons au conseil d’arrondissement, et pour Emmanuel Grégoire à la mairie »

« A Paris, les mêmes électeurs ont souvent voté pour les listes patronnées par Horizons au conseil d’arrondissement, et pour Emmanuel Grégoire à la mairie »

Politique mars 24, 2026
Après les élections municipales, des tendances se dessinent pour les sénatoriales

Après les élections municipales, des tendances se dessinent pour les sénatoriales

Politique mars 24, 2026
Lionel Jospin, l’homme à la croisée des gauches irréconciliables

Lionel Jospin, l’homme à la croisée des gauches irréconciliables

Politique mars 24, 2026
Frédéric Sawicki, politiste : « Le PS ne peut ignorer les attentes des électeurs de LFI »

Frédéric Sawicki, politiste : « Le PS ne peut ignorer les attentes des électeurs de LFI »

Politique mars 24, 2026
Municipales à Montpellier : le socialiste Michaël Delafosse réélu avec une longueur d’avance

Municipales à Montpellier : le socialiste Michaël Delafosse réélu avec une longueur d’avance

Politique mars 24, 2026
« Les accusations indignes qui visent Bally Bagayoko et Sofia Boutrih sont le signe de la violence du racisme dans notre pays »

« Les accusations indignes qui visent Bally Bagayoko et Sofia Boutrih sont le signe de la violence du racisme dans notre pays »

Politique mars 24, 2026
« LFI n’a pas cherché à quadriller le territoire, mais à renforcer ses bastions », affirme le politiste Bruno Cautrès

« LFI n’a pas cherché à quadriller le territoire, mais à renforcer ses bastions », affirme le politiste Bruno Cautrès

Politique mars 24, 2026
Municipales : visualisez les bascules politiques dans les grandes villes françaises

Municipales : visualisez les bascules politiques dans les grandes villes françaises

Politique mars 24, 2026
Un jour à Montargis : Facebook, agora contemporaine et outil de campagne électorale

Un jour à Montargis : Facebook, agora contemporaine et outil de campagne électorale

Politique mars 24, 2026

Actualité à la Une

La mutuelle Just mise en demeure par le gendarme des assureurs pour des « défaillances » de remboursement

La mutuelle Just mise en demeure par le gendarme des assureurs pour des « défaillances » de remboursement

mars 24, 2026
« A Paris, les mêmes électeurs ont souvent voté pour les listes patronnées par Horizons au conseil d’arrondissement, et pour Emmanuel Grégoire à la mairie »

« A Paris, les mêmes électeurs ont souvent voté pour les listes patronnées par Horizons au conseil d’arrondissement, et pour Emmanuel Grégoire à la mairie »

mars 24, 2026
En Biélorussie, Alexandre Loukachenko joue l’apaisement pour sortir de l’isolement international

En Biélorussie, Alexandre Loukachenko joue l’apaisement pour sortir de l’isolement international

mars 24, 2026

Choix de l'éditeur

le maire socialiste de Saint-Ouen, Karim Bouamrane, réclame la démission d’Olivier Faure ; Gabriel Attal veut étudier « les conditions d’un rassemblement » pour la présidentielle

le maire socialiste de Saint-Ouen, Karim Bouamrane, réclame la démission d’Olivier Faure ; Gabriel Attal veut étudier « les conditions d’un rassemblement » pour la présidentielle

mars 24, 2026
Tournés vers 2027, Les Républicains réfléchissent déjà à la désignation de leur futur candidat

Tournés vers 2027, Les Républicains réfléchissent déjà à la désignation de leur futur candidat

mars 24, 2026
« Le contraste entre des patrimoines incommensurables et la menace de plans sociaux massifs est vertigineux »

« Le contraste entre des patrimoines incommensurables et la menace de plans sociaux massifs est vertigineux »

mars 24, 2026
Après les élections municipales, des tendances se dessinent pour les sénatoriales

Après les élections municipales, des tendances se dessinent pour les sénatoriales

mars 24, 2026
La lutte contre l’antisémitisme a cessé d’être un principe structurant pour la gauche

La lutte contre l’antisémitisme a cessé d’être un principe structurant pour la gauche

mars 24, 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?