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