diff --git a/html/index.js b/html/index.js
index e159d17..44c58f2 100644
--- a/html/index.js
+++ b/html/index.js
@@ -28,6 +28,8 @@ function clickAdvancedOptions() {
var wasm = wasm_bindgen;
var tblResult = document.getElementById('result');
+var divLogs2 = document.getElementById('resultLogs2');
+var olStageComments;
function updateResultTable(result) {
for (let i = 0; i < result.length; i++) {
@@ -35,6 +37,12 @@ function updateResultTable(result) {
}
}
+function updateStageComments(comment) {
+ let elLi = document.createElement('li');
+ elLi.innerHTML = comment;
+ olStageComments.append(elLi);
+}
+
async function clickCount() {
if (document.getElementById('bltFile').files.length === 0) {
return;
@@ -53,6 +61,10 @@ async function clickCount() {
// Init results table
tblResult.innerHTML = wasm.init_results_table_Rational(election);
+ divLogs2.innerHTML = '
Stage comments:
';
+ olStageComments = document.createElement('ol');
+ divLogs2.append(olStageComments);
+
// Init STV options
let opts = wasm.STVOptions.new(
document.getElementById('chkRoundTVs').checked ? parseInt(document.getElementById('txtRoundTVs').value) : null,
@@ -72,6 +84,7 @@ async function clickCount() {
let state = wasm.CountStateRational.new(election);
wasm.count_init_Rational(state, opts);
updateResultTable(wasm.update_results_table_Rational(1, state, opts));
+ updateStageComments(wasm.update_stage_comments_Rational(state));
for (let stageNum = 2;; stageNum++) {
let isDone = wasm.count_one_stage_Rational(state, opts);
@@ -79,5 +92,10 @@ async function clickCount() {
break;
}
updateResultTable(wasm.update_results_table_Rational(stageNum, state, opts));
+ updateStageComments(wasm.update_stage_comments_Rational(state));
}
+
+ updateResultTable(wasm.finalise_results_table_Rational(state));
+
+ divLogs2.insertAdjacentHTML('beforeend', wasm.final_result_summary_Rational(state));
}
diff --git a/html/main.css b/html/main.css
index df9353f..f62cbe4 100644
--- a/html/main.css
+++ b/html/main.css
@@ -83,7 +83,7 @@ td.count sup {
tr.stage-no td, tr.stage-kind td, tr.stage-comment td {
text-align: center;
}
-tr.stage-kind td:not(:first-child) {
+tr.stage-kind td {
font-size: 0.75em;
min-width: 5rem;
color: #1b2839;
@@ -106,10 +106,10 @@ tr.info td {
color-adjust: exact;
-webkit-print-color-adjust: exact;
}
-tr.stage-no td:not(:first-child), tr.transfers td {
+tr.stage-no td:not(:empty), tr.transfers td {
border-top: 1px solid #76858c;
}
-tr.info:last-child td {
+tr.info:last-child td, .bb {
border-bottom: 1px solid #76858c;
}
diff --git a/src/stv/wasm.rs b/src/stv/wasm.rs
index 396c6fe..3768b6a 100644
--- a/src/stv/wasm.rs
+++ b/src/stv/wasm.rs
@@ -81,17 +81,23 @@ macro_rules! impl_type {
return update_results_table(stage_num, &state.0, opts);
}
- /*#[wasm_bindgen]
+ #[wasm_bindgen]
#[allow(non_snake_case)]
- pub fn [](stage_num: usize, state: &[]) {
- let result = StageResult {
- kind: state.0.kind,
- title: &state.0.title,
- logs: state.0.logger.render(),
- state: CountStateOrRef::from(&state.0),
- };
- print_stage(stage_num, &result);
- }*/
+ pub fn [](state: &[]) -> String {
+ return update_stage_comments(&state.0);
+ }
+
+ #[wasm_bindgen]
+ #[allow(non_snake_case)]
+ pub fn [](state: &[]) -> Array {
+ return finalise_results_table(&state.0);
+ }
+
+ #[wasm_bindgen]
+ #[allow(non_snake_case)]
+ pub fn [](state: &[]) -> String {
+ return final_result_summary(&state.0);
+ }
// Wrapper structs
// Required as we cannot specify &'static in wasm-bindgen: issue #1187
@@ -128,7 +134,7 @@ impl_type!(NativeFloat64);
// Reporting
fn init_results_table(election: &Election) -> String {
- let mut result = String::from(r#" |
|
"#);
+ let mut result = String::from(r#" |
"#);
for candidate in election.candidates.iter() {
result.push_str(&format!(r#"{} |
"#, candidate.name));
}
@@ -144,66 +150,97 @@ fn update_results_table(stage_num: usize, state: &CountState, opts
for candidate in state.election.candidates.iter() {
let count_card = state.candidates.get(candidate).unwrap();
if count_card.state == stv::CandidateState::ELECTED {
- result.push(&format!(r#"{:.dps$} | "#, count_card.transfers, dps=opts.pp_decimals).into());
- result.push(&format!(r#"{:.dps$} | "#, count_card.votes, dps=opts.pp_decimals).into());
+ result.push(&format!(r#"{} | "#, pp(&count_card.transfers, opts.pp_decimals)).into());
+ result.push(&format!(r#"{} | "#, pp(&count_card.votes, opts.pp_decimals)).into());
+ } else if count_card.state == stv::CandidateState::EXCLUDED {
+ result.push(&format!(r#"{} | "#, pp(&count_card.transfers, opts.pp_decimals)).into());
+ if count_card.votes.is_zero() {
+ result.push(&r#"Ex | "#.into());
+ } else {
+ result.push(&format!(r#"{} | "#, pp(&count_card.votes, opts.pp_decimals)).into());
+ }
} else {
- result.push(&format!(r#"{:.dps$} | "#, count_card.transfers, dps=opts.pp_decimals).into());
- result.push(&format!(r#"{:.dps$} | "#, count_card.votes, dps=opts.pp_decimals).into());
+ result.push(&format!(r#"{} | "#, pp(&count_card.transfers, opts.pp_decimals)).into());
+ result.push(&format!(r#"{} | "#, pp(&count_card.votes, opts.pp_decimals)).into());
}
}
- result.push(&format!(r#"{:.dps$} | "#, state.exhausted.transfers, dps=opts.pp_decimals).into());
- result.push(&format!(r#"{:.dps$} | "#, state.exhausted.votes, dps=opts.pp_decimals).into());
- result.push(&format!(r#"{:.dps$} | "#, state.loss_fraction.transfers, dps=opts.pp_decimals).into());
- result.push(&format!(r#"{:.dps$} | "#, state.loss_fraction.votes, dps=opts.pp_decimals).into());
+ result.push(&format!(r#"{} | "#, pp(&state.exhausted.transfers, opts.pp_decimals)).into());
+ result.push(&format!(r#"{} | "#, pp(&state.exhausted.votes, opts.pp_decimals)).into());
+ result.push(&format!(r#"{} | "#, pp(&state.loss_fraction.transfers, opts.pp_decimals)).into());
+ result.push(&format!(r#"{} | "#, pp(&state.loss_fraction.votes, opts.pp_decimals)).into());
// 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#"{:.dps$} | "#, total_vote, dps=opts.pp_decimals).into());
+ result.push(&format!(r#"{} | "#, pp(&total_vote, opts.pp_decimals)).into());
- result.push(&format!(r#"{:.dps$} | "#, state.quota, dps=opts.pp_decimals).into());
+ result.push(&format!(r#"{} | "#, pp(&state.quota, opts.pp_decimals)).into());
return result;
}
-/*fn print_candidates<'a, N: 'a + Number, I: Iterator- )>>(candidates: I) {
- for (candidate, count_card) in candidates {
- if count_card.state == CandidateState::ELECTED {
- cprintln!("- {}: {:.dps$} ({:.dps$}) - ELECTED {}", candidate.name, count_card.votes, count_card.transfers, count_card.order_elected, dps=2);
- } else if count_card.state == CandidateState::EXCLUDED {
- cprintln!("- {}: {:.dps$} ({:.dps$}) - Excluded {}", candidate.name, count_card.votes, count_card.transfers, -count_card.order_elected, dps=2);
- } else {
- cprintln!("- {}: {:.dps$} ({:.dps$})", candidate.name, count_card.votes, count_card.transfers, dps=2);
- }
- }
+fn update_stage_comments(state: &CountState) -> String {
+ return state.logger.render().join(" ");
}
-fn print_stage(stage_num: usize, result: &StageResult) {
- // Print stage details
- match result.kind {
- None => { cprintln!("{}. {}", stage_num, result.title); }
- Some(kind) => { cprintln!("{}. {} {}", stage_num, kind, result.title); }
- };
- cprintln!("{}", result.logs.join(" "));
+fn finalise_results_table(state: &CountState) -> Array {
+ let result = Array::new();
- let state = result.state.as_ref();
+ // Header rows
+ result.push(&r#"
| "#.into());
+ result.push(&"".into());
+ result.push(&"".into());
- // Print candidates
- let candidates = state.election.candidates.iter()
- .map(|c| (c, state.candidates.get(c).unwrap()));
- print_candidates(candidates);
+ // Candidate states
+ for candidate in state.election.candidates.iter() {
+ let count_card = state.candidates.get(candidate).unwrap();
+ if count_card.state == stv::CandidateState::ELECTED {
+ result.push(&format!(r#"ELECTED {} | "#, count_card.order_elected).into());
+ } else if count_card.state == stv::CandidateState::EXCLUDED {
+ result.push(&format!(r#"Excluded {} | "#, -count_card.order_elected).into());
+ } else {
+ result.push(&r#" | "#.into());
+ }
+ result.push(&"".into());
+ }
- // Print summary rows
- cprintln!("Exhausted: {:.dps$} ({:.dps$})", state.exhausted.votes, state.exhausted.transfers, dps=2);
- cprintln!("Loss by fraction: {:.dps$} ({:.dps$})", state.loss_fraction.votes, state.loss_fraction.transfers, dps=2);
+ return result;
+}
+
+fn final_result_summary(state: &CountState) -> String {
+ let mut result = String::from("Count complete. The winning candidates are, in order of election:
");
- 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;
- cprintln!("Total votes: {:.dps$}", total_vote, dps=2);
+ let mut winners = Vec::new();
+ for (candidate, count_card) in state.candidates.iter() {
+ if count_card.state == CandidateState::ELECTED {
+ winners.push((candidate, count_card.order_elected));
+ }
+ }
+ winners.sort_unstable_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
- cprintln!("Quota: {:.dps$}", state.quota, dps=2);
+ for (winner, _) in winners.into_iter() {
+ result.push_str(&format!("- {}
", winner.name));
+ }
- cprintln!("");
-}*/
+ result.push_str("
");
+ return result;
+}
+
+fn pp(n: &N, dps: usize) -> String {
+ if n.is_zero() {
+ return "".to_string();
+ }
+
+ let mut raw = format!("{:.dps$}", n, dps=dps);
+ if raw.contains('.') {
+ raw = raw.replacen(".", ".", 1);
+ raw.push_str("");
+ }
+
+ if raw.starts_with('-') {
+ raw = raw.replacen("-", "−", 1);
+ }
+
+ return raw;
+}