Skip to content

Commit

Permalink
Merge pull request #509 from artoonie/stv-fix-percentage
Browse files Browse the repository at this point in the history
STV should use first-round total for vote percent
  • Loading branch information
artoonie authored Sep 30, 2024
2 parents b03d1b2 + 46a1791 commit 423e152
Show file tree
Hide file tree
Showing 9 changed files with 55 additions and 30 deletions.
14 changes: 11 additions & 3 deletions static/bargraph/barchart.js
Original file line number Diff line number Diff line change
Expand Up @@ -326,14 +326,21 @@ function makeBarGraph(args) {
}
else
{
return startText + votesAndPctToText(d.data["candidate"], d[1], totalVotesPerRound[d.round], false, false);
const percentDenominator = calculatePercentDenominator(lastRoundNumWinners, totalVotesPerRound[0], totalVotesPerRound[d.round])
return startText + votesAndPctToText(d.data["candidate"], d[1], percentDenominator, false, false);
}
};
function secondaryDataLabelTextFn(d) {
if(isEliminatedThisRound(d) || !isVertical) {
return "";
}
return percentToText(d.data["candidate"], d[1], totalVotesPerRound[d.round]);
let percentDenominator;
if (lastRoundNumWinners > 1) {
percentDenominator = totalVotesPerRound[0];
} else {
percentDenominator = totalVotesPerRound[d.round];
}
return percentToText(d.data["candidate"], d[1], percentDenominator);
};
function rightRoundedRect(x, y, width, height, radius) {
return "M" + x + "," + y
Expand Down Expand Up @@ -428,7 +435,8 @@ function makeBarGraph(args) {
// Hover text helper
function barTextFn(d) {
const text = !isEliminatedThisRound(d) ? "On Round " + (d.round+1) + ", has " : "Eliminated on Round " + (d.round+1) + " with ";
return text + votesAndPctToText(d.data["candidate"], d[1], totalVotesPerRound[d.round], true, false);
const percentDenominator = calculatePercentDenominator(lastRoundNumWinners, totalVotesPerRound[0], totalVotesPerRound[d.round])
return text + votesAndPctToText(d.data["candidate"], d[1], percentDenominator, true, false);
};

function addMetadataToEachBar() {
Expand Down
5 changes: 3 additions & 2 deletions static/sankey/sankey-wrapper.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function makeSankey(graph, numRounds, numCandidates, longestLabelApxWidth, totalVotesPerRound, colorThemeIndex) {
function makeSankey(graph, numRounds, numCandidates, numWinners, longestLabelApxWidth, totalVotesPerRound, colorThemeIndex) {
// Below are crazy heuristics to try to get the graph to look good
// on a variety of sizes.
const units = "Votes";
Expand Down Expand Up @@ -75,7 +75,8 @@ function makeSankey(graph, numRounds, numCandidates, longestLabelApxWidth, total
return textForNode(d)
}
function getNodePercentText(d) {
return percentToText(d.name, d.value, totalVotesPerRound[d.round])
const percentDenominator = calculatePercentDenominator(numWinners, totalVotesPerRound[0], totalVotesPerRound[d.round])
return percentToText(d.name, d.value, percentDenominator)
}

function makeViewboxSizeString(size0, size1) {
Expand Down
13 changes: 11 additions & 2 deletions static/visualizer/visualize-common.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ function votesToText(numVotes, includeWordVotes, doSimplifyNumber)
return fmt(numVotes) + " " + (includeWordVotes ? "votes" : "");
}

function calculatePercentDenominator(numWinners, firstRoundVoteTotal, currRoundVoteTotal)
{
if (numWinners <= 1) {
return currRoundVoteTotal;
} else {
return firstRoundVoteTotal;
}
}

function percentToText(candidateName, numVotes, totalVotes)
{
// Inactive ballots should not show %
Expand All @@ -50,9 +59,9 @@ function percentToText(candidateName, numVotes, totalVotes)
return "(" + percentVotes + "%)";
}

function votesAndPctToText(candidateName, numVotes, totalVotes, includeWordVotes, doSimplifyNumber)
function votesAndPctToText(candidateName, numVotes, percentDenominator, includeWordVotes, doSimplifyNumber)
{
return votesToText(numVotes, includeWordVotes, doSimplifyNumber) + " " + percentToText(candidateName, numVotes, totalVotes);
return votesToText(numVotes, includeWordVotes, doSimplifyNumber) + " " + percentToText(candidateName, numVotes, percentDenominator);
}

function classNameForDescriptionVerb(verb) {
Expand Down
2 changes: 1 addition & 1 deletion templates/sankey/sankey-nonblocking.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
if (numRounds > 1)
{
loadFunctions();
makeSankey(graph, numRounds, numCandidates, longestLabelApxWidth, totalVotesPerRound, config.colorTheme);
makeSankey(graph, numRounds, numCandidates, numWinners, longestLabelApxWidth, totalVotesPerRound, config.colorTheme);
}
else
{
Expand Down
6 changes: 3 additions & 3 deletions testData/wikiOutput.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
| {{won|}} 134
| {{won|}} 33.5%
| {{won|}} 134
| {{won|}} 33.53%
| {{won|}} 33.5%
|-
|-
! scope="row" style="text-align:left;" | {{sortname| Larry|Edwards}}
Expand All @@ -51,7 +51,7 @@
| {{won|}} 184.88
| {{won|}} 46.22%
| {{won|}} 134
| {{won|}} 33.53%
| {{won|}} 33.5%
|-
|-
! scope="row" style="text-align:left;" | {{sortname| Mary|Hall-Rayford}}
Expand All @@ -64,7 +64,7 @@
| 81.11
| 20.28%
| 131.67
| 32.94%
| 32.92%
|-
|-
! scope="row" style="text-align:left;" | {{sortname| Sarah|Lucido}}
Expand Down
9 changes: 9 additions & 0 deletions visualizer/graph/graphSummary.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ def __init__(self, graph):
self.numWinners = len(self.winnerNames)
self.numEliminated = sum(len(r.eliminatedNames) for r in rounds)

def percent_denominator(self, roundNum):
"""
percentDenominator is either the current round total in IRV,
and the first round total in STV.
"""
if self.numWinners > 1:
roundNum = 0
return self.rounds[roundNum].totalActiveVotes


class RoundInfo:
""" Summarizes a single round, with functions to build the round """
Expand Down
1 change: 1 addition & 0 deletions visualizer/sankey/graphToD3.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def __init__(self, graph):
js = ''
js += f'numRounds = {graph.numRounds};\n'
js += f'numCandidates = {len(graph.nodesPerRound[0])} ;\n'
js += f'numWinners = {len(graph.nodesPerRound[0])} ;\n'
js += f'longestLabelApxWidth = {longestLabelApxWidth};\n'
js += f'totalVotesPerRound = {totalVotesPerRound};\n'
js += 'graph = {"nodes" : [], "links" : []};\n'
Expand Down
33 changes: 15 additions & 18 deletions visualizer/tabular/tabular.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from visualizer.descriptors import textForWinnerUtils as TextForWinner


def makePrimarySecondaryLabels(numVotes, allVotes, item):
def makePrimarySecondaryLabels(numVotes, denominator, item):
if item.isActive:
primaryLabel = percentify(numVotes, allVotes)
primaryLabel = percentify(numVotes, denominator)
secondaryLabel = votify(numVotes)
else:
primaryLabel = intify(numVotes)
Expand Down Expand Up @@ -60,9 +60,9 @@ def __init__(self, graph, config):
d['change'] = changify(votesAddedThisRound)

myNumVotes = cinfo.totalVotesPerRound[roundNum]
allVotes = roundData.totalActiveVotes
percentDenominator = summary.percent_denominator(roundNum)
d['primaryLabel'], d['secondaryLabel'] = makePrimarySecondaryLabels(
myNumVotes, allVotes, item)
myNumVotes, percentDenominator, item)
d['name'] = cinfo.name
d['wonThisRound'] = cinfo.name in roundData.winnerNames
d['eliminatedThisRound'] = isEliminatedThisRound
Expand Down Expand Up @@ -117,11 +117,9 @@ def __init__(self, graph, item):
self.rounds = range(numRounds)
for i, myNumVotes in enumerate(candidateInfo.totalVotesPerRound):
thisRoundSummary = summary.rounds[i]
self.eachRound.append(
OneCandidateOneRound(
thisRoundSummary,
myNumVotes,
item))
percentDenominator = summary.percent_denominator(i)
self.eachRound.append(OneCandidateOneRound(
thisRoundSummary, myNumVotes, percentDenominator, item))

# We want all rounds filled out - pad the remaining rounds
numRoundsThisCandidate = len(candidateInfo.totalVotesPerRound)
Expand All @@ -138,14 +136,12 @@ class OneCandidateOneRound:
numVotes: str
pctVotes: str

def __init__(self, thisRoundSummary, myNumVotes, item):
allVotes = thisRoundSummary.totalActiveVotes

def __init__(self, thisRoundSummary, myNumVotes, percentDenominator, item):
self.primaryLabel, self.secondaryLabel = makePrimarySecondaryLabels(
myNumVotes, allVotes, item)
myNumVotes, percentDenominator, item)

self.numVotes = intify(myNumVotes)
self.pctVotes = percentify(myNumVotes, allVotes)
self.pctVotes = percentify(myNumVotes, percentDenominator)

self.isWinner = item.name in thisRoundSummary.winnerNames
self.isEliminated = item.name in thisRoundSummary.eliminatedNames
Expand Down Expand Up @@ -189,7 +185,7 @@ def __init__(self, graph, config, item):

self.rounds.append(
RoundTabulation(config, node.count, i,
item, summary.rounds, linksForThisNode))
item, summary, linksForThisNode))


class RoundTabulation:
Expand All @@ -199,14 +195,15 @@ class RoundTabulation:
# secondaryLabel:str
# round_i:int, 1-indexed

def __init__(self, config, totalActiveVotes, round_i, item, roundInfos, linksForThisNode):
def __init__(self, config, totalActiveVotes, round_i, item, summary, linksForThisNode):
self.round_i = round_i + 1

myNumVotes = float(totalActiveVotes)
allVotes = roundInfos[round_i].totalActiveVotes
percentDenominator = summary.percent_denominator(round_i)
self.primaryLabel, self.secondaryLabel = makePrimarySecondaryLabels(
myNumVotes, allVotes, item)
myNumVotes, percentDenominator, item)

roundInfos = summary.rounds
thisRoundWinners = roundInfos[round_i].winnerNames
if round_i < len(roundInfos) - 1:
thisRoundEliminations = roundInfos[round_i + 1].eliminatedNames
Expand Down
2 changes: 1 addition & 1 deletion visualizer/tests/testSimple.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ def test_wikicode(self, mockGetDateString):
# TODO - how can I test this? I tried mwparserfromhell but that doesn't have a way to
# validate syntax. For now, just validate it doesn't throw an exception, and that the
# length is the same magic number I expect, so I don't inadvertently change anything
magicKnownTextLength = 4102
magicKnownTextLength = 4100
self.assertEqual(len(text), magicKnownTextLength)
with open('testData/wikiOutput.txt', 'r', encoding='utf-8') as f:
self.maxDiff = None
Expand Down

0 comments on commit 423e152

Please sign in to comment.