diff --git a/nupes-elections-front/src/Elections2024.js b/nupes-elections-front/src/Elections2024.js
index fc6e351..a46f4c9 100644
--- a/nupes-elections-front/src/Elections2024.js
+++ b/nupes-elections-front/src/Elections2024.js
@@ -1,572 +1,80 @@
import {useParams} from "react-router-dom"
-import {MenuItem, Select} from "@mui/material"
-import FormGroup from '@mui/material/FormGroup'
-import FormControlLabel from '@mui/material/FormControlLabel'
-import Table from '@mui/material/Table'
-import TableBody from '@mui/material/TableBody'
-import TableCell from '@mui/material/TableCell'
-import TableContainer from '@mui/material/TableContainer'
-import TableHead from '@mui/material/TableHead'
-import TableRow from '@mui/material/TableRow'
-import Paper from '@mui/material/Paper'
-import Switch from '@mui/material/Switch'
+import {AppBar, Container, Toolbar} from "@mui/material"
import * as Highcharts from 'highcharts'
import highchartsItem from 'highcharts/modules/item-series'
-import HighchartsReact from 'highcharts-react-official'
import {useEffect, useMemo, useState} from "react"
-import {GeoJSON, MapContainer, Popup, TileLayer, useMap} from "react-leaflet"
-
-import bbox from 'geojson-bbox'
+import {
+ SelectionAffichage,
+ TableauParticipation,
+ CarteResultats,
+ HistogrammeVoix, CompositionHemicycle, GroupementParBloc, RetirerSeuil
+} from "./includes/composants_elections"
+import {TableauResultatsEuropeennes} from "./includes/composants_elections_europeennes"
+import {calculerSieges, getNomZone, regrouperVoix} from "./utils"
import 'leaflet/dist/leaflet.css'
+
highchartsItem(Highcharts)
-function ResultatsTable({blocs, nuances, listes, resultats, siegesParListe}) {
- const voix_listes = resultats?.voix_listes ?? {}
- const listes_triees = listes.toSorted((l1, l2) => {
- return (voix_listes[l2.numero] || 0) - (voix_listes[l1.numero] || 0)
- })
-
- return <>
-
-
-
-
- Numéro
- Liste
- Nuance
- Bloc
- Voix
- % Inscrit⋅es
- % Exprimé⋅es
- Sièges
-
-
-
- {listes_triees.map((liste) => (
-
- ))}
-
-
-
- >
-}
-
-
-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 <>
-
-
-
-
-
- Nombre
- % Inscrit⋅es
- % Votant⋅es
-
-
-
-
- Inscrit⋅es
- {resultats.inscrits}
-
-
-
-
- Abstention
- {resultats.abstentions}
- {(100 * resultats.abstentions / resultats.inscrits).toFixed(2)} %
-
-
-
- Votant⋅es
- {resultats.votants}
- {(100 * resultats.votants / resultats.inscrits).toFixed(2)} %
-
-
-
- Blancs
- {resultats.blancs}
- {(100 * resultats.blancs / resultats.inscrits).toFixed(2)} %
- {(100 * resultats.blancs / resultats.votants).toFixed(2)} %
-
-
- Nuls
- {resultats.nuls}
- {(100 * resultats.nuls / resultats.inscrits).toFixed(2)} %
- {(100 * resultats.nuls / resultats.votants).toFixed(2)} %
-
-
- Exprimés
- {resultats.exprimes}
- {(100 * resultats.exprimes / resultats.inscrits).toFixed(2)} %
- {(100 * resultats.exprimes / resultats.votants).toFixed(2)} %
-
-
-
-
- >
-}
-
-function ZoneGeoJSON({typeResultats, resultatsZone, typeZone, listes, blocs, nuances, grouperParBloc = false}) {
- const [idZone, nomZone] = useMemo(() => {
- if (!resultatsZone[typeZone])
- return ["", ""]
-
- if (typeZone === "region" || typeZone === "departement" || typeZone === "commune")
- return [resultatsZone[typeZone].code_insee, resultatsZone[typeZone].nom]
- else if (typeZone === "circonscription")
- return [resultatsZone.circonscription.id, `Circonscription ${resultatsZone.circonscription.id}`]
- else if (typeZone === "bureau_vote")
- return [resultatsZone.bureau_vote.id, resultatsZone.bureau_vote.libelle]
- else
- return ["", ""]
- }, [typeZone, resultatsZone])
-
- const voix_listes = resultatsZone?.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] += resultatsZone.voix_listes[liste.numero] || 0
- voixParNuance[liste.nuance] += resultatsZone.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
-
- {nomZone}
-
- {listes_triees.slice(0, 5).map(liste =>
- - {liste.nom} : {voix_listes[liste.numero]} ({(100 * voix_listes[liste.numero] / resultatsZone.exprimes).toFixed(2)} %)
)}
-
-
-
-}
-
-function ContenuCarte({typeResultats, resultats, typeZone, listes, blocs, nuances, grouperParBloc = false}) {
- const map = useMap()
- const [resultatsZones, setResultatsZones] = useState([])
-
- const zones = useMemo(() => {
- const data = resultats[typeResultats]
- if (!data)
- return []
-
- if (typeZone === "region")
- return data?.regions ?? []
- else if (typeZone === "departement")
- return data?.departements ?? []
- else if (typeZone === "circonscription")
- return data?.circonscriptions ?? []
- else if (typeZone === "commune")
- return data?.communes ?? []
- else if (typeZone === "bureau_vote") {
- if (typeResultats === "bureau_vote")
- return data ? [data.id] : []
- else
- return data?.bureaux_vote ?? []
- }
- else
- return []
- }, [typeResultats, resultats, typeZone])
-
- useEffect(() => {
- if (typeResultats === "france")
- return
-
- const geometry = resultats.geometry
- if (geometry) {
- // On centre la carte sur la zone
- const geometry_bbox = bbox(geometry)
- map.fitBounds([[geometry_bbox[1], geometry_bbox[0]], [geometry_bbox[3], geometry_bbox[2]]])
- }
- }, [typeResultats, resultats, map])
-
- useEffect(() => {
- if (!zones)
- return
-
- setResultatsZones(resultatsZones => [])
-
- zones.forEach(zone_id => {
- fetch(`/data/resultats/europeennes2024/${typeZone}/${zone_id}.json`).then(response => response.json())
- .then(resultatsZone => setResultatsZones(resultatsZones => [...resultatsZones, resultatsZone]))
- })
- }, [typeZone, zones, resultats])
-
- function getZoneIdentifier(typeZone, zone) {
- if (typeZone === "region" || typeZone === "departement" || typeZone === "commune")
- return zone.code_insee
- else if (typeZone === "circonscription" || typeZone === "bureau_vote")
- return zone.id
- else
- return ""
- }
-
- return <>
- {resultatsZones.filter(resultatsZone => resultatsZone.geometry['type']).map(resultatsZone =>
- )}
- >
-}
-
-function Carte({typeResultats, resultats, typeZone, listes, blocs, nuances, grouperParBloc = false}) {
- const center = typeResultats === "france" ? [46.603354, 1.888334] : [0, 0]
-
- return <>
-
-
-
-
- >
-}
-
-function SelectionAffichage({typeResultats, typeZone, setTypeZone}) {
- const items = useMemo(() => {
- const items = []
- if (typeResultats === "france") {
- setTypeZone("region")
- items.push()
- }
-
- if (typeResultats === "france" || typeResultats === "region") {
- if (typeResultats !== "france")
- setTypeZone("departement")
- items.push()
- }
-
- if (typeResultats === "france" || typeResultats === "region" || typeResultats === "departement") {
- if (typeResultats !== "france" && typeResultats !== "region")
- setTypeZone("circonscription")
- items.push()
- }
-
- if (typeResultats === "departement") {
- items.push()
- }
-
- if (typeResultats === "circonscription" || typeResultats === "commune" || typeResultats === "bureau_vote") {
- setTypeZone("bureau_vote")
- items.push()
- }
-
- return items
- }, [typeResultats, setTypeZone])
-
- return
-}
-
export default function Election2024() {
const {typeResultats, zoneId} = useParams()
- const [zoneName, setZoneName] = useState("France")
const [grouperParBloc, setGrouperParBloc] = useState(false)
const [retirerSeuil, setRetirerSeuil] = useState(false)
const [blocs, setBlocs] = useState([])
const [nuances, setNuances] = useState([])
const [listes, setListes] = useState([])
const [resultats, setResultats] = useState([])
- const [siegesParListe, setSiegesParListe] = useState({})
- const [voixParBloc, setVoixParBloc] = useState([])
- const [voixParNuance, setVoixParNuance] = useState([])
- const [siegesParBloc, setSiegesParBloc] = useState([])
- const [siegesParNuance, setSiegesParNuance] = useState([])
- const [categoriesVoix, setCategoriesVoix] = useState([])
- const [dataVoix, setDataVoix] = useState([])
- const [dataSieges, setDataSieges] = useState([])
const [typeZone, setTypeZone] = useState("region")
useEffect(() => {
- fetch("/data/resultats/europeennes2024/blocs.json").then(response => response.json())
+ fetch("/data/resultats/europeennes/2024/blocs.json").then(response => response.json())
.then(data => setBlocs(data))
- fetch("/data/resultats/europeennes2024/nuances.json").then(response => response.json())
+ fetch("/data/resultats/europeennes/2024/nuances.json").then(response => response.json())
.then(data => setNuances(data))
- fetch("/data/resultats/europeennes2024/listes.json").then(response => response.json())
+ fetch("/data/resultats/europeennes/2024/listes.json").then(response => response.json())
.then(data => setListes(data))
if (typeResultats === "france") {
- fetch("/data/resultats/europeennes2024/france.json").then(response => response.json())
+ fetch("/data/resultats/europeennes/2024/france.json").then(response => response.json())
.then(data => setResultats(data))
}
else {
- fetch(`/data/resultats/europeennes2024/${typeResultats}/${zoneId}.json`).then(response => response.json())
+ fetch(`/data/resultats/europeennes/2024/${typeResultats}/${zoneId}.json`)
+ .then(response => response.json())
.then(data => setResultats(data))
}
}, [typeResultats, zoneId])
- useEffect(() => {
- if (!resultats || resultats.length === 0)
- setZoneName("")
- else if (typeResultats === "france")
- setZoneName("France")
- else if (typeResultats === "region")
- setZoneName(`Région ${resultats.region.nom}`)
- else if (typeResultats === "departement")
- setZoneName(`Département ${resultats.departement.nom}`)
- else if (typeResultats === "circonscription")
- setZoneName(`Circonscription ${resultats.circonscription.id}`)
- else if (typeResultats === "commune")
- setZoneName(`Commune ${resultats.commune.nom}`)
- else if (typeResultats === "bureau_vote")
- setZoneName(resultats.bureau_vote.libelle)
- }, [typeResultats, resultats])
+ const nomZone = useMemo(() => getNomZone(typeResultats, resultats),
+ [typeResultats, resultats])
- useEffect(() => {
- if (!resultats['voix_listes'])
- return
+ const [voixParBloc, voixParNuance] = regrouperVoix(resultats.voix, listes, blocs, nuances)
- const parBloc = {}
- const parNuance = {}
- for (let bloc of blocs) {
- parBloc[bloc.nom] = 0
- }
- for (let nuance of nuances) {
- parNuance[nuance.code] = 0
- }
-
- for (let liste of listes) {
- parBloc[liste.bloc] += resultats?.voix_listes[liste.numero] ?? 0
- parNuance[liste.nuance] += resultats?.voix_listes[liste.numero] ?? 0
- }
-
- setVoixParBloc(parBloc)
- setVoixParNuance(parNuance)
- }, [blocs, nuances, listes, resultats])
-
- useEffect(() => {
- const categories = []
- const data = []
- if (grouperParBloc) {
- for (let bloc of blocs) {
- categories.push(bloc.nom)
- data.push([bloc.nom, voixParBloc[bloc.nom], bloc.couleur, bloc.nom])
- }
- }
- else {
- for (let nuance of nuances) {
- categories.push(nuance.nom)
- data.push([nuance.nom, voixParNuance[nuance.code], nuance.couleur, nuance.nom])
- }
- }
- setCategoriesVoix(categories)
- setDataVoix(data)
- }, [voixParBloc, voixParNuance, blocs, nuances, grouperParBloc])
-
- useEffect(() => {
- if (!resultats['voix_listes'])
- return
-
- const MAX_SIEGES = 81
- const sieges = {}
- const listesElues = []
- let siegesAffectes = 0
- let totalVoix = resultats.exprimes
- for (let liste of listes) {
- const voix = resultats?.voix_listes[liste.numero] ?? 0
- if (voix / resultats.exprimes < 0.05 && !retirerSeuil) {
- // Barre des 5 % non franchie
- totalVoix -= voix
- sieges[liste.numero] = 0
- }
- else {
- listesElues.push(liste)
- }
- }
-
- if (listesElues.length === 0)
- return
-
- for (let liste of listesElues) {
- const voix = resultats?.voix_listes[liste.numero] ?? 0
- sieges[liste.numero] = Math.floor(MAX_SIEGES * voix / totalVoix)
- siegesAffectes += sieges[liste.numero]
- }
-
- while (siegesAffectes < MAX_SIEGES) {
- // Méthode de la plus forte moyenne pour affecter les sièges restants
- let maxMoyenne = 0
- let listeElue = null
- for (let liste of listesElues) {
- if (sieges[liste.numero] < MAX_SIEGES) {
- const voix = resultats?.voix_listes[liste.numero] ?? 0
- const moyenne = voix / (sieges[liste.numero] + 1)
- if (moyenne > maxMoyenne) {
- maxMoyenne = moyenne
- listeElue = liste
- }
- }
- }
- sieges[listeElue.numero]++
- siegesAffectes++
- }
-
- setSiegesParListe(sieges)
- }, [listes, resultats, retirerSeuil])
-
- useEffect(() => {
- const parBloc = {}
- const parNuance = {}
- for (let bloc of blocs) {
- parBloc[bloc.nom] = 0
- }
- for (let nuance of nuances) {
- parNuance[nuance.code] = 0
- }
-
- for (let liste of listes) {
- parBloc[liste.bloc] += siegesParListe[liste.numero] || 0
- parNuance[liste.nuance] += siegesParListe[liste.numero] || 0
- }
-
- setSiegesParBloc(parBloc)
- setSiegesParNuance(parNuance)
- }, [blocs, nuances, listes, siegesParListe])
-
- useEffect(() => {
- const data = []
- if (grouperParBloc) {
- for (let bloc of blocs) {
- data.push([bloc.nom, siegesParBloc[bloc.nom], bloc.couleur, bloc.nom])
- }
- }
- else {
- for (let nuance of nuances) {
- data.push([nuance.nom, siegesParNuance[nuance.code], nuance.couleur, nuance.nom])
- }
- }
- setDataSieges(data)
- }, [blocs, nuances, siegesParBloc, siegesParNuance, grouperParBloc])
-
- const compositonOptions = {
- chart: {
- type: 'item'
- },
- title: {
- text: 'Projection eurodéputé⋅es français⋅es 2024'
- },
- legend: {
- labelFormat: '{name} {y}'
- },
- series: [{
- name: 'Nombre de sièges',
- keys: ['name', 'y', 'color', 'label'],
- data: dataSieges,
- dataLabels: {
- enabled: false,
- format: '{point.label}'
- },
- // Circular options
- center: ['50%', '88%'],
- size: '170%',
- startAngle: -100,
- endAngle: 100
- }]
- }
-
- const scoreOptions = {
- chart: {
- type: 'column'
- },
- title: {
- text: `Résultats des élections européennes 2024 : ${zoneName}`,
- },
- tooltip: {
- formatter: function () {
- return `${this.x} : ${this.y} voix (${(100 * this.y / resultats.exprimes).toFixed(2)} %)
`
- }
- },
- xAxis: {
- categories: categoriesVoix,
- },
- series: [{
- name: "Nombre de voix",
- keys: ['name', 'y', 'color', 'label'],
- data: dataVoix,
- }]
- }
+ const siegesParListe = calculerSieges(listes, resultats, retirerSeuil ? 0 : 0.05)
+ const [siegesParBloc, siegesParNuance] = regrouperVoix(siegesParListe, listes, blocs, nuances)
return <>
-
- setGrouperParBloc(event.target.checked)} inputProps={{ 'aria-label': 'controlled' }} />}
- label="Grouper par bloc plutôt que nuance politique" />
-
-
- setRetirerSeuil(event.target.checked)} inputProps={{ 'aria-label': 'controlled' }} />}
- label="Retirer le seuil des 5 %" />
-
-
-
-
-
-
- }
- label="Type d'affichage pour la carte" />
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
>
}
diff --git a/nupes-elections-front/src/includes/composants_elections.js b/nupes-elections-front/src/includes/composants_elections.js
new file mode 100644
index 0000000..d1a558c
--- /dev/null
+++ b/nupes-elections-front/src/includes/composants_elections.js
@@ -0,0 +1,374 @@
+import {trierCandidats, regrouperVoix} from "../utils"
+import TableContainer from "@mui/material/TableContainer"
+import Paper from "@mui/material/Paper"
+import Table from "@mui/material/Table"
+import TableHead from "@mui/material/TableHead"
+import TableRow from "@mui/material/TableRow"
+import TableCell from "@mui/material/TableCell"
+import TableBody from "@mui/material/TableBody"
+import {useEffect, useMemo, useState} from "react"
+import {GeoJSON, MapContainer, Popup, TileLayer, useMap} from "react-leaflet"
+import bbox from "geojson-bbox"
+import {FormControl, InputLabel, MenuItem, Select} from "@mui/material"
+import * as Highcharts from "highcharts";
+import HighchartsReact from "highcharts-react-official";
+import Switch from "@mui/material/Switch";
+import FormControlLabel from "@mui/material/FormControlLabel";
+
+
+export function HistogrammeVoix({titre, resultats, voixParNuance, voixParBloc, blocs, nuances, grouperParBloc}) {
+ const [categoriesVoix, dataVoix] = useMemo(() => {
+ const categories = []
+ const data = []
+ if (grouperParBloc) {
+ for (let bloc of blocs) {
+ categories.push(bloc.nom)
+ data.push([bloc.nom, voixParBloc[bloc.nom], bloc.couleur, bloc.nom])
+ }
+ }
+ else {
+ for (let nuance of nuances) {
+ categories.push(nuance.nom)
+ data.push([nuance.nom, voixParNuance[nuance.code], nuance.couleur, nuance.nom])
+ }
+ }
+
+ return [categories, data]
+ }, [voixParBloc, voixParNuance, blocs, nuances, grouperParBloc])
+
+ const scoreOptions = {
+ chart: {
+ type: 'column'
+ },
+ title: {
+ text: titre,
+ },
+ tooltip: {
+ formatter: function () {
+ return `${this.x} : ${this.y} voix (${(100 * this.y / resultats.exprimes).toFixed(2)} %)
`
+ }
+ },
+ xAxis: {
+ categories: categoriesVoix,
+ },
+ series: [{
+ name: "Nombre de voix",
+ keys: ['name', 'y', 'color', 'label'],
+ data: dataVoix,
+ }]
+ }
+
+ return
+}
+
+export function CompositionHemicycle({titre, blocs, nuances, siegesParBloc, siegesParNuance, grouperParBloc}) {
+ const dataSieges = useMemo(() => {
+ const data = []
+ if (grouperParBloc) {
+ for (let bloc of blocs) {
+ data.push([bloc.nom, siegesParBloc[bloc.nom], bloc.couleur, bloc.nom])
+ }
+ }
+ else {
+ for (let nuance of nuances) {
+ data.push([nuance.nom, siegesParNuance[nuance.code], nuance.couleur, nuance.nom])
+ }
+ }
+
+ return data
+ }, [blocs, nuances, siegesParBloc, siegesParNuance, grouperParBloc])
+
+ const compositonOptions = {
+ chart: {
+ type: 'item'
+ },
+ title: {
+ text: titre,
+ },
+ legend: {
+ labelFormat: '{name} {y}'
+ },
+ series: [{
+ name: 'Nombre de sièges',
+ keys: ['name', 'y', 'color', 'label'],
+ data: dataSieges,
+ dataLabels: {
+ enabled: false,
+ format: '{point.label}'
+ },
+ // Circular options
+ center: ['50%', '88%'],
+ size: '170%',
+ startAngle: -100,
+ endAngle: 100
+ }]
+ }
+
+ return
+}
+
+/**
+ * Tableau de participation de l'élection dans la zone concernée
+ * @param resultats
+ * @return {JSX.Element}
+ * @constructor
+ */
+export function TableauParticipation({resultats}) {
+ return <>
+
+
+
+
+
+ Nombre
+ % Inscrit⋅es
+ % Votant⋅es
+
+
+
+
+ Inscrit⋅es
+ {resultats.inscrits}
+
+
+
+
+ Abstention
+ {resultats.abstentions}
+ {(100 * resultats.abstentions / resultats.inscrits).toFixed(2)} %
+
+
+
+ Votant⋅es
+ {resultats.votants}
+ {(100 * resultats.votants / resultats.inscrits).toFixed(2)} %
+
+
+
+ Blancs
+ {resultats.blancs}
+ {(100 * resultats.blancs / resultats.inscrits).toFixed(2)} %
+ {(100 * resultats.blancs / resultats.votants).toFixed(2)} %
+
+
+ Nuls
+ {resultats.nuls}
+ {(100 * resultats.nuls / resultats.inscrits).toFixed(2)} %
+ {(100 * resultats.nuls / resultats.votants).toFixed(2)} %
+
+
+ Exprimés
+ {resultats.exprimes}
+ {(100 * resultats.exprimes / resultats.inscrits).toFixed(2)} %
+ {(100 * resultats.exprimes / resultats.votants).toFixed(2)} %
+
+
+
+
+ >
+}
+
+export function GroupementParBloc({grouperParBloc, setGrouperParBloc}) {
+ return setGrouperParBloc(event.target.checked)} inputProps={{ 'aria-label': 'controlled' }} />}
+ label="Grouper par bloc plutôt que nuance politique" />
+}
+
+export function RetirerSeuil({retirerSeuil, setRetirerSeuil}) {
+ return setRetirerSeuil(event.target.checked)} inputProps={{ 'aria-label': 'controlled' }} />}
+ label="Retirer le seuil des 5 %" />
+}
+
+export function SelectionAffichage({typeResultats, typeZone, setTypeZone}) {
+ const items = useMemo(() => {
+ const items = []
+ if (typeResultats === "france") {
+ setTypeZone("region")
+ items.push()
+ }
+
+ if (typeResultats === "france" || typeResultats === "region") {
+ if (typeResultats !== "france")
+ setTypeZone("departement")
+ items.push()
+ }
+
+ if (typeResultats === "france" || typeResultats === "region" || typeResultats === "departement") {
+ if (typeResultats !== "france" && typeResultats !== "region")
+ setTypeZone("circonscription")
+ items.push()
+ }
+
+ if (typeResultats === "departement") {
+ items.push()
+ }
+
+ if (typeResultats === "circonscription" || typeResultats === "commune" || typeResultats === "bureau_vote") {
+ setTypeZone("bureau_vote")
+ items.push()
+ }
+
+ return items
+ }, [typeResultats, setTypeZone])
+
+ return
+
+ Zone à afficher
+
+
+
+}
+
+
+function ZoneGeoJSON({typeElection, anneeElection, resultatsZone, typeZone,
+ candidats, blocs, nuances, grouperParBloc = false}) {
+ const [idZone, nomZone] = useMemo(() => {
+ if (!resultatsZone[typeZone])
+ return ["", ""]
+
+ if (typeZone === "region" || typeZone === "departement" || typeZone === "commune")
+ return [resultatsZone[typeZone].code_insee, resultatsZone[typeZone].nom]
+ else if (typeZone === "circonscription")
+ return [resultatsZone.circonscription.id, `Circonscription ${resultatsZone.circonscription.id}`]
+ else if (typeZone === "bureau_vote")
+ return [resultatsZone.bureau_vote.id, resultatsZone.bureau_vote.libelle]
+ else
+ return ["", ""]
+ }, [typeZone, resultatsZone])
+
+ const voixCandidats = useMemo(() => resultatsZone?.voix ?? {}, [resultatsZone])
+ const candidatsTries = trierCandidats(candidats, voixCandidats)
+
+ const [voixParBloc, voixParNuance] = regrouperVoix(voixCandidats, candidats, blocs, nuances)
+
+ 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
+
+ {nomZone}
+
+ {candidatsTries.slice(0, 5).map(candidat =>
+ -
+ {candidat.nom} : {voixCandidats[candidat.numero]} ({(100 * voixCandidats[candidat.numero] / resultatsZone.exprimes).toFixed(2)} %)
+
)}
+
+
+
+}
+
+function ContenuCarte({typeElection, anneeElection, typeResultats, resultats,
+ typeZone, candidats, blocs, nuances, grouperParBloc = false}) {
+ const map = useMap()
+ const [resultatsZones, setResultatsZones] = useState([])
+
+ const zones = useMemo(() => {
+ const data = resultats[typeResultats]
+ if (!data)
+ return []
+
+ if (typeZone === "region")
+ return data?.regions ?? []
+ else if (typeZone === "departement")
+ return data?.departements ?? []
+ else if (typeZone === "circonscription")
+ return data?.circonscriptions ?? []
+ else if (typeZone === "commune")
+ return data?.communes ?? []
+ else if (typeZone === "bureau_vote") {
+ if (typeResultats === "bureau_vote")
+ return data ? [data.id] : []
+ else
+ return data?.bureaux_vote ?? []
+ }
+ else
+ return []
+ }, [typeResultats, resultats, typeZone])
+
+ useEffect(() => {
+ if (typeResultats === "france")
+ return
+
+ const geometry = resultats.geometry
+ if (geometry) {
+ // On centre la carte sur la zone
+ const geometry_bbox = bbox(geometry)
+ map.fitBounds([[geometry_bbox[1], geometry_bbox[0]], [geometry_bbox[3], geometry_bbox[2]]])
+ }
+ }, [typeResultats, resultats, map])
+
+ useEffect(() => {
+ if (!zones)
+ return
+
+ setResultatsZones(resultatsZones => [])
+
+ zones.forEach(zoneId => {
+ fetch(`/data/resultats/${typeElection}/${anneeElection}/${typeZone}/${zoneId}.json`)
+ .then(response => response.json())
+ .then(resultatsZone => setResultatsZones(resultatsZones => [...resultatsZones, resultatsZone]))
+ })
+ }, [typeElection, anneeElection, typeZone, zones, resultats])
+
+ function getZoneIdentifier(typeZone, zone) {
+ if (typeZone === "region" || typeZone === "departement" || typeZone === "commune")
+ return zone.code_insee
+ else if (typeZone === "circonscription" || typeZone === "bureau_vote")
+ return zone.id
+ else
+ return ""
+ }
+
+ return <>
+ {resultatsZones.filter(resultatsZone => resultatsZone.geometry['type']).map(resultatsZone =>
+ )}
+ >
+}
+
+export function CarteResultats({typeElection, anneeElection, typeResultats, resultats,
+ typeZone, candidats, blocs, nuances, grouperParBloc = false}) {
+ const center = typeResultats === "france" ? [46.603354, 1.888334] : [0, 0]
+
+ return <>
+
+
+
+
+ >
+}
diff --git a/nupes-elections-front/src/includes/composants_elections_europeennes.js b/nupes-elections-front/src/includes/composants_elections_europeennes.js
new file mode 100644
index 0000000..06f703f
--- /dev/null
+++ b/nupes-elections-front/src/includes/composants_elections_europeennes.js
@@ -0,0 +1,66 @@
+import {trierCandidats} from "../utils"
+import TableContainer from "@mui/material/TableContainer"
+import Paper from "@mui/material/Paper"
+import Table from "@mui/material/Table"
+import TableHead from "@mui/material/TableHead"
+import TableRow from "@mui/material/TableRow"
+import TableCell from "@mui/material/TableCell"
+import TableBody from "@mui/material/TableBody"
+
+/**
+ * Composant pour le tableau des résultats des élections européennes par liste
+ * @param blocs
+ * @param nuances
+ * @param listes
+ * @param resultats
+ * @param siegesParListe
+ * @return {JSX.Element}
+ * @constructor
+ */
+export function TableauResultatsEuropeennes({blocs, nuances, listes, resultats, siegesParListe}) {
+ const voix_listes = resultats?.voix_listes ?? {}
+ const listes_triees = trierCandidats(listes, voix_listes)
+
+ return <>
+
+
+
+
+ Numéro
+ Liste
+ Nuance
+ Bloc
+ Voix
+ % Inscrit⋅es
+ % Exprimé⋅es
+ Sièges
+
+
+
+ {listes_triees.map((liste) => (
+
+ ))}
+
+
+
+ >
+}
+
+function LigneListe({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]}
+
+}
diff --git a/nupes-elections-front/src/utils.js b/nupes-elections-front/src/utils.js
new file mode 100644
index 0000000..7d3f0aa
--- /dev/null
+++ b/nupes-elections-front/src/utils.js
@@ -0,0 +1,96 @@
+export function getNomZone(typeResultats, resultats) {
+ if (!resultats || resultats.length === 0)
+ return ""
+ else if (typeResultats === "france")
+ return "France"
+ else if (typeResultats === "region")
+ return `Région ${resultats.region.nom}`
+ else if (typeResultats === "departement")
+ return `Département ${resultats.departement.nom}`
+ else if (typeResultats === "circonscription")
+ return `Circonscription ${resultats.circonscription.id}`
+ else if (typeResultats === "commune")
+ return `Commune ${resultats.commune.nom}`
+ else if (typeResultats === "bureau_vote")
+ return resultats.bureau_vote.libelle
+}
+
+export function trierCandidats(candidats, voix_par_candidat) {
+ return candidats.toSorted((l1, l2) => {
+ return (voix_par_candidat[l2.numero] || 0) - (voix_par_candidat[l1.numero] || 0)
+ })
+}
+
+export function regrouperVoix(voixCandidats, candidats, blocs, nuances) {
+ if (!candidats || !voixCandidats || !blocs || !nuances
+ || candidats.length === 0 || blocs.length === 0 || nuances.length === 0)
+ return [{}, {}]
+
+ const parBloc = {}
+ const parNuance = {}
+
+ for (let bloc of blocs) {
+ parBloc[bloc.nom] = 0
+ }
+ for (let nuance of nuances) {
+ parNuance[nuance.code] = 0
+ }
+
+ for (let candidat of candidats) {
+ parBloc[candidat.bloc] += voixCandidats[candidat.numero] || 0
+ parNuance[candidat.nuance] += voixCandidats[candidat.numero] || 0
+ }
+
+ return [parBloc, parNuance]
+}
+
+export function calculerSieges(listes, resultats, seuil = 0.05) {
+ if (!resultats['voix'])
+ return {}
+
+ const MAX_SIEGES = 81
+ const sieges = {}
+ const listesElues = []
+ let siegesAffectes = 0
+ let totalVoix = resultats.exprimes
+ for (let liste of listes) {
+ const voix = resultats?.voix[liste.numero] ?? 0
+ if (voix / resultats.exprimes < seuil) {
+ // Barre des 5 % non franchie
+ totalVoix -= voix
+ sieges[liste.numero] = 0
+ }
+ else {
+ listesElues.push(liste)
+ }
+ }
+
+ if (listesElues.length === 0)
+ return
+
+ for (let liste of listesElues) {
+ const voix = resultats?.voix[liste.numero] ?? 0
+ sieges[liste.numero] = Math.floor(MAX_SIEGES * voix / totalVoix)
+ siegesAffectes += sieges[liste.numero]
+ }
+
+ while (siegesAffectes < MAX_SIEGES) {
+ // Méthode de la plus forte moyenne pour affecter les sièges restants
+ let maxMoyenne = 0
+ let listeElue = null
+ for (let liste of listesElues) {
+ if (sieges[liste.numero] < MAX_SIEGES) {
+ const voix = resultats?.voix[liste.numero] ?? 0
+ const moyenne = voix / (sieges[liste.numero] + 1)
+ if (moyenne > maxMoyenne) {
+ maxMoyenne = moyenne
+ listeElue = liste
+ }
+ }
+ }
+ sieges[listeElue.numero]++
+ siegesAffectes++
+ }
+
+ return sieges
+ }
diff --git a/nupes/scripts/export_resultats_2024.py b/nupes/scripts/export_resultats_2024.py
index 8f91393..65f123d 100644
--- a/nupes/scripts/export_resultats_2024.py
+++ b/nupes/scripts/export_resultats_2024.py
@@ -22,7 +22,7 @@ def exporter_listes(engine: Engine, verbose: bool = False) -> None:
bloc_json = {'id': bloc.id, 'nom': bloc.nom, 'couleur': bloc.couleur}
blocs_json.append(bloc_json)
- file = DATA_DIR / "resultats" / "europeennes2024" / "blocs.json"
+ file = DATA_DIR / "resultats" / "europeennes" / "2024" / "blocs.json"
if not file.parent.is_dir():
file.parent.mkdir(parents=True)
@@ -36,7 +36,7 @@ def exporter_listes(engine: Engine, verbose: bool = False) -> None:
nuance_json = {'code': nuance.code, 'nom': nuance.nom, 'couleur': nuance.couleur}
nuances_json.append(nuance_json)
- file = DATA_DIR / "resultats" / "europeennes2024" / "nuances.json"
+ file = DATA_DIR / "resultats" / "europeennes" / "2024" / "nuances.json"
if not file.parent.is_dir():
file.parent.mkdir(parents=True)
@@ -57,7 +57,7 @@ def exporter_listes(engine: Engine, verbose: bool = False) -> None:
'bloc': liste.bloc.nom, 'candidats': candidats}
listes_json.append(liste_json)
- file = DATA_DIR / "resultats" / "europeennes2024" / "listes.json"
+ file = DATA_DIR / "resultats" / "europeennes" / "2024" / "listes.json"
if not file.parent.is_dir():
file.parent.mkdir(parents=True)
@@ -88,11 +88,11 @@ def exporter_resultats_france(engine: Engine, verbose: bool = False) -> None:
}
resultats_listes = {}
- resultats_dict['voix_listes'] = resultats_listes
+ resultats_dict['voix'] = resultats_listes
for voix_liste in resultats_france.voix_listes:
resultats_listes[voix_liste.liste.numero] = voix_liste.voix
- file = DATA_DIR / "resultats" / "europeennes2024" / "france.json"
+ file = DATA_DIR / "resultats" / "europeennes" / "2024" / "france.json"
if not file.parent.is_dir():
file.parent.mkdir(parents=True)
@@ -136,11 +136,11 @@ def exporter_resultats_regions(engine: Engine, verbose: bool = False) -> None:
}
resultats_listes = {}
- resultats_dict['voix_listes'] = resultats_listes
+ resultats_dict['voix'] = resultats_listes
for voix_liste in resultats_region.voix_listes:
resultats_listes[voix_liste.liste.numero] = voix_liste.voix
- file = DATA_DIR / "resultats" / "europeennes2024" / "region" / f"{region.code_insee}.json"
+ file = DATA_DIR / "resultats" / "europeennes" / "2024" / "region" / f"{region.code_insee}.json"
if not file.parent.is_dir():
file.parent.mkdir(parents=True)
@@ -149,7 +149,7 @@ def exporter_resultats_regions(engine: Engine, verbose: bool = False) -> None:
session.commit()
- regions_file = DATA_DIR / "resultats" / "europeennes2024" / "region" / "regions.json"
+ regions_file = DATA_DIR / "resultats" / "europeennes" / "2024" / "region" / "regions.json"
if not regions_file.parent.is_dir():
regions_file.parent.mkdir(parents=True)
@@ -191,11 +191,11 @@ def exporter_resultats_departements(engine: Engine, verbose: bool = False) -> No
}
resultats_listes = {}
- resultats_dict['voix_listes'] = resultats_listes
+ resultats_dict['voix'] = resultats_listes
for voix_liste in resultats_departement.voix_listes:
resultats_listes[voix_liste.liste.numero] = voix_liste.voix
- file = DATA_DIR / "resultats" / "europeennes2024" / "departement" / f"{departement.code_insee}.json"
+ file = DATA_DIR / "resultats" / "europeennes" / "2024" / "departement" / f"{departement.code_insee}.json"
if not file.parent.is_dir():
file.parent.mkdir(parents=True)
@@ -204,7 +204,7 @@ def exporter_resultats_departements(engine: Engine, verbose: bool = False) -> No
session.commit()
- departements_file = DATA_DIR / "resultats" / "europeennes2024" / "departement" / "departements.json"
+ departements_file = DATA_DIR / "resultats" / "europeennes" / "2024" / "departement" / "departements.json"
if not departements_file.parent.is_dir():
departements_file.parent.mkdir(parents=True)
@@ -245,11 +245,11 @@ def exporter_resultats_circonscriptions(engine: Engine, verbose: bool = False) -
}
resultats_listes = {}
- resultats_dict['voix_listes'] = resultats_listes
+ resultats_dict['voix'] = resultats_listes
for voix_liste in resultats_circonscription.voix_listes:
resultats_listes[voix_liste.liste.numero] = voix_liste.voix
- file = DATA_DIR / "resultats" / "europeennes2024" / "circonscription" / f"{circonscription.id}.json"
+ file = DATA_DIR / "resultats" / "europeennes" / "2024" / "circonscription" / f"{circonscription.id}.json"
if not file.parent.is_dir():
file.parent.mkdir(parents=True)
@@ -258,7 +258,8 @@ def exporter_resultats_circonscriptions(engine: Engine, verbose: bool = False) -
session.commit()
- circonscriptions_file = DATA_DIR / "resultats" / "europeennes2024" / "circonscription" / "circonscriptions.json"
+ circonscriptions_file = (DATA_DIR / "resultats" / "europeennes" / "2024"
+ / "circonscription" / "circonscriptions.json")
if not circonscriptions_file.parent.is_dir():
circonscriptions_file.parent.mkdir(parents=True)
@@ -299,11 +300,11 @@ def exporter_resultats_communes(engine: Engine, verbose: bool = False) -> None:
}
resultats_listes = {}
- resultats_dict['voix_listes'] = resultats_listes
+ resultats_dict['voix'] = resultats_listes
for voix_liste in resultats_commune.voix_listes:
resultats_listes[voix_liste.liste.numero] = voix_liste.voix
- file = DATA_DIR / "resultats" / "europeennes2024" / "commune" / f"{commune.code_insee}.json"
+ file = DATA_DIR / "resultats" / "europeennes" / "2024" / "commune" / f"{commune.code_insee}.json"
if not file.parent.is_dir():
file.parent.mkdir(parents=True)
@@ -312,7 +313,7 @@ def exporter_resultats_communes(engine: Engine, verbose: bool = False) -> None:
session.commit()
- communes_file = DATA_DIR / "resultats" / "europeennes2024" / "commune" / "communes.json"
+ communes_file = DATA_DIR / "resultats" / "europeennes" / "2024" / "commune" / "communes.json"
if not communes_file.parent.is_dir():
communes_file.parent.mkdir(parents=True)
@@ -354,11 +355,11 @@ def exporter_resultats_bureaux_vote(engine: Engine, verbose: bool = False) -> No
}
resultats_listes = {}
- resultats_dict['voix_listes'] = resultats_listes
+ resultats_dict['voix'] = resultats_listes
for voix_liste in resultats_bureau_vote.voix_listes:
resultats_listes[voix_liste.liste.numero] = voix_liste.voix
- file = DATA_DIR / "resultats" / "europeennes2024" / "bureau_vote" / f"{bureau_vote.id}.json"
+ file = DATA_DIR / "resultats" / "europeennes" / "2024" / "bureau_vote" / f"{bureau_vote.id}.json"
if not file.parent.is_dir():
file.parent.mkdir(parents=True)
@@ -367,7 +368,7 @@ def exporter_resultats_bureaux_vote(engine: Engine, verbose: bool = False) -> No
session.commit()
- bureaux_vote_file = DATA_DIR / "resultats" / "europeennes2024" / "bureau_vote" / "bureaux_vote.json"
+ bureaux_vote_file = DATA_DIR / "resultats" / "europeennes" / "2024" / "bureau_vote" / "bureaux_vote.json"
if not bureaux_vote_file.parent.is_dir():
bureaux_vote_file.parent.mkdir(parents=True)