Skip to content

Commit 6a95774

Browse files
committed
Adding a table detailing the rounds of an STV count
1 parent 7b8068f commit 6a95774

File tree

10 files changed

+127
-15
lines changed

10 files changed

+127
-15
lines changed

app/Models/Question.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,14 @@ public function close(): void
171171
throw new Exception("tried to close question when it has not been opened");
172172
}
173173
if($this->question_type == self::RANKING){
174+
// 'results' will contain the final vote counts and
175+
// 'stats' the table with the vote counts in each round
176+
$resultObject = $this->computeElectionResults();
177+
$toWriteToCache = array();
178+
$toWriteToCache['results'] = $resultObject->getResultAsArray(true);
179+
$toWriteToCache['stats'] = $resultObject->getStats();
174180
$this->update(
175-
['results_cache' => json_encode($this->computeElectionResults()->getResultAsArray(true))]
181+
['results_cache' => json_encode($toWriteToCache)]
176182
);
177183
}
178184
$this->update(['closed_at' => now()]);
@@ -276,7 +282,9 @@ public function rankingData() {
276282
$obj['options'][$option->id] = $option['title'];
277283
}
278284
if($this->isClosed() && $this->results_cache != null){
279-
$obj['results'] = json_decode($this->results_cache);
285+
$resultsCache = json_decode($this->results_cache, true);
286+
$obj['results'] = $resultsCache['results'];
287+
$obj['stats'] = $resultsCache['stats'];
280288
$obj['results_named'] = array();
281289
foreach($obj['results'] as $place => $id){
282290
$obj['results_named'][$place] = array_map(function ($id) use ($obj) { return $obj['options'][$id]; }, is_array($id) ? $id : [$id]);
@@ -289,7 +297,7 @@ public function rankingData() {
289297
shuffle($obj['ballots']);
290298
return $obj;
291299
} else {
292-
return $obj;
300+
throw new \Exception("rankingData queried for a non-ranking question");
293301
}
294302
}
295303

app/Models/QuestionOption.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,17 @@ public function question(): BelongsTo
4141
{
4242
return $this->belongsTo(Question::class);
4343
}
44+
45+
/**
46+
* @return string The initial letters of the option title.
47+
* Useful if having to display the option title in a small table cell.
48+
*/
49+
public function initials(): string
50+
{
51+
$letters = array();
52+
foreach(explode(' ', $this->title, 3) as $word) {
53+
$letters[] = substr($word, 0, 1);
54+
}
55+
return implode($letters);
56+
}
4457
}

resources/lang/en/general.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
'delete' => 'Delete',
1919
'delete_all' => 'Delete all',
2020
'description' => 'Description',
21+
'details' => 'Details',
2122
'dormitory' => 'Dormitory',
2223
'download' => 'Download',
2324
'edit' => 'Edit',

resources/lang/en/voting.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
'new_question' => 'New question',
1919
'new_sitting' => 'New general assembly',
2020
'not_active' => 'You cannot vote because you have no active status for the semester.',
21+
'number_of_ballots' => 'Number of ballots',
2122
'open' => 'Open',
2223
'open_question' => 'Open question',
2324
'open_sitting' => 'Open general assembly',
@@ -59,4 +60,5 @@
5960
'seats_allocated' => 'Number of seats to be allocated: :seats',
6061
'drag_to_rank' => 'Drag options here to rank them',
6162
'drag_to_exclude' => 'Drag options here to exclude from ranking',
63+
'votes_needed_to_win' => 'Votes needed to win'
6264
];

resources/lang/hu/general.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
'delete' => 'Törlés',
1919
'delete_all' => 'Az összes törlése',
2020
'description' => 'Leírás',
21+
'details' => 'Részletek',
2122
'dormitory' => 'Épület',
2223
'download' => 'Letöltés',
2324
'edit' => 'Szerkesztés',

resources/lang/hu/voting.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
'new_question' => 'Új kérdés',
1919
'new_sitting' => 'Új ülés',
2020
'not_active' => 'Jelenleg a státuszod nem aktív, ezért nem tudsz szavazni.',
21+
'number_of_ballots' => 'Szavazók száma',
2122
'open' => 'Nyitva',
2223
'open_question' => 'Kérdés megnyitása',
2324
'open_sitting' => 'Ülés megnyitása',
@@ -59,4 +60,5 @@
5960
'seats_allocated' => 'Kiosztandó mandátumok száma: :seats',
6061
'drag_to_rank' => 'Húzd ide az opciókat a sorrend felállításához',
6162
'drag_to_exclude' => 'Húzd ide az opciókat, amikre nem kívánsz szavazni',
63+
'votes_needed_to_win' => 'Kvóta a megválasztáshoz'
6264
];

resources/sass/materialize.scss

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,22 @@ span.badge.tag {
118118
}
119119
}
120120

121+
/* for STV ranking stats tables */
122+
table.ranking-stats th,
123+
table.ranking-stats td {
124+
text-align: center;
125+
}
126+
table.ranking-stats th.winner,
127+
table.ranking-stats td.winner.colored {
128+
color: green;
129+
}
130+
table.ranking-stats td.colored {
131+
color: red;
132+
}
133+
table.ranking-stats td.round-of-elimination {
134+
font-weight: bold;
135+
}
136+
121137
/* Settings for dark mode */
122138
$transition-effects: color 1s ease, background-color 0.5s ease;
123139

resources/views/student-council/general-assemblies/questions/show.blade.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
<thead>
7878
<tr>
7979
<th>{{ $question->title }}</th>
80-
<th>Number of ballots: {{ $question->users()->count() }}</th>
80+
<th>@lang('voting.number_of_ballots'): {{ $question->users()->count() }}</th>
8181
</tr>
8282
</thead>
8383
<tbody>

resources/views/utils/questions/results/ranking.blade.php

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
@php
33
$rdata = $question->rankingData()
44
@endphp
5+
56
@if(isset($rdata["results_named"]))
7+
<table>
68
@foreach($rdata["results_named"] as $rank => $names)
79
@foreach($names as $idx => $name)
810
<tr>
@@ -11,18 +13,30 @@
1113
</tr>
1214
@endforeach
1315
@endforeach
16+
</table>
17+
@endif
18+
19+
@if(isset($rdata["stats"]))
20+
@include('utils.questions.results.ranking_stats',
21+
['rdata' => $rdata, 'question' => $question]
22+
)
1423
@endif
15-
<tr>
16-
<td><details>
17-
<summary>Computer-readable data of the election (the order of the ballots is randomized):</summary>
18-
<p>
19-
<button class="waves-effect waves-light btn" onclick="copyData('election-results-data')">
20-
Copy data <i class="material-icons right">content_copy</i>
21-
</button>
22-
</p>
23-
<pre id="election-results-data">{{ json_encode($rdata, JSON_PRETTY_PRINT) }}</pre>
24-
</details></td>
25-
</tr>
24+
25+
<table>
26+
<tr>
27+
<td><details>
28+
<summary>Computer-readable data of the election (the order of the ballots is randomized):</summary>
29+
<p>
30+
<button class="waves-effect waves-light btn" onclick="copyData('election-results-data')">
31+
Copy data <i class="material-icons right">content_copy</i>
32+
</button>
33+
</p>
34+
<pre id="election-results-data">
35+
{{ json_encode($question->rankingData(), JSON_PRETTY_PRINT) }}
36+
</pre>
37+
</details></td>
38+
</tr>
39+
</table>
2640

2741
@push('scripts')
2842
<script>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{{--
2+
Draws the table containing vote counts at each round of elimination
3+
for STV votes.
4+
--}}
5+
6+
@php
7+
$stats = $rdata['stats'];
8+
$winners = array_slice($rdata['results'], 0, $question->max_options);
9+
$optionIds = array();
10+
$roundCount = count($stats['rounds'])
11+
@endphp
12+
13+
<p style="font-size: 20px">
14+
@lang('general.details')
15+
</p>
16+
17+
<p>
18+
@lang('voting.votes_needed_to_win'): {{ $stats['Votes Needed to Win'] }}
19+
</p>
20+
21+
<tr><td colspan="2">
22+
<table class="ranking-stats">
23+
<thead>
24+
<tr>
25+
@foreach($stats['rounds'][1] as $optionId => $voteNumber)
26+
<th @class(['winner' => in_array($optionId, $winners)])>
27+
{{$question->options()->where('id', $optionId)->first()->initials()}}
28+
</th>
29+
@php $optionIds[] = $optionId; @endphp
30+
@endforeach
31+
</tr>
32+
</thead>
33+
<tbody>
34+
@for($i=1; $i <= $roundCount; ++$i)
35+
<tr>
36+
@foreach($optionIds as $optionId)
37+
@php
38+
$value = $stats['rounds'][$i][$optionId] ?? "";
39+
if ("" != $value) $value = round($value, 2);
40+
$isWinner = in_array($optionId, $winners);
41+
$isColored = "" == $value || $roundCount == $i || !isset($stats['rounds'][$i+1][$optionId]);
42+
@endphp
43+
<td @class([
44+
'winner' => $isWinner,
45+
'colored' => $isColored,
46+
'round-of-elimination' => $isColored && "" != $value
47+
])>
48+
{{$value}}
49+
</td>
50+
@endforeach
51+
</tr>
52+
@endfor
53+
</tbody>
54+
<table>
55+
</tr>

0 commit comments

Comments
 (0)