eb_bars/
lib.rs

1// Copyright 2025 Developers of eb_bars.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! EB - Bars: Plotting library for Rust providing a simple way to create barcharts in svg format.
10//!
11//! # Quick Start
12//!
13//! The simplest usecase (which you never really might want) is written like so.
14//! ```
15//! use eb_bars::BarPlot;
16//!
17//! // Start out with an empty plot.
18//! let mut plot = BarPlot::new();
19//!
20//! // Add a set of values.
21//! plot.add_values(&[5.0, 16.4, 17.1, 13.7, 8.9, 3.9, 6.3, 9.6]);
22//!
23//! // Render to svg format.
24//! let svg: String = plot.to_svg(1600, 1000);
25//! ```
26//!
27//! # But the above "Quick Start" looks bad and boring
28//!
29//! As the above example stands, the largest number will have its bar take up the full height of the window.
30//! Also, the lowest value will be a bar of zero height e.g. no bar at all.
31//! The problem is that we have not set a specific scaling for the values.
32//! This means that the bars are all scaled based on the minimum and maximum value.
33//! Let's improve it a bit in the next section by adding a scale.
34//!
35//! ```
36//! use eb_bars::BarPlot;
37//!
38//! let mut plot = BarPlot::new();
39//!
40//! // Add same values as before.
41//! plot.add_values(&[5.0, 16.4, 17.1, 13.7, 8.9, 3.9, 6.3, 9.6]);
42//!
43//! // Here, we are setting a scale range for better visibility/scaling of bars.
44//! plot.set_scale_range(0, 20, 2);
45//! // The bars will look better, but the numbers on the scale won't be visible.
46//! // We need to shrink the plot window relative to the full window.
47//! // Keep in mind that all size and offset values are based of a percentage.
48//! // Setting a width to 100 means it takes up the whole width.
49//! // Same goes for the height.
50//!
51//! // Let's shrink the plot size.
52//! plot.set_plot_window_size(95.0, 85.0, 93.0, 50.0);
53//! // We have now set the width at 95% and moved it 85% right from the left side.
54//! // We also set the height at 93% and moved it 50% down from the top.
55//! // First two parameters affect the width and the left/right offset respectively.
56//! // The last two parameters affect the height and the top/bottom offset respectively.
57//!
58//! // Let's render the svg.
59//! let svg: String = plot.to_svg(1600, 1000);
60//! ```
61//!
62//! It is still kinda boring, so please checkout all the tweaks available in [`BarPlot`].
63//!
64//! # Important note
65//!
66//! If the method name is prefixed `add_`, then calling it multiple times will _add_ stuff to the plot.
67//! This goes for adding multiple sets of values (categories) and adding colors to those values etc..
68//!
69//! If the method name is prefixed `set_`, then calling it multiple times will _override_ the previous one.
70//! You most certainly never want to call these more than once, unless there is a good reason to.
71//!
72//! Check out [`BarPlot`] for all implementations.
73//!
74//! # Panics and error handling.
75//!
76//! This library has very limited error handling at the moment. Actually, it has none.
77//! There are some assertions here and there that will provoke a panic on invalid input.
78//! That way, you can try-and-re-try your code until it works.
79//!
80//! Once everything works, it is very unlikely that it will panic on continous use in your application.
81//! However, if you pass values that are generated from a source that you do not have full control over,
82//! then the task of making sure the input is sanitized and double checked lies on your end and your code.
83
84mod svg;
85
86type Percentage = f64;
87
88const VERSION: &str = "0.7.3";
89const REPOSITORY: &str = "https://github.com/emilbratt/eb_bars";
90
91const DEFAULT_SIZE: (u32, u32) = (1600, 1000);
92
93const DEFAULT_BAR_COLOR: &str = "rgb(112, 153, 182)";
94const DEFAULT_BASE_COLOR: &str = "rgb(197, 197, 197)";
95const DEFAULT_BAR_GAP: Percentage = 0.0;
96const DEFAULT_BIN_GAP: Percentage = 10.0;
97
98const DEFAULT_FONT_SIZE: Percentage = 100.0;
99const DEFAULT_LEGEND_POSITION: (Percentage, Percentage) = (90.0, 20.0);
100const DEFAULT_TEXT_SIDE_OFFSET: Percentage = 35.0;
101const DEFAULT_TICK_LENGTH: Percentage = 10.0;
102
103
104#[derive(Debug, Default)]
105enum BinMarkerPosition {
106    Left,
107    #[default]
108    Middle,
109    Right,
110}
111
112#[derive(Debug, Default)]
113struct PlotLegend<'a> {
114    categories: Option<&'a[&'a str]>,
115    position: Option<(Percentage, Percentage)>,
116}
117
118#[derive(Debug)]
119enum BarColorLayout<'a> {
120    Category(Vec<&'a str>), // Each category has its own color.
121    Indexed(Vec<Vec<&'a str>>), // Every bar has its own selected color.
122    Threshold((&'a str, &'a str, &'a str, &'a str)), // Every bar is given its color based on its value.
123    Uniform(&'a str), // All bars are the same color.
124}
125
126impl Default for BarColorLayout<'_> {
127    fn default() -> Self {
128        Self::Uniform(DEFAULT_BAR_COLOR)
129    }
130}
131
132#[derive(Debug, Default)]
133struct BarColors<'a> {
134    layout: BarColorLayout<'a>,
135    overrides: Vec<(usize, usize, &'a str)>,
136}
137
138#[derive(Debug)]
139struct Colors<'a> {
140    background: Option<&'a str>,
141    bars: BarColors<'a>,
142    line: &'a str,
143    text: &'a str,
144    tick: &'a str,
145}
146
147impl Default for Colors<'_> {
148    fn default() -> Self {
149        Self {
150            background: None,
151            bars: BarColors::default(),
152            line: DEFAULT_BASE_COLOR,
153            text: DEFAULT_BASE_COLOR,
154            tick: DEFAULT_BASE_COLOR,
155        }
156    }
157}
158
159#[derive(Debug, Default)]
160struct Show {
161    window_border: bool,
162    plot_border: bool,
163    horizontal_lines: bool,
164    vertical_lines: bool,
165}
166
167#[derive(Debug, Default)]
168struct PlotText<'a> {
169    left: Option<&'a str>,
170    left_offset: Option<Percentage>,
171
172    right: Option<&'a str>,
173    right_offset: Option<Percentage>,
174
175    top: Option<&'a str>,
176    top_offset: Option<Percentage>,
177
178    bottom: Option<&'a str>,
179    bottom_offset: Option<Percentage>,
180}
181
182#[derive(Debug)]
183struct PlotLayout {
184    bar_gap: Percentage,
185    bin_gap: Percentage,
186    bin_marker_position: BinMarkerPosition,
187    font_size: Percentage,
188    plot_window_scale: Option<(Percentage, Percentage, Percentage, Percentage)>,
189    scale_range: Option<(i64, i64, usize)>,
190    x_axis_tick_length: Percentage,
191    y_axis_tick_length: Percentage,
192    negative_bars_go_down: bool,
193}
194
195impl Default for PlotLayout {
196    fn default() -> Self {
197        Self {
198            bin_gap: DEFAULT_BIN_GAP,
199            bar_gap: DEFAULT_BAR_GAP,
200            bin_marker_position: BinMarkerPosition::default(),
201            font_size: DEFAULT_FONT_SIZE,
202            plot_window_scale: None,
203            scale_range: None,
204            x_axis_tick_length: DEFAULT_TICK_LENGTH,
205            y_axis_tick_length: DEFAULT_TICK_LENGTH,
206            negative_bars_go_down: false,
207        }
208    }
209}
210
211#[derive(Debug)]
212enum LinesAt<'a> {
213    Horizontal(f64, &'a str),
214    Vertical(f64, &'a str),
215}
216
217#[derive(Debug)]
218pub struct BarPlot<'a> {
219    values: Vec<&'a [f64]>,
220    markers: Option<&'a [&'a str]>,
221    lines_at: Vec<LinesAt<'a>>,
222    size: (u32, u32),
223    colors: Colors<'a>,
224    legend: PlotLegend<'a>,
225    layout: PlotLayout,
226    show: Show,
227    plot_text: PlotText<'a>,
228}
229
230// FIXME: add new with default, allow for now with attribute below..
231#[allow(clippy::new_without_default)]
232impl <'a>BarPlot<'a> {
233
234    /// Instantiate a new plot.
235    ///
236    /// # Example
237    ///
238    /// ```
239    /// use eb_bars::BarPlot;
240    ///
241    /// let mut plot = BarPlot::new();
242    /// ```
243    pub fn new() -> Self {
244        Self {
245            values: Vec::new(),
246            markers: None,
247            lines_at: Vec::new(),
248            size: DEFAULT_SIZE,
249            colors: Colors::default(),
250            legend: PlotLegend::default(),
251            layout: PlotLayout::default(),
252            show: Show::default(),
253            plot_text: PlotText::default(),
254        }
255    }
256
257    /// Adding a set of values (bars) to the plot.
258    ///
259    /// # Takes an array slice of f64.
260    ///
261    /// All valus must be f64.
262    /// If you have a `Vec<u32>`, make sure to convert it to a `Vec<f64>` before passing it.
263    /// Then you can pass the vector as a reference.
264    ///
265    /// # This method is required.
266    ///
267    /// There must be at least one set of values to produce a plot. :)
268    ///
269    /// # Grouped bars
270    ///
271    /// Calling this method more than once will create `groups` for values of same index.
272    /// This means that the first datapoint of each added dataset will be the first group,
273    /// the second datapoint of each added dataset will be the second group and so on..
274    /// E.g. calling this method 5 times will add groups of 5 bars in each bin.
275    ///
276    /// # Short summary
277    ///
278    /// * Must be called at least once. A plot without values does not make any sense.. :)
279    /// * If called multiple times, each bin will contain a group with values of the same index.
280    /// * All arrays passed after first call must be of the `exact` same length as the first array.
281    ///
282    /// # Example
283    ///
284    /// ```
285    /// use eb_bars::BarPlot;
286    ///
287    /// let mut plot = BarPlot::new();
288    ///
289    /// let apples: Vec<f64> = vec![5., 16., 17., 8., 3.];
290    /// let oranges: Vec<f64> = vec![7., 6., 7., 16., 9.];
291    /// // The first group contains 5 apples and 7 oranges.
292    /// // The second one 16 and 6 respectively.
293    /// // The last group contains 3 apples and 9 oranges.
294    ///
295    /// // Add the first set of values.
296    /// plot.add_values(&apples);
297    ///
298    /// // Add the second set of values.
299    /// plot.add_values(&oranges);
300    ///
301    /// let svg: String = plot.to_svg(1600, 1000);
302    /// ```
303    pub fn add_values(&mut self, values: &'a [f64]) {
304        if !self.values.is_empty() {
305            // This makes sure that all new values are of same length e.g. same count as previous values.
306            // This is needed so that all bins have the same amount of bars in them,
307            // ..as long as we add more than 1 category, that is..
308            let exp_count = self.values[0].len();
309            let count = values.len();
310            assert_eq!(
311                exp_count,
312                count,
313                "Added values should be same count as old, expected {exp_count}, got {count}"
314            );
315        }
316        self.values.push(values);
317    }
318
319    /// Set a fill color as background.
320    ///
321    /// By default, the image will be fully transparent where there is nothing drawn on it.
322    /// Adding a background color might be a good idea for better visual presentation depending on usecase.
323    ///
324    /// # Accepted color conventions.
325    ///
326    /// * As its name such as "Red".
327    /// * As an RGB value such as "rgb(29, 28, 27)".
328    /// * As a HEX value such as "#1111FA".
329    /// * As an HSL value such as "hsl(30, 3.80%, 10.20%)".
330    ///
331    /// # Example
332    ///
333    /// ```
334    /// use eb_bars::BarPlot;
335    ///
336    /// let mut plot = BarPlot::new();
337    ///
338    /// plot.add_values(&[1., 2., 3.,]);
339    ///
340    /// plot.set_background_color("Black");
341    ///
342    /// let svg: String = plot.to_svg(1600, 1000);
343    /// ```
344    pub fn set_background_color(&mut self, color: &'a str) {
345        self.colors.background = Some(color);
346    }
347
348    /// Set color for the lines.
349    ///
350    /// By default, all lines are drawn with a `default` color.
351    /// You can `override` this by setting your own color.
352    ///
353    /// # Accepted color conventions.
354    ///
355    /// * As its name such as "Red".
356    /// * As an RGB value such as "rgb(29, 28, 27)".
357    /// * As a HEX value such as "#1111FA".
358    /// * As an HSL value such as "hsl(30, 3.80%, 10.20%)".
359    ///
360    /// # Example
361    ///
362    /// ```
363    /// use eb_bars::BarPlot;
364    ///
365    /// let mut plot = BarPlot::new();
366    ///
367    /// plot.add_values(&[1., 2., 3.,]);
368    ///
369    /// plot.set_line_color("Yellow");
370    ///
371    /// let svg: String = plot.to_svg(1600, 1000);
372    /// ```
373    pub fn set_line_color(&mut self, color: &'a str) {
374        self.colors.line = color;
375    }
376
377    /// Set color for text/numbers.
378    ///
379    /// By default, all text are drawn with a `default` color.
380    /// You can `override` this by setting your own color.
381    ///
382    /// # Accepted color conventions.
383    ///
384    /// * As its name such as "Red".
385    /// * As an RGB value such as "rgb(29, 28, 27)".
386    /// * As a HEX value such as "#1111FA".
387    /// * As an HSL value such as "hsl(30, 3.80%, 10.20%)".
388    ///
389    /// # Example
390    ///
391    /// ```
392    /// use eb_bars::BarPlot;
393    ///
394    /// let mut plot = BarPlot::new();
395    ///
396    /// plot.add_values(&[1., 2., 3.,]);
397    ///
398    /// plot.set_text_color("LightBlue");
399    ///
400    /// let svg: String = plot.to_svg(1600, 1000);
401    /// ```
402    pub fn set_text_color(&mut self, color: &'a str) {
403        self.colors.text = color;
404    }
405
406    /// Set color for x-ticks and y-ticks.
407    ///
408    /// By default, all ticks are drawn with a `default` color.
409    /// You can `override` this by setting your own color.
410    ///
411    /// # Accepted color conventions.
412    ///
413    /// * As its name such as "Red".
414    /// * As an RGB value such as "rgb(29, 28, 27)".
415    /// * As a HEX value such as "#1111FA".
416    /// * As an HSL value such as "hsl(30, 3.80%, 10.20%)".
417    ///
418    /// # Example
419    ///
420    /// ```
421    /// use eb_bars::BarPlot;
422    ///
423    /// let mut plot = BarPlot::new();
424    ///
425    /// plot.add_values(&[1., 2., 3.,]);
426    ///
427    /// plot.set_tick_color("LightBlue");
428    ///
429    /// let svg: String = plot.to_svg(1600, 1000);
430    /// ```
431    pub fn set_tick_color(&mut self, color: &'a str) {
432        self.colors.tick = color;
433    }
434
435    /// Set a single color for all bars.
436    ///
437    /// By default, all bars are drawn with a `default` color.
438    /// You can `override` this by setting a new uniform color value.
439    ///
440    /// # Accepted color conventions.
441    ///
442    /// * As its name such as "Red".
443    /// * As an RGB value such as "rgb(29, 28, 27)".
444    /// * As a HEX value such as "#1111FA".
445    /// * As an HSL value such as "hsl(30, 3.80%, 10.20%)".
446    ///
447    /// # Example
448    ///
449    /// ```
450    /// use eb_bars::BarPlot;
451    ///
452    /// let mut plot = BarPlot::new();
453    ///
454    /// plot.add_values(&[1., 2., 3.,]);
455    ///
456    /// plot.set_bar_colors_by_uniform("Green");
457    ///
458    /// let svg: String = plot.to_svg(1600, 1000);
459    /// ```
460    pub fn set_bar_colors_by_uniform(&mut self, color: &'a str) {
461        self.colors.bars.layout = BarColorLayout::Uniform(color);
462    }
463
464    /// Set bar colors by threshold.
465    ///
466    /// By default, all bars are drawn with a `default` color.
467    /// You can `override` this by setting different colors for different thresholds.
468    /// The threshold is as follows: minumum value, less than average, greater than or equal to average and max.
469    ///
470    /// # Accepted color conventions.
471    ///
472    /// * As its name such as "Red".
473    /// * As an RGB value such as "rgb(29, 28, 27)".
474    /// * As a HEX value such as "#1111FA".
475    /// * As an HSL value such as "hsl(30, 3.80%, 10.20%)".
476    ///
477    /// # Avoid if having more than one set of values.
478    ///
479    /// When you add multiple sets of values e.g. calling [`BarPlot::add_values`] multiple times,
480    /// then you might want to use [`BarPlot::add_bar_colors_by_category`] instead.
481    ///
482    /// # Example
483    ///
484    /// ```
485    /// use eb_bars::BarPlot;
486    ///
487    /// let mut plot = BarPlot::new();
488    ///
489    /// plot.add_values(&[1., 2., 3.,]);
490    ///
491    /// // Each color represent how signifacant a value is.
492    /// let min = "Red"; // The lowest value will have its bar colored red.
493    /// let low = "Orange"; // Low values will have their bars be orange.
494    /// let high = "Yellow"; // High values will be yellow.
495    /// let max = "Green"; // Max value (tallest bar) will be green.
496    ///
497    /// plot.set_bar_colors_by_threshold(min, low, high, max);
498    ///
499    /// let svg: String = plot.to_svg(1600, 1000);
500    /// ```
501    pub fn set_bar_colors_by_threshold(&mut self, min: &'a str, low: &'a str, high: &'a str, max: &'a str) {
502        self.colors.bars.layout = BarColorLayout::Threshold((min, low, high, max));
503    }
504
505    /// Add color to last added values.
506    ///
507    /// By default, all bars are drawn with a `default` color.
508    /// You can `override` this by setting different colors for different categories.
509    /// Keep in mind that this only make sens to use if you have called [`BarPlot::add_values`]
510    /// at least 2 times. The two datasets are treated as two distinct categories.
511    /// Note: see [`BarPlot::set_legend`] for displaying the names with their respective color.
512    ///
513    /// # How it operates
514    /// The category added first will have its bars colored first.
515    /// Then, any consecutive call will apply proportional to all consecutive calls to [`BarPlot::add_values`].
516    ///
517    /// In simple terms: if you call [`BarPlot::add_values`] 5 times, then it is required to call this method
518    /// `exactly` 5 times. Each time with its own particular color.
519    ///
520    /// # Accepted color conventions.
521    ///
522    /// * As its name such as "Red".
523    /// * As an RGB value such as "rgb(29, 28, 27)".
524    /// * As a HEX value such as "#1111FA".
525    /// * As an HSL value such as "hsl(30, 3.80%, 10.20%)".
526    ///
527    /// # Avoid if having only one set of values.
528    ///
529    /// If you only add one set of values e.g. calling [`BarPlot::add_values`] one time,
530    /// then it makes no sense using this method. Use any of the other ways to set colors.
531    ///
532    /// # Example
533    ///
534    /// ```
535    /// use eb_bars::BarPlot;
536    ///
537    /// let mut plot = BarPlot::new();
538    ///
539    /// let apples: Vec<f64> = vec![5., 16., 17., 8., 3.];
540    /// let oranges: Vec<f64> = vec![7., 6., 7., 16., 9.];
541    ///
542    /// // Add first set of values.
543    /// plot.add_values(&apples);
544    /// // Adding a second set of values.
545    /// plot.add_values(&oranges);
546    ///
547    /// // First call adds a color to the first category. Red bacuse apples are often red.
548    /// plot.add_bar_colors_by_category("Red");
549    /// // Second call adds a color to the second category. Orange because; oranges are orange. :)
550    /// plot.add_bar_colors_by_category("Orange");
551    ///
552    /// let svg: String = plot.to_svg(1600, 1000);
553    /// ```
554    pub fn add_bar_colors_by_category(&mut self, color: &'a str) {
555        if let BarColorLayout::Category(v) = &mut self.colors.bars.layout {
556            v.push(color);
557        } else {
558            self.colors.bars.layout = BarColorLayout::Category(vec![color]);
559        }
560    }
561
562    /// Add a set of colors for every bar in last added category.
563    ///
564    /// By default, all bars are drawn with a `default` color.
565    /// You can `override` this by adding an array of colors which have same length as the values.
566    ///
567    /// # How it operates
568    /// The category added first will have its bars colored first.
569    /// Then, any consecutive call will apply proportional to all consecutive calls to [`BarPlot::add_values`].
570    ///
571    /// In simple terms: if you call [`BarPlot::add_values`] 5 times, then it is required to call this method
572    /// `exactly` 5 times. Each time with its own particular set of colors.
573    ///
574    /// # Accepted color conventions.
575    ///
576    /// * As its name such as "Red".
577    /// * As an RGB value such as "rgb(29, 28, 27)".
578    /// * As a HEX value such as "#1111FA".
579    /// * As an HSL value such as "hsl(30, 3.80%, 10.20%)".
580    ///
581    /// This method is meant for users that want full control over the bar colors.
582    ///
583    /// # Example
584    ///
585    /// ```
586    /// use eb_bars::BarPlot;
587    ///
588    /// let mut plot = BarPlot::new();
589    ///
590    /// let apples: Vec<f64> = vec![5., 16., 17., 8., 3.];
591    /// let oranges: Vec<f64> = vec![7., 6., 7., 16., 9.];
592    ///
593    /// // Add first set of values.
594    /// plot.add_values(&apples);
595    /// // Adding a second set of values.
596    /// plot.add_values(&oranges);
597    ///
598    /// // First call adds a color to the first category. Red bacuse apples are often red.
599    /// let clr_red = vec!["Red", "Red", "Red", "Red", "Red"];
600    /// plot.add_bar_colors_from_vec(clr_red);
601    /// // Second call adds a color to the second category. Orange because; oranges are orange. :)
602    /// let clr_orange = vec!["Orange", "Orange", "Orange", "Orange", "Orange"];
603    /// plot.add_bar_colors_from_vec(clr_orange);
604    ///
605    /// let svg: String = plot.to_svg(1600, 1000);
606    /// ```
607    pub fn add_bar_colors_from_vec(&mut self, colors: Vec<&'a str>) {
608        if let BarColorLayout::Indexed(v) = &mut self.colors.bars.layout {
609            v.push(colors);
610        } else {
611            self.colors.bars.layout = BarColorLayout::Indexed(vec![colors]);
612        }
613    }
614
615    /// Override any bar with any color.
616    ///
617    /// This one will override the category index and the value (bar) index with a color.
618    /// Say you added 3 sets of values by calling [`BarPlot::add_values`] 3 times.
619    /// This means you have 3 categories times `X` values where `X` is the length of array holding
620    /// each of set of values.
621    ///
622    /// # How it operates
623    /// The category added first will have its category and bars colored zero indexed.
624    /// The first bar in the first category will have index 0-0. The next bar will have 0-1.
625    /// You call this method as many times as you need, passing one color at the time.
626    ///
627    /// Calling [`BarPlot::add_values`] 5 times with 3 values for each set of values,
628    /// then the index for category will be 0-4 (5 indexes ) and the index for values will be 0-2 (3 indexes).
629    ///
630    /// # Accepted color conventions.
631    ///
632    /// * As its name such as "Red".
633    /// * As an RGB value such as "rgb(29, 28, 27)".
634    /// * As a HEX value such as "#1111FA".
635    /// * As an HSL value such as "hsl(30, 3.80%, 10.20%)".
636    ///
637    /// # Avoid if having only one set of values.
638    ///
639    /// If you only add one set of values e.g. calling [`BarPlot::add_values`] one time,
640    /// then it makes no sense using this method. Use any of the other ways to set colors.
641    ///
642    /// # Example
643    ///
644    /// ```
645    /// use eb_bars::BarPlot;
646    ///
647    /// let mut plot = BarPlot::new();
648    ///
649    /// let apples: Vec<f64> = vec![5., 16., 17., 8., 3.];
650    /// let oranges: Vec<f64> = vec![7., 6., 7., 16., 9.];
651    ///
652    /// // Add first set of values.
653    /// plot.add_values(&apples);
654    /// // Adding a second set of values.
655    /// plot.add_values(&oranges);
656    ///
657    /// // First call adds a color to the first category. Red bacuse apples are often red.
658    /// let clr_red = vec!["Red", "Red", "Red", "Red", "Red"];
659    /// plot.add_bar_colors_from_vec(clr_red);
660    /// // Second call adds a color to the second category. Orange because; oranges are orange. :)
661    /// let clr_orange = vec!["Orange", "Orange", "Orange", "Orange", "Orange"];
662    /// plot.add_bar_colors_from_vec(clr_orange);
663    ///
664    /// // Setting the first apple = green.
665    /// plot.add_bar_color_override(0, 0, "Green");
666    /// // Setting the last orange to blue.. :)
667    /// plot.add_bar_color_override(1, 4, "Blue");
668    ///
669    /// let svg: String = plot.to_svg(1600, 1000);
670    /// ```
671    pub fn add_bar_color_override(&mut self, category: usize, bar: usize, color: &'a str) {
672        assert!(
673            category < self.values.len(),
674            "{}\n{}",
675            format!("Can not add category index '{category}' as there are not enough categories added yet."),
676            "This may result in current override being ignored.",
677        );
678
679        self.colors.bars.overrides.push((category, bar, color));
680    }
681
682    /// Show horizontal grid lines.
683    ///
684    /// # Important
685    ///
686    /// Call [`BarPlot::set_scale_range`] first, otherwise there are no values to base the grid on.
687    ///
688    /// # Example
689    ///
690    /// ```
691    /// use eb_bars::BarPlot;
692    ///
693    /// let mut plot = BarPlot::new();
694    ///
695    /// plot.add_values(&[5.0, 16.4, 17.1, 13.7, 8.9, 3.9, 6.3, 9.6]);
696    ///
697    /// // Needed for horizontal (y-grid) lines.
698    /// plot.set_scale_range(0, 20, 2);
699    ///
700    /// plot.set_show_horizontal_lines();
701    ///
702    /// let svg: String = plot.to_svg(1600, 1000);
703    /// ```
704    pub fn set_show_horizontal_lines(&mut self) {
705        self.show.horizontal_lines = true;
706    }
707
708    /// Add custom horizontal grid lines.
709    ///
710    /// # Example
711    ///
712    /// ```
713    /// use eb_bars::BarPlot;
714    ///
715    /// let mut plot = BarPlot::new();
716    ///
717    /// let values = [5.0, 16.4, 17.1, 13.7, 8.9, 3.9, 6.3, 9.6];
718    /// plot.add_values(&values);
719    ///
720    /// // Add a horizontal lines.
721    /// let line_color = "rgb(126, 255, 165)";
722    /// plot.add_horizontal_line_at(25.0, line_color);
723    /// plot.add_horizontal_line_at(50.0, line_color);
724    /// plot.add_horizontal_line_at(75.0, line_color);
725    ///
726    /// let svg: String = plot.to_svg(1600, 1000);
727    /// ```
728    pub fn add_horizontal_line_at(&mut self, p: Percentage, color: &'a str) {
729        self.lines_at.push(LinesAt::Horizontal(p, color));
730    }
731
732    /// Show vertical grid lines.
733    ///
734    /// # Important
735    ///
736    ///  Call [`BarPlot::set_bin_markers`] first, otherwise there are no values to base the grid on.
737    ///
738    /// # Example
739    ///
740    /// ```
741    /// use eb_bars::BarPlot;
742    ///
743    /// let mut plot = BarPlot::new();
744    ///
745    /// let values = [5.0, 16.4, 17.1, 13.7, 8.9, 3.9, 6.3, 9.6];
746    /// plot.add_values(&values);
747    ///
748    /// // Needed for vertical (x-grid) lines.
749    /// let markers: Vec<String> = (0..values.len()).map(|i| (i).to_string()).collect();
750    /// let markers = markers.iter().map(|s| s.as_str()).collect::<Vec<&str>>();
751    /// plot.set_bin_markers(&markers);
752    ///
753    /// plot.set_show_vertical_lines();
754    ///
755    /// let svg: String = plot.to_svg(1600, 1000);
756    /// ```
757    pub fn set_show_vertical_lines(&mut self) {
758        self.show.vertical_lines = true;
759    }
760
761    /// Add custom vertical grid lines.
762    ///
763    /// # Example
764    ///
765    /// ```
766    /// use eb_bars::BarPlot;
767    ///
768    /// let mut plot = BarPlot::new();
769    ///
770    /// let values = [5.0, 16.4, 17.1, 13.7, 8.9, 3.9, 6.3, 9.6];
771    /// plot.add_values(&values);
772    ///
773    /// // Add vertical lines.
774    /// let line_color = "rgb(126, 255, 165)";
775    /// plot.add_vertical_line_at(25.0, line_color);
776    /// plot.add_vertical_line_at(50.0, line_color);
777    /// plot.add_vertical_line_at(75.0, line_color);
778    ///
779    /// let svg: String = plot.to_svg(1600, 1000);
780    /// ```
781    pub fn add_vertical_line_at(&mut self, p: Percentage, color: &'a str) {
782
783        self.lines_at.push(LinesAt::Vertical(p, color));
784    }
785
786    /// Set size of the barplot size (relative to the canvas/frame).
787    ///
788    /// By default, the barchart part of the image will take up the full width and length of the frame.
789    /// However, by adding literally anything to the barplot, you most likely want to change the size of the plot.
790    /// If you skip this method, the text, ticks, markers, legend etc. will not be inside the viewbox of the canvas.
791    ///
792    /// # How it operates
793    ///
794    /// We only work with percentage values when defining sizes. A width of 100 means it takes up 100% of the width.
795    /// A height of 100 will do the same, but for height.
796    /// Same goes for offset. A horizontal offset of 10 means that the offset is pushed 10% from the left.
797    /// A vertical offset of 10 means that the offset is pushed 10% from the top.
798    /// An offset with value 100 means the offset is pushed all the way to the right (horizontal) or bottom (vertical).
799    ///
800    /// # Example
801    ///
802    /// ```
803    /// use eb_bars::BarPlot;
804    ///
805    /// let mut plot = BarPlot::new();
806    ///
807    /// plot.add_values(&[1., 2., 3.,]);
808    ///
809    /// let width = 90.0; // Set width to 90%,
810    /// let horizontal_offset = 65.0; // Move 65% right
811    /// let height = 85.0; // Set height to 85%
812    /// let vertical_offset = 40.0; // Move 40% down.
813    /// plot.set_plot_window_size(width, horizontal_offset, height, vertical_offset);
814    ///
815    /// let svg: String = plot.to_svg(1600, 1000);
816    /// ```
817    pub fn set_plot_window_size(
818        &mut self,
819        x_length: Percentage,
820        x_offset: Percentage,
821        y_length: Percentage,
822        y_offset: Percentage
823    ) {
824        assert!(x_length <= 100.0 && x_offset <= 100.0, "plot window width cannot exceed 100%");
825        assert!(y_length <= 100.0 && y_offset <= 100.0, "plot window height cannot exceed 100%");
826
827        self.layout.plot_window_scale = Some((x_length, x_offset, y_length, y_offset));
828    }
829
830    /// Set a scale for the barchart.
831    ///
832    /// By default, the scale is calculated using the maximum and minimum value throughout all data.
833    /// However, by setting the scale manually we get a more presentable plot.
834    /// Pass whole numbers (can be negative) as the scale minimum and scale maximum.
835    /// The 3rd parameter is the gap between each number on the scale e.g. the step.
836    /// The step must be a positive number as it is an increasing number starting from minimum.
837    ///
838    /// # Example
839    ///
840    /// ```
841    /// use eb_bars::BarPlot;
842    ///
843    /// let mut plot = BarPlot::new();
844    ///
845    /// plot.add_values(&[5.0, 16.4, 17.1, 13.7, 8.9, 3.9, 6.3, 9.6]);
846    ///
847    /// let min = 0;
848    /// let max = 20;
849    /// let step = 2;
850    ///
851    /// plot.set_scale_range(min, max, step);
852    ///
853    /// let svg: String = plot.to_svg(1600, 1000);
854    /// ```
855    pub fn set_scale_range(&mut self, min: i64, max: i64, step: u64) {
856        self.layout.scale_range = Some((min, max, step as usize));
857    }
858
859    /// Set the labels and markers for each bin/bucket on the x-axis.
860    ///
861    /// Note: Passing an array with fewer bin markers than added values will cause some bins to be un-labeled.
862    /// To make sure everything is correct, it is recommended to pass the same amount of markers as values.
863    ///
864    /// # Example
865    ///
866    /// ```
867    /// use eb_bars::BarPlot;
868    ///
869    /// let mut plot = BarPlot::new();
870    ///
871    /// let absence_boys = [5., 3., 8., 4., 7.];
872    /// let absence_girls = [4., 1., 2., 3., 1.];
873    /// let weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday",];
874    ///
875    /// plot.add_values(&absence_boys);
876    /// plot.add_values(&absence_girls);
877    /// plot.set_bin_markers(&weekdays);
878    ///
879    /// let svg: String = plot.to_svg(1600, 1000);
880    /// ```
881    pub fn set_bin_markers(&mut self, markers: &'a [&'a str]) {
882        self.markers = Some(markers);
883    }
884
885    /// Place the bin markers at the middle of each bin/bucket instead of to the left.
886    ///
887    /// By default, the bin markers are placed on the left side in between each bin.
888    /// You can have the markers placed either `left`, `middle` or `right` depending on usecase.
889    ///
890    /// Note: check out [`BarPlot::set_bin_markers_left`] and [`BarPlot::set_bin_markers_right`].
891    ///
892    /// # Example
893    ///
894    /// ```
895    /// use eb_bars::BarPlot;
896    ///
897    /// let mut plot = BarPlot::new();
898    ///
899    /// let values = [1., 2., 3.];
900    /// plot.add_values(&values);
901    ///
902    /// let markers: Vec<String> = (0..values.len()).map(|i| (i).to_string()).collect();
903    /// let markers = markers.iter().map(|s| s.as_str()).collect::<Vec<&str>>();
904    /// plot.set_bin_markers(&markers);
905    ///
906    /// plot.set_bin_markers_middle();
907    ///
908    /// let svg: String = plot.to_svg(1600, 1000);
909    /// ```
910    pub fn set_bin_markers_middle(&mut self) {
911        self.layout.bin_marker_position = BinMarkerPosition::Middle;
912    }
913
914    /// Place the bin markers to the left of each bin/bucket.
915    ///
916    /// By default, the bin markers are already placed on the left side in between each bin.
917    /// This method can be used to _reset_ an eventual change.
918    /// You can have the markers placed either `left`, `middle` or `right` depending on usecase.
919    ///
920    /// Note: check out [`BarPlot::set_bin_markers_middle`] and [`BarPlot::set_bin_markers_right`].
921    ///
922    /// # Example
923    ///
924    /// ```
925    /// use eb_bars::BarPlot;
926    ///
927    /// let mut plot = BarPlot::new();
928    ///
929    /// let values = [1., 2., 3.];
930    /// plot.add_values(&values);
931    ///
932    /// let markers: Vec<String> = (0..values.len()).map(|i| (i).to_string()).collect();
933    /// let markers = markers.iter().map(|s| s.as_str()).collect::<Vec<&str>>();
934    /// plot.set_bin_markers(&markers);
935    ///
936    /// // Setting markers at middle.
937    /// plot.set_bin_markers_middle();
938    /// // Then back to left side.
939    /// plot.set_bin_markers_left();
940    ///
941    /// let svg: String = plot.to_svg(1600, 1000);
942    /// ```
943    pub fn set_bin_markers_left(&mut self) {
944        self.layout.bin_marker_position = BinMarkerPosition::Left;
945    }
946
947    /// Place the bin markers to the right of each bin/bucket instead of to the left.
948    ///
949    /// By default, the bin markers are placed on the left side in between each bin.
950    /// You can have the markers placed either `left`, `middle` or `right` depending on usecase.
951    ///
952    /// Note: check out [`BarPlot::set_bin_markers_left`] and [`BarPlot::set_bin_markers_middle`].
953    ///
954    /// # Example
955    ///
956    /// ```
957    /// use eb_bars::BarPlot;
958    ///
959    /// let mut plot = BarPlot::new();
960    ///
961    /// let values = [1., 2., 3.];
962    /// plot.add_values(&values);
963    ///
964    /// let markers: Vec<String> = (0..values.len()).map(|i| (i).to_string()).collect();
965    /// let markers = markers.iter().map(|s| s.as_str()).collect::<Vec<&str>>();
966    /// plot.set_bin_markers(&markers);
967    ///
968    /// plot.set_bin_markers_right();
969    ///
970    /// let svg: String = plot.to_svg(1600, 1000);
971    /// ```
972    pub fn set_bin_markers_right(&mut self) {
973        self.layout.bin_marker_position = BinMarkerPosition::Right;
974    }
975
976    /// Introduce a `gap` between every bar.
977    ///
978    /// The gap is calculated using a percentage.
979    /// A gap of 0 means there is no gap/air between bars.
980    /// A gap of 50 means that the bar and the gap will take up the same width.
981    /// A gap of 100 means that the gap will take up all space and so the bar becomes invisible.
982    ///
983    /// Note: check out [`BarPlot::set_bin_gap`] for gap only betweem bins.
984    ///
985    /// # Example
986    ///
987    /// ```
988    /// use eb_bars::BarPlot;
989    ///
990    /// let mut plot = BarPlot::new();
991    ///
992    /// plot.add_values(&[1., 2., 3.]);
993    /// plot.add_values(&[4., 5., 6.]);
994    ///
995    /// let gap = 30.0; // The gap will take up 30% of the space, leaving 70% for bar.
996    /// plot.set_bar_gap(gap);
997    ///
998    /// let svg: String = plot.to_svg(1600, 1000);
999    /// ```
1000    pub fn set_bar_gap(&mut self, gap: Percentage) {
1001        self.layout.bar_gap = gap;
1002    }
1003
1004    /// Introduce a `gap` between every bin/bucket.
1005    ///
1006    /// The gap is calculated using a percentage.
1007    /// A gap of 0 means there is no gap/air between bins.
1008    /// A gap of 50 means that the bin and the gap will take up the same width.
1009    /// A gap of 100 means that the gap will take up all space and so the bin squashed into nothing.
1010    ///
1011    /// Note: check out [`BarPlot::set_bar_gap`] for gap betweem bars.
1012    ///
1013    /// # Example
1014    ///
1015    /// ```
1016    /// use eb_bars::BarPlot;
1017    ///
1018    /// let mut plot = BarPlot::new();
1019    ///
1020    /// plot.add_values(&[1., 2., 3.]);
1021    /// plot.add_values(&[4., 5., 6.]);
1022    ///
1023    /// let gap = 30.0; // The gap will take up 30% of the space, leaving 70% for bin.
1024    /// plot.set_bin_gap(gap);
1025    ///
1026    /// let svg: String = plot.to_svg(1600, 1000);
1027    /// ```
1028    pub fn set_bin_gap(&mut self, gap: Percentage) {
1029        self.layout.bin_gap = gap;
1030    }
1031
1032    /// Set length for ticks on the y axis.
1033    ///
1034    /// The length is calculated using a percentage.
1035    /// A length of 0 means the tick will not be generated.
1036    /// A length of 10 means that the tick will be of a _somewhat_ _normal_ length.
1037    /// A length of greater than 10 means that the tick will be long.
1038    /// Try different lengths to find the best length for your usecase.
1039    ///
1040    /// # Example
1041    ///
1042    /// ```
1043    /// use eb_bars::BarPlot;
1044    ///
1045    /// let mut plot = BarPlot::new();
1046    ///
1047    /// plot.add_values(&[1., 2., 3.]);
1048    ///
1049    /// let len = 20.0; // The tick length will be of considerate length.
1050    /// plot.set_y_axis_tick_length(len);
1051    ///
1052    /// let svg: String = plot.to_svg(1600, 1000);
1053    /// ```
1054    pub fn set_y_axis_tick_length(&mut self, p: Percentage) {
1055        self.layout.y_axis_tick_length = p;
1056    }
1057
1058    /// Set length for ticks on the x axis.
1059    ///
1060    /// The length is calculated using a percentage.
1061    /// A length of 0 means the tick will not be generated.
1062    /// A length of 10 means that the tick will be of a _somewhat_ _normal_ length.
1063    /// A length of greater than 10 means that the tick will be long.
1064    /// Try different lengths to find the best length for your usecase.
1065    ///
1066    /// # Example
1067    ///
1068    /// ```
1069    /// use eb_bars::BarPlot;
1070    ///
1071    /// let mut plot = BarPlot::new();
1072    ///
1073    /// plot.add_values(&[1., 2., 3.]);
1074    ///
1075    /// let len = 20.0; // The tick length will be of considerate length.
1076    /// plot.set_x_axis_tick_length(len);
1077    ///
1078    /// let svg: String = plot.to_svg(1600, 1000);
1079    /// ```
1080    pub fn set_x_axis_tick_length(&mut self, p: Percentage) {
1081        self.layout.x_axis_tick_length = p;
1082    }
1083
1084    /// Anchor bars at zero instead of the floor.
1085    /// This will make negative bars grow downwards.
1086    /// If your dataset contains only negative values, then this might not make sense to use.
1087    /// Rather, `use this when your dataset is likely to contain both positive and negative values`.
1088    ///
1089    /// An area where it makes sens is when visualizing temperature differences. :)
1090    ///
1091    /// By default, bars are anchored at the floor of the barchart.
1092    /// However, you might want negative values to stand out by having them point downwards.
1093    /// This method will apply anchoring bars at the zero line instead of the floor.
1094    ///
1095    /// # Important
1096    /// Call [`BarPlot::set_scale_range`] first, and make sure to set `min < 0` and `max >= 0`.
1097    /// Otherwise, you ~might~ will get a barchart that looks goofy. Consider yourself warned.
1098    ///
1099    /// # Example
1100    ///
1101    /// ```
1102    /// use eb_bars::BarPlot;
1103    ///
1104    /// let mut plot = BarPlot::new();
1105    ///
1106    /// plot.add_values(&[5.0, -16.4, 17.1, 13.7, 8.9, 3.9, -6.3, 9.6]);
1107    ///
1108    /// // Adding some extra tweaks that makes this example more clear.
1109    /// let min = -20;
1110    /// let max = 20;
1111    /// let step = 2;
1112    /// plot.set_scale_range(min, max, step);
1113    /// plot.set_plot_window_size(90.0, 80.0, 83.0, 50.0);
1114    ///
1115    /// // Negative bars will now grow downwards instead of upwards. :)
1116    /// plot.set_negative_bars_go_down();
1117    ///
1118    /// let svg: String = plot.to_svg(1600, 1000);
1119    /// ```
1120    pub fn set_negative_bars_go_down(&mut self) {
1121        self.layout.negative_bars_go_down = true;
1122    }
1123
1124    /// Apply text on the left side of the plot window.
1125    ///
1126    /// By default, the plot window takes up the whole window.
1127    /// For the text to be visible, we need to scale down the plot first with [`BarPlot::set_plot_window_size`].
1128    ///
1129    /// The text will by default be offset from the plot window with a sane value.
1130    /// If you want to override this value, you can set a custom offset with [`BarPlot::set_text_left_offset`].
1131    ///
1132    /// # Example
1133    ///
1134    /// ```
1135    /// use eb_bars::BarPlot;
1136    ///
1137    /// let mut plot = BarPlot::new();
1138    ///
1139    /// plot.add_values(&[1., 2., 3.]);
1140    ///
1141    /// // Scale down the plot figure size so text can become be visible.
1142    /// plot.set_plot_window_size(85.0, 65.0, 80.0, 40.0);
1143    ///
1144    /// plot.set_text_left("This is some text.");
1145    ///
1146    /// let svg: String = plot.to_svg(1600, 1000);
1147    /// ```
1148    pub fn set_text_left(&mut self, text: &'a str) {
1149        self.plot_text.left = Some(text);
1150    }
1151
1152    /// Apply offset for text on the left side of the plot window.
1153    ///
1154    /// The offset is calculated percent wise from the frame towards the plot window.
1155    /// The higher the percentage the closer the text moves towards the plot window.
1156    ///
1157    /// Note: you need to explicitly apply text first with [`BarPlot::set_text_lef.
1158    ///
1159    /// # Example
1160    ///
1161    /// ```
1162    /// use eb_bars::BarPlot;
1163    ///
1164    /// let mut plot = BarPlot::new();
1165    ///
1166    /// plot.add_values(&[1., 2., 3.]);
1167    ///
1168    /// // Scale down the plot figure size so text can become be visible.
1169    /// plot.set_plot_window_size(85.0, 65.0, 80.0, 40.0);
1170    ///
1171    /// plot.set_text_left("This is some text.");
1172    ///
1173    /// let percent = 30.0;
1174    /// plot.set_text_left_offset(percent);
1175    ///
1176    /// let svg: String = plot.to_svg(1600, 1000);
1177    /// ```
1178    pub fn set_text_left_offset(&mut self, offset: Percentage) {
1179        self.plot_text.left_offset = Some(offset);
1180    }
1181
1182    /// Apply text on the right side of the plot window.
1183    ///
1184    /// By default, the plot window takes up the whole window.
1185    /// For the text to be visible, we need to scale down the plot first with [`BarPlot::set_plot_window_size`].
1186    ///
1187    /// The text will by default be offset from the plot window with a sane value.
1188    /// If you want to override this value, you can set a custom offset with [`BarPlot::set_text_right_offset`].
1189    ///
1190    /// # Example
1191    ///
1192    /// ```
1193    /// use eb_bars::BarPlot;
1194    ///
1195    /// let mut plot = BarPlot::new();
1196    ///
1197    /// plot.add_values(&[1., 2., 3.]);
1198    ///
1199    /// // Scale down the plot figure size so text can become be visible.
1200    /// plot.set_plot_window_size(85.0, 65.0, 80.0, 40.0);
1201    ///
1202    /// plot.set_text_right("This is some text.");
1203    ///
1204    /// let svg: String = plot.to_svg(1600, 1000);
1205    /// ```
1206    pub fn set_text_right(&mut self, text: &'a str) {
1207        self.plot_text.right = Some(text);
1208    }
1209
1210    /// Apply offset for text on the right side of the plot window.
1211    ///
1212    /// The offset is calculated percent wise from the frame towards the plot window.
1213    /// The higher the percentage the closer the text moves towards the plot window.
1214    ///
1215    /// Note: you need to explicitly apply text first with [`BarPlot::set_text_right`].
1216    ///
1217    /// # Example
1218    ///
1219    /// ```
1220    /// use eb_bars::BarPlot;
1221    ///
1222    /// let mut plot = BarPlot::new();
1223    ///
1224    /// plot.add_values(&[1., 2., 3.]);
1225    ///
1226    /// // Scale down the plot figure size so text can become be visible.
1227    /// plot.set_plot_window_size(85.0, 65.0, 80.0, 40.0);
1228    ///
1229    /// plot.set_text_right("This is some text.");
1230    ///
1231    /// let percent = 30.0;
1232    /// plot.set_text_right_offset(percent);
1233    ///
1234    /// let svg: String = plot.to_svg(1600, 1000);
1235    /// ```
1236    pub fn set_text_right_offset(&mut self, offset: Percentage) {
1237        self.plot_text.right_offset = Some(offset);
1238    }
1239
1240    /// Apply text underneath the plot window.
1241    ///
1242    /// By default, the plot window takes up the whole window.
1243    /// For the text to be visible, we need to scale down the plot first with [`BarPlot::set_plot_window_size`].
1244    ///
1245    /// The text will by default be offset from the plot window with a sane value.
1246    /// If you want to override this value, you can set a custom offset with [`BarPlot::set_text_bottom_offset`].
1247    ///
1248    /// # Example
1249    ///
1250    /// ```
1251    /// use eb_bars::BarPlot;
1252    ///
1253    /// let mut plot = BarPlot::new();
1254    ///
1255    /// plot.add_values(&[1., 2., 3.]);
1256    ///
1257    /// // Scale down the plot figure size so text can become be visible.
1258    /// plot.set_plot_window_size(85.0, 65.0, 80.0, 40.0);
1259    ///
1260    /// plot.set_text_bottom("This is some text.");
1261    ///
1262    /// let svg: String = plot.to_svg(1600, 1000);
1263    /// ```
1264    pub fn set_text_bottom(&mut self, text: &'a str) {
1265        self.plot_text.bottom = Some(text);
1266    }
1267
1268    /// Apply offset for text underneath the plot window.
1269    ///
1270    /// The offset is calculated percent wise from the frame towards the plot window.
1271    /// The higher the percentage the closer the text moves towards the plot window.
1272    ///
1273    /// Note: you need to explicitly apply text first with [`BarPlot::set_text_bottom`].
1274    ///
1275    /// # Example
1276    ///
1277    /// ```
1278    /// use eb_bars::BarPlot;
1279    ///
1280    /// let mut plot = BarPlot::new();
1281    ///
1282    /// plot.add_values(&[1., 2., 3.]);
1283    ///
1284    /// // Scale down the plot figure size so text can become be visible.
1285    /// plot.set_plot_window_size(85.0, 65.0, 80.0, 40.0);
1286    ///
1287    /// plot.set_text_bottom("This is some text.");
1288    ///
1289    /// let percent = 30.0;
1290    /// plot.set_text_bottom_offset(percent);
1291    ///
1292    /// let svg: String = plot.to_svg(1600, 1000);
1293    /// ```
1294    pub fn set_text_bottom_offset(&mut self, offset: Percentage) {
1295        self.plot_text.bottom_offset = Some(offset);
1296    }
1297
1298    /// Apply text above the plot window.
1299    ///
1300    /// By default, the plot window takes up the whole window.
1301    /// For the text to be visible, we need to scale down the plot first with [`BarPlot::set_plot_window_size`].
1302    ///
1303    /// The text will by default be offset from the plot window with a sane value.
1304    /// If you want to override this value, you can set a custom offset with [`BarPlot::set_text_top_offset`].
1305    ///
1306    /// # Example
1307    ///
1308    /// ```
1309    /// use eb_bars::BarPlot;
1310    ///
1311    /// let mut plot = BarPlot::new();
1312    ///
1313    /// plot.add_values(&[1., 2., 3.]);
1314    ///
1315    /// // Scale down the plot figure size so text can become be visible.
1316    /// plot.set_plot_window_size(85.0, 65.0, 80.0, 40.0);
1317    ///
1318    /// plot.set_text_top("This is some text.");
1319    ///
1320    /// let svg: String = plot.to_svg(1600, 1000);
1321    /// ```
1322    pub fn set_text_top(&mut self, text: &'a str) {
1323        self.plot_text.top = Some(text);
1324    }
1325
1326    /// Apply offset for text above the plot window.
1327    ///
1328    /// The offset is calculated percent wise from the frame towards the plot window.
1329    /// The higher the percentage the closer the text moves towards the plot window.
1330    ///
1331    /// Note: you need to explicitly apply text first with [`BarPlot::set_text_top`].
1332    ///
1333    /// # Example
1334    ///
1335    /// ```
1336    /// use eb_bars::BarPlot;
1337    ///
1338    /// let mut plot = BarPlot::new();
1339    ///
1340    /// plot.add_values(&[1., 2., 3.]);
1341    ///
1342    /// // Scale down the plot figure size so text can become be visible.
1343    /// plot.set_plot_window_size(85.0, 65.0, 80.0, 40.0);
1344    ///
1345    /// plot.set_text_top("This is some text.");
1346    ///
1347    /// let percent = 30.0;
1348    /// plot.set_text_top_offset(percent);
1349    ///
1350    /// let svg: String = plot.to_svg(1600, 1000);
1351    /// ```
1352    pub fn set_text_top_offset(&mut self, offset: Percentage) {
1353        self.plot_text.top_offset = Some(offset);
1354    }
1355
1356    /// Apply a legend with category names and their corresponding colors.
1357    ///
1358    /// When calling [`BarPlot::add_values`] multiple times each time adding a set of values (categories),
1359    /// you most likely want a legend to label each category by name and color.
1360    ///
1361    /// Note: The legend will use the colors added with [`BarPlot::add_bar_colors_by_category`],
1362    /// remember to call this method once for each category added.
1363    ///
1364    /// # Example
1365    ///
1366    /// ```
1367    /// use eb_bars::BarPlot;
1368    ///
1369    /// let mut plot = BarPlot::new();
1370    ///
1371    /// let apples: Vec<f64> = vec![5., 16., 17., 8., 3.];
1372    /// let oranges: Vec<f64> = vec![7., 6., 7., 16., 9.];
1373    ///
1374    /// // Add first set of values.
1375    /// plot.add_values(&apples);
1376    /// // Adding a second set of values.
1377    /// plot.add_values(&oranges);
1378    ///
1379    /// // First call adds a color to the first category. Red bacuse apples are often red.
1380    /// plot.add_bar_colors_by_category("Red");
1381    /// // Second call adds a color to the second category. Orange because; oranges are orange. :)
1382    /// plot.add_bar_colors_by_category("Orange");
1383    ///
1384    /// // Adding the labels (colors are already added) for legend.
1385    /// let categories = ["Apples", "Oranges"];
1386    /// // Applying the legend.
1387    /// plot.set_legend(&categories);
1388    ///
1389    /// let svg: String = plot.to_svg(1600, 1000);
1390    /// ```
1391    pub fn set_legend(&mut self, categories: &'a [&'a str]) {
1392        self.legend.categories = Some(categories);
1393    }
1394
1395    /// Set legend position.
1396    ///
1397    /// When calling [`BarPlot::set_legend`], its position is calculated using percentage.
1398    /// By default, the legend is positioned on the far right side and close to the top.
1399    /// You can override the default position by passing two values.
1400    /// First value is the absolute offset from left to right,
1401    /// the second value is the absolute offset from top to bottom.
1402    ///
1403    /// An offset X = 50 and offset Y = 50 will roughly position the legend in the center of the canvas.
1404    /// More precisely, the top left corner of the legend itself will be pinned on that position.
1405    ///
1406    /// Note: you might want to resize the plot window to accomodate for the legend if you want it
1407    /// to be drawn outside the plot, otherwise it will be drawn on top of the plot figure.
1408    /// Check out [`BarPlot::set_plot_window_size`] for that.
1409    ///
1410    /// # Example
1411    ///
1412    /// ```
1413    /// use eb_bars::BarPlot;
1414    ///
1415    /// let mut plot = BarPlot::new();
1416    ///
1417    /// let apples: Vec<f64> = vec![5., 16., 17., 8., 3.];
1418    /// let oranges: Vec<f64> = vec![7., 6., 7., 16., 9.];
1419    ///
1420    /// // Add first set of values.
1421    /// plot.add_values(&apples);
1422    /// // Adding a second set of values.
1423    /// plot.add_values(&oranges);
1424    ///
1425    /// // First call adds a color to the first category. Red bacuse apples are often red.
1426    /// plot.add_bar_colors_by_category("Red");
1427    /// // Second call adds a color to the second category. Orange because; oranges are orange. :)
1428    /// plot.add_bar_colors_by_category("Orange");
1429    ///
1430    /// // Adding the labels (colors are already added) for legend.
1431    /// let categories = ["Apples", "Oranges"];
1432    /// // Applying the legend.
1433    /// plot.set_legend(&categories);
1434    ///
1435    /// // Make room for legend on the right side by shrinking plot window.
1436    /// plot.set_plot_window_size(80.0, 30.0, 85.0, 40.0);
1437    ///
1438    /// let offset_x = 90.0;
1439    /// let offset_y = 22.0;
1440    /// plot.set_legend_position(offset_x, offset_y);
1441    ///
1442    /// let svg: String = plot.to_svg(1600, 1000);
1443    /// ```
1444    pub fn set_legend_position(&mut self, x: Percentage, y: Percentage) {
1445        self.legend.position = Some((x,y));
1446    }
1447
1448    /// Apply a custom font-size for all text.
1449    ///
1450    /// By default, all text and numbers are rendered with a default font-size. This can be overriden.
1451    /// The font-size is calculated using a percentage value.
1452    /// A font-size of size 100 (100%) will not affect the text as it is the default.
1453    /// You can either increase the size by passing a >100 value or decrease it with <100.
1454    ///
1455    /// # Example
1456    ///
1457    /// ```
1458    /// use eb_bars::BarPlot;
1459    ///
1460    /// let mut plot = BarPlot::new();
1461    ///
1462    /// plot.add_values(&[1., 2., 3.,]);
1463    ///
1464    /// // Scale down the plot figure size so text can become be visible.
1465    /// plot.set_plot_window_size(85.0, 65.0, 80.0, 40.0);
1466    ///
1467    /// // Apply some text.
1468    /// plot.set_text_left("This is some text.");
1469    ///
1470    /// // Increase font-size using a percentage greater than 100%.
1471    /// let size = 130.0;
1472    /// plot.set_font_size(size);
1473    ///
1474    /// let svg: String = plot.to_svg(1600, 1000);
1475    /// ```
1476    pub fn set_font_size(&mut self, p: Percentage) {
1477        self.layout.font_size = p;
1478    }
1479
1480    /// Apply a border around the canvas.
1481    ///
1482    /// # Example
1483    ///
1484    /// ```
1485    /// use eb_bars::BarPlot;
1486    ///
1487    /// let mut plot = BarPlot::new();
1488    ///
1489    /// plot.add_values(&[1., 2., 3.]);
1490    ///
1491    /// // Shrink plot so that it becomes clear that the border goes around the canvas.
1492    /// plot.set_plot_window_size(80.0, 30.0, 85.0, 40.0);
1493    ///
1494    /// plot.set_show_window_border();
1495    ///
1496    /// let svg: String = plot.to_svg(1600, 1000);
1497    /// ```
1498    pub fn set_show_window_border(&mut self) {
1499        self.show.window_border = true;
1500    }
1501
1502    /// Apply a border around the plot.
1503    ///
1504    /// # Example
1505    ///
1506    /// ```
1507    /// use eb_bars::BarPlot;
1508    ///
1509    /// let mut plot = BarPlot::new();
1510    ///
1511    /// plot.add_values(&[1., 2., 3.]);
1512    ///
1513    /// // Shrink plot so that it becomes clear that the border goes around the plot window.
1514    /// plot.set_plot_window_size(80.0, 30.0, 85.0, 40.0);
1515    ///
1516    /// plot.set_show_plot_border();
1517    ///
1518    /// let svg: String = plot.to_svg(1600, 1000);
1519    /// ```
1520    pub fn set_show_plot_border(&mut self) {
1521        self.show.plot_border = true;
1522    }
1523
1524    /// Generate the final svg image of all applied content.
1525    ///
1526    /// When you are satisfied with all the tweaks in above methods, it is time to generate the final svg.
1527    /// Keep in mind that you can still add or re-apply changes on the same object after calling this method.
1528    /// Changes will be applied and included on the following call to this method.
1529    ///
1530    /// # Example
1531    ///
1532    /// ```
1533    /// use eb_bars::BarPlot;
1534    ///
1535    /// let mut plot = BarPlot::new();
1536    ///
1537    /// plot.add_values(&[1., 2., 3.]);
1538    ///
1539    /// let mut svg: String = plot.to_svg(1600, 1000);
1540    ///
1541    /// // Add a new configuration (just resize plot window).
1542    /// plot.set_plot_window_size(80.0, 30.0, 85.0, 40.0);
1543    ///
1544    /// // Lets overwrite the old plot with this new configuration.
1545    /// svg = plot.to_svg(1600, 1000);
1546    /// ```
1547    pub fn to_svg(&mut self, width: u32, height: u32) -> String {
1548        assert!(!self.values.is_empty(), "Can not generate plot without any values..");
1549
1550        self.size = (width, height);
1551
1552        let n_categories = self.values.len();
1553        match &mut self.colors.bars.layout {
1554            BarColorLayout::Category(colors) => {
1555                let n_colors = colors.len();
1556                assert_eq!(
1557                    n_categories,
1558                    n_colors,
1559                    "Got {n_categories} categories and {n_colors} colors.",
1560                );
1561            }
1562            BarColorLayout::Indexed(matrix) => {
1563                let n_color_vectors = matrix.len();
1564                assert_eq!(
1565                    n_categories,
1566                    n_color_vectors,
1567                    "Got {n_categories} categories and {n_color_vectors} color vectors.",
1568                );
1569
1570                for (i, colors) in matrix.iter().enumerate() {
1571                    let values = self.values[i].len();
1572                    let n_colors = colors.len();
1573                    assert_eq!(
1574                        values,
1575                        n_colors,
1576                        "Got {values} values and {n_colors} colors for category {i}.",
1577                    );
1578                }
1579            }
1580            // No need to do assertion on remaining variants..
1581            _ => (),
1582        }
1583
1584        svg::render(self)
1585    }
1586}
1587
1588#[cfg(test)]
1589mod tests {
1590    // All other tests are in directory ./tests
1591
1592    use std::fs;
1593
1594    use toml;
1595
1596    use super::{VERSION, REPOSITORY};
1597
1598    #[test]
1599    fn version_and_repo() {
1600        // When updating Cargo.toml, make sure to update corresponding values in src files as well.
1601        let contents = fs::read_to_string("Cargo.toml").unwrap();
1602        let value = contents.parse::<toml::Table>().unwrap();
1603
1604        let version = value["package"]["version"].as_str().unwrap();
1605        assert_eq!(version, VERSION);
1606
1607        let repository = value["package"]["repository"].as_str().unwrap();
1608        assert_eq!(repository, REPOSITORY);
1609    }
1610}