mirror of
https://forge.chapril.org/tykayn/osm-commerces
synced 2025-10-04 17:04:53 +02:00
181 lines
No EOL
7.9 KiB
JavaScript
181 lines
No EOL
7.9 KiB
JavaScript
// Bubble chart du dashboard avec option de taille de bulle proportionnelle ou égale
|
||
|
||
let bubbleChart = null; // Déclaré en dehors pour garder la référence
|
||
function getBubbleData(proportional) {
|
||
// Générer les données puis trier par rayon décroissant
|
||
const data = window.statsDataForBubble.map(stat => {
|
||
const population = parseInt(stat.population, 10);
|
||
const placesCount = parseInt(stat.placesCount, 10);
|
||
// const ratio = population > 0 ? (placesCount / population) * 1000 : 0;
|
||
const completion = parseInt(stat.completionPercent, 10);
|
||
return {
|
||
x: population,
|
||
y: completion,
|
||
r: proportional ? Math.sqrt(placesCount) * 2 : 12,
|
||
label: stat.name,
|
||
completion: stat.completionPercent || 0,
|
||
zone: stat.zone
|
||
};
|
||
});
|
||
// Trier du plus gros au plus petit rayon
|
||
data.sort((a, b) => b.r - a.r);
|
||
return data;
|
||
}
|
||
|
||
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
function waitForChartAndDrawBubble() {
|
||
if (!window.Chart || !window.ChartDataLabels) {
|
||
setTimeout(waitForChartAndDrawBubble, 50);
|
||
return;
|
||
}
|
||
const chartCanvas = document.getElementById('bubbleChart');
|
||
const toggle = document.getElementById('toggleBubbleSize');
|
||
|
||
|
||
|
||
|
||
function drawBubbleChart(proportional) {
|
||
// Détruire toute instance Chart.js existante sur ce canvas (Chart.js v3+)
|
||
const existing = window.Chart.getChart(chartCanvas);
|
||
if (existing) {
|
||
try { existing.destroy(); } catch (e) { console.warn('Erreur destroy Chart:', e); }
|
||
}
|
||
if (bubbleChart) {
|
||
try { bubbleChart.destroy(); } catch (e) { console.warn('Erreur destroy Chart:', e); }
|
||
bubbleChart = null;
|
||
}
|
||
|
||
// Forcer le canvas à occuper toute la largeur/hauteur du conteneur en pixels
|
||
if (chartCanvas && chartCanvas.parentElement) {
|
||
const parentRect = chartCanvas.parentElement.getBoundingClientRect();
|
||
console.log('parentRect', parentRect)
|
||
|
||
chartCanvas.width = (parentRect.width);
|
||
chartCanvas.height = (parentRect.height);
|
||
chartCanvas.style.width = parentRect.width + 'px';
|
||
chartCanvas.style.height = parentRect.height + 'px';
|
||
}
|
||
|
||
|
||
const bubbleChartData = getBubbleData(proportional);
|
||
// Calcul de la régression linéaire (moindres carrés)
|
||
const validPoints = bubbleChartData.filter(d => d.x > 0 && d.y > 0);
|
||
const n = validPoints.length;
|
||
let regressionLine = null, slope = 0, intercept = 0;
|
||
if (n >= 2) {
|
||
let sumX = 0, sumY = 0, sumXY = 0, sumXX = 0;
|
||
validPoints.forEach(d => {
|
||
sumX += Math.log10(d.x);
|
||
sumY += d.y;
|
||
sumXY += Math.log10(d.x) * d.y;
|
||
sumXX += Math.log10(d.x) * Math.log10(d.x);
|
||
});
|
||
const meanX = sumX / n;
|
||
const meanY = sumY / n;
|
||
slope = (sumXY - n * meanX * meanY) / (sumXX - n * meanX * meanX);
|
||
intercept = meanY - slope * meanX;
|
||
const xMin = Math.min(...validPoints.map(d => d.x));
|
||
const xMax = Math.max(...validPoints.map(d => d.x));
|
||
regressionLine = [
|
||
{ x: xMin, y: slope * Math.log10(xMin) + intercept },
|
||
{ x: xMax, y: slope * Math.log10(xMax) + intercept }
|
||
];
|
||
}
|
||
window.Chart.register(window.ChartDataLabels);
|
||
bubbleChart = new window.Chart(chartCanvas.getContext('2d'), {
|
||
type: 'bubble',
|
||
data: {
|
||
datasets: [
|
||
{
|
||
label: 'Villes',
|
||
data: bubbleChartData,
|
||
backgroundColor: bubbleChartData.map(d => `rgba(94, 255, 121, ${d.completion / 100})`),
|
||
borderColor: 'rgb(94, 255, 121)',
|
||
datalabels: {
|
||
anchor: 'center',
|
||
align: 'center',
|
||
color: '#000',
|
||
display: true,
|
||
font: { weight: 'bold' },
|
||
formatter: (value, context) => {
|
||
return context.dataset.data[context.dataIndex].label;
|
||
}
|
||
}
|
||
},
|
||
regressionLine ? {
|
||
label: 'Régression linéaire',
|
||
type: 'line',
|
||
data: regressionLine,
|
||
borderColor: 'rgba(0, 0, 0, 0.7)',
|
||
borderWidth: 2,
|
||
pointRadius: 0,
|
||
fill: false,
|
||
order: 0,
|
||
tension: 0,
|
||
datalabels: { display: false }
|
||
} : null
|
||
].filter(Boolean)
|
||
},
|
||
options: {
|
||
// responsive: true,
|
||
plugins: {
|
||
datalabels: {
|
||
// Désactivé au niveau global, activé par dataset
|
||
display: false
|
||
},
|
||
legend: { display: true },
|
||
tooltip: {
|
||
callbacks: {
|
||
label: (context) => {
|
||
const d = context.raw;
|
||
if (context.dataset.type === 'line') {
|
||
return `Régression: y = ${slope.toFixed(2)} × log10(x) + ${intercept.toFixed(2)}`;
|
||
}
|
||
return [
|
||
`${d.label}`,
|
||
`Population: ${d.x.toLocaleString()}`,
|
||
`Lieux / 1000 hab: ${d.y.toFixed(2)}`,
|
||
`Total lieux: ${Math.round(Math.pow(d.r / 2, 2))}`,
|
||
`Complétion: ${d.completion}%`
|
||
];
|
||
}
|
||
}
|
||
}
|
||
},
|
||
scales: {
|
||
x: {
|
||
type: 'logarithmic',
|
||
title: { display: true, text: 'Population (échelle log)' }
|
||
},
|
||
y: {
|
||
title: { display: true, text: 'Completion' }
|
||
}
|
||
}
|
||
}
|
||
});
|
||
// Ajout du clic sur une bulle
|
||
chartCanvas.onclick = function(evt) {
|
||
const points = bubbleChart.getElementsAtEventForMode(evt, 'nearest', { intersect: true }, true);
|
||
if (points.length > 0) {
|
||
const firstPoint = points[0];
|
||
const dataIndex = firstPoint.index;
|
||
const stat = window.statsDataForBubble[dataIndex];
|
||
if (stat && stat.zone) {
|
||
window.location.href = '/admin/stats/' + stat.zone;
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
// Initial draw
|
||
console.log('[bubble chart] Initialisation avec taille proportionnelle ?', toggle.checked);
|
||
drawBubbleChart(toggle && toggle.checked);
|
||
// Listener
|
||
toggle.addEventListener('change', function() {
|
||
console.log('[bubble chart] Toggle changé, taille proportionnelle ?', toggle.checked);
|
||
drawBubbleChart(toggle.checked);
|
||
});
|
||
}
|
||
waitForChartAndDrawBubble();
|
||
});
|