On rajoute le graphe des résultats

This commit is contained in:
Tom Hubrecht 2021-05-03 10:18:42 +02:00
parent f4f0347e50
commit bd8ba6c327
6 changed files with 104 additions and 8 deletions

View file

@ -1,9 +1,10 @@
{% load i18n %} {% load i18n %}
<div class="panel-block"> <div class="panel-block">
<div class="columns is-centered is-fullwidth"> <div class="columns is-centered is-fullwidth is-vcentered">
<div class="column"> {# Tableau des duels #}
<table class="table is-bordered is-striped"> <div class="column is-narrow">
<table class="table is-bordered is-striped is-centered">
<thead> <thead>
<th class="has-text-centered"> <th class="has-text-centered">
<span class="icon"> <span class="icon">
@ -44,9 +45,16 @@
</tbody> </tbody>
</table> </table>
</div> </div>
{# Graphe #}
<div class="column is-narrow">
<figure class="image">
{{ graph|safe }}
</figure>
</div>
</div> </div>
</div> </div>
<div class="panel-block"> <div class="panel-block">
<i>{% trans "La matrice des résultats montre les points d'avance, l'option gagnante est affichée sur la colonne et la perdante sur la ligne. " %}</i> <i>{% trans "La matrice des résultats montre les points d'avance, l'option gagnante est affichée sur la colonne et la perdante sur la ligne. Le graphe représente les duels entre les options, le nombre de votes d'avance est précisé sur l'arête." %}</i>
</div> </div>

View file

@ -3,6 +3,8 @@ import io
import networkx as nx import networkx as nx
import numpy as np import numpy as np
from matplotlib.colors import ListedColormap
from matplotlib.figure import Figure
from networkx.algorithms.dag import ancestors, descendants from networkx.algorithms.dag import ancestors, descendants
from django.contrib.auth.hashers import make_password from django.contrib.auth.hashers import make_password
@ -245,14 +247,31 @@ class ResultsData:
return render_to_string("elections/results/select.html") return render_to_string("elections/results/select.html")
def rank(question): def rank(question):
"""On récupère la matrice des résultats et on l'affiche""" """
duels = question.duels.all() On récupère la matrice des résultats et on l'affiche, ainsi que le graphe des duels
"""
duels = question.duels.select_related("winner", "loser").all()
options = list(question.options.all()) options = list(question.options.all())
n = len(options) n = len(options)
# Initialisation
_matrix = np.zeros((n, n), dtype=int) _matrix = np.zeros((n, n), dtype=int)
matrix = np.zeros((n, n), dtype=tuple) matrix = np.zeros((n, n), dtype=tuple)
G = nx.DiGraph()
G.add_nodes_from(options)
graph = io.StringIO()
e_labels = {}
max_votes = 1
# Création de la liste des couleurs pour les arêtes
values = np.ones((128, 4))
values[:, 0] = np.linspace(29, 72, 128) / 256
values[:, 1] = np.linspace(39, 199, 128) / 256
values[:, 2] = np.linspace(58, 116, 128) / 256
cmap = ListedColormap(values)
# On récupère les données
for d in duels: for d in duels:
i, j = options.index(d.loser), options.index(d.winner) i, j = options.index(d.loser), options.index(d.winner)
_matrix[i, j] = d.amount _matrix[i, j] = d.amount
@ -261,6 +280,13 @@ class ResultsData:
for j in range(n): for j in range(n):
if _matrix[i, j] > _matrix[j, i]: if _matrix[i, j] > _matrix[j, i]:
matrix[i, j] = (_matrix[i, j], "is-success") matrix[i, j] = (_matrix[i, j], "is-success")
# On rajoute un arête sur le graphe
nb_votes = _matrix[i, j] - _matrix[j, i]
max_votes = max(nb_votes, max_votes)
G.add_edge(options[j], options[i], weight=nb_votes)
e_labels[(options[j], options[i])] = nb_votes
elif _matrix[i, j] < _matrix[j, i]: elif _matrix[i, j] < _matrix[j, i]:
matrix[i, j] = (_matrix[i, j], "is-danger") matrix[i, j] = (_matrix[i, j], "is-danger")
else: else:
@ -268,9 +294,52 @@ class ResultsData:
matrix = zip(matrix.tolist(), options) matrix = zip(matrix.tolist(), options)
# On dessine le graphe
fig = Figure()
ax = fig.gca()
n_labels = {o: o.abbreviation or (i + 1) for i, o in enumerate(options)}
# Calcul des couleurs des arêtes
e_list = G.edges()
w_list = nx.get_edge_attributes(G, "weight")
e_colors = [w_list[e] / max_votes for e in e_list]
# Affichage du graphe
g_pos = nx.spring_layout(G)
nx.draw_networkx_nodes(G, g_pos, node_color="#1d273a", node_size=1500, ax=ax)
nx.draw_networkx_edges(
G,
g_pos,
arrowstyle="->",
arrowsize=30,
edge_color=e_colors,
edge_cmap=cmap,
width=3,
connectionstyle="arc3,rad=0.1",
min_target_margin=18,
ax=ax,
)
nx.draw_networkx_labels(G, g_pos, font_color="#f1f4f8", labels=n_labels, ax=ax)
nx.draw_networkx_edge_labels(
G,
g_pos,
edge_labels=e_labels,
ax=ax,
rotate=False,
bbox={"fc": "#f1f4f8", "ec": "#1d273a", "boxstyle": "round,pad=0.6"},
)
fig.savefig(graph, format="svg")
return render_to_string( return render_to_string(
"elections/results/rank.html", "elections/results/rank.html",
{"q": question, "matrix": matrix, "options": options}, {
"q": question,
"matrix": matrix,
"options": options,
"graph": graph.getvalue(),
},
) )

View file

@ -3,4 +3,5 @@ django-translated-fields==0.11.1
authens>=0.1b2 authens>=0.1b2
numpy numpy
networkx networkx
matplotlib
python-csv python-csv

View file

@ -10589,4 +10589,13 @@ body {
white-space: unset; white-space: unset;
} }
.table.is-centered {
margin-left: auto;
margin-right: auto;
}
.columns.is-fullwidth {
width: 100%;
}
/*# sourceMappingURL=main.css.map */ /*# sourceMappingURL=main.css.map */

File diff suppressed because one or more lines are too long

View file

@ -36,3 +36,12 @@ body
height: auto height: auto
min-height: 2em min-height: 2em
white-space: unset white-space: unset
// Centered tables
.table.is-centered
margin-left: auto
margin-right: auto
// Fullwidth columns
.columns.is-fullwidth
width: 100%