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),
|
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
|
// Step election
|
||||||
let state = wasm.CountStateRational.new(election);
|
let state = wasm.CountStateRational.new(election);
|
||||||
wasm.count_init_Rational(state, opts);
|
wasm.count_init_Rational(state, opts);
|
||||||
|
|
|
@ -71,7 +71,7 @@ table {
|
||||||
}
|
}
|
||||||
.result td {
|
.result td {
|
||||||
padding: 0px 8px;
|
padding: 0px 8px;
|
||||||
min-height: 1em;
|
height: 1em;
|
||||||
}
|
}
|
||||||
td.count {
|
td.count {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
|
|
@ -21,6 +21,7 @@ use crate::numbers::Number;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub struct Election<N> {
|
pub struct Election<N> {
|
||||||
|
pub name: String,
|
||||||
pub seats: usize,
|
pub seats: usize,
|
||||||
pub candidates: Vec<Candidate>,
|
pub candidates: Vec<Candidate>,
|
||||||
pub ballots: Vec<Ballot<N>>,
|
pub ballots: Vec<Ballot<N>>,
|
||||||
|
@ -36,6 +37,7 @@ impl<N: Number> Election<N> {
|
||||||
|
|
||||||
// Initialise the Election object
|
// Initialise the Election object
|
||||||
let mut election = Election {
|
let mut election = Election {
|
||||||
|
name: String::new(),
|
||||||
seats: seats,
|
seats: seats,
|
||||||
candidates: Vec::with_capacity(num_candidates),
|
candidates: Vec::with_capacity(num_candidates),
|
||||||
ballots: Vec::new(),
|
ballots: Vec::new(),
|
||||||
|
@ -66,7 +68,7 @@ impl<N: Number> Election<N> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read candidates
|
// Read candidates
|
||||||
for line in lines.take(num_candidates) {
|
for line in lines.by_ref().take(num_candidates) {
|
||||||
let mut line = &line[..];
|
let mut line = &line[..];
|
||||||
if line.starts_with("\"") && line.ends_with("\"") {
|
if line.starts_with("\"") && line.ends_with("\"") {
|
||||||
line = &line[1..line.len()-1];
|
line = &line[1..line.len()-1];
|
||||||
|
@ -75,6 +77,14 @@ impl<N: Number> Election<N> {
|
||||||
election.candidates.push(Candidate { name: line.to_string() });
|
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;
|
return election;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,3 +19,7 @@ pub mod election;
|
||||||
pub mod logger;
|
pub mod logger;
|
||||||
pub mod numbers;
|
pub mod numbers;
|
||||||
pub mod stv;
|
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 opentally::numbers::{NativeFloat64, Number, Rational};
|
||||||
|
|
||||||
use clap::{AppSettings, Clap};
|
use clap::{AppSettings, Clap};
|
||||||
use git_version::git_version;
|
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, BufRead};
|
use std::io::{self, BufRead};
|
||||||
use std::ops;
|
use std::ops;
|
||||||
|
|
||||||
const VERSION: &str = git_version!(args=["--always", "--dirty=-dev"], fallback="unknown");
|
|
||||||
|
|
||||||
/// Open-source election vote counting
|
/// Open-source election vote counting
|
||||||
#[derive(Clap)]
|
#[derive(Clap)]
|
||||||
#[clap(name="OpenTally", version=VERSION)]
|
#[clap(name="OpenTally", version=opentally::VERSION)]
|
||||||
struct Opts {
|
struct Opts {
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
command: Command,
|
command: Command,
|
||||||
|
|
|
@ -45,6 +45,8 @@ where
|
||||||
{
|
{
|
||||||
fn new() -> Self;
|
fn new() -> Self;
|
||||||
|
|
||||||
|
fn describe() -> String;
|
||||||
|
|
||||||
fn pow_assign(&mut self, exponent: i32);
|
fn pow_assign(&mut self, exponent: i32);
|
||||||
fn floor_mut(&mut self, dps: usize);
|
fn floor_mut(&mut self, dps: usize);
|
||||||
fn ceil_mut(&mut self, dps: usize);
|
fn ceil_mut(&mut self, dps: usize);
|
||||||
|
|
|
@ -31,6 +31,8 @@ pub struct NativeFloat64(ImplType);
|
||||||
impl Number for NativeFloat64 {
|
impl Number for NativeFloat64 {
|
||||||
fn new() -> Self { Self(0.0) }
|
fn new() -> Self { Self(0.0) }
|
||||||
|
|
||||||
|
fn describe() -> String { "--numbers float64".to_string() }
|
||||||
|
|
||||||
fn pow_assign(&mut self, exponent: i32) {
|
fn pow_assign(&mut self, exponent: i32) {
|
||||||
self.0 = self.0.powi(exponent);
|
self.0 = self.0.powi(exponent);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,8 @@ pub struct Rational(BigRational);
|
||||||
impl Number for Rational {
|
impl Number for Rational {
|
||||||
fn new() -> Self { Self(BigRational::zero()) }
|
fn new() -> Self { Self(BigRational::zero()) }
|
||||||
|
|
||||||
|
fn describe() -> String { "--numbers rational".to_string() }
|
||||||
|
|
||||||
fn pow_assign(&mut self, exponent: i32) {
|
fn pow_assign(&mut self, exponent: i32) {
|
||||||
self.0 = self.0.pow(exponent);
|
self.0 = self.0.pow(exponent);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,8 @@ pub struct Rational(rug::Rational);
|
||||||
impl Number for Rational {
|
impl Number for Rational {
|
||||||
fn new() -> Self { Self(rug::Rational::new()) }
|
fn new() -> Self { Self(rug::Rational::new()) }
|
||||||
|
|
||||||
|
fn describe() -> String { "--numbers rational".to_string() }
|
||||||
|
|
||||||
fn pow_assign(&mut self, exponent: i32) {
|
fn pow_assign(&mut self, exponent: i32) {
|
||||||
self.0.pow_assign(exponent);
|
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]
|
#[wasm_bindgen]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
#[derive(PartialEq)]
|
||||||
pub enum QuotaType {
|
pub enum QuotaType {
|
||||||
Droop,
|
Droop,
|
||||||
Hare,
|
Hare,
|
||||||
|
@ -108,15 +129,37 @@ pub enum QuotaType {
|
||||||
HareExact,
|
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]
|
#[wasm_bindgen]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
#[derive(PartialEq)]
|
||||||
pub enum QuotaCriterion {
|
pub enum QuotaCriterion {
|
||||||
GreaterOrEqual,
|
GreaterOrEqual,
|
||||||
Greater,
|
Greater,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl QuotaCriterion {
|
||||||
|
fn describe(self) -> String {
|
||||||
|
match self {
|
||||||
|
QuotaCriterion::GreaterOrEqual => "--quota-criterion geq",
|
||||||
|
QuotaCriterion::Greater => "--quota-criterion gt",
|
||||||
|
}.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
#[derive(PartialEq)]
|
||||||
pub enum SurplusMethod {
|
pub enum SurplusMethod {
|
||||||
WIG,
|
WIG,
|
||||||
UIG,
|
UIG,
|
||||||
|
@ -124,21 +167,53 @@ pub enum SurplusMethod {
|
||||||
Meek,
|
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]
|
#[wasm_bindgen]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
#[derive(PartialEq)]
|
||||||
pub enum SurplusOrder {
|
pub enum SurplusOrder {
|
||||||
BySize,
|
BySize,
|
||||||
ByOrder,
|
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]
|
#[wasm_bindgen]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
#[derive(PartialEq)]
|
||||||
pub enum ExclusionMethod {
|
pub enum ExclusionMethod {
|
||||||
SingleStage,
|
SingleStage,
|
||||||
ByValue,
|
ByValue,
|
||||||
ParcelsByOrder,
|
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) {
|
pub fn count_init<N: Number>(mut state: &mut CountState<'_, N>, opts: &STVOptions) {
|
||||||
distribute_first_preferences(&mut state);
|
distribute_first_preferences(&mut state);
|
||||||
calculate_quota(&mut state, opts);
|
calculate_quota(&mut state, opts);
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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::numbers::{NativeFloat64, Number, Rational};
|
||||||
use crate::stv;
|
use crate::stv;
|
||||||
|
|
||||||
|
@ -24,21 +24,6 @@ extern crate console_error_panic_hook;
|
||||||
use js_sys::Array;
|
use js_sys::Array;
|
||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
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
|
// Helper macros for making functions
|
||||||
|
|
||||||
macro_rules! impl_type {
|
macro_rules! impl_type {
|
||||||
|
@ -75,6 +60,12 @@ macro_rules! impl_type {
|
||||||
return init_results_table(&election.0);
|
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]
|
#[wasm_bindgen]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn [<update_results_table_$type>](stage_num: usize, state: &[<CountState$type>], opts: &stv::STVOptions) -> Array {
|
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;
|
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 {
|
fn update_results_table<N: Number>(stage_num: usize, state: &CountState<N>, opts: &stv::STVOptions) -> Array {
|
||||||
let result = Array::new();
|
let result = Array::new();
|
||||||
result.push(&format!(r#"<td>{}</td>"#, stage_num).into());
|
result.push(&format!(r#"<td>{}</td>"#, stage_num).into());
|
||||||
|
|
Loading…
Reference in New Issue