Skip to content

Commit 584e72a

Browse files
committed
Show possible coalitions
1 parent b9c05c6 commit 584e72a

File tree

2 files changed

+123
-10
lines changed

2 files changed

+123
-10
lines changed

index.html

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,16 @@ <h1>Valgsimulator</h1>
7575
<input type="checkbox" id="grouplocal" oninput="update();"/>
7676
<label for="grouplocal">Samle distrikter</label>
7777
</span>
78+
<span>
79+
<label for="friends">Samarbeidsvillige partier</legend>
80+
<input type="text" id="friends" value="A+SP+SV, SV+MDG, H+FRP+V+KRF" oninput="update();" style="width: 20em"/>
81+
</span>
7882
</fieldset>
79-
<h2><a id="voteslink" onclick="showTables(true, false);" style="margin-right: 1em">Stemmer</a><a id="seatslink" onclick="showTables(false, true);">Mandater</a></h2>
83+
<h2>
84+
<a id="voteslink" onclick="showTables(true, false, false);" style="margin-right: 0.3em">Stemmer</a>
85+
<a id="seatslink" onclick="showTables(false, true, false);" style="margin-right: 0.3em">Mandater</a>
86+
<a id="teamslink" onclick="showTables(false, false, true);">Koalisjoner</a>
87+
</h2>
8088
<div class="tablecontainer">
8189
<table id="votes">
8290
<thead></thead>
@@ -86,6 +94,10 @@ <h2><a id="voteslink" onclick="showTables(true, false);" style="margin-right: 1e
8694
<thead></thead>
8795
<tbody></tbody>
8896
</table>
97+
<table id="teams">
98+
<thead></thead>
99+
<tbody></tbody>
100+
</table>
89101
</div>
90102
<h2>Kilder</h2>
91103
<ul>

valg.js

Lines changed: 110 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
var voteTable = document.getElementById("votes");
22
var seatTable = document.getElementById("seats");
3+
var teamTable = document.getElementById("teams");
34
var voteLink = document.getElementById("voteslink");
45
var seatLink = document.getElementById("seatslink");
6+
var teamLink = document.getElementById("teamslink");
57

68
function roundDown(number, decimals) {
79
number = Math.floor(number * 10**decimals) / 10**decimals;
810
return number.toFixed(decimals);
911
};
1012

11-
function printTable(table, data, parties, mergeParties, mergeDistricts, mergedDistrictLabel, showFraction, showColumnTotals, showRowTotals, decimals) {
13+
function printTable(table, data, parties, mergeParties, mergeDistricts, mergedDistrictLabel, firstHeader, showFraction, showColumnTotals, showRowTotals, decimals) {
1214
if (mergeParties.length > 0) {
1315
var newData = {};
1416
for (var district in data) {
@@ -26,8 +28,9 @@ function printTable(table, data, parties, mergeParties, mergeDistricts, mergedDi
2628
parties.splice(parties.indexOf(party), 1); // remove from list of sorted parties
2729
}
2830
}
31+
parties = parties.slice(); // make copy
2932
parties.push("ANDRE"); // always last
30-
return printTable(table, newData, parties, [], mergeDistricts, mergedDistrictLabel, showFraction, showColumnTotals, showRowTotals, decimals);
33+
return printTable(table, newData, parties, [], mergeDistricts, mergedDistrictLabel, firstHeader, showFraction, showColumnTotals, showRowTotals, decimals);
3134
} else if (mergeDistricts.length > 0) {
3235
var newData = {};
3336
newData[mergedDistrictLabel] = {};
@@ -43,7 +46,7 @@ function printTable(table, data, parties, mergeParties, mergeDistricts, mergedDi
4346
newData[district] = data[district];
4447
}
4548
}
46-
return printTable(table, newData, parties, mergeParties, [], mergedDistrictLabel, showFraction, showColumnTotals, showRowTotals, decimals);
49+
return printTable(table, newData, parties, mergeParties, [], mergedDistrictLabel, firstHeader, showFraction, showColumnTotals, showRowTotals, decimals);
4750
}
4851

4952
// Make ANDRE always appear last
@@ -60,7 +63,7 @@ function printTable(table, data, parties, mergeParties, mergeDistricts, mergedDi
6063
const format = (x, total) => showFraction ? (roundDown(100*x/total, decimals) + " %") : x.toLocaleString("no-NO"); // round *down* so parties slightly below threshold cannot seem to be above it (e.g. show 3.999% as 3.9% instead of 4.0%)
6164
var head = table.tHead.insertRow();
6265
var cell = document.createElement("th");
63-
cell.innerHTML = "Valgdistrikt";
66+
cell.innerHTML = firstHeader;
6467
head.appendChild(cell);
6568
for (var party of parties) {
6669
var cell = document.createElement("th");
@@ -286,6 +289,46 @@ function calculateAllSeats(votes, localSeatCounts, globalSeatCount, globalThresh
286289
return seats;
287290
};
288291

292+
function calculateTeams(friends) {
293+
function dfs(team, teams) {
294+
for (var team2 of teams) {
295+
if (team.length == team2.length) {
296+
var isSubset = true;
297+
for (var party of team) {
298+
if (!team2.includes(party)) {
299+
isSubset = false;
300+
break;
301+
}
302+
}
303+
if (isSubset) {
304+
return; // team already explored
305+
}
306+
}
307+
}
308+
309+
teams.push(team.slice()); // register team
310+
311+
// Explore new teams by adding parties that are friends with everyone already in the team
312+
for (var newParty in friends) {
313+
var friendsWithEveryone = true;
314+
for (var party of team) {
315+
if (!friends[party].includes(newParty)) {
316+
friendsWithEveryone = false;
317+
break;
318+
}
319+
}
320+
if (friendsWithEveryone) {
321+
team.push(newParty);
322+
dfs(team, teams);
323+
team.splice(team.indexOf(newParty), 1);
324+
}
325+
}
326+
return teams;
327+
};
328+
329+
return dfs([], []).slice(1); // drop empty team
330+
};
331+
289332
function update() {
290333
var threshold = parseFloat(document.getElementById("threshold").value);
291334
var firstDivisor = parseFloat(document.getElementById("firstdivisor").value);
@@ -297,6 +340,7 @@ function update() {
297340

298341
var [localSeatCounts, globalSeatCount] = calculateAllSeatCounts(districts, localSeatCount, globalSeatsPerDistrict, areaFactor, minSeatsPerDistrict);
299342
var seats = calculateAllSeats(votes, localSeatCounts, globalSeatCount, threshold, firstDivisor, negativeGlobalSeats);
343+
var globalSeats = sumLocal(seats);
300344

301345
var groupOtherParties = document.getElementById("groupotherparties").checked;
302346
var totalSeats = sumLocal(seats);
@@ -337,7 +381,6 @@ function update() {
337381
var globalVotes = sumLocal(votes);
338382
parties.sort((party1, party2) => compare(party1, party2, globalVotes));
339383
} else if (sortParties == "Mandater") {
340-
var globalSeats = sumLocal(seats);
341384
parties.sort((party1, party2) => compare(party1, party2, globalSeats));
342385
} else if (sortParties == "Farge (subjektivt)") {
343386
var spectrum = ["NKP", "RØDT", "SV", "A", "SP", "MDG", "KYST", "KRF", "V", "H", "FRP", "KRISTNE", "LIBS", "DEMN", "AAN"];
@@ -348,16 +391,74 @@ function update() {
348391
parties.sort((party1, party2) => compare(party2, party1, rightism));
349392
}
350393

351-
printTable(voteTable, votes, parties, mergeParties, mergeDistricts, "Distriktsstemmer", showFraction, true, true, decimals);
352-
printTable(seatTable, seats, parties, mergeParties, mergeDistricts, "Distriktsmandater", showFraction, true, true, decimals);
394+
printTable(voteTable, votes, parties, mergeParties, mergeDistricts, "Distriktsstemmer", "Valgdistrikt", showFraction, true, true, decimals);
395+
printTable(seatTable, seats, parties, mergeParties, mergeDistricts, "Distriktsmandater", "Valgdistrikt", showFraction, true, true, decimals);
396+
397+
// Read graph of friend parties
398+
var friendsInput = document.getElementById("friends");
399+
var friendsText = friendsInput.value.trim();
400+
var friends = {};
401+
for (var party of parties) {
402+
friends[party] = [];
403+
}
404+
var valid = true;
405+
if (friendsText.length > 0) {
406+
for (var text of friendsText.split(/\s*,\s*/)) {
407+
var friendList = text.split(/\s*\+\s*/);
408+
for (var party1 of friendList) {
409+
if (!parties.includes(party1)) {
410+
valid = false;
411+
break;
412+
}
413+
for (var party2 of friendList) {
414+
if (party1 != party2 && !friends[party1].includes(party2)) { // don't be friends with oneself
415+
friends[party1].push(party2);
416+
}
417+
}
418+
}
419+
}
420+
}
421+
422+
// Do not let unrepresented parties be part of coalitions
423+
for (var party in friends) {
424+
if (globalSeats[party] == 0) {
425+
delete friends[party];
426+
}
427+
}
428+
429+
// Color input field depending on validity of input formatting
430+
friendsInput.style["background-color"] = valid ? "limegreen" : "crimson";
431+
432+
if (valid) {
433+
var teams = calculateTeams(friends);
434+
435+
var teamSeats = {};
436+
for (var team of teams) {
437+
teamSeats[team] = 0;
438+
for (var party of team) {
439+
teamSeats[team] += globalSeats[party];
440+
}
441+
}
442+
443+
teams.sort((team1, team2) => teamSeats[team2] - teamSeats[team1]); // TODO: guarantee sorting order in table output of teams (and districts above)
444+
445+
var teamsDict = {};
446+
totalSeats = sumGlobal(totalSeats);
447+
for (var team of teams) {
448+
teamsDict[team.join(" + ")] = {"Posisjon": teamSeats[team], "Opposisjon": totalSeats - teamSeats[team]};
449+
}
450+
printTable(teamTable, teamsDict, ["Posisjon", "Opposisjon"], [], [], "", "Partier i posisjon", showFraction, false, true, 0);
451+
}
353452
};
354453

355-
function showTables(showVotes, showSeats) {
454+
function showTables(showVotes, showSeats, showTeams) {
356455
voteTable.style["display"] = showVotes ? "block" : "none";
357456
seatTable.style["display"] = showSeats ? "block" : "none";
457+
teamTable.style["display"] = showTeams ? "block" : "none";
358458
voteLink.style["color"] = showVotes ? "black" : "gray";
359459
seatLink.style["color"] = showSeats ? "black" : "gray";
460+
teamLink.style["color"] = showTeams ? "black" : "gray";
360461
};
361462

362-
showTables(true, false);
463+
showTables(true, false, false);
363464
update(); // run once on page load

0 commit comments

Comments
 (0)