use lazy_static::lazy_static;
use std::collections::HashMap;
use std::fmt::Write;
use std::sync::RwLock;
use crate::core::error::{Error, Result};
use crate::dataframe::base::DataFrame;
use crate::series::base::Series;
use crate::{read_lock_safe, write_lock_safe};
#[derive(Debug, Clone)]
pub struct JupyterConfig {
pub max_rows: usize,
pub max_columns: usize,
pub interactive: bool,
pub precision: usize,
pub color_scheme: JupyterColorScheme,
pub table_style: TableStyle,
pub syntax_highlighting: bool,
}
impl Default for JupyterConfig {
fn default() -> Self {
Self {
max_rows: 100,
max_columns: 20,
interactive: true,
precision: 6,
color_scheme: JupyterColorScheme::Default,
table_style: TableStyle::default(),
syntax_highlighting: true,
}
}
}
#[derive(Debug, Clone)]
pub enum JupyterColorScheme {
Default,
Dark,
Light,
Custom {
header_bg: String,
header_text: String,
row_bg: String,
alt_row_bg: String,
text_color: String,
border_color: String,
},
}
impl JupyterColorScheme {
pub fn to_css(&self) -> String {
match self {
JupyterColorScheme::Default => r#"
.pandrs-table { background-color: #ffffff; color: #333333; }
.pandrs-header { background-color: #f8f9fa; color: #495057; font-weight: bold; }
.pandrs-row { background-color: #ffffff; }
.pandrs-row:nth-child(even) { background-color: #f8f9fa; }
.pandrs-border { border-color: #dee2e6; }
"#
.to_string(),
JupyterColorScheme::Dark => r#"
.pandrs-table { background-color: #2d3748; color: #e2e8f0; }
.pandrs-header { background-color: #4a5568; color: #f7fafc; font-weight: bold; }
.pandrs-row { background-color: #2d3748; }
.pandrs-row:nth-child(even) { background-color: #4a5568; }
.pandrs-border { border-color: #718096; }
"#
.to_string(),
JupyterColorScheme::Light => r#"
.pandrs-table { background-color: #fefefe; color: #2d3748; }
.pandrs-header { background-color: #edf2f7; color: #2d3748; font-weight: bold; }
.pandrs-row { background-color: #fefefe; }
.pandrs-row:nth-child(even) { background-color: #f7fafc; }
.pandrs-border { border-color: #cbd5e0; }
"#
.to_string(),
JupyterColorScheme::Custom {
header_bg,
header_text,
row_bg,
alt_row_bg,
text_color,
border_color,
} => {
format!(
r#"
.pandrs-table {{ background-color: {}; color: {}; }}
.pandrs-header {{ background-color: {}; color: {}; font-weight: bold; }}
.pandrs-row {{ background-color: {}; }}
.pandrs-row:nth-child(even) {{ background-color: {}; }}
.pandrs-border {{ border-color: {}; }}
"#,
row_bg, text_color, header_bg, header_text, row_bg, alt_row_bg, border_color
)
}
}
}
}
#[derive(Debug, Clone)]
pub struct TableStyle {
pub show_borders: bool,
pub show_index: bool,
pub show_dtypes: bool,
pub show_summary: bool,
pub width: TableWidth,
pub cell_padding: String,
pub font_family: String,
pub font_size: String,
}
impl Default for TableStyle {
fn default() -> Self {
Self {
show_borders: true,
show_index: true,
show_dtypes: false,
show_summary: false,
width: TableWidth::Auto,
cell_padding: "8px 12px".to_string(),
font_family: "'Monaco', 'Menlo', 'Ubuntu Mono', monospace".to_string(),
font_size: "13px".to_string(),
}
}
}
#[derive(Debug, Clone)]
pub enum TableWidth {
Auto,
Fixed(u32),
Percentage(u32),
Full,
}
impl TableWidth {
fn to_css(&self) -> String {
match self {
TableWidth::Auto => "width: auto;".to_string(),
TableWidth::Fixed(px) => format!("width: {}px;", px),
TableWidth::Percentage(pct) => format!("width: {}%;", pct),
TableWidth::Full => "width: 100%;".to_string(),
}
}
}
pub trait JupyterDisplay {
fn to_jupyter_html(&self, config: &JupyterConfig) -> Result<String>;
fn to_interactive_widget(&self, config: &JupyterConfig) -> Result<String>;
fn to_summary_html(&self, config: &JupyterConfig) -> Result<String>;
fn to_data_explorer(&self, config: &JupyterConfig) -> Result<String>;
}
impl JupyterDisplay for DataFrame {
fn to_jupyter_html(&self, config: &JupyterConfig) -> Result<String> {
let mut html = String::new();
html.push_str(&format!(
r#"
<style>
.pandrs-container {{
margin: 10px 0;
font-family: {};
font-size: {};
}}
.pandrs-table {{
border-collapse: collapse;
margin: 10px 0;
{}
}}
.pandrs-table th, .pandrs-table td {{
padding: {};
text-align: left;
border: {};
}}
{}
.pandrs-info {{
margin: 5px 0;
font-size: 12px;
color: #666;
}}
.pandrs-dtype {{
font-style: italic;
color: #888;
font-size: 11px;
}}
</style>
"#,
config.table_style.font_family,
config.table_style.font_size,
config.table_style.width.to_css(),
config.table_style.cell_padding,
if config.table_style.show_borders {
"1px solid var(--pandrs-border-color, #ddd)"
} else {
"none"
},
config.color_scheme.to_css()
));
html.push_str(r#"<div class="pandrs-container">"#);
html.push_str(&format!(
r#"<div class="pandrs-info">DataFrame: {} rows × {} columns</div>"#,
self.row_count(),
self.column_names().len()
));
html.push_str(r#"<table class="pandrs-table">"#);
html.push_str("<thead><tr class='pandrs-header'>");
if config.table_style.show_index {
html.push_str("<th></th>"); }
let columns = self.column_names();
let visible_columns = if columns.len() > config.max_columns {
&columns[..config.max_columns]
} else {
&columns
};
for col_name in visible_columns {
html.push_str(&format!("<th>{}</th>", escape_html(col_name)));
}
if columns.len() > config.max_columns {
html.push_str("<th>...</th>");
}
html.push_str("</tr>");
if config.table_style.show_dtypes {
html.push_str("<tr class='pandrs-header'>");
if config.table_style.show_index {
html.push_str("<td class='pandrs-dtype'></td>");
}
for col_name in visible_columns {
let dtype = self.infer_column_type(col_name);
html.push_str(&format!("<td class='pandrs-dtype'>{}</td>", dtype));
}
if columns.len() > config.max_columns {
html.push_str("<td class='pandrs-dtype'>...</td>");
}
html.push_str("</tr>");
}
html.push_str("</thead>");
html.push_str("<tbody>");
let row_count = self.row_count();
let visible_rows = if row_count > config.max_rows {
config.max_rows / 2
} else {
row_count
};
for i in 0..visible_rows.min(row_count) {
html.push_str(&format!("<tr class='pandrs-row'>"));
if config.table_style.show_index {
html.push_str(&format!("<td><strong>{}</strong></td>", i));
}
for col_name in visible_columns {
let value = self
.get_value_at(i, col_name)
.unwrap_or_else(|_| "NaN".to_string());
let formatted_value = format_value(&value, config.precision);
html.push_str(&format!("<td>{}</td>", escape_html(&formatted_value)));
}
if columns.len() > config.max_columns {
html.push_str("<td>...</td>");
}
html.push_str("</tr>");
}
if row_count > config.max_rows {
html.push_str("<tr class='pandrs-row'>");
if config.table_style.show_index {
html.push_str("<td>...</td>");
}
for _ in 0..visible_columns.len() {
html.push_str("<td>...</td>");
}
if columns.len() > config.max_columns {
html.push_str("<td>...</td>");
}
html.push_str("</tr>");
let remaining_rows = config.max_rows - visible_rows;
let start_idx = row_count - remaining_rows;
for i in start_idx..row_count {
html.push_str(&format!("<tr class='pandrs-row'>"));
if config.table_style.show_index {
html.push_str(&format!("<td><strong>{}</strong></td>", i));
}
for col_name in visible_columns {
let value = self
.get_value_at(i, col_name)
.unwrap_or_else(|_| "NaN".to_string());
let formatted_value = format_value(&value, config.precision);
html.push_str(&format!("<td>{}</td>", escape_html(&formatted_value)));
}
if columns.len() > config.max_columns {
html.push_str("<td>...</td>");
}
html.push_str("</tr>");
}
}
html.push_str("</tbody>");
html.push_str("</table>");
if config.table_style.show_summary {
html.push_str(&format!(
r#"<div class="pandrs-info">
Memory usage: ~{} KB |
Columns: {} |
Data types: {}
</div>"#,
estimate_memory_usage(self),
columns.len(),
self.get_column_types().join(", ")
));
}
html.push_str("</div>");
Ok(html)
}
fn to_interactive_widget(&self, config: &JupyterConfig) -> Result<String> {
let mut html = String::new();
html.push_str(&format!(
r#"
<div id="pandrs-widget-{}" class="pandrs-interactive-widget">
<style>
.pandrs-interactive-widget {{
border: 1px solid #ddd;
border-radius: 5px;
padding: 10px;
margin: 10px 0;
font-family: {};
}}
.pandrs-controls {{
margin-bottom: 10px;
padding: 5px;
background-color: #f8f9fa;
border-radius: 3px;
}}
.pandrs-controls button {{
margin: 2px;
padding: 5px 10px;
border: 1px solid #ccc;
background-color: #fff;
cursor: pointer;
border-radius: 3px;
}}
.pandrs-controls button:hover {{
background-color: #e9ecef;
}}
.pandrs-search {{
padding: 5px;
border: 1px solid #ccc;
border-radius: 3px;
margin: 0 5px;
}}
</style>
<div class="pandrs-controls">
<button onclick="pandrs_show_info('{}')">📊 Info</button>
<button onclick="pandrs_show_describe('{}')">📈 Describe</button>
<button onclick="pandrs_show_plot('{}')">📊 Plot</button>
<input type="text" class="pandrs-search" placeholder="Search columns..."
onkeyup="pandrs_filter_columns('{}', this.value)">
</div>
<div id="pandrs-content-{}">
{}
</div>
</div>
<script>
function pandrs_show_info(widget_id) {{
document.getElementById('pandrs-content-' + widget_id).innerHTML = `
<h4>DataFrame Information</h4>
<ul>
<li>Rows: {}</li>
<li>Columns: {}</li>
<li>Memory usage: ~{} KB</li>
<li>Column types: {}</li>
</ul>
`;
}}
function pandrs_show_describe(widget_id) {{
document.getElementById('pandrs-content-' + widget_id).innerHTML = `
<h4>Statistical Description</h4>
<p>Summary statistics would be displayed here...</p>
`;
}}
function pandrs_show_plot(widget_id) {{
document.getElementById('pandrs-content-' + widget_id).innerHTML = `
<h4>Quick Plot</h4>
<p>Interactive plotting widget would be displayed here...</p>
`;
}}
function pandrs_filter_columns(widget_id, filter) {{
// Column filtering functionality would be implemented here
console.log('Filtering columns by:', filter);
}}
</script>
"#,
self.row_count(), config.table_style.font_family,
self.row_count(), self.row_count(), self.row_count(), self.row_count(), self.row_count(), self.to_jupyter_html(config)?,
self.row_count(),
self.column_names().len(),
estimate_memory_usage(self),
self.get_column_types().join(", ")
));
Ok(html)
}
fn to_summary_html(&self, config: &JupyterConfig) -> Result<String> {
let mut html = String::new();
html.push_str(&format!(
r#"
<div class="pandrs-summary" style="
font-family: {};
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
margin: 10px 0;
background-color: #f8f9fa;
">
<h3 style="margin-top: 0;">DataFrame Summary</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
<div>
<h4>Basic Information</h4>
<ul style="list-style: none; padding: 0;">
<li><strong>Shape:</strong> {} rows × {} columns</li>
<li><strong>Memory usage:</strong> ~{} KB</li>
<li><strong>Non-null values:</strong> {}</li>
<li><strong>Null values:</strong> {}</li>
</ul>
</div>
<div>
<h4>Column Information</h4>
<ul style="list-style: none; padding: 0; max-height: 200px; overflow-y: auto;">
"#,
config.table_style.font_family,
self.row_count(),
self.column_names().len(),
estimate_memory_usage(self),
estimate_non_null_count(self),
estimate_null_count(self)
));
for (i, col_name) in self.column_names().iter().enumerate() {
let col_type = self.infer_column_type(col_name);
html.push_str(&format!(
"<li><strong>{}:</strong> {} <span style='color: #666; font-size: 0.9em'>({})</span></li>",
escape_html(col_name),
col_type,
format!("col {}", i)
));
}
html.push_str(
r#"
</ul>
</div>
</div>
</div>
"#,
);
Ok(html)
}
fn to_data_explorer(&self, config: &JupyterConfig) -> Result<String> {
let widget_id = format!("explorer_{}", self.row_count());
let mut html = String::new();
html.push_str(&format!(r#"
<div id="pandrs-explorer-{}" class="pandrs-data-explorer">
<style>
.pandrs-data-explorer {{
border: 1px solid #ddd;
border-radius: 8px;
padding: 0;
margin: 15px 0;
font-family: {};
background-color: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}}
.pandrs-explorer-header {{
background-color: #007bff;
color: white;
padding: 12px 15px;
border-radius: 8px 8px 0 0;
font-weight: bold;
}}
.pandrs-explorer-tabs {{
display: flex;
background-color: #f8f9fa;
border-bottom: 1px solid #ddd;
}}
.pandrs-explorer-tab {{
padding: 10px 20px;
cursor: pointer;
border: none;
background: none;
border-bottom: 3px solid transparent;
font-weight: 500;
}}
.pandrs-explorer-tab.active {{
background-color: white;
border-bottom-color: #007bff;
color: #007bff;
}}
.pandrs-explorer-content {{
padding: 15px;
min-height: 300px;
}}
</style>
<div class="pandrs-explorer-header">
🔍 DataFrame Explorer - {} rows × {} columns
</div>
<div class="pandrs-explorer-tabs">
<button class="pandrs-explorer-tab active" onclick="pandrs_show_tab('{}', 'data')">
📊 Data
</button>
<button class="pandrs-explorer-tab" onclick="pandrs_show_tab('{}', 'info')">
ℹ️ Info
</button>
<button class="pandrs-explorer-tab" onclick="pandrs_show_tab('{}', 'stats')">
📈 Statistics
</button>
<button class="pandrs-explorer-tab" onclick="pandrs_show_tab('{}', 'viz')">
📊 Visualize
</button>
</div>
<div class="pandrs-explorer-content">
<div id="pandrs-tab-data-{}" class="pandrs-tab-content">
{}
</div>
<div id="pandrs-tab-info-{}" class="pandrs-tab-content" style="display: none;">
{}
</div>
<div id="pandrs-tab-stats-{}" class="pandrs-tab-content" style="display: none;">
<h4>Statistical Summary</h4>
<p>Descriptive statistics would be computed and displayed here...</p>
</div>
<div id="pandrs-tab-viz-{}" class="pandrs-tab-content" style="display: none;">
<h4>Quick Visualizations</h4>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px;">
<button onclick="alert('Histogram plot would be generated')">📊 Histogram</button>
<button onclick="alert('Scatter plot would be generated')">🔸 Scatter Plot</button>
<button onclick="alert('Box plot would be generated')">📦 Box Plot</button>
<button onclick="alert('Correlation matrix would be generated')">🔗 Correlation</button>
</div>
</div>
</div>
</div>
<script>
function pandrs_show_tab(explorer_id, tab_name) {{
// Hide all tabs
['data', 'info', 'stats', 'viz'].forEach(function(name) {{
var element = document.getElementById('pandrs-tab-' + name + '-' + explorer_id);
if (element) element.style.display = 'none';
}});
// Remove active class from all tab buttons
var tabs = document.querySelectorAll('#pandrs-explorer-' + explorer_id + ' .pandrs-explorer-tab');
tabs.forEach(function(tab) {{
tab.classList.remove('active');
}});
// Show selected tab
var selectedTab = document.getElementById('pandrs-tab-' + tab_name + '-' + explorer_id);
if (selectedTab) selectedTab.style.display = 'block';
// Add active class to clicked button
event.target.classList.add('active');
}}
</script>
"#,
widget_id,
config.table_style.font_family,
self.row_count(),
self.column_names().len(),
widget_id, widget_id, widget_id, widget_id, widget_id, self.to_jupyter_html(config)?,
widget_id, self.to_summary_html(config)?,
widget_id, widget_id ));
Ok(html)
}
}
impl<T> JupyterDisplay for Series<T>
where
T: Clone + std::fmt::Display + std::fmt::Debug,
{
fn to_jupyter_html(&self, config: &JupyterConfig) -> Result<String> {
let mut html = String::new();
html.push_str(&format!(
r#"
<style>
.pandrs-series-container {{
margin: 10px 0;
font-family: {};
font-size: {};
}}
.pandrs-series-table {{
border-collapse: collapse;
margin: 10px 0;
{}
}}
.pandrs-series-table th, .pandrs-series-table td {{
padding: {};
text-align: left;
border: {};
}}
{}
.pandrs-series-info {{
margin: 5px 0;
font-size: 12px;
color: #666;
}}
</style>
"#,
config.table_style.font_family,
config.table_style.font_size,
config.table_style.width.to_css(),
config.table_style.cell_padding,
if config.table_style.show_borders {
"1px solid var(--pandrs-border-color, #ddd)"
} else {
"none"
},
config.color_scheme.to_css()
));
html.push_str(r#"<div class="pandrs-series-container">"#);
let series_name = self.name().map_or("Unnamed", |s| s.as_str());
html.push_str(&format!(
r#"<div class="pandrs-series-info">Series '{}': {} values</div>"#,
escape_html(series_name),
self.len()
));
html.push_str(r#"<table class="pandrs-series-table">"#);
html.push_str("<thead><tr class='pandrs-header'>");
if config.table_style.show_index {
html.push_str("<th>Index</th>");
}
html.push_str(&format!("<th>{}</th>", escape_html(series_name)));
html.push_str("</tr></thead>");
html.push_str("<tbody>");
let values = self.values();
let visible_count = if values.len() > config.max_rows {
config.max_rows / 2
} else {
values.len()
};
for (i, value) in values.iter().enumerate().take(visible_count) {
html.push_str(&format!("<tr class='pandrs-row'>"));
if config.table_style.show_index {
html.push_str(&format!("<td><strong>{}</strong></td>", i));
}
let formatted_value = format!("{}", value);
html.push_str(&format!("<td>{}</td>", escape_html(&formatted_value)));
html.push_str("</tr>");
}
if values.len() > config.max_rows {
html.push_str("<tr class='pandrs-row'>");
if config.table_style.show_index {
html.push_str("<td>...</td>");
}
html.push_str("<td>...</td>");
html.push_str("</tr>");
let remaining_count = config.max_rows - visible_count;
let start_idx = values.len() - remaining_count;
for (i, value) in values.iter().enumerate().skip(start_idx) {
html.push_str(&format!("<tr class='pandrs-row'>"));
if config.table_style.show_index {
html.push_str(&format!("<td><strong>{}</strong></td>", i));
}
let formatted_value = format!("{}", value);
html.push_str(&format!("<td>{}</td>", escape_html(&formatted_value)));
html.push_str("</tr>");
}
}
html.push_str("</tbody>");
html.push_str("</table>");
html.push_str("</div>");
Ok(html)
}
fn to_interactive_widget(&self, config: &JupyterConfig) -> Result<String> {
let mut html = String::new();
let series_name = self.name().map_or("Unnamed", |s| s.as_str());
html.push_str(&format!(
r#"
<div class="pandrs-series-widget">
<div style="margin-bottom: 10px; font-weight: bold;">
Series '{}' Interactive Widget
</div>
{}
</div>
"#,
escape_html(series_name),
self.to_jupyter_html(config)?
));
Ok(html)
}
fn to_summary_html(&self, config: &JupyterConfig) -> Result<String> {
let series_name = self.name().map_or("Unnamed", |s| s.as_str());
Ok(format!(
r#"
<div class="pandrs-series-summary" style="
font-family: {};
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
margin: 10px 0;
background-color: #f8f9fa;
">
<h3 style="margin-top: 0;">Series '{}' Summary</h3>
<ul style="list-style: none; padding: 0;">
<li><strong>Length:</strong> {} values</li>
<li><strong>Non-null values:</strong> {}</li>
<li><strong>Data type:</strong> {}</li>
</ul>
</div>
"#,
config.table_style.font_family,
escape_html(series_name),
self.len(),
self.len(), std::any::type_name::<T>()
))
}
fn to_data_explorer(&self, config: &JupyterConfig) -> Result<String> {
self.to_summary_html(config)
}
}
pub struct JupyterMagics;
impl JupyterMagics {
pub fn register_magics() -> String {
r#"
# PandRS Magic Commands for Jupyter
from IPython.core.magic import Magics, magics_class, line_magic, cell_magic
from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring
@magics_class
class PandRSMagics(Magics):
@line_magic
def pandrs_info(self, line):
"""Display PandRS version and configuration info"""
return "PandRS 0.3.0 - High-performance DataFrame library for Rust"
@line_magic
def pandrs_config(self, line):
"""Configure PandRS display settings for Jupyter"""
# Configuration management would be implemented here
return "PandRS configuration updated"
@cell_magic
def pandrs_sql(self, line, cell):
"""Execute SQL-like queries on DataFrames"""
# SQL query execution would be implemented here
return f"SQL query executed: {cell}"
# Register the magic class
ip = get_ipython()
ip.register_magic_functions(PandRSMagics)
"#
.to_string()
}
}
fn escape_html(input: &str) -> String {
input
.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
.replace('\'', "'")
}
fn format_value(value: &str, precision: usize) -> String {
if let Ok(f) = value.parse::<f64>() {
if f.fract() == 0.0 && f.abs() < 1e10 {
format!("{:.0}", f)
} else {
format!("{:.prec$}", f, prec = precision)
}
} else {
value.to_string()
}
}
fn estimate_memory_usage(df: &DataFrame) -> u64 {
let cell_count = df.row_count() * df.column_names().len();
(cell_count * 8) as u64 / 1024 }
fn estimate_non_null_count(df: &DataFrame) -> usize {
df.row_count() * df.column_names().len()
}
fn estimate_null_count(df: &DataFrame) -> usize {
0
}
impl DataFrame {
fn infer_column_type(&self, column_name: &str) -> String {
if let Ok(value) = self.get_value_at(0, column_name) {
if value.parse::<i64>().is_ok() {
"integer".to_string()
} else if value.parse::<f64>().is_ok() {
"float".to_string()
} else if value.parse::<bool>().is_ok() {
"boolean".to_string()
} else {
"string".to_string()
}
} else {
"unknown".to_string()
}
}
fn get_column_types(&self) -> Vec<String> {
self.column_names()
.iter()
.map(|col| self.infer_column_type(col))
.collect()
}
fn get_value_at(&self, row: usize, column: &str) -> Result<String> {
let values = self.get_column_string_values(column)?;
if row < values.len() {
Ok(values[row].clone())
} else {
Err(Error::IndexOutOfBounds {
index: row,
size: values.len(),
})
}
}
}
lazy_static! {
static ref JUPYTER_CONFIG: RwLock<Option<JupyterConfig>> = RwLock::new(None);
}
pub fn get_jupyter_config() -> JupyterConfig {
read_lock_safe!(JUPYTER_CONFIG, "jupyter config read")
.ok()
.and_then(|c| c.clone())
.unwrap_or_default()
}
pub fn set_jupyter_config(config: JupyterConfig) {
if let Ok(mut cfg) = write_lock_safe!(JUPYTER_CONFIG, "jupyter config write") {
*cfg = Some(config);
}
}
pub fn init_jupyter() -> Result<()> {
set_jupyter_config(JupyterConfig::default());
println!("🎯 PandRS Jupyter Integration Initialized!");
println!("📚 Enhanced DataFrame display and interactive widgets are now available.");
println!("🔍 Use .to_jupyter_html(), .to_interactive_widget(), or .to_data_explorer() on DataFrames.");
Ok(())
}
pub fn jupyter_dark_mode() -> JupyterConfig {
JupyterConfig {
color_scheme: JupyterColorScheme::Dark,
..Default::default()
}
}
pub fn jupyter_light_mode() -> JupyterConfig {
JupyterConfig {
color_scheme: JupyterColorScheme::Light,
..Default::default()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn test_jupyter_config_default() {
let config = JupyterConfig::default();
assert_eq!(config.max_rows, 100);
assert_eq!(config.max_columns, 20);
assert!(config.interactive);
}
#[test]
fn test_color_scheme_css() {
let dark_scheme = JupyterColorScheme::Dark;
let css = dark_scheme.to_css();
assert!(css.contains("background-color: #2d3748"));
}
#[test]
fn test_table_width_css() {
let auto_width = TableWidth::Auto;
assert_eq!(auto_width.to_css(), "width: auto;");
let fixed_width = TableWidth::Fixed(800);
assert_eq!(fixed_width.to_css(), "width: 800px;");
}
#[test]
fn test_escape_html() {
assert_eq!(escape_html("<test>"), "<test>");
assert_eq!(escape_html("AT&T"), "AT&T");
}
#[test]
fn test_format_value() {
assert_eq!(format_value("3.14159", 2), "3.14");
assert_eq!(format_value("42", 2), "42");
assert_eq!(format_value("hello", 2), "hello");
}
#[test]
fn test_jupyter_html_generation() {
let mut data = HashMap::new();
data.insert("col1".to_string(), vec!["1".to_string(), "2".to_string()]);
data.insert("col2".to_string(), vec!["a".to_string(), "b".to_string()]);
let df = DataFrame::from_map(data, None).expect("operation should succeed");
let config = JupyterConfig::default();
let html = df
.to_jupyter_html(&config)
.expect("operation should succeed");
assert!(html.contains("pandrs-table"));
assert!(html.contains("col1"));
assert!(html.contains("col2"));
}
}