You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
<perspective-viewer> is now a workspace. Multi-panel dashboards, drag-and-drop docking and master/detail cross-filtering are core features.
Virtual Servers for DuckDB, ClickHouse and Polars. These are not data-ingest; Virtual Servers completely replace the built-in query engine entirely with push-down SQL, driven by <perspective-viewer> and read via Apache Arrow IPC.
New charting package, @perspective-dev/viewer-charts — GPU-rendered, context pooled, multi-threaded, with twelve chart types (and maps!), plus loads of new configuration options.
PerspectiveWidget is now an AnyWidget — one pip install, no labextensions, identical behavior in JupyterLab, VSCode, Colab, Marimo — and via new Emscripten wheels, JupyterLite.
New engine features: reactive Client::join, group_rollup_mode, coalesce, the gmv aggregate, page_to_disk disk-backed tables, and a batch of hot-path performance work.
New platforms: Perspective now publishes PYPI wheels for arm64 and emscripten (pyodide/jupyterlite), plus a WebAssembly Memory64 (wasm64) build of the engine for 16GB in-browser heaps.
Deprecated packages: @perspective-dev/viewer-d3fc, @perspective-dev/viewer-openlayers and @perspective-dev/workspace will be deprecated.
See the Migration Notes at the end for everything that changed incompatibly since v4.0.0.
<perspective-viewer> is now a Workspace
The @perspective-dev/workspace package has been deprecated, and its functionality is now built directly into <perspective-viewer> itself, rather than as a separate lumino-based TypeScript wrapper (#3199). Every <perspective-viewer> element is now a workspace, with panels that can be split, stacked into tabs, resized, duplicated, and closed via the UI or the API:
Multi-panel layouts — panels are arranged by the regular-layout engine (splits, tab stacks, drag-to-dock, drag-to-resize) inside the viewer element.
Master/detail global filters — toggling a panel to Master puts its plugin into row-selection mode; selections contribute filter clauses to an element-level global filter set, applied as a transient overlay to every detail panel's view (never written into their saved configs). A global filter bar displays and edits the active clauses.
Panel API — addPanel(config), removePanel(id), getPanelNames(), getActivePanel(), setActivePanel(id), plus a panel option on the existing single-viewer methods (save, restore, getView, getTable, reset, export, …) which target the active panel by default.
Whole-element persistence — save() and restore() still emit and accept the classic single-panel ViewerConfig (existing round-trips are backwards-compatible, and restore({panel})upserts — restoring to a panel name that doesn't exist creates it). The new saveWorkspace() / restoreWorkspace() pair captures the entire element — layout tree, per-panel configs, active panel, masters and global filters — in one token:
Events — perspective-click and perspective-select details gain a panel field identifying the originating panel, and a new perspective-global-filter-update event fires when the global filter set changes.
See the migration notes below for @perspective-dev/workspace migration notes.
Viewers bind Clients, not Tables
In v5.0.0, load() takes a Client, which the element binds to exclusively, and the Table is named by a new table field in ViewerConfig:
Calling load() with a Client does not render (no Table is selected yet); restore() selects both the Table and its config (guaranteeing one atomic render once both are called and the element is mounted). load(table) still works as a legacy convenience — it is internally equivalent to load(await table.get_client()) followed by restore({table: await table.get_name()}) — but new code should pass a Client.
One footgun to be aware of: Tables constructed without a name get a random one, which will be captured by save() and won't match on the next page load — always name your tables (though you can capture the name with get_name()).
New @perspective-dev/viewer-charts charting package
@perspective-dev/viewer-d3fc has been replaced by @perspective-dev/viewer-charts — a ground-up rewrite that renders Perspective views on the GPU with custom GLSL shaders, a shared buffer pool for GPU memory reuse, and zero-copy Arrow data transfer to a Web Worker host (#3160, #3166, #3176).
Twelve chart types: X/Y Scatter, X/Y Line, Y Bar, X Bar, Y Line, Y Scatter, Y Area, Treemap, Sunburst, Candlestick, OHLC and Heatmap — plus new Density and Maps types which deprecate @perspective-dev/viewer-openlayers.
Multithreaded — the chart engine runs in a Web Worker with its own Client instance, taking Arrow slices directly from the engine without ever touching the main thread. An optional blit mode (default) copies frames to 2D Canvas instances to sidestep the browser's WebGL context limit, so dozens of charts can share one page.
Fast. All chart types are now super fast — so fast the Render Warning thresholds have been raised to 1,000,000 rows.
Faceting — split_by groups can render as a grid of sub-plots with shared axes and zoom routing between facets (#3168).
Per-column series config — each Y column can independently choose chart_type ∈ {Bar, Line, Scatter, Area} and a stack override, closing some of the oldest feature requests in the tracker (#2938, #2390, #2331).
Zoom everywhere — all chart types support locked/unlocked zooming (even on categorical axes) and auto-fit, category axis support when a non-numeric column is placed on an X, Y or X/Y domain axis, and an interpolate option for Line charts (#3182).
A new plugin configuration tab
A new Plugin API (#3176) which lets plugins describe their configuration programmatically to the <perspective-viewer> host: plugin_config_schema and column_config_schema declare a plugin's options, and a generated plugin-configuration tab will show up in the viewer's settings panel. Plugins also no longer implement save() (This is a breaking change for custom plugin authors — see migration notes).
Notebooks: AnyWidget, VSCode and JupyterLite
PerspectiveWidget's dual-package ipywidgets + JupyterLab labextension architecture has been replaced with a single AnyWidget-based widget shipped entirely from the perspective-python wheel (#3192). The widget now runs identically in JupyterLab, classic Jupyter Notebook, VSCode notebooks, Colab, Marimo, etc. — with no per-host labextension install:
pip install "perspective-python[jupyter]"
You can also install the optional @perspective-dev/jupyterlab, which is slimmed down to a renderers-only labextension (the CSV/JSON/Arrow "Open With → Perspective" file viewers).
New Python architecture support
Perspective's Python distribution also gained two new architectures:
Linux arm64/aarch64 wheels (#3092), for Graviton servers and Linux-on-Apple-Silicon containers.
Emscripten wheels per PEP 783 (#3191) — perspective-python now installs in Pyodide, which combined with the AnyWidget port means PerspectiveWidget works in JupyterLite — a full Perspective notebook experience with no server.
Virtual Servers: DuckDB, ClickHouse and Polars
Perspective 5.0.0 ships the Virtual Server API — implement a small handler interface and <perspective-viewer> will drive your query engine instead of Perspective's own, translating every ViewerConfig option (group_bys, filters, sorts, expressions) into queries against the backing store. Three implementations are included for Python, and two for JavaScript (Polars is Python-only):
ClickHouse (#3119) — JavaScript and Python implementations that translate Perspective queries into ClickHouse SQL, sharing a generic SQL model in Rust with the DuckDB backend.
Polars (#3125) — a Python implementation over Polars dataframes, with a test suite mirroring the DuckDB one.
While none of these servers support real-time updates, they should otherwise by fully-featured under <perspective-viewer> - except, you will see e.g. DuckDB's aggregates, DuckDB's expression syntax, etc. in the UI, rather than perspective-server's.
C++ Engine
Performance
Lots of performance improvements, some highlights:
Bunch of small algo improvements + SIMD-vectorized aggregation loops (with -fopenmp-simd, and -msimd128/-mrelaxed-simd for WASM) #3141.
View registration and CSV loading were overhauled in #3165 — bulk-load fast paths and zero-copy column aliasing between Table and View replace per-column deep clones.
The same PR adds View::with_typed_arrays, a new zero-copy JS API that hands your callback TypedArray views directly over the Arrow buffers (with an optional float32 downcast mode for half-memory GPU uploads) — this is the API the new charts package is built on.
Client::join
Client::join creates read-only, reactive joined tables in JavaScript, Python and Rust (#3143):
Joins support inner (default), left and outer types, an optional right_on for differently-named key columns, correct duplicate (cross-product) handling with left-precedence natural order for visual stability — and they're streaming: updates to either source Table automatically recompute the join and propagate to any derived View. Joins can be chained, and a Table created by join behaves like any other (minus writes) — including in a <perspective-viewer>.
group_rollup_mode
The per-query leaves_only serialization flag has been replaced by group_rollup_mode, a first-class ViewConfig property with three variants (#3132, #3135): "rollup" (the classic hierarchical tree), "flat" (leaf rows only — the datagrid hides tree expanders and renders flat column headers), and "total" (only the grand-total row, aggregated across all rows, no group_by required). It's a UI dropdown in the settings panel, works on virtual servers, and applies to every serialization method and plugin, not just to_columns.
page_to_disk mode, new aggregates and functions
page_to_disk is a new Client.table(...) option that backs a Table's master columns with disk-resident storage instead of heap, allowing datasets larger than addressable memory (#3185) — with a runtime-appropriate backend on every platform: mmap for Python/Rust, OPFS in the browser, and node:fs for Node.js.
gmv ("gross market value") is a new aggregate for group_by/split_by trees (#3180): a leaf is a plain signed sum, while a parent is the sum over its immediate children of |sum(child_subtree)| — the natural aggregate for netted long/short position books. It recursively recalculates per depth, so it is noticeably slower than sum; use it wisely.
Memory64 (wasm64) builds
@perspective-dev/server now publishes a WebAssembly Memory64 build, perspective-server.memory64.wasm, alongside the standard wasm32 binary (#3203) — raising the in-browser engine's heap ceiling from 4GB to 16GB. init_server now accepts both binaries at once lazily; the engine feature-detects Memory64 support in the host and downloads only the binary it selects:
Registering only the wasm32 binary (as in prior versions, which remains supported) opts out entirely, while the stock CDN and Node.js entrypoints register both, so capable hosts upgrade automatically (and hosts without Memory64 support fall back to wasm32).
Datagrid
The datagrid has added label-bar mode for numeric columns, which overlays the formatted value on top of the proportional bar fill using mix-blend-mode: difference for contrast against the fill color (#3156):
Also added SELECT_ROW_TREE, a tree-aware row-selection edit mode that tracks selections by tree identity rather than flat row index — the machinery behind the workspace's master/detail filtering.
Themes
Theming was redesigned in #3146: all exported CSS variables were renamed to a consistent --psp-{module}--{component?}--{property} format, and all icon SVGs now carry correct intrinsic dimensions so they can be swapped for arbitrarily-sized replacements. The workspace merger added per-panel themes (#3107, #3150), and a new "Botanical" and "Phosphor" theme (#3135).
Migration Notes (v4.0.0 → v5.0.0)
<perspective-viewer>.load() takes a Client
load(table) still works as a legacy path, but the primary signature is now load(client), with the Table named by the new table field of ViewerConfig in save()/restore():
// Before (4.0)consttable=awaitworker.table(data);awaitviewer.load(table);awaitviewer.restore(config);// After (5.0)awaitworker.table(data,{name: "my_table"});awaitviewer.load(worker);awaitviewer.restore({ ...config,table: "my_table"});
A Table constructed without a name is assigned a random one, which save() will capture — and which will not exist after a page reload, breaking restore(). Duplicate Table names in the engine now error instead of silently overwriting each other.
@perspective-dev/workspace is removed
The package is deprecated and its functionality moved into <perspective-viewer> itself. Migrating:
Replace <perspective-workspace> with <perspective-viewer>; addTable(), removeTables() and the tables property are gone — host your Tables on a Client (they already have a name) and load() the client.
Workspace-style save/restore is viewer.saveWorkspace() / viewer.restoreWorkspace(); per-panel configs use save({panel}) / restore(config, {panel}). restore() to an unknown panel name creates it (upsert). Old workspace layout JSON is not the same format — panels is keyed by panel id with per-panel ViewerConfig values and layout is a regular-layout tree; plan a one-time conversion for persisted layouts.
The selectable attribute master/detail mechanism is replaced by the SELECT_ROW_TREE edit mode and the Master/Detail toggle; master state is captured in the masters field of the workspace config.
Events: workspace-layout-update and workspace-new-view are replace by perspective-config-update (which now also fires for layout changes), perspective-global-filter-update (detail: Filter[]), and the new panel field on perspective-click/perspective-select details.
Options-dict method signatures
Several <perspective-viewer> methods were update to options-dict signatures as part of the workspace merge (#3201): methods that previously took positional arguments (e.g. getSelection, getEditPort, export, download, copy, reset) now take an options object which may include a panel key, and eject(options) is client-scoped. Plain no-argument calls behave as before against the active panel.
Replace leaves_only with group_rollup_mode
The per-query leaves_only parameter on to_columns/to_rows/to_ndjson is removed. Set group_rollup_mode: "flat" in ViewConfig instead (or "total" for just the grand-total row; "rollup" is the default hierarchical behavior).
Replace @perspective-dev/viewer-d3fc and @perspective-dev/viewer-openlayers with @perspective-dev/viewer-charts
viewer-d3fc is deleted. @perspective-dev/viewer-charts is a compatible superset of its predecessor's API (same chart types and more), but it is a new package name with new internals — update your imports and bundler config. Note the Render Warning thresholds are now 1,000,000 rows, so warnings you previously suppressed may simply be gone.
@perspective-dev/viewer-openlayers is deprecated, the Maps and Density chart types in @perspective-dev/viewer-charts replace it.
Custom plugins and themes API
The condensed plugin API breaks any custom plugin implementation: the small metadata methods are consolidated into a single plugin_static_config call, plugin_config_schema / column_config_schema declare configuration to the viewer (which renders the new plugin-config tab for you), and save() is removed — plugin state is now managed viewer-side. See the API docs for these methods.
All exported CSS variables were renamed to the --psp-{module}--{component?}--{property} convention,
Jupyter does not need the old labextension install
PerspectiveWidget ships entirely inside the perspective-python wheel via AnyWidget. Uninstall any @perspective-dev/jupyterlab labextension plumbing from your environments and Dockerfiles and just pip install "perspective-python[jupyter]". @perspective-dev/jupyterlab is now an optional, renderers-only extension (the "Open With → Perspective" file viewers) — install it only if you want that feature.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
TL;DR:
<perspective-viewer>is now a workspace. Multi-panel dashboards, drag-and-drop docking and master/detail cross-filtering are core features.<perspective-viewer>and read via Apache Arrow IPC.@perspective-dev/viewer-charts— GPU-rendered, context pooled, multi-threaded, with twelve chart types (and maps!), plus loads of new configuration options.PerspectiveWidgetis now an AnyWidget — onepip install, no labextensions, identical behavior in JupyterLab, VSCode, Colab, Marimo — and via new Emscripten wheels, JupyterLite.Client::join,group_rollup_mode,coalesce, thegmvaggregate,page_to_diskdisk-backed tables, and a batch of hot-path performance work.arm64andemscripten(pyodide/jupyterlite), plus a WebAssembly Memory64 (wasm64) build of the engine for 16GB in-browser heaps.@perspective-dev/viewer-d3fc,@perspective-dev/viewer-openlayersand@perspective-dev/workspacewill be deprecated.See the Migration Notes at the end for everything that changed incompatibly since
v4.0.0.<perspective-viewer>is now a WorkspaceThe
@perspective-dev/workspacepackage has been deprecated, and its functionality is now built directly into<perspective-viewer>itself, rather than as a separatelumino-based TypeScript wrapper (#3199). Every<perspective-viewer>element is now a workspace, with panels that can be split, stacked into tabs, resized, duplicated, and closed via the UI or the API:Multi-panel layouts — panels are arranged by the
regular-layoutengine (splits, tab stacks, drag-to-dock, drag-to-resize) inside the viewer element.Master/detail global filters — toggling a panel to Master puts its plugin into row-selection mode; selections contribute filter clauses to an element-level global filter set, applied as a transient overlay to every detail panel's view (never written into their saved configs). A global filter bar displays and edits the active clauses.
Panel API —
addPanel(config),removePanel(id),getPanelNames(),getActivePanel(),setActivePanel(id), plus apaneloption on the existing single-viewer methods (save,restore,getView,getTable,reset,export, …) which target the active panel by default.Whole-element persistence —
save()andrestore()still emit and accept the classic single-panelViewerConfig(existing round-trips are backwards-compatible, andrestore({panel})upserts — restoring to a panel name that doesn't exist creates it). The newsaveWorkspace()/restoreWorkspace()pair captures the entire element — layout tree, per-panel configs, active panel, masters and global filters — in one token:{ "version": "...", "active": "PANEL_ID_0", "layout": { "...": "regular-layout tree" }, "panels": { "PANEL_ID_0": { "...": "per-panel ViewerConfig" } }, "global_filters": [["State", "==", "Texas"]], "masters": ["PANEL_ID_1"] }Events —
perspective-clickandperspective-selectdetails gain apanelfield identifying the originating panel, and a newperspective-global-filter-updateevent fires when the global filter set changes.See the migration notes below for
@perspective-dev/workspacemigration notes.Viewers bind
Clients, notTablesIn
v5.0.0,load()takes aClient, which the element binds to exclusively, and theTableis named by a newtablefield inViewerConfig:Calling
load()with aClientdoes not render (noTableis selected yet);restore()selects both theTableand its config (guaranteeing one atomic render once both are called and the element is mounted).load(table)still works as a legacy convenience — it is internally equivalent toload(await table.get_client())followed byrestore({table: await table.get_name()})— but new code should pass aClient.One footgun to be aware of:
Tables constructed without anameget a random one, which will be captured bysave()and won't match on the next page load — always name your tables (though you can capture the name withget_name()).New
@perspective-dev/viewer-chartscharting package@perspective-dev/viewer-d3fchas been replaced by@perspective-dev/viewer-charts— a ground-up rewrite that renders Perspective views on the GPU with custom GLSL shaders, a shared buffer pool for GPU memory reuse, and zero-copy Arrow data transfer to a Web Worker host (#3160, #3166, #3176).@perspective-dev/viewer-openlayers.Clientinstance, taking Arrow slices directly from the engine without ever touching the main thread. An optionalblitmode (default) copies frames to 2DCanvasinstances to sidestep the browser's WebGL context limit, so dozens of charts can share one page.split_bygroups can render as a grid of sub-plots with shared axes and zoom routing between facets (#3168).chart_type∈ {Bar, Line, Scatter, Area} and astackoverride, closing some of the oldest feature requests in the tracker (#2938, #2390, #2331).interpolateoption for Line charts (#3182).A new plugin configuration tab
A new Plugin API (#3176) which lets plugins describe their configuration programmatically to the
<perspective-viewer>host:plugin_config_schemaandcolumn_config_schemadeclare a plugin's options, and a generated plugin-configuration tab will show up in the viewer's settings panel. Plugins also no longer implementsave()(This is a breaking change for custom plugin authors — see migration notes).Notebooks: AnyWidget, VSCode and JupyterLite
PerspectiveWidget's dual-packageipywidgets+ JupyterLab labextension architecture has been replaced with a single AnyWidget-based widget shipped entirely from theperspective-pythonwheel (#3192). The widget now runs identically in JupyterLab, classic Jupyter Notebook, VSCode notebooks, Colab, Marimo, etc. — with no per-host labextension install:pip install "perspective-python[jupyter]"You can also install the optional
@perspective-dev/jupyterlab, which is slimmed down to a renderers-only labextension (the CSV/JSON/Arrow "Open With → Perspective" file viewers).New Python architecture support
Perspective's Python distribution also gained two new architectures:
perspective-pythonnow installs in Pyodide, which combined with the AnyWidget port meansPerspectiveWidgetworks in JupyterLite — a full Perspective notebook experience with no server.Virtual Servers: DuckDB, ClickHouse and Polars
Perspective 5.0.0 ships the Virtual Server API — implement a small handler interface and
<perspective-viewer>will drive your query engine instead of Perspective's own, translating everyViewerConfigoption (group_bys, filters, sorts, expressions) into queries against the backing store. Three implementations are included for Python, and two for JavaScript (Polars is Python-only):DuckDB (#3062) DEMO
ClickHouse (#3119) — JavaScript and Python implementations that translate Perspective queries into ClickHouse SQL, sharing a generic SQL model in Rust with the DuckDB backend.
Polars (#3125) — a Python implementation over Polars dataframes, with a test suite mirroring the DuckDB one.
While none of these servers support real-time updates, they should otherwise by fully-featured under
<perspective-viewer>- except, you will see e.g. DuckDB's aggregates, DuckDB's expression syntax, etc. in the UI, rather thanperspective-server's.C++ Engine
Performance
Lots of performance improvements, some highlights:
-fopenmp-simd, and-msimd128/-mrelaxed-simdfor WASM) #3141.Viewregistration and CSV loading were overhauled in #3165 — bulk-load fast paths and zero-copy column aliasing betweenTableandViewreplace per-column deep clones.View::with_typed_arrays, a new zero-copy JS API that hands your callbackTypedArrayviews directly over the Arrow buffers (with an optionalfloat32downcast mode for half-memory GPU uploads) — this is the API the new charts package is built on.Client::joinClient::joincreates read-only, reactive joined tables in JavaScript, Python and Rust (#3143):Joins support
inner(default),leftandoutertypes, an optionalright_onfor differently-named key columns, correct duplicate (cross-product) handling with left-precedence natural order for visual stability — and they're streaming: updates to either sourceTableautomatically recompute the join and propagate to any derivedView. Joins can be chained, and aTablecreated byjoinbehaves like any other (minus writes) — including in a<perspective-viewer>.group_rollup_modeThe per-query
leaves_onlyserialization flag has been replaced bygroup_rollup_mode, a first-classViewConfigproperty with three variants (#3132, #3135):"rollup"(the classic hierarchical tree),"flat"(leaf rows only — the datagrid hides tree expanders and renders flat column headers), and"total"(only the grand-total row, aggregated across all rows, nogroup_byrequired). It's a UI dropdown in the settings panel, works on virtual servers, and applies to every serialization method and plugin, not justto_columns.page_to_diskmode, new aggregates and functionspage_to_diskis a newClient.table(...)option that backs aTable's master columns with disk-resident storage instead of heap, allowing datasets larger than addressable memory (#3185) — with a runtime-appropriate backend on every platform:mmapfor Python/Rust, OPFS in the browser, andnode:fsfor Node.js.coalesceadded to ExprTK (#3172).gmv("gross market value") is a new aggregate forgroup_by/split_bytrees (#3180): a leaf is a plain signedsum, while a parent is the sum over its immediate children of|sum(child_subtree)|— the natural aggregate for netted long/short position books. It recursively recalculates per depth, so it is noticeably slower thansum; use it wisely.Memory64 (wasm64) builds
@perspective-dev/servernow publishes a WebAssembly Memory64 build,perspective-server.memory64.wasm, alongside the standard wasm32 binary (#3203) — raising the in-browser engine's heap ceiling from 4GB to 16GB.init_servernow accepts both binaries at once lazily; the engine feature-detects Memory64 support in the host and downloads only the binary it selects:Registering only the wasm32 binary (as in prior versions, which remains supported) opts out entirely, while the stock CDN and Node.js entrypoints register both, so capable hosts upgrade automatically (and hosts without Memory64 support fall back to wasm32).
Datagrid
The datagrid has added
label-barmode for numeric columns, which overlays the formatted value on top of the proportional bar fill usingmix-blend-mode: differencefor contrast against the fill color (#3156):Also added
SELECT_ROW_TREE, a tree-aware row-selection edit mode that tracks selections by tree identity rather than flat row index — the machinery behind the workspace's master/detail filtering.Themes
Theming was redesigned in #3146: all exported CSS variables were renamed to a consistent
--psp-{module}--{component?}--{property}format, and all icon SVGs now carry correct intrinsic dimensions so they can be swapped for arbitrarily-sized replacements. The workspace merger added per-panel themes (#3107, #3150), and a new "Botanical" and "Phosphor" theme (#3135).Migration Notes (
v4.0.0→v5.0.0)<perspective-viewer>.load()takes aClientload(table)still works as a legacy path, but the primary signature is nowload(client), with theTablenamed by the newtablefield ofViewerConfiginsave()/restore():A
Tableconstructed without anameis assigned a random one, whichsave()will capture — and which will not exist after a page reload, breakingrestore(). DuplicateTablenames in the engine now error instead of silently overwriting each other.@perspective-dev/workspaceis removedThe package is deprecated and its functionality moved into
<perspective-viewer>itself. Migrating:<perspective-workspace>with<perspective-viewer>;addTable(),removeTables()and thetablesproperty are gone — host yourTables on aClient(they already have aname) andload()the client.viewer.saveWorkspace()/viewer.restoreWorkspace(); per-panel configs usesave({panel})/restore(config, {panel}).restore()to an unknown panel name creates it (upsert). Old workspace layout JSON is not the same format —panelsis keyed by panel id with per-panelViewerConfigvalues andlayoutis aregular-layouttree; plan a one-time conversion for persisted layouts.selectableattribute master/detail mechanism is replaced by theSELECT_ROW_TREEedit mode and the Master/Detail toggle; master state is captured in themastersfield of the workspace config.workspace-layout-updateandworkspace-new-vieware replace byperspective-config-update(which now also fires for layout changes),perspective-global-filter-update(detail:Filter[]), and the newpanelfield onperspective-click/perspective-selectdetails.Options-dict method signatures
Several
<perspective-viewer>methods were update to options-dict signatures as part of the workspace merge (#3201): methods that previously took positional arguments (e.g.getSelection,getEditPort,export,download,copy,reset) now take an options object which may include apanelkey, andeject(options)is client-scoped. Plain no-argument calls behave as before against the active panel.Replace
leaves_onlywithgroup_rollup_modeThe per-query
leaves_onlyparameter onto_columns/to_rows/to_ndjsonis removed. Setgroup_rollup_mode: "flat"inViewConfiginstead (or"total"for just the grand-total row;"rollup"is the default hierarchical behavior).Replace
@perspective-dev/viewer-d3fcand@perspective-dev/viewer-openlayerswith@perspective-dev/viewer-chartsviewer-d3fcis deleted.@perspective-dev/viewer-chartsis a compatible superset of its predecessor's API (same chart types and more), but it is a new package name with new internals — update your imports and bundler config. Note the Render Warning thresholds are now 1,000,000 rows, so warnings you previously suppressed may simply be gone.@perspective-dev/viewer-openlayersis deprecated, the Maps and Density chart types in@perspective-dev/viewer-chartsreplace it.Custom plugins and themes API
The condensed plugin API breaks any custom plugin implementation: the small metadata methods are consolidated into a single
plugin_static_configcall,plugin_config_schema/column_config_schemadeclare configuration to the viewer (which renders the new plugin-config tab for you), andsave()is removed — plugin state is now managed viewer-side. See the API docs for these methods.All exported CSS variables were renamed to the
--psp-{module}--{component?}--{property}convention,Jupyter does not need the old labextension install
PerspectiveWidgetships entirely inside theperspective-pythonwheel via AnyWidget. Uninstall any@perspective-dev/jupyterlablabextension plumbing from your environments and Dockerfiles and justpip install "perspective-python[jupyter]".@perspective-dev/jupyterlabis now an optional, renderers-only extension (the "Open With → Perspective" file viewers) — install it only if you want that feature.All reactions