diff --git a/nupes-elections-front/package-lock.json b/nupes-elections-front/package-lock.json index 0c46ba4..f1ea476 100644 --- a/nupes-elections-front/package-lock.json +++ b/nupes-elections-front/package-lock.json @@ -17,8 +17,10 @@ "@testing-library/user-event": "^13.5.0", "highcharts": "^11.4.3", "highcharts-react-official": "^3.2.1", + "leaflet": "^1.9.4", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-leaflet": "^4.2.1", "react-router-dom": "^6.23.1", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" @@ -4053,6 +4055,17 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@react-leaflet/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz", + "integrity": "sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==", + "license": "Hippocratic-2.1", + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@remix-run/router": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz", @@ -13913,6 +13926,12 @@ "shell-quote": "^1.8.1" } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -16798,6 +16817,20 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "license": "MIT" }, + "node_modules/react-leaflet": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz", + "integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==", + "license": "Hippocratic-2.1", + "dependencies": { + "@react-leaflet/core": "^2.1.0" + }, + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", diff --git a/nupes-elections-front/package.json b/nupes-elections-front/package.json index be6285c..0879248 100644 --- a/nupes-elections-front/package.json +++ b/nupes-elections-front/package.json @@ -12,8 +12,10 @@ "@testing-library/user-event": "^13.5.0", "highcharts": "^11.4.3", "highcharts-react-official": "^3.2.1", + "leaflet": "^1.9.4", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-leaflet": "^4.2.1", "react-router-dom": "^6.23.1", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" diff --git a/nupes-elections-front/src/Elections2024.js b/nupes-elections-front/src/Elections2024.js index 266b0c6..f2adc5b 100644 --- a/nupes-elections-front/src/Elections2024.js +++ b/nupes-elections-front/src/Elections2024.js @@ -13,6 +13,9 @@ import * as Highcharts from 'highcharts' import highchartsItem from 'highcharts/modules/item-series' import HighchartsReact from 'highcharts-react-official' import {useEffect, useState} from "react" +import {GeoJSON, MapContainer, Popup, TileLayer, useMap} from "react-leaflet" + +import 'leaflet/dist/leaflet.css' highchartsItem(Highcharts) @@ -39,18 +42,7 @@ function ResultatsTable({blocs, nuances, listes, resultats, siegesParListe}) { {listes_triees.map((liste) => ( - - {liste.numero} - {liste.nom} - nuance.code === liste.nuance)[0].couleur, padding: "0.2em"}}> - {liste.nuance} - bloc.nom === liste.bloc)[0].couleur, padding: "0.2em"}}> - {liste.bloc} - {voix_listes[liste.numero] || 0} - {(100 * (voix_listes[liste.numero] || 0) / resultats.inscrits).toFixed(2)} % - {(100 * (voix_listes[liste.numero] || 0) / resultats.exprimes).toFixed(2)} % - {siegesParListe[liste.numero]} - + ))} @@ -59,6 +51,25 @@ function ResultatsTable({blocs, nuances, listes, resultats, siegesParListe}) { } +function ListeRow({liste, voix, resultats, siegesParListe, blocs, nuances}) { + const bloc = blocs.filter(bloc => bloc.nom === liste.bloc)[0] + const nuance = nuances.filter(nuance => nuance.code === liste.nuance)[0] + + return + {liste.numero} + {liste.nom} + + {liste.nuance} + + {liste.bloc} + {voix} + {(100 * voix / resultats.inscrits).toFixed(2)} % + {(100 * voix / resultats.exprimes).toFixed(2)} % + {siegesParListe[liste.numero]} + +} + + function ParticipationTable({resultats}) { return <> @@ -114,6 +125,96 @@ function ParticipationTable({resultats}) { } +function RegionGeoJSON({resultats_region, listes, blocs, nuances, grouperParBloc = false}) { + const voix_listes = resultats_region?.voix_listes ?? {} + const listes_triees = listes.toSorted((l1, l2) => { + return (voix_listes[l2.numero] || 0) - (voix_listes[l1.numero] || 0) + }) + + const voixParBloc = {} + const voixParNuance = {} + for (let bloc of blocs) { + voixParBloc[bloc.nom] = 0 + } + for (let nuance of nuances) { + voixParNuance[nuance.code] = 0 + } + + for (let liste of listes) { + voixParBloc[liste.bloc] += resultats_region.voix_listes[liste.numero] || 0 + voixParNuance[liste.nuance] += resultats_region.voix_listes[liste.numero] || 0 + } + + let couleur = 'grey' + if (grouperParBloc) { + let maxVoix = 0 + for (let bloc of blocs) { + if (voixParBloc[bloc.nom] > maxVoix) { + maxVoix = voixParBloc[bloc.nom] + couleur = bloc.couleur + } + } + } + else { + let maxVoix = 0 + for (let nuance of nuances) { + if (voixParNuance[nuance.code] > maxVoix) { + maxVoix = voixParNuance[nuance.code] + couleur = nuance.couleur + } + } + } + + return + + {resultats_region.region.nom} +
    + {listes_triees.slice(0, 5).map(liste => +
  • {liste.nom} : {voix_listes[liste.numero]} ({(100 * voix_listes[liste.numero] / resultats_region.exprimes).toFixed(2)} %)
  • )} +
+
+
+} + +function ContenuCarte({typeResultats, resultats, listes, blocs, nuances, grouperParBloc = false}) { + const [regions, setRegions] = useState([]) + + useEffect(() => { + if (!resultats || !resultats.france || !resultats.france.regions) + return + + setRegions(regions => []) + + resultats.france.regions.forEach(region_code => { + fetch(`/data/resultats/europeennes2024/regions/${region_code}.json`).then(response => response.json()) + .then(region => setRegions(regions => [...regions, region])) + }) + }, [typeResultats, resultats]) + + const map = useMap() + + return <> + {regions.map(region => )} + +} + +function Carte({typeResultats, resultats, listes, blocs, nuances, grouperParBloc = false}) { + const center = [46.603354, 1.888334] + + return <> + + + + + +} + export default function Election2024({typeResultats = "france"}) { const {zoneId} = useParams() @@ -321,8 +422,10 @@ export default function Election2024({typeResultats = "france"}) { title: { text: `Résultats des élections européennes 2024 : ${zoneName}`, }, - legend: { - labelFormat: '{name} {x}' + tooltip: { + formatter: function () { + return `${this.x} : ${this.y} voix (${(100 * this.y / resultats.exprimes).toFixed(2)} %)
` + } }, xAxis: { categories: categoriesVoix, @@ -355,5 +458,6 @@ export default function Election2024({typeResultats = "france"}) { /> + -}; \ No newline at end of file +} diff --git a/nupes/scripts/export_resultats_2024.py b/nupes/scripts/export_resultats_2024.py index aea55cd..54fe926 100644 --- a/nupes/scripts/export_resultats_2024.py +++ b/nupes/scripts/export_resultats_2024.py @@ -73,6 +73,11 @@ def exporter_resultats_france(engine: Engine, verbose: bool = False) -> None: session.add(resultats_france) resultats_dict = { + 'france': { + 'regions': [reg.code_insee for reg in session.execute(select(Region)).scalars().all()], + 'departements': [dpt.code_insee for dpt in session.execute(select(Departement)).scalars().all()], + 'circonscriptions': [circo.id for circo in session.execute(select(Circonscription)).scalars().all()], + }, "inscrits": resultats_france.inscrits, "votants": resultats_france.votants, "abstentions": resultats_france.abstentions, @@ -105,7 +110,11 @@ def exporter_resultats_regions(engine: Engine, verbose: bool = False) -> None: regions_iterator = tqdm(regions, desc="Régions") if verbose else regions for region in regions_iterator: region_json = {'code_insee': region.code_insee, 'nom': region.libelle, - 'departements': [dpt.code_insee for dpt in region.departements]} + 'departements': [dpt.code_insee for dpt in region.departements], + 'circonscriptions': + [circo.id for circo in session.execute( + select(Circonscription).join(Departement).filter_by(region_code=region.code_insee)) + .scalars().all()]} regions_json.append(region_json) resultats_region = session.execute(select(ResultatsRegion).filter_by(region_id=region.code_insee)) \ @@ -318,6 +327,7 @@ def exporter_resultats_bureaux_vote(engine: Engine, verbose: bool = False) -> No iterator = tqdm(bureaux_vote, desc="Bureaux de vote") if verbose else bureaux_vote for bureau_vote in iterator: bureau_vote_json = {'id': bureau_vote.id, + 'libelle': bureau_vote.libelle, 'commune': bureau_vote.commune_code, 'circonscription': bureau_vote.circo_code} bureaux_vote_json.append(bureau_vote_json) diff --git a/nupes/scripts/import_geographie.py b/nupes/scripts/import_geographie.py index b6de1b0..910da5b 100644 --- a/nupes/scripts/import_geographie.py +++ b/nupes/scripts/import_geographie.py @@ -251,7 +251,7 @@ def importer_bureaux_vote(engine: Engine, verbose: bool = False) -> None: numero_circo = int(bv_dict['codeCirconscription'][len(dpt_code):]) code_circo = f"{dpt_code}-{numero_circo:02d}" bv_id = bv_dict['id_bv'].split()[0] - bv_libelle = f"Bureau {code_bv}" + bv_libelle = f"Bureau {code_bv} de {bv_dict['nomCommune']}" if not session.execute(select(Commune).filter_by(code_insee=code_commune)).scalar_one_or_none(): print("Commune non trouvée avec le code", code_commune, "et le nom", bv_dict['nomCommune'])