diff --git a/docs/options.md b/docs/options.md
index d6e2c8d..3155102 100644
--- a/docs/options.md
+++ b/docs/options.md
@@ -166,6 +166,19 @@ OpenTally applies constraints using the Grey–Fitzgerald method. Whenever a can
Multiple constraints are supported using the method described by Hill ([*Voting Matters* 1998;(9):2–4](http://www.votingmatters.org.uk/ISSUE9/P1.HTM)) and Otten ([*Voting Matters* 2001;(13):4–7](http://www.votingmatters.org.uk/ISSUE13/P3.HTM)).
+## Report options
+
+### Report style
+
+* *Votes only*: The result sheet displays the number of votes held by each candidate at each stage of the count.
+* *Ballots and votes*: The result sheet displays the number of votes *and ballot papers* held by each candidate at each stage of the count.
+
+This functionality is not available on the command line.
+
+### Display up to [n] d.p. (--pp-decimals)
+
+This option allows you to specify to how many decimal places votes will be reported in the results report. It does not affect the internal precision of calculations.
+
## Numeric representation
### Numbers (-n/--numbers), Decimal places (--decimals)
@@ -177,10 +190,6 @@ This dropdown allows you to select how numbers (vote totals, etc.) are represent
* *Rational* (default): Numbers are represented exactly as fractions, resulting in the elimination of rounding error, but increasing computational complexity when the number of surplus transfers is very large.
* *Float (64-bit)*: Numbers are represented as native 64-bit floating-point numbers. This is fast, but not recommended as unexpectedly large rounding errors may be introduced in some circumstances.
-### Display up to [n] d.p. (--pp-decimals)
-
-This option allows you to specify to how many decimal places votes will be reported in the results report. It does not affect the internal precision of calculations.
-
### Normalise ballots (--normalise-ballots)
In the BLT file format, each set of preferences can have a specified weight – this is typically used to indicate multiple voters who had the same preferences.
diff --git a/html/index.html b/html/index.html
index 454bdf4..a9301e8 100644
--- a/html/index.html
+++ b/html/index.html
@@ -182,6 +182,27 @@
+
+ Report options:
+
+
+
+ Report style:
+
+ Votes only
+ Ballots and votes
+
+
+
+
+ Transpose transfers/totals
+
+
+
+ Display up to
+
+ d.p.
+
@@ -203,11 +224,6 @@
-
- Display up to
-
- d.p.
-
Normalise ballots
diff --git a/html/index.js b/html/index.js
index 42e3d4f..be494a0 100644
--- a/html/index.js
+++ b/html/index.js
@@ -58,16 +58,19 @@ worker.onmessage = function(evt) {
document.getElementById('resultLogs1').innerHTML = evt.data.content;
} else if (evt.data.type === 'updateResultsTable') {
- for (let i = 0; i < evt.data.result.length; i++) {
- if (evt.data.result[i]) {
- tblResult.rows[i].insertAdjacentHTML('beforeend', evt.data.result[i]);
+ for (let row = 0; row < evt.data.result.length; row++) {
+ if (evt.data.result[row]) {
+ tblResult.rows[row].insertAdjacentHTML('beforeend', evt.data.result[row]);
// Update candidate status
- if (i >= 3 && i % 2 == 1) {
- if (tblResult.rows[i].lastElementChild.classList.contains('elected')) {
- tblResult.rows[i].cells[0].classList.add('elected');
+ if (
+ (document.getElementById('selReport').value == 'votes' && row >= 3 && row % 2 == 1) ||
+ (document.getElementById('selReport').value == 'ballots_votes' && row >= 4 && row % 2 == 0)
+ ) {
+ if (tblResult.rows[row].lastElementChild.classList.contains('elected')) {
+ tblResult.rows[row].cells[0].classList.add('elected');
} else {
- tblResult.rows[i].cells[0].classList.remove('elected');
+ tblResult.rows[row].cells[0].classList.remove('elected');
}
}
}
@@ -170,14 +173,18 @@ async function clickCount() {
// Dispatch to worker
worker.postMessage({
'type': 'countElection',
+ // Data
'bltData': bltData,
'conData': conData,
- 'optsStr': optsStr,
'bltPath': bltPath,
'conPath': conPath,
+ // Options
+ 'optsStr': optsStr,
'numbers': document.getElementById('selNumbers').value,
'decimals': document.getElementById('txtDP').value,
'normaliseBallots': document.getElementById('chkNormaliseBallots').checked,
+ 'reportStyle': document.getElementById('selReport').value,
+ 'reportTranspose': document.getElementById('chkReportTranspose').checked,
});
}
diff --git a/html/main.css b/html/main.css
index 8ac70da..8acc577 100644
--- a/html/main.css
+++ b/html/main.css
@@ -54,6 +54,10 @@ li.highlight {
.menudiv .subheading {
font-size: 0.8em;
font-weight: 600;
+ margin-top: 0.5rem;
+}
+.menudiv > div > .subheading:first-child {
+ margin-top: 0;
}
.pill-grey {
@@ -101,7 +105,7 @@ td.count sup {
font-size: 0.6rem;
top: 0;
}
-tr.stage-no td, tr.stage-kind td, tr.stage-comment td {
+tr.stage-no td, tr.stage-kind td, tr.stage-comment td, tr.hint-papers-votes td {
text-align: center;
}
tr.stage-kind td {
@@ -110,6 +114,10 @@ tr.stage-kind td {
color: #1b2839;
background-color: #f0f5fb;
}
+tr.hint-papers-votes td {
+ font-size: 0.75em;
+ font-style: italic;
+}
td.excluded {
background-color: #fde2e2;
}
@@ -119,7 +127,7 @@ td.elected {
tr.info td {
background-color: #f0f5fb;
}
-tr.stage-no td:not(:empty), tr.transfers td {
+tr.stage-no td:not(:empty), tr.hint-papers-votes td:not(:empty), tr.transfers td {
border-top: 1px solid #76858c;
}
tr.info:last-child td, .bb {
@@ -134,6 +142,7 @@ tr.info:last-child td, .bb {
tr.stage-no td:nth-child(even):not([rowspan]),
tr.stage-comment td:nth-child(odd),
+tr.hint-papers-votes td:nth-child(even),
tr.candidate.transfers td:nth-child(even):not(.elected):not(.excluded),
tr.candidate.votes td:nth-child(odd):not(.elected):not(.excluded) {
background-color: #f9f9f9;
diff --git a/html/worker.js b/html/worker.js
index 822699a..1f0a208 100644
--- a/html/worker.js
+++ b/html/worker.js
@@ -17,6 +17,7 @@ async function initWasm() {
}
initWasm();
+var reportStyle;
var numbers, election, opts, state, stageNum;
onmessage = function(evt) {
@@ -38,6 +39,8 @@ onmessage = function(evt) {
throw 'Unknown --numbers';
}
+ reportStyle = evt.data.reportStyle;
+
// Init election
election = wasm['election_from_blt_' + numbers](evt.data.bltData);
@@ -60,13 +63,13 @@ onmessage = function(evt) {
postMessage({'type': 'describeCount', 'content': wasm['describe_count_' + numbers](evt.data.bltPath, election, opts)});
// Init results table
- postMessage({'type': 'initResultsTable', 'content': wasm['init_results_table_' + numbers](election, opts)});
+ postMessage({'type': 'initResultsTable', 'content': wasm['init_results_table_' + numbers](election, opts, reportStyle)});
// Step election
state = wasm['CountState' + numbers].new(election);
wasm['count_init_' + numbers](state, opts);
- postMessage({'type': 'updateResultsTable', 'result': wasm['update_results_table_' + numbers](1, state, opts)});
+ postMessage({'type': 'updateResultsTable', 'result': wasm['update_results_table_' + numbers](1, state, opts, reportStyle)});
postMessage({'type': 'updateStageComments', 'comment': wasm['update_stage_comments_' + numbers](state)});
stageNum = 2;
@@ -104,11 +107,11 @@ function resumeCount() {
break;
}
- postMessage({'type': 'updateResultsTable', 'result': wasm['update_results_table_' + numbers](stageNum, state, opts)});
+ postMessage({'type': 'updateResultsTable', 'result': wasm['update_results_table_' + numbers](stageNum, state, opts, reportStyle)});
postMessage({'type': 'updateStageComments', 'comment': wasm['update_stage_comments_' + numbers](state)});
}
- postMessage({'type': 'updateResultsTable', 'result': wasm['finalise_results_table_' + numbers](state)});
+ postMessage({'type': 'updateResultsTable', 'result': wasm['finalise_results_table_' + numbers](state, reportStyle)});
postMessage({'type': 'finalResultSummary', 'summary': wasm['final_result_summary_' + numbers](state, opts)});
}
diff --git a/src/election.rs b/src/election.rs
index 2e25ca4..8534438 100644
--- a/src/election.rs
+++ b/src/election.rs
@@ -304,6 +304,9 @@ pub struct CountCard<'a, N> {
/// Votes of the candidate at the end of this stage
pub votes: N,
+ /// Net ballots transferred to this candidate in this stage
+ pub ballot_transfers: N,
+
/// Parcels of ballots assigned to this candidate
pub parcels: Vec>,
@@ -320,6 +323,7 @@ impl<'a, N: Number> CountCard<'a, N> {
finalised: false,
transfers: N::new(),
votes: N::new(),
+ ballot_transfers: N::new(),
parcels: Vec::new(),
keep_value: None,
};
@@ -333,8 +337,8 @@ impl<'a, N: Number> CountCard<'a, N> {
/// Set [transfers](CountCard::transfers) to 0
pub fn step(&mut self) {
- //self.orig_votes = self.votes.clone();
self.transfers = N::new();
+ self.ballot_transfers = N::new();
}
/// Concatenate all parcels into a single parcel, leaving [parcels](CountCard::parcels) empty
@@ -345,6 +349,11 @@ impl<'a, N: Number> CountCard<'a, N> {
}
return result;
}
+
+ /// Return the number of ballots across all parcels
+ pub fn num_ballots(&self) -> N {
+ return self.parcels.iter().fold(N::new(), |acc, p| acc + p.num_ballots());
+ }
}
/// Parcel of [Vote]s during a count
diff --git a/src/stv/gregory.rs b/src/stv/gregory.rs
index 4b54a21..cd5e3f9 100644
--- a/src/stv/gregory.rs
+++ b/src/stv/gregory.rs
@@ -46,6 +46,7 @@ pub fn distribute_first_preferences(state: &mut CountState) {
let count_card = state.candidates.get_mut(candidate).unwrap();
count_card.parcels.push(parcel);
count_card.transfer(&entry.num_ballots);
+ count_card.ballot_transfers += entry.num_ballots;
}
// Transfer exhausted votes
@@ -56,6 +57,7 @@ pub fn distribute_first_preferences(state: &mut CountState) {
};
state.exhausted.parcels.push(parcel);
state.exhausted.transfer(&result.exhausted.num_ballots);
+ state.exhausted.ballot_transfers += result.exhausted.num_ballots;
state.kind = None;
state.title = "First preferences".to_string();
@@ -251,7 +253,9 @@ where
state.title = String::from(&elected_candidate.name);
state.logger.log_literal(format!("Surplus of {} distributed.", elected_candidate.name));
- let count_card = &state.candidates[elected_candidate];
+ let count_card = state.candidates.get_mut(elected_candidate).unwrap();
+ count_card.ballot_transfers = -count_card.num_ballots();
+
let surplus = &count_card.votes - state.quota.as_ref().unwrap();
// Determine which votes to examine
@@ -361,13 +365,14 @@ where
candidate_transfers.insert(candidate, transfers_orig + transfers_add);
// Transfer candidate votes
- let new_parcel = Parcel {
+ let parcel = Parcel {
votes: entry.votes,
value_fraction: reweight_value_fraction(&value_fraction, &surplus, is_weighted, &surplus_fraction, &surplus_denom, opts.round_surplus_fractions),
source_order: state.num_elected + state.num_excluded,
};
let count_card = state.candidates.get_mut(candidate).unwrap();
- count_card.parcels.push(new_parcel);
+ count_card.ballot_transfers += parcel.num_ballots();
+ count_card.parcels.push(parcel);
}
// Record exhausted votes
@@ -387,12 +392,14 @@ where
value_fraction: value_fraction, // TODO: Reweight exhausted votes
source_order: state.num_elected + state.num_excluded,
};
+ state.exhausted.ballot_transfers += parcel.num_ballots();
state.exhausted.parcels.push(parcel);
}
let mut checksum = N::new();
// Credit transferred votes
+ // ballot_transfers updated above
for (candidate, mut votes) in candidate_transfers {
if let Some(dps) = opts.round_votes {
votes.floor_mut(dps);
@@ -453,6 +460,7 @@ where
// Exclude in one round
for excluded_candidate in excluded_candidates.iter() {
let count_card = state.candidates.get_mut(excluded_candidate).unwrap();
+ count_card.ballot_transfers = -count_card.num_ballots();
count_card.finalised = true;
parcels.append(&mut count_card.parcels);
@@ -493,6 +501,8 @@ where
for mut parcel in cc_parcels {
if parcel.value_fraction == max_value {
+ count_card.ballot_transfers -= parcel.num_ballots();
+
let votes_transferred = parcel.num_votes();
votes.append(&mut parcel.votes);
@@ -546,6 +556,8 @@ where
for parcel in cc_parcels {
if parcel.source_order == min_order {
+ count_card.ballot_transfers -= parcel.num_ballots();
+
let votes_transferred = parcel.num_votes();
parcels.push(parcel);
@@ -581,6 +593,8 @@ where
parcels.push(count_card.parcels.remove(0));
votes_remain = !count_card.parcels.is_empty();
+ count_card.ballot_transfers -= parcels.first().unwrap().num_ballots();
+
// Update votes
let votes_transferred = parcels.first().unwrap().num_votes();
checksum -= &votes_transferred;
@@ -621,6 +635,7 @@ where
candidate_transfers.insert(candidate, transfers_orig + &entry.num_ballots * &parcel.value_fraction);
let count_card = state.candidates.get_mut(candidate).unwrap();
+ count_card.ballot_transfers += parcel.num_ballots();
count_card.parcels.push(parcel);
}
@@ -632,8 +647,8 @@ where
};
// Record transfers
+ state.exhausted.ballot_transfers += parcel.num_ballots();
exhausted_transfers += &result.exhausted.num_ballots * &parcel.value_fraction;
-
state.exhausted.parcels.push(parcel);
// TODO: Detailed transfers logs
@@ -656,6 +671,7 @@ where
}
// Credit transferred votes
+ // ballot_transfers updated above
for (candidate, mut votes) in candidate_transfers {
if let Some(dps) = opts.round_votes {
votes.floor_mut(dps);
diff --git a/src/stv/wasm.rs b/src/stv/wasm.rs
index 48acd11..a0ed6e6 100644
--- a/src/stv/wasm.rs
+++ b/src/stv/wasm.rs
@@ -118,8 +118,8 @@ macro_rules! impl_type {
/// Wrapper for [init_results_table]
#[wasm_bindgen]
#[allow(non_snake_case)]
- pub fn [](election: &[], opts: &STVOptions) -> String {
- return init_results_table(&election.0, &opts.0);
+ pub fn [](election: &[], opts: &STVOptions, report_style: &str) -> String {
+ return init_results_table(&election.0, &opts.0, report_style);
}
/// Wrapper for [describe_count]
@@ -132,8 +132,8 @@ macro_rules! impl_type {
/// Wrapper for [update_results_table]
#[wasm_bindgen]
#[allow(non_snake_case)]
- pub fn [](stage_num: usize, state: &[], opts: &STVOptions) -> Array {
- return update_results_table(stage_num, &state.0, &opts.0);
+ pub fn [](stage_num: usize, state: &[], opts: &STVOptions, report_style: &str) -> Array {
+ return update_results_table(stage_num, &state.0, &opts.0, report_style);
}
/// Wrapper for [update_stage_comments]
@@ -146,8 +146,8 @@ macro_rules! impl_type {
/// Wrapper for [finalise_results_table]
#[wasm_bindgen]
#[allow(non_snake_case)]
- pub fn [](state: &[]) -> Array {
- return finalise_results_table(&state.0);
+ pub fn [](state: &[], report_style: &str) -> Array {
+ return finalise_results_table(&state.0, report_style);
}
/// Wrapper for [final_result_summary]
@@ -321,8 +321,14 @@ fn should_show_vre(opts: &stv::STVOptions) -> bool {
}
/// Generate the first column of the HTML results table
-fn init_results_table(election: &Election, opts: &stv::STVOptions) -> String {
+fn init_results_table(election: &Election, opts: &stv::STVOptions, report_style: &str) -> String {
let mut result = String::from(r#" "#);
+ match report_style {
+ "ballots_votes" => {
+ result.push_str(&r#" "#);
+ }
+ _ => {}
+ }
for candidate in election.candidates.iter() {
result.push_str(&format!(r#"{} "#, candidate.name));
}
@@ -334,7 +340,7 @@ fn init_results_table(election: &Election, opts: &stv::STVOptions)
}
/// Generate subsequent columns of the HTML results table
-fn update_results_table(stage_num: usize, state: &CountState, opts: &stv::STVOptions) -> Array {
+fn update_results_table(stage_num: usize, state: &CountState, opts: &stv::STVOptions, report_style: &str) -> Array {
let result = Array::new();
// Insert borders to left of new exclusions in Wright STV
@@ -345,55 +351,152 @@ fn update_results_table(stage_num: usize, state: &CountState, opts
tdclasses2 = r#"blw "#;
}
- result.push(&format!(r##"{1} "##, tdclasses1, stage_num).into());
- result.push(&format!(r#"{} "#, tdclasses1, state.kind.unwrap_or("")).into());
- result.push(&format!(r#"{} "#, tdclasses1, state.title).into());
+ // Header rows
+ match report_style {
+ "votes" => {
+ result.push(&format!(r##"{1} "##, tdclasses1, stage_num).into());
+ result.push(&format!(r#"{} "#, tdclasses1, state.kind.unwrap_or("")).into());
+ result.push(&format!(r#"{} "#, tdclasses1, state.title).into());
+ }
+ "ballots_votes" => {
+ result.push(&format!(r##"{1} "##, tdclasses1, stage_num).into());
+ result.push(&format!(r#"{} "#, tdclasses1, state.kind.unwrap_or("")).into());
+ result.push(&format!(r#"{} "#, tdclasses1, state.title).into());
+ result.push(&format!(r#"Ballots Votes "#, tdclasses1).into());
+ }
+ _ => unreachable!("Invalid report_style")
+ }
+
for candidate in state.election.candidates.iter() {
let count_card = &state.candidates[candidate];
- match count_card.state {
- CandidateState::Hopeful | CandidateState::Guarded => {
- result.push(&format!(r#"{} "#, tdclasses2, pp(&count_card.transfers, opts.pp_decimals)).into());
- result.push(&format!(r#"{} "#, tdclasses2, pp(&count_card.votes, opts.pp_decimals)).into());
- }
- CandidateState::Elected => {
- result.push(&format!(r#"{} "#, tdclasses2, pp(&count_card.transfers, opts.pp_decimals)).into());
- result.push(&format!(r#"{} "#, tdclasses2, pp(&count_card.votes, opts.pp_decimals)).into());
- }
- CandidateState::Doomed => {
- result.push(&format!(r#"{} "#, tdclasses2, pp(&count_card.transfers, opts.pp_decimals)).into());
- result.push(&format!(r#"{} "#, tdclasses2, pp(&count_card.votes, opts.pp_decimals)).into());
- }
- CandidateState::Withdrawn => {
- result.push(&format!(r#" "#, tdclasses2).into());
- result.push(&format!(r#"WD "#, tdclasses2).into());
- }
- CandidateState::Excluded => {
- result.push(&format!(r#"{} "#, tdclasses2, pp(&count_card.transfers, opts.pp_decimals)).into());
- if count_card.votes.is_zero() && count_card.parcels.iter().all(|p| p.votes.is_empty()) {
- result.push(&format!(r#"Ex "#, tdclasses2).into());
- } else {
- result.push(&format!(r#"{} "#, tdclasses2, pp(&count_card.votes, opts.pp_decimals)).into());
+
+ match report_style {
+ "votes" => {
+ match count_card.state {
+ CandidateState::Hopeful | CandidateState::Guarded => {
+ result.push(&format!(r#"{} "#, tdclasses2, pp(&count_card.transfers, opts.pp_decimals)).into());
+ result.push(&format!(r#"{} "#, tdclasses2, pp(&count_card.votes, opts.pp_decimals)).into());
+ }
+ CandidateState::Elected => {
+ result.push(&format!(r#"{} "#, tdclasses2, pp(&count_card.transfers, opts.pp_decimals)).into());
+ result.push(&format!(r#"{} "#, tdclasses2, pp(&count_card.votes, opts.pp_decimals)).into());
+ }
+ CandidateState::Doomed => {
+ result.push(&format!(r#"{} "#, tdclasses2, pp(&count_card.transfers, opts.pp_decimals)).into());
+ result.push(&format!(r#"{} "#, tdclasses2, pp(&count_card.votes, opts.pp_decimals)).into());
+ }
+ CandidateState::Withdrawn => {
+ result.push(&format!(r#" "#, tdclasses2).into());
+ result.push(&format!(r#"WD "#, tdclasses2).into());
+ }
+ CandidateState::Excluded => {
+ result.push(&format!(r#"{} "#, tdclasses2, pp(&count_card.transfers, opts.pp_decimals)).into());
+ if count_card.votes.is_zero() && count_card.parcels.iter().all(|p| p.votes.is_empty()) {
+ result.push(&format!(r#"Ex "#, tdclasses2).into());
+ } else {
+ result.push(&format!(r#"{} "#, tdclasses2, pp(&count_card.votes, opts.pp_decimals)).into());
+ }
+ }
}
}
+ "ballots_votes" => {
+ match count_card.state {
+ CandidateState::Hopeful | CandidateState::Guarded => {
+ result.push(&format!(r#"{} {} "#, tdclasses2, pp(&count_card.ballot_transfers, 0), pp(&count_card.transfers, opts.pp_decimals)).into());
+ result.push(&format!(r#"{} {} "#, tdclasses2, pp(&count_card.num_ballots(), 0), pp(&count_card.votes, opts.pp_decimals)).into());
+ }
+ CandidateState::Elected => {
+ result.push(&format!(r#"{} {} "#, tdclasses2, pp(&count_card.ballot_transfers, 0), pp(&count_card.transfers, opts.pp_decimals)).into());
+ result.push(&format!(r#"{} {} "#, tdclasses2, pp(&count_card.num_ballots(), 0), pp(&count_card.votes, opts.pp_decimals)).into());
+ }
+ CandidateState::Doomed => {
+ result.push(&format!(r#"{} {} "#, tdclasses2, pp(&count_card.ballot_transfers, 0), pp(&count_card.transfers, opts.pp_decimals)).into());
+ result.push(&format!(r#"{} {} "#, tdclasses2, pp(&count_card.num_ballots(), 0), pp(&count_card.votes, opts.pp_decimals)).into());
+ }
+ CandidateState::Withdrawn => {
+ result.push(&format!(r#" "#, tdclasses2).into());
+ result.push(&format!(r#"WD "#, tdclasses2).into());
+ }
+ CandidateState::Excluded => {
+ result.push(&format!(r#"{} {} "#, tdclasses2, pp(&count_card.ballot_transfers, 0), pp(&count_card.transfers, opts.pp_decimals)).into());
+ if count_card.votes.is_zero() && count_card.parcels.iter().all(|p| p.votes.is_empty()) {
+ result.push(&format!(r#"Ex "#, tdclasses2).into());
+ } else {
+ result.push(&format!(r#"{} {} "#, tdclasses2, pp(&count_card.num_ballots(), 0), pp(&count_card.votes, opts.pp_decimals)).into());
+ }
+ }
+ }
+ }
+ _ => unreachable!("Invalid report_style")
}
}
- result.push(&format!(r#"{} "#, tdclasses2, pp(&state.exhausted.transfers, opts.pp_decimals)).into());
- result.push(&format!(r#"{} "#, tdclasses2, pp(&state.exhausted.votes, opts.pp_decimals)).into());
- result.push(&format!(r#"{} "#, tdclasses2, pp(&state.loss_fraction.transfers, opts.pp_decimals)).into());
- result.push(&format!(r#"{} "#, tdclasses2, pp(&state.loss_fraction.votes, opts.pp_decimals)).into());
+
+ match report_style {
+ "votes" => {
+ result.push(&format!(r#"{} "#, tdclasses2, pp(&state.exhausted.transfers, opts.pp_decimals)).into());
+ result.push(&format!(r#"{} "#, tdclasses2, pp(&state.exhausted.votes, opts.pp_decimals)).into());
+ result.push(&format!(r#"{} "#, tdclasses2, pp(&state.loss_fraction.transfers, opts.pp_decimals)).into());
+ result.push(&format!(r#"{} "#, tdclasses2, pp(&state.loss_fraction.votes, opts.pp_decimals)).into());
+ }
+ "ballots_votes" => {
+ result.push(&format!(r#"{} {} "#, tdclasses2, pp(&state.exhausted.ballot_transfers, 0), pp(&state.exhausted.transfers, opts.pp_decimals)).into());
+ result.push(&format!(r#"{} {} "#, tdclasses2, pp(&state.exhausted.num_ballots(), 0), pp(&state.exhausted.votes, opts.pp_decimals)).into());
+ result.push(&format!(r#"{} "#, tdclasses2, pp(&state.loss_fraction.transfers, opts.pp_decimals)).into());
+ result.push(&format!(r#"{} "#, tdclasses2, pp(&state.loss_fraction.votes, opts.pp_decimals)).into());
+ }
+ _ => unreachable!("Invalid report_style")
+ }
// Calculate total votes
let mut total_vote = state.candidates.values().fold(N::zero(), |acc, cc| { acc + &cc.votes });
total_vote += &state.exhausted.votes;
total_vote += &state.loss_fraction.votes;
- result.push(&format!(r#"{} "#, tdclasses2, pp(&total_vote, opts.pp_decimals)).into());
- result.push(&format!(r#"{} "#, tdclasses2, pp(state.quota.as_ref().unwrap(), opts.pp_decimals)).into());
+ match report_style {
+ "votes" => {
+ result.push(&format!(r#"{} "#, tdclasses2, pp(&total_vote, opts.pp_decimals)).into());
+ }
+ "ballots_votes" => {
+ // Calculate total ballots
+ let mut total_ballots = state.candidates.values().fold(N::zero(), |acc, cc| { acc + cc.num_ballots() });
+ total_ballots += state.exhausted.num_ballots();
+
+ result.push(&format!(r#"{} {} "#, tdclasses2, pp(&total_ballots, 0), pp(&total_vote, opts.pp_decimals)).into());
+ }
+ _ => unreachable!("Invalid report_style")
+ }
+
+ match report_style {
+ "votes" => {
+ result.push(&format!(r#"{} "#, tdclasses2, pp(state.quota.as_ref().unwrap(), opts.pp_decimals)).into());
+ }
+ "ballots_votes" => {
+ result.push(&format!(r#"{} "#, tdclasses2, pp(state.quota.as_ref().unwrap(), opts.pp_decimals)).into());
+ }
+ _ => unreachable!("Invalid report_style")
+ }
+
if should_show_vre(opts) {
if let Some(vre) = &state.vote_required_election {
- result.push(&format!(r#"{} "#, tdclasses2, pp(vre, opts.pp_decimals)).into());
+ match report_style {
+ "votes" => {
+ result.push(&format!(r#"{} "#, tdclasses2, pp(vre, opts.pp_decimals)).into());
+ }
+ "ballots_votes" => {
+ result.push(&format!(r#"{} "#, tdclasses2, pp(vre, opts.pp_decimals)).into());
+ }
+ _ => unreachable!("Invalid report_style")
+ }
} else {
- result.push(&format!(r#" "#, tdclasses2).into());
+ match report_style {
+ "votes" => {
+ result.push(&format!(r#" "#, tdclasses2).into());
+ }
+ "ballots_votes" => {
+ result.push(&format!(r#" "#, tdclasses2).into());
+ }
+ _ => unreachable!("Invalid report_style")
+ }
}
}
@@ -406,13 +509,24 @@ fn update_stage_comments(state: &CountState) -> String {
}
/// Generate the final column of the HTML results table
-fn finalise_results_table(state: &CountState) -> Array {
+fn finalise_results_table(state: &CountState, report_style: &str) -> Array {
let result = Array::new();
// Header rows
- result.push(&r#" "#.into());
- result.push(&"".into());
- result.push(&"".into());
+ match report_style {
+ "votes" => {
+ result.push(&r#" "#.into());
+ result.push(&"".into());
+ result.push(&"".into());
+ }
+ "ballots_votes" => {
+ result.push(&r#" "#.into());
+ result.push(&"".into());
+ result.push(&"".into());
+ result.push(&"".into());
+ }
+ _ => unreachable!("Invalid report_style")
+ }
// Candidate states
for candidate in state.election.candidates.iter() {