diff --git a/assets/app.js b/assets/app.js
index 782e2a7..536bb37 100644
--- a/assets/app.js
+++ b/assets/app.js
@@ -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');
});
-
+
\ No newline at end of file
diff --git a/assets/dashboard-charts.js b/assets/dashboard-charts.js
new file mode 100644
index 0000000..be21412
--- /dev/null
+++ b/assets/dashboard-charts.js
@@ -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();
+});
\ No newline at end of file
diff --git a/assets/stats-charts.js b/assets/stats-charts.js
new file mode 100644
index 0000000..6034f23
--- /dev/null
+++ b/assets/stats-charts.js
@@ -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 = '
Aucune donnée de modification disponible pour cette ville.
';
+ }
+ }
+ drawModificationsByQuarterChart();
+});
\ No newline at end of file
diff --git a/assets/table-map-toggles.js b/assets/table-map-toggles.js
new file mode 100644
index 0000000..1f1c6cf
--- /dev/null
+++ b/assets/table-map-toggles.js
@@ -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);
+ });
+}
\ No newline at end of file
diff --git a/assets/utils.js b/assets/utils.js
index 83a2029..a6db706 100644
--- a/assets/utils.js
+++ b/assets/utils.js
@@ -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;
\ No newline at end of file
+window.calculateCompletion = calculateCompletion;
+window.toggleCompletionInfo = toggleCompletionInfo;
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index e2adc6d..39b892c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index e902c31..d3f21c8 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/Controller/AdminController.php b/src/Controller/AdminController.php
index 5aa5f9b..77f98ab 100644
--- a/src/Controller/AdminController.php
+++ b/src/Controller/AdminController.php
@@ -308,6 +308,7 @@ final class AdminController extends AbstractController
'maptiler_token' => $_ENV['MAPTILER_TOKEN'],
'statsHistory' => $statsHistory,
'CTC_urls' => $urls,
+ 'overpass' => ''
]);
}
diff --git a/templates/admin/stats.html.twig b/templates/admin/stats.html.twig
index cafcb64..b72fd66 100644
--- a/templates/admin/stats.html.twig
+++ b/templates/admin/stats.html.twig
@@ -244,14 +244,18 @@
-
+ #}
@@ -301,17 +305,65 @@
{% block javascripts %}
{{ parent() }}
-
-
-
+
+
{% endblock %}
diff --git a/templates/public/dashboard.html.twig b/templates/public/dashboard.html.twig
index dd6eea2..cef177e 100644
--- a/templates/public/dashboard.html.twig
+++ b/templates/public/dashboard.html.twig
@@ -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;
+ }
+ }
{% endblock %}
@@ -59,16 +75,26 @@
+
+
+
+
+
+
+
+
+