diff --git a/Cargo.lock b/Cargo.lock index 56fa273..d0f758d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -202,6 +202,15 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +[[package]] +name = "js-sys" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -290,6 +299,7 @@ dependencies = [ "csv", "flate2", "git-version", + "js-sys", "num-bigint", "num-rational", "num-traits", diff --git a/Cargo.toml b/Cargo.toml index 510a917..4a06971 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ wasm-bindgen = "0.2.74" # Only for WebAssembly - include here for syntax highlighting #[target.'cfg(target_arch = "wasm32")'.dependencies] console_error_panic_hook = "0.1.6" +js-sys = "0.3.51" num-bigint = "0.4.0" num-rational = "0.4.0" paste = "1.0.5" diff --git a/html/index.js b/html/index.js index da2c09b..e159d17 100644 --- a/html/index.js +++ b/html/index.js @@ -25,13 +25,16 @@ function clickAdvancedOptions() { } } -console.log = function(v) { - document.getElementById('resultLogs1').append(v); - document.getElementById('resultLogs1').append("\n"); -}; - var wasm = wasm_bindgen; +var tblResult = document.getElementById('result'); + +function updateResultTable(result) { + for (let i = 0; i < result.length; i++) { + tblResult.rows[i].insertAdjacentHTML('beforeend', result[i]); + } +} + async function clickCount() { if (document.getElementById('bltFile').files.length === 0) { return; @@ -46,10 +49,12 @@ async function clickCount() { // Init election let election = wasm.election_from_blt_Rational(electionData); - let state = wasm.CountStateRational.new(election); + + // Init results table + tblResult.innerHTML = wasm.init_results_table_Rational(election); // Init STV options - let stv_opts = wasm.STVOptions.new( + let opts = wasm.STVOptions.new( document.getElementById('chkRoundTVs').checked ? parseInt(document.getElementById('txtRoundTVs').value) : null, document.getElementById('chkRoundWeights').checked ? parseInt(document.getElementById('txtRoundWeights').value) : null, document.getElementById('chkRoundVotes').checked ? parseInt(document.getElementById('txtRoundVotes').value) : null, @@ -64,14 +69,15 @@ async function clickCount() { ); // Step election - wasm.count_init_Rational(state, stv_opts); - wasm.make_and_print_result_Rational(1, state); + let state = wasm.CountStateRational.new(election); + wasm.count_init_Rational(state, opts); + updateResultTable(wasm.update_results_table_Rational(1, state, opts)); - for (let stage_num = 2;; stage_num++) { - let is_done = wasm.count_one_stage_Rational(state, stv_opts); - if (is_done) { + for (let stageNum = 2;; stageNum++) { + let isDone = wasm.count_one_stage_Rational(state, opts); + if (isDone) { break; } - wasm.make_and_print_result_Rational(stage_num, state); + updateResultTable(wasm.update_results_table_Rational(stageNum, state, opts)); } } diff --git a/html/main.css b/html/main.css index be1024f..df9353f 100644 --- a/html/main.css +++ b/html/main.css @@ -83,9 +83,6 @@ td.count sup { tr.stage-no td, tr.stage-kind td, tr.stage-comment td { text-align: center; } -tr.stage-no td:not(:first-child) { - border-top: 1px solid #76858c; -} tr.stage-kind td:not(:first-child) { font-size: 0.75em; min-width: 5rem; @@ -109,10 +106,10 @@ tr.info td { color-adjust: exact; -webkit-print-color-adjust: exact; } -td.bt { +tr.stage-no td:not(:first-child), tr.transfers td { border-top: 1px solid #76858c; } -td.bb { +tr.info:last-child td { border-bottom: 1px solid #76858c; } diff --git a/src/stv/wasm.rs b/src/stv/wasm.rs index 7f7b2dd..396c6fe 100644 --- a/src/stv/wasm.rs +++ b/src/stv/wasm.rs @@ -21,6 +21,7 @@ use crate::stv; extern crate console_error_panic_hook; +use js_sys::Array; use wasm_bindgen::prelude::wasm_bindgen; // Logging @@ -70,6 +71,18 @@ macro_rules! impl_type { #[wasm_bindgen] #[allow(non_snake_case)] + pub fn [](election: &[]) -> String { + return init_results_table(&election.0); + } + + #[wasm_bindgen] + #[allow(non_snake_case)] + pub fn [](stage_num: usize, state: &[], opts: &stv::STVOptions) -> Array { + return update_results_table(stage_num, &state.0, opts); + } + + /*#[wasm_bindgen] + #[allow(non_snake_case)] pub fn [](stage_num: usize, state: &[]) { let result = StageResult { kind: state.0.kind, @@ -78,7 +91,7 @@ macro_rules! impl_type { state: CountStateOrRef::from(&state.0), }; print_stage(stage_num, &result); - } + }*/ // Wrapper structs // Required as we cannot specify &'static in wasm-bindgen: issue #1187 @@ -114,7 +127,47 @@ impl_type!(NativeFloat64); // Reporting -fn print_candidates<'a, N: 'a + Number, I: Iterator)>>(candidates: I) { +fn init_results_table(election: &Election) -> String { + let mut result = String::from(r#""#); + for candidate in election.candidates.iter() { + result.push_str(&format!(r#"{}"#, candidate.name)); + } + result.push_str(r#"ExhaustedLoss by fractionTotalQuota"#); + return result; +} + +fn update_results_table(stage_num: usize, state: &CountState, opts: &stv::STVOptions) -> Array { + let result = Array::new(); + result.push(&format!(r#"{}"#, stage_num).into()); + result.push(&format!(r#"{}"#, state.kind.unwrap_or("")).into()); + result.push(&format!(r#"{}"#, state.title).into()); + 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()); + } 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#"{:.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()); + + // 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#"{:.dps$}"#, state.quota, dps=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); @@ -153,4 +206,4 @@ fn print_stage(stage_num: usize, result: &StageResult) { cprintln!("Quota: {:.dps$}", state.quota, dps=2); cprintln!(""); -} +}*/