lc_render/
chart.rs

1use crate::render::svg::*;
2use crate::shape::axis::{Axis, AxisPosition};
3use crate::view::View;
4use crate::{BandScale, Error, LinearScale};
5use std::path::Path;
6use svg::Node;
7
8const DEFAULT_MARGIN_TOP: i32 = 90;
9const DEFAULT_MARGIN_BOTTOM: i32 = 50;
10const DEFAULT_MARGIN_LEFT: i32 = 60;
11const DEFAULT_MARGIN_RIGHT: i32 = 40;
12const DEFAULT_WIDTH: i32 = 800;
13const DEFAULT_HEIGHT: i32 = 600;
14
15const DEFAULT_TITLE_FONT_SIZE: &str = "24px";
16const DEFAULT_TITLE_Y_TRANSFORM: i32 = 25;
17
18/// Chart represents a single document with one or more views, axes and a title.
19/// It will also contain grid and legend in the future.
20pub struct Chart<'a> {
21    margin_top: i32,
22    margin_bottom: i32,
23    margin_left: i32,
24    margin_right: i32,
25    width: i32,
26    height: i32,
27    x_axis_top: Option<Axis>,
28    x_axis_bottom: Option<Axis>,
29    y_axis_left: Option<Axis>,
30    y_axis_right: Option<Axis>,
31    views: Vec<&'a dyn View>,
32    title: String,
33}
34
35impl<'a> Chart<'a> {
36    /// Create a new chart.
37    pub fn new() -> Self {
38        Chart {
39            margin_top: DEFAULT_MARGIN_TOP,
40            margin_bottom: DEFAULT_MARGIN_BOTTOM,
41            margin_left: DEFAULT_MARGIN_LEFT,
42            margin_right: DEFAULT_MARGIN_RIGHT,
43            width: DEFAULT_WIDTH,
44            height: DEFAULT_HEIGHT,
45            x_axis_top: None,
46            x_axis_bottom: None,
47            y_axis_left: None,
48            y_axis_right: None,
49            views: Vec::new(),
50            title: String::new(),
51        }
52    }
53
54    /// Get chart width that can be used for views.
55    pub fn view_width(&self) -> i32 {
56        self.width - self.margin_left - self.margin_right
57    }
58
59    /// Get chart height that can be used for views.
60    pub fn view_height(&self) -> i32 {
61        self.height - self.margin_top - self.margin_bottom
62    }
63
64    /// Set chart top margin.
65    pub fn set_margin_top(mut self, margin_top: i32) -> Self {
66        self.margin_top = margin_top;
67        self
68    }
69
70    /// Set chart bottom margin.
71    pub fn set_margin_bottom(mut self, margin_bottom: i32) -> Self {
72        self.margin_bottom = margin_bottom;
73        self
74    }
75
76    /// Set chart left margin.
77    pub fn set_margin_left(mut self, margin_left: i32) -> Self {
78        self.margin_left = margin_left;
79        self
80    }
81
82    /// Set chart right margin.
83    pub fn set_margin_right(mut self, margin_right: i32) -> Self {
84        self.margin_right = margin_right;
85        self
86    }
87
88    /// Set chart width.
89    pub fn set_width(mut self, width: i32) -> Self {
90        self.width = width;
91        self
92    }
93
94    /// Set chart height.
95    pub fn set_height(mut self, height: i32) -> Self {
96        self.height = height;
97        self
98    }
99
100    /// Set BandScale for top axis.
101    pub fn set_axis_top_band(mut self, scale: BandScale) -> Self {
102        self.x_axis_top = Some(Axis::new(
103            &scale,
104            AxisPosition::Top,
105            self.view_width(),
106            self.view_height(),
107        ));
108        self
109    }
110
111    /// Set LinearScale for top axis.
112    pub fn set_axis_top_linear(mut self, scale: LinearScale) -> Self {
113        self.x_axis_top = Some(Axis::new(
114            &scale,
115            AxisPosition::Top,
116            self.view_width(),
117            self.view_height(),
118        ));
119        self
120    }
121
122    /// Set BandScale for bottom axis.
123    pub fn set_axis_bottom_band(mut self, scale: BandScale) -> Self {
124        self.x_axis_bottom = Some(Axis::new(
125            &scale,
126            AxisPosition::Bottom,
127            self.view_width(),
128            self.view_height(),
129        ));
130        self
131    }
132
133    /// Set LinearScale for bottom axis.
134    pub fn set_axis_bottom_linear(mut self, scale: LinearScale) -> Self {
135        self.x_axis_bottom = Some(Axis::new(
136            &scale,
137            AxisPosition::Bottom,
138            self.view_width(),
139            self.view_height(),
140        ));
141        self
142    }
143
144    /// Set BandScale for left axis.
145    pub fn set_axis_left_band(mut self, scale: BandScale) -> Self {
146        self.y_axis_left = Some(Axis::new(
147            &scale,
148            AxisPosition::Left,
149            self.view_width(),
150            self.view_height(),
151        ));
152        self
153    }
154
155    /// Set LinearScale for left axis.
156    pub fn set_axis_left_linear(mut self, scale: LinearScale) -> Self {
157        self.y_axis_left = Some(Axis::new(
158            &scale,
159            AxisPosition::Left,
160            self.view_width(),
161            self.view_height(),
162        ));
163        self
164    }
165
166    /// Set BandScale for right axis.
167    pub fn set_axis_right_band(mut self, scale: BandScale) -> Self {
168        self.y_axis_right = Some(Axis::new(
169            &scale,
170            AxisPosition::Right,
171            self.view_width(),
172            self.view_height(),
173        ));
174        self
175    }
176
177    /// Set LinearScale for right axis.
178    pub fn set_axis_right_linear(mut self, scale: LinearScale) -> Self {
179        self.y_axis_right = Some(Axis::new(
180            &scale,
181            AxisPosition::Right,
182            self.view_width(),
183            self.view_height(),
184        ));
185        self
186    }
187
188    /// Set label for top axis.
189    pub fn set_axis_top_label(mut self, label: &str) -> Self {
190        if let Some(ref mut axis) = self.x_axis_top {
191            axis.set_label(label);
192        }
193        self
194    }
195
196    /// Set label for bottom axis.
197    pub fn set_axis_bottom_label(mut self, label: &str) -> Self {
198        if let Some(ref mut axis) = self.x_axis_bottom {
199            axis.set_label(label);
200        }
201        self
202    }
203
204    /// Set label for left axis.
205    pub fn set_axis_left_label(mut self, label: &str) -> Self {
206        if let Some(ref mut axis) = self.y_axis_left {
207            axis.set_label(label);
208        }
209        self
210    }
211
212    /// Set label for right axis.
213    pub fn set_axis_right_label(mut self, label: &str) -> Self {
214        if let Some(ref mut axis) = self.y_axis_right {
215            axis.set_label(label);
216        }
217        self
218    }
219
220    /// Set chart title.
221    pub fn set_title(mut self, title: &str) -> Self {
222        self.title = title.to_string();
223        self
224    }
225
226    /// Add a view to chart.
227    pub fn add_view(mut self, view: &'a dyn View) -> Self {
228        self.views.push(view);
229        self
230    }
231
232    /// Set chart views.
233    pub fn set_views(mut self, views: Vec<&'a dyn View>) -> Self {
234        self.views = views;
235        self
236    }
237
238    /// Get chart SVG representation.
239    pub fn to_svg(&self) -> svg::Document {
240        let mut res = svg::node::element::Group::new().set(CLASS_ATTR, CLASS_CHART);
241
242        // Add axes.
243        if let Some(ref axis) = self.x_axis_top {
244            let mut axis_group = axis.to_svg();
245            axis_group.assign(
246                TRANSFORM_ATTR,
247                translate_x_y(self.margin_left, self.margin_top),
248            );
249            res.append(axis_group);
250        };
251        if let Some(ref axis) = self.x_axis_bottom {
252            let mut axis_group = axis.to_svg();
253            axis_group.assign(
254                TRANSFORM_ATTR,
255                translate_x_y(self.margin_left, self.height - self.margin_bottom),
256            );
257            res.append(axis_group);
258        };
259        if let Some(ref axis) = self.y_axis_left {
260            let mut axis_group = axis.to_svg();
261            axis_group.assign(
262                TRANSFORM_ATTR,
263                translate_x_y(self.margin_left, self.margin_top),
264            );
265            res.append(axis_group);
266        };
267        if let Some(ref axis) = self.y_axis_right {
268            let mut axis_group = axis.to_svg();
269            axis_group.assign(
270                TRANSFORM_ATTR,
271                translate_x_y(self.width - self.margin_right, self.margin_top),
272            );
273            res.append(axis_group);
274        };
275
276        // Add views.
277        let mut views_group = svg::node::element::Group::new()
278            .set(CLASS_ATTR, CLASS_VIEWS)
279            .set(
280                TRANSFORM_ATTR,
281                translate_x_y(self.margin_left, self.margin_top),
282            );
283        for view in self.views.iter() {
284            views_group.append(view.to_svg());
285        }
286        res.append(views_group);
287
288        // Add title.
289        if !self.title.is_empty() {
290            let title_group = svg::node::element::Group::new()
291                .set(CLASS_ATTR, CLASS_TITLE)
292                .set(
293                    TRANSFORM_ATTR,
294                    translate_x_y(self.width / 2, DEFAULT_TITLE_Y_TRANSFORM),
295                )
296                .add(
297                    svg::node::element::Text::new()
298                        .set(X_ATTR, START)
299                        .set(Y_ATTR, START)
300                        .set(DY_ATTR, DEFAULT_DY)
301                        .set(FILL_ATTR, DEFAULT_FONT_COLOR)
302                        .set(TEXT_ANCHOR_ATTR, TEXT_ANCHOR_MIDDLE)
303                        .set(FONT_SIZE_ATTR, DEFAULT_TITLE_FONT_SIZE)
304                        .set(FONT_FAMILY_ATTR, DEFAULT_FONT_FAMILY)
305                        .add(svg::node::Text::new(&self.title)),
306                );
307            res.append(title_group);
308        }
309
310        svg::Document::new()
311            .set(WIDTH_ATTR, self.width)
312            .set(HEIGHT_ATTR, self.height)
313            .set(VIEW_BOX_ATTR, (START, START, self.width, self.height))
314            .add(res)
315    }
316
317    /// Save chart to SVG file at the specified path.
318    pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
319        svg::save(path, &self.to_svg())?;
320
321        Ok(())
322    }
323}
324
325impl<'a> Default for Chart<'a> {
326    fn default() -> Self {
327        Self::new()
328    }
329}