679 lines
No EOL
36 KiB
Twig
679 lines
No EOL
36 KiB
Twig
{% extends 'base.html.twig' %}
|
|
|
|
{% block title %}Propositions archivées OSM{% endblock %}
|
|
|
|
{% block stylesheets %}
|
|
{{ parent() }}
|
|
<style>
|
|
.vote-bar {
|
|
height: 24px;
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
display: flex;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.vote-approve {
|
|
background-color: #28a745;
|
|
height: 100%;
|
|
}
|
|
|
|
.vote-abstain {
|
|
background-color: #ffc107;
|
|
height: 100%;
|
|
}
|
|
|
|
.vote-oppose {
|
|
background-color: #dc3545;
|
|
height: 100%;
|
|
}
|
|
|
|
.vote-count {
|
|
font-size: 0.85rem;
|
|
font-weight: bold;
|
|
color: white;
|
|
text-align: center;
|
|
padding: 2px 5px;
|
|
}
|
|
|
|
.proposal-card {
|
|
margin-bottom: 1.5rem;
|
|
transition: transform 0.2s;
|
|
}
|
|
|
|
.proposal-card:hover {
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.proposal-metadata {
|
|
font-size: 0.85rem;
|
|
color: #6c757d;
|
|
}
|
|
|
|
.voter-badge {
|
|
display: inline-block;
|
|
margin-right: 5px;
|
|
margin-bottom: 5px;
|
|
padding: 2px 8px;
|
|
border-radius: 12px;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.voter-approve {
|
|
background-color: rgba(40, 167, 69, 0.2);
|
|
border: 1px solid rgba(40, 167, 69, 0.4);
|
|
}
|
|
|
|
.voter-abstain {
|
|
background-color: rgba(255, 193, 7, 0.2);
|
|
border: 1px solid rgba(255, 193, 7, 0.4);
|
|
}
|
|
|
|
.voter-oppose {
|
|
background-color: rgba(220, 53, 69, 0.2);
|
|
border: 1px solid rgba(220, 53, 69, 0.4);
|
|
}
|
|
|
|
.stats-card {
|
|
transition: transform 0.2s;
|
|
}
|
|
|
|
.stats-card:hover {
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.stats-value {
|
|
font-size: 2rem;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.stats-label {
|
|
font-size: 0.9rem;
|
|
color: #6c757d;
|
|
}
|
|
|
|
.top-voters-table th, .top-voters-table td {
|
|
padding: 0.5rem;
|
|
}
|
|
|
|
.vote-duration {
|
|
display: inline-block;
|
|
padding: 3px 8px;
|
|
border-radius: 4px;
|
|
background-color: #e9ecef;
|
|
margin-top: 5px;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.comment-text {
|
|
font-style: italic;
|
|
color: #495057;
|
|
background-color: #f8f9fa;
|
|
padding: 8px;
|
|
border-radius: 4px;
|
|
margin-top: 5px;
|
|
margin-bottom: 10px;
|
|
border-left: 3px solid #dee2e6;
|
|
}
|
|
|
|
.chart-container {
|
|
position: relative;
|
|
height: 300px;
|
|
margin-bottom: 20px;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block body %}
|
|
<div class="container mt-4">
|
|
{% include 'admin/_wiki_navigation.html.twig' %}
|
|
|
|
<h1>Propositions archivées OpenStreetMap</h1>
|
|
<p class="lead">Analyse des votes sur les propositions archivées du wiki OSM</p>
|
|
|
|
{% if last_updated %}
|
|
<div class="alert alert-info">
|
|
<div class="row align-items-center">
|
|
<div class="col-md-6">
|
|
<i class="bi bi-info-circle"></i> Dernière mise à jour : {{ last_updated|date('d/m/Y H:i') }}
|
|
</div>
|
|
<div class="col-md-6 text-end">
|
|
<form class="d-inline-flex align-items-center" method="get" action="{{ path('app_admin_wiki_archived_proposals') }}">
|
|
<div class="me-2">
|
|
<label for="limit" class="me-2">Limiter à:</label>
|
|
<select name="limit" id="limit" class="form-select form-select-sm d-inline-block" style="width: auto;">
|
|
<option value="">Toutes les propositions</option>
|
|
<option value="10" {% if limit == 10 %}selected{% endif %}>10 propositions</option>
|
|
<option value="20" {% if limit == 20 %}selected{% endif %}>20 propositions</option>
|
|
<option value="50" {% if limit == 50 %}selected{% endif %}>50 propositions</option>
|
|
<option value="100" {% if limit == 100 %}selected{% endif %}>100 propositions</option>
|
|
{% if limit and limit not in [10, 20, 50, 100] %}
|
|
<option value="{{ limit }}" selected>{{ limit }} propositions</option>
|
|
{% endif %}
|
|
</select>
|
|
</div>
|
|
<input type="hidden" name="refresh" value="1">
|
|
<button type="submit" class="btn btn-sm btn-outline-primary">
|
|
<i class="bi bi-arrow-clockwise"></i> Rafraîchir les données
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% if limit %}
|
|
<div class="mt-2">
|
|
<small class="text-muted">
|
|
<i class="bi bi-info-circle"></i> Les données sont limitées à {{ limit }} propositions.
|
|
<a href="{{ path('app_admin_wiki_archived_proposals') }}">Voir toutes les propositions</a>
|
|
</small>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if statistics %}
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h2>Statistiques globales</h2>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-3 col-sm-6 mb-3">
|
|
<div class="card stats-card h-100">
|
|
<div class="card-body text-center">
|
|
<div class="stats-value">{{ statistics.total_proposals }}</div>
|
|
<div class="stats-label">Propositions analysées</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3 col-sm-6 mb-3">
|
|
<div class="card stats-card h-100">
|
|
<div class="card-body text-center">
|
|
<div class="stats-value">{{ statistics.total_votes }}</div>
|
|
<div class="stats-label">Votes au total</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3 col-sm-6 mb-3">
|
|
<div class="card stats-card h-100">
|
|
<div class="card-body text-center">
|
|
<div class="stats-value">{{ statistics.avg_votes_per_proposal }}</div>
|
|
<div class="stats-label">Votes par proposition (moyenne)</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3 col-sm-6 mb-3">
|
|
<div class="card stats-card h-100">
|
|
<div class="card-body text-center">
|
|
<div class="stats-value">{{ statistics.unique_voters }}</div>
|
|
<div class="stats-label">Votants uniques</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if statistics.avg_vote_duration_days is defined %}
|
|
<div class="row mt-3">
|
|
<div class="col-md-3 col-sm-6 mb-3">
|
|
<div class="card stats-card h-100">
|
|
<div class="card-body text-center">
|
|
<div class="stats-value">{{ statistics.avg_vote_duration_days }}</div>
|
|
<div class="stats-label">Durée moyenne des votes (jours)</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if statistics.status_distribution is defined and statistics.status_distribution|length > 0 %}
|
|
<div class="row mt-4">
|
|
<div class="col-md-6">
|
|
<h4>Répartition par statut</h4>
|
|
<div class="chart-container">
|
|
<canvas id="statusChart"></canvas>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h4>Détail des statuts</h4>
|
|
<table class="table table-sm table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Statut</th>
|
|
<th>Nombre</th>
|
|
<th>Pourcentage</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for status, count in statistics.status_distribution %}
|
|
<tr>
|
|
<td>{{ status }}</td>
|
|
<td>{{ count }}</td>
|
|
<td>{{ (count / statistics.total_proposals * 100)|round(1) }}%</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="row mt-4">
|
|
<div class="col-12">
|
|
<h4>Répartition des années des propositions</h4>
|
|
<div class="chart-container">
|
|
<canvas id="yearDistributionChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h2>Contributeurs les plus actifs</h2>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover top-voters-table">
|
|
<thead>
|
|
<tr>
|
|
<th>#</th>
|
|
<th>Utilisateur</th>
|
|
<th>Total votes</th>
|
|
<th>Approbations</th>
|
|
<th>Abstentions</th>
|
|
<th>Oppositions</th>
|
|
<th>Répartition</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for voter in statistics.top_voters %}
|
|
<tr>
|
|
<td>{{ loop.index }}</td>
|
|
<td>
|
|
<a href="https://wiki.openstreetmap.org/wiki/User:{{ voter.username }}" target="_blank">
|
|
{{ voter.username }}
|
|
</a>
|
|
</td>
|
|
<td>{{ voter.total }}</td>
|
|
<td>{{ voter.approve }}</td>
|
|
<td>{{ voter.abstain }}</td>
|
|
<td>{{ voter.oppose }}</td>
|
|
<td>
|
|
<div class="vote-bar">
|
|
{% if voter.approve > 0 %}
|
|
<div class="vote-approve" style="width: {{ (voter.approve / voter.total * 100)|round }}%">
|
|
<span class="vote-count">{{ (voter.approve / voter.total * 100)|round }}%</span>
|
|
</div>
|
|
{% endif %}
|
|
{% if voter.abstain > 0 %}
|
|
<div class="vote-abstain" style="width: {{ (voter.abstain / voter.total * 100)|round }}%">
|
|
<span class="vote-count">{{ (voter.abstain / voter.total * 100)|round }}%</span>
|
|
</div>
|
|
{% endif %}
|
|
{% if voter.oppose > 0 %}
|
|
<div class="vote-oppose" style="width: {{ (voter.oppose / voter.total * 100)|round }}%">
|
|
<span class="vote-count">{{ (voter.oppose / voter.total * 100)|round }}%</span>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="card mb-4">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h2 class="mb-0">Liste des propositions archivées</h2>
|
|
<div>
|
|
<div class="btn-group" role="group">
|
|
<button type="button" class="btn btn-outline-secondary btn-sm filter-btn active" data-filter="all">Toutes</button>
|
|
<button type="button" class="btn btn-outline-success btn-sm filter-btn" data-filter="approved">Approuvées</button>
|
|
<button type="button" class="btn btn-outline-danger btn-sm filter-btn" data-filter="rejected">Rejetées</button>
|
|
<button type="button" class="btn btn-outline-warning btn-sm filter-btn" data-filter="neutral">Neutres</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row" id="proposals-container">
|
|
{% for proposal in proposals %}
|
|
{% set total_votes = proposal.votes.approve.count + proposal.votes.oppose.count + proposal.votes.abstain.count %}
|
|
{% set is_approved = proposal.votes.approve.count > proposal.votes.oppose.count %}
|
|
{% set is_rejected = proposal.votes.approve.count < proposal.votes.oppose.count %}
|
|
{% set is_neutral = proposal.votes.approve.count == proposal.votes.oppose.count %}
|
|
|
|
<div class="col-md-6 mb-4 proposal-item {% if is_approved %}approved{% elseif is_rejected %}rejected{% else %}neutral{% endif %}">
|
|
<div class="card proposal-card h-100 {% if is_approved %}border-success{% elseif is_rejected %}border-danger{% else %}border-warning{% endif %}">
|
|
<div class="card-header {% if is_approved %}bg-success text-white{% elseif is_rejected %}bg-danger text-white{% else %}bg-warning{% endif %}">
|
|
<h5 class="card-title mb-0">
|
|
<a href="{{ proposal.url }}" target="_blank" class="text-white">
|
|
{{ proposal.title }}
|
|
</a>
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="proposal-metadata mb-3">
|
|
{% if proposal.proposer %}
|
|
<div><strong>Proposé par :</strong> {{ proposal.proposer }}</div>
|
|
{% endif %}
|
|
{% if proposal.last_modified %}
|
|
<div><strong>Dernière modification :</strong> {{ proposal.last_modified }}</div>
|
|
{% endif %}
|
|
<div><strong>Sections :</strong> {{ proposal.section_count }}</div>
|
|
<div><strong>Liens :</strong> {{ proposal.link_count }}</div>
|
|
<div><strong>Mots :</strong> {{ proposal.word_count }}</div>
|
|
{% if proposal.votes.duration_days is defined %}
|
|
<div class="mt-2">
|
|
<span class="vote-duration">
|
|
<i class="bi bi-calendar-range"></i>
|
|
<strong>Durée du vote :</strong> {{ proposal.votes.duration_days }} jours
|
|
({{ proposal.votes.first_vote }} → {{ proposal.votes.last_vote }})
|
|
</span>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if total_votes > 0 %}
|
|
<h6>Résultats des votes ({{ total_votes }} votes)</h6>
|
|
<div class="vote-bar">
|
|
{% if proposal.votes.approve.count > 0 %}
|
|
<div class="vote-approve" style="width: {{ proposal.approve_percentage }}%">
|
|
<span class="vote-count">{{ proposal.votes.approve.count }} ({{ proposal.approve_percentage }}%)</span>
|
|
</div>
|
|
{% endif %}
|
|
{% if proposal.votes.abstain.count > 0 %}
|
|
<div class="vote-abstain" style="width: {{ proposal.abstain_percentage }}%">
|
|
<span class="vote-count">{{ proposal.votes.abstain.count }} ({{ proposal.abstain_percentage }}%)</span>
|
|
</div>
|
|
{% endif %}
|
|
{% if proposal.votes.oppose.count > 0 %}
|
|
<div class="vote-oppose" style="width: {{ proposal.oppose_percentage }}%">
|
|
<span class="vote-count">{{ proposal.votes.oppose.count }} ({{ proposal.oppose_percentage }}%)</span>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="mt-3">
|
|
<div class="accordion" id="votersAccordion{{ loop.index }}">
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header" id="headingVoters{{ loop.index }}">
|
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
|
data-bs-target="#collapseVoters{{ loop.index }}" aria-expanded="false"
|
|
aria-controls="collapseVoters{{ loop.index }}">
|
|
Voir les votants
|
|
</button>
|
|
</h2>
|
|
<div id="collapseVoters{{ loop.index }}" class="accordion-collapse collapse"
|
|
aria-labelledby="headingVoters{{ loop.index }}"
|
|
data-bs-parent="#votersAccordion{{ loop.index }}">
|
|
<div class="accordion-body">
|
|
{% if proposal.votes.approve.users|length > 0 %}
|
|
<div class="mb-2">
|
|
<strong>Approbations ({{ proposal.votes.approve.count }}):</strong>
|
|
<div>
|
|
{% for user in proposal.votes.approve.users %}
|
|
<div class="mb-2">
|
|
<span class="voter-badge voter-approve">{{ user.username }}</span>
|
|
{% if user.comment is defined and user.comment is not empty %}
|
|
<div class="comment-text">{{ user.comment }}</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if proposal.votes.abstain.users|length > 0 %}
|
|
<div class="mb-2">
|
|
<strong>Abstentions ({{ proposal.votes.abstain.count }}):</strong>
|
|
<div>
|
|
{% for user in proposal.votes.abstain.users %}
|
|
<div class="mb-2">
|
|
<span class="voter-badge voter-abstain">{{ user.username }}</span>
|
|
{% if user.comment is defined and user.comment is not empty %}
|
|
<div class="comment-text">{{ user.comment }}</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if proposal.votes.oppose.users|length > 0 %}
|
|
<div>
|
|
<strong>Oppositions ({{ proposal.votes.oppose.count }}):</strong>
|
|
<div>
|
|
{% for user in proposal.votes.oppose.users %}
|
|
<div class="mb-2">
|
|
<span class="voter-badge voter-oppose">{{ user.username }}</span>
|
|
{% if user.comment is defined and user.comment is not empty %}
|
|
<div class="comment-text">{{ user.comment }}</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="alert alert-secondary">
|
|
Aucun vote trouvé pour cette proposition.
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
<div class="card-footer">
|
|
<a href="{{ proposal.url }}" target="_blank" class="btn btn-sm btn-outline-primary">
|
|
<i class="bi bi-box-arrow-up-right"></i> Voir sur le wiki
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="alert alert-warning">
|
|
<i class="bi bi-exclamation-triangle"></i> Aucune proposition archivée n'a été trouvée.
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block javascripts %}
|
|
{{ parent() }}
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Filtering functionality
|
|
const filterButtons = document.querySelectorAll('.filter-btn');
|
|
const proposalItems = document.querySelectorAll('.proposal-item');
|
|
|
|
filterButtons.forEach(button => {
|
|
button.addEventListener('click', function() {
|
|
// Remove active class from all buttons
|
|
filterButtons.forEach(btn => btn.classList.remove('active'));
|
|
|
|
// Add active class to clicked button
|
|
this.classList.add('active');
|
|
|
|
const filter = this.getAttribute('data-filter');
|
|
|
|
// Show/hide proposals based on filter
|
|
proposalItems.forEach(item => {
|
|
if (filter === 'all') {
|
|
item.style.display = 'block';
|
|
} else if (filter === 'approved' && item.classList.contains('approved')) {
|
|
item.style.display = 'block';
|
|
} else if (filter === 'rejected' && item.classList.contains('rejected')) {
|
|
item.style.display = 'block';
|
|
} else if (filter === 'neutral' && item.classList.contains('neutral')) {
|
|
item.style.display = 'block';
|
|
} else {
|
|
item.style.display = 'none';
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
// Initialize status distribution chart if it exists
|
|
const statusChartCanvas = document.getElementById('statusChart');
|
|
if (statusChartCanvas) {
|
|
// Get status distribution data from the template
|
|
const statusDistribution = {{ statistics.status_distribution|json_encode|raw }};
|
|
|
|
if (statusDistribution && Object.keys(statusDistribution).length > 0) {
|
|
const labels = Object.keys(statusDistribution);
|
|
const data = Object.values(statusDistribution);
|
|
|
|
// Generate colors for each status
|
|
const backgroundColors = [
|
|
'#28a745', // Approved - green
|
|
'#dc3545', // Rejected - red
|
|
'#ffc107', // Voting/Proposed - yellow
|
|
'#6c757d', // Abandoned/Inactive - gray
|
|
'#17a2b8', // Other statuses - blue
|
|
'#6610f2', // Other statuses - purple
|
|
'#fd7e14', // Other statuses - orange
|
|
'#20c997', // Other statuses - teal
|
|
'#e83e8c' // Other statuses - pink
|
|
];
|
|
|
|
// Create the chart
|
|
new Chart(statusChartCanvas, {
|
|
type: 'pie',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [{
|
|
data: data,
|
|
backgroundColor: backgroundColors.slice(0, labels.length),
|
|
borderWidth: 1
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'right',
|
|
labels: {
|
|
font: {
|
|
size: 12
|
|
}
|
|
}
|
|
},
|
|
tooltip: {
|
|
callbacks: {
|
|
label: function(context) {
|
|
const label = context.label || '';
|
|
const value = context.raw || 0;
|
|
const total = context.dataset.data.reduce((a, b) => a + b, 0);
|
|
const percentage = Math.round((value / total) * 100);
|
|
return `${label}: ${value} (${percentage}%)`;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Initialize year distribution chart
|
|
const yearChartCanvas = document.getElementById('yearDistributionChart');
|
|
if (yearChartCanvas) {
|
|
// Get proposals data from the template
|
|
const proposals = {{ proposals|json_encode|raw }};
|
|
|
|
if (proposals && proposals.length > 0) {
|
|
// Extract years from last_modified dates
|
|
const yearCounts = {};
|
|
|
|
proposals.forEach(proposal => {
|
|
if (proposal.last_modified) {
|
|
// Extract year from the date string (format: "DD Month YYYY")
|
|
const yearMatch = proposal.last_modified.match(/\d{4}$/);
|
|
if (yearMatch) {
|
|
const year = yearMatch[0];
|
|
yearCounts[year] = (yearCounts[year] || 0) + 1;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Sort years chronologically
|
|
const sortedYears = Object.keys(yearCounts).sort();
|
|
const counts = sortedYears.map(year => yearCounts[year]);
|
|
|
|
// Generate a color gradient for the bars
|
|
const colors = sortedYears.map((year, index) => {
|
|
// Create a gradient from blue to green
|
|
const ratio = index / (sortedYears.length - 1 || 1);
|
|
return `rgba(${Math.round(33 + (20 * ratio))}, ${Math.round(150 + (50 * ratio))}, ${Math.round(243 - (100 * ratio))}, 0.7)`;
|
|
});
|
|
|
|
// Create the chart
|
|
new Chart(yearChartCanvas, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: sortedYears,
|
|
datasets: [{
|
|
label: 'Nombre de propositions',
|
|
data: counts,
|
|
backgroundColor: colors,
|
|
borderColor: colors.map(color => color.replace('0.7', '1')),
|
|
borderWidth: 1
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
ticks: {
|
|
precision: 0
|
|
},
|
|
title: {
|
|
display: true,
|
|
text: 'Nombre de propositions'
|
|
}
|
|
},
|
|
x: {
|
|
title: {
|
|
display: true,
|
|
text: 'Année'
|
|
}
|
|
}
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
display: false
|
|
},
|
|
tooltip: {
|
|
callbacks: {
|
|
title: function(context) {
|
|
return `Année ${context[0].label}`;
|
|
},
|
|
label: function(context) {
|
|
const count = context.raw;
|
|
return count > 1 ? `${count} propositions` : `${count} proposition`;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %} |