1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
//! Reusable UI widget for displaying a preview of `polars::DataFrame`s.
use ratatui::{
layout::{Constraint, Rect},
style::{Style, Color}, // REMOVED Modifier, Style
widgets::{Block, Borders, Cell, Row, Table},
Frame,
};
use polars::prelude::DataFrame; // Removed DataType as it's no longer used
use anyhow::Result;
use crate::ui::theme; // ADD THIS LINE
/// A widget for displaying a paginated preview of a `polars::DataFrame`.
pub struct DataPreviewWidget<'a> {
/// The DataFrame to display.
df: &'a DataFrame,
/// The number of rows to display per page.
page_size: usize,
}
impl<'a> DataPreviewWidget<'a> {
/// Creates a new `DataPreviewWidget`.
///
/// # Arguments
///
/// * `df` - A reference to the DataFrame to be displayed.
/// * `page_size` - The maximum number of rows to show per page.
pub fn new(df: &'a DataFrame, page_size: usize) -> Self {
DataPreviewWidget {
df,
page_size,
}
}
/// Renders the data preview table to the specified area of the terminal frame.
///
/// The table displays a paginated view of the DataFrame, including headers and rows.
/// Column widths are dynamically calculated based on the content of the current page.
///
/// # Arguments
///
/// * `self` - The `DataPreviewWidget` instance.
/// * `f` - A mutable reference to the `Frame` to draw on.
/// * `area` - The `Rect` representing the area to draw the widget in.
/// * `current_page` - The 0-based index of the current page to display.
///
/// # Returns
///
/// `Ok(())` if rendering is successful, otherwise an `anyhow::Result` error.
pub fn render(&self, f: &mut Frame, area: Rect, current_page: usize, highlight_column: Option<&str>, highlight_color: Color, clustering_column: Option<&str>) -> Result<()> {
let mut header_cells = Vec::new();
let mut column_datatypes = Vec::new();
for col_name in self.df.get_column_names() {
let series = self.df.column(col_name)?;
let dtype = series.dtype();
column_datatypes.push(dtype.clone()); // Store dtype
// Removed the icon
header_cells.push(Cell::from(col_name.to_string()));
}
let header = Row::new(header_cells)
.style(theme::ACCENT_BOLD_STYLE) // REPLACED
.height(1);
let num_rows = self.df.height();
let start_index = current_page * self.page_size;
let end_index = (start_index + self.page_size).min(num_rows);
let is_highlight_series = if let Some(col_name) = highlight_column {
self.df.column(col_name).ok().and_then(|s| s.bool().ok())
} else {
None
};
let cluster_id_series = if let Some(col_name) = clustering_column {
self.df.column(col_name).ok().and_then(|s| s.u32().ok()) // Assuming cluster_id is u32
} else {
None
};
// Define a set of colors for clusters
let cluster_colors_ref = &theme::CHART_COLORS; // Use theme::CHART_COLORS
let mut page_data: Vec<Vec<String>> = Vec::new();
if start_index < num_rows {
for i in start_index..end_index {
let mut row_data: Vec<String> = Vec::new();
for col_name in self.df.get_column_names() {
let series = self.df.column(col_name)?;
let value = series.get(i).ok().map_or("".to_string(), |v| v.to_string());
row_data.push(value);
}
page_data.push(row_data);
}
}
let mut widths: Vec<u16> = self.df.get_column_names()
.iter()
.zip(column_datatypes.iter())
.map(|(name, _dtype)| name.len() as u16)
.collect();
for row_data in &page_data {
for (i, cell_data) in row_data.iter().enumerate() {
let cell_width = cell_data.len() as u16;
if cell_width > widths[i] {
widths[i] = cell_width;
}
}
}
let constraints: Vec<Constraint> = widths.into_iter().map(Constraint::Length).collect();
let rows: Vec<Row> = page_data
.into_iter()
.enumerate()
.map(|(i, row_data)| {
let cells = row_data.into_iter().map(Cell::from);
let mut row_style = if i % 2 == 0 {
Style::new().bg(theme::TEXT_COLOR) // REPLACED
} else {
Style::new().bg(Color::Black) // REPLACED
};
// Apply anomaly highlighting first (overrides base row style)
if let Some(ref series) = is_highlight_series {
let global_row_index = start_index + i;
if global_row_index < series.len() && series.get(global_row_index).unwrap_or(false) {
row_style = row_style.bg(highlight_color);
}
}
// Apply clustering highlighting (overrides anomaly highlighting if present)
if let Some(ref series) = cluster_id_series {
let global_row_index = start_index + i;
if global_row_index < series.len() {
if let Some(cluster_id) = series.get(global_row_index) {
let color_index = cluster_id as usize % cluster_colors_ref.len();
row_style = row_style.bg(cluster_colors_ref[color_index]);
}
}
}
Row::new(cells).style(row_style)
})
.collect();
let table = Table::new(rows, &constraints)
.header(header)
.block(Block::default().borders(Borders::ALL).title("Data Preview").border_style(theme::BORDER_STYLE)) // REPLACED
.column_spacing(1);
f.render_widget(table, area);
Ok(())
}
}