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