up décompte de suivi des lieux et arbres

This commit is contained in:
Tykayn 2025-06-30 15:51:51 +02:00 committed by tykayn
parent 6c457986ad
commit 949dc71fb8
4 changed files with 65 additions and 23 deletions

View file

@ -86,13 +86,22 @@ class FollowUpController extends AbstractController
$followups = $followups->toArray(); $followups = $followups->toArray();
usort($followups, fn($a, $b) => $a->getDate() <=> $b->getDate()); usort($followups, fn($a, $b) => $a->getDate() <=> $b->getDate());
$series = []; $series = [];
$all_points = [];
foreach ($followups as $fu) { foreach ($followups as $fu) {
$series[$fu->getName()][] = [ $series[$fu->getName()][] = [
'date' => $fu->getDate()->format('c'), 'date' => $fu->getDate()->format('c'),
'value' => $fu->getMeasure(), 'value' => $fu->getMeasure(),
'name' => $fu->getName(), 'name' => $fu->getName(),
]; ];
$all_points[] = [
'date' => $fu->getDate()->format('c'),
'type' => $fu->getName(),
'name' => $fu->getName(),
'value' => $fu->getMeasure(),
];
} }
usort($all_points, fn($a, $b) => strcmp($b['date'], $a['date']));
$all_points = array_slice($all_points, 0, 20);
// Tri par date dans chaque série // Tri par date dans chaque série
foreach ($series as &$points) { foreach ($series as &$points) {
usort($points, function($a, $b) { usort($points, function($a, $b) {
@ -103,6 +112,7 @@ class FollowUpController extends AbstractController
return $this->render('admin/followup_graph.html.twig', [ return $this->render('admin/followup_graph.html.twig', [
'stats' => $stats, 'stats' => $stats,
'series' => $series, 'series' => $series,
'all_points' => $all_points,
'followup_labels' => FollowUpService::getFollowUpThemes(), 'followup_labels' => FollowUpService::getFollowUpThemes(),
'followup_icons' => FollowUpService::getFollowUpIcons(), 'followup_icons' => FollowUpService::getFollowUpIcons(),
'followup_overpass' => FollowUpService::getFollowUpOverpassQueries(), 'followup_overpass' => FollowUpService::getFollowUpOverpassQueries(),

View file

@ -183,20 +183,24 @@ class FollowUpService
}); });
} elseif ($type === 'tree') { } elseif ($type === 'tree') {
$completed = array_filter($data['objects'], function($el) { $completed = array_filter($data['objects'], function($el) {
// Complet si le tag "species" ou "ref" est présent $tags = $el['tags'] ?? [];
return !empty($el['tags']['species'] ?? null) || !empty($el['tags']['ref'] ?? null); $hasTaxonomy = !empty($tags['species'] ?? null)
|| !empty($tags['genus'] ?? null)
|| !empty($tags['taxon'] ?? null)
|| !empty($tags['taxon:binomial'] ?? null);
$hasLeaf = !empty($tags['leaf_type'] ?? null)
|| !empty($tags['leaf_cycle'] ?? null)
|| !empty($tags['leaf_shape'] ?? null)
|| !empty($tags['leaf_color'] ?? null)
|| !empty($tags['leaf_fall'] ?? null);
return $hasTaxonomy && $hasLeaf;
}); });
} }
if ($type === 'lieux') { if ($type === 'places') {
// Si le type est "lieux", on utilise la méthode completionPercent() de $stats $completion = $stats->getCompletionPercent();
if (method_exists($stats, 'getCompletionPercent')) { } else {
$completion = $stats->getCompletionPercent(); $completion = count($data['objects']) > 0 ? round(count($completed) / count($data['objects']) * 100) : 0;
} else {
$completion = 0;
}
} }
$completion = count($data['objects']) > 0 ? round(count($completed) / count($data['objects']) * 100) : 0;
$followupCompletion = new CityFollowUp(); $followupCompletion = new CityFollowUp();
$followupCompletion->setName($type . '_completion') $followupCompletion->setName($type . '_completion')
->setMeasure($completion) ->setMeasure($completion)

View file

@ -21,6 +21,7 @@
{{ parent() }} {{ parent() }}
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0"></script> <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2"></script>
<script> <script>
const series = {{ series|json_encode|raw }}; const series = {{ series|json_encode|raw }};
const theme = {{ theme|json_encode|raw }}; const theme = {{ theme|json_encode|raw }};
@ -40,6 +41,13 @@ if (canvas) {
backgroundColor: 'rgba(0,0,255,0.1)', backgroundColor: 'rgba(0,0,255,0.1)',
fill: false, fill: false,
yAxisID: 'y', yAxisID: 'y',
datalabels: {
align: 'top',
anchor: 'end',
display: true,
formatter: function(value) { return value.y; },
font: { weight: 'bold' }
}
}, },
{ {
label: 'Complétion (%)', label: 'Complétion (%)',
@ -48,6 +56,13 @@ if (canvas) {
backgroundColor: 'rgba(0,255,0,0.1)', backgroundColor: 'rgba(0,255,0,0.1)',
fill: false, fill: false,
yAxisID: 'y1', yAxisID: 'y1',
datalabels: {
align: 'bottom',
anchor: 'end',
display: true,
formatter: function(value) { return value.y + '%'; },
font: { weight: 'bold' }
}
} }
] ]
}, },
@ -58,14 +73,29 @@ if (canvas) {
title: { title: {
display: true, display: true,
text: label text: label
},
datalabels: {
display: true
},
tooltip: {
callbacks: {
title: function(context) {
// Affiche la date au survol
return context[0].parsed.x ? new Date(context[0].parsed.x).toLocaleString() : '';
},
label: function(context) {
return context.dataset.label + ': ' + context.parsed.y;
}
}
} }
}, },
scales: { scales: {
x: { type: 'time', time: { unit: 'day' }, title: { display: true, text: 'Date' } }, x: { type: 'time', time: { unit: 'day' }, title: { display: true, text: 'Date' } },
y: { beginAtZero: true, title: { display: true, text: 'Nombre' } }, y: { beginAtZero: true, title: { display: true, text: 'Nombre' } },
y1: { beginAtZero: true, position: 'right', title: { display: true, text: 'Complétion (%)' }, grid: { drawOnChartArea: false } } y1: { beginAtZero: true, position: 'right', title: { display: true, text: 'Complétion (%)' }, grid: { drawOnChartArea: false }, min: 0, max: 100 }
} }
} },
plugins: [ChartDataLabels]
}); });
} }
// Affichage de la progression sur une semaine // Affichage de la progression sur une semaine

View file

@ -44,15 +44,13 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for type, points in series %} {% for point in all_points %}
{% for point in points %} <tr>
<tr> <td>{{ point.date }}</td>
<td>{{ point.date }}</td> <td>{{ point.type }}</td>
<td>{{ type }}</td> <td>{{ point.name }}</td>
<td>{{ point.name }}</td> <td>{{ point.value }}</td>
<td>{{ point.value }}</td> </tr>
</tr>
{% endfor %}
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
@ -150,7 +148,7 @@
scales: { scales: {
x: { type: 'time', time: { unit: 'day' }, title: { display: true, text: 'Date' } }, x: { type: 'time', time: { unit: 'day' }, title: { display: true, text: 'Date' } },
y: { beginAtZero: true, title: { display: true, text: 'Nombre' } }, y: { beginAtZero: true, title: { display: true, text: 'Nombre' } },
y1: { beginAtZero: true, position: 'right', title: { display: true, text: 'Complétion (%)' }, grid: { drawOnChartArea: false } } y1: { beginAtZero: true, position: 'right', title: { display: true, text: 'Complétion (%)' }, grid: { drawOnChartArea: false }, min: 0, max: 100 }
} }
}, },
plugins: [ChartDataLabels] plugins: [ChartDataLabels]