Implement constraints in web UI

This commit is contained in:
RunasSudo 2021-06-27 22:09:34 +10:00
parent 38eef74e77
commit 1d4036c19e
No known key found for this signature in database
GPG Key ID: 7234E476BF21C61A
4 changed files with 58 additions and 23 deletions

View File

@ -139,12 +139,12 @@
<input type="text" id="txtSeed" value="">
</label>
</div>
<!--<div class="subheading">
<div class="subheading">
Constraints:
</div>
<div>
<input type="file" id="conFile">
</div>-->
</div>
</div>
<div class="col-6 cols-12" style="align-self: start;">
<div class="col-12 subheading">

View File

@ -90,11 +90,22 @@ async function clickCount() {
}
// Read BLT file
let filePath = document.getElementById('bltFile').value;
filePath = filePath.substring(Math.max(filePath.lastIndexOf('\\'), filePath.lastIndexOf('/')) + 1);
let bltPath = document.getElementById('bltFile').value;
bltPath = bltPath.substring(Math.max(bltPath.lastIndexOf('\\'), bltPath.lastIndexOf('/')) + 1);
let bltFile = document.getElementById('bltFile').files[0];
let electionData = await bltFile.text();
let bltData = await bltFile.text();
// Read CON file (if applicable)
let conPath = null;
let conData = null;
if (document.getElementById('conFile').files.length > 0) {
conPath = document.getElementById('conFile').value;
conPath = conPath.substring(Math.max(conPath.lastIndexOf('\\'), conPath.lastIndexOf('/')) + 1);
let conFile = document.getElementById('conFile').files[0];
conData = await conFile.text();
}
// Init STV options
let optsStr = [
@ -127,9 +138,11 @@ async function clickCount() {
// Dispatch to worker
worker.postMessage({
'type': 'countElection',
'electionData': electionData,
'bltData': bltData,
'conData': conData,
'optsStr': optsStr,
'filePath': filePath,
'bltPath': bltPath,
'conPath': conPath,
'numbers': document.getElementById('selNumbers').value,
'decimals': document.getElementById('txtDP').value,
'normaliseBallots': document.getElementById('chkNormaliseBallots').checked,

View File

@ -27,12 +27,17 @@ onmessage = function(evt) {
}
// Init election
election = wasm['election_from_blt_' + numbers](evt.data.electionData);
election = wasm['election_from_blt_' + numbers](evt.data.bltData);
if (evt.data.normaliseBallots) {
wasm['election_normalise_ballots_' + numbers](election);
}
// Init constraints if applicable
if (evt.data.conData) {
wasm['election_load_constraints_' + numbers](election, evt.data.conData);
}
// Init STV options
opts = wasm.STVOptions.new.apply(null, evt.data.optsStr);
@ -40,7 +45,7 @@ onmessage = function(evt) {
opts.validate();
// Describe count
postMessage({'type': 'describeCount', 'content': wasm['describe_count_' + numbers](evt.data.filePath, election, opts)});
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)});

View File

@ -17,6 +17,7 @@
#![allow(rustdoc::private_intra_doc_links)]
use crate::constraints::Constraints;
use crate::election::{CandidateState, CountState, Election};
use crate::numbers::{Fixed, GuardedFixed, NativeFloat64, Number, Rational};
use crate::stv;
@ -66,6 +67,13 @@ macro_rules! impl_type {
election.0.normalise_ballots();
}
/// Call [Constraints::from_con] and set [Election::constraints]
#[wasm_bindgen]
#[allow(non_snake_case)]
pub fn [<election_load_constraints_$type>](election: &mut [<Election$type>], text: String) {
election.0.constraints = Some(Constraints::from_con(text.lines().map(|s| s.to_string()).into_iter()));
}
/// Wrapper for [stv::count_init]
#[wasm_bindgen]
#[allow(non_snake_case)]
@ -303,22 +311,31 @@ fn update_results_table<N: Number>(stage_num: usize, state: &CountState<N>, opts
result.push(&format!(r#"<td{}>{}</td>"#, tdclasses1, 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#"<td class="{}count elected">{}</td>"#, tdclasses2, pp(&count_card.transfers, opts.pp_decimals)).into());
result.push(&format!(r#"<td class="{}count elected">{}</td>"#, tdclasses2, 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>"#, tdclasses2, pp(&count_card.transfers, opts.pp_decimals)).into());
if count_card.votes.is_zero() {
result.push(&format!(r#"<td class="{}count excluded">Ex</td>"#, tdclasses2).into());
} else {
match count_card.state {
CandidateState::Hopeful | CandidateState::Guarded => {
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(&count_card.transfers, opts.pp_decimals)).into());
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(&count_card.votes, opts.pp_decimals)).into());
}
CandidateState::Elected => {
result.push(&format!(r#"<td class="{}count elected">{}</td>"#, tdclasses2, pp(&count_card.transfers, opts.pp_decimals)).into());
result.push(&format!(r#"<td class="{}count elected">{}</td>"#, tdclasses2, pp(&count_card.votes, opts.pp_decimals)).into());
}
CandidateState::Doomed => {
result.push(&format!(r#"<td class="{}count excluded">{}</td>"#, tdclasses2, pp(&count_card.transfers, opts.pp_decimals)).into());
result.push(&format!(r#"<td class="{}count excluded">{}</td>"#, tdclasses2, pp(&count_card.votes, opts.pp_decimals)).into());
}
} else if count_card.state == stv::CandidateState::Withdrawn {
result.push(&format!(r#"<td class="{}count excluded"></td>"#, tdclasses2).into());
result.push(&format!(r#"<td class="{}count excluded">WD</td>"#, tdclasses2).into());
} else {
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(&count_card.transfers, opts.pp_decimals)).into());
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(&count_card.votes, opts.pp_decimals)).into());
CandidateState::Withdrawn => {
result.push(&format!(r#"<td class="{}count excluded"></td>"#, tdclasses2).into());
result.push(&format!(r#"<td class="{}count excluded">WD</td>"#, tdclasses2).into());
}
CandidateState::Excluded => {
result.push(&format!(r#"<td class="{}count excluded">{}</td>"#, tdclasses2, pp(&count_card.transfers, opts.pp_decimals)).into());
if count_card.votes.is_zero() {
result.push(&format!(r#"<td class="{}count excluded">Ex</td>"#, tdclasses2).into());
} else {
result.push(&format!(r#"<td class="{}count excluded">{}</td>"#, tdclasses2, pp(&count_card.votes, opts.pp_decimals)).into());
}
}
}
}
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(&state.exhausted.transfers, opts.pp_decimals)).into());