Sandbox — LSD is in active testing. All systems operational·payments are test-mode (no real charges).
Overview Use Cases News Docs Tool Store Support Community Get LSD
All docs
Docs/ Views/ Data views represent data
Views

Data views represent data

A data view renders data as a table, chart, or flowchart via to_text()/to_image(). Feed it, return one from @tool.view, and present it with show().

TL;DRA data view is a View with a kind that represents data. Get one from the views façade — views.table, views.chart(kind=...), views.flowchart — and give it data with feed() (for literal data) or, to follow a pipeline, watch(entity) — which holds the entity's live output and refreshes on every re-run. Every data view answers to_text() and to_image(), so an agent or the CLI sees it without a display. @tool.view returns a View for the desktop to draw; standalone, show() it, or attach it with engine.add(a_view) then engine.show() — the mirror of engine.add(entity) + engine.run().

A data view is a View with a kind — a representation of data. You give it data with feed(data) and it renders — as text for an agent or the CLI, as an image for a report, or as a live control in the desktop. A data view is just a View with data on top of its param() config and @action operations.

The three built-in kinds#

Get a ready-made data view from the views façade. Each factory imports its package lazily, so importing lsdtools never pulls in cairo or a view package:

Python
from lsdtools import views, Table

# a data grid
grid = views.table(Table({"name": ["ann", "bob"], "score": [12, 30]}))

# a chart — kind is bar / stacked_bar / line / area / scatter / histogram / box / heatmap
chart = views.chart(kind="line", title="Loss")
chart.feed({"series": [{"name": "train", "x": [0, 1, 2, 3], "y": [0.9, 0.6, 0.4, 0.25]}]})

# a flowchart from the indented text DSL
flow = views.flowchart("A\n  B\n  C")

print(grid.to_text())
print(chart.to_text())

views.table(data), views.chart(data, kind=...), and views.flowchart(source) all accept their data inline or you feed it later with feed(...).

Interaction — the same state, in the desktop and headless#

A data view is not render-once. In the desktop each is a live, interactive widget; and because every interaction is a param() or a method on the view, an agent, a test, or a script drives the exact same behaviour with no display — set the param, read the result.

Table — sort, filter, select, export:

Python
grid = views.table(t, selection="multi")
grid.sort_by, grid.sort_desc = "score", True   # click a column header in the desktop
grid.filter = "an"                              # the search box; case-insensitive, across columns
grid.select_rows([0, 2])                        # row selection (selection= none/single/multi)
picked = grid.get_selection()                   # a Table of the selected rows
csv = grid.to_csv()                             # copy / export the current sorted+filtered view

Sort, filter, and paging compose — page_table() / row_count() reflect the filtered set.

Chart — new stacked_bar and area kinds, axis titles, and a togglable legend:

Python
chart = views.chart(kind="stacked_bar", title="Grades", x_label="bucket", y_label="count")
chart.toggle_series("train")        # what a legend click does; or set_series_hidden(name, True)
chart.hidden_series                  # -> ["train"] — hidden series drop out of the ranges and marks,
                                     #    but stay greyed in the legend so you can toggle them back

In the desktop the chart also zooms (scroll), pans (drag), and shows a nearest-point hover tooltip.

Flowchart — node selection and hover:

Python
flow.select(node_id)                 # what a click does; add additive=True for Ctrl-click multi-select
flow.selected_ids                    # -> ["n1"]; also clear_selection() and set_hovered(node_id)

Selecting emits a selection-changed view event, so a host or agent can react to it.

Headless renderings#

Every data view answers to_text() (a plain-text view for logs, agents, and tests) and to_image() (a PNG — returns the bytes, or writes to a path):

Python
print(grid.to_text())
# name  score
# ----  -----
# ann   12
# bob   30

grid.to_image("grades.png")        # or: png_bytes = grid.to_image()

Contribute one with @tool.view#

@tool.view decorates a factory (ctx, entity=None) -> View. The desktop mounts it as an editor tab and resolves its shell (a headless host uses to_text() / to_image() instead). Returning anything other than a View is a mount error — a package never touches GTK directly:

Python
from lsdtools import Tool, Context, views

tool = Tool("grade", label="Grade tools")

@tool.view("grade.scores", title="Scores", location="editor")
def scores_view(ctx: Context, entity=None):
    return views.table(title="Scores").watch(entity)   # follows the entity — refreshes on re-run

Give the view the entity with watch(entity), not views.table(entity.output): entity.output reads the table once at mount and never updates, while watch holds the entity's live output and refreshes on every re-run (and pulls immediately if the entity has already run). watch returns the view, so views.table(title=...).watch(entity) is one phrase. It is the same verb a deliver step's ViewerPayload is routed through.

Present one with the engine#

Standalone, show() the data view, or attach it to the engine. engine.add(a_view) then engine.show() is the exact mirror of engine.add(entity) then engine.run():

Python
from lsdtools import Engine, views, Table

t = Table({"name": ["ann", "bob"], "score": [12, 30]})

views.table(t, title="Grades").show()      # standalone window (or text when headless)

engine = Engine()
engine.add(views.chart(kind="histogram", title="Scores"))
engine.show()

show(mode="text") prints the text rendering, so a view is testable without a display.

LearnEverything you show is a View · Custom views · Params & actions

StartHow an LSD package fits together · Naming: which is which

ExamplesA dockable panel · A complete package

Frequently asked questions

What kinds of data view ship with lsdtools?

Three, through the views façade — table, chart (kind = bar/stacked_bar/line/area/scatter/histogram/box/heatmap), and flowchart. Each lives in its own package that the façade imports lazily, so importing lsdtools never drags in cairo or a view package. For anything else, subclass View (see Custom views).

Are the data views interactive, or render-once?

Both. Each answers to_text()/to_image() headlessly, and in the desktop each is fully interactive — the table sorts, filters, selects, and exports; the chart zooms, pans, shows hover tooltips, and toggles series from the legend; the flowchart selects and highlights nodes. Every interaction is driven by a param or method on the view (sort_by, filter, toggle_series, select…), so an agent or a test drives the same behaviour without a display.

What makes a data view different from a plain View?

Both are a View. A View with no kind is param() fields plus @action buttons, controls with no data. A data view has a kind — it adds feed(data) plus to_text()/to_image() to represent a result. Use a data view to show a result, a plain View to take input.

By LSD Team · Last updated Jul 18, 2026 Ask on Discord View as Markdown