1pub mod canvas;
2pub mod chart;
3pub mod draw;
4
5pub use canvas::{Canvas, Color};
6pub use chart::{Better, Chart, Format, Group, Party, Row, Words};
7pub use draw::{bars, paired, ratios};
8
9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct Error(String);
11
12impl Error {
13 pub fn new(message: impl Into<String>) -> Self {
14 Self(message.into())
15 }
16
17 pub fn message(&self) -> &str {
18 &self.0
19 }
20}
21
22impl std::fmt::Display for Error {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 f.write_str(&self.0)
25 }
26}
27
28impl std::error::Error for Error {}
29
30pub type Result<T> = std::result::Result<T, Error>;
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum Kind {
34 All,
35 Paired,
36 Ratio,
37 Bars,
38}
39
40pub fn render(chart: &Chart, kind: Kind) -> Result<Vec<(String, Canvas)>> {
41 chart.check()?;
42 let mut out = Vec::new();
43 let single = chart.parties.len() == 1;
44 if kind == Kind::Paired || (kind == Kind::All && !single) {
45 out.push(("paired".to_string(), paired(chart)?));
46 }
47 if kind == Kind::Ratio || (kind == Kind::All && !single) {
48 out.push(("ratio".to_string(), ratios(chart)?));
49 }
50 if kind == Kind::Bars || (kind == Kind::All && single) {
51 if single {
52 out.push(("bars".to_string(), bars(chart, 0)?));
53 } else {
54 let mut used = Vec::new();
55 for (i, p) in chart.parties.iter().enumerate() {
56 let mut name = format!("bars-{}", slug(&p.name));
57 if name == "bars-" || used.contains(&name) {
58 name = format!("bars-{}", i + 1);
59 }
60 used.push(name.clone());
61 out.push((name, bars(chart, i)?));
62 }
63 }
64 }
65 Ok(out)
66}
67
68fn slug(name: &str) -> String {
69 let mut s = String::new();
70 for c in name.chars() {
71 if c.is_ascii_alphanumeric() {
72 s.push(c.to_ascii_lowercase());
73 } else if !s.ends_with('-') && !s.is_empty() {
74 s.push('-');
75 }
76 }
77 s.trim_end_matches('-').to_string()
78}