Display up to 5 names only in web UI header, separate with line breaks
This commit is contained in:
parent
18c974117e
commit
e4bfe45f49
|
@ -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
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {}.",
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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")
|
||||
|
|
10
src/ties.rs
10
src/ties.rs
|
@ -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');
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue