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