1use crate::theme::Gradient;
7use presentar_core::{
8 Brick, BrickAssertion, BrickBudget, BrickVerification, Canvas, Color, Constraints, Event,
9 LayoutResult, Point, Rect, Size, TextStyle, TypeId, Widget,
10};
11use std::any::Any;
12use std::time::Duration;
13
14#[derive(Debug, Clone)]
16pub struct TreemapNode {
17 pub label: String,
19 pub value: f64,
21 pub color: Option<Color>,
23 pub children: Vec<Self>,
25 pub flash_intensity: f32,
28 pub previous_value: Option<f64>,
30}
31
32impl TreemapNode {
33 #[must_use]
35 pub fn leaf(label: &str, value: f64) -> Self {
36 Self {
37 label: label.to_string(),
38 value,
39 color: None,
40 children: Vec::new(),
41 flash_intensity: 0.0,
42 previous_value: None,
43 }
44 }
45
46 #[must_use]
48 pub fn leaf_colored(label: &str, value: f64, color: Color) -> Self {
49 Self {
50 label: label.to_string(),
51 value,
52 color: Some(color),
53 children: Vec::new(),
54 flash_intensity: 0.0,
55 previous_value: None,
56 }
57 }
58
59 #[must_use]
61 pub fn branch(label: &str, children: Vec<Self>) -> Self {
62 let value = children.iter().map(Self::total_value).sum();
63 Self {
64 label: label.to_string(),
65 value,
66 color: None,
67 children,
68 flash_intensity: 0.0,
69 previous_value: None,
70 }
71 }
72
73 pub fn update_value(&mut self, new_value: f64) -> bool {
77 let threshold = 0.01; let changed = self.previous_value.map_or(true, |prev| {
79 let delta = (new_value - prev).abs();
80 let relative = delta / prev.max(1.0);
81 relative > threshold
82 });
83
84 if changed {
85 self.flash_intensity = 1.0; }
87
88 self.previous_value = Some(self.value);
89 self.value = new_value;
90 changed
91 }
92
93 pub fn decay_flash(&mut self, decay_rate: f32) {
97 self.flash_intensity = (self.flash_intensity - decay_rate).max(0.0);
98 for child in &mut self.children {
99 child.decay_flash(decay_rate);
100 }
101 }
102
103 #[must_use]
105 pub fn is_flashing(&self) -> bool {
106 self.flash_intensity > 0.01
107 }
108
109 #[must_use]
111 pub fn flash_color(&self) -> Color {
112 Color::new(1.0, 1.0, 1.0, self.flash_intensity * 0.5)
113 }
114
115 #[must_use]
117 pub fn total_value(&self) -> f64 {
118 if self.children.is_empty() {
119 self.value
120 } else {
121 self.children.iter().map(Self::total_value).sum()
122 }
123 }
124
125 #[must_use]
127 pub fn is_leaf(&self) -> bool {
128 self.children.is_empty()
129 }
130}
131
132#[derive(Debug, Clone, Copy, Default)]
134pub enum TreemapLayout {
135 #[default]
137 Squarify,
138 SliceAndDice,
140 Binary,
142}
143
144#[derive(Debug, Clone)]
146struct ComputedRect {
147 rect: Rect,
148 node_idx: usize,
149 depth: usize,
150}
151
152#[derive(Debug, Clone)]
154pub struct Treemap {
155 root: Option<TreemapNode>,
156 layout: TreemapLayout,
157 gradient: Gradient,
158 show_labels: bool,
159 max_depth: usize,
160 border_width: f32,
161 bounds: Rect,
162 computed_rects: Vec<ComputedRect>,
164 flat_nodes: Vec<(TreemapNode, usize)>, }
166
167impl Treemap {
168 #[must_use]
170 pub fn new() -> Self {
171 Self {
172 root: None,
173 layout: TreemapLayout::default(),
174 gradient: Gradient::three(
175 Color::new(0.2, 0.4, 0.8, 1.0), Color::new(0.4, 0.8, 0.4, 1.0), Color::new(0.8, 0.4, 0.2, 1.0), ),
179 show_labels: true,
180 max_depth: 3,
181 border_width: 0.0,
182 bounds: Rect::default(),
183 computed_rects: Vec::new(),
184 flat_nodes: Vec::new(),
185 }
186 }
187
188 #[must_use]
190 pub fn with_root(mut self, root: TreemapNode) -> Self {
191 self.root = Some(root);
192 self.invalidate_layout();
193 self
194 }
195
196 #[must_use]
198 pub fn with_layout(mut self, layout: TreemapLayout) -> Self {
199 self.layout = layout;
200 self.invalidate_layout();
201 self
202 }
203
204 #[must_use]
206 pub fn with_gradient(mut self, gradient: Gradient) -> Self {
207 self.gradient = gradient;
208 self
209 }
210
211 #[must_use]
213 pub fn with_labels(mut self, show: bool) -> Self {
214 self.show_labels = show;
215 self
216 }
217
218 #[must_use]
220 pub fn with_max_depth(mut self, depth: usize) -> Self {
221 self.max_depth = depth;
222 self.invalidate_layout();
223 self
224 }
225
226 fn invalidate_layout(&mut self) {
228 self.computed_rects.clear();
229 self.flat_nodes.clear();
230 }
231
232 fn flatten_nodes_static(node: &TreemapNode, depth: usize, out: &mut Vec<(TreemapNode, usize)>) {
234 out.push((node.clone(), depth));
235 if depth < 3 {
237 for child in &node.children {
238 Self::flatten_nodes_static(child, depth + 1, out);
239 }
240 }
241 }
242
243 fn compute_layout(&mut self) {
245 self.computed_rects.clear();
246 self.flat_nodes.clear();
247
248 let Some(root) = self.root.clone() else {
249 return;
250 };
251
252 let mut flat_nodes = Vec::new();
254 Self::flatten_nodes_static(&root, 0, &mut flat_nodes);
255 self.flat_nodes = flat_nodes;
256
257 let bounds = self.bounds;
259 self.squarify_layout(&root, bounds, 0, &mut 0);
260 }
261
262 fn squarify_layout(
264 &mut self,
265 node: &TreemapNode,
266 rect: Rect,
267 depth: usize,
268 node_idx: &mut usize,
269 ) {
270 let current_idx = *node_idx;
271 *node_idx += 1;
272
273 self.computed_rects.push(ComputedRect {
275 rect,
276 node_idx: current_idx,
277 depth,
278 });
279
280 if node.children.is_empty()
281 || depth >= self.max_depth
282 || rect.width < 2.0
283 || rect.height < 2.0
284 {
285 return;
286 }
287
288 let mut children: Vec<_> = node.children.iter().collect();
290 children.sort_by(|a, b| {
291 b.total_value()
292 .partial_cmp(&a.total_value())
293 .unwrap_or(std::cmp::Ordering::Equal)
294 });
295
296 let total_value: f64 = children.iter().map(|c| c.total_value()).sum();
297 if total_value <= 0.0 {
298 return;
299 }
300
301 let mut remaining_rect = Rect::new(
303 rect.x + self.border_width,
304 rect.y + self.border_width,
305 (rect.width - 2.0 * self.border_width).max(0.0),
306 (rect.height - 2.0 * self.border_width).max(0.0),
307 );
308
309 let mut row: Vec<(usize, f64)> = Vec::new();
310 let mut row_sum = 0.0;
311
312 for (i, child) in children.iter().enumerate() {
313 let child_value = child.total_value();
314 if child_value <= 0.0 {
315 continue;
316 }
317
318 row.push((i, child_value));
320 row_sum += child_value;
321
322 let worst_current = self.worst_ratio(&row, row_sum, remaining_rect, total_value);
324
325 if i + 1 < children.len() {
326 let next_value = children[i + 1].total_value();
327 let mut test_row = row.clone();
328 test_row.push((i + 1, next_value));
329 let worst_with_next =
330 self.worst_ratio(&test_row, row_sum + next_value, remaining_rect, total_value);
331
332 if worst_with_next > worst_current {
333 remaining_rect = self.layout_row(
335 &children,
336 &row,
337 row_sum,
338 remaining_rect,
339 total_value,
340 depth,
341 node_idx,
342 );
343 row.clear();
344 row_sum = 0.0;
345 }
346 }
347 }
348
349 if !row.is_empty() {
351 self.layout_row(
352 &children,
353 &row,
354 row_sum,
355 remaining_rect,
356 total_value,
357 depth,
358 node_idx,
359 );
360 }
361 }
362
363 fn worst_ratio(&self, row: &[(usize, f64)], row_sum: f64, rect: Rect, total: f64) -> f64 {
365 if row.is_empty() || row_sum <= 0.0 || total <= 0.0 {
366 return f64::INFINITY;
367 }
368
369 let area = rect.width as f64 * rect.height as f64;
370 let row_area = area * (row_sum / total);
371
372 let is_horizontal = rect.width >= rect.height;
373 let side = if is_horizontal {
374 rect.height
375 } else {
376 rect.width
377 } as f64;
378
379 if side <= 0.0 {
380 return f64::INFINITY;
381 }
382
383 let side_sq = side * side;
384 let row_sum_sq = row_sum * row_sum;
385
386 row.iter()
387 .map(|(_, v)| {
388 let ratio = (row_area * v) / (side_sq * row_sum_sq / row_area);
389 ratio.max(1.0 / ratio)
390 })
391 .fold(0.0f64, f64::max)
392 }
393
394 #[allow(clippy::too_many_arguments)]
396 fn layout_row(
397 &mut self,
398 children: &[&TreemapNode],
399 row: &[(usize, f64)],
400 row_sum: f64,
401 rect: Rect,
402 total: f64,
403 depth: usize,
404 node_idx: &mut usize,
405 ) -> Rect {
406 if row.is_empty() || row_sum <= 0.0 || total <= 0.0 {
407 return rect;
408 }
409
410 let is_horizontal = rect.width >= rect.height;
411 let row_fraction = row_sum / total;
412
413 let (row_rect, remaining) = if is_horizontal {
414 let row_height = rect.height * row_fraction as f32;
415 (
416 Rect::new(rect.x, rect.y, rect.width, row_height),
417 Rect::new(
418 rect.x,
419 rect.y + row_height,
420 rect.width,
421 rect.height - row_height,
422 ),
423 )
424 } else {
425 let row_width = rect.width * row_fraction as f32;
426 (
427 Rect::new(rect.x, rect.y, row_width, rect.height),
428 Rect::new(
429 rect.x + row_width,
430 rect.y,
431 rect.width - row_width,
432 rect.height,
433 ),
434 )
435 };
436
437 let mut offset = 0.0f32;
439 for &(child_idx, child_value) in row {
440 let child_fraction = child_value / row_sum;
441
442 let child_rect = if is_horizontal {
443 let w = row_rect.width * child_fraction as f32;
444 let r = Rect::new(row_rect.x + offset, row_rect.y, w, row_rect.height);
445 offset += w;
446 r
447 } else {
448 let h = row_rect.height * child_fraction as f32;
449 let r = Rect::new(row_rect.x, row_rect.y + offset, row_rect.width, h);
450 offset += h;
451 r
452 };
453
454 self.squarify_layout(children[child_idx], child_rect, depth + 1, node_idx);
455 }
456
457 remaining
458 }
459}
460
461impl Default for Treemap {
462 fn default() -> Self {
463 Self::new()
464 }
465}
466
467impl Widget for Treemap {
468 fn type_id(&self) -> TypeId {
469 TypeId::of::<Self>()
470 }
471
472 fn measure(&self, constraints: Constraints) -> Size {
473 Size::new(
474 constraints.max_width.min(80.0),
475 constraints.max_height.min(40.0),
476 )
477 }
478
479 fn layout(&mut self, bounds: Rect) -> LayoutResult {
480 self.bounds = bounds;
481 self.compute_layout();
482 LayoutResult {
483 size: Size::new(bounds.width, bounds.height),
484 }
485 }
486
487 fn paint(&self, canvas: &mut dyn Canvas) {
488 if self.bounds.width < 4.0 || self.bounds.height < 2.0 {
489 return;
490 }
491
492 #[allow(clippy::redundant_closure_for_method_calls)]
493 let total_value = self.root.as_ref().map_or(1.0, |r| r.total_value());
494
495 let mut sorted_rects: Vec<_> = self.computed_rects.iter().collect();
497 sorted_rects.sort_by(|a, b| b.depth.cmp(&a.depth));
498
499 for computed in sorted_rects {
500 if computed.rect.width < 1.0 || computed.rect.height < 1.0 {
501 continue;
502 }
503
504 let node = if computed.node_idx < self.flat_nodes.len() {
505 &self.flat_nodes[computed.node_idx].0
506 } else {
507 continue;
508 };
509
510 let color = node.color.unwrap_or_else(|| {
512 let t = (node.total_value() / total_value).clamp(0.0, 1.0);
513 self.gradient.sample(t)
514 });
515
516 let fill_char = if computed.depth == 0 { ' ' } else { '░' };
518 let style = TextStyle {
519 color,
520 ..Default::default()
521 };
522
523 for y in 0..(computed.rect.height as usize).max(1) {
524 let row: String = (0..(computed.rect.width as usize).max(1))
525 .map(|_| fill_char)
526 .collect();
527 canvas.draw_text(
528 &row,
529 Point::new(computed.rect.x, computed.rect.y + y as f32),
530 &style,
531 );
532 }
533
534 if self.show_labels && computed.rect.width >= 3.0 && computed.rect.height >= 1.0 {
536 let label = if node.label.len() > computed.rect.width as usize - 1 {
537 format!("{}…", &node.label[..computed.rect.width as usize - 2])
538 } else {
539 node.label.clone()
540 };
541
542 let label_style = TextStyle {
543 color: Color::new(1.0, 1.0, 1.0, 1.0),
544 ..Default::default()
545 };
546
547 canvas.draw_text(
548 &label,
549 Point::new(computed.rect.x + 1.0, computed.rect.y),
550 &label_style,
551 );
552 }
553 }
554 }
555
556 fn event(&mut self, _event: &Event) -> Option<Box<dyn Any + Send>> {
557 None
558 }
559
560 fn children(&self) -> &[Box<dyn Widget>] {
561 &[]
562 }
563
564 fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
565 &mut []
566 }
567}
568
569impl Brick for Treemap {
570 fn brick_name(&self) -> &'static str {
571 "Treemap"
572 }
573
574 fn assertions(&self) -> &[BrickAssertion] {
575 static ASSERTIONS: &[BrickAssertion] = &[BrickAssertion::max_latency_ms(16)];
576 ASSERTIONS
577 }
578
579 fn budget(&self) -> BrickBudget {
580 BrickBudget::uniform(16)
581 }
582
583 fn verify(&self) -> BrickVerification {
584 let mut passed = Vec::new();
585 let mut failed = Vec::new();
586
587 if self.bounds.width >= 4.0 && self.bounds.height >= 2.0 {
588 passed.push(BrickAssertion::max_latency_ms(16));
589 } else {
590 failed.push((
591 BrickAssertion::max_latency_ms(16),
592 "Size too small".to_string(),
593 ));
594 }
595
596 BrickVerification {
597 passed,
598 failed,
599 verification_time: Duration::from_micros(10),
600 }
601 }
602
603 fn to_html(&self) -> String {
604 String::new()
605 }
606
607 fn to_css(&self) -> String {
608 String::new()
609 }
610}
611
612#[cfg(test)]
613mod tests {
614 use super::*;
615 use crate::{CellBuffer, DirectTerminalCanvas};
616
617 #[test]
618 fn test_treemap_creation() {
619 let treemap = Treemap::new();
620 assert!(treemap.root.is_none());
621 }
622
623 #[test]
624 fn test_leaf_node() {
625 let node = TreemapNode::leaf("test", 100.0);
626 assert_eq!(node.label, "test");
627 assert_eq!(node.value, 100.0);
628 assert!(node.is_leaf());
629 }
630
631 #[test]
632 fn test_leaf_node_colored() {
633 let color = Color::new(0.5, 0.6, 0.7, 1.0);
634 let node = TreemapNode::leaf_colored("colored", 50.0, color);
635 assert_eq!(node.label, "colored");
636 assert_eq!(node.value, 50.0);
637 assert!(node.color.is_some());
638 assert!(node.is_leaf());
639 }
640
641 #[test]
642 fn test_branch_node() {
643 let branch = TreemapNode::branch(
644 "parent",
645 vec![
646 TreemapNode::leaf("child1", 50.0),
647 TreemapNode::leaf("child2", 30.0),
648 ],
649 );
650 assert!(!branch.is_leaf());
651 assert_eq!(branch.total_value(), 80.0);
652 }
653
654 #[test]
655 fn test_nested_branch_total_value() {
656 let root = TreemapNode::branch(
657 "root",
658 vec![
659 TreemapNode::branch(
660 "sub1",
661 vec![TreemapNode::leaf("a", 10.0), TreemapNode::leaf("b", 20.0)],
662 ),
663 TreemapNode::leaf("c", 30.0),
664 ],
665 );
666 assert_eq!(root.total_value(), 60.0);
667 }
668
669 #[test]
670 fn test_treemap_with_root() {
671 let root = TreemapNode::branch(
672 "root",
673 vec![TreemapNode::leaf("a", 100.0), TreemapNode::leaf("b", 50.0)],
674 );
675 let treemap = Treemap::new().with_root(root);
676 assert!(treemap.root.is_some());
677 }
678
679 #[test]
680 fn test_treemap_with_layout() {
681 let treemap = Treemap::new().with_layout(TreemapLayout::SliceAndDice);
682 assert!(matches!(treemap.layout, TreemapLayout::SliceAndDice));
683
684 let treemap2 = Treemap::new().with_layout(TreemapLayout::Binary);
685 assert!(matches!(treemap2.layout, TreemapLayout::Binary));
686 }
687
688 #[test]
689 fn test_treemap_with_gradient() {
690 let gradient = Gradient::two(
691 Color::new(1.0, 0.0, 0.0, 1.0),
692 Color::new(0.0, 0.0, 1.0, 1.0),
693 );
694 let treemap = Treemap::new().with_gradient(gradient);
695 let sample = treemap.gradient.sample(0.5);
697 assert!(sample.r > 0.0);
698 }
699
700 #[test]
701 fn test_treemap_with_labels() {
702 let treemap = Treemap::new().with_labels(false);
703 assert!(!treemap.show_labels);
704
705 let treemap2 = Treemap::new().with_labels(true);
706 assert!(treemap2.show_labels);
707 }
708
709 #[test]
710 fn test_treemap_with_max_depth() {
711 let treemap = Treemap::new().with_max_depth(5);
712 assert_eq!(treemap.max_depth, 5);
713 }
714
715 #[test]
716 fn test_treemap_measure() {
717 let treemap = Treemap::new();
718 let constraints = Constraints::new(0.0, 100.0, 0.0, 50.0);
719 let size = treemap.measure(constraints);
720 assert_eq!(size.width, 80.0); assert_eq!(size.height, 40.0); }
723
724 #[test]
725 fn test_treemap_measure_small_constraints() {
726 let treemap = Treemap::new();
727 let constraints = Constraints::new(0.0, 30.0, 0.0, 20.0);
728 let size = treemap.measure(constraints);
729 assert_eq!(size.width, 30.0);
730 assert_eq!(size.height, 20.0);
731 }
732
733 #[test]
734 fn test_treemap_layout_and_paint() {
735 let root = TreemapNode::branch(
736 "root",
737 vec![
738 TreemapNode::leaf_colored("big", 100.0, Color::new(0.8, 0.2, 0.2, 1.0)),
739 TreemapNode::leaf_colored("small", 50.0, Color::new(0.2, 0.8, 0.2, 1.0)),
740 ],
741 );
742 let mut treemap = Treemap::new().with_root(root);
743
744 let mut buffer = CellBuffer::new(40, 20);
745 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
746
747 let result = treemap.layout(Rect::new(0.0, 0.0, 40.0, 20.0));
748 assert_eq!(result.size.width, 40.0);
749 assert_eq!(result.size.height, 20.0);
750
751 treemap.paint(&mut canvas);
752
753 let cells = buffer.cells();
755 let non_empty = cells
756 .iter()
757 .filter(|c| !c.symbol.is_empty() && c.symbol != " ")
758 .count();
759 assert!(non_empty > 0, "Treemap should render some content");
760 }
761
762 #[test]
763 fn test_treemap_paint_too_small() {
764 let root = TreemapNode::leaf("tiny", 10.0);
765 let mut treemap = Treemap::new().with_root(root);
766
767 let mut buffer = CellBuffer::new(2, 1);
768 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
769
770 treemap.layout(Rect::new(0.0, 0.0, 2.0, 1.0));
771 treemap.paint(&mut canvas);
772 }
774
775 #[test]
776 fn test_treemap_paint_no_root() {
777 let mut treemap = Treemap::new();
778
779 let mut buffer = CellBuffer::new(40, 20);
780 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
781
782 treemap.layout(Rect::new(0.0, 0.0, 40.0, 20.0));
783 treemap.paint(&mut canvas);
784 }
786
787 #[test]
788 fn test_treemap_deep_hierarchy() {
789 let root = TreemapNode::branch(
790 "level0",
791 vec![TreemapNode::branch(
792 "level1",
793 vec![TreemapNode::branch(
794 "level2",
795 vec![TreemapNode::branch(
796 "level3",
797 vec![TreemapNode::leaf("deep", 100.0)],
798 )],
799 )],
800 )],
801 );
802 let mut treemap = Treemap::new().with_root(root).with_max_depth(4);
803
804 let mut buffer = CellBuffer::new(60, 30);
805 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
806
807 treemap.layout(Rect::new(0.0, 0.0, 60.0, 30.0));
808 treemap.paint(&mut canvas);
809
810 let cells = buffer.cells();
812 assert!(!cells.is_empty());
813 }
814
815 #[test]
816 fn test_treemap_many_children() {
817 let children: Vec<TreemapNode> = (0..10)
818 .map(|i| TreemapNode::leaf(&format!("node{i}"), (i + 1) as f64 * 10.0))
819 .collect();
820 let root = TreemapNode::branch("root", children);
821
822 let mut treemap = Treemap::new().with_root(root);
823
824 let mut buffer = CellBuffer::new(80, 40);
825 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
826
827 treemap.layout(Rect::new(0.0, 0.0, 80.0, 40.0));
828 treemap.paint(&mut canvas);
829 }
830
831 #[test]
832 fn test_treemap_zero_value_children() {
833 let root = TreemapNode::branch(
834 "root",
835 vec![
836 TreemapNode::leaf("valid", 100.0),
837 TreemapNode::leaf("zero", 0.0),
838 ],
839 );
840 let mut treemap = Treemap::new().with_root(root);
841
842 let mut buffer = CellBuffer::new(40, 20);
843 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
844
845 treemap.layout(Rect::new(0.0, 0.0, 40.0, 20.0));
846 treemap.paint(&mut canvas);
847 }
849
850 #[test]
851 fn test_treemap_long_labels() {
852 let root = TreemapNode::branch(
853 "root",
854 vec![TreemapNode::leaf(
855 "this_is_a_very_long_label_that_should_be_truncated",
856 100.0,
857 )],
858 );
859 let mut treemap = Treemap::new().with_root(root).with_labels(true);
860
861 let mut buffer = CellBuffer::new(20, 10);
862 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
863
864 treemap.layout(Rect::new(0.0, 0.0, 20.0, 10.0));
865 treemap.paint(&mut canvas);
866 }
867
868 #[test]
869 fn test_treemap_labels_disabled() {
870 let root = TreemapNode::leaf("test", 100.0);
871 let mut treemap = Treemap::new().with_root(root).with_labels(false);
872
873 let mut buffer = CellBuffer::new(40, 20);
874 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
875
876 treemap.layout(Rect::new(0.0, 0.0, 40.0, 20.0));
877 treemap.paint(&mut canvas);
878 }
879
880 #[test]
881 fn test_treemap_assertions() {
882 let treemap = Treemap::default();
883 assert!(!treemap.assertions().is_empty());
884 }
885
886 #[test]
887 fn test_treemap_verify_valid() {
888 let mut treemap = Treemap::default();
889 treemap.bounds = Rect::new(0.0, 0.0, 80.0, 40.0);
890 assert!(treemap.verify().is_valid());
891 }
892
893 #[test]
894 fn test_treemap_verify_invalid_small() {
895 let mut treemap = Treemap::default();
896 treemap.bounds = Rect::new(0.0, 0.0, 2.0, 1.0);
897 assert!(!treemap.verify().is_valid());
898 }
899
900 #[test]
901 fn test_treemap_children() {
902 let treemap = Treemap::default();
903 assert!(treemap.children().is_empty());
904 }
905
906 #[test]
907 fn test_treemap_children_mut() {
908 let mut treemap = Treemap::default();
909 assert!(treemap.children_mut().is_empty());
910 }
911
912 #[test]
913 fn test_treemap_brick_name() {
914 let treemap = Treemap::new();
915 assert_eq!(treemap.brick_name(), "Treemap");
916 }
917
918 #[test]
919 fn test_treemap_budget() {
920 let treemap = Treemap::new();
921 let budget = treemap.budget();
922 assert!(budget.layout_ms > 0);
923 assert!(budget.paint_ms > 0);
924 }
925
926 #[test]
927 fn test_treemap_to_html() {
928 let treemap = Treemap::new();
929 assert!(treemap.to_html().is_empty());
930 }
931
932 #[test]
933 fn test_treemap_to_css() {
934 let treemap = Treemap::new();
935 assert!(treemap.to_css().is_empty());
936 }
937
938 #[test]
939 fn test_treemap_type_id() {
940 let treemap = Treemap::new();
941 let type_id = Widget::type_id(&treemap);
942 assert_eq!(type_id, TypeId::of::<Treemap>());
943 }
944
945 #[test]
946 fn test_treemap_event() {
947 let mut treemap = Treemap::new();
948 let event = Event::Resize {
949 width: 80.0,
950 height: 24.0,
951 };
952 assert!(treemap.event(&event).is_none());
953 }
954
955 #[test]
956 fn test_treemap_vertical_layout() {
957 let root = TreemapNode::branch(
959 "root",
960 vec![TreemapNode::leaf("a", 100.0), TreemapNode::leaf("b", 100.0)],
961 );
962 let mut treemap = Treemap::new().with_root(root);
963
964 let mut buffer = CellBuffer::new(10, 40); let mut canvas = DirectTerminalCanvas::new(&mut buffer);
966
967 treemap.layout(Rect::new(0.0, 0.0, 10.0, 40.0));
968 treemap.paint(&mut canvas);
969 }
970
971 #[test]
972 fn test_treemap_layout_default() {
973 let layout = TreemapLayout::default();
974 assert!(matches!(layout, TreemapLayout::Squarify));
975 }
976
977 #[test]
982 fn test_treemap_node_flash_intensity_default() {
983 let node = TreemapNode::leaf("test", 100.0);
984 assert_eq!(node.flash_intensity, 0.0);
985 assert!(node.previous_value.is_none());
986 }
987
988 #[test]
989 fn test_treemap_node_flash_intensity_colored() {
990 let node = TreemapNode::leaf_colored("test", 100.0, Color::new(1.0, 0.0, 0.0, 1.0));
991 assert_eq!(node.flash_intensity, 0.0);
992 assert!(node.previous_value.is_none());
993 }
994
995 #[test]
996 fn test_treemap_node_flash_intensity_branch() {
997 let node = TreemapNode::branch("root", vec![TreemapNode::leaf("child", 50.0)]);
998 assert_eq!(node.flash_intensity, 0.0);
999 assert!(node.previous_value.is_none());
1000 }
1001
1002 #[test]
1003 fn test_treemap_node_update_value_first_call() {
1004 let mut node = TreemapNode::leaf("test", 100.0);
1005 let changed = node.update_value(150.0);
1006 assert!(changed, "First update should always report change");
1007 assert_eq!(node.flash_intensity, 1.0);
1008 assert_eq!(node.value, 150.0);
1009 assert_eq!(node.previous_value, Some(100.0));
1010 }
1011
1012 #[test]
1013 fn test_treemap_node_update_value_significant_change() {
1014 let mut node = TreemapNode::leaf("test", 100.0);
1015 node.previous_value = Some(100.0);
1016 node.flash_intensity = 0.0;
1017
1018 let changed = node.update_value(150.0); assert!(changed, "50% change should trigger flash");
1020 assert_eq!(node.flash_intensity, 1.0);
1021 }
1022
1023 #[test]
1024 fn test_treemap_node_update_value_small_change() {
1025 let mut node = TreemapNode::leaf("test", 100.0);
1026 node.previous_value = Some(100.0);
1027 node.flash_intensity = 0.0;
1028
1029 let changed = node.update_value(100.5); assert!(!changed, "0.5% change should not trigger flash");
1031 assert_eq!(node.flash_intensity, 0.0);
1032 }
1033
1034 #[test]
1035 fn test_treemap_node_decay_flash() {
1036 let mut node = TreemapNode::leaf("test", 100.0);
1037 node.flash_intensity = 1.0;
1038
1039 node.decay_flash(0.1);
1040 assert!((node.flash_intensity - 0.9).abs() < 0.001);
1041
1042 node.decay_flash(0.5);
1043 assert!((node.flash_intensity - 0.4).abs() < 0.001);
1044 }
1045
1046 #[test]
1047 fn test_treemap_node_decay_flash_clamps_zero() {
1048 let mut node = TreemapNode::leaf("test", 100.0);
1049 node.flash_intensity = 0.1;
1050
1051 node.decay_flash(0.5); assert_eq!(node.flash_intensity, 0.0);
1053 }
1054
1055 #[test]
1056 fn test_treemap_node_decay_flash_recursive() {
1057 let mut root = TreemapNode::branch(
1058 "root",
1059 vec![TreemapNode::leaf("a", 50.0), TreemapNode::leaf("b", 50.0)],
1060 );
1061 root.flash_intensity = 1.0;
1062 root.children[0].flash_intensity = 0.8;
1063 root.children[1].flash_intensity = 0.5;
1064
1065 root.decay_flash(0.2);
1066
1067 assert!((root.flash_intensity - 0.8).abs() < 0.001);
1068 assert!((root.children[0].flash_intensity - 0.6).abs() < 0.001);
1069 assert!((root.children[1].flash_intensity - 0.3).abs() < 0.001);
1070 }
1071
1072 #[test]
1073 fn test_treemap_node_is_flashing() {
1074 let mut node = TreemapNode::leaf("test", 100.0);
1075 assert!(!node.is_flashing());
1076
1077 node.flash_intensity = 1.0;
1078 assert!(node.is_flashing());
1079
1080 node.flash_intensity = 0.02;
1081 assert!(node.is_flashing());
1082
1083 node.flash_intensity = 0.005; assert!(!node.is_flashing());
1085 }
1086
1087 #[test]
1088 fn test_treemap_node_flash_color() {
1089 let mut node = TreemapNode::leaf("test", 100.0);
1090 node.flash_intensity = 1.0;
1091
1092 let color = node.flash_color();
1093 assert_eq!(color.r, 1.0);
1094 assert_eq!(color.g, 1.0);
1095 assert_eq!(color.b, 1.0);
1096 assert!((color.a - 0.5).abs() < 0.001); node.flash_intensity = 0.5;
1099 let color2 = node.flash_color();
1100 assert!((color2.a - 0.25).abs() < 0.001); }
1102
1103 #[test]
1104 fn test_treemap_node_flash_color_zero_intensity() {
1105 let node = TreemapNode::leaf("test", 100.0);
1106 let color = node.flash_color();
1107 assert_eq!(color.a, 0.0);
1108 }
1109}