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/ Custom views
Views

Custom views

Subclass View to render your own data: set kind, implement feed() and to_text()/to_image(), and register the GTK shell with set_view_shell.

TL;DRWhen the built-in table/chart/flowchart views aren't enough, subclass View. Set a class-level kind (its registry key), implement feed(data) to store it, and to_text()/to_image() to render it. Defining the subclass registers it by kind. The GTK drawing shell lives separately: your view's gtk module calls set_view_shell(kind, factory) — the one sanctioned place a package writes GTK, imported only when a display is present. Everything else stays headless.

The three built-in data views cover most cases, but you can render anything by subclassing View. A custom view keeps the split that makes every view testable: the representation (data + text/image) is headless, and the GTK shell that draws it lives apart, in the one place a package is allowed to write GTK.

Subclass View#

Set a class-level kind (its registry key), implement feed(data) to take the data, and to_text() / to_image() to render it. Config that changes presentation stays in param() fields. Defining the subclass registers it by kind (redefining a kind overrides it):

Python
# grade_band/view.py
from lsdtools import View, param

class GradeBandView(View):
    kind = "grade_band"                        # its registry key

    cutoff = param(float, default=0.5, min=0.0, max=1.0, label="Cut-off")

    def feed(self, data):
        """Store the data (a Table of {name, grade}) and notify any shell to redraw."""
        self._rows = data.to_dicts()
        self._data_changed()

    def to_text(self):
        above = [r for r in self._rows if r["grade"] >= self.cutoff]
        return f"{len(above)}/{len(self._rows)} rows at or above {self.cutoff}"

_data_changed() fires a view event that a shell subscribes to (redraw). Add to_image(self, path=None, *, width=1024, height=768) when you want a PNG rendering; the base raises NotImplementedError for whichever you don't provide.

Run it headless#

The view imports no GTK, so it renders anywhere — feed it a Table and read to_text(), or attach it to an Engine:

Python
from lsdtools import Table
from grade_band.view import GradeBandView

w = GradeBandView(cutoff=0.6)
w.feed(Table({"name": ["ann", "bob", "cy"], "grade": [0.8, 0.4, 0.65]}))

print(w.to_text())
# → 2/3 rows at or above 0.6

w.show("text")

Register the GTK shell#

The drawing code lives in your view's gtk module — the one sanctioned place a package writes GTK. It calls set_view_shell(kind, factory), where the factory turns a view instance into a control. Import GTK inside the factory so the module stays safe to load headless:

Python
# grade_band/gtk.py — loaded by the desktop when a display is present
from lsdtools.views import set_view_shell

def build_grade_band(view):
    import gi                                  # GTK stays inside the factory
    gi.require_version("Gtk", "4.0")
    from gi.repository import Gtk
    area = Gtk.DrawingArea()
    # draw view._rows against view.cutoff; subscribe to redraw:
    view.on_view_event("data-changed", area.queue_draw)
    return area

def register():
    set_view_shell("grade_band", build_grade_band)

The desktop calls register() when it loads your view package. Until a shell exists for a kind, headless consumers still work through to_text() / to_image(); only building the live control needs the shell. That is the design: check and ship the data half first, add pixels later.

LearnData views · Everything you show is a View · Params & actions

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

ExamplesA dockable panel · A complete package </content>

Frequently asked questions

Why is the GTK shell registered separately from the view?

So the view stays headless. The View subclass — its data, its to_text()/to_image() — imports no GTK, so it runs in the CLI, in CI, and from an agent. The drawing shell is a separate concern registered with set_view_shell in the view's gtk module, the one sanctioned place a package writes GTK; the desktop loads it only when a display is present.

What happens if no shell is registered for my kind?

Headless consumers still work — they call your to_text() / to_image(). Only building the live GTK control needs the shell; without it that path raises a clear error telling you to install the view package (whose gtk module registers the shell). Data and text first, pixels later.

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