up details theme graph page

This commit is contained in:
Tykayn 2025-08-12 11:39:05 +02:00 committed by tykayn
parent af1233c246
commit b959c695ae
3 changed files with 90 additions and 181 deletions

View file

@ -81,7 +81,7 @@ THEMES = {
}, },
"advertising_board": { "advertising_board": {
"tag_filter": "advertising=board and message=political", "tag_filter": "advertising=board and message=political",
"important_tags": ["operator", "content"], "important_tags": ["operator", ],
}, },
"building": { "building": {
"tag_filter": "building", "tag_filter": "building",

View file

@ -2,8 +2,8 @@
namespace App\Service; namespace App\Service;
use App\Entity\Stats;
use App\Entity\CityFollowUp; use App\Entity\CityFollowUp;
use App\Entity\Stats;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
class FollowUpService class FollowUpService
@ -38,7 +38,7 @@ class FollowUpService
} elseif ($type === 'police') { } elseif ($type === 'police') {
$objects = array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'police') ?? []; $objects = array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'police') ?? [];
} elseif ($type === 'healthcare') { } elseif ($type === 'healthcare') {
$objects = array_filter($elements, function($el) { $objects = array_filter($elements, function ($el) {
return isset($el['tags']['healthcare']) return isset($el['tags']['healthcare'])
|| ($el['tags']['amenity'] ?? null) === 'doctors' || ($el['tags']['amenity'] ?? null) === 'doctors'
|| ($el['tags']['amenity'] ?? null) === 'pharmacy' || ($el['tags']['amenity'] ?? null) === 'pharmacy'
@ -74,7 +74,7 @@ class FollowUpService
$objects = array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'public_bookcase') ?? []; $objects = array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'public_bookcase') ?? [];
} elseif ($type === 'playground') { } elseif ($type === 'playground') {
$objects = array_filter($elements, fn($el) => ($el['tags']['leisure'] ?? null) === 'playground') ?? []; $objects = array_filter($elements, fn($el) => ($el['tags']['leisure'] ?? null) === 'playground') ?? [];
}elseif ($type === 'restaurant') { } elseif ($type === 'restaurant') {
$objects = array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'restaurant') ?? []; $objects = array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'restaurant') ?? [];
} else { } else {
$objects = []; $objects = [];
@ -133,8 +133,7 @@ class FollowUpService
$completed[] = $el; $completed[] = $el;
} }
} }
} } // ... fallback pour les types sans tags attendus
// ... fallback pour les types sans tags attendus
else { else {
$completed = []; $completed = [];
$partialCompletions = array_fill(0, count($data['objects']), 0); $partialCompletions = array_fill(0, count($data['objects']), 0);
@ -501,7 +500,7 @@ class FollowUpService
'police' => ['phone', 'website'], 'police' => ['phone', 'website'],
'healthcare' => ['name', 'contact:phone', 'phone', 'email', 'contact:email'], 'healthcare' => ['name', 'contact:phone', 'phone', 'email', 'contact:email'],
'bicycle_parking' => ['capacity', 'covered'], 'bicycle_parking' => ['capacity', 'covered'],
'advertising_board' => ['operator', 'contact:phone'], 'advertising_board' => ['operator'],
'building' => ['building'], 'building' => ['building'],
'rnb' => ['building', 'ref:FR:RNB'], 'rnb' => ['building', 'ref:FR:RNB'],
'email' => ['name', 'phone'], 'email' => ['name', 'phone'],

View file

@ -7,6 +7,10 @@
<link href='{{ asset('js/maplibre/maplibre-gl.css') }}' rel='stylesheet'/> <link href='{{ asset('js/maplibre/maplibre-gl.css') }}' rel='stylesheet'/>
<link href='{{ asset('css/city-sidebar.css') }}' rel='stylesheet'/> <link href='{{ asset('css/city-sidebar.css') }}' rel='stylesheet'/>
<style> <style>
#themeMap {
margin-top: 1rem;
}
.chart-container { .chart-container {
width: 100%; width: 100%;
height: 400px; height: 400px;
@ -101,9 +105,6 @@
font-size: 0.95em; font-size: 0.95em;
} }
.btn-josm {
margin-bottom: 1rem;
}
/* Bouton flottant suggestion desktop */ /* Bouton flottant suggestion desktop */
.suggestion-float-btn { .suggestion-float-btn {
@ -230,11 +231,19 @@
<div class="stats-grid"> <div class="stats-grid">
<div class="stat-card"> <div class="stat-card">
<div class="stat-value" id="currentCount">{{ current_count }}</div> <div class="stat-value" id="currentCount">
{{ current_count }}
<i class="bi bi-load bi-spin"></i>
...
</div>
<div class="stat-label">Nombre actuel</div> <div class="stat-label">Nombre actuel</div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-value" id="currentCompletion">{{ current_completion }}</div> <div class="stat-value" id="currentCompletion">
{{ current_completion }}
<i class="bi bi-load bi-spin"></i>
...
</div>
<div class="stat-label">Complétion actuelle</div> <div class="stat-label">Complétion actuelle</div>
</div> </div>
{# <div class="stat-card"> #} {# <div class="stat-card"> #}
@ -251,11 +260,6 @@
<div class="chart-container"> <div class="chart-container">
<canvas id="themeChart"></canvas> <canvas id="themeChart"></canvas>
</div> </div>
{% if completion_tags is defined %}
completion_tags
{{ dump(completion_tags[theme]) }}
{% endif %}
{% if completion_tags is defined and completion_tags[theme] is defined %} {% if completion_tags is defined and completion_tags[theme] is defined %}
<div class="card mt-4"> <div class="card mt-4">
<div class="card-header"> <div class="card-header">
@ -264,7 +268,11 @@
<div class="card-body p-2"> <div class="card-body p-2">
<ul class="mb-0"> <ul class="mb-0">
{% for tag in completion_tags[theme] %} {% for tag in completion_tags[theme] %}
<li><code>{{ tag }}</code></li> <li>
<a href="https://wiki.openstreetmap.org/FR:Key:{{ tag }}">
<code>{{ tag }}</code>
</a>
</li>
{% else %} {% else %}
<li><span class="text-muted">Aucun critère défini</span></li> <li><span class="text-muted">Aucun critère défini</span></li>
{% endfor %} {% endfor %}
@ -273,11 +281,11 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
<div class="card mt-5"> <div class="card mt-5 ">
<div class="card-header"> <div class="card-header">
<i class="bi bi-tags"></i> Statistiques des tags utilisés dans les objets trouvés <i class="bi bi-tags"></i> Statistiques des tags utilisés dans les objets trouvés
</div> </div>
<div class="card-body p-2"> <div class="card-body p-4">
<div id="tags-stats-block"> <div id="tags-stats-block">
<table class="table table-sm table-bordered mb-0" id="tags-stats-table" <table class="table table-sm table-bordered mb-0" id="tags-stats-table"
style="max-width:600px;"> style="max-width:600px;">
@ -298,12 +306,12 @@
{# Bloc navigation autres thématiques #} {# Bloc navigation autres thématiques #}
{% if followup_labels is defined and icons is defined %} {% if followup_labels is defined and icons is defined %}
<hr> <hr>
<div class="mt-4"> <div class="mt-4 p-6 m-4">
<h4>Autres thématiques de suivi :</h4> <h4>Autres thématiques de suivi :</h4>
<ul class="list-inline"> <ul class="list-inline p-6 m-4">
{% for t, label in followup_labels %} {% for t, label in followup_labels %}
{% if t != theme %} {% if t != theme %}
<li class="list-inline-item mb-2"> <li class="list-inline-item mb-2 ml-4">
<a href="{{ path('admin_followup_theme_graph', {'insee_code': stats.zone, 'theme': t}) }}" <a href="{{ path('admin_followup_theme_graph', {'insee_code': stats.zone, 'theme': t}) }}"
class="btn btn-outline-secondary"> class="btn btn-outline-secondary">
<i class="bi {{ icons[t]|default('bi-question-circle') }}"></i> {{ label }} <i class="bi {{ icons[t]|default('bi-question-circle') }}"></i> {{ label }}
@ -543,6 +551,7 @@
const countData = {{ count_data|json_encode|raw }}; const countData = {{ count_data|json_encode|raw }};
console.log('Count data:', countData); console.log('Count data:', countData);
window.countData = countData
const completionData = {{ completion_data|json_encode|raw }}; const completionData = {{ completion_data|json_encode|raw }};
console.log('Completion data:', completionData); console.log('Completion data:', completionData);
@ -622,7 +631,7 @@
} }
}; };
// Graphique fusionné console.log('completionData', completionData)
const ctx = document.getElementById('themeChart').getContext('2d'); const ctx = document.getElementById('themeChart').getContext('2d');
new Chart(ctx, { new Chart(ctx, {
type: 'line', type: 'line',
@ -637,152 +646,53 @@
fill: true, fill: true,
tension: 0.1, tension: 0.1,
yAxisID: 'y1', yAxisID: 'y1',
} },
}
}
}
}
},
{
label: 'Pourcentage de complétion',
data
:
Array.isArray(completionData) ? completionData.map(d => ({x: new Date(d.date), y: d.value})) : [],
borderColor
:
'#198754',
backgroundColor
:
'rgba(25, 135, 84, 0.1)',
borderWidth
:
2,
fill
:
true,
tension
:
0.1,
yAxisID
:
'y2',
}
,
// Add current data point if no historical data exists
...
((!Array.isArray(countData) || countData.length === 0) && typeof currentCount !== 'undefined' ? [{
label: "Nombre actuel",
data: [{x: new Date(), y: currentCount}],
borderColor: '#dc3545',
backgroundColor: 'rgba(220, 53, 69, 0.1)',
borderWidth: 2,
pointRadius: 5,
fill: false,
yAxisID: 'y1',
}] : []),
...
((!Array.isArray(completionData) || completionData.length === 0) && typeof currentCompletion !== 'undefined' ? [{
label: "Complétion actuelle",
data: [{x: new Date(), y: currentCompletion}],
borderColor: '#fd7e14',
backgroundColor: 'rgba(253, 126, 20, 0.1)',
borderWidth: 2,
pointRadius: 5,
fill: false,
yAxisID: 'y2',
}] : [])
]
},
options: {
responsive: true,
maintainAspectRatio
:
false,
plugins
:
{
legend: {
display: true
}
,
tooltip: {
mode: 'index', intersect
:
false
}
}
,
interaction: {
mode: 'nearest', axis
:
'x', intersect
:
false
}
,
scales: {
x: {
type: 'time',
time
:
{ {
unit: 'day', displayFormats label: 'Pourcentage de complétion',
: data: Array.isArray(completionData) ? completionData.map(d => ({
{ x: new Date(d.date),
day: 'dd/MM/yyyy' y: d.value
} })) : [],
borderColor: '#198754',
backgroundColor: 'rgba(25, 135, 84, 0.1)',
borderWidth: 2,
fill: true,
tension: 0.1,
yAxisID: 'y2',
} }
, ]
title: { },
display: true, text options: {
: responsive: true,
'Date' maintainAspectRatio: false,
} plugins: {
} legend: {display: true},
, tooltip: {mode: 'index', intersect: false}
y1: { },
type: 'linear', interaction: {mode: 'nearest', axis: 'x', intersect: false},
position scales: {
: x: {
'left', type: 'time',
title time: {unit: 'day', displayFormats: {day: 'dd/MM/yyyy'}},
: title: {display: true, text: 'Date'}
{ },
display: true, text y1: {
: type: 'linear',
"Nombre d'objets" position: 'left',
} title: {display: true, text: "Nombre d'objets"},
, beginAtZero: true
beginAtZero: true },
} y2: {
, type: 'linear',
y2: { position: 'right',
type: 'linear', title: {display: true, text: 'Complétion (%)'},
position min: 0,
: max: 100,
'right', grid: {drawOnChartArea: false}
title
:
{
display: true, text
:
'Complétion (%)'
}
,
min: 0,
max
:
100,
grid
:
{
drawOnChartArea: false
} }
} }
} }
} });
})
;
// Initialiser les statistiques // Initialiser les statistiques
updateStats(); updateStats();