On rajoute le graphe des résultats
This commit is contained in:
parent
f4f0347e50
commit
bd8ba6c327
6 changed files with 104 additions and 8 deletions
|
@ -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>
|
||||||
|
|
|
@ -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(),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
@ -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%
|
||||||
|
|
Loading…
Reference in a new issue