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
Branches
No related tags found
No related merge requests found
/data/raw/*.txt
/data/raw/*.csv
\ No newline at end of file
/data/processed/*.csv
\ No newline at end of file
......@@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.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 cors = require("cors");
......@@ -6,30 +8,68 @@ app.use(cors());
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"));
const colors = [
"#E69F00", // Jaune doré
"#56B4E9", // Bleu clair
"#009E73", // Vert foncé
"#F0E442", // Jaune vif
"#D55E00", // Orange vif
"#CC79A7" // Rose fuchsia
];
// Ajoute la corse
departments.push("2A", "2B");
return departments.reduce((acc, dept) => {
acc[dept] = colors[Math.floor(Math.random() * colors.length)];
return acc;
}, {});
};
app.get("/api/indicateurs", (req, res) => {
res.json(generateFakeIndicators());
app.get("/api/indicateurs", async (req, res) => {
try {
const { averages, colors } = await generateDepartmentAveragesAndColors("score_global");
res.json({
averages,
colors
});
} catch (error) {
res.status(500).send('Erreur lors du traitement du fichier CSV');
}
});
app.get("/api/indicateursPH", async (req, res) => {
try {
const { averages, colors } = await generateDepartmentAveragesAndColors("score_pH");
res.json({
averages,
colors
});
} catch (error) {
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, () => {
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 MetricsComponent from "./components/MetricsComponent";
import TitleWithSearchComponent from "./components/UpBar";
import CheckboxGroup from "./components/CheckBoxGroup"; // Importer le groupe de checkboxes
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 (
<div>
<TitleWithSearchComponent />
......@@ -11,16 +24,22 @@ const App = () => {
{/* Partie gauche - Paramètres */}
<div style={{ flex: 1, height: '100%', padding: '20px' }}>
<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>
{/* Partie droite - Carte */}
<div style={{ flex: 6, minWidth: '320px', height: '100%' }}>
<FranceMap />
<div style={{ flex: 5, minWidth: '320px', height: '100%' }}>
{/* Passer selectedParam à FranceMap */}
<FranceMap typeIndic={selectedParam} />
</div>
{/* Partie droite - Metrics */}
<div style={{ flex: 3, minWidth: '300px', height: '100%' }}>
<div style={{ flex: 2, minWidth: '300px', height: '100%' }}>
<MetricsComponent />
</div>
</div>
......
// CheckboxGroup.js
import { useState } from "react";
import PropTypes from 'prop-types';
import CheckboxWithLabel from "./checkBox";
const CheckboxGroup = () => {
// On utilise ici une seule valeur pour l'état, car seulement une case peut être sélectionnée
const CheckboxGroup = ({ onCheckboxChange }) => {
// État pour stocker la checkbox cochée
const [selectedParam, setSelectedParam] = useState(null);
// Fonction pour gérer le changement de la case sélectionnée
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 (
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
<CheckboxWithLabel
label="Tout"
isChecked={selectedParam === "all"} // Vérifie si cette case est sélectionnée
onChange={() => handleCheckboxChange("all")}
name="all"
isChecked={selectedParam === "indicateurs"} // Vérifie si cette case est sélectionnée
onChange={() => handleCheckboxChange("indicateurs")}
name="indicateurs"
/>
<CheckboxWithLabel
label="pH"
isChecked={selectedParam === "pH"}
onChange={() => handleCheckboxChange("pH")}
name="pH"
isChecked={selectedParam === "indicateursPH"}
onChange={() => handleCheckboxChange("indicateursPH")}
name="indicateursPH"
/>
<CheckboxWithLabel
label="Chlore"
isChecked={selectedParam === "chlore"}
onChange={() => handleCheckboxChange("chlore")}
name="chlore"
isChecked={selectedParam === "indicateursChlore"}
onChange={() => handleCheckboxChange("indicateursChlore")}
name="indicateursChlore"
/>
<CheckboxWithLabel
label="Nitrates & Nitrites"
isChecked={selectedParam === "nit"}
onChange={() => handleCheckboxChange("nit")}
name="nitrates_nitrites"
isChecked={selectedParam === "indicateursNitritesNitrates"}
onChange={() => handleCheckboxChange("indicateursNitritesNitrates")}
name="indicateursNitritesNitrates"
/>
<CheckboxWithLabel
label="Métaux lourds"
isChecked={selectedParam === "metaux"}
onChange={() => handleCheckboxChange("metaux")}
name="metaux_lourds"
isChecked={selectedParam === "indicateursMetauxLourds"}
onChange={() => handleCheckboxChange("indicateursMetauxLourds")}
name="indicateursMetauxLourds"
/>
</div>
);
};
CheckboxGroup.propTypes = {
onCheckboxChange: PropTypes.func.isRequired,
};
export default CheckboxGroup;
\ No newline at end of file
import { useEffect, useState, useRef } from "react";
import PropTypes from "prop-types";
import axios from "axios";
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 [deptColors, setDeptColors] = useState({});
const [deptAverages, setDeptAverages] = useState({});
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const [selectedDept, setSelectedDept] = useState(null);
const svgRef = useRef();
......@@ -12,29 +22,33 @@ const FranceMap = () => {
// Charger les données GeoJSON
useEffect(() => {
console.log("url : ", "http://localhost:5000/api/" + typeIndic);
axios
.get("/France_dep.geojson") // Assure-toi que ton fichier est accessible
.get("/France_dep.geojson")
.then((res) => {
setGeoData(res.data.features);
})
.catch((err) => console.error("Erreur lors du chargement du GeoJSON :", err));
// Charger les couleurs depuis l'API backend
axios.get("http://localhost:5000/api/indicateurs")
axios.get("http://localhost:5000/api/" + typeIndic)
.then((res) => {
console.log("Couleurs reçues du backend :", res.data); // Debug ici
setDeptColors(res.data);
console.log("Couleurs reçues du backend :", res.data.colors);
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));
}, []);
}, [typeIndic]);
// Calculer la taille du conteneur parent et mettre à jour les dimensions
useEffect(() => {
const updateDimensions = () => {
const { clientWidth, clientHeight } = containerRef.current;
setDimensions({
width: clientWidth * 0.8,
height: clientHeight * 0.8,
width: clientWidth * 0.75,
height: clientHeight * 0.75,
});
};
......@@ -82,7 +96,7 @@ const FranceMap = () => {
.attr("opacity", (dept) => dept.properties.code === d.properties.code || dept === selectedDept ? 1 : 0.25);
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("top", event.pageY + 3 + "px");
})
......@@ -105,7 +119,7 @@ const FranceMap = () => {
}
return deptColors[d.properties.code] || "lightgray";
});
}, [geoData, deptColors, dimensions, selectedDept]);
}, [geoData, deptColors, dimensions, selectedDept, deptAverages]);
return (
<div ref={containerRef} style={{ position: "relative", width: "100%", height: "100%", display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "center" }}>
......@@ -115,5 +129,8 @@ const FranceMap = () => {
</div>
);
};
FranceMap.propTypes = {
typeIndic: PropTypes.string,
};
export default FranceMap;
......@@ -16,42 +16,6 @@ const TitleWithSearchComponent = () => {
fontSize: '1.8rem',
fontWeight: 'bold',
}}>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>
);
};
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment