Describe election count/options in web UI
This commit is contained in:
parent
3bb538e99e
commit
ba8d9bf79c
@ -80,6 +80,11 @@ async function clickCount() {
|
||||
parseInt(document.getElementById('txtPPDP').value),
|
||||
);
|
||||
|
||||
// Describe count
|
||||
let filePath = document.getElementById('bltFile').value;
|
||||
filePath = filePath.substring(Math.max(filePath.lastIndexOf('\\'), filePath.lastIndexOf('/')) + 1);
|
||||
document.getElementById('resultLogs1').innerHTML = wasm.describe_count_Rational(filePath, election, opts);
|
||||
|
||||
// Step election
|
||||
let state = wasm.CountStateRational.new(election);
|
||||
wasm.count_init_Rational(state, opts);
|
||||
|
@ -71,7 +71,7 @@ table {
|
||||
}
|
||||
.result td {
|
||||
padding: 0px 8px;
|
||||
min-height: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
td.count {
|
||||
text-align: right;
|
||||
|
@ -21,6 +21,7 @@ use crate::numbers::Number;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct Election<N> {
|
||||
pub name: String,
|
||||
pub seats: usize,
|
||||
pub candidates: Vec<Candidate>,
|
||||
pub ballots: Vec<Ballot<N>>,
|
||||
@ -36,6 +37,7 @@ impl<N: Number> Election<N> {
|
||||
|
||||
// Initialise the Election object
|
||||
let mut election = Election {
|
||||
name: String::new(),
|
||||
seats: seats,
|
||||
candidates: Vec::with_capacity(num_candidates),
|
||||
ballots: Vec::new(),
|
||||
@ -66,7 +68,7 @@ impl<N: Number> Election<N> {
|
||||
}
|
||||
|
||||
// Read candidates
|
||||
for line in lines.take(num_candidates) {
|
||||
for line in lines.by_ref().take(num_candidates) {
|
||||
let mut line = &line[..];
|
||||
if line.starts_with("\"") && line.ends_with("\"") {
|
||||
line = &line[1..line.len()-1];
|
||||
@ -75,6 +77,14 @@ impl<N: Number> Election<N> {
|
||||
election.candidates.push(Candidate { name: line.to_string() });
|
||||
}
|
||||
|
||||
// Read name
|
||||
let line = lines.next().expect("Syntax Error");
|
||||
let mut line = &line[..];
|
||||
if line.starts_with("\"") && line.ends_with("\"") {
|
||||
line = &line[1..line.len()-1];
|
||||
}
|
||||
election.name.push_str(line);
|
||||
|
||||
return election;
|
||||
}
|
||||
}
|
||||
|
@ -19,3 +19,7 @@ pub mod election;
|
||||
pub mod logger;
|
||||
pub mod numbers;
|
||||
pub mod stv;
|
||||
|
||||
use git_version::git_version;
|
||||
|
||||
pub const VERSION: &str = git_version!(args=["--always", "--dirty=-dev"], fallback="unknown");
|
||||
|
@ -20,17 +20,14 @@ use opentally::election::{Candidate, CandidateState, CountCard, CountState, Coun
|
||||
use opentally::numbers::{NativeFloat64, Number, Rational};
|
||||
|
||||
use clap::{AppSettings, Clap};
|
||||
use git_version::git_version;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufRead};
|
||||
use std::ops;
|
||||
|
||||
const VERSION: &str = git_version!(args=["--always", "--dirty=-dev"], fallback="unknown");
|
||||
|
||||
/// Open-source election vote counting
|
||||
#[derive(Clap)]
|
||||
#[clap(name="OpenTally", version=VERSION)]
|
||||
#[clap(name="OpenTally", version=opentally::VERSION)]
|
||||
struct Opts {
|
||||
#[clap(subcommand)]
|
||||
command: Command,
|
||||
|
@ -45,6 +45,8 @@ where
|
||||
{
|
||||
fn new() -> Self;
|
||||
|
||||
fn describe() -> String;
|
||||
|
||||
fn pow_assign(&mut self, exponent: i32);
|
||||
fn floor_mut(&mut self, dps: usize);
|
||||
fn ceil_mut(&mut self, dps: usize);
|
||||
|
@ -31,6 +31,8 @@ pub struct NativeFloat64(ImplType);
|
||||
impl Number for NativeFloat64 {
|
||||
fn new() -> Self { Self(0.0) }
|
||||
|
||||
fn describe() -> String { "--numbers float64".to_string() }
|
||||
|
||||
fn pow_assign(&mut self, exponent: i32) {
|
||||
self.0 = self.0.powi(exponent);
|
||||
}
|
||||
|
@ -30,6 +30,8 @@ pub struct Rational(BigRational);
|
||||
impl Number for Rational {
|
||||
fn new() -> Self { Self(BigRational::zero()) }
|
||||
|
||||
fn describe() -> String { "--numbers rational".to_string() }
|
||||
|
||||
fn pow_assign(&mut self, exponent: i32) {
|
||||
self.0 = self.0.pow(exponent);
|
||||
}
|
||||
|
@ -30,6 +30,8 @@ pub struct Rational(rug::Rational);
|
||||
impl Number for Rational {
|
||||
fn new() -> Self { Self(rug::Rational::new()) }
|
||||
|
||||
fn describe() -> String { "--numbers rational".to_string() }
|
||||
|
||||
fn pow_assign(&mut self, exponent: i32) {
|
||||
self.0.pow_assign(exponent);
|
||||
}
|
||||
|
@ -99,8 +99,29 @@ impl STVOptions {
|
||||
}
|
||||
}
|
||||
|
||||
impl STVOptions {
|
||||
// Not exported to WebAssembly
|
||||
pub fn describe<N: Number>(&self) -> String {
|
||||
let mut flags = Vec::new();
|
||||
flags.push(N::describe());
|
||||
if let Some(dps) = self.round_tvs { flags.push(format!("--round-tvs {}", dps)); }
|
||||
if let Some(dps) = self.round_weights { flags.push(format!("--round-weights {}", dps)); }
|
||||
if let Some(dps) = self.round_votes { flags.push(format!("--round-votes {}", dps)); }
|
||||
if let Some(dps) = self.round_quota { flags.push(format!("--round-quota {}", dps)); }
|
||||
if self.quota != QuotaType::Droop { flags.push(self.quota.describe()); }
|
||||
if self.quota_criterion != QuotaCriterion::GreaterOrEqual { flags.push(self.quota_criterion.describe()); }
|
||||
if self.surplus != SurplusMethod::WIG { flags.push(self.surplus.describe()); }
|
||||
if self.surplus_order != SurplusOrder::BySize { flags.push(self.surplus_order.describe()); }
|
||||
if self.transferable_only { flags.push("--transferable-only".to_string()); }
|
||||
if self.exclusion != ExclusionMethod::SingleStage { flags.push(self.exclusion.describe()); }
|
||||
if self.pp_decimals != 2 { flags.push(format!("--pp-decimals {}", self.pp_decimals)); }
|
||||
return flags.join(" ");
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(PartialEq)]
|
||||
pub enum QuotaType {
|
||||
Droop,
|
||||
Hare,
|
||||
@ -108,15 +129,37 @@ pub enum QuotaType {
|
||||
HareExact,
|
||||
}
|
||||
|
||||
impl QuotaType {
|
||||
fn describe(self) -> String {
|
||||
match self {
|
||||
QuotaType::Droop => "--quota droop",
|
||||
QuotaType::Hare => "--quota hare",
|
||||
QuotaType::DroopExact => "--quota droop_exact",
|
||||
QuotaType::HareExact => "--quota hare_exact",
|
||||
}.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(PartialEq)]
|
||||
pub enum QuotaCriterion {
|
||||
GreaterOrEqual,
|
||||
Greater,
|
||||
}
|
||||
|
||||
impl QuotaCriterion {
|
||||
fn describe(self) -> String {
|
||||
match self {
|
||||
QuotaCriterion::GreaterOrEqual => "--quota-criterion geq",
|
||||
QuotaCriterion::Greater => "--quota-criterion gt",
|
||||
}.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(PartialEq)]
|
||||
pub enum SurplusMethod {
|
||||
WIG,
|
||||
UIG,
|
||||
@ -124,21 +167,53 @@ pub enum SurplusMethod {
|
||||
Meek,
|
||||
}
|
||||
|
||||
impl SurplusMethod {
|
||||
fn describe(self) -> String {
|
||||
match self {
|
||||
SurplusMethod::WIG => "--surplus wig",
|
||||
SurplusMethod::UIG => "--surplus uig",
|
||||
SurplusMethod::EG => "--surplus eg",
|
||||
SurplusMethod::Meek => "--surplus meek",
|
||||
}.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(PartialEq)]
|
||||
pub enum SurplusOrder {
|
||||
BySize,
|
||||
ByOrder,
|
||||
}
|
||||
|
||||
impl SurplusOrder {
|
||||
fn describe(self) -> String {
|
||||
match self {
|
||||
SurplusOrder::BySize => "--surplus-order by_size",
|
||||
SurplusOrder::ByOrder => "--surplus-order by_order",
|
||||
}.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(PartialEq)]
|
||||
pub enum ExclusionMethod {
|
||||
SingleStage,
|
||||
ByValue,
|
||||
ParcelsByOrder,
|
||||
}
|
||||
|
||||
impl ExclusionMethod {
|
||||
fn describe(self) -> String {
|
||||
match self {
|
||||
ExclusionMethod::SingleStage => "--exclusion single_stage",
|
||||
ExclusionMethod::ByValue => "--exclusion by_value",
|
||||
ExclusionMethod::ParcelsByOrder => "--exclusion parcels_by_value",
|
||||
}.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn count_init<N: Number>(mut state: &mut CountState<'_, N>, opts: &STVOptions) {
|
||||
distribute_first_preferences(&mut state);
|
||||
calculate_quota(&mut state, opts);
|
||||
|
@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use crate::election::{Candidate, CandidateState, CountCard, CountState, CountStateOrRef, Election, StageResult};
|
||||
use crate::election::{CandidateState, CountState, Election};
|
||||
use crate::numbers::{NativeFloat64, Number, Rational};
|
||||
use crate::stv;
|
||||
|
||||
@ -24,21 +24,6 @@ extern crate console_error_panic_hook;
|
||||
use js_sys::Array;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
|
||||
// Logging
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: &str);
|
||||
}
|
||||
/// println! to console
|
||||
macro_rules! cprintln {
|
||||
($($t:tt)*) => (
|
||||
#[allow(unused_unsafe)]
|
||||
unsafe { log(&format_args!($($t)*).to_string()) }
|
||||
)
|
||||
}
|
||||
|
||||
// Helper macros for making functions
|
||||
|
||||
macro_rules! impl_type {
|
||||
@ -75,6 +60,12 @@ macro_rules! impl_type {
|
||||
return init_results_table(&election.0);
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn [<describe_count_$type>](filename: String, election: &[<Election$type>], opts: &stv::STVOptions) -> String {
|
||||
return describe_count(filename, &election.0, opts);
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn [<update_results_table_$type>](stage_num: usize, state: &[<CountState$type>], opts: &stv::STVOptions) -> Array {
|
||||
@ -142,6 +133,14 @@ fn init_results_table<N: Number>(election: &Election<N>) -> String {
|
||||
return result;
|
||||
}
|
||||
|
||||
fn describe_count<N: Number>(filename: String, election: &Election<N>, opts: &stv::STVOptions) -> String {
|
||||
let mut result = String::from("<p>Count computed by OpenTally (revision ");
|
||||
result.push_str(crate::VERSION);
|
||||
let total_ballots = election.ballots.iter().fold(N::zero(), |acc, b| { acc + &b.orig_value });
|
||||
result.push_str(&format!(r#"). Read {} ballots from ‘{}’ for election ‘{}’. There are {} candidates for {} vacancies. Counting using options <span style="font-family: monospace;">{}</span>.</p>"#, total_ballots, filename, election.name, election.candidates.len(), election.seats, opts.describe::<N>()));
|
||||
return result;
|
||||
}
|
||||
|
||||
fn update_results_table<N: Number>(stage_num: usize, state: &CountState<N>, opts: &stv::STVOptions) -> Array {
|
||||
let result = Array::new();
|
||||
result.push(&format!(r#"<td>{}</td>"#, stage_num).into());
|
||||
|
Loading…
Reference in New Issue
Block a user