timelog/chart/
legend.rs

1//! Represent the configuration for a legend on a [`PieChart`]
2//!
3//! # Examples
4//!
5//! ```rust, no_run
6//! use xml::writer::EmitterConfig;
7//! use timelog::chart::{ColorIter, Legend, TagPercent};
8//! # use timelog::Result;
9//!
10//! # fn main() -> Result<()> {
11//! # let mut target: Vec<u8> = Vec::new();
12//!   let mut writer = EmitterConfig::new()
13//!       .perform_indent(true)
14//!       .create_writer(&mut target);
15//!   let legend = Legend::new(16.0, ColorIter::default());
16//!   let percents = vec![
17//!       TagPercent::new("First",  40.0).unwrap(),
18//!       TagPercent::new("Second", 30.0).unwrap(),
19//!       TagPercent::new("Third",  20.0).unwrap(),
20//!       TagPercent::new("Fourth", 10.0).unwrap(),
21//!   ];
22//!
23//!   legend.write(&mut writer, percents.iter()).expect("Failed to write");
24//! #   Ok(())
25//! #  }
26//! ```
27//!
28//! # Description
29//!
30//! The [`Legend`] struct holds the configuration information needed to build a
31//! legend for a [`PieChart`].
32
33use std::io::Write;
34
35use xml::writer::{EventWriter, XmlEvent};
36
37use crate::chart::tag_percent::TagPercent;
38use crate::chart::ColorIter;
39#[cfg(doc)]
40use crate::chart::PieChart;
41use crate::emit_xml;
42
43/// Configuration for displaying a legend for a pie chart
44pub struct Legend<'a> {
45    /// Size of the font used for the legend text.
46    font_size: f32,
47    /// Iterator over colors for the legends
48    colors:    ColorIter<'a>
49}
50
51impl<'a> Legend<'a> {
52    /// Create a new [ `Legend` ] object.
53    pub fn new(font_size: f32, colors: ColorIter<'a>) -> Self { Self { font_size, colors } }
54
55    /// Return the font size for the legend.
56    pub fn font_size(&self) -> f32 { self.font_size }
57
58    // Write the color block for a line of the legend
59    //
60    // # Errors
61    //
62    // Could return any formatting error
63    fn color<W: Write>(&self, w: &mut EventWriter<W>, clr: &str) -> crate::Result<()> {
64        let size = format!("{}", self.font_size());
65        emit_xml!(w, svg, height: &size, width: &size => {
66            emit_xml!(w, rect, height: &size, width: &size, fill: clr)
67        })
68    }
69
70    // Write a single line of the legend
71    //
72    // # Errors
73    //
74    // Could return any formatting error
75    fn write_line<W: Write>(
76        &self, w: &mut EventWriter<W>, clr: &str, label: &str
77    ) -> crate::Result<()> {
78        emit_xml!(w, tr => {
79            emit_xml!(w, td => {
80                self.color(w, clr)?;
81                emit_xml!(w, span; label)
82            })
83        })
84    }
85
86    /// Write a legend representing the supplied percentages.
87    ///
88    /// # Errors
89    ///
90    /// Could return any formatting error
91    pub fn write<'b, W, Iter>(&self, w: &mut EventWriter<W>, percents: Iter) -> crate::Result<()>
92    where
93        Iter: Iterator<Item = &'b TagPercent>,
94        W: Write
95    {
96        emit_xml!(w, table, class: "legend", style: &format!("font-size: {}px", self.font_size()) => {
97            for (p, clr) in percents.zip(self.colors.clone()) {
98                self.write_line(w, clr, &p.display_label())?;
99            }
100            Ok(())
101        })
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use spectral::prelude::*;
108    use xml::writer::EmitterConfig;
109
110    use super::*;
111
112    #[test]
113    fn test_new() {
114        let legend = Legend::new(12.0, ColorIter::default());
115        assert_that!(legend.font_size()).is_equal_to(&12.0);
116    }
117
118    #[test]
119    fn test_fmt_line() {
120        let colors = ["blue", "green"];
121        let legend = Legend::new(14.0, ColorIter::new(&colors));
122        let tags = [
123            TagPercent::new("Foo", 30.0).unwrap(),
124            TagPercent::new("Bar", 70.0).unwrap()
125        ];
126
127        let mut actual: Vec<u8> = Vec::new();
128        let mut w = EmitterConfig::new()
129            .perform_indent(true)
130            .write_document_declaration(false)
131            .create_writer(&mut actual);
132
133        let expected = r#"<table class="legend" style="font-size: 14px">
134  <tr>
135    <td>
136      <svg height="14" width="14">
137        <rect height="14" width="14" fill="blue" />
138      </svg>
139      <span>30% - Foo</span>
140    </td>
141  </tr>
142  <tr>
143    <td>
144      <svg height="14" width="14">
145        <rect height="14" width="14" fill="green" />
146      </svg>
147      <span>70% - Bar</span>
148    </td>
149  </tr>
150</table>"#;
151        assert_that!(legend.write(&mut w, tags.iter())).is_ok();
152        assert_that!(String::from_utf8(actual).unwrap()).is_equal_to(expected.to_string())
153    }
154}