```{toctree} --- maxdepth: 2 hidden: true --- docs/src/tutorial.md docs/src/how-to.md docs/src/reference.md docs/src/explanation.md ``` spaday-perspective logo, receding Perspective P letterforms inside a browser window [Perspective](https://perspective-dev.github.io) for [spaday](https://1kbgz.github.io/spaday/) [![Build Status](https://github.com/1kbgz/spaday-perspective/actions/workflows/build.yaml/badge.svg?branch=main&event=push)](https://github.com/1kbgz/spaday-perspective/actions/workflows/build.yaml) [![codecov](https://codecov.io/gh/1kbgz/spaday-perspective/branch/main/graph/badge.svg)](https://codecov.io/gh/1kbgz/spaday-perspective) [![License](https://img.shields.io/github/license/1kbgz/spaday-perspective)](https://github.com/1kbgz/spaday-perspective) [![PyPI](https://img.shields.io/pypi/v/spaday-perspective.svg)](https://pypi.python.org/pypi/spaday-perspective) ## Overview `spaday-perspective` moves the existing `PerspectivePanel` integration out of spaday core. Its self-contained `` bundle includes Perspective's client, viewer (the multi-panel workspace element since Perspective 5), datagrid, themes, and viewer WASM. ## Documentation - [Build a live market workspace](docs/src/tutorial.md) — guided first application. - [Stream data and change layouts](docs/src/how-to.md) — task-focused server and UI recipes. - [API reference](docs/src/reference.md) — component props, layout shape, and package descriptor. - [Why Perspective uses its own data channel](docs/src/explanation.md) — integration design and tradeoffs. ## Quick example This complete app serves a live Perspective table. **Add trade** streams a new row into the grid, the select switches between flat and grouped layouts, and the checkbox changes the viewer theme. ```python import perspective from perspective.handlers.starlette import PerspectiveStarletteHandler from starlette.responses import JSONResponse from starlette.routing import Route, WebSocketRoute from starlette.websockets import WebSocket from spaday import CallEndpoint, cond, element, eq, field, obj from spaday.backends.starlette import serve from spaday_perspective import PerspectivePanel server = perspective.Server() client = server.new_local_client() trades = client.table( [ {"id": 1, "symbol": "AAPL", "side": "buy", "price": 211.10}, {"id": 2, "symbol": "MSFT", "side": "sell", "price": 503.02}, ], name="trades", ) next_id = 2 async def add_trade(_request): global next_id next_id += 1 trades.update( [ { "id": next_id, "symbol": "AAPL" if next_id % 2 else "MSFT", "side": "buy" if next_id % 2 else "sell", "price": 210 + next_id, } ] ) return JSONResponse({"id": next_id}) async def perspective_socket(websocket: WebSocket): await PerspectiveStarletteHandler(perspective_server=server, websocket=websocket).run() def layout(*, grouped=False): viewer = {"table": "trades", "plugin": "Datagrid", "columns": ["symbol", "side", "price"]} if grouped: viewer.update({"group_by": ["symbol"], "columns": ["price"], "aggregates": {"price": "avg"}}) return { "layout": {"type": "tab-layout", "tabs": ["trades"]}, "panels": {"trades": viewer}, } panel = ( PerspectivePanel() .compute("theme", cond(field("dark"), "dark", "light")) .compute( "config", obj( { "ws_url": "/perspective", "tables": ["trades"], "layout": cond(eq(field("view"), "grouped"), layout(grouped=True), layout()), } ), ) .style(display="block", height="70vh", margin_top="1rem") ) view = element("select").bind("value", "view", mode="two-way").child( element("option", "Blotter", value="blotter"), element("option", "Average by symbol", value="grouped"), ) dark = element("input", type="checkbox").bind("checked", "dark", mode="two-way") page = ( element("main") .style(margin="1rem", font_family="system-ui") .child(element("button").text("Add trade").on("click", CallEndpoint("POST", "/api/trades"))) .child(view) .child(element("label").child(dark, " Dark theme")) .child(panel) ) app = serve( page, packages=["perspective"], store={"view": "blotter", "dark": False}, routes=[ Route("/api/trades", add_trade, methods=["POST"]), WebSocketRoute("/perspective", perspective_socket), ], ) ``` Save this as `app.py`, then run `pip install spaday-perspective perspective-python starlette uvicorn` and `uvicorn app:app`. Open . This keeps Perspective's bulk data on its native websocket. A spaday/transports model only needs to carry the small serializable connection/layout config. Replacing `config` reconnects when `ws_url` changes and restores changed layouts; `theme` accepts `light`, `dark`, or a Perspective theme name. Installing this project registers the `perspective` entry point with spaday. The equivalent explicit forms are `packages=[spaday_perspective.package]` and `packages=["spaday_perspective:package"]`. ## Run the local example ```bash python -m pip install -e ".[examples]" python -m spaday_perspective.example ``` Open `http://127.0.0.1:8015` to inspect the [complete market-monitor example](spaday_perspective/example.py): live native Perspective streaming, flat and grouped layouts, server-authoritative order submission, reactive metrics, and light/dark themes. It passes the local package descriptor directly, so it does not install or resolve the integration from GitHub. > [!NOTE] > This library was generated using [copier](https://copier.readthedocs.io/en/stable/) from the [Base Python Project Template repository](https://github.com/python-project-templates/base).