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}