tui_lipan/widgets/heatmap/
mod.rs1mod layout;
4mod node;
5mod reconcile;
6
7pub use layout::measure_heatmap;
8pub use node::HeatmapNode;
9pub(crate) use reconcile::reconcile_heatmap_node;
10
11use std::sync::Arc;
12
13use crate::core::element::{Element, ElementKind};
14use crate::layout::hash::LayoutHash;
15use crate::style::{BorderStyle, Color, Length, Padding, Style};
16use crate::utils::gradient::ColorGradient;
17use unicode_width::UnicodeWidthStr;
18
19#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
21pub enum HeatmapCellMode {
22 #[default]
24 Background,
25 Glyph(Arc<str>),
27 GlyphForeground(Arc<str>),
29}
30
31#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
33pub enum HeatmapLegendWidth {
34 #[default]
36 Grid,
37 Full,
39}
40
41#[derive(Clone)]
43pub struct Heatmap {
44 pub(crate) data: Vec<Vec<f64>>,
45 pub(crate) row_labels: Vec<Arc<str>>,
46 pub(crate) column_labels: Vec<Arc<str>>,
47 pub(crate) gradient: ColorGradient,
48 pub(crate) range_min: Option<f64>,
49 pub(crate) range_max: Option<f64>,
50 pub(crate) cell_mode: HeatmapCellMode,
51 pub(crate) cell_width: u16,
52 pub(crate) gap_x: u16,
53 pub(crate) gap_y: u16,
54 pub(crate) legend_gap: u16,
55 pub(crate) legend_spacing: u16,
56 pub(crate) legend_width: HeatmapLegendWidth,
57 pub(crate) show_values: bool,
58 pub(crate) show_legend: bool,
59 pub(crate) style: Style,
60 pub(crate) label_style: Style,
61 pub(crate) legend_style: Style,
62 pub(crate) padding: Padding,
63 pub(crate) border: bool,
64 pub(crate) border_style: BorderStyle,
65 pub(crate) width: Length,
66 pub(crate) height: Length,
67}
68
69impl Default for Heatmap {
70 fn default() -> Self {
71 Self {
72 data: Vec::new(),
73 row_labels: Vec::new(),
74 column_labels: Vec::new(),
75 gradient: ColorGradient::new(Color::Rgb(60, 179, 113), Color::Rgb(226, 82, 87)),
76 range_min: None,
77 range_max: None,
78 cell_mode: HeatmapCellMode::Background,
79 cell_width: 4,
80 gap_x: 0,
81 gap_y: 0,
82 legend_gap: 0,
83 legend_spacing: 0,
84 legend_width: HeatmapLegendWidth::Grid,
85 show_values: false,
86 show_legend: false,
87 style: Style::default(),
88 label_style: Style::default(),
89 legend_style: Style::default(),
90 padding: Padding::default(),
91 border: false,
92 border_style: BorderStyle::Plain,
93 width: Length::Auto,
94 height: Length::Auto,
95 }
96 }
97}
98
99impl Heatmap {
100 pub fn new(data: impl IntoIterator<Item = impl IntoIterator<Item = f64>>) -> Self {
102 Self {
103 data: data
104 .into_iter()
105 .map(|row| row.into_iter().collect())
106 .collect(),
107 ..Self::default()
108 }
109 }
110
111 pub fn row_labels(mut self, labels: impl IntoIterator<Item = impl Into<Arc<str>>>) -> Self {
113 self.row_labels = labels.into_iter().map(Into::into).collect();
114 self
115 }
116
117 pub fn column_labels(mut self, labels: impl IntoIterator<Item = impl Into<Arc<str>>>) -> Self {
119 self.column_labels = labels.into_iter().map(Into::into).collect();
120 self
121 }
122
123 pub fn gradient(mut self, gradient: ColorGradient) -> Self {
125 self.gradient = gradient;
126 self
127 }
128
129 pub fn range(mut self, min: f64, max: f64) -> Self {
131 self.range_min = Some(min);
132 self.range_max = Some(max);
133 self
134 }
135
136 pub fn cell_mode(mut self, cell_mode: HeatmapCellMode) -> Self {
138 self.cell_mode = cell_mode;
139 self
140 }
141
142 pub fn cell_width(mut self, cell_width: u16) -> Self {
144 self.cell_width = cell_width.max(1);
145 self
146 }
147
148 pub fn gap_x(mut self, gap_x: u16) -> Self {
150 self.gap_x = gap_x;
151 self
152 }
153
154 pub fn gap_y(mut self, gap_y: u16) -> Self {
156 self.gap_y = gap_y;
157 self
158 }
159
160 pub fn legend_gap(mut self, legend_gap: u16) -> Self {
162 self.legend_gap = legend_gap;
163 self
164 }
165
166 pub fn legend_spacing(mut self, legend_spacing: u16) -> Self {
168 self.legend_spacing = legend_spacing;
169 self
170 }
171
172 pub fn legend_width(mut self, legend_width: HeatmapLegendWidth) -> Self {
174 self.legend_width = legend_width;
175 self
176 }
177
178 pub fn show_values(mut self, show: bool) -> Self {
180 self.show_values = show;
181 self
182 }
183
184 pub fn show_legend(mut self, show: bool) -> Self {
186 self.show_legend = show;
187 self
188 }
189
190 pub fn style(mut self, style: Style) -> Self {
192 self.style = style;
193 self
194 }
195
196 pub fn label_style(mut self, style: Style) -> Self {
198 self.label_style = style;
199 self
200 }
201
202 pub fn legend_style(mut self, style: Style) -> Self {
204 self.legend_style = style;
205 self
206 }
207
208 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
210 self.padding = padding.into();
211 self
212 }
213
214 pub fn border(mut self, border: bool) -> Self {
216 self.border = border;
217 self
218 }
219
220 pub fn border_style(mut self, border_style: BorderStyle) -> Self {
222 self.border_style = border_style;
223 self
224 }
225
226 pub fn width(mut self, width: Length) -> Self {
228 self.width = width;
229 self
230 }
231
232 pub fn height(mut self, height: Length) -> Self {
234 self.height = height;
235 self
236 }
237
238 pub(crate) fn effective_cell_width(&self) -> u16 {
239 let min_width = match &self.cell_mode {
240 HeatmapCellMode::Background => 1,
241 HeatmapCellMode::Glyph(glyph) | HeatmapCellMode::GlyphForeground(glyph) => {
242 UnicodeWidthStr::width(glyph.as_ref())
243 .max(1)
244 .min(u16::MAX as usize) as u16
245 }
246 };
247
248 self.cell_width.max(min_width)
249 }
250}
251
252impl From<Heatmap> for Element {
253 fn from(value: Heatmap) -> Self {
254 Element::new(ElementKind::Heatmap(value))
255 }
256}
257
258impl LayoutHash for Heatmap {
259 fn layout_hash(
260 &self,
261 hasher: &mut impl std::hash::Hasher,
262 _recurse: &dyn Fn(&Element) -> Option<u64>,
263 ) -> Option<()> {
264 use std::hash::Hash;
265
266 self.width.hash(hasher);
267 self.height.hash(hasher);
268 self.row_labels.hash(hasher);
269 self.column_labels.hash(hasher);
270 self.cell_mode.hash(hasher);
271 self.cell_width.hash(hasher);
272 self.gap_x.hash(hasher);
273 self.gap_y.hash(hasher);
274 self.legend_gap.hash(hasher);
275 self.legend_spacing.hash(hasher);
276 self.legend_width.hash(hasher);
277 self.show_legend.hash(hasher);
278 self.padding.hash(hasher);
279 self.border.hash(hasher);
280 self.data.len().hash(hasher);
281 for row in &self.data {
282 row.len().hash(hasher);
283 }
284
285 Some(())
286 }
287}