Files
MyFastHtml/docs/DataGrid.md

22 KiB

DataGrid Component

Introduction

The DataGrid component provides a high-performance tabular data display for your FastHTML application. It renders pandas DataFrames with interactive features like column resizing, reordering, and filtering, all powered by HTMX for seamless updates without page reloads.

Key features:

  • Display tabular data from pandas DataFrames
  • Resizable columns with drag handles
  • Draggable columns for reordering
  • Real-time filtering with search bar
  • Virtual scrolling for large datasets (pagination with lazy loading)
  • Custom scrollbars for consistent cross-browser appearance
  • Optional state persistence per session

Common use cases:

  • Data exploration and analysis dashboards
  • Admin interfaces with tabular data
  • Report viewers
  • Database table browsers
  • CSV/Excel file viewers

Quick Start

Here's a minimal example showing a data table with a pandas DataFrame:

import pandas as pd
from fasthtml.common import *
from myfasthtml.controls.DataGrid import DataGrid
from myfasthtml.core.instances import RootInstance

# Create sample data
df = pd.DataFrame({
    "Name": ["Alice", "Bob", "Charlie", "Diana"],
    "Age": [25, 30, 35, 28],
    "City": ["Paris", "London", "Berlin", "Madrid"]
})

# Create root instance and data grid
root = RootInstance(session)
grid = DataGrid(parent=root)
grid.init_from_dataframe(df)

# Render the grid
return grid

This creates a complete data grid with:

  • A header row with column names ("Name", "Age", "City")
  • Data rows displaying the DataFrame content
  • A search bar for filtering data
  • Resizable column borders (drag to resize)
  • Draggable columns (drag headers to reorder)
  • Custom scrollbars for horizontal and vertical scrolling

Note: The DataGrid automatically detects column types (Text, Number, Bool, Datetime) from the DataFrame dtypes and applies appropriate formatting.

Basic Usage

Visual Structure

The DataGrid component consists of a filter bar, a table with header/body/footer, and custom scrollbars:

┌────────────────────────────────────────────────────────────┐
│  Filter Bar                                                │
│  ┌─────────────────────────────────────────────┐  ┌────┐   │
│  │ 🔍 Search...                                │  │  ✕ │   │
│  └─────────────────────────────────────────────┘  └────┘   │
├────────────────────────────────────────────────────────────┤
│  Header Row                                            ▲   │
│  ┌──────────┬──────────┬──────────┬──────────┐         │   │
│  │ Column 1 │ Column 2 │ Column 3 │ Column 4 │         █   │
│  └──────────┴──────────┴──────────┴──────────┘         █   │
├────────────────────────────────────────────────────────█───┤
│  Body (scrollable)                                     █   │
│  ┌──────────┬──────────┬──────────┬──────────┐         █   │
│  │ Value    │ Value    │ Value    │ Value    │         █   │
│  ├──────────┼──────────┼──────────┼──────────┤         │   │
│  │ Value    │ Value    │ Value    │ Value    │         │   │
│  ├──────────┼──────────┼──────────┼──────────┤         ▼   │
│  │ Value    │ Value    │ Value    │ Value    │             │
│  └──────────┴──────────┴──────────┴──────────┘             │
│  ◄═══════════════════════════════════════════════════════► │
└────────────────────────────────────────────────────────────┘

Component details:

Element Description
Filter bar Search input with filter mode toggle and clear button
Header row Column names with resize handles and drag support
Body Scrollable data rows with virtual pagination
Scrollbars Custom vertical and horizontal scrollbars

Creating a DataGrid

The DataGrid is a MultipleInstance, meaning you can create multiple independent grids in your application. Create it by providing a parent instance:

grid = DataGrid(parent=root_instance)

# Or with a custom ID
grid = DataGrid(parent=root_instance, _id="my-grid")

# Or with state persistence enabled
grid = DataGrid(parent=root_instance, save_state=True)

Parameters:

  • parent: Parent instance (required)
  • _id (str, optional): Custom identifier for the grid
  • save_state (bool, optional): Enable state persistence (column widths, order, filters)

Loading Data

Use the init_from_dataframe() method to load data into the grid:

import pandas as pd

# Create a DataFrame
df = pd.DataFrame({
    "Product": ["Laptop", "Phone", "Tablet"],
    "Price": [999.99, 699.99, 449.99],
    "In Stock": [True, False, True]
})

# Load into grid
grid.init_from_dataframe(df)

Column type detection:

The DataGrid automatically detects column types from pandas dtypes:

pandas dtype DataGrid type Display
int64, float64 Number Right-aligned
bool Bool Checkbox icon
datetime64 Datetime Formatted date
object, others Text Left-aligned, truncated

Row Index Column

By default, the DataGrid displays a row index column on the left. This can be useful for identifying rows:

# Row index is enabled by default
grid._state.row_index = True

# To disable the row index column
grid._state.row_index = False
grid.init_from_dataframe(df)

Column Features

Resizing Columns

Users can resize columns by dragging the border between column headers:

  • Drag handle location: Right edge of each column header
  • Minimum width: 30 pixels
  • Persistence: Resized widths are automatically saved when save_state=True

The resize interaction:

  1. Hover over the right edge of a column header (cursor changes)
  2. Click and drag to resize
  3. Release to confirm the new width
  4. Double-click to reset to default width

Programmatic width control:

# Set a specific column width
for col in grid._state.columns:
  if col.col_id == "my_column":
    col.width = 200  # pixels
    break

Moving Columns

Users can reorder columns by dragging column headers:

  1. Click and hold a column header
  2. Drag to the desired position
  3. Release to drop the column

The columns animate smoothly during the move, and other columns shift to accommodate the new position.

Note: Column order is persisted when save_state=True.

Column Visibility

Columns can be hidden programmatically:

# Hide a specific column
for col in grid._state.columns:
  if col.col_id == "internal_id":
    col.visible = False
    break

Hidden columns are not rendered but remain in the state, allowing them to be shown again later.

Filtering

The DataGrid includes a built-in search bar that filters rows in real-time:

┌─────────────────────────────────────────────┐  ┌────┐
│ 🔍 Search...                                │  │  ✕ │
└─────────────────────────────────────────────┘  └────┘
  │                                                 │
  │                                                 └── Clear button
  └── Filter mode icon (click to cycle)

How filtering works:

  1. Type in the search box
  2. The grid filters rows where ANY visible column contains the search text
  3. Matching text is highlighted in the results
  4. Click the ✕ button to clear the filter

Filter Modes

Click the filter icon to cycle through three modes:

Mode Icon Description
Filter 🔍 Hides non-matching rows
Search 🔎 Highlights matches, shows all rows
AI 🧠 AI-powered search (future feature)

The current mode affects how results are displayed:

  • Filter mode: Only matching rows are shown
  • Search mode: All rows shown, matches highlighted

Advanced Features

State Persistence

Enable state persistence to save user preferences across sessions:

# Enable state persistence
grid = DataGrid(parent=root, save_state=True)

What gets persisted:

State Description
Column widths User-resized column sizes
Column order User-defined column arrangement
Column visibility Which columns are shown/hidden
Sort order Current sort configuration
Filter state Active filters

Virtual Scrolling

For large datasets, the DataGrid uses virtual scrolling with lazy loading:

  • Only a subset of rows (page) is rendered initially
  • As the user scrolls down, more rows are loaded automatically
  • Uses Intersection Observer API for efficient scroll detection
  • Default page size: configurable via DATAGRID_PAGE_SIZE

This allows smooth performance even with thousands of rows.

Text Size

Customize the text size for the grid body:

# Available sizes: "xs", "sm", "md", "lg"
grid._settings.text_size = "sm"  # default

CSS Customization

The DataGrid uses CSS classes that you can customize:

Class Element
dt2-table-wrapper Root table container
dt2-table Table element
dt2-header-container Header wrapper
dt2-body-container Scrollable body wrapper
dt2-footer-container Footer wrapper
dt2-row Table row
dt2-cell Table cell
dt2-resize-handle Column resize handle
dt2-scrollbars-vertical Vertical scrollbar
dt2-scrollbars-horizontal Horizontal scrollbar
dt2-highlight-1 Search match highlight

Example customization:

/* Change highlight color */
.dt2-highlight-1 {
    background-color: #fef08a;
    font-weight: bold;
}

/* Customize row hover */
.dt2-row:hover {
    background-color: #f3f4f6;
}

/* Style the scrollbars */
.dt2-scrollbars-vertical,
.dt2-scrollbars-horizontal {
    background-color: #3b82f6;
    border-radius: 4px;
}

Examples

Example 1: Simple Data Table

A basic data table displaying product information:

import pandas as pd
from fasthtml.common import *
from myfasthtml.controls.DataGrid import DataGrid
from myfasthtml.core.instances import RootInstance

# Sample product data
df = pd.DataFrame({
    "Product": ["Laptop Pro", "Wireless Mouse", "USB-C Hub", "Monitor 27\"", "Keyboard"],
    "Category": ["Computers", "Accessories", "Accessories", "Displays", "Accessories"],
    "Price": [1299.99, 49.99, 79.99, 399.99, 129.99],
    "In Stock": [True, True, False, True, True],
    "Rating": [4.5, 4.2, 4.8, 4.6, 4.3]
})

# Create and configure grid
root = RootInstance(session)
grid = DataGrid(parent=root, _id="products-grid")
grid.init_from_dataframe(df)

# Render
return Div(
  H1("Product Catalog"),
  grid,
  cls="p-4"
)

Example 2: Large Dataset with Filtering

Handling a large dataset with virtual scrolling and filtering:

import pandas as pd
import numpy as np
from fasthtml.common import *
from myfasthtml.controls.DataGrid import DataGrid
from myfasthtml.core.instances import RootInstance

# Generate large dataset (10,000 rows)
np.random.seed(42)
n_rows = 10000

df = pd.DataFrame({
    "ID": range(1, n_rows + 1),
    "Name": [f"Item_{i}" for i in range(n_rows)],
    "Value": np.random.uniform(10, 1000, n_rows).round(2),
    "Category": np.random.choice(["A", "B", "C", "D"], n_rows),
    "Active": np.random.choice([True, False], n_rows),
    "Created": pd.date_range("2024-01-01", periods=n_rows, freq="h")
})

# Create grid with state persistence
root = RootInstance(session)
grid = DataGrid(parent=root, _id="large-dataset", save_state=True)
grid.init_from_dataframe(df)

return Div(
  H1("Large Dataset Explorer"),
  P(f"Displaying {n_rows:,} rows with virtual scrolling"),
  grid,
  cls="p-4",
  style="height: 100vh;"
)

Note: Virtual scrolling loads rows on demand as you scroll, ensuring smooth performance even with 10,000+ rows.

Example 3: Dashboard with Multiple Grids

An application with multiple data grids in different tabs:

import pandas as pd
from fasthtml.common import *
from myfasthtml.controls.DataGrid import DataGrid
from myfasthtml.controls.TabsManager import TabsManager
from myfasthtml.core.instances import RootInstance

# Create data for different views
sales_df = pd.DataFrame({
    "Date": pd.date_range("2024-01-01", periods=30, freq="D"),
    "Revenue": [1000 + i * 50 for i in range(30)],
    "Orders": [10 + i for i in range(30)]
})

customers_df = pd.DataFrame({
    "Customer": ["Acme Corp", "Tech Inc", "Global Ltd"],
    "Country": ["USA", "UK", "Germany"],
    "Total Spent": [15000, 12000, 8500]
})

# Create instances
root = RootInstance(session)
tabs = TabsManager(parent=root, _id="dashboard-tabs")

# Create grids
sales_grid = DataGrid(parent=root, _id="sales-grid")
sales_grid.init_from_dataframe(sales_df)

customers_grid = DataGrid(parent=root, _id="customers-grid")
customers_grid.init_from_dataframe(customers_df)

# Add to tabs
tabs.create_tab("Sales", sales_grid)
tabs.create_tab("Customers", customers_grid)

return Div(
  H1("Sales Dashboard"),
  tabs,
  cls="p-4"
)

Developer Reference

This section contains technical details for developers working on the DataGrid component itself.

State

The DataGrid uses two state objects:

DatagridState - Main state for grid data and configuration:

Name Type Description Default
sidebar_visible bool Whether sidebar is visible False
row_index bool Show row index column True
columns list[DataGridColumnState] Column definitions []
rows list[DataGridRowState] Row-specific states []
sorted list Sort configuration []
filtered dict Active filters {}
selection DatagridSelectionState Selection state -
ne_df DataFrame The data (non-persisted) None

DatagridSettings - User preferences:

Name Type Description Default
save_state bool Enable persistence False
header_visible bool Show header row True
filter_all_visible bool Show filter bar True
text_size str Body text size "sm"

Column State

Each column is represented by DataGridColumnState:

Name Type Description Default
col_id str Column identifier -
col_index int Index in DataFrame -
title str Display title None
type ColumnType Data type Text
visible bool Is column visible True
usable bool Is column usable True
width int Width in pixels 150

Commands

Available commands for programmatic control:

Name Description
get_page(page_index) Load a specific page of data (lazy loading)
set_column_width() Update column width after resize
move_column() Move column to new position
filter() Apply current filter to grid

Public Methods

Method Description
init_from_dataframe(df, init_state=True) Load data from pandas DataFrame
set_column_width(col_id, width) Set column width programmatically
move_column(source_col_id, target_col_id) Move column to new position
filter() Apply filter and return partial render
render() Render the complete grid
render_partial(fragment, redraw_scrollbars) Render only part of the grid

High Level Hierarchical Structure

Div(id="{id}", cls="grid")
├── Div (filter bar)
│   └── DataGridQuery                    # Filter/search component
├── Div(id="tw_{id}", cls="dt2-table-wrapper")
│   ├── Div(id="t_{id}", cls="dt2-table")
│   │   ├── Div (dt2-header-container)
│   │   │   └── Div(id="th_{id}", cls="dt2-row dt2-header")
│   │   │       ├── Div (dt2-cell)       # Column 1 header
│   │   │       ├── Div (dt2-cell)       # Column 2 header
│   │   │       └── ...
│   │   ├── Div(id="tb_{id}", cls="dt2-body-container")
│   │   │   └── Div (dt2-body)
│   │   │       ├── Div (dt2-row)        # Data row 1
│   │   │       ├── Div (dt2-row)        # Data row 2
│   │   │       └── ...
│   │   └── Div (dt2-footer-container)
│   │       └── Div (dt2-row dt2-header) # Footer row
│   └── Div (dt2-scrollbars)
│       ├── Div (dt2-scrollbars-vertical-wrapper)
│       │   └── Div (dt2-scrollbars-vertical)
│       └── Div (dt2-scrollbars-horizontal-wrapper)
│           └── Div (dt2-scrollbars-horizontal)
└── Script                                # Initialization script

Element IDs

Pattern Description
{id} Root grid container
tw_{id} Table wrapper (scrollbar container)
t_{id} Table element
th_{id} Header row
tb_{id} Body container
tf_{id} Footer row
tsm_{id} Selection Manager
tr_{id}-{row_index} Individual data row

Internal Methods

These methods are used internally for rendering:

Method Description
mk_headers() Renders the header row
mk_body() Renders the body with first page
mk_body_container() Renders the scrollable body container
mk_body_content_page(page_index) Renders a specific page of rows
mk_body_cell(col_pos, row_index, col_def) Renders a single cell
mk_body_cell_content(...) Renders cell content with highlighting
mk_footers() Renders the footer row
mk_table() Renders the complete table structure
mk_aggregation_cell(...) Renders footer aggregation cell
_get_filtered_df() Returns filtered and sorted DataFrame
_apply_sort(df) Applies sort configuration
_apply_filter(df) Applies filter configuration

DataGridQuery Component

The filter bar is a separate component (DataGridQuery) with its own state:

State Property Type Description Default
filter_type str Current mode ("filter", "search", "ai") "filter"
query str Current search text None

Commands:

Command Description
change_filter_type() Cycle through filter modes
on_filter_changed() Handle search input changes
on_cancel_query() Clear the search query