refacto js, up graph

This commit is contained in:
Tykayn 2025-06-21 18:37:31 +02:00 committed by tykayn
parent d66bc5e40c
commit 68680e0569
11 changed files with 840 additions and 48 deletions

View file

@ -15,10 +15,14 @@ import './utils.js';
import './opening_hours.js';
import './josm.js';
import './edit.js';
import './table-map-toggles.js';
import './stats-charts.js';
import './dashboard-charts.js';
import Chart from 'chart.js/auto';
import ChartDataLabels from 'chartjs-plugin-datalabels';
import { genererCouleurPastel, setupCitySearch, handleAddCityFormSubmit, enableLabourageForm, getLabourerUrl, adjustListGroupFontSize } from './utils.js';
import maplibregl from 'maplibre-gl';
import { genererCouleurPastel, setupCitySearch, handleAddCityFormSubmit, enableLabourageForm, getLabourerUrl, adjustListGroupFontSize, toggleCompletionInfo } from './utils.js';
import Tablesort from 'tablesort';
window.Chart = Chart;
@ -26,6 +30,9 @@ window.genererCouleurPastel = genererCouleurPastel;
window.setupCitySearch = setupCitySearch;
window.handleAddCityFormSubmit = handleAddCityFormSubmit;
window.getLabourerUrl = getLabourerUrl;
window.ChartDataLabels = ChartDataLabels;
window.maplibregl = maplibregl;
window.toggleCompletionInfo = toggleCompletionInfo;
Chart.register(ChartDataLabels);
@ -95,13 +102,13 @@ document.addEventListener('DOMContentLoaded', () => {
});
// Focus sur le premier champ texte au chargement
const firstTextInput = document.querySelector('input.form-control');
if (firstTextInput) {
firstTextInput.focus();
console.log('focus sur le premier champ texte', firstTextInput);
} else {
console.log('pas de champ texte trouvé');
}
// const firstTextInput = document.querySelector('input.form-control');
// if (firstTextInput) {
// firstTextInput.focus();
// console.log('focus sur le premier champ texte', firstTextInput);
// } else {
// console.log('pas de champ texte trouvé');
// }
@ -179,4 +186,4 @@ document.addEventListener('DOMContentLoaded', () => {
enableLabourageForm();
adjustListGroupFontSize('.list-group-item');
});

180
assets/dashboard-charts.js Normal file
View file

@ -0,0 +1,180 @@
// 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;
return {
x: population,
y: ratio,
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: 'Commerces par habitant' }
}
}
}
});
// 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();
});

44
assets/stats-charts.js Normal file
View file

@ -0,0 +1,44 @@
// Affichage du graphique de fréquence des mises à jour par trimestre sur la page stats d'une ville
document.addEventListener('DOMContentLoaded', function() {
function drawModificationsByQuarterChart() {
if (!window.Chart) {
setTimeout(drawModificationsByQuarterChart, 50);
return;
}
if (typeof window.modificationsByQuarter === 'undefined') return;
const modifData = window.modificationsByQuarter;
const modifLabels = Object.keys(modifData);
const modifCounts = Object.values(modifData);
const modifCanvas = document.getElementById('modificationsByQuarterChart');
if (modifCanvas && modifLabels.length > 0) {
new window.Chart(modifCanvas.getContext('2d'), {
type: 'bar',
data: {
labels: modifLabels,
datasets: [{
label: 'Nombre de lieux modifiés',
data: modifCounts,
backgroundColor: 'rgba(54, 162, 235, 0.7)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
plugins: {
legend: { display: false },
title: { display: true, text: 'Fréquence des mises à jour par trimestre' }
},
scales: {
y: { beginAtZero: true, title: { display: true, text: 'Nombre de lieux' } },
x: { title: { display: true, text: 'Trimestre' } }
}
}
});
} else if (modifCanvas) {
modifCanvas.parentNode.innerHTML = '<div class="alert alert-info">Aucune donnée de modification disponible pour cette ville.</div>';
}
}
drawModificationsByQuarterChart();
});

View file

@ -0,0 +1,23 @@
// Gestion du tri des tableaux
import Tablesort from 'tablesort';
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.js-sort-table').forEach(table => {
new Tablesort(table);
});
// Gestion du toggle gouttes/ronds sur la carte
const toggle = document.getElementById('toggleMarkers');
if (toggle && window.updateMarkers) {
toggle.addEventListener('change', (e) => {
window.updateMarkers(e.target.value);
});
}
});
// Exposer une fonction pour (ré)appliquer le tri si besoin
export function applyTableSort() {
document.querySelectorAll('.js-sort-table').forEach(table => {
new Tablesort(table);
});
}

View file

@ -338,9 +338,23 @@ export function calculateCompletion(properties) {
};
}
export function toggleCompletionInfo() {
const content = document.getElementById('completionInfoContent');
const icon = document.getElementById('completionInfoIcon');
if (content) {
const isVisible = content.style.display === 'block';
content.style.display = isVisible ? 'none' : 'block';
if (icon) {
icon.classList.toggle('bi-chevron-down', isVisible);
icon.classList.toggle('bi-chevron-up', !isVisible);
}
}
}
window.check_validity = check_validity;
window.colorHeadingTable = colorHeadingTable;
window.openInPanoramax = openInPanoramax;
window.listChangesets = listChangesets;
window.adjustListGroupFontSize = adjustListGroupFontSize;
window.calculateCompletion = calculateCompletion;
window.calculateCompletion = calculateCompletion;
window.toggleCompletionInfo = toggleCompletionInfo;

399
package-lock.json generated
View file

@ -12,6 +12,7 @@
"chart.js": "^4.5.0",
"chartjs-plugin-datalabels": "^2.2.0",
"core-js": "^3.38.0",
"maplibre-gl": "^5.6.0",
"regenerator-runtime": "^0.13.9",
"tablesort": "^5.6.0",
"webpack": "^5.74.0",
@ -1704,6 +1705,91 @@
"dev": true,
"license": "MIT"
},
"node_modules/@mapbox/geojson-rewind": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz",
"integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==",
"dev": true,
"license": "ISC",
"dependencies": {
"get-stream": "^6.0.1",
"minimist": "^1.2.6"
},
"bin": {
"geojson-rewind": "geojson-rewind"
}
},
"node_modules/@mapbox/jsonlint-lines-primitives": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz",
"integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==",
"dev": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/@mapbox/point-geometry": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz",
"integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==",
"dev": true,
"license": "ISC"
},
"node_modules/@mapbox/tiny-sdf": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz",
"integrity": "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA==",
"dev": true,
"license": "BSD-2-Clause"
},
"node_modules/@mapbox/unitbezier": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz",
"integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==",
"dev": true,
"license": "BSD-2-Clause"
},
"node_modules/@mapbox/vector-tile": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz",
"integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@mapbox/point-geometry": "~0.1.0"
}
},
"node_modules/@mapbox/whoots-js": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz",
"integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@maplibre/maplibre-gl-style-spec": {
"version": "23.3.0",
"resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-23.3.0.tgz",
"integrity": "sha512-IGJtuBbaGzOUgODdBRg66p8stnwj9iDXkgbYKoYcNiiQmaez5WVRfXm4b03MCDwmZyX93csbfHFWEJJYHnn5oA==",
"dev": true,
"license": "ISC",
"dependencies": {
"@mapbox/jsonlint-lines-primitives": "~2.0.2",
"@mapbox/unitbezier": "^0.0.1",
"json-stringify-pretty-compact": "^4.0.0",
"minimist": "^1.2.8",
"quickselect": "^3.0.0",
"rw": "^1.3.3",
"tinyqueue": "^3.0.0"
},
"bin": {
"gl-style-format": "dist/gl-style-format.mjs",
"gl-style-migrate": "dist/gl-style-migrate.mjs",
"gl-style-validate": "dist/gl-style-validate.mjs"
}
},
"node_modules/@nuxt/friendly-errors-webpack-plugin": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@nuxt/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-2.6.0.tgz",
@ -1932,6 +2018,23 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/geojson": {
"version": "7946.0.16",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/geojson-vt": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/@types/geojson-vt/-/geojson-vt-3.2.5.tgz",
"integrity": "sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/geojson": "*"
}
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
@ -1966,6 +2069,25 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/mapbox__point-geometry": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz",
"integrity": "sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/mapbox__vector-tile": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.4.tgz",
"integrity": "sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/geojson": "*",
"@types/mapbox__point-geometry": "*",
"@types/pbf": "*"
}
},
"node_modules/@types/node": {
"version": "22.15.21",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz",
@ -1976,6 +2098,23 @@
"undici-types": "~6.21.0"
}
},
"node_modules/@types/pbf": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.5.tgz",
"integrity": "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/supercluster": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz",
"integrity": "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/geojson": "*"
}
},
"node_modules/@types/yargs": {
"version": "17.0.33",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
@ -3015,6 +3154,13 @@
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/earcut": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.1.tgz",
"integrity": "sha512-0l1/0gOjESMeQyYaK5IDiPNvFeu93Z/cO0TjZh9eZ1vyCtZnA7KMZ8rQggpsJHIbGSdrqYq9OhuveadOVHCshw==",
"dev": true,
"license": "ISC"
},
"node_modules/electron-to-chromium": {
"version": "1.5.158",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.158.tgz",
@ -3278,6 +3424,33 @@
"node": ">=6.9.0"
}
},
"node_modules/geojson-vt": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-4.0.2.tgz",
"integrity": "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==",
"dev": true,
"license": "ISC"
},
"node_modules/get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/gl-matrix": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
"integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==",
"dev": true,
"license": "MIT"
},
"node_modules/glob-to-regexp": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
@ -3285,6 +3458,47 @@
"dev": true,
"license": "BSD-2-Clause"
},
"node_modules/global-prefix": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-4.0.0.tgz",
"integrity": "sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ini": "^4.1.3",
"kind-of": "^6.0.3",
"which": "^4.0.0"
},
"engines": {
"node": ">=16"
}
},
"node_modules/global-prefix/node_modules/isexe": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz",
"integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=16"
}
},
"node_modules/global-prefix/node_modules/which": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz",
"integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^3.1.1"
},
"bin": {
"node-which": "bin/which.js"
},
"engines": {
"node": "^16.13.0 || >=18.0.0"
}
},
"node_modules/globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
@ -3358,6 +3572,27 @@
"postcss": "^8.1.0"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/import-local": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
@ -3457,6 +3692,16 @@
"node": ">=8"
}
},
"node_modules/ini": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz",
"integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==",
"dev": true,
"license": "ISC",
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/interpret": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz",
@ -3693,6 +3938,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/json-stringify-pretty-compact": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz",
"integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==",
"dev": true,
"license": "MIT"
},
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@ -3706,6 +3958,13 @@
"node": ">=6"
}
},
"node_modules/kdbush": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz",
"integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==",
"dev": true,
"license": "ISC"
},
"node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@ -3808,6 +4067,48 @@
"yallist": "^3.0.2"
}
},
"node_modules/maplibre-gl": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.6.0.tgz",
"integrity": "sha512-7TuHMozUC4rlIp08bSsxCixFn18P24otrlZU/7UGCO5RufFTJadFzauTrvBHr9FB67MbJ6nvFXEftGd0bUl4Iw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@mapbox/geojson-rewind": "^0.5.2",
"@mapbox/jsonlint-lines-primitives": "^2.0.2",
"@mapbox/point-geometry": "^0.1.0",
"@mapbox/tiny-sdf": "^2.0.6",
"@mapbox/unitbezier": "^0.0.1",
"@mapbox/vector-tile": "^1.3.1",
"@mapbox/whoots-js": "^3.1.0",
"@maplibre/maplibre-gl-style-spec": "^23.3.0",
"@types/geojson": "^7946.0.16",
"@types/geojson-vt": "3.2.5",
"@types/mapbox__point-geometry": "^0.1.4",
"@types/mapbox__vector-tile": "^1.3.4",
"@types/pbf": "^3.0.5",
"@types/supercluster": "^7.1.3",
"earcut": "^3.0.1",
"geojson-vt": "^4.0.2",
"gl-matrix": "^3.4.3",
"global-prefix": "^4.0.0",
"kdbush": "^4.0.2",
"murmurhash-js": "^1.0.0",
"pbf": "^3.3.0",
"potpack": "^2.0.0",
"quickselect": "^3.0.0",
"supercluster": "^8.0.1",
"tinyqueue": "^3.0.0",
"vt-pbf": "^3.1.3"
},
"engines": {
"node": ">=16.14.0",
"npm": ">=8.1.0"
},
"funding": {
"url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1"
}
},
"node_modules/mdn-data": {
"version": "2.0.30",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
@ -3866,6 +4167,16 @@
"webpack": "^5.0.0"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@ -3873,6 +4184,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/murmurhash-js": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz",
"integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==",
"dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@ -3988,6 +4306,20 @@
"dev": true,
"license": "MIT"
},
"node_modules/pbf": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/pbf/-/pbf-3.3.0.tgz",
"integrity": "sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"ieee754": "^1.1.12",
"resolve-protobuf-schema": "^2.1.0"
},
"bin": {
"pbf": "bin/pbf"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@ -4575,6 +4907,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/potpack": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz",
"integrity": "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==",
"dev": true,
"license": "ISC"
},
"node_modules/pretty-error": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz",
@ -4586,6 +4925,20 @@
"renderkid": "^3.0.0"
}
},
"node_modules/protocol-buffers-schema": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz",
"integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==",
"dev": true,
"license": "MIT"
},
"node_modules/quickselect": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz",
"integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==",
"dev": true,
"license": "ISC"
},
"node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@ -4762,6 +5115,16 @@
"node": ">=8"
}
},
"node_modules/resolve-protobuf-schema": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
"integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"protocol-buffers-schema": "^3.3.1"
}
},
"node_modules/resolve-url-loader": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz",
@ -4786,6 +5149,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/rw": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
"integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -4983,6 +5353,16 @@
"postcss": "^8.4.32"
}
},
"node_modules/supercluster": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz",
"integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==",
"dev": true,
"license": "ISC",
"dependencies": {
"kdbush": "^4.0.2"
}
},
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@ -5234,6 +5614,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/tinyqueue": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz",
"integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==",
"dev": true,
"license": "ISC"
},
"node_modules/tmp": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
@ -5340,6 +5727,18 @@
"dev": true,
"license": "MIT"
},
"node_modules/vt-pbf": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz",
"integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@mapbox/point-geometry": "0.1.0",
"@mapbox/vector-tile": "^1.3.1",
"pbf": "^3.2.1"
}
},
"node_modules/watchpack": {
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",

View file

@ -6,6 +6,7 @@
"chart.js": "^4.5.0",
"chartjs-plugin-datalabels": "^2.2.0",
"core-js": "^3.38.0",
"maplibre-gl": "^5.6.0",
"regenerator-runtime": "^0.13.9",
"tablesort": "^5.6.0",
"webpack": "^5.74.0",

View file

@ -308,6 +308,7 @@ final class AdminController extends AbstractController
'maptiler_token' => $_ENV['MAPTILER_TOKEN'],
'statsHistory' => $statsHistory,
'CTC_urls' => $urls,
'overpass' => ''
]);
}

View file

@ -244,14 +244,18 @@
</table>
</div>
<div class="card mt-4">
{# <div class="card mt-4">
<div class="card-header">
<h2>Requête Overpass</h2>
</div>
<div class="card-body">
<canvas id="modificationsByQuarterChart"></canvas>
</div>
</div>
<div id=overPassRequest >
<pre>
{{overpass}}
</pre>
</div>
</div>
</div> #}
<div class="completion-info mt-4">
<div class="alert alert-info">
@ -292,7 +296,7 @@
<div class="accordion mb-3" id="accordionStats">
<div class="accordion-item">
<h2 class="accordion-header" id="headingOne">
</div>
</div>
</div>
<!-- Bouton caché pour JOSM -->
<a id="josmButton" style="display: none;"></a>
@ -301,17 +305,65 @@
{% block javascripts %}
{{ parent() }}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src='{{ asset('js/maplibre/maplibre-gl.js') }}'></script>
<script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const geojsonData = {{ geojson|raw }};
console.log('geojsonData', geojsonData)
const map_token = "{{ maptiler_token }}";
let map;
let map_is_loaded = false;
let dropMarkers = [];
let map_is_loaded = false;
let currentMode = 'drop';
// Fonction pour mettre à jour le style des marqueurs
window.updateMarkers = function(mode) {
currentMode = mode;
// Mettre à jour l'état visuel des boutons
document.getElementById('circleMarkersBtn').classList.toggle('active', mode === 'circle');
document.getElementById('dropMarkersBtn').classList.toggle('active', mode === 'drop');
if (!map) return;
// Supprimer la couche si elle existe déjà
if (map.getLayer('unclustered-point')) {
map.removeLayer('unclustered-point');
}
// Ajouter la couche selon le mode
if (mode === 'circle') {
map.addLayer({
id: 'unclustered-point',
type: 'circle',
source: 'places',
filter: ['!', ['has', 'point_count']],
paint: {
'circle-color': [
'case',
['has', 'completion'],
[
'rgb',
['-', 255, ['*', ['get', 'completion'], 2.55]],
['*', ['get', 'completion'], 2.55],
80
],
'#cccccc'
],
'circle-radius': 10,
'circle-stroke-width': 2,
'circle-stroke-color': '#fff'
}
});
} else {
map.addLayer({
id: 'unclustered-point',
type: 'circle',
source: 'places',
filter: ['!', ['has', 'point_count']],
paint: {
'circle-color': '#11b4da',
'circle-radius': 8,
'circle-stroke-width': 2,
'circle-stroke-color': '#fff'
}
});
}
}
if (map_token && geojsonData && geojsonData.features.length > 0) {
map = new maplibregl.Map({
container: 'map',
@ -322,7 +374,7 @@
map.on('load', function() {
map_is_loaded = true;
document.getElementById('maploader').style.display = 'none';
document.getElementById('maploader').style.display = 'none';
map.addSource('places', {
type: 'geojson',
@ -365,20 +417,18 @@
}
});
map.addLayer({
id: 'unclustered-point',
type: 'circle',
source: 'places',
filter: ['!', ['has', 'point_count']],
paint: {
'circle-color': '#11b4da',
'circle-radius': 8,
'circle-stroke-width': 2,
'circle-stroke-color': '#fff'
}
// Par défaut, gouttes bleues
window.updateMarkers('drop');
// Listeners boutons
document.getElementById('circleMarkersBtn').addEventListener('click', function() {
window.updateMarkers('circle');
});
// inspect a cluster on click
document.getElementById('dropMarkersBtn').addEventListener('click', function() {
window.updateMarkers('drop');
});
// Popups et clusters (inchangé)
map.on('click', 'clusters', function(e) {
const features = map.queryRenderedFeatures(e.point, { layers: ['clusters'] });
const clusterId = features[0].properties.cluster_id;
@ -405,10 +455,10 @@
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}
new maplibregl.Popup()
new maplibregl.Popup()
.setLngLat(coordinates)
.setHTML(popupContent)
.addTo(map);
.setHTML(popupContent)
.addTo(map);
});
map.on('mouseenter', 'clusters', function() {
@ -436,7 +486,45 @@
openInJOSM(map, map_is_loaded, osmElements);
});
}
// === GRAPHIQUE FRÉQUENCE DES MISES À JOUR PAR TRIMESTRE ===
const modifData = {{ modificationsByQuarter|raw }};
const modifLabels = Object.keys(modifData);
const modifCounts = Object.values(modifData);
const modifCanvas = document.getElementById('modificationsByQuarterChart');
if (modifCanvas && modifLabels.length > 0) {
new Chart(modifCanvas.getContext('2d'), {
type: 'bar',
data: {
labels: modifLabels,
datasets: [{
label: 'Nombre de lieux modifiés',
data: modifCounts,
backgroundColor: 'rgba(54, 162, 235, 0.7)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
plugins: {
legend: { display: false },
title: { display: true, text: 'Fréquence des mises à jour par trimestre' }
},
scales: {
y: { beginAtZero: true, title: { display: true, text: 'Nombre de lieux' } },
x: { title: { display: true, text: 'Trimestre' } }
}
}
});
} else if (modifCanvas) {
modifCanvas.parentNode.innerHTML = '<div class="alert alert-info">Aucune donnée de modification disponible pour cette ville.</div>';
}
});
</script>
<script>
window.modificationsByQuarter = {{ modificationsByQuarter|raw }};
</script>
{% endblock %}

View file

@ -47,6 +47,22 @@
position: relative;
margin-bottom: 1rem;
}
@media (max-width: 768px) {
.bubble-chart-container {
min-height: 300px;
height: 60vw;
max-height: 400px;
overflow-x: auto;
}
}
@media (min-width: 769px) {
.bubble-chart-container {
min-height: 400px;
height: 50vw;
max-height: 600px;
overflow-x: auto;
}
}
</style>
{% endblock %}
@ -59,16 +75,26 @@
</div>
</div>
<div class="row mb-2">
<div class="col-12 text-end">
<div class="form-check form-switch d-inline-block">
<input class="form-check-input" type="checkbox" id="toggleBubbleSize" checked>
<label class="form-check-label" for="toggleBubbleSize">
Taille des bulles proportionnelle au nombre de lieux
</label>
</div>
</div>
</div>
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-header">
Statistiques des villes (nombre de commerces)
Statistiques des villes (bubble chart)
</div>
<div class="card-body">
<canvas id="statsBubbleChart" style="min-height: 400px; width: 100%;" data-stats="{{ stats|raw }}"></canvas>
<div class="card-body bubble-chart-container">
<canvas id="bubbleChart" style="width: 100%; height: 100%; min-height: 300px;"></canvas>
</div>
<pre>
</div>
</div>
</div>
@ -177,10 +203,11 @@
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2"></script>
<script>
window.statsDataForBubble = {{ stats|raw }};
document.addEventListener('DOMContentLoaded', function() {
const chartCanvas = document.getElementById('statsBubbleChart');
const chartCanvas = document.getElementById('bubbleChart');
if (!chartCanvas) {
console.warn('Canvas #statsBubbleChart introuvable');
console.warn('Canvas #bubbleChart introuvable');
return;
}
const statsData = {{ stats|raw }};

View file

@ -1 +1,9 @@
{# (medias sociaux TODO) #}
{# (medias sociaux TODO) #}
{% block social_medias %}
<fieldset>
<div id="tags">
</div>
</fieldset>
{% endblock social_medias %}