lk-inside 0.3.1

A terminal user interface (TUI) application for interactive data analysis.
Documentation
//! 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(())

            }
}