rumatui_tui/widgets/
barchart.rs1use std::cmp::{max, min};
2
3use unicode_width::UnicodeWidthStr;
4
5use crate::buffer::Buffer;
6use crate::layout::Rect;
7use crate::style::Style;
8use crate::symbols::bar;
9use crate::widgets::{Block, Widget};
10
11pub struct BarChart<'a> {
29 block: Option<Block<'a>>,
31 bar_width: u16,
33 bar_gap: u16,
35 value_style: Style,
37 label_style: Style,
39 style: Style,
41 data: &'a [(&'a str, u64)],
43 max: Option<u64>,
46 values: Vec<String>,
48}
49
50impl<'a> Default for BarChart<'a> {
51 fn default() -> BarChart<'a> {
52 BarChart {
53 block: None,
54 max: None,
55 data: &[],
56 values: Vec::new(),
57 bar_width: 1,
58 bar_gap: 1,
59 value_style: Default::default(),
60 label_style: Default::default(),
61 style: Default::default(),
62 }
63 }
64}
65
66impl<'a> BarChart<'a> {
67 pub fn data(mut self, data: &'a [(&'a str, u64)]) -> BarChart<'a> {
68 self.data = data;
69 self.values = Vec::with_capacity(self.data.len());
70 for &(_, v) in self.data {
71 self.values.push(format!("{}", v));
72 }
73 self
74 }
75
76 pub fn block(mut self, block: Block<'a>) -> BarChart<'a> {
77 self.block = Some(block);
78 self
79 }
80 pub fn max(mut self, max: u64) -> BarChart<'a> {
81 self.max = Some(max);
82 self
83 }
84
85 pub fn bar_width(mut self, width: u16) -> BarChart<'a> {
86 self.bar_width = width;
87 self
88 }
89 pub fn bar_gap(mut self, gap: u16) -> BarChart<'a> {
90 self.bar_gap = gap;
91 self
92 }
93 pub fn value_style(mut self, style: Style) -> BarChart<'a> {
94 self.value_style = style;
95 self
96 }
97 pub fn label_style(mut self, style: Style) -> BarChart<'a> {
98 self.label_style = style;
99 self
100 }
101 pub fn style(mut self, style: Style) -> BarChart<'a> {
102 self.style = style;
103 self
104 }
105}
106
107impl<'a> Widget for BarChart<'a> {
108 fn render(mut self, area: Rect, buf: &mut Buffer) {
109 let chart_area = match self.block {
110 Some(ref mut b) => {
111 b.render(area, buf);
112 b.inner(area)
113 }
114 None => area,
115 };
116
117 if chart_area.height < 2 {
118 return;
119 }
120
121 buf.set_background(chart_area, self.style.bg);
122
123 let max = self
124 .max
125 .unwrap_or_else(|| self.data.iter().fold(0, |acc, &(_, v)| max(v, acc)));
126 let max_index = min(
127 (chart_area.width / (self.bar_width + self.bar_gap)) as usize,
128 self.data.len(),
129 );
130 let mut data = self
131 .data
132 .iter()
133 .take(max_index)
134 .map(|&(l, v)| {
135 (
136 l,
137 v * u64::from(chart_area.height) * 8 / std::cmp::max(max, 1),
138 )
139 })
140 .collect::<Vec<(&str, u64)>>();
141 for j in (0..chart_area.height - 1).rev() {
142 for (i, d) in data.iter_mut().enumerate() {
143 let symbol = match d.1 {
144 0 => " ",
145 1 => bar::ONE_EIGHTH,
146 2 => bar::ONE_QUARTER,
147 3 => bar::THREE_EIGHTHS,
148 4 => bar::HALF,
149 5 => bar::FIVE_EIGHTHS,
150 6 => bar::THREE_QUARTERS,
151 7 => bar::SEVEN_EIGHTHS,
152 _ => bar::FULL,
153 };
154
155 for x in 0..self.bar_width {
156 buf.get_mut(
157 chart_area.left() + i as u16 * (self.bar_width + self.bar_gap) + x,
158 chart_area.top() + j,
159 )
160 .set_symbol(symbol)
161 .set_style(self.style);
162 }
163
164 if d.1 > 8 {
165 d.1 -= 8;
166 } else {
167 d.1 = 0;
168 }
169 }
170 }
171
172 for (i, &(label, value)) in self.data.iter().take(max_index).enumerate() {
173 if value != 0 {
174 let value_label = &self.values[i];
175 let width = value_label.width() as u16;
176 if width < self.bar_width {
177 buf.set_string(
178 chart_area.left()
179 + i as u16 * (self.bar_width + self.bar_gap)
180 + (self.bar_width - width) / 2,
181 chart_area.bottom() - 2,
182 value_label,
183 self.value_style,
184 );
185 }
186 }
187 buf.set_stringn(
188 chart_area.left() + i as u16 * (self.bar_width + self.bar_gap),
189 chart_area.bottom() - 1,
190 label,
191 self.bar_width as usize,
192 self.label_style,
193 );
194 }
195 }
196}