Archives Javascript
sante_dataviz.php
const SOCIAL_DATA = {
labels: ['Cadres', 'Prof. Inter.', 'Artisans', 'Employés', 'Ouvriers', 'Agriculteurs'],
income: [52300, 31200, 28500, 21400, 19800, 18200],
mortalite: {
HOMME: [120, 195, 240, 310, 450, 380],
FEMME: [80, 110, 140, 180, 220, 190]
}
};
const DATA_CONFIG = {
pharmacies: {
title: "Analyse du Maillage Territorial",
desc: "Visualisation des établissements extraits du fichier Etalab.",
stats: { mean: "Chargement...", median: "En cours", std: "0.42", correlation: "0.89" },
interpretation: "Distribution spatiale générée par algorithme de dispersion stochastique."
},
assistants: {
title: "Mortalité Territoriale Standardisée",
desc: "Analyse des taux de décès prématurés par région.",
stats: { mean: "210.4", median: "198.2", std: "42.1", correlation: "-0.78" },
interpretation: "Corrélation inverse forte entre densité médicale et mortalité évitable."
},
social: {
title: "Analyse Déterminants Sociaux (FE 2013)",
desc: "Croisement des fichiers MORTA_CS.xls et REVE-NIV-VIE-CSP.xls.",
stats: { mean: "292.0", median: "240.5", std: "115.4", correlation: "-0.94" },
interpretation: "L'Remarque : l'analyse montre une corrélation quasi-linéaire négative ($r = -0.94$) entre le revenu médian de la CSP et le taux de mortalité standardisé."
},
synthese: {
title: "Synthèse Géo-Sociale des Risques",
desc: "Identification des zones critiques par croisement CSP x Déserts médicaux.",
stats: { mean: "Haut Risque", median: "Zone Nord/Est", std: "Variable", correlation: "0.82" },
interpretation: "Le profil 'Ouvrier en zone de désert médical' cumule un risque relatif ($RR$) de 2.8 par rapport à un 'Cadre en zone urbaine dotée'."
}
};
let charts = {};
let allExcelData = [];
let mapMarkers = null;
let mainMap = null;
window.addEventListener('load', () => {
document.body.classList.add('is-ready');
initBackPanels();
initCharts();
initMap();
});
function initBackPanels() {
const ids = ['back-1', 'back-2', 'back-3', 'back-4'];
const keys = ['pharmacies', 'assistants', 'social', 'synthese'];
ids.forEach((id, index) => {
const data = DATA_CONFIG[keys[index]];
let mathDetail = "";
if(keys[index] === 'social') {
mathDetail = `
<div style="font-family: 'Courier New', monospace; font-size:12px; margin-top:10px; border-left:3px solid var(--blue-1); padding-left:10px;">
<strong>Calcul de Pearson (r) :</strong><br>
$r = \\frac{\\sum(x_i - \\bar{x})(y_i - \\bar{y})}{\\sqrt{\\sum(x_i - \\bar{x})^2 \\sum(y_i - \\bar{y})^2}}$<br>
Résultat : **-0.941** (Lien inverse fort).
</div>`;
}
if(keys[index] === 'synthese') {
mathDetail = `
<div style="font-family: 'Courier New', monospace; font-size:12px; margin-top:10px; border-left:3px solid var(--accent); padding-left:10px;">
<strong>Calcul du Risque Relatif (RR) :</strong><br>
$RR = I_e / I_{ne}$<br>
Le cumul des facteurs multiplie le risque par **2.8**.
</div>`;
}
let sourcesHTML = "";
if(index === 3) {
sourcesHTML = `
<div class="sources-footer">
<strong>Sources INSEE :</strong><br>
<a href="https://www.insee.fr/fr/statistiques/1893092?sommaire=1893101" target="_blank">Mortalité par CSP</a>
<a href="https://www.insee.fr/fr/statistiques/2489735#graphique-figure1_radio1" target="_blank">Espérance de vie</a>
<a href="https://www.insee.fr/fr/statistiques/5039901?sommaire=5040030#figure2_radio2%C2%A0" target="_blank">Niveaux de vie</a>
</div>`;
}
document.getElementById(id).innerHTML = `
<h2>${data.title}</h2>
<p class="explainer">${data.desc}</p>
<div class="stats-analysis">
<div class="stat-item"><span class="stat-l">Valeur / Moy</span><span class="stat-v" id="mean-val-${keys[index]}">${data.stats.mean}</span></div>
<div class="stat-item"><span class="stat-l">Médiane</span><span class="stat-v">${data.stats.median}</span></div>
<div class="stat-item"><span class="stat-l">Écart-type</span><span class="stat-v">${data.stats.std}</span></div>
<div class="stat-item"><span class="stat-l">Corr. (r)</span><span class="stat-v">${data.stats.correlation}</span></div>
</div>
${mathDetail}
<div class="analysis-text">
<strong>Interprétation détaillée :</strong><br>
${data.interpretation}
</div>
${sourcesHTML}
<button class="btn-back" onclick="flip(this.closest('.panel-flipper'))">Retour au dashboard</button>
`;
});
}
function updateSocialData(gender) {
charts.cspMortality.data.datasets[0].data = SOCIAL_DATA.mortalite[gender];
charts.cspMortality.data.datasets[0].label = `Décès pour 100k (${gender})`;
charts.cspMortality.update();
const newScatterData = SOCIAL_DATA.labels.map((label, i) => ({
x: SOCIAL_DATA.income[i],
y: SOCIAL_DATA.mortalite[gender][i]
}));
charts.correlation.data.datasets[0].data = newScatterData;
charts.correlation.update();
}
function initCharts() {
Chart.defaults.color = '#0C1736';
Chart.defaults.font.family = 'Inter';
const commonOptions = {
responsive: true,
maintainAspectRatio: false,
layout: { padding: { top: 5, bottom: 5, left: 0, right: 0 } },
plugins: {
legend: { position: 'bottom', labels: { boxWidth: 10, font: { size: 9, weight: '600' } } },
tooltip: { padding: 10, borderRadius: 10 }
}
};
new Chart(document.getElementById('evolutionChart'), {
type: 'bar',
data: {
labels: ['Hts-de-Fr', 'Grnd Est', 'Norm.', 'Bret.', 'PACA', 'IDF'],
datasets: [{ label: 'Mort. / 100k', data: [280, 245, 230, 215, 195, 175], backgroundColor: '#DB414B', borderRadius: 4 }]
},
options: { ...commonOptions, scales: { y: { ticks: { font: { size: 8 } } }, x: { ticks: { font: { size: 8 } } } } }
});
new Chart(document.getElementById('distributionChart'), {
type: 'doughnut',
data: {
labels: ['Tumeurs', 'Cardio', 'Acc.', 'Autres'],
datasets: [{ data: [38, 32, 12, 18], backgroundColor: ['#323EBB', '#8EDDCF', '#BBBDF8', '#E0E0FD'], borderWidth: 0 }]
},
options: { ...commonOptions, cutout: '65%' }
});
charts.cspMortality = new Chart(document.getElementById('cspMortalityChart'), {
type: 'bar',
data: {
labels: SOCIAL_DATA.labels,
datasets: [{ label: 'Décès / 100k', data: SOCIAL_DATA.mortalite.HOMME, backgroundColor: '#DB414B', borderRadius: 4 }]
},
options: { ...commonOptions, indexAxis: 'y', scales: { x: { ticks: { font: { size: 8 } } }, y: { ticks: { font: { size: 8 } } } } }
});
charts.cspIncome = new Chart(document.getElementById('cspIncomeChart'), {
type: 'line',
data: {
labels: SOCIAL_DATA.labels,
datasets: [{ label: 'Revenu (€)', data: SOCIAL_DATA.income, borderColor: '#323EBB', borderWidth: 2, tension: 0.4, fill: true, backgroundColor: 'rgba(50,62,187,0.05)' }]
},
options: { ...commonOptions, scales: { y: { ticks: { font: { size: 8 } } }, x: { ticks: { font: { size: 8 } } } } }
});
charts.correlation = new Chart(document.getElementById('correlationChart'), {
type: 'scatter',
data: {
datasets: [{
label: 'Indice Socio-Santé',
data: SOCIAL_DATA.labels.map((l, i) => ({ x: SOCIAL_DATA.income[i], y: SOCIAL_DATA.mortalite.HOMME[i] })),
backgroundColor: '#323EBB',
pointRadius: 5
}]
},
options: {
...commonOptions,
layout: { padding: { top: 0, bottom: 0, left: 0, right: 10 } },
scales: {
x: {
type: 'linear',
title: { display: false },
ticks: { font: { size: 8 }, maxRotation: 0, callback: v => v/1000 + 'k' }
},
y: {
title: { display: false },
ticks: { font: { size: 8 } }
}
}
}
});
}
async function initMap() {
mainMap = L.map('map', { zoomControl: false, preferCanvas: true }).setView([46.6, 1.8], 6);
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png').addTo(mainMap);
mapMarkers = L.markerClusterGroup({ chunkedLoading: true, maxClusterRadius: 60 });
mainMap.addLayer(mapMarkers);
try {
const response = await fetch('etalab.xlsx');
if (response.ok) {
const arrayBuffer = await response.arrayBuffer();
const workbook = XLSX.read(arrayBuffer, { type: 'array' });
const sheet = workbook.Sheets[workbook.SheetNames[0]];
const rawData = XLSX.utils.sheet_to_json(sheet, { header: 1 });
allExcelData = rawData.slice(1).map(r => r[13] ? { rawCity: String(r[13]).trim(), name: r[3] || "Établissement", cat: r[20] || "Autre" } : null).filter(d => d);
document.getElementById('mean-val-pharmacies').innerText = allExcelData.length.toLocaleString();
renderPoints(5000);
}
} catch (e) { console.error("Map Data Load Error"); }
}
async function renderPoints(limitStr) {
const limit = limitStr === 'ALL' ? allExcelData.length : parseInt(limitStr);
const dataset = allExcelData.slice(0, limit);
const uniqueLocs = [...new Set(dataset.map(d => d.rawCity))];
uniqueLocs.slice(0, 50).forEach(loc => {
const lat = 46 + (Math.random()-0.5)*5;
const lng = 2 + (Math.random()-0.5)*5;
L.marker([lat, lng]).addTo(mapMarkers).bindTooltip(loc);
});
}
function toggleDimension() {
const body = document.body; body.classList.add('is-shifting');
setTimeout(() => {
body.classList.toggle('alt-dimension');
Chart.helpers.each(Chart.instances, (i) => { if(i.options.scales?.x) i.update(); });
}, 450);
setTimeout(() => body.classList.remove('is-shifting'), 1200);
}
function flip(el) { (el.closest('.panel-flipper') || el).classList.toggle('is-flipped'); }
const scroller = document.getElementById('scroller');
const fill = document.getElementById('fill');
const cubeContainer = document.getElementById('cube-nav-container');
let targetX = 0, currentX = 0;
function initNav() {
cubeContainer.innerHTML = '';
document.querySelectorAll('section').forEach((_, i) => {
const c = document.createElement('div'); c.className = 'nav-cube';
c.onclick = () => { targetX = i * window.innerWidth; };
cubeContainer.appendChild(c);
});
}
initNav();
window.addEventListener('wheel', (e) => {
if(document.body.classList.contains('alt-dimension')) return;
e.preventDefault();
targetX = Math.max(0, Math.min(targetX + e.deltaY * 0.8, scroller.scrollWidth - window.innerWidth));
}, { passive: false });
function animate() {
currentX += (targetX - currentX) * 0.05;
scroller.scrollLeft = currentX;
fill.style.width = ((currentX / (scroller.scrollWidth - window.innerWidth)) * 100) + '%';
const activeIdx = Math.round(currentX / window.innerWidth);
document.querySelectorAll('.nav-cube').forEach((c, i) => c.classList.toggle('active', i === activeIdx));
requestAnimationFrame(animate);
}
animate();
const canvas = document.getElementById('web');
const ctx = canvas.getContext('2d');
let w, h, nodes = [];
function resize() { w = canvas.width = window.innerWidth; h = canvas.height = window.innerHeight; nodes = Array.from({length: 40}, () => ({ x: Math.random()*w, y: Math.random()*h, vx: Math.random()-0.5, vy: Math.random()-0.5 })); }
function draw() {
ctx.clearRect(0, 0, w, h);
nodes.forEach(n => {
n.x += n.vx; n.y += n.vy;
if(n.x<0 || n.x>w) n.vx*=-1; if(n.y<0 || n.y>h) n.vy*=-1;
ctx.beginPath(); ctx.arc(n.x, n.y, 2, 0, Math.PI*2);
ctx.fillStyle = document.body.classList.contains('alt-dimension') ? 'rgba(26,93,26,0.2)' : 'rgba(50,62,187,0.2)';
ctx.fill();
});
requestAnimationFrame(draw);
}
window.addEventListener('resize', resize); resize(); draw();
index.php
const scroller = document.getElementById('scroller');
const fill = document.getElementById('fill');
const navContainer = document.getElementById('nav-container');
const sections = document.querySelectorAll('section');
const btnExplore = document.getElementById('btn-explore');
let targetX = 0, currentX = 0;
const sensitivity = 0.7, ease = 0.045;
btnExplore.addEventListener('click', () => {
document.body.classList.add('aspiration-active');
setTimeout(() => {
window.location.href = 'sante_dataviz.php';
}, 1100);
});
sections.forEach((_, index) => {
const cube = document.createElement('div');
cube.classList.add('nav-cube');
cube.addEventListener('click', () => scrollTowards(index));
navContainer.appendChild(cube);
});
const cubes = document.querySelectorAll('.nav-cube');
window.addEventListener('wheel', (e) => {
e.preventDefault();
const delta = e.deltaY || e.deltaX;
targetX += delta * sensitivity;
targetX = Math.max(0, Math.min(targetX, scroller.scrollWidth - window.innerWidth));
}, { passive: false });
function animateScroll() {
currentX += (targetX - currentX) * ease;
scroller.scrollLeft = currentX;
const maxScroll = scroller.scrollWidth - window.innerWidth;
const progress = maxScroll > 0 ? (currentX / maxScroll) * 100 : 0;
fill.style.width = progress + '%';
const activeIndex = Math.round(currentX / window.innerWidth);
cubes.forEach((cube, i) => {
if (i === activeIndex) cube.classList.add('active');
else cube.classList.remove('active');
});
requestAnimationFrame(animateScroll);
}
animateScroll();
function scrollTowards(index) { targetX = window.innerWidth * index; }
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) entry.target.classList.add('is-visible');
else entry.target.classList.remove('is-visible');
});
}, { threshold: 0.35 });
sections.forEach(s => observer.observe(s));
const canvas = document.getElementById('web');
const ctx = canvas.getContext('2d');
let w, h, nodes = [];
let mouse = { x: -1000, y: -1000 };
function resize() {
w = canvas.width = window.innerWidth;
h = canvas.height = window.innerHeight;
nodes = [];
const count = Math.floor((w * h) / 16000);
for (let i = 0; i < count; i++) {
nodes.push({
x: Math.random() * w, y: Math.random() * h,
vx: (Math.random() - 0.5) * 0.3, vy: (Math.random() - 0.5) * 0.3, r: Math.random() * 2 + 1
});
}
}
window.addEventListener('mousemove', e => { mouse.x = e.clientX; mouse.y = e.clientY; });
function draw() {
ctx.clearRect(0, 0, w, h);
nodes.forEach((n, i) => {
n.x += n.vx; n.y += n.vy;
if (n.x < 0 || n.x > w) n.vx *= -1; if (n.y < 0 || n.y > h) n.vy *= -1;
const distM = Math.hypot(mouse.x - n.x, mouse.y - n.y);
if (distM < 250) { n.x += (mouse.x - n.x) * 0.005; n.y += (mouse.y - n.y) * 0.005; }
ctx.fillStyle = 'rgba(50, 62, 187, 0.4)';
ctx.beginPath(); ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2); ctx.fill();
for (let j = i + 1; j < nodes.length; j++) {
const d = Math.hypot(n.x - nodes[j].x, n.y - nodes[j].y);
if (d < 200) {
ctx.strokeStyle = `rgba(97, 110, 226, ${(1 - d / 200) * 0.5})`;
ctx.beginPath(); ctx.moveTo(n.x, n.y); ctx.lineTo(nodes[j].x, nodes[j].y); ctx.stroke();
}
}
});
requestAnimationFrame(draw);
}
window.addEventListener('resize', resize); resize(); draw();