asciigraph/options/config.rs
1// Config — the builder-pattern configuration struct for graph rendering.
2
3use crate::color::AnsiColor;
4use crate::options::charset::{CharSet};
5use crate::options::extensions::{ZeroLine, Threshold, StatAnnotations};
6
7// ---------------------------------------------------------------------------
8// Config
9// ---------------------------------------------------------------------------
10
11/// Configuration for controlling the appearance and behavior of a graph.
12///
13/// `Config` uses a builder pattern — start with [`Config::default()`] and
14/// chain the methods for the options you want to set. Every method consumes
15/// and returns `Self`, so calls can be chained fluently.
16///
17/// # Example
18///
19/// ```rust
20/// use asciigraph::{plot, Config, AnsiColor};
21///
22/// let data = vec![1.0, 2.0, 3.0, 2.0, 1.0];
23/// let graph = plot(
24/// &data,
25/// Config::default()
26/// .height(5)
27/// .caption("My Graph")
28/// .axis_color(AnsiColor::GREEN),
29/// );
30/// ```
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub struct Config {
33 /// Target width of the data area in columns. `0` means auto-size to the
34 /// number of data points.
35 pub width: usize,
36
37 /// Target height of the graph in rows. `0` means auto-size based on the
38 /// data range.
39 pub height: usize,
40
41 /// Optional lower bound for the Y-axis. Ignored if the data minimum is
42 /// already below this value.
43 pub lower_bound: Option<f64>,
44
45 /// Optional upper bound for the Y-axis. Ignored if the data maximum is
46 /// already above this value.
47 pub upper_bound: Option<f64>,
48
49 /// Number of columns reserved for the Y-axis label area. Defaults to `3`.
50 pub offset: usize,
51
52 /// Caption string rendered below the graph body.
53 pub caption: String,
54
55 /// Number of decimal places for Y-axis labels. `None` means auto-detect.
56 pub precision: Option<usize>,
57
58 /// ANSI color for the caption text.
59 pub caption_color: AnsiColor,
60
61 /// ANSI color for axis lines and tick characters.
62 pub axis_color: AnsiColor,
63
64 /// ANSI color for Y-axis labels.
65 pub label_color: AnsiColor,
66
67 /// Per-series ANSI colors. The first color applies to the first series,
68 /// the second to the second, and so on.
69 pub series_colors: Vec<AnsiColor>,
70
71 /// Per-series legend labels rendered below the graph.
72 pub series_legends: Vec<String>,
73
74 /// Line ending sequence. Defaults to `"\n"`. Use `"\r\n"` for Windows
75 /// raw terminals.
76 pub line_ending: String,
77
78 /// Per-series character sets. Falls back to [`DEFAULT_CHAR_SET`] for any
79 /// series that does not have an explicit entry.
80 pub series_chars: Vec<CharSet>,
81
82 /// Number of tick marks on the X-axis. `0` means auto-calculate.
83 /// Minimum when set explicitly is `2`.
84 pub x_axis_tick_count: usize,
85
86 /// The `[min, max]` domain mapped onto the X-axis. Setting this enables
87 /// the X-axis.
88 pub x_axis_range: Option<[f64; 2]>,
89
90 /// Custom formatter for X-axis tick labels. Accepts any closure of the
91 /// form `Fn(f64) -> String`.
92 #[cfg_attr(feature = "serde", serde(skip))]
93 pub x_axis_value_formatter: Option<Box<dyn Fn(f64) -> String>>,
94
95 /// Custom formatter for Y-axis labels. Accepts any closure of the form
96 /// `Fn(f64) -> String`.
97 #[cfg_attr(feature = "serde", serde(skip))]
98 pub y_axis_value_formatter: Option<Box<dyn Fn(f64) -> String>>,
99
100 /// Optional zero line drawn at Y = 0.0 across the data area.
101 pub zero_line: Option<ZeroLine>,
102
103 /// Horizontal reference lines drawn at user-specified Y values.
104 pub thresholds: Vec<Threshold>,
105
106 /// Window size for the moving average overlay. `None` means disabled.
107 pub moving_average_window: Option<usize>,
108
109 /// Descriptive label rendered flush left above the graph body.
110 /// Set via [`Config::y_axis_label()`].
111 pub y_axis_label: Option<String>,
112
113 /// Descriptive label rendered inline on the same row as the X-axis line,
114 /// to the right of the tick marks. Only visible when [`Config::x_axis_range()`]
115 /// is also configured.
116 pub x_axis_label: Option<String>,
117
118 /// Optional statistical annotations rendered as horizontal reference lines
119 /// at computed values — minimum, maximum, mean, median, and standard deviation.
120 /// Set via [`Config::stat_annotations()`].
121 pub stat_annotations: Option<StatAnnotations>,
122}
123
124impl Default for Config {
125 fn default() -> Self {
126 Config {
127 width: 0,
128 height: 0,
129 lower_bound: None,
130 upper_bound: None,
131 offset: 3,
132 caption: String::new(),
133 precision: None,
134 caption_color: AnsiColor::DEFAULT,
135 axis_color: AnsiColor::DEFAULT,
136 label_color: AnsiColor::DEFAULT,
137 series_colors: Vec::new(),
138 series_legends: Vec::new(),
139 line_ending: String::from("\n"),
140 series_chars: Vec::new(),
141 x_axis_tick_count: 0,
142 x_axis_range: None,
143 x_axis_value_formatter: None,
144 y_axis_value_formatter: None,
145 zero_line: None,
146 thresholds: Vec::new(),
147 moving_average_window: None,
148 x_axis_label: None,
149 y_axis_label: None,
150 stat_annotations: None,
151 }
152 }
153}
154
155impl Config {
156 /// Sets the graph width in columns.
157 ///
158 /// When set to a positive value, the input data is interpolated to produce
159 /// exactly this many data columns regardless of how many points were
160 /// provided. Pass `0` to auto-size the width to the number of data points.
161 pub fn width(mut self, w: usize) -> Self {
162 self.width = w;
163 self
164 }
165
166 /// Sets the graph height in rows.
167 ///
168 /// Pass `0` to auto-size the height based on the data range, which is the
169 /// default behavior.
170 pub fn height(mut self, h: usize) -> Self {
171 self.height = h;
172 self
173 }
174
175 /// Sets an optional lower bound for the Y-axis.
176 ///
177 /// This value is only applied if it is lower than the actual data minimum.
178 /// It will not compress the visible range — it can only expand it downward.
179 pub fn lower_bound(mut self, min: f64) -> Self {
180 self.lower_bound = Some(min);
181 self
182 }
183
184 /// Sets an optional upper bound for the Y-axis.
185 ///
186 /// This value is only applied if it is higher than the actual data maximum.
187 /// It will not compress the visible range — it can only expand it upward.
188 pub fn upper_bound(mut self, max: f64) -> Self {
189 self.upper_bound = Some(max);
190 self
191 }
192
193 /// Sets the number of columns reserved for the Y-axis label area.
194 ///
195 /// Increase this value if your Y-axis labels are wider than the default
196 /// allows. Defaults to `3` when not set.
197 pub fn offset(mut self, off: usize) -> Self {
198 self.offset = off;
199 self
200 }
201
202 /// Sets the number of decimal places used in Y-axis labels.
203 ///
204 /// When not set, the library auto-detects appropriate precision based
205 /// on the data range — more decimal places for small values, fewer for
206 /// large ones.
207 pub fn precision(mut self, p: usize) -> Self {
208 self.precision = Some(p);
209 self
210 }
211
212 /// Sets the caption rendered below the graph body.
213 ///
214 /// The caption is centered over the data area. Leading and trailing
215 /// whitespace is trimmed before rendering.
216 pub fn caption(mut self, c: &str) -> Self {
217 self.caption = c.trim().to_string();
218 self
219 }
220
221 /// Sets the ANSI color for the caption text.
222 pub fn caption_color(mut self, color: AnsiColor) -> Self {
223 self.caption_color = color;
224 self
225 }
226
227 /// Sets the ANSI color for axis lines and tick characters.
228 pub fn axis_color(mut self, color: AnsiColor) -> Self {
229 self.axis_color = color;
230 self
231 }
232
233 /// Sets the ANSI color for Y-axis labels.
234 pub fn label_color(mut self, color: AnsiColor) -> Self {
235 self.label_color = color;
236 self
237 }
238
239 /// Sets per-series ANSI colors.
240 ///
241 /// The first color applies to the first series, the second to the second,
242 /// and so on. Series without a corresponding color entry are rendered in
243 /// the terminal default color.
244 pub fn series_colors(mut self, colors: &[AnsiColor]) -> Self {
245 self.series_colors = colors.to_vec();
246 self
247 }
248
249 /// Sets per-series legend labels rendered below the graph.
250 ///
251 /// The first label corresponds to the first series, the second to the
252 /// second, and so on. Labels are rendered alongside a colored square
253 /// marker matching the series color.
254 pub fn series_legends(mut self, text: &[&str]) -> Self {
255 self.series_legends = text.iter().map(|s| s.to_string()).collect();
256 self
257 }
258
259 /// Sets the line-ending sequence used between rows.
260 ///
261 /// Defaults to `"\n"`. Use `"\r\n"` for Windows raw terminals or any
262 /// environment that requires CRLF line endings.
263 pub fn line_ending(mut self, ending: &str) -> Self {
264 self.line_ending = ending.to_string();
265 self
266 }
267
268 /// Sets per-series character sets.
269 ///
270 /// The first [`CharSet`] applies to the first series, the second to the
271 /// second, and so on. Series without a corresponding entry fall back to
272 /// [`DEFAULT_CHAR_SET`]. Use [`create_char_set`] to create a uniform set,
273 /// or struct update syntax to override individual characters.
274 ///
275 /// [`create_char_set`]: crate::options::create_char_set
276 pub fn series_chars(mut self, cs: &[CharSet]) -> Self {
277 self.series_chars = cs.to_vec();
278 self
279 }
280
281 /// Sets the number of tick marks on the X-axis, overriding the automatic
282 /// calculation.
283 ///
284 /// When this is not called, the library automatically determines a sensible
285 /// tick count based on the available graph width and the estimated width of
286 /// the tick labels. The minimum accepted value is `2` — values below `2`
287 /// are ignored and the automatic calculation is used instead.
288 ///
289 /// Only takes effect when an X-axis range has been set via
290 /// [`Config::x_axis_range()`].
291 pub fn x_axis_tick_count(mut self, count: usize) -> Self {
292 if count >= 2 {
293 self.x_axis_tick_count = count;
294 }
295 self
296 }
297
298 /// Enables the X-axis and maps the domain `[min, max]` onto the plot width.
299 ///
300 /// Once set, an X-axis line and tick labels are rendered below the graph
301 /// body. The number of ticks is calculated automatically — call
302 /// [`Config::x_axis_tick_count()`] to override.
303 ///
304 /// # Example
305 ///
306 /// ```rust
307 /// use asciigraph::Config;
308 ///
309 /// let config = Config::default().x_axis_range(0.0, 100.0);
310 /// ```
311 pub fn x_axis_range(mut self, min: f64, max: f64) -> Self {
312 self.x_axis_range = Some([min, max]);
313 self
314 }
315
316 /// Sets a custom formatter for X-axis tick labels.
317 ///
318 /// Accepts any closure of the form `Fn(f64) -> String`. Use this to add
319 /// units, control decimal places, or apply any other formatting to the
320 /// values printed below the X-axis ticks.
321 ///
322 /// # Example
323 ///
324 /// ```rust
325 /// use asciigraph::Config;
326 ///
327 /// let config = Config::default()
328 /// .x_axis_range(0.0, 1000.0)
329 /// .x_axis_value_formatter(Box::new(|v| format!("{:.0}ms", v)));
330 /// ```
331 pub fn x_axis_value_formatter(mut self, formatter: Box<dyn Fn(f64) -> String>) -> Self {
332 self.x_axis_value_formatter = Some(formatter);
333 self
334 }
335
336 /// Sets a custom formatter for Y-axis labels.
337 ///
338 /// Accepts any closure of the form `Fn(f64) -> String`. Use this to add
339 /// units, convert between scales, or apply any other formatting to the
340 /// values printed on the Y-axis.
341 ///
342 /// # Example
343 ///
344 /// ```rust
345 /// use asciigraph::Config;
346 ///
347 /// let config = Config::default()
348 /// .y_axis_value_formatter(Box::new(|v| format!("{:.1} GiB", v / 1024.0)));
349 /// ```
350 pub fn y_axis_value_formatter(mut self, formatter: Box<dyn Fn(f64) -> String>) -> Self {
351 self.y_axis_value_formatter = Some(formatter);
352 self
353 }
354
355 /// Enables a horizontal reference line at Y = 0.0 across the data area.
356 ///
357 /// The line is only visible when the data range straddles zero. Series arc
358 /// characters always render on top of the zero line.
359 ///
360 /// # Example
361 ///
362 /// ```rust
363 /// use asciigraph::{plot, Config, ZeroLine, AnsiColor};
364 ///
365 /// let data = vec![-2.0, -1.0, 0.0, 1.0, 2.0];
366 /// let graph = plot(
367 /// &data,
368 /// Config::default().zero_line(ZeroLine::with_color(AnsiColor::RED)),
369 /// );
370 /// ```
371 pub fn zero_line(mut self, zero_line: ZeroLine) -> Self {
372 self.zero_line = Some(zero_line);
373 self
374 }
375
376 /// Adds a horizontal reference line at a user-specified Y value.
377 ///
378 /// Call this method multiple times to add more than one threshold. Series
379 /// arc characters always render on top of threshold lines.
380 ///
381 /// # Example
382 ///
383 /// ```rust
384 /// use asciigraph::{plot, Config, Threshold, AnsiColor};
385 ///
386 /// let data = vec![60.0, 70.0, 85.0, 92.0, 78.0, 65.0];
387 /// let graph = plot(
388 /// &data,
389 /// Config::default()
390 /// .threshold(Threshold::with_color(80.0, AnsiColor::YELLOW))
391 /// .threshold(Threshold::with_color(90.0, AnsiColor::RED)),
392 /// );
393 /// ```
394 pub fn threshold(mut self, t: Threshold) -> Self {
395 self.thresholds.push(t);
396 self
397 }
398
399 /// Enables a moving average overlay rendered as an additional series.
400 ///
401 /// The smoothed series is drawn on top of the original data using the next
402 /// available series color and character set. A window of `0` or `1` has no
403 /// effect.
404 ///
405 /// # Example
406 ///
407 /// ```rust
408 /// use asciigraph::{plot, Config};
409 ///
410 /// let data = vec![3.0, 1.0, 5.0, 2.0, 4.0, 1.0, 6.0, 2.0, 5.0, 1.0];
411 /// let graph = plot(&data, Config::default().moving_average(3));
412 /// ```
413 pub fn moving_average(mut self, window: usize) -> Self {
414 self.moving_average_window = Some(window);
415 self
416 }
417
418 /// Sets a descriptive label for the X-axis.
419 ///
420 /// Rendered inline on the same row as the axis line, to the right of the
421 /// tick marks. Only visible when [`Config::x_axis_range()`] is also set.
422 ///
423 /// # Example
424 ///
425 /// ```rust
426 /// use asciigraph::Config;
427 ///
428 /// let config = Config::default()
429 /// .x_axis_range(0.0, 100.0)
430 /// .x_axis_label("Time (seconds)");
431 /// ```
432 pub fn x_axis_label(mut self, label: &str) -> Self {
433 self.x_axis_label = Some(label.to_string());
434 self
435 }
436
437 /// Sets a descriptive label for the Y-axis.
438 ///
439 /// Rendered flush left above the graph body.
440 ///
441 /// # Example
442 ///
443 /// ```rust
444 /// use asciigraph::Config;
445 ///
446 /// let config = Config::default().y_axis_label("Memory (MB)");
447 /// ```
448 pub fn y_axis_label(mut self, label: &str) -> Self {
449 self.y_axis_label = Some(label.to_string());
450 self
451 }
452
453 /// Enables statistical annotations as horizontal reference lines across
454 /// the data area.
455 ///
456 /// Supports minimum, maximum, mean, median, and standard deviation.
457 /// Annotations are rendered before the series so series arc characters
458 /// always appear on top.
459 ///
460 /// # Example
461 ///
462 /// ```rust
463 /// use asciigraph::{plot, Config, StatAnnotations, AnsiColor};
464 ///
465 /// let data = vec![3.0, 1.0, 4.0, 1.0, 5.0, 9.0, 2.0, 6.0];
466 /// let graph = plot(
467 /// &data,
468 /// Config::default()
469 /// .stat_annotations(StatAnnotations::with_color(AnsiColor::YELLOW)),
470 /// );
471 /// ```
472 pub fn stat_annotations(mut self, sa: StatAnnotations) -> Self {
473 self.stat_annotations = Some(sa);
474 self
475 }
476}