Further work on HTML output

This commit is contained in:
RunasSudo 2021-06-03 15:47:19 +10:00
parent 37622eb78d
commit 3bb538e99e
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
3 changed files with 111 additions and 56 deletions

View File

@ -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 = '<p>Stage comments:</p>';
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));
}

View File

@ -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;
}

View File

@ -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 [<make_and_print_result_$type>](stage_num: usize, state: &[<CountState$type>]) {
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 [<update_stage_comments_$type>](state: &[<CountState$type>]) -> String {
return update_stage_comments(&state.0);
}
#[wasm_bindgen]
#[allow(non_snake_case)]
pub fn [<finalise_results_table_$type>](state: &[<CountState$type>]) -> Array {
return finalise_results_table(&state.0);
}
#[wasm_bindgen]
#[allow(non_snake_case)]
pub fn [<final_result_summary_$type>](state: &[<CountState$type>]) -> 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<N: Number>(election: &Election<N>) -> String {
let mut result = String::from(r#"<tr class="stage-no"><td></td></tr><tr class="stage-kind"><td></td></tr><tr class="stage-comment"><td></td></tr>"#);
let mut result = String::from(r#"<tr class="stage-no"><td rowspan="3"></td></tr><tr class="stage-kind"></tr><tr class="stage-comment"></tr>"#);
for candidate in election.candidates.iter() {
result.push_str(&format!(r#"<tr class="candidate transfers"><td rowspan="2">{}</td></tr><tr class="candidate votes"></tr>"#, candidate.name));
}
@ -144,66 +150,97 @@ fn update_results_table<N: Number>(stage_num: usize, state: &CountState<N>, 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#"<td class="elected">{:.dps$}</td>"#, count_card.transfers, dps=opts.pp_decimals).into());
result.push(&format!(r#"<td class="elected">{:.dps$}</td>"#, count_card.votes, dps=opts.pp_decimals).into());
result.push(&format!(r#"<td class="count elected">{}</td>"#, pp(&count_card.transfers, opts.pp_decimals)).into());
result.push(&format!(r#"<td class="count elected">{}</td>"#, pp(&count_card.votes, opts.pp_decimals)).into());
} else if count_card.state == stv::CandidateState::EXCLUDED {
result.push(&format!(r#"<td class="count excluded">{}</td>"#, pp(&count_card.transfers, opts.pp_decimals)).into());
if count_card.votes.is_zero() {
result.push(&r#"<td class="count excluded">Ex</td>"#.into());
} else {
result.push(&format!(r#"<td class="count excluded">{}</td>"#, pp(&count_card.votes, opts.pp_decimals)).into());
}
} else {
result.push(&format!(r#"<td>{:.dps$}</td>"#, count_card.transfers, dps=opts.pp_decimals).into());
result.push(&format!(r#"<td>{:.dps$}</td>"#, count_card.votes, dps=opts.pp_decimals).into());
result.push(&format!(r#"<td class="count">{}</td>"#, pp(&count_card.transfers, opts.pp_decimals)).into());
result.push(&format!(r#"<td class="count">{}</td>"#, pp(&count_card.votes, opts.pp_decimals)).into());
}
}
result.push(&format!(r#"<td>{:.dps$}</td>"#, state.exhausted.transfers, dps=opts.pp_decimals).into());
result.push(&format!(r#"<td>{:.dps$}</td>"#, state.exhausted.votes, dps=opts.pp_decimals).into());
result.push(&format!(r#"<td>{:.dps$}</td>"#, state.loss_fraction.transfers, dps=opts.pp_decimals).into());
result.push(&format!(r#"<td>{:.dps$}</td>"#, state.loss_fraction.votes, dps=opts.pp_decimals).into());
result.push(&format!(r#"<td class="count">{}</td>"#, pp(&state.exhausted.transfers, opts.pp_decimals)).into());
result.push(&format!(r#"<td class="count">{}</td>"#, pp(&state.exhausted.votes, opts.pp_decimals)).into());
result.push(&format!(r#"<td class="count">{}</td>"#, pp(&state.loss_fraction.transfers, opts.pp_decimals)).into());
result.push(&format!(r#"<td class="count">{}</td>"#, 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#"<td>{:.dps$}</td>"#, total_vote, dps=opts.pp_decimals).into());
result.push(&format!(r#"<td class="count">{}</td>"#, pp(&total_vote, opts.pp_decimals)).into());
result.push(&format!(r#"<td>{:.dps$}</td>"#, state.quota, dps=opts.pp_decimals).into());
result.push(&format!(r#"<td class="count">{}</td>"#, pp(&state.quota, opts.pp_decimals)).into());
return result;
}
/*fn print_candidates<'a, N: 'a + Number, I: Iterator<Item=(&'a Candidate, &'a CountCard<'a, N>)>>(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<N: Number>(state: &CountState<N>) -> String {
return state.logger.render().join(" ");
}
fn print_stage<N: Number>(stage_num: usize, result: &StageResult<N>) {
// 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<N: Number>(state: &CountState<N>) -> Array {
let result = Array::new();
let state = result.state.as_ref();
// Header rows
result.push(&r#"<td rowspan="3"></td>"#.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#"<td rowspan="2" class="bb elected">ELECTED {}</td>"#, count_card.order_elected).into());
} else if count_card.state == stv::CandidateState::EXCLUDED {
result.push(&format!(r#"<td rowspan="2" class="bb excluded">Excluded {}</td>"#, -count_card.order_elected).into());
} else {
result.push(&r#"<td rowspan="2" class="bb"></td>"#.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<N: Number>(state: &CountState<N>) -> String {
let mut result = String::from("<p>Count complete. The winning candidates are, in order of election:</p><ol>");
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!("<li>{}</li>", winner.name));
}
cprintln!("");
}*/
result.push_str("</ol>");
return result;
}
fn pp<N: Number>(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(".", ".<sup>", 1);
raw.push_str("</sup>");
}
if raw.starts_with('-') {
raw = raw.replacen("-", "&minus;", 1);
}
return raw;
}