use super::plural;
use crate::spec::{Locale, Quadrant};
use std::fmt::Write as _;
pub(crate) fn box_display_note(locale: Locale, ridgeline: bool) -> String {
match (locale, ridgeline) {
(Locale::En, false) => " Every observation is drawn as a dot, moved sideways so that no two dots overlap.".to_owned(),
(Locale::De, false) => " Jede Beobachtung ist ein Punkt, seitlich verschoben, sodass sich keine zwei Punkte überdecken.".to_owned(),
(Locale::En, true) => " Each category is a density curve of its observations on a shared value axis, the curves stacked with a slight overlap.".to_owned(),
(Locale::De, true) => " Jede Kategorie ist eine Dichtekurve ihrer Beobachtungen auf einer gemeinsamen Wertachse, die Kurven leicht überlappend gestapelt.".to_owned(),
}
}
pub(crate) fn boxplot_summary(
locale: Locale,
boxes: usize,
highest: (&str, &str),
lowest: (&str, &str),
outliers: &[&str],
) -> String {
let outliers = match (locale, outliers.is_empty()) {
(_, true) => String::new(),
(Locale::En, false) => format!(" Outliers beyond the whiskers: {}.", outliers.join(", ")),
(Locale::De, false) => format!(" Ausreißer jenseits der Whisker: {}.", outliers.join(", ")),
};
match locale {
Locale::En => format!(
"Box plot with {boxes} box{}, each from the first to the third quartile with its median. Highest median: {} ({}). Lowest median: {} ({}).{outliers}",
plural(boxes, "", "es"),
highest.0,
highest.1,
lowest.0,
lowest.1,
),
Locale::De => format!(
"Boxplot mit {boxes} {}, jeweils vom ersten bis zum dritten Quartil mit dem Median. Höchster Median: {} ({}). Niedrigster Median: {} ({}).{outliers}",
plural(boxes, "Box", "Boxen"),
highest.0,
highest.1,
lowest.0,
lowest.1,
),
}
}
pub(crate) fn value_span(locale: Locale, low: &str, high: &str) -> String {
match locale {
Locale::En if low == high => format!("at {low}"),
Locale::De if low == high => format!("bei {low}"),
Locale::En => format!("from {low} to {high}"),
Locale::De => format!("von {low} bis {high}"),
}
}
pub(crate) fn scatter_summary(
locale: Locale,
points: usize,
(x_span, y_span): (&str, &str),
(groups, lines): (&str, &str),
fits: &str,
) -> String {
let mut text = match locale {
Locale::En => format!(
"Scatter plot of {points} point{}: x {x_span}, y {y_span}.",
plural(points, "", "s")
),
Locale::De => format!(
"Streudiagramm aus {points} {}: x {x_span}, y {y_span}.",
plural(points, "Punkt", "Punkten")
),
};
if !groups.is_empty() {
let word = match locale {
Locale::En => "Groups (points)",
Locale::De => "Gruppen (Punkte)",
};
write!(text, " {word}: {groups}.").expect("writing to String cannot fail");
}
if !lines.is_empty() {
let word = match locale {
Locale::En => "Lines",
Locale::De => "Linien",
};
write!(text, " {word}: {lines}.").expect("writing to String cannot fail");
}
if !fits.is_empty() {
write!(text, " {fits}").expect("writing to String cannot fail");
}
text
}
pub(crate) fn points_above_line(locale: Locale, count: usize) -> String {
match locale {
Locale::En => format!("{count} point{} above it", plural(count, "", "s")),
Locale::De => format!("{count} {} darüber", plural(count, "Punkt", "Punkte")),
}
}
pub(crate) fn scatter_fit(
locale: Locale,
group: Option<&str>,
(slope, intercept, r_squared): (String, String, String),
(log, points): (bool, usize),
) -> String {
let of = match (locale, group) {
(Locale::En, Some(group)) => format!("Line for {group}"),
(Locale::En, None) => "Line".to_owned(),
(Locale::De, Some(group)) => format!("Gerade für {group}"),
(Locale::De, None) => "Gerade".to_owned(),
};
match (locale, log) {
(Locale::En, false) => {
format!("{of} through {points} points: y = {intercept} + {slope} x, r² = {r_squared}.")
}
(Locale::En, true) => format!(
"{of} through {points} points, fitted to the logarithms: ln y = {intercept} + {slope} ln x, r² = {r_squared}."
),
(Locale::De, false) => {
format!("{of} durch {points} Punkte: y = {intercept} + {slope} x, r² = {r_squared}.")
}
(Locale::De, true) => format!(
"{of} durch {points} Punkte, an die Logarithmen angepasst: ln y = {intercept} + {slope} ln x, r² = {r_squared}."
),
}
}
pub(crate) fn quadrant_position(locale: Locale, quadrant: Quadrant, capital: bool) -> String {
let words = match (locale, quadrant) {
(Locale::En, Quadrant::TopLeft) => "top left",
(Locale::En, Quadrant::TopRight) => "top right",
(Locale::En, Quadrant::BottomLeft) => "bottom left",
(Locale::En, Quadrant::BottomRight) => "bottom right",
(Locale::De, Quadrant::TopLeft) => "oben links",
(Locale::De, Quadrant::TopRight) => "oben rechts",
(Locale::De, Quadrant::BottomLeft) => "unten links",
(Locale::De, Quadrant::BottomRight) => "unten rechts",
};
if capital {
let mut chars = words.chars();
chars.next().map_or_else(String::new, |first| {
first.to_uppercase().chain(chars).collect()
})
} else {
words.to_owned()
}
}
pub(crate) const fn on_dividing_line(locale: Locale) -> &'static str {
match locale {
Locale::En => "on a dividing line",
Locale::De => "auf einer Trennlinie",
}
}
pub(crate) fn quadrants_opening(locale: Locale, x: &str, y: &str) -> String {
match locale {
Locale::En => format!("Quadrants divided at x = {x} and y = {y}."),
Locale::De => format!("Quadranten geteilt bei x = {x} und y = {y}."),
}
}
pub(crate) fn quadrant_points(
locale: Locale,
area: Option<(&str, Option<&str>)>,
count: usize,
names: &[&str],
) -> String {
let head = match (locale, area) {
(_, Some((position, Some(label)))) => format!("{position}, {label}"),
(_, Some((position, None))) => position.to_owned(),
(Locale::En, None) => "On a dividing line".to_owned(),
(Locale::De, None) => "Auf einer Trennlinie".to_owned(),
};
let points = match (locale, count) {
(Locale::En, 0) => "no points".to_owned(),
(Locale::De, 0) => "keine Punkte".to_owned(),
(Locale::En, _) => format!("{count} point{}", plural(count, "", "s")),
(Locale::De, _) => format!("{count} {}", plural(count, "Punkt", "Punkte")),
};
let shown = &names[..names.len().min(10)];
let more = count - shown.len();
let list = match (locale, shown.is_empty(), more) {
(_, true, _) => String::new(),
(_, false, 0) => format!(" ({})", shown.join(", ")),
(Locale::En, false, _) => format!(" ({}, and {more} more)", shown.join(", ")),
(Locale::De, false, _) => format!(" ({} und {more} weitere)", shown.join(", ")),
};
format!("{head}: {points}{list}.")
}
pub(crate) fn scatter_caption(
locale: Locale,
caption: &str,
(shown, all): (usize, usize),
) -> String {
match locale {
Locale::En => format!("{caption} — {shown} of {all} points, the named or the highest"),
Locale::De => {
format!("{caption} — {shown} von {all} Punkten, die benannten oder die höchsten")
}
}
}
pub(crate) fn survival_group(
locale: Locale,
(label, observations, events): (&str, usize, usize),
median: Option<&str>,
last: &str,
) -> String {
let event = plural(events, "event", "events");
let ereignis = plural(events, "Ereignis", "Ereignisse");
match (locale, median) {
(Locale::En, Some(median)) => format!(
"{label}: {observations} observations, {events} {event}, median {median}, {last} at the end."
),
(Locale::En, None) => format!(
"{label}: {observations} observations, {events} {event}, median not reached, {last} at the end."
),
(Locale::De, Some(median)) => format!(
"{label}: {observations} Beobachtungen, {events} {ereignis}, Median {median}, {last} am Ende."
),
(Locale::De, None) => format!(
"{label}: {observations} Beobachtungen, {events} {ereignis}, Median nicht erreicht, {last} am Ende."
),
}
}
pub(crate) fn survival_test_short(locale: Locale, p: &str) -> String {
match locale {
Locale::En => format!("Log-rank p {p}"),
Locale::De => format!("Log-Rank-Test p {p}"),
}
}
pub(crate) fn survival_test(locale: Locale, (chi, degrees): (&str, usize), p: &str) -> String {
match locale {
Locale::En => format!(
"Log-rank test: χ² = {chi}, {degrees} degree{} of freedom, p {p}.",
plural(degrees, "", "s")
),
Locale::De => format!(
"Log-Rank-Test: χ² = {chi}, {degrees} Freiheitsgrad{}, p {p}.",
plural(degrees, "", "e")
),
}
}
pub(crate) fn survival_hazard(
locale: Locale,
(group, reference): (&str, &str),
(ratio, lower, upper): (&str, &str, &str),
) -> String {
match locale {
Locale::En => format!(
"Hazard ratio of {group} against {reference}: {ratio} (95% confidence interval {lower} to {upper})."
),
Locale::De => format!(
"Hazard Ratio von {group} gegenüber {reference}: {ratio} (95-%-Konfidenzintervall {lower} bis {upper})."
),
}
}
pub(crate) fn survival_summary(
locale: Locale,
groups: usize,
confidence: bool,
each: &str,
) -> String {
match locale {
Locale::En => format!(
"Kaplan-Meier curves of {groups} group{}{}.{each}",
plural(groups, "", "s"),
if confidence {
", with 95% confidence bands"
} else {
""
}
),
Locale::De => format!(
"Kaplan-Meier-Kurven von {groups} {}{}.{each}",
plural(groups, "Gruppe", "Gruppen"),
if confidence {
", mit 95-%-Konfidenzbändern"
} else {
""
}
),
}
}
#[cfg(test)]
mod tests {
use super::survival_group;
use crate::spec::Locale;
#[test]
fn one_event_is_singular() {
assert_eq!(
survival_group(Locale::En, ("A", 5, 1), None, "80%"),
"A: 5 observations, 1 event, median not reached, 80% at the end."
);
assert_eq!(
survival_group(Locale::De, ("A", 5, 1), Some("3"), "80 %"),
"A: 5 Beobachtungen, 1 Ereignis, Median 3, 80 % am Ende."
);
}
}