Display up to 5 names only in web UI header, separate with line breaks

This commit is contained in:
RunasSudo 2021-09-06 02:43:33 +10:00
parent 18c974117e
commit e4bfe45f49
No known key found for this signature in database
GPG Key ID: 7234E476BF21C61A
9 changed files with 106 additions and 61 deletions

View File

@ -361,10 +361,7 @@ where
fn print_stage<N: Number>(stage_num: usize, state: &CountState<N>, opts: &STVOptions) {
// Print stage details
match state.kind {
None => { println!("{}. {}", stage_num, state.title); }
Some(kind) => { println!("{}. {} {}", stage_num, kind, state.title); }
};
println!("{}. {}", stage_num, state.title);
println!("{}", state.logger.render().join(" "));
// Print candidates

View File

@ -30,6 +30,7 @@ use crate::numbers::{SerializedNumber, SerializedOptionNumber};
use std::cmp::max;
use std::collections::HashMap;
use std::fmt;
/// An election to be counted
#[derive(Clone)]
@ -134,12 +135,8 @@ pub struct CountState<'a, N: Number> {
/// [ConstraintMatrix] for constrained elections
pub constraint_matrix: Option<ConstraintMatrix>,
/// The type of stage being counted
///
/// For example, "Surplus of", "Exclusion of"
pub kind: Option<&'a str>,
/// The description of the stage being counted, excluding [CountState::kind]
pub title: String,
/// The type of stage being counted, etc.
pub title: StageKind<'a>,
/// [Logger] for this stage of the count
pub logger: Logger<'a>,
}
@ -161,8 +158,7 @@ impl<'a, N: Number> CountState<'a, N> {
num_elected: 0,
num_excluded: 0,
constraint_matrix: None,
kind: None,
title: String::new(),
title: StageKind::FirstPreferences,
logger: Logger { entries: Vec::new() },
};
@ -293,6 +289,56 @@ impl<'a, N: Number> CountState<'a, N> {
}
}
/// The kind, title, etc. of the stage being counted
#[derive(Clone)]
pub enum StageKind<'a> {
/// First preferences
FirstPreferences,
/// Surplus of ...
SurplusOf(&'a Candidate),
/// Exclusion of ...
ExclusionOf(Vec<&'a Candidate>),
/// Surpluses distributed (Meek)
SurplusesDistributed,
/// Bulk election
BulkElection,
}
impl<'a> StageKind<'a> {
/// Return the "kind" portion of the title
pub fn kind_as_string(&self) -> &'static str {
return match self {
StageKind::FirstPreferences => "",
StageKind::SurplusOf(_) => "Surplus of",
StageKind::ExclusionOf(_) => "Exclusion of",
StageKind::SurplusesDistributed => "",
StageKind::BulkElection => "",
};
}
}
impl<'a> fmt::Display for StageKind<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StageKind::FirstPreferences => {
return f.write_str("First preferences");
}
StageKind::SurplusOf(candidate) => {
return f.write_fmt(format_args!("{} {}", self.kind_as_string(), candidate.name));
}
StageKind::ExclusionOf(candidates) => {
return f.write_fmt(format_args!("{} {}", self.kind_as_string(), candidates.iter().map(|c| &c.name).sorted().join(", ")));
}
StageKind::SurplusesDistributed => {
return f.write_str("Surpluses distributed");
}
StageKind::BulkElection => {
return f.write_str("Bulk election");
}
}
}
}
/// Current state of a [Candidate] during an election count
#[derive(Clone)]
pub struct CountCard<'a, N> {

View File

@ -19,7 +19,7 @@ use super::{ExclusionMethod, NextPreferencesEntry, STVError, STVOptions, SumSurp
use super::sample;
use crate::constraints;
use crate::election::{Candidate, CandidateState, CountState, Parcel, Vote};
use crate::election::{Candidate, CandidateState, CountState, Parcel, StageKind, Vote};
use crate::numbers::Number;
use crate::ties;
@ -78,8 +78,7 @@ where
state.loss_fraction.transfers = lbf;
}
state.kind = None;
state.title = "First preferences".to_string();
state.title = StageKind::FirstPreferences;
state.logger.log_literal("First preferences distributed.".to_string());
}
@ -264,7 +263,7 @@ where
}
/// Distribute the surplus of a given candidate according to the Gregory method, based on [STVOptions::surplus]
fn distribute_surplus<N: Number>(state: &mut CountState<N>, opts: &STVOptions, elected_candidate: &Candidate)
fn distribute_surplus<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions, elected_candidate: &'a Candidate)
where
for<'r> &'r N: ops::Add<&'r N, Output=N>,
for<'r> &'r N: ops::Sub<&'r N, Output=N>,
@ -272,8 +271,7 @@ where
for<'r> &'r N: ops::Div<&'r N, Output=N>,
for<'r> &'r N: ops::Neg<Output=N>
{
state.kind = Some("Surplus of");
state.title = String::from(&elected_candidate.name);
state.title = StageKind::SurplusOf(&elected_candidate);
state.logger.log_literal(format!("Surplus of {} distributed.", elected_candidate.name));
let count_card = &state.candidates[elected_candidate];
@ -787,7 +785,6 @@ where
// Redistribute first preferences
super::distribute_first_preferences(state, opts);
state.kind = Some("Exclusion of");
state.title = orig_title;
// Trigger recalculation of quota within stv::count_one_stage

View File

@ -17,7 +17,7 @@
use super::{STVError, STVOptions};
use crate::election::{Ballot, Candidate, CandidateState, CountCard, CountState, Election};
use crate::election::{Ballot, Candidate, CandidateState, CountCard, CountState, Election, StageKind};
use crate::numbers::Number;
use itertools::Itertools;
@ -137,8 +137,7 @@ where
state.loss_fraction.transfers = lbf;
}
state.kind = None;
state.title = "First preferences".to_string();
state.title = StageKind::FirstPreferences;
state.logger.log_literal("First preferences distributed.".to_string());
}
@ -337,8 +336,7 @@ where
// Remove intermediate logs on quota calculation
state.logger.entries.clear();
state.kind = None;
state.title = "Surpluses distributed".to_string();
state.title = StageKind::SurplusesDistributed;
if num_iterations == 1 {
state.logger.log_literal("Surpluses distributed, requiring 1 iteration.".to_string());
} else {

View File

@ -31,7 +31,7 @@ pub mod wasm;
use crate::constraints;
use crate::election::Election;
use crate::numbers::Number;
use crate::election::{Candidate, CandidateState, CountCard, CountState, Vote};
use crate::election::{Candidate, CandidateState, CountCard, CountState, StageKind, Vote};
use crate::sharandom::SHARandom;
use crate::ties::{self, TieStrategy};
@ -1220,9 +1220,7 @@ fn do_bulk_elect<N: Number>(state: &mut CountState<N>, opts: &STVOptions, templa
/// Returns `true` if any candidates were elected.
fn bulk_elect<N: Number>(state: &mut CountState<N>, opts: &STVOptions) -> Result<bool, STVError> {
if can_bulk_elect(state, 0) {
state.kind = None;
state.title = "Bulk election".to_string();
state.title = StageKind::BulkElection;
do_bulk_elect(state, opts, "{} is elected to fill the remaining vacancy.", "{} are elected to fill the remaining vacancies.")?;
return Ok(true);
}
@ -1257,8 +1255,7 @@ where
}
let names: Vec<&str> = excluded_candidates.iter().map(|c| c.name.as_str()).sorted().collect();
state.kind = Some("Exclusion of");
state.title = names.join(", ");
state.title = StageKind::ExclusionOf(excluded_candidates.clone());
state.logger.log_smart(
"Doomed candidate, {}, is excluded.",
"Doomed candidates, {}, are excluded.",
@ -1396,8 +1393,7 @@ where
}
let names: Vec<&str> = excluded_candidates.iter().map(|c| c.name.as_str()).sorted().collect();
state.kind = Some("Exclusion of");
state.title = names.join(", ");
state.title = StageKind::ExclusionOf(excluded_candidates.clone());
state.logger.log_smart(
"No surpluses to distribute, so {} is excluded.",
"No surpluses to distribute, so {} are excluded.",
@ -1450,8 +1446,7 @@ where
.collect();
let names: Vec<&str> = excluded_candidates.iter().map(|c| c.name.as_str()).sorted().collect();
state.kind = Some("Exclusion of");
state.title = names.join(", ");
state.title = StageKind::ExclusionOf(excluded_candidates.clone());
state.logger.log_smart(
"Continuing exclusion of {}.",
"Continuing exclusion of {}.",

View File

@ -16,7 +16,7 @@
*/
use crate::constraints;
use crate::election::{Candidate, CandidateState, CountState, Parcel, Vote};
use crate::election::{Candidate, CandidateState, CountState, Parcel, StageKind, Vote};
use crate::numbers::Number;
use crate::stv::{STVOptions, SampleMethod, SurplusMethod};
@ -45,14 +45,13 @@ where
}
/// Distribute the surplus of a given candidate according to the random subset method, based on [STVOptions::surplus]
pub fn distribute_surplus<N: Number>(state: &mut CountState<N>, opts: &STVOptions, elected_candidate: &Candidate) -> Result<(), STVError>
pub fn distribute_surplus<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions, elected_candidate: &'a Candidate) -> Result<(), STVError>
where
for<'r> &'r N: ops::Sub<&'r N, Output=N>,
for<'r> &'r N: ops::Div<&'r N, Output=N>,
for<'r> &'r N: ops::Neg<Output=N>
{
state.kind = Some("Surplus of");
state.title = String::from(&elected_candidate.name);
state.title = StageKind::SurplusOf(&elected_candidate);
state.logger.log_literal(format!("Surplus of {} distributed.", elected_candidate.name));
let count_card = state.candidates.get_mut(elected_candidate).unwrap();

View File

@ -19,7 +19,7 @@
#![allow(unused_unsafe)] // Confuses cargo check
use crate::constraints::Constraints;
use crate::election::{CandidateState, CountState, Election};
use crate::election::{CandidateState, CountState, Election, StageKind};
use crate::numbers::{Fixed, GuardedFixed, NativeFloat64, Number, Rational};
use crate::parser::blt;
use crate::stv;
@ -27,6 +27,7 @@ use crate::ties;
extern crate console_error_panic_hook;
use itertools::Itertools;
use js_sys::Array;
use wasm_bindgen::prelude::wasm_bindgen;
@ -363,7 +364,7 @@ fn update_results_table<N: Number>(stage_num: usize, state: &CountState<N>, opts
// Insert borders to left of new exclusions in Wright STV
let classes_o; // Outer version
let classes_i; // Inner version
if opts.exclusion == stv::ExclusionMethod::Wright && state.kind == Some("Exclusion of") {
if opts.exclusion == stv::ExclusionMethod::Wright && (if let StageKind::ExclusionOf(_) = state.title { true } else { false }) {
classes_o = r#" class="blw""#;
classes_i = r#"blw "#;
} else {
@ -373,35 +374,56 @@ fn update_results_table<N: Number>(stage_num: usize, state: &CountState<N>, opts
// Hide transfers column for first preferences if transposed
let hide_xfers_trsp;
if state.title == "First preferences" || (opts.exclusion == stv::ExclusionMethod::Wright && state.kind == Some("Exclusion of")) {
if let StageKind::FirstPreferences = state.title {
hide_xfers_trsp = true;
} else if opts.exclusion == stv::ExclusionMethod::Wright && (if let StageKind::ExclusionOf(_) = state.title { true } else { false }) {
hide_xfers_trsp = true;
} else {
hide_xfers_trsp = false;
}
// Header rows
let kind_str = state.title.kind_as_string();
let title_str;
match &state.title {
StageKind::FirstPreferences | StageKind::SurplusesDistributed | StageKind::BulkElection => {
title_str = format!("{}", state.title);
}
StageKind::SurplusOf(candidate) => {
title_str = candidate.name.clone();
}
StageKind::ExclusionOf(candidates) => {
if candidates.len() > 5 {
let first_4_cands = candidates.iter().map(|c| &c.name).sorted().take(4).join(",<br>");
title_str = format!("{},<br>and {} others", first_4_cands, candidates.len() - 4);
} else {
title_str = candidates.iter().map(|c| &c.name).join(",<br>");
}
}
};
match report_style {
"votes" => {
result.push(&format!(r##"<td{0}><a href="#stage{1}">{1}</a></td>"##, classes_o, stage_num).into());
result.push(&format!(r#"<td{}>{}</td>"#, classes_o, state.kind.unwrap_or("")).into());
result.push(&format!(r#"<td{}>{}</td>"#, classes_o, state.title).into());
result.push(&format!(r#"<td{}>{}</td>"#, classes_o, kind_str).into());
result.push(&format!(r#"<td{}>{}</td>"#, classes_o, title_str).into());
}
"votes_transposed" => {
if hide_xfers_trsp {
result.push(&format!(r##"<td{0}><a href="#stage{1}">{1}</a></td>"##, classes_o, stage_num).into());
result.push(&format!(r#"<td{}>{}</td>"#, classes_o, state.kind.unwrap_or("")).into());
result.push(&format!(r#"<td{}>{}</td>"#, classes_o, state.title).into());
result.push(&format!(r#"<td{}>{}</td>"#, classes_o, kind_str).into());
result.push(&format!(r#"<td{}>{}</td>"#, classes_o, title_str).into());
} else {
result.push(&format!(r##"<td{0} colspan="2"><a href="#stage{1}">{1}</a></td>"##, classes_o, stage_num).into());
result.push(&format!(r#"<td{} colspan="2">{}</td>"#, classes_o, state.kind.unwrap_or("")).into());
result.push(&format!(r#"<td{} colspan="2">{}</td>"#, classes_o, state.title).into());
result.push(&format!(r#"<td{} colspan="2">{}</td>"#, classes_o, kind_str).into());
result.push(&format!(r#"<td{} colspan="2">{}</td>"#, classes_o, title_str).into());
//result.push(&format!(r#"<td{}>X'fers</td><td>Total</td>"#, tdclasses1).into());
}
}
"ballots_votes" => {
result.push(&format!(r##"<td{0} colspan="2"><a href="#stage{1}">{1}</a></td>"##, classes_o, stage_num).into());
result.push(&format!(r#"<td{} colspan="2">{}</td>"#, classes_o, state.kind.unwrap_or("")).into());
result.push(&format!(r#"<td{} colspan="2">{}</td>"#, classes_o, state.title).into());
result.push(&format!(r#"<td{} colspan="2">{}</td>"#, classes_o, kind_str).into());
result.push(&format!(r#"<td{} colspan="2">{}</td>"#, classes_o, title_str).into());
result.push(&format!(r#"<td{}>Ballots</td><td>Votes</td>"#, classes_o).into());
}
_ => unreachable!("Invalid report_style")

View File

@ -224,10 +224,7 @@ fn prompt<'c, N: Number>(state: &CountState<N>, opts: &STVOptions, candidates: &
// Show intrastage progress if required
if !state.logger.entries.is_empty() {
// Print stage details
match state.kind {
None => { println!("Tie during: {}", state.title); }
Some(kind) => { println!("Tie during: {} {}", kind, state.title); }
};
println!("Tie during: {}", state.title);
println!("{}", state.logger.render().join(" "));
// Print candidates
@ -279,10 +276,7 @@ fn prompt<'c, N: Number>(state: &CountState<N>, opts: &STVOptions, candidates: &
// Show intrastage progress if required
if !state.logger.entries.is_empty() {
// Print stage details
match state.kind {
None => { message.push_str(&format!("Tie during: {}\n", state.title)); }
Some(kind) => { message.push_str(&format!("Tie during: {} {}\n", kind, state.title)); }
};
message.push_str(&format!("Tie during: {}\n", state.title));
message.push_str(&state.logger.render().join(" "));
message.push('\n');

View File

@ -92,10 +92,7 @@ where
// Validate stage name
let stage_name = &records.iter().nth(1).unwrap()[idx*2 + 1];
if stage_name.len() > 0 {
match state.kind {
Some(kind) => assert_eq!(format!("{} {}", kind, state.title), stage_name),
None => assert_eq!(state.title, stage_name),
}
assert_eq!(format!("{}", state.title), stage_name);
}
let mut candidate_votes: Vec<Option<N>> = records.iter().skip(2)