Skip to content

An editable data table beside a 3D scene

Keywords: split view · table · tabular data · TableView

A horizontal split layout: the left pane is a single TableView (an editable native grid) filling its pane, and the right pane is the 3D SceneView. Editing a cell echoes the change into the scene's annotation; buttons overlaid on the scene add rows/columns (TableView.add_row / add_column) and reset the table.

Run

uv run python py/examples/viz/ui/controls/table_split.py

Source

viz/ui/controls/table_split.py

Code

# SPDX-License-Identifier: Apache-2.0
# Copyright 2021 Christian Perwass

"""table_split.py — An editable data table beside a 3D scene.

A horizontal split layout: the left pane is a single ``TableView`` (an editable
native grid) filling its pane, and the right pane is the 3D ``SceneView``.
Editing a cell echoes the change into the scene's annotation; buttons overlaid
on the scene add rows/columns (``TableView.add_row`` / ``add_column``) and reset
the table.

Run with:  uv run python py/examples/viz/ui/controls/table_split.py

Keywords: split view, table, tabular data, TableView
"""

from typing import Any

from pytanga.geometry import Point, Sphere
from pytanga.viz import (
    ButtonView,
    ControlEvent,
    GroupView,
    SceneView,
    Size,
    SplitView,
    TableCellChange,
    TableColumnAdd,
    TableRowAdd,
    TableView,
    Visualizer,
)

viz = Visualizer(reuse_existing=False, title="Tanga — Table + Scene")

# Main scene content (the default scene, name "").
viz.add(
    Sphere(Point(0, 0, 0), radius=2), entity_id="sphere", color="#4488ff", opacity=0.3
)
viz.add(Point(1, 1, 1), color="#ff4444")

_COLUMNS = ["x", "y", "z"]
_ROWS = [["1", "2", "3"], ["4", "5", "6"]]


async def _on_cell(change: TableCellChange, _event: ControlEvent) -> None:
    viz.set_annotation(f"Cell ({change.row}, {change.col}) = {change.value!r}")


async def _on_row(add: TableRowAdd, _event: ControlEvent) -> None:
    viz.set_annotation(f"Added row {add.row}")


async def _on_column(add: TableColumnAdd, _event: ControlEvent) -> None:
    viz.set_annotation(f"Added column {add.col} ({add.header!r})")


async def _on_reset(_value: Any, _event: ControlEvent) -> None:
    # `set_value` mutates the control *and* pushes `control_update` (injected at
    # mount), so the browser redraws the grid.
    table_view.set_value({"columns": _COLUMNS, "rows": _ROWS})
    viz.set_annotation("Table reset.")


async def _on_add_row(_value: Any, _event: ControlEvent) -> None:
    table_view.add_row()
    viz.set_annotation("Added a row.")


async def _on_add_column(_value: Any, _event: ControlEvent) -> None:
    table_view.add_column(f"C{len(table_view.columns) + 1}")
    viz.set_annotation("Added a column.")


table_view = TableView(
    "data",
    label="Data",
    columns=_COLUMNS,
    rows=_ROWS,
    on_cell_change=_on_cell,
    on_row_add=_on_row,
    on_column_add=_on_column,
)

layout = SplitView(
    orientation="horizontal",
    sizes=[Size.percent(40), Size.percent(60)],
    children=[
        table_view,
        SceneView(
            "",
            overlay=[
                GroupView(
                    "Actions",
                    [
                        ButtonView("btn_add_row", label="+ Row", on_click=_on_add_row),
                        ButtonView(
                            "btn_add_col", label="+ Column", on_click=_on_add_column
                        ),
                        ButtonView(
                            "btn_reset", label="Reset table", on_click=_on_reset
                        ),
                    ],
                    position="top-left",
                ),
            ],
        ),
    ],
)

viz.show(layout=layout)
print("Table + scene split view is shown at a single URL. Press Ctrl+C to exit.")
viz.wait()