1use alloc::vec;
4use alloc::vec::Vec;
5
6use ratatui_core::buffer::Buffer;
7use ratatui_core::layout::{Direction, Rect};
8use ratatui_core::style::{Style, Styled};
9use ratatui_core::symbols;
10use ratatui_core::text::Line;
11use ratatui_core::widgets::Widget;
12
13pub use self::bar::Bar;
14pub use self::bar_group::BarGroup;
15use crate::block::{Block, BlockExt};
16
17mod bar;
18mod bar_group;
19
20#[derive(Debug, Clone, Eq, PartialEq, Hash)]
83pub struct BarChart<'a> {
84 block: Option<Block<'a>>,
86 bar_width: u16,
88 bar_gap: u16,
90 group_gap: u16,
92 bar_set: symbols::bar::Set<'a>,
94 bar_style: Style,
96 value_style: Style,
98 label_style: Style,
100 style: Style,
102 data: Vec<BarGroup<'a>>,
104 max: Option<u64>,
107 direction: Direction,
109}
110
111impl Default for BarChart<'_> {
112 fn default() -> Self {
113 Self {
114 block: None,
115 max: None,
116 data: Vec::new(),
117 bar_style: Style::default(),
118 bar_width: 1,
119 bar_gap: 1,
120 value_style: Style::default(),
121 label_style: Style::default(),
122 group_gap: 0,
123 bar_set: symbols::bar::NINE_LEVELS,
124 style: Style::default(),
125 direction: Direction::Vertical,
126 }
127 }
128}
129
130impl<'a> BarChart<'a> {
131 pub fn new<T: Into<Vec<Bar<'a>>>>(bars: T) -> Self {
144 Self {
145 data: Self::non_empty_groups(vec![BarGroup::new(bars.into())]),
146 direction: Direction::Vertical,
147 ..Default::default()
148 }
149 }
150
151 pub fn vertical(bars: impl Into<Vec<Bar<'a>>>) -> Self {
155 Self::new(bars)
156 }
157
158 pub fn horizontal(bars: impl Into<Vec<Bar<'a>>>) -> Self {
168 Self {
169 data: Self::non_empty_groups(vec![BarGroup::new(bars.into())]),
170 direction: Direction::Horizontal,
171 ..Default::default()
172 }
173 }
174
175 pub fn grouped<T: Into<Vec<BarGroup<'a>>>>(groups: T) -> Self {
194 Self {
195 data: Self::non_empty_groups(groups.into()),
196 ..Default::default()
197 }
198 }
199
200 fn non_empty_groups(groups: Vec<BarGroup<'a>>) -> Vec<BarGroup<'a>> {
202 groups
203 .into_iter()
204 .filter(|group| !group.bars.is_empty())
205 .collect()
206 }
207
208 #[must_use = "method moves the value of self and returns the modified value"]
226 pub fn data(mut self, data: impl Into<BarGroup<'a>>) -> Self {
227 let group: BarGroup = data.into();
228 if !group.bars.is_empty() {
229 self.data.push(group);
230 }
231 self
232 }
233
234 #[must_use = "method moves the value of self and returns the modified value"]
236 pub fn block(mut self, block: Block<'a>) -> Self {
237 self.block = Some(block);
238 self
239 }
240
241 #[must_use = "method moves the value of self and returns the modified value"]
272 pub const fn max(mut self, max: u64) -> Self {
273 self.max = Some(max);
274 self
275 }
276
277 #[must_use = "method moves the value of self and returns the modified value"]
287 pub fn bar_style<S: Into<Style>>(mut self, style: S) -> Self {
288 self.bar_style = style.into();
289 self
290 }
291
292 #[must_use = "method moves the value of self and returns the modified value"]
300 pub const fn bar_width(mut self, width: u16) -> Self {
301 self.bar_width = width;
302 self
303 }
304
305 #[must_use = "method moves the value of self and returns the modified value"]
325 pub const fn bar_gap(mut self, gap: u16) -> Self {
326 self.bar_gap = gap;
327 self
328 }
329
330 #[must_use = "method moves the value of self and returns the modified value"]
334 pub const fn bar_set(mut self, bar_set: symbols::bar::Set<'a>) -> Self {
335 self.bar_set = bar_set;
336 self
337 }
338
339 #[must_use = "method moves the value of self and returns the modified value"]
353 pub fn value_style<S: Into<Style>>(mut self, style: S) -> Self {
354 self.value_style = style.into();
355 self
356 }
357
358 #[must_use = "method moves the value of self and returns the modified value"]
372 pub fn label_style<S: Into<Style>>(mut self, style: S) -> Self {
373 self.label_style = style.into();
374 self
375 }
376
377 #[must_use = "method moves the value of self and returns the modified value"]
379 pub const fn group_gap(mut self, gap: u16) -> Self {
380 self.group_gap = gap;
381 self
382 }
383
384 #[must_use = "method moves the value of self and returns the modified value"]
393 pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
394 self.style = style.into();
395 self
396 }
397
398 #[must_use = "method moves the value of self and returns the modified value"]
418 pub const fn direction(mut self, direction: Direction) -> Self {
419 self.direction = direction;
420 self
421 }
422}
423
424#[derive(Clone, Copy)]
425struct LabelInfo {
426 group_label_visible: bool,
427 bar_label_visible: bool,
428 height: u16,
429}
430
431impl BarChart<'_> {
432 fn group_ticks(&self, available_space: u16, bar_max_length: u16) -> Vec<Vec<u64>> {
436 let max: u64 = self.maximum_data_value();
437 self.data
438 .iter()
439 .scan(available_space, |space, group| {
440 if *space == 0 {
441 return None;
442 }
443 let n_bars = group.bars.len() as u16;
444 let group_width = n_bars * self.bar_width + n_bars.saturating_sub(1) * self.bar_gap;
445
446 let n_bars = if *space > group_width {
447 *space = space.saturating_sub(group_width + self.group_gap + self.bar_gap);
448 Some(n_bars)
449 } else {
450 let max_bars = (*space + self.bar_gap) / (self.bar_width + self.bar_gap);
451 if max_bars > 0 {
452 *space = 0;
453 Some(max_bars)
454 } else {
455 None
456 }
457 };
458
459 n_bars.map(|n| {
460 group
461 .bars
462 .iter()
463 .take(n as usize)
464 .map(|bar| Self::scale_ticks(bar.value, max, bar_max_length))
465 .collect()
466 })
467 })
468 .collect()
469 }
470
471 fn scale_ticks(value: u64, max: u64, max_length: u16) -> u64 {
472 let max_ticks = u128::from(max_length) * 8;
473 let ticks = u128::from(value) * max_ticks / u128::from(max);
474 ticks.min(max_ticks) as u64
475 }
476
477 fn label_info(&self, available_height: u16) -> LabelInfo {
486 if available_height == 0 {
487 return LabelInfo {
488 group_label_visible: false,
489 bar_label_visible: false,
490 height: 0,
491 };
492 }
493
494 let bar_label_visible = self
495 .data
496 .iter()
497 .any(|e| e.bars.iter().any(|e| e.label.is_some()));
498
499 if available_height == 1 && bar_label_visible {
500 return LabelInfo {
501 group_label_visible: false,
502 bar_label_visible: true,
503 height: 1,
504 };
505 }
506
507 let group_label_visible = self.data.iter().any(|e| e.label.is_some());
508 LabelInfo {
509 group_label_visible,
510 bar_label_visible,
511 height: u16::from(group_label_visible) + u16::from(bar_label_visible),
513 }
514 }
515
516 fn render_horizontal(&self, buf: &mut Buffer, area: Rect) {
517 let label_size = self
519 .data
520 .iter()
521 .flat_map(|group| group.bars.iter().map(|bar| &bar.label))
522 .flatten() .map(Line::width)
524 .max()
525 .unwrap_or(0) as u16;
526
527 let label_x = area.x;
528 let bars_area = {
529 let margin = u16::from(label_size != 0);
530 Rect {
531 x: area.x + label_size + margin,
532 width: area.width.saturating_sub(label_size).saturating_sub(margin),
533 ..area
534 }
535 };
536
537 let group_ticks = self.group_ticks(bars_area.height, bars_area.width);
538
539 let mut bar_y = bars_area.top();
541 for (ticks_vec, group) in group_ticks.into_iter().zip(self.data.iter()) {
542 for (ticks, bar) in ticks_vec.into_iter().zip(group.bars.iter()) {
543 let bar_length = (ticks / 8) as u16;
544 let bar_style = self.bar_style.patch(bar.style);
545
546 for y in 0..self.bar_width {
547 let bar_y = bar_y + y;
548 for x in 0..bars_area.width {
549 let symbol = if x < bar_length {
550 self.bar_set.full
551 } else {
552 self.bar_set.empty
553 };
554 buf[(bars_area.left() + x, bar_y)]
555 .set_symbol(symbol)
556 .set_style(bar_style);
557 }
558 }
559
560 let bar_value_area = Rect {
561 y: bar_y + (self.bar_width >> 1),
562 ..bars_area
563 };
564
565 if let Some(label) = &bar.label {
567 buf.set_line(label_x, bar_value_area.top(), label, label_size);
568 }
569
570 bar.render_value_with_different_styles(
571 buf,
572 bar_value_area,
573 bar_length as usize,
574 self.value_style,
575 self.bar_style,
576 );
577
578 bar_y += self.bar_gap + self.bar_width;
579 }
580
581 let label_y = bar_y - self.bar_gap;
584 if self.group_gap > 0 && label_y < bars_area.bottom() {
585 let label_rect = Rect {
586 y: label_y,
587 ..bars_area
588 };
589 group.render_label(buf, label_rect, self.label_style);
590 bar_y += self.group_gap;
591 }
592 }
593 }
594
595 fn render_vertical(&self, buf: &mut Buffer, area: Rect) {
596 let label_info = self.label_info(area.height.saturating_sub(1));
597
598 let bars_area = Rect {
599 height: area.height.saturating_sub(label_info.height),
600 ..area
601 };
602
603 let group_ticks = self.group_ticks(bars_area.width, bars_area.height);
604 self.render_vertical_bars(bars_area, buf, &group_ticks);
605 self.render_labels_and_values(area, buf, label_info, &group_ticks);
606 }
607
608 fn render_vertical_bars(&self, area: Rect, buf: &mut Buffer, group_ticks: &[Vec<u64>]) {
609 let mut bar_x = area.left();
611 for (ticks_vec, group) in group_ticks.iter().zip(&self.data) {
612 for (ticks, bar) in ticks_vec.iter().zip(&group.bars) {
613 let mut ticks = *ticks;
614 for j in (0..area.height).rev() {
615 let symbol = match ticks {
616 0 => self.bar_set.empty,
617 1 => self.bar_set.one_eighth,
618 2 => self.bar_set.one_quarter,
619 3 => self.bar_set.three_eighths,
620 4 => self.bar_set.half,
621 5 => self.bar_set.five_eighths,
622 6 => self.bar_set.three_quarters,
623 7 => self.bar_set.seven_eighths,
624 _ => self.bar_set.full,
625 };
626
627 let bar_style = self.bar_style.patch(bar.style);
628
629 for x in 0..self.bar_width {
630 buf[(bar_x + x, area.top() + j)]
631 .set_symbol(symbol)
632 .set_style(bar_style);
633 }
634
635 ticks = ticks.saturating_sub(8);
636 }
637 bar_x += self.bar_gap + self.bar_width;
638 }
639 bar_x += self.group_gap;
640 }
641 }
642
643 fn maximum_data_value(&self) -> u64 {
645 self.max
646 .unwrap_or_else(|| {
647 self.data
648 .iter()
649 .map(|group| group.max().unwrap_or_default())
650 .max()
651 .unwrap_or_default()
652 })
653 .max(1)
654 }
655
656 fn render_labels_and_values(
657 &self,
658 area: Rect,
659 buf: &mut Buffer,
660 label_info: LabelInfo,
661 group_ticks: &[Vec<u64>],
662 ) {
663 let mut bar_x = area.left();
665 let bar_y = area.bottom() - label_info.height - 1;
666 for (group, ticks_vec) in self.data.iter().zip(group_ticks) {
667 if group.bars.is_empty() {
668 continue;
669 }
670 if label_info.group_label_visible {
672 let label_max_width =
673 ticks_vec.len() as u16 * (self.bar_width + self.bar_gap) - self.bar_gap;
674 let group_area = Rect {
675 x: bar_x,
676 y: area.bottom() - 1,
677 width: label_max_width,
678 height: 1,
679 };
680 group.render_label(buf, group_area, self.label_style);
681 }
682
683 for (bar, ticks) in group.bars.iter().zip(ticks_vec) {
685 if label_info.bar_label_visible {
686 bar.render_label(buf, self.bar_width, bar_x, bar_y + 1, self.label_style);
687 }
688
689 bar.render_value(buf, self.bar_width, bar_x, bar_y, self.value_style, *ticks);
690
691 bar_x += self.bar_gap + self.bar_width;
692 }
693 bar_x += self.group_gap;
694 }
695 }
696}
697
698impl Widget for BarChart<'_> {
699 fn render(self, area: Rect, buf: &mut Buffer) {
700 Widget::render(&self, area, buf);
701 }
702}
703
704impl Widget for &BarChart<'_> {
705 fn render(self, area: Rect, buf: &mut Buffer) {
706 buf.set_style(area, self.style);
707
708 self.block.as_ref().render(area, buf);
709 let inner = self.block.inner_if_some(area);
710
711 if inner.is_empty() || self.data.is_empty() || self.bar_width == 0 {
712 return;
713 }
714
715 match self.direction {
716 Direction::Horizontal => self.render_horizontal(buf, inner),
717 Direction::Vertical => self.render_vertical(buf, inner),
718 }
719 }
720}
721
722impl Styled for BarChart<'_> {
723 type Item = Self;
724 fn style(&self) -> Style {
725 self.style
726 }
727
728 fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
729 self.style(style)
730 }
731}
732
733#[cfg(test)]
734mod tests {
735 use itertools::iproduct;
736 use ratatui_core::layout::Alignment;
737 use ratatui_core::style::{Color, Modifier, Stylize};
738 use ratatui_core::text::Span;
739 use rstest::rstest;
740
741 use super::*;
742 use crate::borders::BorderType;
743
744 #[test]
745 fn default() {
746 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
747 let widget = BarChart::default();
748 widget.render(buffer.area, &mut buffer);
749 assert_eq!(buffer, Buffer::with_lines([" "; 3]));
750 }
751
752 #[test]
753 fn horizontal_empty_barchart_does_not_panic() {
754 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
755 let widget = BarChart::horizontal(Vec::<Bar>::new());
756
757 widget.render(buffer.area, &mut buffer);
758 }
759
760 #[test]
761 fn constructors_ignore_empty_groups() {
762 assert!(BarChart::new(Vec::<Bar>::new()).data.is_empty());
763 assert!(BarChart::horizontal(Vec::<Bar>::new()).data.is_empty());
764
765 let chart = BarChart::grouped([
766 BarGroup::new(Vec::<Bar>::new()),
767 BarGroup::new([Bar::new(1)]),
768 ]);
769 assert_eq!(chart.data, vec![BarGroup::new([Bar::new(1)])]);
770 }
771
772 #[test]
773 fn data() {
774 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
775 let widget = BarChart::default().data(&[("foo", 1), ("bar", 2)]);
776 widget.render(buffer.area, &mut buffer);
777 #[rustfmt::skip]
778 let expected = Buffer::with_lines([
779 " █ ",
780 "1 2 ",
781 "f b ",
782 ]);
783 assert_eq!(buffer, expected);
784 }
785
786 #[test]
787 fn block() {
788 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 5));
789 let block = Block::bordered()
790 .border_type(BorderType::Double)
791 .title("Block");
792 let widget = BarChart::default()
793 .data(&[("foo", 1), ("bar", 2)])
794 .block(block);
795 widget.render(buffer.area, &mut buffer);
796 let expected = Buffer::with_lines([
797 "╔Block═══╗",
798 "║ █ ║",
799 "║1 2 ║",
800 "║f b ║",
801 "╚════════╝",
802 ]);
803 assert_eq!(buffer, expected);
804 }
805
806 #[test]
807 fn max() {
808 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
809 let without_max = BarChart::default().data(&[("foo", 1), ("bar", 2), ("baz", 100)]);
810 without_max.render(buffer.area, &mut buffer);
811 #[rustfmt::skip]
812 let expected = Buffer::with_lines([
813 " █ ",
814 " █ ",
815 "f b b ",
816 ]);
817 assert_eq!(buffer, expected);
818 let with_max = BarChart::default()
819 .data(&[("foo", 1), ("bar", 2), ("baz", 100)])
820 .max(2);
821 with_max.render(buffer.area, &mut buffer);
822 #[rustfmt::skip]
823 let expected = Buffer::with_lines([
824 " █ █ ",
825 "1 2 █ ",
826 "f b b ",
827 ]);
828 assert_eq!(buffer, expected);
829 }
830
831 #[test]
832 fn bar_style() {
833 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
834 let widget = BarChart::default()
835 .data(&[("foo", 1), ("bar", 2)])
836 .bar_style(Style::new().red());
837 widget.render(buffer.area, &mut buffer);
838 #[rustfmt::skip]
839 let mut expected = Buffer::with_lines([
840 " █ ",
841 "1 2 ",
842 "f b ",
843 ]);
844 for (x, y) in iproduct!([0, 2], [0, 1]) {
845 expected[(x, y)].set_fg(Color::Red);
846 }
847 assert_eq!(buffer, expected);
848 }
849
850 #[test]
851 fn bar_width() {
852 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
853 let widget = BarChart::default()
854 .data(&[("foo", 1), ("bar", 2)])
855 .bar_width(3);
856 widget.render(buffer.area, &mut buffer);
857 #[rustfmt::skip]
858 let expected = Buffer::with_lines([
859 " ███ ",
860 "█1█ █2█ ",
861 "foo bar ",
862 ]);
863 assert_eq!(buffer, expected);
864 }
865
866 #[test]
867 fn bar_gap() {
868 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
869 let widget = BarChart::default()
870 .data(&[("foo", 1), ("bar", 2)])
871 .bar_gap(2);
872 widget.render(buffer.area, &mut buffer);
873 #[rustfmt::skip]
874 let expected = Buffer::with_lines([
875 " █ ",
876 "1 2 ",
877 "f b ",
878 ]);
879 assert_eq!(buffer, expected);
880 }
881
882 #[test]
883 fn bar_set() {
884 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
885 let widget = BarChart::default()
886 .data(&[("foo", 0), ("bar", 1), ("baz", 3)])
887 .bar_set(symbols::bar::THREE_LEVELS);
888 widget.render(buffer.area, &mut buffer);
889 #[rustfmt::skip]
890 let expected = Buffer::with_lines([
891 " █ ",
892 " ▄ 3 ",
893 "f b b ",
894 ]);
895 assert_eq!(buffer, expected);
896 }
897
898 #[test]
899 fn bar_set_nine_levels() {
900 let mut buffer = Buffer::empty(Rect::new(0, 0, 18, 3));
901 let widget = BarChart::default()
902 .data(&[
903 ("a", 0),
904 ("b", 1),
905 ("c", 2),
906 ("d", 3),
907 ("e", 4),
908 ("f", 5),
909 ("g", 6),
910 ("h", 7),
911 ("i", 8),
912 ])
913 .bar_set(symbols::bar::NINE_LEVELS);
914 widget.render(Rect::new(0, 1, 18, 2), &mut buffer);
915 let expected = Buffer::with_lines([
916 " ",
917 " ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8 ",
918 "a b c d e f g h i ",
919 ]);
920 assert_eq!(buffer, expected);
921 }
922
923 #[test]
924 fn value_style() {
925 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
926 let widget = BarChart::default()
927 .data(&[("foo", 1), ("bar", 2)])
928 .bar_width(3)
929 .value_style(Style::new().red());
930 widget.render(buffer.area, &mut buffer);
931 #[rustfmt::skip]
932 let mut expected = Buffer::with_lines([
933 " ███ ",
934 "█1█ █2█ ",
935 "foo bar ",
936 ]);
937 expected[(1, 1)].set_fg(Color::Red);
938 expected[(5, 1)].set_fg(Color::Red);
939 assert_eq!(buffer, expected);
940 }
941
942 #[test]
943 fn label_style() {
944 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
945 let widget = BarChart::default()
946 .data(&[("foo", 1), ("bar", 2)])
947 .label_style(Style::new().red());
948 widget.render(buffer.area, &mut buffer);
949 #[rustfmt::skip]
950 let mut expected = Buffer::with_lines([
951 " █ ",
952 "1 2 ",
953 "f b ",
954 ]);
955 expected[(0, 2)].set_fg(Color::Red);
956 expected[(2, 2)].set_fg(Color::Red);
957 assert_eq!(buffer, expected);
958 }
959
960 #[test]
961 fn style() {
962 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
963 let widget = BarChart::default()
964 .data(&[("foo", 1), ("bar", 2)])
965 .style(Style::new().red());
966 widget.render(buffer.area, &mut buffer);
967 #[rustfmt::skip]
968 let mut expected = Buffer::with_lines([
969 " █ ",
970 "1 2 ",
971 "f b ",
972 ]);
973 for (x, y) in iproduct!(0..10, 0..3) {
974 expected[(x, y)].set_fg(Color::Red);
975 }
976 assert_eq!(buffer, expected);
977 }
978
979 #[test]
980 fn can_be_stylized() {
981 assert_eq!(
982 BarChart::default().black().on_white().bold().style,
983 Style::default()
984 .fg(Color::Black)
985 .bg(Color::White)
986 .add_modifier(Modifier::BOLD)
987 );
988 }
989
990 #[test]
991 fn test_empty_group() {
992 let chart = BarChart::default()
993 .data(BarGroup::default().label("invisible"))
994 .data(
995 BarGroup::default()
996 .label("G")
997 .bars(&[Bar::default().value(1), Bar::default().value(2)]),
998 );
999
1000 let mut buffer = Buffer::empty(Rect::new(0, 0, 3, 3));
1001 chart.render(buffer.area, &mut buffer);
1002 #[rustfmt::skip]
1003 let expected = Buffer::with_lines([
1004 " █",
1005 "1 2",
1006 "G ",
1007 ]);
1008 assert_eq!(buffer, expected);
1009 }
1010
1011 fn build_test_barchart<'a>() -> BarChart<'a> {
1012 BarChart::default()
1013 .data(BarGroup::default().label("G1").bars(&[
1014 Bar::default().value(2),
1015 Bar::default().value(3),
1016 Bar::default().value(4),
1017 ]))
1018 .data(BarGroup::default().label("G2").bars(&[
1019 Bar::default().value(3),
1020 Bar::default().value(4),
1021 Bar::default().value(5),
1022 ]))
1023 .group_gap(1)
1024 .direction(Direction::Horizontal)
1025 .bar_gap(0)
1026 }
1027
1028 #[test]
1029 fn test_horizontal_bars() {
1030 let chart: BarChart<'_> = build_test_barchart();
1031
1032 let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 8));
1033 chart.render(buffer.area, &mut buffer);
1034 let expected = Buffer::with_lines([
1035 "2█ ",
1036 "3██ ",
1037 "4███ ",
1038 "G1 ",
1039 "3██ ",
1040 "4███ ",
1041 "5████",
1042 "G2 ",
1043 ]);
1044 assert_eq!(buffer, expected);
1045 }
1046
1047 #[test]
1048 fn test_horizontal_bars_no_space_for_group_label() {
1049 let chart: BarChart<'_> = build_test_barchart();
1050
1051 let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 7));
1052 chart.render(buffer.area, &mut buffer);
1053 let expected = Buffer::with_lines([
1054 "2█ ",
1055 "3██ ",
1056 "4███ ",
1057 "G1 ",
1058 "3██ ",
1059 "4███ ",
1060 "5████",
1061 ]);
1062 assert_eq!(buffer, expected);
1063 }
1064
1065 #[test]
1066 fn test_horizontal_bars_no_space_for_all_bars() {
1067 let chart: BarChart<'_> = build_test_barchart();
1068
1069 let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 5));
1070 chart.render(buffer.area, &mut buffer);
1071 #[rustfmt::skip]
1072 let expected = Buffer::with_lines([
1073 "2█ ",
1074 "3██ ",
1075 "4███ ",
1076 "G1 ",
1077 "3██ ",
1078 ]);
1079 assert_eq!(buffer, expected);
1080 }
1081
1082 fn test_horizontal_bars_label_width_greater_than_bar(bar_color: Option<Color>) {
1083 let mut bar = Bar::default()
1084 .value(2)
1085 .text_value("label")
1086 .value_style(Style::default().red());
1087
1088 if let Some(color) = bar_color {
1089 bar = bar.style(Style::default().fg(color));
1090 }
1091
1092 let chart: BarChart<'_> = BarChart::default()
1093 .data(BarGroup::default().bars(&[bar, Bar::default().value(5)]))
1094 .direction(Direction::Horizontal)
1095 .bar_style(Style::default().yellow())
1096 .value_style(Style::default().italic())
1097 .bar_gap(0);
1098
1099 let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 2));
1100 chart.render(buffer.area, &mut buffer);
1101
1102 let mut expected = Buffer::with_lines(["label", "5████"]);
1103
1104 expected[(0, 1)].modifier.insert(Modifier::ITALIC);
1106 for x in 0..5 {
1107 expected[(x, 1)].set_fg(Color::Yellow);
1108 }
1109
1110 let expected_color = bar_color.unwrap_or(Color::Yellow);
1111
1112 let cell = expected[(0, 0)].set_fg(Color::Red);
1116 cell.modifier.insert(Modifier::ITALIC);
1117 let cell = expected[(1, 0)].set_fg(Color::Red);
1118 cell.modifier.insert(Modifier::ITALIC);
1119 expected[(2, 0)].set_fg(expected_color);
1120 expected[(3, 0)].set_fg(expected_color);
1121 expected[(4, 0)].set_fg(expected_color);
1122
1123 assert_eq!(buffer, expected);
1124 }
1125
1126 #[test]
1127 fn test_horizontal_bars_label_width_greater_than_bar_without_style() {
1128 test_horizontal_bars_label_width_greater_than_bar(None);
1129 }
1130
1131 #[test]
1132 fn test_horizontal_bars_label_width_greater_than_bar_with_style() {
1133 test_horizontal_bars_label_width_greater_than_bar(Some(Color::White));
1134 }
1135
1136 #[test]
1138 fn test_horizontal_label() {
1139 let chart = BarChart::default()
1140 .direction(Direction::Horizontal)
1141 .bar_gap(0)
1142 .data(&[("Jan", 10), ("Feb", 20), ("Mar", 5)]);
1143
1144 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1145 chart.render(buffer.area, &mut buffer);
1146 #[rustfmt::skip]
1147 let expected = Buffer::with_lines([
1148 "Jan 10█ ",
1149 "Feb 20████",
1150 "Mar 5 ",
1151 ]);
1152 assert_eq!(buffer, expected);
1153 }
1154
1155 #[test]
1156 fn test_group_label_style() {
1157 let chart: BarChart<'_> = BarChart::default()
1158 .data(
1159 BarGroup::default()
1160 .label(Span::from("G1").red())
1161 .bars(&[Bar::default().value(2)]),
1162 )
1163 .group_gap(1)
1164 .direction(Direction::Horizontal)
1165 .label_style(Style::default().bold().yellow());
1166
1167 let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 2));
1168 chart.render(buffer.area, &mut buffer);
1169
1170 let mut expected = Buffer::with_lines(["2████", "G1 "]);
1174 let cell = expected[(0, 1)].set_fg(Color::Red);
1175 cell.modifier.insert(Modifier::BOLD);
1176 let cell = expected[(1, 1)].set_fg(Color::Red);
1177 cell.modifier.insert(Modifier::BOLD);
1178
1179 assert_eq!(buffer, expected);
1180 }
1181
1182 #[test]
1183 fn test_group_label_center() {
1184 let group = BarGroup::from(&[("a", 1), ("b", 2), ("c", 3), ("c", 4)]);
1186 let chart = BarChart::default()
1187 .data(
1188 group
1189 .clone()
1190 .label(Line::from("G1").alignment(Alignment::Center)),
1191 )
1192 .data(group.label(Line::from("G2").alignment(Alignment::Center)));
1193
1194 let mut buffer = Buffer::empty(Rect::new(0, 0, 13, 5));
1195 chart.render(buffer.area, &mut buffer);
1196 let expected = Buffer::with_lines([
1197 " ▂ █ ▂",
1198 " ▄ █ █ ▄ █",
1199 "▆ 2 3 4 ▆ 2 3",
1200 "a b c c a b c",
1201 " G1 G2 ",
1202 ]);
1203 assert_eq!(buffer, expected);
1204 }
1205
1206 #[test]
1207 fn test_group_label_right() {
1208 let chart: BarChart<'_> = BarChart::default().data(
1209 BarGroup::default()
1210 .label(Line::from(Span::from("G")).alignment(Alignment::Right))
1211 .bars(&[Bar::default().value(2), Bar::default().value(5)]),
1212 );
1213
1214 let mut buffer = Buffer::empty(Rect::new(0, 0, 3, 3));
1215 chart.render(buffer.area, &mut buffer);
1216 #[rustfmt::skip]
1217 let expected = Buffer::with_lines([
1218 " █",
1219 "▆ 5",
1220 " G",
1221 ]);
1222 assert_eq!(buffer, expected);
1223 }
1224
1225 #[test]
1226 fn test_unicode_as_value() {
1227 let group = BarGroup::default().bars(&[
1228 Bar::default().value(123).label("B1").text_value("写"),
1229 Bar::default().value(321).label("B2").text_value("写"),
1230 Bar::default().value(333).label("B2").text_value("写"),
1231 ]);
1232 let chart = BarChart::default().data(group).bar_width(3).bar_gap(1);
1233
1234 let mut buffer = Buffer::empty(Rect::new(0, 0, 11, 5));
1235 chart.render(buffer.area, &mut buffer);
1236 let expected = Buffer::with_lines([
1237 " ▆▆▆ ███",
1238 " ███ ███",
1239 "▃▃▃ ███ ███",
1240 "写█ 写█ 写█",
1241 "B1 B2 B2 ",
1242 ]);
1243 assert_eq!(buffer, expected);
1244 }
1245
1246 #[test]
1247 fn handles_zero_width() {
1248 let chart = BarChart::default()
1250 .data(&[("A", 1)])
1251 .bar_width(0)
1252 .bar_gap(0);
1253 let mut buffer = Buffer::empty(Rect::new(0, 0, 0, 10));
1254 chart.render(buffer.area, &mut buffer);
1255 assert_eq!(buffer, Buffer::empty(Rect::new(0, 0, 0, 10)));
1256 }
1257
1258 #[test]
1259 fn single_line() {
1260 let mut group: BarGroup = (&[
1261 ("a", 0),
1262 ("b", 1),
1263 ("c", 2),
1264 ("d", 3),
1265 ("e", 4),
1266 ("f", 5),
1267 ("g", 6),
1268 ("h", 7),
1269 ("i", 8),
1270 ])
1271 .into();
1272 group = group.label("Group");
1273
1274 let chart = BarChart::default()
1275 .data(group)
1276 .bar_set(symbols::bar::NINE_LEVELS);
1277
1278 let mut buffer = Buffer::empty(Rect::new(0, 0, 17, 1));
1279 chart.render(buffer.area, &mut buffer);
1280 assert_eq!(buffer, Buffer::with_lines([" ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8"]));
1281 }
1282
1283 #[test]
1284 fn two_lines() {
1285 let mut group: BarGroup = (&[
1286 ("a", 0),
1287 ("b", 1),
1288 ("c", 2),
1289 ("d", 3),
1290 ("e", 4),
1291 ("f", 5),
1292 ("g", 6),
1293 ("h", 7),
1294 ("i", 8),
1295 ])
1296 .into();
1297 group = group.label("Group");
1298
1299 let chart = BarChart::default()
1300 .data(group)
1301 .bar_set(symbols::bar::NINE_LEVELS);
1302
1303 let mut buffer = Buffer::empty(Rect::new(0, 0, 17, 3));
1304 chart.render(Rect::new(0, 1, buffer.area.width, 2), &mut buffer);
1305 let expected = Buffer::with_lines([
1306 " ",
1307 " ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8",
1308 "a b c d e f g h i",
1309 ]);
1310 assert_eq!(buffer, expected);
1311 }
1312
1313 #[test]
1314 fn three_lines() {
1315 let mut group: BarGroup = (&[
1316 ("a", 0),
1317 ("b", 1),
1318 ("c", 2),
1319 ("d", 3),
1320 ("e", 4),
1321 ("f", 5),
1322 ("g", 6),
1323 ("h", 7),
1324 ("i", 8),
1325 ])
1326 .into();
1327 group = group.label(Line::from("Group").alignment(Alignment::Center));
1328
1329 let chart = BarChart::default()
1330 .data(group)
1331 .bar_set(symbols::bar::NINE_LEVELS);
1332
1333 let mut buffer = Buffer::empty(Rect::new(0, 0, 17, 3));
1334 chart.render(buffer.area, &mut buffer);
1335 let expected = Buffer::with_lines([
1336 " ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8",
1337 "a b c d e f g h i",
1338 " Group ",
1339 ]);
1340 assert_eq!(buffer, expected);
1341 }
1342
1343 #[test]
1344 fn three_lines_double_width() {
1345 let mut group = BarGroup::from(&[
1346 ("a", 0),
1347 ("b", 1),
1348 ("c", 2),
1349 ("d", 3),
1350 ("e", 4),
1351 ("f", 5),
1352 ("g", 6),
1353 ("h", 7),
1354 ("i", 8),
1355 ]);
1356 group = group.label(Line::from("Group").alignment(Alignment::Center));
1357
1358 let chart = BarChart::default()
1359 .data(group)
1360 .bar_width(2)
1361 .bar_set(symbols::bar::NINE_LEVELS);
1362
1363 let mut buffer = Buffer::empty(Rect::new(0, 0, 26, 3));
1364 chart.render(buffer.area, &mut buffer);
1365 let expected = Buffer::with_lines([
1366 " 1▁ 2▂ 3▃ 4▄ 5▅ 6▆ 7▇ 8█",
1367 "a b c d e f g h i ",
1368 " Group ",
1369 ]);
1370 assert_eq!(buffer, expected);
1371 }
1372
1373 #[test]
1374 fn four_lines() {
1375 let mut group: BarGroup = (&[
1376 ("a", 0),
1377 ("b", 1),
1378 ("c", 2),
1379 ("d", 3),
1380 ("e", 4),
1381 ("f", 5),
1382 ("g", 6),
1383 ("h", 7),
1384 ("i", 8),
1385 ])
1386 .into();
1387 group = group.label(Line::from("Group").alignment(Alignment::Center));
1388
1389 let chart = BarChart::default()
1390 .data(group)
1391 .bar_set(symbols::bar::NINE_LEVELS);
1392
1393 let mut buffer = Buffer::empty(Rect::new(0, 0, 17, 4));
1394 chart.render(buffer.area, &mut buffer);
1395 let expected = Buffer::with_lines([
1396 " ▂ ▄ ▆ █",
1397 " ▂ ▄ ▆ 4 5 6 7 8",
1398 "a b c d e f g h i",
1399 " Group ",
1400 ]);
1401 assert_eq!(buffer, expected);
1402 }
1403
1404 #[test]
1405 fn two_lines_without_bar_labels() {
1406 let group = BarGroup::default()
1407 .label(Line::from("Group").alignment(Alignment::Center))
1408 .bars(&[
1409 Bar::default().value(0),
1410 Bar::default().value(1),
1411 Bar::default().value(2),
1412 Bar::default().value(3),
1413 Bar::default().value(4),
1414 Bar::default().value(5),
1415 Bar::default().value(6),
1416 Bar::default().value(7),
1417 Bar::default().value(8),
1418 ]);
1419
1420 let chart = BarChart::default().data(group);
1421
1422 let mut buffer = Buffer::empty(Rect::new(0, 0, 17, 3));
1423 chart.render(Rect::new(0, 1, buffer.area.width, 2), &mut buffer);
1424 let expected = Buffer::with_lines([
1425 " ",
1426 " ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8",
1427 " Group ",
1428 ]);
1429 assert_eq!(buffer, expected);
1430 }
1431
1432 #[test]
1433 fn one_lines_with_more_bars() {
1434 let bars: Vec<Bar> = (0..30).map(|i| Bar::default().value(i)).collect();
1435
1436 let chart = BarChart::default().data(BarGroup::default().bars(&bars));
1437
1438 let mut buffer = Buffer::empty(Rect::new(0, 0, 59, 1));
1439 chart.render(buffer.area, &mut buffer);
1440 let expected =
1441 Buffer::with_lines([" ▁ ▁ ▁ ▁ ▂ ▂ ▂ ▃ ▃ ▃ ▃ ▄ ▄ ▄ ▄ ▅ ▅ ▅ ▆ ▆ ▆ ▆ ▇ ▇ ▇ █"]);
1442 assert_eq!(buffer, expected);
1443 }
1444
1445 #[test]
1446 fn first_bar_of_the_group_is_half_outside_view() {
1447 let chart = BarChart::default()
1448 .data(&[("a", 1), ("b", 2)])
1449 .data(&[("a", 1), ("b", 2)])
1450 .bar_width(2);
1451
1452 let mut buffer = Buffer::empty(Rect::new(0, 0, 7, 6));
1453 chart.render(buffer.area, &mut buffer);
1454 let expected = Buffer::with_lines([
1455 " ██ ",
1456 " ██ ",
1457 "▄▄ ██ ",
1458 "██ ██ ",
1459 "1█ 2█ ",
1460 "a b ",
1461 ]);
1462 assert_eq!(buffer, expected);
1463 }
1464
1465 #[test]
1466 fn render_handles_u64_max_value() {
1467 let chart = BarChart::new([Bar::new(u64::MAX)]).max(u64::MAX);
1468 let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 5));
1469
1470 chart.render(buffer.area, &mut buffer);
1471
1472 let expected = Buffer::with_lines(["█ "; 5]);
1473 assert_eq!(buffer, expected);
1474 }
1475
1476 #[test]
1477 fn render_keeps_integer_precision_for_large_values() {
1478 let chart = BarChart::new([Bar::new(u64::MAX - 1)]).max(u64::MAX);
1479 let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
1480
1481 chart.render(buffer.area, &mut buffer);
1482
1483 assert_eq!(buffer, Buffer::with_lines(["▇"]));
1484 }
1485
1486 #[test]
1487 fn test_barchart_new() {
1488 let bars = [Bar::with_label("Red", 1), Bar::with_label("Green", 2)];
1489
1490 let chart = BarChart::new(bars.clone());
1491 assert_eq!(chart.data.len(), 1);
1492 assert_eq!(chart.data[0].bars, bars);
1493
1494 let bars2 = [("Blue", 3)];
1495
1496 let updated_chart = chart.data(&bars2);
1497 assert_eq!(updated_chart.data.len(), 2);
1498 assert_eq!(updated_chart.data[1].bars, [Bar::with_label("Blue", 3)]);
1499 }
1500
1501 #[test]
1506 fn regression_1928() {
1507 let text_value = "\u{202f}"; let bars = [
1509 Bar::default().text_value(text_value).value(0),
1510 Bar::default().text_value(text_value).value(1),
1511 Bar::default().text_value(text_value).value(2),
1512 Bar::default().text_value(text_value).value(3),
1513 Bar::default().text_value(text_value).value(4),
1514 ];
1515 let chart = BarChart::default()
1516 .data(BarGroup::default().bars(&bars))
1517 .bar_gap(0)
1518 .direction(Direction::Horizontal);
1519 let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 5));
1520 chart.render(buffer.area, &mut buffer);
1521 #[rustfmt::skip]
1522 let expected = Buffer::with_lines([
1523 "\u{202f} ",
1524 "\u{202f} ",
1525 "\u{202f}█ ",
1526 "\u{202f}██ ",
1527 "\u{202f}███",
1528 ]);
1529 assert_eq!(buffer, expected);
1530 }
1531
1532 #[rstest]
1533 #[case::horizontal(Direction::Horizontal)]
1534 #[case::vertical(Direction::Vertical)]
1535 fn render_in_minimal_buffer(#[case] direction: Direction) {
1536 let chart = BarChart::default()
1537 .data(&[("A", 1), ("B", 2)])
1538 .bar_width(3)
1539 .bar_gap(1)
1540 .direction(direction);
1541
1542 let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
1543 chart.render(buffer.area, &mut buffer);
1545 assert_eq!(buffer, Buffer::with_lines([" "]));
1546 }
1547
1548 #[rstest]
1549 #[case::horizontal(Direction::Horizontal)]
1550 #[case::vertical(Direction::Vertical)]
1551 fn render_in_zero_size_buffer(#[case] direction: Direction) {
1552 let chart = BarChart::default()
1553 .data(&[("A", 1), ("B", 2)])
1554 .bar_width(3)
1555 .bar_gap(1)
1556 .direction(direction);
1557
1558 let mut buffer = Buffer::empty(Rect::ZERO);
1559 chart.render(buffer.area, &mut buffer);
1561 }
1562}