Skip to content
Snippets Groups Projects
Commit 7d9559ab authored by Besson Lucas's avatar Besson Lucas
Browse files

Relier les données à la carte

parent e664a864
No related branches found
No related tags found
No related merge requests found
/data/raw/*.txt /data/raw/*.txt
/data/raw/*.csv /data/raw/*.csv
/data/processed/*.csv
\ No newline at end of file
...@@ -22,3 +22,5 @@ dist-ssr ...@@ -22,3 +22,5 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
/data/processed/*
Source diff could not be displayed: it is stored in LFS. Options to address this: view the blob.
const { generateDepartmentAveragesAndColors } = require('./indicateurs.js');
const express = require("express"); const express = require("express");
const cors = require("cors"); const cors = require("cors");
...@@ -6,30 +8,68 @@ app.use(cors()); ...@@ -6,30 +8,68 @@ app.use(cors());
const PORT = 5000; const PORT = 5000;
// Fonction pour générer des couleurs aléatoires par département
const generateFakeIndicators = () => {
const departments = Array.from({ length: 95 }, (_, i) => (i + 1).toString().padStart(2, "0")); app.get("/api/indicateurs", async (req, res) => {
const colors = [ try {
"#E69F00", // Jaune doré const { averages, colors } = await generateDepartmentAveragesAndColors("score_global");
"#56B4E9", // Bleu clair res.json({
"#009E73", // Vert foncé averages,
"#F0E442", // Jaune vif colors
"#D55E00", // Orange vif });
"#CC79A7" // Rose fuchsia } catch (error) {
]; res.status(500).send('Erreur lors du traitement du fichier CSV');
}
// Ajoute la corse });
departments.push("2A", "2B");
app.get("/api/indicateursPH", async (req, res) => {
return departments.reduce((acc, dept) => { try {
acc[dept] = colors[Math.floor(Math.random() * colors.length)]; const { averages, colors } = await generateDepartmentAveragesAndColors("score_pH");
return acc; res.json({
}, {}); averages,
}; colors
});
app.get("/api/indicateurs", (req, res) => { } catch (error) {
res.json(generateFakeIndicators()); res.status(500).send('Erreur lors du traitement du fichier CSV');
}
});
app.get("/api/indicateursChlore", async (req, res) => {
try {
const { averages, colors } = await generateDepartmentAveragesAndColors("score_Chlore");
res.json({
averages,
colors
}); });
} catch (error) {
res.status(500).send('Erreur lors du traitement du fichier CSV');
}
});
app.get("/api/indicateursNitritesNitrates", async (req, res) => {
try {
const { averages, colors } = await generateDepartmentAveragesAndColors("score_Nitrites_Nitrates");
res.json({
averages,
colors
});
} catch (error) {
res.status(500).send('Erreur lors du traitement du fichier CSV');
}
});
app.get("/api/indicateursMetauxLourds", async (req, res) => {
try {
const { averages, colors } = await generateDepartmentAveragesAndColors("score_Metaux_Lourds");
res.json({
averages,
colors
});
} catch (error) {
res.status(500).send('Erreur lors du traitement du fichier CSV');
}
});
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`Serveur lancé sur http://localhost:${PORT}`); console.log(`Serveur lancé sur http://localhost:${PORT}`);
......
const fs = require("fs");
const csv = require("csv-parser");
// Fonction pour générer la couleur en fonction du score global
const getColorForScore = (score) => {
if (score >= 0.8) return "#4CAF50"; // Vert moyen (très bonne qualité)
if (score >= 0.5) return "#FFEB3B"; // Jaune (acceptable, à surveiller)
if (score >= 0.2) return "#FF9800"; // Orange (médiocre)
return "#F44336"; // Rouge (très pollué)
};
// Fonction pour lire le CSV et calculer les scores moyens par département
const generateDepartmentAveragesAndColors = (scoreType) => {
return new Promise((resolve, reject) => {
const departmentScores = {};
const departmentCounts = {};
fs.createReadStream('data/water_scores_2024_filtered.csv')
.pipe(csv())
.on('data', (row) => {
let dept = row.cddept_x.replace('.0', ''); // Supprimer ".0" si présent
// Traitement spécial pour la Corse (02A et 02B)
if (dept.startsWith('02A') || dept.startsWith('02B')) {
// Supprimer le "0" initial pour obtenir "2A" ou "2B"
dept = dept.substring(1); // Résultat : "2A" ou "2B"
} else {
// Formater les autres codes pour qu'ils aient 2 chiffres
dept = dept.padStart(2, '0');
}
const scoreGlobal = parseFloat(row[scoreType]);
if (!departmentScores[dept]) {
departmentScores[dept] = 0;
departmentCounts[dept] = 0;
}
departmentScores[dept] += scoreGlobal;
departmentCounts[dept]++;
})
.on('end', () => {
const results = {
averages: {},
colors: {}
};
for (const dept in departmentScores) {
const avgScore = departmentScores[dept] / departmentCounts[dept];
results.averages[dept] = avgScore.toFixed(2); // Moyenne avec 2 décimales
results.colors[dept] = getColorForScore(avgScore);
}
console.log('CSV fichier traité');
resolve(results);
})
.on('error', (err) => {
reject(err);
});
});
};
module.exports = { generateDepartmentAveragesAndColors };
\ No newline at end of file
Source diff could not be displayed: it is stored in LFS. Options to address this: view the blob.
Source diff could not be displayed: it is stored in LFS. Options to address this: view the blob.
import { useState } from "react";
import FranceMap from './components/FranceMap'; import FranceMap from './components/FranceMap';
import MetricsComponent from "./components/MetricsComponent"; import MetricsComponent from "./components/MetricsComponent";
import TitleWithSearchComponent from "./components/UpBar"; import TitleWithSearchComponent from "./components/UpBar";
import CheckboxGroup from "./components/CheckBoxGroup"; // Importer le groupe de checkboxes import CheckboxGroup from "./components/CheckBoxGroup"; // Importer le groupe de checkboxes
const App = () => { const App = () => {
// État pour stocker le paramètre sélectionné
const [selectedParam, setSelectedParam] = useState("indicateurs");
// Fonction pour gérer le changement de la case sélectionnée
const handleCheckboxChange = (param) => {
console.log("Changement dans App, nouveau paramètre sélectionné :", param); // Log du changement
setSelectedParam(param); // Mise à jour de l'état avec le paramètre sélectionné
};
console.log("Paramètre sélectionné :", selectedParam);
return ( return (
<div> <div>
<TitleWithSearchComponent /> <TitleWithSearchComponent />
...@@ -11,16 +24,22 @@ const App = () => { ...@@ -11,16 +24,22 @@ const App = () => {
{/* Partie gauche - Paramètres */} {/* Partie gauche - Paramètres */}
<div style={{ flex: 1, height: '100%', padding: '20px' }}> <div style={{ flex: 1, height: '100%', padding: '20px' }}>
<h2>Paramètres</h2> <h2>Paramètres</h2>
<CheckboxGroup /> {/* Utiliser le groupe de checkboxes */} {/* Passer selectedParam et handleCheckboxChange à CheckboxGroup */}
<CheckboxGroup
onCheckboxChange={handleCheckboxChange} // Ici on passe handleCheckboxChange correctement
selectedParam={selectedParam} // Et on passe également selectedParam si nécessaire
/>
</div> </div>
{/* Partie droite - Carte */} {/* Partie droite - Carte */}
<div style={{ flex: 6, minWidth: '320px', height: '100%' }}> <div style={{ flex: 5, minWidth: '320px', height: '100%' }}>
<FranceMap /> {/* Passer selectedParam à FranceMap */}
<FranceMap typeIndic={selectedParam} />
</div> </div>
{/* Partie droite - Metrics */} {/* Partie droite - Metrics */}
<div style={{ flex: 3, minWidth: '300px', height: '100%' }}> <div style={{ flex: 2, minWidth: '300px', height: '100%' }}>
<MetricsComponent /> <MetricsComponent />
</div> </div>
</div> </div>
......
// CheckboxGroup.js
import { useState } from "react"; import { useState } from "react";
import PropTypes from 'prop-types';
import CheckboxWithLabel from "./checkBox"; import CheckboxWithLabel from "./checkBox";
const CheckboxGroup = () => { const CheckboxGroup = ({ onCheckboxChange }) => {
// On utilise ici une seule valeur pour l'état, car seulement une case peut être sélectionnée // État pour stocker la checkbox cochée
const [selectedParam, setSelectedParam] = useState(null); const [selectedParam, setSelectedParam] = useState(null);
// Fonction pour gérer le changement de la case sélectionnée // Fonction pour gérer le changement de la case sélectionnée
const handleCheckboxChange = (param) => { const handleCheckboxChange = (param) => {
setSelectedParam(param); // Mise à jour de l'état avec le paramètre sélectionné console.log("Changement de paramètre dans CheckboxGroup :", param); // Log de changement
setSelectedParam(param); // Met à jour l'état local
onCheckboxChange(param); // Notifie le parent de la nouvelle valeur
}; };
return ( return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}> <div style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
<CheckboxWithLabel <CheckboxWithLabel
label="Tout" label="Tout"
isChecked={selectedParam === "all"} // Vérifie si cette case est sélectionnée isChecked={selectedParam === "indicateurs"} // Vérifie si cette case est sélectionnée
onChange={() => handleCheckboxChange("all")} onChange={() => handleCheckboxChange("indicateurs")}
name="all" name="indicateurs"
/> />
<CheckboxWithLabel <CheckboxWithLabel
label="pH" label="pH"
isChecked={selectedParam === "pH"} isChecked={selectedParam === "indicateursPH"}
onChange={() => handleCheckboxChange("pH")} onChange={() => handleCheckboxChange("indicateursPH")}
name="pH" name="indicateursPH"
/> />
<CheckboxWithLabel <CheckboxWithLabel
label="Chlore" label="Chlore"
isChecked={selectedParam === "chlore"} isChecked={selectedParam === "indicateursChlore"}
onChange={() => handleCheckboxChange("chlore")} onChange={() => handleCheckboxChange("indicateursChlore")}
name="chlore" name="indicateursChlore"
/> />
<CheckboxWithLabel <CheckboxWithLabel
label="Nitrates & Nitrites" label="Nitrates & Nitrites"
isChecked={selectedParam === "nit"} isChecked={selectedParam === "indicateursNitritesNitrates"}
onChange={() => handleCheckboxChange("nit")} onChange={() => handleCheckboxChange("indicateursNitritesNitrates")}
name="nitrates_nitrites" name="indicateursNitritesNitrates"
/> />
<CheckboxWithLabel <CheckboxWithLabel
label="Métaux lourds" label="Métaux lourds"
isChecked={selectedParam === "metaux"} isChecked={selectedParam === "indicateursMetauxLourds"}
onChange={() => handleCheckboxChange("metaux")} onChange={() => handleCheckboxChange("indicateursMetauxLourds")}
name="metaux_lourds" name="indicateursMetauxLourds"
/> />
</div> </div>
); );
}; };
CheckboxGroup.propTypes = {
onCheckboxChange: PropTypes.func.isRequired,
};
export default CheckboxGroup; export default CheckboxGroup;
\ No newline at end of file
import { useEffect, useState, useRef } from "react"; import { useEffect, useState, useRef } from "react";
import PropTypes from "prop-types";
import axios from "axios"; import axios from "axios";
import * as d3 from "d3"; import * as d3 from "d3";
const FranceMap = () => { const FranceMap = ({ typeIndic = "indicateurs" }) => {
if (typeof typeIndic !== "string") {
console.error("typeIndic doit être une chaîne de caractères, reçu :", typeIndic);
typeIndic = "indicateurs"; // Valeur par défaut
}
console.log("typeIndic reçu :", typeIndic);
const [geoData, setGeoData] = useState(null); const [geoData, setGeoData] = useState(null);
const [deptColors, setDeptColors] = useState({}); const [deptColors, setDeptColors] = useState({});
const [deptAverages, setDeptAverages] = useState({});
const [dimensions, setDimensions] = useState({ width: 0, height: 0 }); const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const [selectedDept, setSelectedDept] = useState(null); const [selectedDept, setSelectedDept] = useState(null);
const svgRef = useRef(); const svgRef = useRef();
...@@ -12,29 +22,33 @@ const FranceMap = () => { ...@@ -12,29 +22,33 @@ const FranceMap = () => {
// Charger les données GeoJSON // Charger les données GeoJSON
useEffect(() => { useEffect(() => {
console.log("url : ", "http://localhost:5000/api/" + typeIndic);
axios axios
.get("/France_dep.geojson") // Assure-toi que ton fichier est accessible .get("/France_dep.geojson")
.then((res) => { .then((res) => {
setGeoData(res.data.features); setGeoData(res.data.features);
}) })
.catch((err) => console.error("Erreur lors du chargement du GeoJSON :", err)); .catch((err) => console.error("Erreur lors du chargement du GeoJSON :", err));
// Charger les couleurs depuis l'API backend // Charger les couleurs depuis l'API backend
axios.get("http://localhost:5000/api/indicateurs") axios.get("http://localhost:5000/api/" + typeIndic)
.then((res) => { .then((res) => {
console.log("Couleurs reçues du backend :", res.data); // Debug ici console.log("Couleurs reçues du backend :", res.data.colors);
setDeptColors(res.data); setDeptColors(res.data.colors);
console.log("Moyennes reçues du backend :", res.data.averages);
setDeptAverages(res.data.averages);
}) })
.catch((err) => console.error("Erreur lors du chargement des couleurs :", err)); .catch((err) => console.error("Erreur lors du chargement des couleurs :", err));
}, []); }, [typeIndic]);
// Calculer la taille du conteneur parent et mettre à jour les dimensions // Calculer la taille du conteneur parent et mettre à jour les dimensions
useEffect(() => { useEffect(() => {
const updateDimensions = () => { const updateDimensions = () => {
const { clientWidth, clientHeight } = containerRef.current; const { clientWidth, clientHeight } = containerRef.current;
setDimensions({ setDimensions({
width: clientWidth * 0.8, width: clientWidth * 0.75,
height: clientHeight * 0.8, height: clientHeight * 0.75,
}); });
}; };
...@@ -82,7 +96,7 @@ const FranceMap = () => { ...@@ -82,7 +96,7 @@ const FranceMap = () => {
.attr("opacity", (dept) => dept.properties.code === d.properties.code || dept === selectedDept ? 1 : 0.25); .attr("opacity", (dept) => dept.properties.code === d.properties.code || dept === selectedDept ? 1 : 0.25);
tooltip.style("display", "block") tooltip.style("display", "block")
.html(`<strong>${d.properties.nom}</strong>`) .html(`<strong>${d.properties.nom} (${d.properties.code})</strong><br>Score moyen : ${deptAverages[d.properties.code] || "N/A"}`)
.style("left", event.pageX + 3 + "px") .style("left", event.pageX + 3 + "px")
.style("top", event.pageY + 3 + "px"); .style("top", event.pageY + 3 + "px");
}) })
...@@ -105,7 +119,7 @@ const FranceMap = () => { ...@@ -105,7 +119,7 @@ const FranceMap = () => {
} }
return deptColors[d.properties.code] || "lightgray"; return deptColors[d.properties.code] || "lightgray";
}); });
}, [geoData, deptColors, dimensions, selectedDept]); }, [geoData, deptColors, dimensions, selectedDept, deptAverages]);
return ( return (
<div ref={containerRef} style={{ position: "relative", width: "100%", height: "100%", display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "center" }}> <div ref={containerRef} style={{ position: "relative", width: "100%", height: "100%", display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "center" }}>
...@@ -115,5 +129,8 @@ const FranceMap = () => { ...@@ -115,5 +129,8 @@ const FranceMap = () => {
</div> </div>
); );
}; };
FranceMap.propTypes = {
typeIndic: PropTypes.string,
};
export default FranceMap; export default FranceMap;
...@@ -16,42 +16,6 @@ const TitleWithSearchComponent = () => { ...@@ -16,42 +16,6 @@ const TitleWithSearchComponent = () => {
fontSize: '1.8rem', fontSize: '1.8rem',
fontWeight: 'bold', fontWeight: 'bold',
}}>Dashboard France</h1> }}>Dashboard France</h1>
{/* Barre de recherche */}
<div style={{
display: 'flex',
alignItems: 'center',
backgroundColor: 'white',
borderRadius: '25px',
padding: '5px 15px',
width: '350px',
boxShadow: '0 2px 5px rgba(0, 0, 0, 0.1)'
}}>
<input
type="text"
placeholder="Rechercher..."
style={{
border: 'none',
outline: 'none',
flex: 1,
padding: '8px',
fontSize: '1rem',
borderRadius: '25px',
marginRight: '10px'
}}
/>
<button style={{
backgroundColor: '#1c49ba',
border: 'none',
color: 'white',
padding: '8px 15px',
fontSize: '1rem',
borderRadius: '25px',
cursor: 'pointer'
}}>
Rechercher
</button>
</div>
</div> </div>
); );
}; };
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment