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.1";
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_text_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        // Will always select the bar from the last added category e.g. after most recent BarPlot.add_values() call.
673        assert!(
674            !self.values.is_empty(),
675            "Can't override bar '{bar}' with color '{color}', because no bars (values) have been added yet."
676        );
677        self.colors.bars.overrides.push((category, bar, color));
678    }
679
680    /// Show horizontal grid lines.
681    ///
682    /// # Important
683    ///
684    /// Call [`BarPlot::set_scale_range`] first, otherwise there are no values to base the grid on.
685    ///
686    /// # Example
687    ///
688    /// ```
689    /// use eb_bars::BarPlot;
690    ///
691    /// let mut plot = BarPlot::new();
692    ///
693    /// plot.add_values(&[5.0, 16.4, 17.1, 13.7, 8.9, 3.9, 6.3, 9.6]);
694    ///
695    /// // Needed for horizontal (y-grid) lines.
696    /// plot.set_scale_range(0, 20, 2);
697    ///
698    /// plot.set_show_horizontal_lines();
699    ///
700    /// let svg: String = plot.to_svg(1600, 1000);
701    /// ```
702    pub fn set_show_horizontal_lines(&mut self) {
703        self.show.horizontal_lines = true;
704    }
705
706    /// Add custom horizontal grid lines.
707    ///
708    /// # Example
709    ///
710    /// ```
711    /// use eb_bars::BarPlot;
712    ///
713    /// let mut plot = BarPlot::new();
714    ///
715    /// let values = [5.0, 16.4, 17.1, 13.7, 8.9, 3.9, 6.3, 9.6];
716    /// plot.add_values(&values);
717    ///
718    /// // Add a horizontal lines.
719    /// let line_color = "rgb(126, 255, 165)";
720    /// plot.add_horizontal_line_at(25.0, line_color);
721    /// plot.add_horizontal_line_at(50.0, line_color);
722    /// plot.add_horizontal_line_at(75.0, line_color);
723    ///
724    /// let svg: String = plot.to_svg(1600, 1000);
725    /// ```
726    pub fn add_horizontal_line_at(&mut self, p: Percentage, color: &'a str) {
727        self.lines_at.push(LinesAt::Horizontal(p, color));
728    }
729
730    /// Show vertical grid lines.
731    ///
732    /// # Important
733    ///
734    ///  Call [`BarPlot::set_bin_markers`] first, otherwise there are no values to base the grid on.
735    ///
736    /// # Example
737    ///
738    /// ```
739    /// use eb_bars::BarPlot;
740    ///
741    /// let mut plot = BarPlot::new();
742    ///
743    /// let values = [5.0, 16.4, 17.1, 13.7, 8.9, 3.9, 6.3, 9.6];
744    /// plot.add_values(&values);
745    ///
746    /// // Needed for vertical (x-grid) lines.
747    /// let markers: Vec<String> = (0..values.len()).map(|i| (i).to_string()).collect();
748    /// let markers = markers.iter().map(|s| s.as_str()).collect::<Vec<&str>>();
749    /// plot.set_bin_markers(&markers);
750    ///
751    /// plot.set_show_vertical_lines();
752    ///
753    /// let svg: String = plot.to_svg(1600, 1000);
754    /// ```
755    pub fn set_show_vertical_lines(&mut self) {
756        self.show.vertical_lines = true;
757    }
758
759    /// Add custom vertical grid lines.
760    ///
761    /// # Example
762    ///
763    /// ```
764    /// use eb_bars::BarPlot;
765    ///
766    /// let mut plot = BarPlot::new();
767    ///
768    /// let values = [5.0, 16.4, 17.1, 13.7, 8.9, 3.9, 6.3, 9.6];
769    /// plot.add_values(&values);
770    ///
771    /// // Add vertical lines.
772    /// let line_color = "rgb(126, 255, 165)";
773    /// plot.add_vertical_line_at(25.0, line_color);
774    /// plot.add_vertical_line_at(50.0, line_color);
775    /// plot.add_vertical_line_at(75.0, line_color);
776    ///
777    /// let svg: String = plot.to_svg(1600, 1000);
778    /// ```
779    pub fn add_vertical_line_at(&mut self, p: Percentage, color: &'a str) {
780
781        self.lines_at.push(LinesAt::Vertical(p, color));
782    }
783
784    /// Set size of the barplot size (relative to the canvas/frame).
785    ///
786    /// By default, the barchart part of the image will take up the full width and length of the frame.
787    /// However, by adding literally anything to the barplot, you most likely want to change the size of the plot.
788    /// If you skip this method, the text, ticks, markers, legend etc. will not be inside the viewbox of the canvas.
789    ///
790    /// # How it operates
791    ///
792    /// We only work with percentage values when defining sizes. A width of 100 means it takes up 100% of the width.
793    /// A height of 100 will do the same, but for height.
794    /// Same goes for offset. A horizontal offset of 10 means that the offset is pushed 10% from the left.
795    /// A vertical offset of 10 means that the offset is pushed 10% from the top.
796    /// An offset with value 100 means the offset is pushed all the way to the right (horizontal) or bottom (vertical).
797    ///
798    /// # Example
799    ///
800    /// ```
801    /// use eb_bars::BarPlot;
802    ///
803    /// let mut plot = BarPlot::new();
804    ///
805    /// plot.add_values(&[1., 2., 3.,]);
806    ///
807    /// let width = 90.0; // Set width to 90%,
808    /// let horizontal_offset = 65.0; // Move 65% right
809    /// let height = 85.0; // Set height to 85%
810    /// let vertical_offset = 40.0; // Move 40% down.
811    /// plot.set_plot_window_size(width, horizontal_offset, height, vertical_offset);
812    ///
813    /// let svg: String = plot.to_svg(1600, 1000);
814    /// ```
815    pub fn set_plot_window_size(
816        &mut self,
817        x_length: Percentage,
818        x_offset: Percentage,
819        y_length: Percentage,
820        y_offset: Percentage
821    ) {
822        assert!(x_length <= 100.0 && x_offset <= 100.0, "plot window width cannot exceed 100%");
823        assert!(y_length <= 100.0 && y_offset <= 100.0, "plot window height cannot exceed 100%");
824
825        self.layout.plot_window_scale = Some((x_length, x_offset, y_length, y_offset));
826    }
827
828    /// Set a scale for the barchart.
829    ///
830    /// By default, the scale is calculated using the maximum and minimum value throughout all data.
831    /// However, by setting the scale manually we get a more presentable plot.
832    /// Pass whole numbers (can be negative) as the scale minimum and scale maximum.
833    /// The 3rd parameter is the gap between each number on the scale e.g. the step.
834    /// The step must be a positive number as it is an increasing number starting from minimum.
835    ///
836    /// # Example
837    ///
838    /// ```
839    /// use eb_bars::BarPlot;
840    ///
841    /// let mut plot = BarPlot::new();
842    ///
843    /// plot.add_values(&[5.0, 16.4, 17.1, 13.7, 8.9, 3.9, 6.3, 9.6]);
844    ///
845    /// let min = 0;
846    /// let max = 20;
847    /// let step = 2;
848    ///
849    /// plot.set_scale_range(min, max, step);
850    ///
851    /// let svg: String = plot.to_svg(1600, 1000);
852    /// ```
853    pub fn set_scale_range(&mut self, min: i64, max: i64, step: u64) {
854        self.layout.scale_range = Some((min, max, step as usize));
855    }
856
857    /// Set the labels and markers for each bin/bucket on the x-axis.
858    ///
859    /// Note: Passing an array with fewer bin markers than added values will cause some bins to be un-labeled.
860    /// To make sure everything is correct, it is recommended to pass the same amount of markers as values.
861    ///
862    /// # Example
863    ///
864    /// ```
865    /// use eb_bars::BarPlot;
866    ///
867    /// let mut plot = BarPlot::new();
868    ///
869    /// let absence_boys = [5., 3., 8., 4., 7.];
870    /// let absence_girls = [4., 1., 2., 3., 1.];
871    /// let weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday",];
872    ///
873    /// plot.add_values(&absence_boys);
874    /// plot.add_values(&absence_girls);
875    /// plot.set_bin_markers(&weekdays);
876    ///
877    /// let svg: String = plot.to_svg(1600, 1000);
878    /// ```
879    pub fn set_bin_markers(&mut self, markers: &'a [&'a str]) {
880        self.markers = Some(markers);
881    }
882
883    /// Place the bin markers at the middle of each bin/bucket instead of to the left.
884    ///
885    /// By default, the bin markers are placed on the left side in between each bin.
886    /// You can have the markers placed either `left`, `middle` or `right` depending on usecase.
887    ///
888    /// Note: check out [`BarPlot::set_bin_markers_left`] and [`BarPlot::set_bin_markers_right`].
889    ///
890    /// # Example
891    ///
892    /// ```
893    /// use eb_bars::BarPlot;
894    ///
895    /// let mut plot = BarPlot::new();
896    ///
897    /// let values = [1., 2., 3.];
898    /// plot.add_values(&values);
899    ///
900    /// let markers: Vec<String> = (0..values.len()).map(|i| (i).to_string()).collect();
901    /// let markers = markers.iter().map(|s| s.as_str()).collect::<Vec<&str>>();
902    /// plot.set_bin_markers(&markers);
903    ///
904    /// plot.set_bin_markers_middle();
905    ///
906    /// let svg: String = plot.to_svg(1600, 1000);
907    /// ```
908    pub fn set_bin_markers_middle(&mut self) {
909        self.layout.bin_marker_position = BinMarkerPosition::Middle;
910    }
911
912    /// Place the bin markers to the left of each bin/bucket.
913    ///
914    /// By default, the bin markers are already placed on the left side in between each bin.
915    /// This method can be used to _reset_ an eventual change.
916    /// You can have the markers placed either `left`, `middle` or `right` depending on usecase.
917    ///
918    /// Note: check out [`BarPlot::set_bin_markers_middle`] and [`BarPlot::set_bin_markers_right`].
919    ///
920    /// # Example
921    ///
922    /// ```
923    /// use eb_bars::BarPlot;
924    ///
925    /// let mut plot = BarPlot::new();
926    ///
927    /// let values = [1., 2., 3.];
928    /// plot.add_values(&values);
929    ///
930    /// let markers: Vec<String> = (0..values.len()).map(|i| (i).to_string()).collect();
931    /// let markers = markers.iter().map(|s| s.as_str()).collect::<Vec<&str>>();
932    /// plot.set_bin_markers(&markers);
933    ///
934    /// // Setting markers at middle.
935    /// plot.set_bin_markers_middle();
936    /// // Then back to left side.
937    /// plot.set_bin_markers_left();
938    ///
939    /// let svg: String = plot.to_svg(1600, 1000);
940    /// ```
941    pub fn set_bin_markers_left(&mut self) {
942        self.layout.bin_marker_position = BinMarkerPosition::Left;
943    }
944
945    /// Place the bin markers to the right of each bin/bucket instead of to the left.
946    ///
947    /// By default, the bin markers are placed on the left side in between each bin.
948    /// You can have the markers placed either `left`, `middle` or `right` depending on usecase.
949    ///
950    /// Note: check out [`BarPlot::set_bin_markers_left`] and [`BarPlot::set_bin_markers_middle`].
951    ///
952    /// # Example
953    ///
954    /// ```
955    /// use eb_bars::BarPlot;
956    ///
957    /// let mut plot = BarPlot::new();
958    ///
959    /// let values = [1., 2., 3.];
960    /// plot.add_values(&values);
961    ///
962    /// let markers: Vec<String> = (0..values.len()).map(|i| (i).to_string()).collect();
963    /// let markers = markers.iter().map(|s| s.as_str()).collect::<Vec<&str>>();
964    /// plot.set_bin_markers(&markers);
965    ///
966    /// plot.set_bin_markers_right();
967    ///
968    /// let svg: String = plot.to_svg(1600, 1000);
969    /// ```
970    pub fn set_bin_markers_right(&mut self) {
971        self.layout.bin_marker_position = BinMarkerPosition::Right;
972    }
973
974    /// Introduce a `gap` between every bar.
975    ///
976    /// The gap is calculated using a percentage.
977    /// A gap of 0 means there is no gap/air between bars.
978    /// A gap of 50 means that the bar and the gap will take up the same width.
979    /// A gap of 100 means that the gap will take up all space and so the bar becomes invisible.
980    ///
981    /// Note: check out [`BarPlot::set_bin_gap`] for gap only betweem bins.
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    /// plot.add_values(&[4., 5., 6.]);
992    ///
993    /// let gap = 30.0; // The gap will take up 30% of the space, leaving 70% for bar.
994    /// plot.set_bar_gap(gap);
995    ///
996    /// let svg: String = plot.to_svg(1600, 1000);
997    /// ```
998    pub fn set_bar_gap(&mut self, gap: Percentage) {
999        self.layout.bar_gap = gap;
1000    }
1001
1002    /// Introduce a `gap` between every bin/bucket.
1003    ///
1004    /// The gap is calculated using a percentage.
1005    /// A gap of 0 means there is no gap/air between bins.
1006    /// A gap of 50 means that the bin and the gap will take up the same width.
1007    /// A gap of 100 means that the gap will take up all space and so the bin squashed into nothing.
1008    ///
1009    /// Note: check out [`BarPlot::set_bar_gap`] for gap betweem bars.
1010    ///
1011    /// # Example
1012    ///
1013    /// ```
1014    /// use eb_bars::BarPlot;
1015    ///
1016    /// let mut plot = BarPlot::new();
1017    ///
1018    /// plot.add_values(&[1., 2., 3.]);
1019    /// plot.add_values(&[4., 5., 6.]);
1020    ///
1021    /// let gap = 30.0; // The gap will take up 30% of the space, leaving 70% for bin.
1022    /// plot.set_bin_gap(gap);
1023    ///
1024    /// let svg: String = plot.to_svg(1600, 1000);
1025    /// ```
1026    pub fn set_bin_gap(&mut self, gap: Percentage) {
1027        self.layout.bin_gap = gap;
1028    }
1029
1030    /// Set length for ticks on the y axis.
1031    ///
1032    /// The length is calculated using a percentage.
1033    /// A length of 0 means the tick will not be generated.
1034    /// A length of 10 means that the tick will be of a _somewhat_ _normal_ length.
1035    /// A length of greater than 10 means that the tick will be long.
1036    /// Try different lengths to find the best length for your usecase.
1037    ///
1038    /// # Example
1039    ///
1040    /// ```
1041    /// use eb_bars::BarPlot;
1042    ///
1043    /// let mut plot = BarPlot::new();
1044    ///
1045    /// plot.add_values(&[1., 2., 3.]);
1046    ///
1047    /// let len = 20.0; // The tick length will be of considerate length.
1048    /// plot.set_y_axis_tick_length(len);
1049    ///
1050    /// let svg: String = plot.to_svg(1600, 1000);
1051    /// ```
1052    pub fn set_y_axis_tick_length(&mut self, p: Percentage) {
1053        self.layout.y_axis_tick_length = p;
1054    }
1055
1056    /// Set length for ticks on the x axis.
1057    ///
1058    /// The length is calculated using a percentage.
1059    /// A length of 0 means the tick will not be generated.
1060    /// A length of 10 means that the tick will be of a _somewhat_ _normal_ length.
1061    /// A length of greater than 10 means that the tick will be long.
1062    /// Try different lengths to find the best length for your usecase.
1063    ///
1064    /// # Example
1065    ///
1066    /// ```
1067    /// use eb_bars::BarPlot;
1068    ///
1069    /// let mut plot = BarPlot::new();
1070    ///
1071    /// plot.add_values(&[1., 2., 3.]);
1072    ///
1073    /// let len = 20.0; // The tick length will be of considerate length.
1074    /// plot.set_x_axis_tick_length(len);
1075    ///
1076    /// let svg: String = plot.to_svg(1600, 1000);
1077    /// ```
1078    pub fn set_x_axis_tick_length(&mut self, p: Percentage) {
1079        self.layout.x_axis_tick_length = p;
1080    }
1081
1082    /// Anchor bars at zero instead of the floor.
1083    /// This will make negative bars grow downwards.
1084    /// If your dataset contains only negative values, then this might not make sense to use.
1085    /// Rather, `use this when your dataset is likely to contain both positive and negative values`.
1086    ///
1087    /// An area where it makes sens is when visualizing temperature differences. :)
1088    ///
1089    /// By default, bars are anchored at the floor of the barchart.
1090    /// However, you might want negative values to stand out by having them point downwards.
1091    /// This method will apply anchoring bars at the zero line instead of the floor.
1092    ///
1093    /// # Important
1094    /// Call [`BarPlot::set_scale_range`] first, and make sure to set `min < 0` and `max >= 0`.
1095    /// Otherwise, you ~might~ will get a barchart that looks goofy. Consider yourself warned.
1096    ///
1097    /// # Example
1098    ///
1099    /// ```
1100    /// use eb_bars::BarPlot;
1101    ///
1102    /// let mut plot = BarPlot::new();
1103    ///
1104    /// plot.add_values(&[5.0, -16.4, 17.1, 13.7, 8.9, 3.9, -6.3, 9.6]);
1105    ///
1106    /// // Adding some extra tweaks that makes this example more clear.
1107    /// let min = -20;
1108    /// let max = 20;
1109    /// let step = 2;
1110    /// plot.set_scale_range(min, max, step);
1111    /// plot.set_plot_window_size(90.0, 80.0, 83.0, 50.0);
1112    ///
1113    /// // Negative bars will now grow downwards instead of upwards. :)
1114    /// plot.set_negative_bars_go_down();
1115    ///
1116    /// let svg: String = plot.to_svg(1600, 1000);
1117    /// ```
1118    pub fn set_negative_bars_go_down(&mut self) {
1119        self.layout.negative_bars_go_down = true;
1120    }
1121
1122    /// Apply text on the left side of the plot window.
1123    ///
1124    /// By default, the plot window takes up the whole window.
1125    /// For the text to be visible, we need to scale down the plot first with [`BarPlot::set_plot_window_size`].
1126    ///
1127    /// The text will by default be offset from the plot window with a sane value.
1128    /// If you want to override this value, you can set a custom offset with [`BarPlot::set_text_left_offset`].
1129    ///
1130    /// # Example
1131    ///
1132    /// ```
1133    /// use eb_bars::BarPlot;
1134    ///
1135    /// let mut plot = BarPlot::new();
1136    ///
1137    /// plot.add_values(&[1., 2., 3.]);
1138    ///
1139    /// // Scale down the plot figure size so text can become be visible.
1140    /// plot.set_plot_window_size(85.0, 65.0, 80.0, 40.0);
1141    ///
1142    /// plot.set_text_left("This is some text.");
1143    ///
1144    /// let svg: String = plot.to_svg(1600, 1000);
1145    /// ```
1146    pub fn set_text_left(&mut self, text: &'a str) {
1147        self.plot_text.left = Some(text);
1148    }
1149
1150    /// Apply offset for text on the left side of the plot window.
1151    ///
1152    /// The offset is calculated percent wise from the frame towards the plot window.
1153    /// The higher the percentage the closer the text moves towards the plot window.
1154    ///
1155    /// Note: you need to explicitly apply text first with [`BarPlot::set_text_lef.
1156    ///
1157    /// # Example
1158    ///
1159    /// ```
1160    /// use eb_bars::BarPlot;
1161    ///
1162    /// let mut plot = BarPlot::new();
1163    ///
1164    /// plot.add_values(&[1., 2., 3.]);
1165    ///
1166    /// // Scale down the plot figure size so text can become be visible.
1167    /// plot.set_plot_window_size(85.0, 65.0, 80.0, 40.0);
1168    ///
1169    /// plot.set_text_left("This is some text.");
1170    ///
1171    /// let percent = 30.0;
1172    /// plot.set_text_left_offset(percent);
1173    ///
1174    /// let svg: String = plot.to_svg(1600, 1000);
1175    /// ```
1176    pub fn set_text_left_offset(&mut self, offset: Percentage) {
1177        self.plot_text.left_offset = Some(offset);
1178    }
1179
1180    /// Apply text on the right side of the plot window.
1181    ///
1182    /// By default, the plot window takes up the whole window.
1183    /// For the text to be visible, we need to scale down the plot first with [`BarPlot::set_plot_window_size`].
1184    ///
1185    /// The text will by default be offset from the plot window with a sane value.
1186    /// If you want to override this value, you can set a custom offset with [`BarPlot::set_text_right_offset`].
1187    ///
1188    /// # Example
1189    ///
1190    /// ```
1191    /// use eb_bars::BarPlot;
1192    ///
1193    /// let mut plot = BarPlot::new();
1194    ///
1195    /// plot.add_values(&[1., 2., 3.]);
1196    ///
1197    /// // Scale down the plot figure size so text can become be visible.
1198    /// plot.set_plot_window_size(85.0, 65.0, 80.0, 40.0);
1199    ///
1200    /// plot.set_text_right("This is some text.");
1201    ///
1202    /// let svg: String = plot.to_svg(1600, 1000);
1203    /// ```
1204    pub fn set_text_right(&mut self, text: &'a str) {
1205        self.plot_text.right = Some(text);
1206    }
1207
1208    /// Apply offset for text on the right side of the plot window.
1209    ///
1210    /// The offset is calculated percent wise from the frame towards the plot window.
1211    /// The higher the percentage the closer the text moves towards the plot window.
1212    ///
1213    /// Note: you need to explicitly apply text first with [`BarPlot::set_text_right`].
1214    ///
1215    /// # Example
1216    ///
1217    /// ```
1218    /// use eb_bars::BarPlot;
1219    ///
1220    /// let mut plot = BarPlot::new();
1221    ///
1222    /// plot.add_values(&[1., 2., 3.]);
1223    ///
1224    /// // Scale down the plot figure size so text can become be visible.
1225    /// plot.set_plot_window_size(85.0, 65.0, 80.0, 40.0);
1226    ///
1227    /// plot.set_text_right("This is some text.");
1228    ///
1229    /// let percent = 30.0;
1230    /// plot.set_text_right_offset(percent);
1231    ///
1232    /// let svg: String = plot.to_svg(1600, 1000);
1233    /// ```
1234    pub fn set_text_right_offset(&mut self, offset: Percentage) {
1235        self.plot_text.right_offset = Some(offset);
1236    }
1237
1238    /// Apply text underneath the plot window.
1239    ///
1240    /// By default, the plot window takes up the whole window.
1241    /// For the text to be visible, we need to scale down the plot first with [`BarPlot::set_plot_window_size`].
1242    ///
1243    /// The text will by default be offset from the plot window with a sane value.
1244    /// If you want to override this value, you can set a custom offset with [`BarPlot::set_text_bottom_offset`].
1245    ///
1246    /// # Example
1247    ///
1248    /// ```
1249    /// use eb_bars::BarPlot;
1250    ///
1251    /// let mut plot = BarPlot::new();
1252    ///
1253    /// plot.add_values(&[1., 2., 3.]);
1254    ///
1255    /// // Scale down the plot figure size so text can become be visible.
1256    /// plot.set_plot_window_size(85.0, 65.0, 80.0, 40.0);
1257    ///
1258    /// plot.set_text_bottom("This is some text.");
1259    ///
1260    /// let svg: String = plot.to_svg(1600, 1000);
1261    /// ```
1262    pub fn set_text_bottom(&mut self, text: &'a str) {
1263        self.plot_text.bottom = Some(text);
1264    }
1265
1266    /// Apply offset for text underneath the plot window.
1267    ///
1268    /// The offset is calculated percent wise from the frame towards the plot window.
1269    /// The higher the percentage the closer the text moves towards the plot window.
1270    ///
1271    /// Note: you need to explicitly apply text first with [`BarPlot::set_text_bottom`].
1272    ///
1273    /// # Example
1274    ///
1275    /// ```
1276    /// use eb_bars::BarPlot;
1277    ///
1278    /// let mut plot = BarPlot::new();
1279    ///
1280    /// plot.add_values(&[1., 2., 3.]);
1281    ///
1282    /// // Scale down the plot figure size so text can become be visible.
1283    /// plot.set_plot_window_size(85.0, 65.0, 80.0, 40.0);
1284    ///
1285    /// plot.set_text_bottom("This is some text.");
1286    ///
1287    /// let percent = 30.0;
1288    /// plot.set_text_bottom_offset(percent);
1289    ///
1290    /// let svg: String = plot.to_svg(1600, 1000);
1291    /// ```
1292    pub fn set_text_bottom_offset(&mut self, offset: Percentage) {
1293        self.plot_text.bottom_offset = Some(offset);
1294    }
1295
1296    /// Apply text above the plot window.
1297    ///
1298    /// By default, the plot window takes up the whole window.
1299    /// For the text to be visible, we need to scale down the plot first with [`BarPlot::set_plot_window_size`].
1300    ///
1301    /// The text will by default be offset from the plot window with a sane value.
1302    /// If you want to override this value, you can set a custom offset with [`BarPlot::set_text_top_offset`].
1303    ///
1304    /// # Example
1305    ///
1306    /// ```
1307    /// use eb_bars::BarPlot;
1308    ///
1309    /// let mut plot = BarPlot::new();
1310    ///
1311    /// plot.add_values(&[1., 2., 3.]);
1312    ///
1313    /// // Scale down the plot figure size so text can become be visible.
1314    /// plot.set_plot_window_size(85.0, 65.0, 80.0, 40.0);
1315    ///
1316    /// plot.set_text_top("This is some text.");
1317    ///
1318    /// let svg: String = plot.to_svg(1600, 1000);
1319    /// ```
1320    pub fn set_text_top(&mut self, text: &'a str) {
1321        self.plot_text.top = Some(text);
1322    }
1323
1324    /// Apply offset for text above the plot window.
1325    ///
1326    /// The offset is calculated percent wise from the frame towards the plot window.
1327    /// The higher the percentage the closer the text moves towards the plot window.
1328    ///
1329    /// Note: you need to explicitly apply text first with [`BarPlot::set_text_top`].
1330    ///
1331    /// # Example
1332    ///
1333    /// ```
1334    /// use eb_bars::BarPlot;
1335    ///
1336    /// let mut plot = BarPlot::new();
1337    ///
1338    /// plot.add_values(&[1., 2., 3.]);
1339    ///
1340    /// // Scale down the plot figure size so text can become be visible.
1341    /// plot.set_plot_window_size(85.0, 65.0, 80.0, 40.0);
1342    ///
1343    /// plot.set_text_top("This is some text.");
1344    ///
1345    /// let percent = 30.0;
1346    /// plot.set_text_top_offset(percent);
1347    ///
1348    /// let svg: String = plot.to_svg(1600, 1000);
1349    /// ```
1350    pub fn set_text_top_offset(&mut self, offset: Percentage) {
1351        self.plot_text.top_offset = Some(offset);
1352    }
1353
1354    /// Apply a legend with category names and their corresponding colors.
1355    ///
1356    /// When calling [`BarPlot::add_values`] multiple times each time adding a set of values (categories),
1357    /// you most likely want a legend to label each category by name and color.
1358    ///
1359    /// Note: The legend will use the colors added with [`BarPlot::add_bar_colors_by_category`],
1360    /// remember to call this method once for each category added.
1361    ///
1362    /// # Example
1363    ///
1364    /// ```
1365    /// use eb_bars::BarPlot;
1366    ///
1367    /// let mut plot = BarPlot::new();
1368    ///
1369    /// let apples: Vec<f64> = vec![5., 16., 17., 8., 3.];
1370    /// let oranges: Vec<f64> = vec![7., 6., 7., 16., 9.];
1371    ///
1372    /// // Add first set of values.
1373    /// plot.add_values(&apples);
1374    /// // Adding a second set of values.
1375    /// plot.add_values(&oranges);
1376    ///
1377    /// // First call adds a color to the first category. Red bacuse apples are often red.
1378    /// plot.add_bar_colors_by_category("Red");
1379    /// // Second call adds a color to the second category. Orange because; oranges are orange. :)
1380    /// plot.add_bar_colors_by_category("Orange");
1381    ///
1382    /// // Adding the labels (colors are already added) for legend.
1383    /// let categories = ["Apples", "Oranges"];
1384    /// // Applying the legend.
1385    /// plot.set_legend(&categories);
1386    ///
1387    /// let svg: String = plot.to_svg(1600, 1000);
1388    /// ```
1389    pub fn set_legend(&mut self, categories: &'a [&'a str]) {
1390        self.legend.categories = Some(categories);
1391    }
1392
1393    /// Set legend position.
1394    ///
1395    /// When calling [`BarPlot::set_legend`], its position is calculated using percentage.
1396    /// By default, the legend is positioned on the far right side and close to the top.
1397    /// You can override the default position by passing two values.
1398    /// First value is the absolute offset from left to right,
1399    /// the second value is the absolute offset from top to bottom.
1400    ///
1401    /// An offset X = 50 and offset Y = 50 will roughly position the legend in the center of the canvas.
1402    /// More precisely, the top left corner of the legend itself will be pinned on that position.
1403    ///
1404    /// Note: you might want to resize the plot window to accomodate for the legend if you want it
1405    /// to be drawn outside the plot, otherwise it will be drawn on top of the plot figure.
1406    /// Check out [`BarPlot::set_plot_window_size`] for that.
1407    ///
1408    /// # Example
1409    ///
1410    /// ```
1411    /// use eb_bars::BarPlot;
1412    ///
1413    /// let mut plot = BarPlot::new();
1414    ///
1415    /// let apples: Vec<f64> = vec![5., 16., 17., 8., 3.];
1416    /// let oranges: Vec<f64> = vec![7., 6., 7., 16., 9.];
1417    ///
1418    /// // Add first set of values.
1419    /// plot.add_values(&apples);
1420    /// // Adding a second set of values.
1421    /// plot.add_values(&oranges);
1422    ///
1423    /// // First call adds a color to the first category. Red bacuse apples are often red.
1424    /// plot.add_bar_colors_by_category("Red");
1425    /// // Second call adds a color to the second category. Orange because; oranges are orange. :)
1426    /// plot.add_bar_colors_by_category("Orange");
1427    ///
1428    /// // Adding the labels (colors are already added) for legend.
1429    /// let categories = ["Apples", "Oranges"];
1430    /// // Applying the legend.
1431    /// plot.set_legend(&categories);
1432    ///
1433    /// // Make room for legend on the right side by shrinking plot window.
1434    /// plot.set_plot_window_size(80.0, 30.0, 85.0, 40.0);
1435    ///
1436    /// let offset_x = 90.0;
1437    /// let offset_y = 22.0;
1438    /// plot.set_legend_position(offset_x, offset_y);
1439    ///
1440    /// let svg: String = plot.to_svg(1600, 1000);
1441    /// ```
1442    pub fn set_legend_position(&mut self, x: Percentage, y: Percentage) {
1443        self.legend.position = Some((x,y));
1444    }
1445
1446    /// Apply a custom font-size for all text.
1447    ///
1448    /// By default, all text and numbers are rendered with a default font-size. This can be overriden.
1449    /// The font-size is calculated using a percentage value.
1450    /// A font-size of size 100 (100%) will not affect the text as it is the default.
1451    /// You can either increase the size by passing a >100 value or decrease it with <100.
1452    ///
1453    /// # Example
1454    ///
1455    /// ```
1456    /// use eb_bars::BarPlot;
1457    ///
1458    /// let mut plot = BarPlot::new();
1459    ///
1460    /// plot.add_values(&[1., 2., 3.,]);
1461    ///
1462    /// // Scale down the plot figure size so text can become be visible.
1463    /// plot.set_plot_window_size(85.0, 65.0, 80.0, 40.0);
1464    ///
1465    /// // Apply some text.
1466    /// plot.set_text_left("This is some text.");
1467    ///
1468    /// // Increase font-size using a percentage greater than 100%.
1469    /// let size = 130.0;
1470    /// plot.set_font_size(size);
1471    ///
1472    /// let svg: String = plot.to_svg(1600, 1000);
1473    /// ```
1474    pub fn set_font_size(&mut self, p: Percentage) {
1475        self.layout.font_size = p;
1476    }
1477
1478    /// Apply a border around the canvas.
1479    ///
1480    /// # Example
1481    ///
1482    /// ```
1483    /// use eb_bars::BarPlot;
1484    ///
1485    /// let mut plot = BarPlot::new();
1486    ///
1487    /// plot.add_values(&[1., 2., 3.]);
1488    ///
1489    /// // Shrink plot so that it becomes clear that the border goes around the canvas.
1490    /// plot.set_plot_window_size(80.0, 30.0, 85.0, 40.0);
1491    ///
1492    /// plot.set_show_window_border();
1493    ///
1494    /// let svg: String = plot.to_svg(1600, 1000);
1495    /// ```
1496    pub fn set_show_window_border(&mut self) {
1497        self.show.window_border = true;
1498    }
1499
1500    /// Apply a border around the plot.
1501    ///
1502    /// # Example
1503    ///
1504    /// ```
1505    /// use eb_bars::BarPlot;
1506    ///
1507    /// let mut plot = BarPlot::new();
1508    ///
1509    /// plot.add_values(&[1., 2., 3.]);
1510    ///
1511    /// // Shrink plot so that it becomes clear that the border goes around the plot window.
1512    /// plot.set_plot_window_size(80.0, 30.0, 85.0, 40.0);
1513    ///
1514    /// plot.set_show_plot_border();
1515    ///
1516    /// let svg: String = plot.to_svg(1600, 1000);
1517    /// ```
1518    pub fn set_show_plot_border(&mut self) {
1519        self.show.plot_border = true;
1520    }
1521
1522    /// Generate the final svg image of all applied content.
1523    ///
1524    /// When you are satisfied with all the tweaks in above methods, it is time to generate the final svg.
1525    /// Keep in mind that you can still add or re-apply changes on the same object after calling this method.
1526    /// Changes will be applied and included on the following call to this method.
1527    ///
1528    /// # Example
1529    ///
1530    /// ```
1531    /// use eb_bars::BarPlot;
1532    ///
1533    /// let mut plot = BarPlot::new();
1534    ///
1535    /// plot.add_values(&[1., 2., 3.]);
1536    ///
1537    /// let mut svg: String = plot.to_svg(1600, 1000);
1538    ///
1539    /// // Add a new configuration (just resize plot window).
1540    /// plot.set_plot_window_size(80.0, 30.0, 85.0, 40.0);
1541    ///
1542    /// // Lets overwrite the old plot with this new configuration.
1543    /// svg = plot.to_svg(1600, 1000);
1544    /// ```
1545    pub fn to_svg(&mut self, width: u32, height: u32) -> String {
1546        assert!(!self.values.is_empty(), "Can not generate plot without any values..");
1547
1548        self.size = (width, height);
1549
1550        let n_categories = self.values.len();
1551        match &mut self.colors.bars.layout {
1552            BarColorLayout::Category(colors) => {
1553                let n_colors = colors.len();
1554                assert_eq!(
1555                    n_categories,
1556                    n_colors,
1557                    "Got {n_categories} categories and {n_colors} colors.",
1558                );
1559            }
1560            BarColorLayout::Indexed(matrix) => {
1561                let n_color_vectors = matrix.len();
1562                assert_eq!(
1563                    n_categories,
1564                    n_color_vectors,
1565                    "Got {n_categories} categories and {n_color_vectors} color vectors.",
1566                );
1567
1568                for (i, colors) in matrix.iter().enumerate() {
1569                    let values = self.values[i].len();
1570                    let n_colors = colors.len();
1571                    assert_eq!(
1572                        values,
1573                        n_colors,
1574                        "Got {values} values and {n_colors} colors for category {i}.",
1575                    );
1576                }
1577            }
1578            // No need to do assertion on remaining variants..
1579            _ => (),
1580        }
1581
1582        svg::render(self)
1583    }
1584}
1585
1586#[cfg(test)]
1587mod tests {
1588    // All other tests are in directory ./tests
1589
1590    use std::fs;
1591
1592    use toml;
1593
1594    use super::{VERSION, REPOSITORY};
1595
1596    #[test]
1597    fn version_and_repo() {
1598        // When updating Cargo.toml, make sure to update corresponding values in src files as well.
1599        let contents = fs::read_to_string("Cargo.toml").unwrap();
1600        let value = contents.parse::<toml::Table>().unwrap();
1601
1602        let version = value["package"]["version"].as_str().unwrap();
1603        assert_eq!(version, VERSION);
1604
1605        let repository = value["package"]["repository"].as_str().unwrap();
1606        assert_eq!(repository, REPOSITORY);
1607    }
1608}