Skip to content

Understanding show_row_labels of a DataTable #6189

@need47

Description

@need47

I'm trying to understand how the attr show_row_labels works in a DataTable. I have a simple App for viewing a Polars dataframe. As a dataframe could be large, I do lazy loading (100 rows initially and 50 rows later in batch). When building the table, I always include a row label (i.e., 1-indexing row id) via the function _setup_table().

By default, I want to hide the row labels. So I have the following in on_mount():

self.call_later(lambda: setattr(self.table, "show_row_labels", False))

More information can be found below why I chose call_later().

When row labels are requested, I just press the key t to toggle it with the function action_toggle_row_labels():

def action_toggle_row_labels(self):
    self.table.show_row_labels = not self.table.show_row_labels

So far so good. I was able to load the table and see the row labels with the key t.

Then I added a key binding r to restore to its initial state (e.g., after some manipulations like dropping a column):

def on_key(self, event):
    if event.key == "r":
        # Restore original display
        self._setup_table()
        # Hide labels by default after initial load
        self.call_later(lambda: setattr(self.table, "show_row_labels", False))

This is basically the same as what's in on_mount(). So after restoring to initial state with the key r, I pressed t and I got:

Image

The row labels seem to be empty.

However, as I kept on going down (either via the Down key, PageDown key, or mouse scroll), the row labels show up (even for those before the 100th row) when the table went past the 100 rows:

Image

I had a similar observation before with the following on_mount() function:

def on_mount(self) -> None:
    self._setup_table()
    # self.call_later(lambda: setattr(self.table, "show_row_labels", False))

    # this doesn't work
    self.table.show_row_labels = False

After loading the table initially, I got empty row labels when pressing t. If I continued to go down, the rows labels came back. That's why I used call_later() at last. But I couldn't get it work with table restoring.

Please help me understand show_row_labels better or if there is an alternative solution. Thanks in advance.

Full code can be found here. See simplified code below:

# Pagination settings
INITIAL_BATCH_SIZE = 100  # Load this many rows initially
BATCH_SIZE = 50  # Load this many rows when scrolling


class DataFrameViewer(App):
    """A Textual app to view dataframe interactively."""

    BINDINGS = [
        ("t", "toggle_row_labels", "Toggle Row Labels"),
    ]

    def __init__(self, df: pl.DataFrame):
        super().__init__()
        self.dataframe = df  # Original dataframe
        self.df = df  # Internal dataframe
        self.loaded_rows = 0  # Track how many rows are currently loaded
        self.total_rows = len(df)

    def compose(self) -> ComposeResult:
        """Create child widgets for the app."""
        self.table = DataTable(zebra_stripes=True)
        yield self.table

    def on_mount(self) -> None:
        """Set up the DataTable when app starts."""
        self._setup_table()
        # Hide labels by default after initial load
        self.call_later(lambda: setattr(self.table, "show_row_labels", False))

    def on_key(self, event) -> None:
        """Handle key events."""
        if event.key == "r":
            # Restore original display
            self._setup_table()
            # Hide labels by default after initial load
            # self.call_later(lambda: setattr(self.table, "show_row_labels", False))
            self.log(f"{self.table.show_row_labels = }")

            self.notify("Restored original display", title="Reset")

    def action_toggle_row_labels(self) -> None:
        """Toggle row labels visibility using CSS property."""
        self.table.show_row_labels = not self.table.show_row_labels

    def _setup_table(self) -> None:
        """Setup the table for display."""
        # Reset to original dataframe
        self.df = self.dataframe
        self.loaded_rows = 0
        self.total_rows = len(self.dataframe)

        self._setup_columns()
        self._load_rows(INITIAL_BATCH_SIZE)

    def _setup_columns(self) -> None:
        """Clear table and setup columns.

        Args:
            visible_columns: List of column names to display. If None, all columns are visible.
        """

        self.table.clear(columns=True)
        self.loaded_rows = 0

        # Add columns with justified headers (only visible columns)
        for col, dtype in zip(self.df.columns, self.df.dtypes):
            if col not in self.visible_columns:
                continue
            style_config = STYLES.get(str(dtype), {"style": "green", "justify": "left"})
            self.table.add_column(Text(col, justify=style_config["justify"]), key=col)

        self.table.cursor_type = "cell"
        self.table.focus()

    def _load_rows(self, count: int) -> None:
        """Load a batch of rows into the table."""
        start_idx = self.loaded_rows
        if start_idx >= self.total_rows:
            return

        end_idx = min(start_idx + count, self.total_rows)
        df_slice = self.df.slice(start_idx, end_idx - start_idx)

        for row_idx, row in enumerate(df_slice.rows(), start_idx):
            vals, dtypes = [], []
            for val, col, dtype in zip(row, self.df.columns, self.df.dtypes):
                if col not in self.visible_columns:
                    continue
                vals.append(val)
                dtypes.append(dtype)
            formatted_row = _format_row(vals, dtypes)
            # Always add labels so they can be shown/hidden via CSS
            self.table.add_row(*formatted_row, label=str(row_idx + 1))

        self.loaded_rows = end_idx

        if count != INITIAL_BATCH_SIZE:
            self.notify(
                f"Loaded {self.loaded_rows}/{self.total_rows} rows", title="Load"
            )

    ```


## Versions

| Name    | Value  |
|---------|--------|
| Textual | 6.3.0  |
| Rich    | 14.2.0 |

## Python

| Name           | Value                                                   |
|----------------|---------------------------------------------------------|
| Version        | 3.14.0                                                  |
| Implementation | CPython                                                 |
| Compiler       | Clang 20.1.4                                            |
| Executable     | /Users/tjcheng/Coding/dataframe-viewer/.venv/bin/python |

## Operating System

| Name    | Value                                                                                                   |
|---------|---------------------------------------------------------------------------------------------------------|
| System  | Darwin                                                                                                  |
| Release | 24.6.0                                                                                                  |
| Version | Darwin Kernel Version 24.6.0: Mon Jul 14 11:30:40 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T8132 |

## Terminal

| Name                 | Value         |
|----------------------|---------------|
| Terminal Application | tmux (3.5a)   |
| TERM                 | tmux-256color |
| COLORTERM            | *Not set*     |
| FORCE_COLOR          | *Not set*     |
| NO_COLOR             | *Not set*     |

## Rich Console options

| Name           | Value                |
|----------------|----------------------|
| size           | width=210, height=57 |
| legacy_windows | False                |
| min_width      | 1                    |
| max_width      | 210                  |
| is_terminal    | True                 |
| encoding       | utf-8                |
| max_height     | 57                   |
| justify        | None                 |
| overflow       | None                 |
| no_wrap        | False                |
| highlight      | None                 |
| markup         | None                 |
| height         | None                 |

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions