Skip to main content

esoc_chart/grammar/
chart.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Chart: the top-level grammar container.
3
4use crate::error::Result;
5use crate::grammar::annotation::Annotation;
6use crate::grammar::coord::CoordSystem;
7use crate::grammar::facet::{Facet, FacetScales};
8use crate::grammar::layer::Layer;
9use crate::new_theme::NewTheme;
10use esoc_scene::SceneGraph;
11
12/// A chart definition (grammar of graphics).
13#[derive(Clone, Debug)]
14pub struct Chart {
15    /// Layers of marks.
16    pub layers: Vec<Layer>,
17    /// Chart title.
18    pub title: Option<String>,
19    /// X-axis label.
20    pub x_label: Option<String>,
21    /// Y-axis label.
22    pub y_label: Option<String>,
23    /// Width in pixels.
24    pub width: f32,
25    /// Height in pixels.
26    pub height: f32,
27    /// Theme.
28    pub theme: NewTheme,
29    /// Annotations (reference lines, bands, text).
30    pub annotations: Vec<Annotation>,
31    /// Subtitle (below title).
32    pub subtitle: Option<String>,
33    /// Caption (below plot).
34    pub caption: Option<String>,
35    /// Accessibility description for SVG output.
36    pub description: Option<String>,
37    /// Coordinate system.
38    pub coord: CoordSystem,
39    /// Faceting mode.
40    pub facet: Facet,
41    /// Facet scale sharing.
42    pub facet_scales: FacetScales,
43    /// Explicit x-axis domain override (min, max).
44    pub x_domain: Option<(f64, f64)>,
45    /// Explicit y-axis domain override (min, max).
46    pub y_domain: Option<(f64, f64)>,
47    /// Legend title (optional).
48    pub legend_title: Option<String>,
49}
50
51impl Chart {
52    /// Create a new empty chart.
53    pub fn new() -> Self {
54        Self {
55            layers: Vec::new(),
56            title: None,
57            x_label: None,
58            y_label: None,
59            width: 800.0,
60            height: 600.0,
61            theme: NewTheme::default(),
62            annotations: Vec::new(),
63            subtitle: None,
64            caption: None,
65            description: None,
66            coord: CoordSystem::default(),
67            facet: Facet::default(),
68            facet_scales: FacetScales::default(),
69            x_domain: None,
70            y_domain: None,
71            legend_title: None,
72        }
73    }
74
75    /// Add a layer.
76    pub fn layer(mut self, layer: Layer) -> Self {
77        self.layers.push(layer);
78        self
79    }
80
81    /// Set the title.
82    pub fn title(mut self, title: impl Into<String>) -> Self {
83        self.title = Some(title.into());
84        self
85    }
86
87    /// Set the X-axis label.
88    pub fn x_label(mut self, label: impl Into<String>) -> Self {
89        self.x_label = Some(label.into());
90        self
91    }
92
93    /// Set the Y-axis label.
94    pub fn y_label(mut self, label: impl Into<String>) -> Self {
95        self.y_label = Some(label.into());
96        self
97    }
98
99    /// Set dimensions.
100    pub fn size(mut self, width: f32, height: f32) -> Self {
101        self.width = width;
102        self.height = height;
103        self
104    }
105
106    /// Set theme.
107    pub fn theme(mut self, theme: NewTheme) -> Self {
108        self.theme = theme;
109        self
110    }
111
112    /// Add an annotation.
113    pub fn annotate(mut self, annotation: Annotation) -> Self {
114        self.annotations.push(annotation);
115        self
116    }
117
118    /// Set subtitle.
119    pub fn subtitle(mut self, subtitle: impl Into<String>) -> Self {
120        self.subtitle = Some(subtitle.into());
121        self
122    }
123
124    /// Set caption.
125    pub fn caption(mut self, caption: impl Into<String>) -> Self {
126        self.caption = Some(caption.into());
127        self
128    }
129
130    /// Set accessibility description.
131    pub fn description(mut self, desc: impl Into<String>) -> Self {
132        self.description = Some(desc.into());
133        self
134    }
135
136    /// Set coordinate system.
137    pub fn coord(mut self, coord: CoordSystem) -> Self {
138        self.coord = coord;
139        self
140    }
141
142    /// Set faceting mode.
143    pub fn facet(mut self, facet: Facet) -> Self {
144        self.facet = facet;
145        self
146    }
147
148    /// Set facet scale sharing.
149    pub fn facet_scales(mut self, scales: FacetScales) -> Self {
150        self.facet_scales = scales;
151        self
152    }
153
154    /// Set explicit x-axis domain (overrides auto-computed bounds).
155    pub fn x_domain(mut self, min: f64, max: f64) -> Self {
156        self.x_domain = Some((min, max));
157        self
158    }
159
160    /// Set explicit y-axis domain (overrides auto-computed bounds).
161    pub fn y_domain(mut self, min: f64, max: f64) -> Self {
162        self.y_domain = Some((min, max));
163        self
164    }
165
166    /// Set legend title.
167    pub fn legend_title(mut self, title: impl Into<String>) -> Self {
168        self.legend_title = Some(title.into());
169        self
170    }
171
172    /// Compile to a scene graph.
173    pub fn build(&self) -> Result<SceneGraph> {
174        crate::compile::compile_chart(self)
175    }
176
177    /// Compile and render to SVG.
178    pub fn to_svg(&self) -> Result<String> {
179        let scene = self.build()?;
180        let svg = esoc_gfx::scene_svg::render_scene_svg_with_metadata(
181            &scene,
182            self.width,
183            self.height,
184            self.title.as_deref(),
185            self.description.as_deref(),
186        )?;
187        Ok(svg)
188    }
189
190    /// Save as SVG (deprecated — use [`save_svg_to`] for `impl AsRef<Path>`).
191    #[deprecated(note = "Use save_svg_to(path) which accepts impl AsRef<Path>")]
192    pub fn save_svg(&self, path: &str) -> Result<()> {
193        self.save_svg_to(path)
194    }
195
196    /// Save as SVG to a path.
197    pub fn save_svg_to(&self, path: impl AsRef<std::path::Path>) -> Result<()> {
198        let svg = self.to_svg()?;
199        std::fs::write(path, svg)?;
200        Ok(())
201    }
202
203    /// Compile and render to PNG (requires `png` feature).
204    #[cfg(feature = "png")]
205    pub fn to_png(&self) -> Result<Vec<u8>> {
206        let scene = self.build()?;
207        let bytes = esoc_gfx::scene_svg::render_scene_png(&scene, self.width, self.height)?;
208        Ok(bytes)
209    }
210
211    /// Save as PNG (deprecated — use [`save_png_to`] for `impl AsRef<Path>`).
212    #[cfg(feature = "png")]
213    #[deprecated(note = "Use save_png_to(path) which accepts impl AsRef<Path>")]
214    pub fn save_png(&self, path: &str) -> Result<()> {
215        self.save_png_to(path)
216    }
217
218    /// Save as PNG to a path (requires `png` feature).
219    #[cfg(feature = "png")]
220    pub fn save_png_to(&self, path: impl AsRef<std::path::Path>) -> Result<()> {
221        let bytes = self.to_png()?;
222        std::fs::write(path, bytes)?;
223        Ok(())
224    }
225}
226
227impl Default for Chart {
228    fn default() -> Self {
229        Self::new()
230    }
231}