memscope_rs/
export_enhanced.rs

1//! Enhanced export functionality for memory tracking data.
2
3use crate::tracker::MemoryTracker;
4use crate::types::{AllocationInfo, MemoryStats, TrackingResult, TypeMemoryUsage};
5use crate::utils::{format_bytes, get_category_color, simplify_type_name};
6
7/// Calculate real median and P95 percentiles from allocation sizes
8/// Returns (median_size, p95_size)
9fn calculate_allocation_percentiles(allocations: &[AllocationInfo]) -> (usize, usize) {
10    if allocations.is_empty() {
11        return (0, 0);
12    }
13
14    // Collect all allocation sizes
15    let mut sizes: Vec<usize> = allocations.iter().map(|a| a.size).collect();
16    sizes.sort_unstable();
17
18    let len = sizes.len();
19
20    // Calculate median (50th percentile)
21    let median = if len % 2 == 0 {
22        (sizes[len / 2 - 1] + sizes[len / 2]) / 2
23    } else {
24        sizes[len / 2]
25    };
26
27    // Calculate P95 (95th percentile)
28    let p95_index = ((len as f64) * 0.95) as usize;
29    let p95 = if p95_index >= len {
30        sizes[len - 1]
31    } else {
32        sizes[p95_index]
33    };
34
35    (median, p95)
36}
37use std::collections::HashMap;
38use std::fs::File;
39use std::io::Write;
40use std::path::Path;
41use svg::node::element::{Circle, Rectangle, Text as SvgText};
42use svg::Document;
43
44/// Enhanced type information processing with variable names and inner type extraction
45pub fn enhance_type_information(
46    memory_by_type: &[TypeMemoryUsage],
47    allocations: &[AllocationInfo],
48) -> Vec<EnhancedTypeInfo> {
49    let mut enhanced_types = Vec::new();
50    let mut inner_type_stats: std::collections::HashMap<String, (usize, usize, Vec<String>)> =
51        std::collections::HashMap::new();
52
53    for usage in memory_by_type {
54        // Skip unknown types
55        if usage.type_name == "Unknown" {
56            continue;
57        }
58
59        // Use enhanced type analysis for better categorization
60        let (simplified_name, category, subcategory) =
61            analyze_type_with_detailed_subcategory(&usage.type_name);
62
63        // Collect variable names for this type
64        let variable_names: Vec<String> = allocations
65            .iter()
66            .filter_map(|alloc| {
67                if let (Some(var_name), Some(type_name)) = (&alloc.var_name, &alloc.type_name) {
68                    let (alloc_simplified, _, _) =
69                        analyze_type_with_detailed_subcategory(type_name);
70                    if alloc_simplified == simplified_name {
71                        Some(var_name.clone())
72                    } else {
73                        None
74                    }
75                } else {
76                    None
77                }
78            })
79            .take(5) // Limit to 5 variable names
80            .collect();
81
82        // Add the main type with subcategory information
83        enhanced_types.push(EnhancedTypeInfo {
84            simplified_name: simplified_name.clone(),
85            category: category.clone(),
86            subcategory: subcategory.clone(),
87            total_size: usage.total_size,
88            allocation_count: usage.allocation_count,
89            variable_names,
90        });
91
92        // Extract and accumulate inner types (e.g., i32 from Vec<i32>, u8 from Vec<u8>)
93        extract_and_accumulate_inner_types_enhanced(
94            &usage.type_name,
95            usage.total_size,
96            usage.allocation_count,
97            &mut inner_type_stats,
98        );
99    }
100
101    // Add accumulated inner types as separate entries
102    for (inner_type, (total_size, allocation_count, var_names)) in inner_type_stats {
103        let (simplified_name, category, subcategory) =
104            analyze_type_with_detailed_subcategory(&inner_type);
105        // tracing::info!("Adding inner type: '{}' -> '{}' ({}), size: {}, category: {}, subcategory: {}",
106        //               inner_type, simplified_name, allocation_count, total_size, category, subcategory);
107        enhanced_types.push(EnhancedTypeInfo {
108            simplified_name,
109            category,
110            subcategory,
111            total_size,
112            allocation_count,
113            variable_names: var_names.into_iter().take(5).collect(),
114        });
115    }
116
117    // Debug: Print final enhanced types
118    // tracing::info!("Final enhanced types count: {}", enhanced_types.len());
119    // for (i, t) in enhanced_types.iter().enumerate() {
120    //     tracing::info!("Type {}: {} ({} -> {}) - {} bytes", i, t.simplified_name, t.category, t.subcategory, t.total_size);
121    // }
122
123    enhanced_types
124}
125
126/// Enhanced type analysis with detailed subcategory detection
127fn analyze_type_with_detailed_subcategory(type_name: &str) -> (String, String, String) {
128    let clean_type = type_name.trim();
129
130    // Handle empty or explicitly unknown types first
131    if clean_type.is_empty() || clean_type == "Unknown" {
132        return (
133            "Unknown Type".to_string(),
134            "Unknown".to_string(),
135            "Other".to_string(),
136        );
137    }
138
139    // Collections analysis with precise subcategorization
140    if clean_type.contains("Vec<") || clean_type.contains("vec::Vec") {
141        let inner = extract_generic_inner_type(clean_type, "Vec");
142        return (
143            format!("Vec<{}>", inner),
144            "Collections".to_string(),
145            "Vec<T>".to_string(),
146        );
147    }
148
149    if clean_type.contains("HashMap") || clean_type.contains("hash_map") {
150        return (
151            "HashMap<K,V>".to_string(),
152            "Collections".to_string(),
153            "HashMap<K,V>".to_string(),
154        );
155    }
156
157    if clean_type.contains("HashSet") || clean_type.contains("hash_set") {
158        return (
159            "HashSet<T>".to_string(),
160            "Collections".to_string(),
161            "HashSet<T>".to_string(),
162        );
163    }
164
165    if clean_type.contains("BTreeMap") || clean_type.contains("btree_map") {
166        return (
167            "BTreeMap<K,V>".to_string(),
168            "Collections".to_string(),
169            "BTreeMap<K,V>".to_string(),
170        );
171    }
172
173    if clean_type.contains("BTreeSet") || clean_type.contains("btree_set") {
174        return (
175            "BTreeSet<T>".to_string(),
176            "Collections".to_string(),
177            "BTreeSet<T>".to_string(),
178        );
179    }
180
181    if clean_type.contains("VecDeque") || clean_type.contains("vec_deque") {
182        return (
183            "VecDeque<T>".to_string(),
184            "Collections".to_string(),
185            "VecDeque<T>".to_string(),
186        );
187    }
188
189    if clean_type.contains("LinkedList") {
190        return (
191            "LinkedList<T>".to_string(),
192            "Collections".to_string(),
193            "LinkedList<T>".to_string(),
194        );
195    }
196
197    // Basic Types analysis with precise subcategorization
198    if clean_type.contains("String") || clean_type.contains("string::String") {
199        return (
200            "String".to_string(),
201            "Basic Types".to_string(),
202            "Strings".to_string(),
203        );
204    }
205
206    if clean_type.contains("&str") || clean_type == "str" {
207        return (
208            "&str".to_string(),
209            "Basic Types".to_string(),
210            "Strings".to_string(),
211        );
212    }
213
214    // Integer types - exact matching
215    if clean_type == "i32" || clean_type.ends_with("::i32") {
216        return (
217            "i32".to_string(),
218            "Basic Types".to_string(),
219            "Integers".to_string(),
220        );
221    }
222    if clean_type == "i64" || clean_type.ends_with("::i64") {
223        return (
224            "i64".to_string(),
225            "Basic Types".to_string(),
226            "Integers".to_string(),
227        );
228    }
229    if clean_type == "u32" || clean_type.ends_with("::u32") {
230        return (
231            "u32".to_string(),
232            "Basic Types".to_string(),
233            "Integers".to_string(),
234        );
235    }
236    if clean_type == "u64" || clean_type.ends_with("::u64") {
237        return (
238            "u64".to_string(),
239            "Basic Types".to_string(),
240            "Integers".to_string(),
241        );
242    }
243    if clean_type == "usize" || clean_type.ends_with("::usize") {
244        return (
245            "usize".to_string(),
246            "Basic Types".to_string(),
247            "Integers".to_string(),
248        );
249    }
250    if clean_type == "isize" || clean_type.ends_with("::isize") {
251        return (
252            "isize".to_string(),
253            "Basic Types".to_string(),
254            "Integers".to_string(),
255        );
256    }
257    if clean_type == "i8" || clean_type.ends_with("::i8") {
258        return (
259            "i8".to_string(),
260            "Basic Types".to_string(),
261            "Integers".to_string(),
262        );
263    }
264    if clean_type == "u8" || clean_type.ends_with("::u8") {
265        return (
266            "u8".to_string(),
267            "Basic Types".to_string(),
268            "Integers".to_string(),
269        );
270    }
271    if clean_type == "i16" || clean_type.ends_with("::i16") {
272        return (
273            "i16".to_string(),
274            "Basic Types".to_string(),
275            "Integers".to_string(),
276        );
277    }
278    if clean_type == "u16" || clean_type.ends_with("::u16") {
279        return (
280            "u16".to_string(),
281            "Basic Types".to_string(),
282            "Integers".to_string(),
283        );
284    }
285
286    // Float types
287    if clean_type == "f32" || clean_type.ends_with("::f32") {
288        return (
289            "f32".to_string(),
290            "Basic Types".to_string(),
291            "Floats".to_string(),
292        );
293    }
294    if clean_type == "f64" || clean_type.ends_with("::f64") {
295        return (
296            "f64".to_string(),
297            "Basic Types".to_string(),
298            "Floats".to_string(),
299        );
300    }
301
302    // Other basic types
303    if clean_type == "bool" || clean_type.ends_with("::bool") {
304        return (
305            "bool".to_string(),
306            "Basic Types".to_string(),
307            "Booleans".to_string(),
308        );
309    }
310    if clean_type == "char" || clean_type.ends_with("::char") {
311        return (
312            "char".to_string(),
313            "Basic Types".to_string(),
314            "Characters".to_string(),
315        );
316    }
317
318    // Smart Pointers
319    if clean_type.contains("Box<") {
320        let inner = extract_generic_inner_type(clean_type, "Box");
321        return (
322            format!("Box<{}>", inner),
323            "Smart Pointers".to_string(),
324            "Box<T>".to_string(),
325        );
326    }
327
328    if clean_type.contains("Rc<") {
329        let inner = extract_generic_inner_type(clean_type, "Rc");
330        return (
331            format!("Rc<{}>", inner),
332            "Smart Pointers".to_string(),
333            "Rc<T>".to_string(),
334        );
335    }
336
337    if clean_type.contains("Arc<") {
338        let inner = extract_generic_inner_type(clean_type, "Arc");
339        return (
340            format!("Arc<{}>", inner),
341            "Smart Pointers".to_string(),
342            "Arc<T>".to_string(),
343        );
344    }
345
346    // Fall back to original logic for other types
347    let (simplified_name, category) = simplify_type_name(clean_type);
348    (simplified_name, category.clone(), "Other".to_string())
349}
350
351/// Extract generic inner type for display
352fn extract_generic_inner_type(type_name: &str, container: &str) -> String {
353    if let Some(start) = type_name.find(&format!("{}<", container)) {
354        let start = start + container.len() + 1;
355        if let Some(end) = type_name[start..].rfind('>') {
356            let inner = &type_name[start..start + end];
357            return inner.split("::").last().unwrap_or(inner).to_string();
358        }
359    }
360    "?".to_string()
361}
362
363/// Extract inner types from complex types and accumulate their statistics (enhanced version)
364fn extract_and_accumulate_inner_types_enhanced(
365    type_name: &str,
366    size: usize,
367    count: usize,
368    stats: &mut std::collections::HashMap<String, (usize, usize, Vec<String>)>,
369) {
370    // Extract inner types from generic containers
371    let inner_types = extract_inner_primitive_types_enhanced(type_name);
372
373    // Debug: Print what we found
374    // if !inner_types.is_empty() {
375    // tracing::info!("Found inner types in '{}': {:?}", type_name, inner_types);
376    // }
377
378    for inner_type in inner_types {
379        let entry = stats
380            .entry(inner_type.clone())
381            .or_insert((0, 0, Vec::new()));
382        entry.0 += size / 4; // Rough estimation of inner type contribution
383        entry.1 += count;
384        entry.2.push(format!("from {}", type_name));
385        // tracing::info!("Accumulated {} bytes for inner type '{}' from '{}'", size / 4, inner_type, type_name);
386    }
387}
388
389/// Extract primitive types from complex type signatures (enhanced version)
390fn extract_inner_primitive_types_enhanced(type_name: &str) -> Vec<String> {
391    let mut inner_types = Vec::new();
392
393    // Common primitive type patterns
394    let primitives = [
395        "i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32", "u64", "u128", "usize",
396        "f32", "f64", "bool", "char",
397    ];
398
399    for primitive in &primitives {
400        if type_name.contains(primitive) {
401            // Make sure it's actually the primitive type, not part of another word
402            if type_name.contains(&format!("{}>", primitive))
403                || type_name.contains(&format!("{},", primitive))
404                || type_name.contains(&format!(" {}", primitive))
405                || type_name.ends_with(primitive)
406            {
407                inner_types.push(primitive.to_string());
408            }
409        }
410    }
411
412    inner_types
413}
414
415/// Categorize allocations for better visualization
416pub fn categorize_allocations(allocations: &[AllocationInfo]) -> Vec<AllocationCategory> {
417    let mut categories: HashMap<String, AllocationCategory> = HashMap::new();
418
419    for allocation in allocations {
420        // Skip allocations without variable names or with unknown types
421        if allocation.var_name.is_none()
422            || allocation.type_name.as_ref().is_none_or(|t| t == "Unknown")
423        {
424            continue;
425        }
426
427        let type_name = allocation.type_name.as_ref().unwrap();
428        let (_, category_name) = simplify_type_name(type_name);
429
430        let category =
431            categories
432                .entry(category_name.clone())
433                .or_insert_with(|| AllocationCategory {
434                    name: category_name.clone(),
435                    allocations: Vec::new(),
436                    total_size: 0,
437                    color: get_category_color(&category_name),
438                });
439
440        category.allocations.push(allocation.clone());
441        category.total_size += allocation.size;
442    }
443
444    let mut result: Vec<_> = categories.into_values().collect();
445    result.sort_by(|a, b| b.total_size.cmp(&a.total_size));
446    result
447}
448
449/// Categorize enhanced type information for consistent visualization
450/// This ensures "Tracked Variables by Category" uses the same data as "Memory Usage by Type"
451pub fn categorize_enhanced_allocations(
452    enhanced_types: &[EnhancedTypeInfo],
453) -> Vec<AllocationCategory> {
454    let mut categories: HashMap<String, AllocationCategory> = HashMap::new();
455
456    for enhanced_type in enhanced_types {
457        // Skip unknown types
458        if enhanced_type.simplified_name == "Unknown" {
459            continue;
460        }
461
462        let category_name = &enhanced_type.category;
463
464        let category =
465            categories
466                .entry(category_name.clone())
467                .or_insert_with(|| AllocationCategory {
468                    name: category_name.clone(),
469                    allocations: Vec::new(),
470                    total_size: 0,
471                    color: get_category_color(category_name),
472                });
473
474        // Create synthetic allocation info for display
475        let mut synthetic_allocation = AllocationInfo::new(0, enhanced_type.total_size);
476        synthetic_allocation.var_name = Some(enhanced_type.variable_names.join(", "));
477        synthetic_allocation.type_name = Some(enhanced_type.simplified_name.clone());
478
479        category.allocations.push(synthetic_allocation);
480        category.total_size += enhanced_type.total_size;
481    }
482
483    let mut result: Vec<_> = categories.into_values().collect();
484    result.sort_by(|a, b| b.total_size.cmp(&a.total_size));
485    result
486}
487
488/// Enhanced type information with variable names and categorization
489#[derive(Debug, Clone)]
490pub struct EnhancedTypeInfo {
491    /// Simplified type name for display
492    pub simplified_name: String,
493    /// Category this type belongs to
494    pub category: String,
495    /// Subcategory for fine-grained classification
496    pub subcategory: String,
497    /// Total memory size used by this type
498    pub total_size: usize,
499    /// Number of allocations of this type
500    pub allocation_count: usize,
501    /// Variable names associated with this type
502    pub variable_names: Vec<String>,
503}
504
505/// Allocation category for grouping related allocations
506#[derive(Debug, Clone)]
507pub struct AllocationCategory {
508    /// Category name
509    pub name: String,
510    /// Allocations in this category
511    pub allocations: Vec<AllocationInfo>,
512    /// Total size of all allocations in this category
513    pub total_size: usize,
514    /// Color used for visualization
515    pub color: String,
516}
517
518/// Enhanced SVG export with comprehensive visualization
519pub fn export_enhanced_svg<P: AsRef<Path>>(tracker: &MemoryTracker, path: P) -> TrackingResult<()> {
520    let path = path.as_ref();
521
522    // Create parent directories if needed
523    if let Some(parent) = path.parent() {
524        if !parent.exists() {
525            std::fs::create_dir_all(parent)?;
526        }
527    }
528
529    let active_allocations = tracker.get_active_allocations()?;
530    let memory_by_type = tracker.get_memory_by_type()?;
531    let stats = tracker.get_stats()?;
532
533    // Filter out unknown types and enhance type information
534    let enhanced_memory_by_type = enhance_type_information(&memory_by_type, &active_allocations);
535    let _categorized_allocations = categorize_allocations(&active_allocations);
536
537    // Create COMPACT SVG document - REDUCED HEIGHT for space efficiency
538    let mut document = Document::new()
539        .set("viewBox", (0, 0, 1800, 400))
540        .set("width", 1800)
541        .set("height", 400)
542        .set(
543            "style",
544            "background: linear-gradient(135deg, #2C3E50 0%, #34495E 100%); font-family: 'Segoe UI', Arial, sans-serif;",
545        );
546
547    // COMPACT LAYOUT - Only essential components for 400px height
548
549    // Title (y: 0-50)
550    let title = SvgText::new("Top 3 Memory Analysis - Compact View")
551        .set("x", 700)
552        .set("y", 30)
553        .set("text-anchor", "middle")
554        .set("font-size", 24)
555        .set("font-weight", "bold")
556        .set("fill", "#FFFFFF");
557    document = document.add(title);
558
559    // Only show TOP 3 type chart (y: 60-350)
560    if !enhanced_memory_by_type.is_empty() {
561        document = add_compact_type_chart(document, &enhanced_memory_by_type)?;
562    }
563
564    // Compact summary (y: 350-400)
565    document = add_compact_summary(document, &stats, &active_allocations)?;
566
567    // Write SVG to file
568    let mut file = File::create(path)?;
569    write!(file, "{document}")?;
570
571    Ok(())
572}
573
574/// Add compact type chart - TOP 3 ONLY with progress bars
575fn add_compact_type_chart(
576    mut document: Document,
577    types: &[EnhancedTypeInfo],
578) -> TrackingResult<Document> {
579    let chart_x = 100;
580    let chart_y = 60;
581    let chart_width = 1200;
582
583    if types.is_empty() {
584        let no_data = SvgText::new("No tracked variables found")
585            .set("x", chart_x + chart_width / 2)
586            .set("y", 200)
587            .set("text-anchor", "middle")
588            .set("font-size", 16)
589            .set("fill", "#E74C3C");
590        document = document.add(no_data);
591        return Ok(document);
592    }
593
594    let max_size = types.iter().map(|t| t.total_size).max().unwrap_or(1);
595
596    // Show TOP 3 ONLY
597    for (i, type_info) in types.iter().take(3).enumerate() {
598        let y = chart_y + (i as i32) * 80;
599
600        // Progress bar background
601        let bg_bar = Rectangle::new()
602            .set("x", chart_x)
603            .set("y", y)
604            .set("width", 600)
605            .set("height", 30)
606            .set("fill", "#34495E")
607            .set("stroke", "#ECF0F1")
608            .set("stroke-width", 1)
609            .set("rx", 6);
610        document = document.add(bg_bar);
611
612        // Progress bar fill
613        let bar_width = ((type_info.total_size as f64 / max_size as f64) * 600.0) as i32;
614        let color = get_category_color(&type_info.category);
615        let progress_bar = Rectangle::new()
616            .set("x", chart_x)
617            .set("y", y)
618            .set("width", bar_width)
619            .set("height", 30)
620            .set("fill", color)
621            .set("rx", 6);
622        document = document.add(progress_bar);
623
624        // Type and size info
625        let content_text = format!(
626            "{} ({} vars) | Total: {}",
627            type_info.simplified_name,
628            type_info.allocation_count,
629            format_bytes(type_info.total_size)
630        );
631
632        let content_label = SvgText::new(content_text)
633            .set("x", chart_x + 10)
634            .set("y", y + 20)
635            .set("font-size", 12)
636            .set("font-weight", "600")
637            .set("fill", "#FFFFFF")
638            .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.8)");
639        document = document.add(content_label);
640
641        // Progress percentage
642        let percentage = (type_info.total_size as f64 / max_size as f64 * 100.0) as i32;
643        let percent_label = SvgText::new(format!("{percentage}%"))
644            .set("x", chart_x + 720)
645            .set("y", y + 20)
646            .set("font-size", 14)
647            .set("font-weight", "bold")
648            .set("fill", "#ECF0F1");
649        document = document.add(percent_label);
650
651        // Variable names below
652        let var_names_text = if type_info.variable_names.is_empty() {
653            "no tracked vars".to_string()
654        } else {
655            format!("Variables: {}", type_info.variable_names.join(", "))
656        };
657
658        let vars_label = SvgText::new(var_names_text)
659            .set("x", chart_x + 10)
660            .set("y", y + 45)
661            .set("font-size", 9)
662            .set("fill", "#94A3B8")
663            .set("font-style", "italic");
664        document = document.add(vars_label);
665    }
666
667    Ok(document)
668}
669
670/// Add compact summary
671fn add_compact_summary(
672    mut document: Document,
673    stats: &MemoryStats,
674    allocations: &[AllocationInfo],
675) -> TrackingResult<Document> {
676    let tracked_vars = allocations.iter().filter(|a| a.var_name.is_some()).count();
677
678    let summary_text = format!(
679        "Showing TOP 3 memory-consuming types | Total tracked: {} variables | Active memory: {}",
680        tracked_vars,
681        format_bytes(stats.active_memory)
682    );
683
684    let summary = SvgText::new(summary_text)
685        .set("x", 700)
686        .set("y", 370)
687        .set("text-anchor", "middle")
688        .set("font-size", 14)
689        .set("font-weight", "bold")
690        .set("fill", "#ECF0F1");
691    document = document.add(summary);
692
693    Ok(document)
694}
695
696/// Add enhanced header with 8 core metrics - Modern Dashboard Style
697pub fn add_enhanced_header(
698    mut document: Document,
699    stats: &MemoryStats,
700    allocations: &[AllocationInfo],
701) -> TrackingResult<Document> {
702    // Main title with modern styling
703    let title = SvgText::new("Rust Memory Usage Analysis")
704        .set("x", 900)
705        .set("y", 40)
706        .set("text-anchor", "middle")
707        .set("font-size", 28)
708        .set("font-weight", "300")
709        .set("fill", "#2c3e50")
710        .set("style", "letter-spacing: 1px;");
711
712    document = document.add(title);
713
714    // Calculate 8 core metrics with percentage values for progress rings
715    let active_memory = stats.active_memory;
716    let peak_memory = stats.peak_memory;
717    let active_allocations = stats.active_allocations;
718
719    let memory_reclamation_rate = if stats.total_allocated > 0 {
720        (stats.total_deallocated as f64 / stats.total_allocated as f64) * 100.0
721    } else {
722        0.0
723    };
724
725    let allocator_efficiency = if stats.peak_memory > 0 {
726        (stats.active_memory as f64 / stats.peak_memory as f64) * 100.0
727    } else {
728        0.0
729    };
730
731    let (median_alloc_size, p95_alloc_size) = calculate_allocation_percentiles(allocations);
732
733    let memory_fragmentation = if stats.peak_memory > 0 {
734        ((stats.peak_memory - stats.active_memory) as f64 / stats.peak_memory as f64) * 100.0
735    } else {
736        0.0
737    };
738
739    // Define metrics with their display values and progress percentages
740    let metrics = [
741        (
742            "Active Memory",
743            format_bytes(active_memory),
744            (active_memory as f64 / peak_memory.max(1) as f64 * 100.0).min(100.0),
745            "#3498db",
746        ),
747        ("Peak Memory", format_bytes(peak_memory), 100.0, "#e74c3c"),
748        (
749            "Active Allocs",
750            format!("{}", active_allocations),
751            (active_allocations as f64 / 1000.0 * 100.0).min(100.0),
752            "#2ecc71",
753        ),
754        (
755            "Reclamation",
756            format!("{:.1}%", memory_reclamation_rate),
757            memory_reclamation_rate,
758            "#f39c12",
759        ),
760        (
761            "Efficiency",
762            format!("{:.1}%", allocator_efficiency),
763            allocator_efficiency,
764            "#9b59b6",
765        ),
766        (
767            "Median Size",
768            format_bytes(median_alloc_size),
769            (median_alloc_size as f64 / 1024.0 * 100.0).min(100.0),
770            "#1abc9c",
771        ),
772        (
773            "P95 Size",
774            format_bytes(p95_alloc_size),
775            (p95_alloc_size as f64 / 4096.0 * 100.0).min(100.0),
776            "#e67e22",
777        ),
778        (
779            "Fragmentation",
780            format!("{:.1}%", memory_fragmentation),
781            memory_fragmentation,
782            "#95a5a6",
783        ),
784    ];
785
786    // Single row layout parameters
787    let card_width = 200;
788    let card_height = 120;
789    let start_x = 50;
790    let start_y = 130;
791    let spacing_x = 220;
792
793    // Add single section header for all metrics
794    let header = SvgText::new("KEY PERFORMANCE METRICS")
795        .set("x", 900)
796        .set("y", start_y - 20)
797        .set("text-anchor", "middle")
798        .set("font-size", 11)
799        .set("font-weight", "600")
800        .set("fill", "#7f8c8d")
801        .set("style", "letter-spacing: 2px;");
802    document = document.add(header);
803
804    // Render all 8 metrics in a single row
805    for (i, (title, value, percentage, color)) in metrics.iter().enumerate() {
806        let x = start_x + i * spacing_x;
807        let y = start_y;
808
809        // Modern card background with gradient and shadow
810        let card_bg = Rectangle::new()
811            .set("x", x)
812            .set("y", y)
813            .set("width", card_width)
814            .set("height", card_height)
815            .set("fill", "#ffffff")
816            .set("stroke", "none")
817            .set("rx", 12)
818            .set("style", "filter: drop-shadow(0 4px 12px rgba(0,0,0,0.15));");
819
820        // Use simple solid color instead of gradient to avoid SVG compatibility issues
821        if i == 0 {
822            // Skip gradient definition for now - use solid colors
823        }
824
825        document = document.add(card_bg);
826
827        // Progress ring background
828        let ring_center_x = x + 40;
829        let ring_center_y = y + 60;
830        let ring_radius = 25;
831
832        let ring_bg = Circle::new()
833            .set("cx", ring_center_x)
834            .set("cy", ring_center_y)
835            .set("r", ring_radius)
836            .set("fill", "none")
837            .set("stroke", "#ecf0f1")
838            .set("stroke-width", 6);
839        document = document.add(ring_bg);
840
841        // Progress ring foreground
842        let circumference = 2.0 * std::f64::consts::PI * ring_radius as f64;
843        let progress_offset = circumference * (1.0 - percentage / 100.0);
844
845        let progress_ring = Circle::new()
846            .set("cx", ring_center_x)
847            .set("cy", ring_center_y)
848            .set("r", ring_radius)
849            .set("fill", "none")
850            .set("stroke", *color)
851            .set("stroke-width", 6)
852            .set("stroke-linecap", "round")
853            .set(
854                "stroke-dasharray",
855                format!("{} {}", circumference, circumference),
856            )
857            .set("stroke-dashoffset", progress_offset)
858            .set(
859                "transform",
860                format!("rotate(-90 {} {})", ring_center_x, ring_center_y),
861            )
862            .set("style", "transition: stroke-dashoffset 0.5s ease;");
863        document = document.add(progress_ring);
864
865        // Percentage text in center of ring
866        let percent_text = SvgText::new(format!("{:.0}%", percentage))
867            .set("x", ring_center_x)
868            .set("y", ring_center_y + 4)
869            .set("text-anchor", "middle")
870            .set("font-size", 12)
871            .set("font-weight", "bold")
872            .set("fill", *color);
873        document = document.add(percent_text);
874
875        // Metric title
876        let title_text = SvgText::new(*title)
877            .set("x", x + 90)
878            .set("y", y + 35)
879            .set("font-size", 12)
880            .set("font-weight", "600")
881            .set("fill", "#2c3e50");
882        document = document.add(title_text);
883
884        // Metric value with larger, prominent display
885        let value_text = SvgText::new(value)
886            .set("x", x + 90)
887            .set("y", y + 55)
888            .set("font-size", 16)
889            .set("font-weight", "bold")
890            .set("fill", "#2c3e50");
891        document = document.add(value_text);
892
893        // Status indicator based on percentage
894        let status_color = if *percentage >= 80.0 {
895            "#e74c3c" // Red for high values
896        } else if *percentage >= 50.0 {
897            "#f39c12" // Orange for medium values
898        } else {
899            "#27ae60" // Green for low values
900        };
901
902        let status_dot = Circle::new()
903            .set("cx", x + 90)
904            .set("cy", y + 75)
905            .set("r", 4)
906            .set("fill", status_color);
907        document = document.add(status_dot);
908
909        // Status text
910        let status_text = if *percentage >= 80.0 {
911            "HIGH"
912        } else if *percentage >= 50.0 {
913            "MEDIUM"
914        } else {
915            "OPTIMAL"
916        };
917
918        let status_label = SvgText::new(status_text)
919            .set("x", x + 105)
920            .set("y", y + 79)
921            .set("font-size", 9)
922            .set("font-weight", "600")
923            .set("fill", status_color);
924        document = document.add(status_label);
925    }
926
927    Ok(document)
928}
929
930/// Add enhanced treemap chart with real data
931pub fn add_enhanced_type_chart(
932    mut document: Document,
933    types: &[EnhancedTypeInfo],
934) -> TrackingResult<Document> {
935    // Optimized position to avoid overlap with other modules
936    let chart_x = 50;
937    let chart_y = 720; // Moved up slightly for better spacing
938    let chart_width = 850;
939    let chart_height = 300; // Reduced height to prevent overlap
940
941    // Chart title
942    let title = SvgText::new("Memory Usage by Type - Treemap Visualization")
943        .set("x", chart_x + chart_width / 2)
944        .set("y", chart_y - 10)
945        .set("text-anchor", "middle")
946        .set("font-size", 16)
947        .set("font-weight", "bold")
948        .set("fill", "#2c3e50");
949
950    document = document.add(title);
951
952    // Add treemap styles
953    let styles = svg::node::element::Style::new(
954        r#"
955        .integrated-treemap-rect { 
956            transition: all 0.3s ease; 
957            cursor: pointer; 
958            stroke: #ffffff; 
959            stroke-width: 2; 
960        }
961        .integrated-treemap-rect:hover { 
962            stroke: #2c3e50; 
963            stroke-width: 3; 
964            filter: brightness(1.1); 
965        }
966        .integrated-treemap-label { 
967            fill: #ffffff; 
968            font-weight: 700; 
969            text-anchor: middle; 
970            dominant-baseline: middle; 
971            pointer-events: none;
972            text-shadow: 1px 1px 2px rgba(0,0,0,0.8);
973        }
974        .integrated-treemap-percentage { 
975            fill: #f8f9fa; 
976            font-weight: 600;
977            text-anchor: middle; 
978            dominant-baseline: middle; 
979            pointer-events: none;
980            text-shadow: 1px 1px 2px rgba(0,0,0,0.6);
981        }
982    "#,
983    );
984    document = document.add(styles);
985
986    // Create integrated treemap layout
987    let treemap_area = IntegratedTreemapArea {
988        x: chart_x as f64,
989        y: chart_y as f64,
990        width: chart_width as f64,
991        height: chart_height as f64,
992    };
993
994    // Build and render real data treemap
995    document = render_real_data_treemap(document, treemap_area, types)?;
996
997    // Note: Legend is now integrated within the treemap rendering, no separate legend needed
998
999    Ok(document)
1000}
1001
1002/// Integrated treemap area structure
1003#[derive(Debug, Clone)]
1004struct IntegratedTreemapArea {
1005    x: f64,
1006    y: f64,
1007    width: f64,
1008    height: f64,
1009}
1010
1011/// Render integrated treemap with real data - placeholder function
1012
1013/// Render treemap with real memory data matching task.md layout
1014fn render_real_data_treemap(
1015    mut document: Document,
1016    area: IntegratedTreemapArea,
1017    types: &[EnhancedTypeInfo],
1018) -> TrackingResult<Document> {
1019    if types.is_empty() {
1020        let no_data_rect = Rectangle::new()
1021            .set("x", area.x)
1022            .set("y", area.y)
1023            .set("width", area.width)
1024            .set("height", area.height)
1025            .set("fill", "#f8f9fa")
1026            .set("stroke", "#dee2e6")
1027            .set("stroke-width", 2)
1028            .set("rx", 10);
1029        document = document.add(no_data_rect);
1030
1031        let no_data_text = SvgText::new("No Memory Type Data Available")
1032            .set("x", area.x + area.width / 2.0)
1033            .set("y", area.y + area.height / 2.0)
1034            .set("text-anchor", "middle")
1035            .set("font-size", 16)
1036            .set("font-weight", "bold")
1037            .set("fill", "#6c757d");
1038        document = document.add(no_data_text);
1039
1040        return Ok(document);
1041    }
1042
1043    // Calculate total memory for percentage calculations
1044    let total_memory: usize = types.iter().map(|t| t.total_size).sum();
1045    if total_memory == 0 {
1046        return Ok(document);
1047    }
1048
1049    // Group types by category to match task.md structure
1050    let mut collections_types = Vec::new();
1051    let mut basic_types = Vec::new();
1052    let mut smart_pointers_types = Vec::new();
1053    let mut other_types = Vec::new();
1054
1055    for type_info in types {
1056        match type_info.category.as_str() {
1057            "Collections" => collections_types.push(type_info),
1058            "Basic Types" => basic_types.push(type_info),
1059            "Strings" => basic_types.push(type_info), // Legacy support - redirect to Basic Types
1060            "Smart Pointers" => smart_pointers_types.push(type_info),
1061            _ => other_types.push(type_info),
1062        }
1063    }
1064
1065    // Calculate category totals
1066    let _collections_total: usize = collections_types.iter().map(|t| t.total_size).sum();
1067    let _basic_types_total: usize = basic_types.iter().map(|t| t.total_size).sum();
1068    let _smart_pointers_total: usize = smart_pointers_types.iter().map(|t| t.total_size).sum();
1069    let _other_total: usize = other_types.iter().map(|t| t.total_size).sum();
1070
1071    // Analyze data distribution for intelligent layout decision
1072    let layout_strategy = analyze_data_distribution(
1073        &collections_types,
1074        &basic_types,
1075        &smart_pointers_types,
1076        &other_types,
1077        total_memory,
1078    );
1079
1080    // Build treemap structure based on intelligent analysis
1081    let treemap_data = build_adaptive_treemap_data(
1082        collections_types,
1083        basic_types,
1084        smart_pointers_types,
1085        other_types,
1086        total_memory,
1087        &layout_strategy,
1088    );
1089
1090    // Render treemap using adaptive algorithm
1091    document = render_adaptive_treemap(document, area, &treemap_data, &layout_strategy)?;
1092
1093    Ok(document)
1094}
1095
1096/// Treemap data structure for hierarchical visualization
1097#[derive(Debug, Clone)]
1098struct TreemapNode {
1099    name: String,
1100    size: usize,
1101    percentage: f64,
1102    color: String,
1103    children: Vec<TreemapNode>,
1104}
1105
1106/// Layout strategy for treemap rendering
1107#[derive(Debug, Clone)]
1108enum TreemapLayoutStrategy {
1109    /// Full treemap with all categories and subcategories
1110    FullLayout,
1111    /// Simplified layout focusing on dominant category
1112    DominantCategoryLayout { dominant_category: String },
1113    /// Minimal layout for simple programs
1114    MinimalLayout,
1115    /// Collections-only layout
1116    CollectionsOnlyLayout,
1117    /// Basic types only layout
1118    BasicTypesOnlyLayout,
1119}
1120
1121/// Analyze data distribution to determine optimal layout strategy
1122fn analyze_data_distribution(
1123    collections_types: &[&EnhancedTypeInfo],
1124    basic_types: &[&EnhancedTypeInfo],
1125    smart_pointers_types: &[&EnhancedTypeInfo],
1126    _other_types: &[&EnhancedTypeInfo],
1127    total_memory: usize,
1128) -> TreemapLayoutStrategy {
1129    let collections_total: usize = collections_types.iter().map(|t| t.total_size).sum();
1130    let basic_types_total: usize = basic_types.iter().map(|t| t.total_size).sum();
1131    let smart_pointers_total: usize = smart_pointers_types.iter().map(|t| t.total_size).sum();
1132
1133    let collections_percentage = if total_memory > 0 {
1134        (collections_total as f64 / total_memory as f64) * 100.0
1135    } else {
1136        0.0
1137    };
1138    let basic_types_percentage = if total_memory > 0 {
1139        (basic_types_total as f64 / total_memory as f64) * 100.0
1140    } else {
1141        0.0
1142    };
1143    let smart_pointers_percentage = if total_memory > 0 {
1144        (smart_pointers_total as f64 / total_memory as f64) * 100.0
1145    } else {
1146        0.0
1147    };
1148
1149    // Count non-zero categories
1150    let active_categories = [
1151        (collections_total > 0, "Collections"),
1152        (basic_types_total > 0, "Basic Types"),
1153        (smart_pointers_total > 0, "Smart Pointers"),
1154    ]
1155    .iter()
1156    .filter(|(active, _)| *active)
1157    .count();
1158
1159    match active_categories {
1160        0 => TreemapLayoutStrategy::MinimalLayout,
1161        1 => {
1162            // Single dominant category
1163            if collections_percentage > 80.0 {
1164                TreemapLayoutStrategy::CollectionsOnlyLayout
1165            } else if basic_types_percentage > 80.0 {
1166                TreemapLayoutStrategy::BasicTypesOnlyLayout
1167            } else {
1168                TreemapLayoutStrategy::DominantCategoryLayout {
1169                    dominant_category: if collections_total > basic_types_total
1170                        && collections_total > smart_pointers_total
1171                    {
1172                        "Collections".to_string()
1173                    } else if basic_types_total > smart_pointers_total {
1174                        "Basic Types".to_string()
1175                    } else {
1176                        "Smart Pointers".to_string()
1177                    },
1178                }
1179            }
1180        }
1181        2 => {
1182            // Two categories - use simplified layout
1183            if collections_percentage > 70.0
1184                || basic_types_percentage > 70.0
1185                || smart_pointers_percentage > 70.0
1186            {
1187                TreemapLayoutStrategy::DominantCategoryLayout {
1188                    dominant_category: if collections_total > basic_types_total
1189                        && collections_total > smart_pointers_total
1190                    {
1191                        "Collections".to_string()
1192                    } else if basic_types_total > smart_pointers_total {
1193                        "Basic Types".to_string()
1194                    } else {
1195                        "Smart Pointers".to_string()
1196                    },
1197                }
1198            } else {
1199                TreemapLayoutStrategy::FullLayout
1200            }
1201        }
1202        _ => TreemapLayoutStrategy::FullLayout, // 3+ categories - use full layout
1203    }
1204}
1205
1206/// Build adaptive treemap data structure based on layout strategy
1207fn build_adaptive_treemap_data(
1208    collections_types: Vec<&EnhancedTypeInfo>,
1209    basic_types: Vec<&EnhancedTypeInfo>,
1210    smart_pointers_types: Vec<&EnhancedTypeInfo>,
1211    _other_types: Vec<&EnhancedTypeInfo>,
1212    total_memory: usize,
1213    strategy: &TreemapLayoutStrategy,
1214) -> Vec<TreemapNode> {
1215    let mut treemap_nodes = Vec::new();
1216
1217    match strategy {
1218        TreemapLayoutStrategy::MinimalLayout => {
1219            // Show a simple "No significant data" message
1220            treemap_nodes.push(TreemapNode {
1221                name: "No Significant Memory Usage".to_string(),
1222                size: total_memory.max(1),
1223                percentage: 100.0,
1224                color: "#95a5a6".to_string(),
1225                children: Vec::new(),
1226            });
1227        }
1228        TreemapLayoutStrategy::CollectionsOnlyLayout => {
1229            // Show only Collections with detailed subcategories
1230            let collections_total: usize = collections_types.iter().map(|t| t.total_size).sum();
1231            if collections_total > 0 {
1232                treemap_nodes.push(build_collections_node(&collections_types, total_memory));
1233            }
1234        }
1235        TreemapLayoutStrategy::BasicTypesOnlyLayout => {
1236            // Show only Basic Types with detailed subcategories
1237            let basic_types_total: usize = basic_types.iter().map(|t| t.total_size).sum();
1238            if basic_types_total > 0 {
1239                treemap_nodes.push(build_basic_types_node(&basic_types, total_memory));
1240            }
1241        }
1242        TreemapLayoutStrategy::DominantCategoryLayout { dominant_category } => {
1243            // Show dominant category with details, others simplified
1244            match dominant_category.as_str() {
1245                "Collections" => {
1246                    let collections_total: usize =
1247                        collections_types.iter().map(|t| t.total_size).sum();
1248                    if collections_total > 0 {
1249                        treemap_nodes
1250                            .push(build_collections_node(&collections_types, total_memory));
1251                    }
1252                    // Add other categories as simple nodes
1253                    add_simple_categories(
1254                        &mut treemap_nodes,
1255                        &basic_types,
1256                        &smart_pointers_types,
1257                        total_memory,
1258                    );
1259                }
1260                "Basic Types" => {
1261                    let basic_types_total: usize = basic_types.iter().map(|t| t.total_size).sum();
1262                    if basic_types_total > 0 {
1263                        treemap_nodes.push(build_basic_types_node(&basic_types, total_memory));
1264                    }
1265                    // Add other categories as simple nodes
1266                    add_simple_categories_alt(
1267                        &mut treemap_nodes,
1268                        &collections_types,
1269                        &smart_pointers_types,
1270                        total_memory,
1271                    );
1272                }
1273                _ => {
1274                    // Smart Pointers dominant - use full layout
1275                    build_full_layout(
1276                        &mut treemap_nodes,
1277                        &collections_types,
1278                        &basic_types,
1279                        &smart_pointers_types,
1280                        total_memory,
1281                    );
1282                }
1283            }
1284        }
1285        TreemapLayoutStrategy::FullLayout => {
1286            // Show all categories with full details
1287            build_full_layout(
1288                &mut treemap_nodes,
1289                &collections_types,
1290                &basic_types,
1291                &smart_pointers_types,
1292                total_memory,
1293            );
1294        }
1295    }
1296
1297    treemap_nodes
1298}
1299
1300/// Build Collections node with comprehensive subcategories using enhanced type info
1301fn build_collections_node(
1302    collections_types: &[&EnhancedTypeInfo],
1303    total_memory: usize,
1304) -> TreemapNode {
1305    let collections_total: usize = collections_types.iter().map(|t| t.total_size).sum();
1306
1307    // Debug: Print what collections we received
1308    // tracing::info!("build_collections_node received {} types, total: {} bytes", collections_types.len(), collections_total);
1309    // 2
1310
1311    if collections_total == 0 {
1312        return TreemapNode {
1313            name: "Collections".to_string(),
1314            size: 1,
1315            percentage: 0.0,
1316            color: "#ecf0f1".to_string(),
1317            children: Vec::new(),
1318        };
1319    }
1320
1321    // Group by subcategory for more accurate classification
1322    let mut subcategory_groups: std::collections::HashMap<String, Vec<&EnhancedTypeInfo>> =
1323        std::collections::HashMap::new();
1324
1325    for collection_type in collections_types {
1326        subcategory_groups
1327            .entry(collection_type.subcategory.clone())
1328            .or_insert_with(Vec::new)
1329            .push(collection_type);
1330    }
1331
1332    let mut collections_children = Vec::new();
1333
1334    // Define colors for each subcategory
1335    let subcategory_colors = [
1336        ("Vec<T>", "#e74c3c"),
1337        ("HashMap<K,V>", "#3498db"),
1338        ("HashSet<T>", "#9b59b6"),
1339        ("BTreeMap<K,V>", "#2ecc71"),
1340        ("BTreeSet<T>", "#27ae60"),
1341        ("VecDeque<T>", "#f39c12"),
1342        ("LinkedList<T>", "#e67e22"),
1343        ("Other", "#95a5a6"),
1344    ]
1345    .iter()
1346    .cloned()
1347    .collect::<std::collections::HashMap<&str, &str>>();
1348
1349    for (subcategory, types) in subcategory_groups {
1350        let category_total: usize = types.iter().map(|t| t.total_size).sum();
1351
1352        // Debug: Print subcategory details
1353        // tracing::info!("Collections Subcategory '{}': {} types, total: {} bytes",
1354        //               subcategory, types.len(), category_total);
1355        // for t in &types {
1356        //     tracing::info!("  - '{}' - {} bytes", t.simplified_name, t.total_size);
1357        // }
1358
1359        if category_total > 0 {
1360            let relative_percentage = (category_total as f64 / collections_total as f64) * 100.0;
1361            let color = subcategory_colors
1362                .get(subcategory.as_str())
1363                .unwrap_or(&"#95a5a6")
1364                .to_string();
1365
1366            collections_children.push(TreemapNode {
1367                name: format!("{} ({:.1}%)", subcategory, relative_percentage),
1368                size: category_total,
1369                percentage: (category_total as f64 / total_memory as f64) * 100.0,
1370                color,
1371                children: Vec::new(),
1372            });
1373        }
1374    }
1375
1376    // Sort children by size (largest first) for better visual hierarchy
1377    collections_children.sort_by(|a, b| b.size.cmp(&a.size));
1378
1379    TreemapNode {
1380        name: format!(
1381            "Collections ({:.1}%)",
1382            (collections_total as f64 / total_memory as f64) * 100.0
1383        ),
1384        size: collections_total,
1385        percentage: (collections_total as f64 / total_memory as f64) * 100.0,
1386        color: "#ecf0f1".to_string(),
1387        children: collections_children,
1388    }
1389}
1390
1391/// Build Basic Types node with comprehensive subcategories using enhanced type info
1392fn build_basic_types_node(basic_types: &[&EnhancedTypeInfo], total_memory: usize) -> TreemapNode {
1393    let basic_types_total: usize = basic_types.iter().map(|t| t.total_size).sum();
1394
1395    // Debug: Print what basic types we received
1396    // tracing::info!("build_basic_types_node received {} types, total: {} bytes", basic_types.len(), basic_types_total);
1397    // for (i, bt) in basic_types.iter().enumerate() {
1398    //     tracing::info!("  Basic type {}: '{}' (subcategory: '{}') - {} bytes", i, bt.simplified_name, bt.subcategory, bt.total_size);
1399    // }
1400
1401    if basic_types_total == 0 {
1402        return TreemapNode {
1403            name: "Basic Types".to_string(),
1404            size: 1,
1405            percentage: 0.0,
1406            color: "#ecf0f1".to_string(),
1407            children: Vec::new(),
1408        };
1409    }
1410
1411    // Group by subcategory for more accurate classification
1412    let mut subcategory_groups: std::collections::HashMap<String, Vec<&EnhancedTypeInfo>> =
1413        std::collections::HashMap::new();
1414
1415    for basic_type in basic_types {
1416        subcategory_groups
1417            .entry(basic_type.subcategory.clone())
1418            .or_insert_with(Vec::new)
1419            .push(basic_type);
1420    }
1421
1422    let mut basic_types_children = Vec::new();
1423
1424    // Define colors for each subcategory
1425    let subcategory_colors = [
1426        ("Strings", "#2ecc71"),
1427        ("Integers", "#3498db"),
1428        ("Floats", "#e74c3c"),
1429        ("Booleans", "#f39c12"),
1430        ("Characters", "#9b59b6"),
1431        ("Arrays", "#1abc9c"),
1432        ("References", "#e67e22"),
1433        ("Other", "#95a5a6"),
1434    ]
1435    .iter()
1436    .cloned()
1437    .collect::<std::collections::HashMap<&str, &str>>();
1438
1439    for (subcategory, types) in subcategory_groups {
1440        let category_total: usize = types.iter().map(|t| t.total_size).sum();
1441
1442        // Debug: Print subcategory details
1443        // tracing::info!("Basic Types Subcategory '{}': {} types, total: {} bytes",
1444        //               subcategory, types.len(), category_total);
1445        // for t in &types {
1446        //     tracing::info!("  - '{}' - {} bytes", t.simplified_name, t.total_size);
1447        // }
1448
1449        if category_total > 0 {
1450            let relative_percentage = (category_total as f64 / basic_types_total as f64) * 100.0;
1451            let color = subcategory_colors
1452                .get(subcategory.as_str())
1453                .unwrap_or(&"#95a5a6")
1454                .to_string();
1455
1456            basic_types_children.push(TreemapNode {
1457                name: format!("{} ({:.1}%)", subcategory, relative_percentage),
1458                size: category_total,
1459                percentage: (category_total as f64 / total_memory as f64) * 100.0,
1460                color,
1461                children: Vec::new(),
1462            });
1463        }
1464    }
1465
1466    // Sort children by size (largest first) for better visual hierarchy
1467    basic_types_children.sort_by(|a, b| b.size.cmp(&a.size));
1468
1469    // Debug: Print final children
1470    // tracing::info!("Basic Types node will have {} children:", basic_types_children.len());
1471    // for (i, child) in basic_types_children.iter().enumerate() {
1472    //     tracing::info!("  Child {}: '{}' - {} bytes", i, child.name, child.size);
1473    // }
1474
1475    TreemapNode {
1476        name: format!(
1477            "Basic Types ({:.1}%)",
1478            (basic_types_total as f64 / total_memory as f64) * 100.0
1479        ),
1480        size: basic_types_total,
1481        percentage: (basic_types_total as f64 / total_memory as f64) * 100.0,
1482        color: "#ecf0f1".to_string(),
1483        children: basic_types_children,
1484    }
1485}
1486
1487/// Add simple category nodes without subcategories
1488fn add_simple_categories(
1489    treemap_nodes: &mut Vec<TreemapNode>,
1490    basic_types: &[&EnhancedTypeInfo],
1491    smart_pointers_types: &[&EnhancedTypeInfo],
1492    total_memory: usize,
1493) {
1494    let basic_types_total: usize = basic_types.iter().map(|t| t.total_size).sum();
1495    if basic_types_total > 0 {
1496        treemap_nodes.push(TreemapNode {
1497            name: "Basic Types".to_string(),
1498            size: basic_types_total,
1499            percentage: (basic_types_total as f64 / total_memory as f64) * 100.0,
1500            color: "#f39c12".to_string(),
1501            children: Vec::new(),
1502        });
1503    }
1504
1505    let smart_pointers_total: usize = smart_pointers_types.iter().map(|t| t.total_size).sum();
1506    if smart_pointers_total > 0 {
1507        treemap_nodes.push(TreemapNode {
1508            name: "Smart Pointers".to_string(),
1509            size: smart_pointers_total,
1510            percentage: (smart_pointers_total as f64 / total_memory as f64) * 100.0,
1511            color: "#9b59b6".to_string(),
1512            children: Vec::new(),
1513        });
1514    }
1515}
1516
1517/// Add simple category nodes (alternative version)
1518fn add_simple_categories_alt(
1519    treemap_nodes: &mut Vec<TreemapNode>,
1520    collections_types: &[&EnhancedTypeInfo],
1521    smart_pointers_types: &[&EnhancedTypeInfo],
1522    total_memory: usize,
1523) {
1524    let collections_total: usize = collections_types.iter().map(|t| t.total_size).sum();
1525    if collections_total > 0 {
1526        treemap_nodes.push(TreemapNode {
1527            name: "Collections".to_string(),
1528            size: collections_total,
1529            percentage: (collections_total as f64 / total_memory as f64) * 100.0,
1530            color: "#3498db".to_string(),
1531            children: Vec::new(),
1532        });
1533    }
1534
1535    let smart_pointers_total: usize = smart_pointers_types.iter().map(|t| t.total_size).sum();
1536    if smart_pointers_total > 0 {
1537        treemap_nodes.push(TreemapNode {
1538            name: "Smart Pointers".to_string(),
1539            size: smart_pointers_total,
1540            percentage: (smart_pointers_total as f64 / total_memory as f64) * 100.0,
1541            color: "#9b59b6".to_string(),
1542            children: Vec::new(),
1543        });
1544    }
1545}
1546
1547/// Build full layout with all categories
1548fn build_full_layout(
1549    treemap_nodes: &mut Vec<TreemapNode>,
1550    collections_types: &[&EnhancedTypeInfo],
1551    basic_types: &[&EnhancedTypeInfo],
1552    smart_pointers_types: &[&EnhancedTypeInfo],
1553    total_memory: usize,
1554) {
1555    let collections_total: usize = collections_types.iter().map(|t| t.total_size).sum();
1556    if collections_total > 0 {
1557        treemap_nodes.push(build_collections_node(collections_types, total_memory));
1558    }
1559
1560    let basic_types_total: usize = basic_types.iter().map(|t| t.total_size).sum();
1561    if basic_types_total > 0 {
1562        treemap_nodes.push(build_basic_types_node(basic_types, total_memory));
1563    }
1564
1565    let smart_pointers_total: usize = smart_pointers_types.iter().map(|t| t.total_size).sum();
1566    if smart_pointers_total > 0 {
1567        treemap_nodes.push(TreemapNode {
1568            name: "Smart Pointers".to_string(),
1569            size: smart_pointers_total,
1570            percentage: (smart_pointers_total as f64 / total_memory as f64) * 100.0,
1571            color: "#9b59b6".to_string(),
1572            children: Vec::new(),
1573        });
1574    }
1575}
1576
1577/// Render adaptive treemap based on layout strategy
1578fn render_adaptive_treemap(
1579    document: Document,
1580    area: IntegratedTreemapArea,
1581    treemap_data: &[TreemapNode],
1582    _strategy: &TreemapLayoutStrategy,
1583) -> TrackingResult<Document> {
1584    // Use the existing render_squarified_treemap function
1585    render_squarified_treemap(document, area, treemap_data)
1586}
1587
1588/// Render intelligent horizontal-band treemap layout (like basic_usage_graph.svg)
1589fn render_squarified_treemap(
1590    mut document: Document,
1591    area: IntegratedTreemapArea,
1592    nodes: &[TreemapNode],
1593) -> TrackingResult<Document> {
1594    if nodes.is_empty() {
1595        return render_empty_treemap(document, area);
1596    }
1597
1598    // Container background
1599    let container_bg = Rectangle::new()
1600        .set("class", "integrated-treemap-container")
1601        .set("x", area.x)
1602        .set("y", area.y)
1603        .set("width", area.width)
1604        .set("height", area.height)
1605        .set("fill", "#ecf0f1")
1606        .set("stroke", "#bdc3c7")
1607        .set("stroke-width", 2)
1608        .set("rx", 20);
1609    document = document.add(container_bg);
1610
1611    // Calculate total size for proportional height allocation
1612    let total_size: usize = nodes.iter().map(|n| n.size).sum();
1613    if total_size == 0 {
1614        return render_empty_treemap(document, area);
1615    }
1616
1617    // Layout parameters
1618    let padding = 15.0;
1619    let band_spacing = 10.0;
1620    let available_width = area.width - (padding * 2.0);
1621    let available_height =
1622        area.height - (padding * 2.0) - (band_spacing * (nodes.len() - 1) as f64);
1623
1624    let mut current_y = area.y + padding;
1625
1626    // Sort nodes by size (largest first) for better visual hierarchy
1627    let mut sorted_nodes = nodes.to_vec();
1628    sorted_nodes.sort_by(|a, b| b.size.cmp(&a.size));
1629
1630    for node in &sorted_nodes {
1631        // Calculate proportional height for this band
1632        let band_height = (node.size as f64 / total_size as f64) * available_height;
1633
1634        // Render the band based on whether it has children
1635        if !node.children.is_empty() {
1636            document = render_smart_horizontal_band(
1637                document,
1638                area.x + padding,
1639                current_y,
1640                available_width,
1641                band_height,
1642                node,
1643            )?;
1644        } else {
1645            document = render_simple_horizontal_band(
1646                document,
1647                area.x + padding,
1648                current_y,
1649                available_width,
1650                band_height,
1651                node,
1652            )?;
1653        }
1654
1655        current_y += band_height + band_spacing;
1656    }
1657
1658    // Note: No separate legend needed - information is embedded in treemap labels
1659
1660    Ok(document)
1661}
1662
1663/// Render empty treemap when no data available
1664fn render_empty_treemap(
1665    mut document: Document,
1666    area: IntegratedTreemapArea,
1667) -> TrackingResult<Document> {
1668    let container_bg = Rectangle::new()
1669        .set("class", "integrated-treemap-container")
1670        .set("x", area.x)
1671        .set("y", area.y)
1672        .set("width", area.width)
1673        .set("height", area.height)
1674        .set("fill", "#f8f9fa")
1675        .set("stroke", "#dee2e6")
1676        .set("stroke-width", 2)
1677        .set("rx", 20);
1678    document = document.add(container_bg);
1679
1680    let no_data_text = SvgText::new("No Memory Type Data Available")
1681        .set("x", area.x + area.width / 2.0)
1682        .set("y", area.y + area.height / 2.0)
1683        .set("text-anchor", "middle")
1684        .set("font-size", 16)
1685        .set("font-weight", "bold")
1686        .set("fill", "#6c757d");
1687    document = document.add(no_data_text);
1688
1689    Ok(document)
1690}
1691
1692/// Render horizontal band with children (Collections, Basic Types)
1693fn render_smart_horizontal_band(
1694    mut document: Document,
1695    x: f64,
1696    y: f64,
1697    width: f64,
1698    height: f64,
1699    node: &TreemapNode,
1700) -> TrackingResult<Document> {
1701    // Band background
1702    let band_bg = Rectangle::new()
1703        .set("class", "integrated-treemap-rect")
1704        .set("x", x)
1705        .set("y", y)
1706        .set("width", width)
1707        .set("height", height)
1708        .set("fill", node.color.as_str())
1709        .set("rx", 18);
1710    document = document.add(band_bg);
1711
1712    // Band title
1713    let title_y = y + 20.0;
1714    let title = SvgText::new(format!("{} - {:.0}%", node.name, node.percentage))
1715        .set("class", "integrated-treemap-label")
1716        .set("x", x + width / 2.0)
1717        .set("y", title_y)
1718        .set("font-size", 16);
1719    document = document.add(title);
1720
1721    // Children layout area
1722    let children_y = title_y + 10.0;
1723    let children_height = height - 30.0;
1724    let children_padding = 10.0;
1725    let available_children_width = width - (children_padding * 2.0);
1726
1727    // Calculate total children size for proportional width allocation
1728    let total_children_size: usize = node.children.iter().map(|c| c.size).sum();
1729    if total_children_size == 0 {
1730        return Ok(document);
1731    }
1732
1733    let mut current_x = x + children_padding;
1734
1735    for child in &node.children {
1736        // Calculate proportional width for this child
1737        let child_width =
1738            (child.size as f64 / total_children_size as f64) * available_children_width;
1739
1740        // Child rectangle
1741        let child_rect = Rectangle::new()
1742            .set("class", "integrated-treemap-rect")
1743            .set("x", current_x)
1744            .set("y", children_y)
1745            .set("width", child_width - 5.0) // Small gap between children
1746            .set("height", children_height - 10.0)
1747            .set("fill", child.color.as_str())
1748            .set("rx", 18);
1749        document = document.add(child_rect);
1750
1751        // Child label (extract percentage from name if present)
1752        let (child_name, child_relative_percentage) = extract_name_and_percentage(&child.name);
1753
1754        let child_label = SvgText::new(child_name)
1755            .set("class", "integrated-treemap-label")
1756            .set("x", current_x + child_width / 2.0)
1757            .set("y", children_y + children_height / 2.0 - 5.0)
1758            .set("font-size", calculate_font_size(child_width))
1759            .set("font-weight", "bold");
1760        document = document.add(child_label);
1761
1762        // Child percentage
1763        let percentage_text = if let Some(pct) = child_relative_percentage {
1764            format!("({})", pct)
1765        } else {
1766            format!(
1767                "({:.0}%)",
1768                (child.size as f64 / total_children_size as f64) * 100.0
1769            )
1770        };
1771
1772        let child_percentage = SvgText::new(percentage_text)
1773            .set("class", "integrated-treemap-percentage")
1774            .set("x", current_x + child_width / 2.0)
1775            .set("y", children_y + children_height / 2.0 + 15.0)
1776            .set("font-size", calculate_font_size(child_width) - 2);
1777        document = document.add(child_percentage);
1778
1779        current_x += child_width;
1780    }
1781
1782    Ok(document)
1783}
1784
1785/// Render simple horizontal band without children (Smart Pointers)
1786fn render_simple_horizontal_band(
1787    mut document: Document,
1788    x: f64,
1789    y: f64,
1790    width: f64,
1791    height: f64,
1792    node: &TreemapNode,
1793) -> TrackingResult<Document> {
1794    // Simple band rectangle
1795    let band_rect = Rectangle::new()
1796        .set("class", "integrated-treemap-rect")
1797        .set("x", x)
1798        .set("y", y)
1799        .set("width", width)
1800        .set("height", height)
1801        .set("fill", node.color.as_str())
1802        .set("rx", 18);
1803    document = document.add(band_rect);
1804
1805    // Band label
1806    let label = SvgText::new(&node.name)
1807        .set("class", "integrated-treemap-label")
1808        .set("x", x + width / 2.0)
1809        .set("y", y + height / 2.0 - 5.0)
1810        .set("font-size", 16)
1811        .set("font-weight", "bold");
1812    document = document.add(label);
1813
1814    // Band percentage
1815    let percentage_label = SvgText::new(format!("{:.0}%", node.percentage))
1816        .set("class", "integrated-treemap-percentage")
1817        .set("x", x + width / 2.0)
1818        .set("y", y + height / 2.0 + 15.0)
1819        .set("font-size", 12);
1820    document = document.add(percentage_label);
1821
1822    Ok(document)
1823}
1824
1825/// Extract name and percentage from formatted strings like "Vec<T> (100%)"
1826fn extract_name_and_percentage(formatted_name: &str) -> (&str, Option<&str>) {
1827    if let Some(open_paren) = formatted_name.find(" (") {
1828        if let Some(close_paren) = formatted_name.find(')') {
1829            let name = &formatted_name[..open_paren];
1830            let percentage = &formatted_name[open_paren + 2..close_paren];
1831            return (name, Some(percentage));
1832        }
1833    }
1834    (formatted_name, None)
1835}
1836
1837/// Calculate appropriate font size based on available width
1838fn calculate_font_size(width: f64) -> i32 {
1839    if width > 200.0 {
1840        15
1841    } else if width > 100.0 {
1842        13
1843    } else if width > 50.0 {
1844        11
1845    } else {
1846        9
1847    }
1848}
1849
1850/// Add categorized allocations visualization
1851pub fn add_categorized_allocations(
1852    mut document: Document,
1853    categories: &[AllocationCategory],
1854) -> TrackingResult<Document> {
1855    let chart_x = 50;
1856    let chart_y = 1080; // Adjusted to provide better spacing (720 + 300 + 60 margin)
1857    let chart_width = 850;
1858    let chart_height = 280; // Slightly reduced height
1859
1860    // Chart background with rounded corners
1861    let bg = Rectangle::new()
1862        .set("x", chart_x)
1863        .set("y", chart_y)
1864        .set("width", chart_width)
1865        .set("height", chart_height)
1866        .set("fill", "white")
1867        .set("stroke", "#bdc3c7")
1868        .set("stroke-width", 2)
1869        .set("rx", 20); // Even more rounded corners for natural look
1870
1871    document = document.add(bg);
1872
1873    // Chart title
1874    let title = SvgText::new("Tracked Variables by Category")
1875        .set("x", chart_x + chart_width / 2)
1876        .set("y", chart_y - 10)
1877        .set("text-anchor", "middle")
1878        .set("font-size", 16)
1879        .set("font-weight", "bold")
1880        .set("fill", "#2c3e50");
1881
1882    document = document.add(title);
1883
1884    if categories.is_empty() {
1885        let no_data = SvgText::new("No tracked variables found")
1886            .set("x", chart_x + chart_width / 2)
1887            .set("y", chart_y + chart_height / 2)
1888            .set("text-anchor", "middle")
1889            .set("font-size", 14)
1890            .set("fill", "#7f8c8d");
1891
1892        document = document.add(no_data);
1893        return Ok(document);
1894    }
1895
1896    // Create simple bar chart for categories
1897    let max_size = categories.iter().map(|c| c.total_size).max().unwrap_or(1);
1898    let bar_height = (chart_height - 60) / categories.len().min(8);
1899
1900    for (i, category) in categories.iter().take(8).enumerate() {
1901        let y = chart_y + 30 + i * bar_height;
1902        let bar_width =
1903            ((category.total_size as f64 / max_size as f64) * (chart_width - 200) as f64) as i32;
1904
1905        // Bar with rounded corners
1906        let bar = Rectangle::new()
1907            .set("x", chart_x + 150)
1908            .set("y", y)
1909            .set("width", bar_width)
1910            .set("height", bar_height - 5)
1911            .set("fill", category.color.as_str())
1912            .set("stroke", "#34495e")
1913            .set("stroke-width", 1)
1914            .set("rx", 12); // More rounded bar corners for natural look
1915
1916        document = document.add(bar);
1917
1918        // Category name
1919        let name_text = SvgText::new(&category.name)
1920            .set("x", chart_x + 10)
1921            .set("y", y + bar_height / 2 + 4)
1922            .set("font-size", 12)
1923            .set("font-weight", "600")
1924            .set("fill", "#2c3e50");
1925
1926        document = document.add(name_text);
1927
1928        // Enhanced variable names display - optimize text overflow issues
1929        let var_names: Vec<String> = category
1930            .allocations
1931            .iter()
1932            .filter_map(|a| {
1933                if let Some(var_name) = &a.var_name {
1934                    let type_name = a.type_name.as_deref().unwrap_or("Unknown");
1935                    let (simplified_type, _) = simplify_type_name(type_name);
1936                    // Shorten variable name display to avoid overflow
1937                    let short_var = if var_name.len() > 12 {
1938                        format!("{}...", &var_name[..9])
1939                    } else {
1940                        var_name.clone()
1941                    };
1942                    Some(format!("{short_var}({simplified_type})"))
1943                } else {
1944                    None
1945                }
1946            })
1947            .take(3) // Reduce number of displayed variables to avoid overflow
1948            .collect();
1949
1950        let mut display_text = if var_names.is_empty() {
1951            format!(
1952                "{} ({} vars)",
1953                format_bytes(category.total_size),
1954                category.allocations.len()
1955            )
1956        } else {
1957            format!(
1958                "{} - Vars: {}",
1959                format_bytes(category.total_size),
1960                var_names.join(", ")
1961            )
1962        };
1963
1964        // Dynamically truncate text to ensure it doesn't exceed chart boundaries
1965        let max_text_width = chart_width - 180; // Reserve margin
1966        let estimated_char_width = 7; // Estimate character width
1967        let max_chars = (max_text_width / estimated_char_width) as usize;
1968
1969        if display_text.len() > max_chars {
1970            display_text = format!("{}...", &display_text[..max_chars.saturating_sub(3)]);
1971        }
1972
1973        let size_text = SvgText::new(display_text)
1974            .set("x", chart_x + 160)
1975            .set("y", y + bar_height / 2 + 4)
1976            .set("font-size", 11) // Slightly reduce font size to avoid overflow
1977            .set("font-weight", "bold")
1978            .set("fill", "#FFFFFF")
1979            .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.8)");
1980
1981        document = document.add(size_text);
1982    }
1983
1984    Ok(document)
1985}
1986
1987/// Add memory timeline visualization
1988pub fn add_memory_timeline(
1989    mut document: Document,
1990    allocations: &[AllocationInfo],
1991    _stats: &MemoryStats,
1992) -> TrackingResult<Document> {
1993    let chart_x = 50;
1994    let chart_y = 1780;
1995    let chart_width = 1700;
1996    let chart_height = 300;
1997
1998    // Chart background
1999    let bg = Rectangle::new()
2000        .set("x", chart_x)
2001        .set("y", chart_y)
2002        .set("width", chart_width)
2003        .set("height", chart_height)
2004        .set("fill", "white")
2005        .set("stroke", "#bdc3c7")
2006        .set("stroke-width", 1)
2007        .set("rx", 5);
2008
2009    document = document.add(bg);
2010
2011    // Chart title
2012    let title = SvgText::new("Variable Allocation Timeline")
2013        .set("x", chart_x + chart_width / 2)
2014        .set("y", chart_y - 10)
2015        .set("text-anchor", "middle")
2016        .set("font-size", 16)
2017        .set("font-weight", "bold")
2018        .set("fill", "#2c3e50");
2019
2020    document = document.add(title);
2021
2022    if allocations.is_empty() {
2023        let no_data = SvgText::new("No allocation data available")
2024            .set("x", chart_x + chart_width / 2)
2025            .set("y", chart_y + chart_height / 2)
2026            .set("text-anchor", "middle")
2027            .set("font-size", 14)
2028            .set("fill", "#7f8c8d");
2029
2030        document = document.add(no_data);
2031        return Ok(document);
2032    }
2033
2034    // Filter and sort tracked allocations
2035    let mut tracked_allocs: Vec<_> = allocations
2036        .iter()
2037        .filter(|a| a.var_name.is_some())
2038        .collect();
2039    tracked_allocs.sort_by_key(|a| a.timestamp_alloc);
2040
2041    if tracked_allocs.is_empty() {
2042        let no_data = SvgText::new("No tracked variables found")
2043            .set("x", chart_x + chart_width / 2)
2044            .set("y", chart_y + chart_height / 2)
2045            .set("text-anchor", "middle")
2046            .set("font-size", 14)
2047            .set("fill", "#7f8c8d");
2048
2049        document = document.add(no_data);
2050        return Ok(document);
2051    }
2052
2053    let min_time = tracked_allocs
2054        .first()
2055        .map(|a| a.timestamp_alloc)
2056        .unwrap_or(0);
2057    let max_time = tracked_allocs
2058        .last()
2059        .map(|a| a.timestamp_alloc)
2060        .unwrap_or(min_time + 1);
2061    let _time_range = (max_time - min_time).max(1);
2062
2063    // Calculate layout parameters for better alignment and prevent text overflow
2064    let label_width = 400; // Increased reserved space for labels to prevent overflow
2065    let timeline_width = chart_width - label_width - 60; // More margin
2066    let max_items = 8; // Limit items to prevent overcrowding
2067
2068    // Draw timeline for tracked variables with proper spacing
2069    for (i, allocation) in tracked_allocs.iter().take(max_items).enumerate() {
2070        // Distribute items evenly across timeline instead of by timestamp
2071        let x = chart_x + 20 + (i * timeline_width / max_items.max(1));
2072        let y = chart_y + 50 + (i * 25); // Increased vertical spacing
2073
2074        // Ensure x position stays within timeline bounds
2075        let x = x.min(chart_x + timeline_width).max(chart_x + 20);
2076
2077        // Get color based on type category
2078        let color = if let Some(type_name) = &allocation.type_name {
2079            let (_, category) = simplify_type_name(type_name);
2080            get_category_color(&category)
2081        } else {
2082            "#95a5a6".to_string()
2083        };
2084
2085        // Draw allocation point
2086        let point = Circle::new()
2087            .set("cx", x)
2088            .set("cy", y)
2089            .set("r", 5)
2090            .set("fill", color)
2091            .set("stroke", "#2c3e50")
2092            .set("stroke-width", 2);
2093
2094        document = document.add(point);
2095
2096        // Draw connecting line to label area
2097        let label_start_x = chart_x + timeline_width + 20;
2098        let line = svg::node::element::Line::new()
2099            .set("x1", x + 5)
2100            .set("y1", y)
2101            .set("x2", label_start_x)
2102            .set("y2", y)
2103            .set("stroke", "#bdc3c7")
2104            .set("stroke-width", 1)
2105            .set("stroke-dasharray", "3,3");
2106
2107        document = document.add(line);
2108
2109        // Add variable name with type in dedicated label area - prevent overflow
2110        if let Some(var_name) = &allocation.var_name {
2111            let type_name = allocation.type_name.as_deref().unwrap_or("Unknown");
2112            let (simplified_type, _) = simplify_type_name(type_name);
2113            let mut label_text = format!(
2114                "{}({}) memory: {}",
2115                var_name,
2116                simplified_type,
2117                format_bytes(allocation.size)
2118            );
2119
2120            // Truncate text if too long to prevent overflow
2121            if label_text.len() > 45 {
2122                label_text = format!("{}...", &label_text[..42]);
2123            }
2124
2125            let label = SvgText::new(label_text)
2126                .set("x", label_start_x + 5)
2127                .set("y", y + 4)
2128                .set("font-size", 10) // Slightly smaller font
2129                .set("font-weight", "500")
2130                .set("fill", "#2c3e50");
2131
2132            document = document.add(label);
2133        }
2134    }
2135
2136    // Add timeline axis
2137    let axis_y = chart_y + chart_height - 40;
2138    let axis_line = svg::node::element::Line::new()
2139        .set("x1", chart_x + 20)
2140        .set("y1", axis_y)
2141        .set("x2", chart_x + timeline_width)
2142        .set("y2", axis_y)
2143        .set("stroke", "#34495e")
2144        .set("stroke-width", 2);
2145
2146    document = document.add(axis_line);
2147
2148    // Add axis labels
2149    let start_label = SvgText::new("Timeline")
2150        .set("x", chart_x + 20)
2151        .set("y", axis_y + 20)
2152        .set("font-size", 12)
2153        .set("font-weight", "600")
2154        .set("fill", "#7f8c8d");
2155
2156    document = document.add(start_label);
2157
2158    Ok(document)
2159}
2160
2161/// Add fragmentation analysis chart
2162pub fn add_fragmentation_analysis(
2163    mut document: Document,
2164    allocations: &[AllocationInfo],
2165) -> TrackingResult<Document> {
2166    let chart_x = 950;
2167    let chart_y = 710; // Fix: adjust position to avoid overlap
2168    let chart_width = 750; // 修复:适应新的1600px宽度
2169    let chart_height = 300;
2170
2171    // Chart background
2172    let bg = Rectangle::new()
2173        .set("x", chart_x)
2174        .set("y", chart_y)
2175        .set("width", chart_width)
2176        .set("height", chart_height)
2177        .set("fill", "white")
2178        .set("stroke", "#f39c12")
2179        .set("stroke-width", 2)
2180        .set("rx", 10);
2181
2182    document = document.add(bg);
2183
2184    // Chart title
2185    let title = SvgText::new("Memory Fragmentation Analysis")
2186        .set("x", chart_x + chart_width / 2)
2187        .set("y", chart_y - 10)
2188        .set("text-anchor", "middle")
2189        .set("font-size", 18)
2190        .set("font-weight", "bold")
2191        .set("fill", "#2c3e50");
2192
2193    document = document.add(title);
2194
2195    if allocations.is_empty() {
2196        let no_data = SvgText::new("No allocation data available")
2197            .set("x", chart_x + chart_width / 2)
2198            .set("y", chart_y + chart_height / 2)
2199            .set("text-anchor", "middle")
2200            .set("font-size", 14)
2201            .set("fill", "#7f8c8d");
2202
2203        document = document.add(no_data);
2204        return Ok(document);
2205    }
2206
2207    // Create size distribution histogram
2208    let size_buckets = [
2209        (0, 64, "Tiny (0-64B)"),
2210        (65, 256, "Small (65-256B)"),
2211        (257, 1024, "Medium (257B-1KB)"),
2212        (1025, 4096, "Large (1-4KB)"),
2213        (4097, 16384, "XLarge (4-16KB)"),
2214        (16385, usize::MAX, "Huge (>16KB)"),
2215    ];
2216
2217    let mut bucket_counts = vec![0; size_buckets.len()];
2218
2219    for allocation in allocations {
2220        for (i, &(min, max, _)) in size_buckets.iter().enumerate() {
2221            if allocation.size >= min && allocation.size <= max {
2222                bucket_counts[i] += 1;
2223                break;
2224            }
2225        }
2226    }
2227
2228    let max_count = bucket_counts.iter().max().copied().unwrap_or(1);
2229    let bar_width = (chart_width - 100) / size_buckets.len();
2230
2231    // Draw histogram bars
2232    for (i, (&(_, _, label), &count)) in size_buckets.iter().zip(bucket_counts.iter()).enumerate() {
2233        let x = chart_x + 50 + i * bar_width;
2234        let bar_height = if max_count > 0 {
2235            (count as f64 / max_count as f64 * (chart_height - 80) as f64) as i32
2236        } else {
2237            0
2238        };
2239        let y = chart_y + chart_height - 40 - bar_height;
2240
2241        // Color based on fragmentation level
2242        let color = match i {
2243            0..=1 => "#27ae60", // Green for small allocations
2244            2..=3 => "#f39c12", // Orange for medium
2245            _ => "#e74c3c",     // Red for large (potential fragmentation)
2246        };
2247
2248        let bar = Rectangle::new()
2249            .set("x", x)
2250            .set("y", y)
2251            .set("width", bar_width - 5)
2252            .set("height", bar_height)
2253            .set("fill", color)
2254            .set("stroke", "#2c3e50")
2255            .set("stroke-width", 1);
2256
2257        document = document.add(bar);
2258
2259        // Count label
2260        let count_text = SvgText::new(count.to_string())
2261            .set("x", x + bar_width / 2)
2262            .set("y", y - 5)
2263            .set("text-anchor", "middle")
2264            .set("font-size", 12)
2265            .set("font-weight", "bold")
2266            .set("fill", "#2c3e50");
2267
2268        document = document.add(count_text);
2269
2270        // Size label
2271        let size_text = SvgText::new(label)
2272            .set("x", x + bar_width / 2)
2273            .set("y", chart_y + chart_height - 10)
2274            .set("text-anchor", "middle")
2275            .set("font-size", 10)
2276            .set("fill", "#7f8c8d");
2277
2278        document = document.add(size_text);
2279    }
2280
2281    Ok(document)
2282}
2283
2284/// Add call stack analysis visualization
2285pub fn add_callstack_analysis(
2286    mut document: Document,
2287    allocations: &[AllocationInfo],
2288) -> TrackingResult<Document> {
2289    let chart_x = 950;
2290    let chart_y = 1060; // Fix: adjust position to avoid overlap
2291    let chart_width = 750; // 修复:适应新的1600px宽度
2292    let chart_height = 300;
2293
2294    // Chart background
2295    let bg = Rectangle::new()
2296        .set("x", chart_x)
2297        .set("y", chart_y)
2298        .set("width", chart_width)
2299        .set("height", chart_height)
2300        .set("fill", "white")
2301        .set("stroke", "#9b59b6")
2302        .set("stroke-width", 2)
2303        .set("rx", 10);
2304
2305    document = document.add(bg);
2306
2307    // Chart title
2308    let title = SvgText::new("Call Stack Analysis")
2309        .set("x", chart_x + chart_width / 2)
2310        .set("y", chart_y - 10)
2311        .set("text-anchor", "middle")
2312        .set("font-size", 18)
2313        .set("font-weight", "bold")
2314        .set("fill", "#2c3e50");
2315
2316    document = document.add(title);
2317
2318    // Group allocations by variable name and type with better categorization
2319    let mut source_stats: HashMap<String, (usize, usize)> = HashMap::new();
2320
2321    for allocation in allocations {
2322        // Create a more descriptive key that helps identify allocations
2323        let source_key = if let Some(var_name) = &allocation.var_name {
2324            // Tracked variables - show variable name and type
2325            if let Some(type_name) = &allocation.type_name {
2326                let (simplified_type, _) = simplify_type_name(type_name);
2327                format!(
2328                    "{}({}) memory: {}",
2329                    var_name,
2330                    simplified_type,
2331                    format_bytes(allocation.size)
2332                )
2333            } else {
2334                format!(
2335                    "{}(Unknown Type) memory: {}",
2336                    var_name,
2337                    format_bytes(allocation.size)
2338                )
2339            }
2340        } else if let Some(type_name) = &allocation.type_name {
2341            // Untracked allocations with known type - categorize by type and source
2342            let (simplified_type, _) = simplify_type_name(type_name);
2343
2344            // Try to identify the source of untracked allocations
2345            if type_name.contains("std::") || type_name.contains("alloc::") {
2346                format!("System/Runtime {simplified_type} (untracked)")
2347            } else if simplified_type.contains("Vec") {
2348                "Internal Vec allocations (untracked)".to_string()
2349            } else if simplified_type.contains("String") {
2350                "Internal String allocations (untracked)".to_string()
2351            } else if simplified_type.contains("HashMap") {
2352                "Internal HashMap allocations (untracked)".to_string()
2353            } else {
2354                format!("Internal {simplified_type} allocations (untracked)")
2355            }
2356        } else {
2357            // Completely unknown allocations
2358            "System/Runtime allocations (no type info)".to_string()
2359        };
2360
2361        let entry = source_stats.entry(source_key).or_insert((0, 0));
2362        entry.0 += 1;
2363        entry.1 += allocation.size;
2364    }
2365
2366    if source_stats.is_empty() {
2367        let no_data = SvgText::new("No call stack data available")
2368            .set("x", chart_x + chart_width / 2)
2369            .set("y", chart_y + chart_height / 2)
2370            .set("text-anchor", "middle")
2371            .set("font-size", 14)
2372            .set("fill", "#7f8c8d");
2373
2374        document = document.add(no_data);
2375        return Ok(document);
2376    }
2377
2378    // Sort by total size and take top 10
2379    let mut sorted_sources: Vec<_> = source_stats.iter().collect();
2380    sorted_sources.sort_by(|a, b| b.1 .1.cmp(&a.1 .1));
2381
2382    let max_size = sorted_sources
2383        .first()
2384        .map(|(_, (_, size))| *size)
2385        .unwrap_or(1);
2386
2387    // Draw tree-like visualization
2388    for (i, (source, (count, total_size))) in sorted_sources.iter().take(10).enumerate() {
2389        let y = chart_y + 40 + i * 25;
2390        let node_size = ((*total_size as f64 / max_size as f64) * 15.0 + 5.0) as i32;
2391
2392        // Draw node
2393        let colors = ["#e74c3c", "#f39c12", "#27ae60", "#3498db", "#9b59b6"];
2394        let color = colors[i % colors.len()];
2395
2396        let node = Circle::new()
2397            .set("cx", chart_x + 50)
2398            .set("cy", y)
2399            .set("r", node_size)
2400            .set("fill", color)
2401            .set("stroke", "#2c3e50")
2402            .set("stroke-width", 2);
2403
2404        document = document.add(node);
2405
2406        // Source label
2407        let source_text = format!("{source} ({count} allocs, {total_size} bytes)");
2408
2409        let label = SvgText::new(source_text)
2410            .set("x", chart_x + 80)
2411            .set("y", y + 5)
2412            .set("font-size", 11)
2413            .set("font-weight", "500")
2414            .set("fill", "#2c3e50");
2415
2416        document = document.add(label);
2417    }
2418
2419    Ok(document)
2420}
2421
2422/// Add memory growth trends visualization
2423pub fn add_memory_growth_trends(
2424    mut document: Document,
2425    allocations: &[AllocationInfo],
2426    stats: &MemoryStats,
2427) -> TrackingResult<Document> {
2428    let chart_x = 50;
2429    let chart_y = 1430;
2430    let chart_width = 1700;
2431    let chart_height = 300;
2432
2433    // Chart background
2434    let bg = Rectangle::new()
2435        .set("x", chart_x)
2436        .set("y", chart_y)
2437        .set("width", chart_width)
2438        .set("height", chart_height)
2439        .set("fill", "white")
2440        .set("stroke", "#27ae60")
2441        .set("stroke-width", 2)
2442        .set("rx", 10);
2443
2444    document = document.add(bg);
2445
2446    // Chart title
2447    let title = SvgText::new("Memory Growth Trends")
2448        .set("x", chart_x + chart_width / 2)
2449        .set("y", chart_y - 10)
2450        .set("text-anchor", "middle")
2451        .set("font-size", 18)
2452        .set("font-weight", "bold")
2453        .set("fill", "#2c3e50");
2454
2455    document = document.add(title);
2456
2457    if allocations.is_empty() {
2458        let no_data = SvgText::new("No allocation data available")
2459            .set("x", chart_x + chart_width / 2)
2460            .set("y", chart_y + chart_height / 2)
2461            .set("text-anchor", "middle")
2462            .set("font-size", 14)
2463            .set("fill", "#7f8c8d");
2464
2465        document = document.add(no_data);
2466        return Ok(document);
2467    }
2468
2469    // Create simplified trend visualization
2470    let time_points = 10;
2471    let point_width = (chart_width - 100) / time_points;
2472
2473    for i in 0..time_points {
2474        let x = chart_x + 50 + i * point_width;
2475        let simulated_memory = stats.active_memory / time_points * (i + 1);
2476        let y = chart_y + chart_height
2477            - 50
2478            - ((simulated_memory as f64 / stats.peak_memory as f64) * (chart_height - 100) as f64)
2479                as i32;
2480
2481        // Draw data points
2482        let point = Circle::new()
2483            .set("cx", x)
2484            .set("cy", y)
2485            .set("r", 4)
2486            .set("fill", "#27ae60")
2487            .set("stroke", "#2c3e50")
2488            .set("stroke-width", 1);
2489
2490        document = document.add(point);
2491
2492        // Connect with lines
2493        if i > 0 {
2494            let prev_x = chart_x + 50 + (i - 1) * point_width;
2495            let prev_memory = stats.active_memory / time_points * i;
2496            let prev_y = chart_y + chart_height
2497                - 50
2498                - ((prev_memory as f64 / stats.peak_memory as f64) * (chart_height - 100) as f64)
2499                    as i32;
2500
2501            let line = svg::node::element::Line::new()
2502                .set("x1", prev_x)
2503                .set("y1", prev_y)
2504                .set("x2", x)
2505                .set("y2", y)
2506                .set("stroke", "#27ae60")
2507                .set("stroke-width", 2);
2508
2509            document = document.add(line);
2510        }
2511    }
2512
2513    // Add peak memory indicator
2514    let peak_y = chart_y + 50;
2515    let peak_line = svg::node::element::Line::new()
2516        .set("x1", chart_x + 50)
2517        .set("y1", peak_y)
2518        .set("x2", chart_x + chart_width - 50)
2519        .set("y2", peak_y)
2520        .set("stroke", "#e74c3c")
2521        .set("stroke-width", 2)
2522        .set("stroke-dasharray", "10,5");
2523
2524    document = document.add(peak_line);
2525
2526    let peak_label = SvgText::new(format!("Peak: {} bytes", stats.peak_memory))
2527        .set("x", chart_x + chart_width - 100)
2528        .set("y", peak_y - 10)
2529        .set("font-size", 12)
2530        .set("font-weight", "bold")
2531        .set("fill", "#e74c3c");
2532
2533    document = document.add(peak_label);
2534
2535    Ok(document)
2536}
2537
2538/// Add interactive legend
2539pub fn add_interactive_legend(mut document: Document) -> TrackingResult<Document> {
2540    let legend_x = 50;
2541    let legend_y = 2130;
2542    let legend_width = 850;
2543    let legend_height = 250;
2544
2545    // Legend background
2546    let bg = Rectangle::new()
2547        .set("x", legend_x)
2548        .set("y", legend_y)
2549        .set("width", legend_width)
2550        .set("height", legend_height)
2551        .set("fill", "white")
2552        .set("stroke", "#34495e")
2553        .set("stroke-width", 2)
2554        .set("rx", 10);
2555
2556    document = document.add(bg);
2557
2558    // Legend title
2559    let title = SvgText::new("Interactive Legend & Guide")
2560        .set("x", legend_x + legend_width / 2)
2561        .set("y", legend_y - 10)
2562        .set("text-anchor", "middle")
2563        .set("font-size", 18)
2564        .set("font-weight", "bold")
2565        .set("fill", "#2c3e50");
2566
2567    document = document.add(title);
2568
2569    // Legend items
2570    let legend_items = [
2571        ("#e74c3c", "High Memory Usage / Critical"),
2572        ("#f39c12", "Medium Usage / Warning"),
2573        ("#27ae60", "Low Usage / Good"),
2574        ("#3498db", "Performance Metrics"),
2575        ("#9b59b6", "Call Stack Data"),
2576        ("#34495e", "General Information"),
2577    ];
2578
2579    for (i, (color, description)) in legend_items.iter().enumerate() {
2580        let x = legend_x + 30 + (i % 3) * 220;
2581        let y = legend_y + 40 + (i / 3) * 40;
2582
2583        // Color swatch
2584        let swatch = Rectangle::new()
2585            .set("x", x)
2586            .set("y", y - 10)
2587            .set("width", 20)
2588            .set("height", 15)
2589            .set("fill", *color)
2590            .set("stroke", "#2c3e50")
2591            .set("stroke-width", 1);
2592
2593        document = document.add(swatch);
2594
2595        // Description
2596        let desc_text = SvgText::new(*description)
2597            .set("x", x + 30)
2598            .set("y", y)
2599            .set("font-size", 12)
2600            .set("fill", "#2c3e50");
2601
2602        document = document.add(desc_text);
2603    }
2604
2605    Ok(document)
2606}
2607
2608/// Add comprehensive summary
2609pub fn add_comprehensive_summary(
2610    mut document: Document,
2611    stats: &MemoryStats,
2612    allocations: &[AllocationInfo],
2613) -> TrackingResult<Document> {
2614    let summary_x = 950;
2615    let summary_y = 2130;
2616    let summary_width = 800;
2617    let summary_height = 250;
2618
2619    // Summary background
2620    let bg = Rectangle::new()
2621        .set("x", summary_x)
2622        .set("y", summary_y)
2623        .set("width", summary_width)
2624        .set("height", summary_height)
2625        .set("fill", "white")
2626        .set("stroke", "#2c3e50")
2627        .set("stroke-width", 2)
2628        .set("rx", 10);
2629
2630    document = document.add(bg);
2631
2632    // Summary title
2633    let title = SvgText::new("Memory Analysis Summary")
2634        .set("x", summary_x + summary_width / 2)
2635        .set("y", summary_y - 10)
2636        .set("text-anchor", "middle")
2637        .set("font-size", 18)
2638        .set("font-weight", "bold")
2639        .set("fill", "#2c3e50");
2640
2641    document = document.add(title);
2642
2643    // Calculate summary metrics
2644    let tracked_vars = allocations.iter().filter(|a| a.var_name.is_some()).count();
2645    let avg_size = if !allocations.is_empty() {
2646        allocations.iter().map(|a| a.size).sum::<usize>() / allocations.len()
2647    } else {
2648        0
2649    };
2650
2651    let summary_items = [
2652        format!("Total Active Allocations: {}", stats.active_allocations),
2653        format!(
2654            "Tracked Variables: {} ({:.1}%)",
2655            tracked_vars,
2656            if stats.active_allocations > 0 {
2657                tracked_vars as f64 / stats.active_allocations as f64 * 100.0
2658            } else {
2659                0.0
2660            }
2661        ),
2662        format!("Average Allocation Size: {avg_size} bytes"),
2663        format!(
2664            "Memory Efficiency: {:.1}%",
2665            if stats.total_allocations > 0 {
2666                stats.total_deallocations as f64 / stats.total_allocations as f64 * 100.0
2667            } else {
2668                0.0
2669            }
2670        ),
2671        format!(
2672            "Peak vs Current: {} vs {} bytes",
2673            stats.peak_memory, stats.active_memory
2674        ),
2675    ];
2676
2677    for (i, item) in summary_items.iter().enumerate() {
2678        let summary_text = SvgText::new(item)
2679            .set("x", summary_x + 30)
2680            .set("y", summary_y + 40 + i * 25)
2681            .set("font-size", 13)
2682            .set("font-weight", "500")
2683            .set("fill", "#2c3e50");
2684
2685        document = document.add(summary_text);
2686    }
2687
2688    Ok(document)
2689}
2690
2691/// Add CSS styles for interactive elements
2692
2693/// Add enhanced memory allocation timeline with multiple visualization options
2694pub fn add_enhanced_timeline_dashboard(
2695    mut document: Document,
2696    _stats: &MemoryStats,
2697    allocations: &[AllocationInfo],
2698) -> TrackingResult<Document> {
2699    let chart_x = 50;
2700    let chart_y = 300; // Move down to avoid overlapping with header section
2701    let chart_width = 1700;
2702    let chart_height = 350; // Reduce height to avoid being blocked by modules below
2703
2704    // Chart background
2705    let bg = Rectangle::new()
2706        .set("x", chart_x)
2707        .set("y", chart_y)
2708        .set("width", chart_width)
2709        .set("height", chart_height)
2710        .set("fill", "white")
2711        .set("stroke", "#bdc3c7")
2712        .set("stroke-width", 1)
2713        .set("rx", 8);
2714
2715    document = document.add(bg);
2716
2717    // Chart title
2718    let title = SvgText::new("Memory Allocation Timeline")
2719        .set("x", chart_x + chart_width / 2)
2720        .set("y", chart_y - 15)
2721        .set("text-anchor", "middle")
2722        .set("font-size", 16)
2723        .set("font-weight", "bold")
2724        .set("fill", "#2c3e50");
2725
2726    document = document.add(title);
2727
2728    // Group tracked variables by scope for better visualization
2729    let tracked_vars: Vec<&AllocationInfo> = allocations
2730        .iter()
2731        .filter(|a| a.var_name.is_some())
2732        .collect();
2733
2734    if tracked_vars.is_empty() {
2735        let no_data = SvgText::new("No tracked variables found for timeline visualization")
2736            .set("x", chart_x + chart_width / 2)
2737            .set("y", chart_y + chart_height / 2)
2738            .set("text-anchor", "middle")
2739            .set("font-size", 14)
2740            .set("fill", "#7f8c8d");
2741        document = document.add(no_data);
2742        return Ok(document);
2743    }
2744
2745    // Group variables by scope (extract scope from variable names)
2746    let mut scope_groups: std::collections::HashMap<String, Vec<&AllocationInfo>> =
2747        std::collections::HashMap::new();
2748    for var in &tracked_vars {
2749        let scope_name = extract_scope_name(var.var_name.as_ref().unwrap());
2750        scope_groups
2751            .entry(scope_name)
2752            .or_insert_with(Vec::new)
2753            .push(*var);
2754    }
2755
2756    // Sort scopes for consistent display
2757    let mut sorted_scopes: Vec<_> = scope_groups.into_iter().collect();
2758    sorted_scopes.sort_by(|a, b| a.0.cmp(&b.0));
2759
2760    // Calculate time ranges for tracked variables
2761    let max_time = tracked_vars
2762        .iter()
2763        .map(|a| a.timestamp_alloc)
2764        .max()
2765        .unwrap_or(1000) as f64;
2766    let min_time = tracked_vars
2767        .iter()
2768        .map(|a| a.timestamp_alloc)
2769        .min()
2770        .unwrap_or(0) as f64;
2771    let time_range = (max_time - min_time).max(1.0);
2772
2773    // Plot area dimensions - Gantt chart layout
2774    let plot_x = chart_x + 200; // More space for variable names
2775    let plot_y = chart_y + 50;
2776    let plot_width = chart_width - 350; // Space for names and legend
2777    let plot_height = chart_height - 100;
2778
2779    let row_height = (plot_height as f64 / sorted_scopes.len().max(1) as f64)
2780        .min(40.0)
2781        .max(25.0); // Ensure minimum spacing for scopes
2782
2783    // Time axis (horizontal)
2784    let time_axis = svg::node::element::Line::new()
2785        .set("x1", plot_x)
2786        .set("y1", plot_y + plot_height)
2787        .set("x2", plot_x + plot_width)
2788        .set("y2", plot_y + plot_height)
2789        .set("stroke", "#34495e")
2790        .set("stroke-width", 2);
2791    document = document.add(time_axis);
2792
2793    // Add time labels with better formatting - use relative time units
2794    let time_units = ["0ms", "0.25ms", "0.5ms", "0.75ms", "1ms"];
2795    for i in 0..=4 {
2796        let x = plot_x + (i * plot_width / 4);
2797
2798        let tick = svg::node::element::Line::new()
2799            .set("x1", x)
2800            .set("y1", plot_y + plot_height)
2801            .set("x2", x)
2802            .set("y2", plot_y + plot_height + 5)
2803            .set("stroke", "#34495e")
2804            .set("stroke-width", 1);
2805        document = document.add(tick);
2806
2807        // Better formatted time labels
2808        let time_label = time_units[i];
2809        let label = SvgText::new(time_label)
2810            .set("x", x)
2811            .set("y", plot_y + plot_height + 18)
2812            .set("text-anchor", "middle")
2813            .set("font-size", 10)
2814            .set("font-weight", "500")
2815            .set("fill", "#2c3e50");
2816        document = document.add(label);
2817    }
2818
2819    // X-axis label - clearly indicate horizontal axis meaning
2820    let x_label = SvgText::new("Execution Time (milliseconds)")
2821        .set("x", plot_x + plot_width / 2)
2822        .set("y", plot_y + plot_height + 40)
2823        .set("text-anchor", "middle")
2824        .set("font-size", 12)
2825        .set("font-weight", "bold")
2826        .set("fill", "#2c3e50");
2827    document = document.add(x_label);
2828
2829    // Remove Y-axis label to save space and reduce clutter
2830
2831    // Add Y-axis scale markers for better readability
2832    let scale_markers = [
2833        (16, "16B"),
2834        (256, "256B"),
2835        (1024, "1KB"),
2836        (4096, "4KB"),
2837        (16384, "16KB"),
2838    ];
2839
2840    for (size, label) in scale_markers.iter() {
2841        // Skip size filtering for now - just show all scale markers
2842        {
2843            let log_size = (*size as f64).ln();
2844            let log_min = 1.0_f64.ln();
2845            let log_max = 16384.0_f64.ln();
2846            let log_range = log_max - log_min;
2847
2848            if log_range > 0.0 {
2849                let y_pos = plot_y + plot_height
2850                    - ((log_size - log_min) / log_range * plot_height as f64) as i32;
2851
2852                // Scale marker line
2853                let marker_line = svg::node::element::Line::new()
2854                    .set("x1", plot_x - 5)
2855                    .set("y1", y_pos)
2856                    .set("x2", plot_x + 5)
2857                    .set("y2", y_pos)
2858                    .set("stroke", "#7f8c8d")
2859                    .set("stroke-width", 1);
2860                document = document.add(marker_line);
2861
2862                // Scale marker label
2863                let marker_label = SvgText::new(*label)
2864                    .set("x", plot_x - 15)
2865                    .set("y", y_pos + 3)
2866                    .set("text-anchor", "end")
2867                    .set("font-size", 10)
2868                    .set("fill", "#7f8c8d");
2869                document = document.add(marker_label);
2870            }
2871        }
2872    }
2873
2874    // Add X-axis time scale markers with better formatting
2875    let time_markers = 5;
2876    for i in 0..=time_markers {
2877        let time_point = min_time + (time_range * i as f64 / time_markers as f64);
2878        let x_pos = plot_x + (i * plot_width / time_markers);
2879
2880        // Time marker line
2881        let time_marker_line = svg::node::element::Line::new()
2882            .set("x1", x_pos)
2883            .set("y1", plot_y + plot_height - 5)
2884            .set("x2", x_pos)
2885            .set("y2", plot_y + plot_height + 5)
2886            .set("stroke", "#7f8c8d")
2887            .set("stroke-width", 1);
2888        document = document.add(time_marker_line);
2889
2890        // Format time more readably
2891        let formatted_time = if time_point < 1000.0 {
2892            format!("{:.1}ms", time_point)
2893        } else if time_point < 60000.0 {
2894            format!("{:.2}s", time_point / 1000.0)
2895        } else {
2896            format!("{:.1}m", time_point / 60000.0)
2897        };
2898
2899        let time_label = SvgText::new(formatted_time)
2900            .set("x", x_pos)
2901            .set("y", plot_y + plot_height + 20)
2902            .set("text-anchor", "middle")
2903            .set("font-size", 10)
2904            .set("font-weight", "500")
2905            .set("fill", "#2c3e50");
2906        document = document.add(time_label);
2907    }
2908
2909    // Categorize allocations and get colors
2910    let categorized = categorize_allocations(allocations);
2911    let mut category_colors: HashMap<String, String> = HashMap::new();
2912    for category in &categorized {
2913        category_colors.insert(category.name.clone(), category.color.clone());
2914    }
2915
2916    // Calculate P95 threshold for larger dots
2917    let mut sizes: Vec<usize> = allocations
2918        .iter()
2919        .map(|a| a.size)
2920        .filter(|&s| s > 0)
2921        .collect();
2922    sizes.sort_unstable();
2923    let _p95_threshold = if !sizes.is_empty() {
2924        let p95_index = (sizes.len() as f64 * 0.95) as usize;
2925        sizes[p95_index.min(sizes.len() - 1)]
2926    } else {
2927        0
2928    };
2929
2930    // Draw scope-based Gantt chart - move scopes up slightly
2931    for (scope_index, (scope_name, scope_vars)) in sorted_scopes.iter().enumerate() {
2932        let scope_y = plot_y + 10 + (scope_index as f64 * row_height) as i32; // Moved up by 10px
2933
2934        // Get scope color (different color for each scope)
2935        let scope_colors = [
2936            "#3498db", "#e74c3c", "#2ecc71", "#f39c12", "#9b59b6", "#1abc9c",
2937        ];
2938        let scope_color = scope_colors[scope_index % scope_colors.len()];
2939
2940        // Draw scope background bar (full timeline)
2941        let scope_bg = Rectangle::new()
2942            .set("x", plot_x)
2943            .set("y", scope_y)
2944            .set("width", plot_width)
2945            .set("height", (row_height * 0.8) as i32)
2946            .set("fill", scope_color)
2947            .set("opacity", 0.2)
2948            .set("rx", 5);
2949        document = document.add(scope_bg);
2950
2951        // Scope name label
2952        let scope_label = SvgText::new(format!("Scope: {}", scope_name))
2953            .set("x", plot_x - 15)
2954            .set("y", scope_y + (row_height * 0.4) as i32)
2955            .set("text-anchor", "end")
2956            .set("font-size", 11)
2957            .set("font-weight", "bold")
2958            .set("fill", scope_color);
2959        document = document.add(scope_label);
2960
2961        // Draw variables within this scope
2962        for (var_index, alloc) in scope_vars.iter().enumerate() {
2963            let var_name = alloc.var_name.as_ref().unwrap();
2964
2965            // Calculate variable bar position
2966            let start_x = plot_x as i32
2967                + ((alloc.timestamp_alloc as f64 - min_time) / time_range * plot_width as f64)
2968                    as i32;
2969            let var_y = scope_y + 5 + (var_index as f64 * 8.0) as i32; // Stack variables within scope
2970
2971            // Get variable type color
2972            let (_, category) = if let Some(type_name) = &alloc.type_name {
2973                simplify_type_name(type_name)
2974            } else {
2975                (
2976                    "Unknown".to_string(),
2977                    "Runtime/System Allocation".to_string(),
2978                )
2979            };
2980            let var_color = get_category_color(&category);
2981
2982            // Variable bar width based on memory size
2983            let max_size = scope_vars.iter().map(|a| a.size).max().unwrap_or(1024) as f64;
2984            let min_size = scope_vars
2985                .iter()
2986                .map(|a| a.size)
2987                .filter(|&s| s > 0)
2988                .min()
2989                .unwrap_or(1) as f64;
2990            let size_ratio = if max_size > min_size {
2991                ((alloc.size as f64).ln() - min_size.ln()) / (max_size.ln() - min_size.ln())
2992            } else {
2993                0.5
2994            };
2995            let bar_width = ((plot_width as f64 * 0.4 * size_ratio) + 30.0) as i32;
2996
2997            // Draw variable bar
2998            let plot_x_i32 = plot_x as i32;
2999            let plot_width_i32 = plot_width as i32;
3000            let var_bar = Rectangle::new()
3001                .set("x", start_x.max(plot_x_i32))
3002                .set("y", var_y)
3003                .set(
3004                    "width",
3005                    bar_width.min(plot_x_i32 + plot_width_i32 - start_x.max(plot_x_i32)),
3006                )
3007                .set("height", 6)
3008                .set("fill", var_color)
3009                .set("stroke", "#ffffff")
3010                .set("stroke-width", 1)
3011                .set("rx", 2)
3012                .set("opacity", 0.9);
3013            document = document.add(var_bar);
3014
3015            // Variable info label
3016            let var_info = format!("{}: {}", var_name, format_bytes(alloc.size));
3017            let var_label = SvgText::new(if var_info.len() > 25 {
3018                format!("{}...", &var_info[..22])
3019            } else {
3020                var_info
3021            })
3022            .set("x", start_x.max(plot_x_i32) + 5)
3023            .set("y", var_y + 4)
3024            .set("font-size", 7)
3025            .set("font-weight", "500")
3026            .set("fill", "#2c3e50");
3027            document = document.add(var_label);
3028        }
3029    }
3030
3031    // Move legend to top-left corner with proportional scaling
3032    let legend_x = chart_x + 20;
3033    let legend_y = chart_y + 20;
3034    let legend_width = 200; // Scaled proportionally
3035    let legend_height = 120; // Scaled proportionally
3036
3037    // Legend background box - completely transparent with no border
3038    let legend_bg = Rectangle::new()
3039        .set("x", legend_x - 10)
3040        .set("y", legend_y - 15)
3041        .set("width", legend_width)
3042        .set("height", legend_height)
3043        .set("fill", "rgba(255,255,255,0.0)") // Transparent background
3044        .set("stroke", "none") // No border
3045        .set("stroke-width", 0)
3046        .set("rx", 5);
3047    document = document.add(legend_bg);
3048
3049    let legend_title = SvgText::new("Type Categories")
3050        .set("x", legend_x)
3051        .set("y", legend_y)
3052        .set("font-size", 10) // Smaller title
3053        .set("font-weight", "bold")
3054        .set("fill", "#2c3e50");
3055    document = document.add(legend_title);
3056
3057    // Add compact system allocation info
3058    let unknown_count = allocations
3059        .iter()
3060        .filter(|a| {
3061            if let Some(type_name) = &a.type_name {
3062                let (_, category) = simplify_type_name(type_name);
3063                category == "Unknown"
3064            } else {
3065                true // No type_name at all
3066            }
3067        })
3068        .count();
3069
3070    let unknown_legend_y = legend_y + 15;
3071
3072    // System allocation color square - smaller
3073    let unknown_color_square = Rectangle::new()
3074        .set("x", legend_x)
3075        .set("y", unknown_legend_y - 6)
3076        .set("width", 8)
3077        .set("height", 8)
3078        .set("fill", "#95a5a6");
3079    document = document.add(unknown_color_square);
3080
3081    // Compact system allocation label
3082    let unknown_label = if unknown_count > 0 {
3083        format!("System ({} allocs)", unknown_count)
3084    } else {
3085        "No System Allocs".to_string()
3086    };
3087
3088    let unknown_text = SvgText::new(unknown_label)
3089        .set("x", legend_x + 12)
3090        .set("y", unknown_legend_y - 1)
3091        .set("font-size", 8)
3092        .set("fill", "#2c3e50");
3093    document = document.add(unknown_text);
3094
3095    for (i, category) in categorized.iter().take(4).enumerate() {
3096        // Reduce to 4 items for compact legend
3097        let legend_item_y = legend_y + 30 + (i as i32) * 15; // Reduce spacing
3098
3099        // Color square - smaller size
3100        let color_square = Rectangle::new()
3101            .set("x", legend_x)
3102            .set("y", legend_item_y - 6)
3103            .set("width", 8)
3104            .set("height", 8)
3105            .set("fill", category.color.as_str());
3106        document = document.add(color_square);
3107
3108        // Category name - more compact
3109        let category_name = if category.name.len() > 15 {
3110            format!("{}...", &category.name[..12])
3111        } else {
3112            category.name.clone()
3113        };
3114
3115        let category_text = SvgText::new(category_name)
3116            .set("x", legend_x + 12)
3117            .set("y", legend_item_y - 1)
3118            .set("font-size", 8) // Smaller font
3119            .set("fill", "#2c3e50");
3120        document = document.add(category_text);
3121    }
3122
3123    Ok(document)
3124}
3125
3126/// Add memory heatmap visualization (placeholder for now)
3127pub fn add_memory_heatmap(
3128    document: Document,
3129    _allocations: &[AllocationInfo],
3130) -> TrackingResult<Document> {
3131    // For now, just return the document as-is
3132    // This will be replaced with actual heatmap implementation later
3133    Ok(document)
3134}
3135
3136/// Extract scope name from variable name - use actual program scopes
3137fn extract_scope_name(var_name: &str) -> String {
3138    // Extract meaningful scope information based on actual program structure
3139    if var_name.contains("global") {
3140        "Global Scope".to_string()
3141    } else if var_name.contains("static") {
3142        "Static Scope".to_string()
3143    } else if var_name.contains("boxed") {
3144        "Boxed Allocations".to_string()
3145    } else if var_name.contains("shared") || var_name.contains("arc") || var_name.contains("rc") {
3146        "Shared References".to_string()
3147    } else if var_name.contains("node") {
3148        "Graph Nodes".to_string()
3149    } else if var_name.contains("mutable") {
3150        "Mutable Data".to_string()
3151    } else if var_name.contains("_") {
3152        // Use prefix before first underscore as scope
3153        let prefix = var_name.split('_').next().unwrap_or("Unknown");
3154        format!("{} Scope", prefix.to_ascii_uppercase())
3155    } else {
3156        // Group by variable type/pattern
3157        if var_name.len() > 6 {
3158            format!("{} Scope", &var_name[..6].to_ascii_uppercase())
3159        } else {
3160            "Main Scope".to_string()
3161        }
3162    }
3163}