docs/md/how_to/javascript/save_restore.md
<perspective-viewer> is persistent, in that its entire state (sans the data
itself) can be serialized or deserialized. This include all column, filter,
pivot, expressions, etc. properties, as well as datagrid style settings, config
panel visibility, and more. This overloaded feature covers a range of use cases:
<perspective-viewer>'s initial state after a load() call.localStorage or a server.To retrieve the entire state as a JSON-ready JavaScript object, use the save()
method. save() also supports a few other formats such as "arraybuffer" and
"string" (base64, not JSON), which you may choose for size at the expense of
easy migration/manual-editing.
const json_token = await elem.save();
const string_token = await elem.save("string");
For any format, the serialized token can be restored to any
<perspective-viewer> with a Table of identical schema, via the restore()
method. Note that while the data for a token returned from save() may differ,
generally its schema may not, as many other settings depend on column names and
types.
await elem.restore(json_token);
await elem.restore(string_token);
As restore() dispatches on the token's type, it is important to make sure that
these types match! A common source of error occurs when passing a
JSON-stringified token to restore(), which will assume base64-encoded msgpack
when a string token is used.
// This will error!
await elem.restore(JSON.stringify(json_token));
Using the JSON format, every facet of a <perspective-viewer>'s configuration
can be manipulated from JavaScript using the restore() method. The valid
structure of properties is described via the
ViewerConfigUpdate
and embedded
ViewConfigUpdate
type declarations (both generated from the Rust definitions), and the
View chapter of the documentation which has
several examples for each ViewConfig property.
// Set the plugin (will also update `columns` to plugin-defaults)
await elem.restore({ plugin: "X Bar" });
// Update plugin and columns (only draws once)
await elem.restore({ plugin: "X Bar", columns: ["Sales"] });
// Open the config panel
await elem.restore({ settings: true });
// Create an expression
await elem.restore({
columns: ['"Sales" + 100'],
expressions: { "New Column": '"Sales" + 100' },
});
// ERROR if the column does not exist in the schema or expressions
// await elem.restore({columns: ["\"Sales\" + 100"], expressions: {}});
// Add a filter
await elem.restore({ filter: [["Sales", "<", 100]] });
// Add a sort, don't remove filter
await elem.restore({ sort: [["Prodit", "desc"]] });
// Reset just filter, preserve sort
await elem.restore({ filter: undefined });
// Reset all properties to default e.g. after `load()`
await elem.reset();
Another effective way to quickly create a token for a desired configuration is
to simply copy the token returned from save() after settings the view manually
in the browser. The JSON format is human-readable and should be quite easy to
tweak once generated, as save() will return even the default settings for all
properties. You can call save() in your application code, or e.g. through the
Chrome developer console:
// Copy to clipboard
copy(await document.querySelector("perspective-viewer").save());
save() and restore() operate on a single panel — the active one by
default, or a specific panel via their optional { panel } selector (e.g.
await elem.save({ panel: "my-panel" })). If restore()'s panel names no
existing panel, a new panel is created with that id.
A <perspective-viewer> may host multiple panels. To serialize or restore the
whole element — every panel plus the layout and cross-filter state — use
saveWorkspace() and restoreWorkspace() instead:
const workspace_token = await elem.saveWorkspace();
await elem.restoreWorkspace(workspace_token);
A saveWorkspace() token is a WorkspaceConfig
({ version, layout, panels, ... }), not a ViewerConfig — passing it to the
single-panel restore() will not restore the layout (its panels/layout
keys are ignored).
Per-column color styling lives in a panel's columns_config, keyed by column
name, and every color-scale value is a string usable verbatim in CSS:
| Kind | Value |
|---|---|
| color | "#rrggbb" (#rgb, rgb() and rgba() are accepted on input) |
| palette | "linear-gradient(to right, #rrggbb, #rrggbb, …)" — N colors, no positions |
| gradient | "linear-gradient(to right, #rrggbb 0%, #rrggbb 37.5%, …)" — every stop positioned |
Which reader applies is decided by the style control's kind (the datagrid's
fg_colors/bg_colors and the charts' gradient are gradients; palette is a
palette), never by inspecting the string — a position anywhere in a palette is
rejected, while a gradient may omit positions on input (the CSS
implicit-position rules fill them) and may carry any direction token, which is
normalized to to right. Values equal to the plugin's default are not
serialized.
await viewer.restore({
plugin: "Datagrid",
columns_config: {
Profit: {
number_bg_mode: "gradient",
bg_colors: "linear-gradient(to right, #ff0000, #ffffff, #0000ff)",
},
},
});
Any of these may instead be a reference to a CSS custom property of the same
kind — "var(--psp-user--color-<name>)", "var(--psp-user--palette-<name>)" or
"var(--psp-user--gradient-<name>)". References are resolved when the config is
written, against the element's computed style: the palette of the last
restoreWorkspace() (below) takes precedence, then any --psp-user--* property
a theme or the page defines on the element. An unresolvable reference is dropped
(the plugin's default renders). Panels hold literals from then on — save()
always emits literals, and the column style tab always edits a literal.
saveWorkspace() emits a palette: every color value in use across the
panels is written in panels as a var() reference, and the top-level
palette map (custom property name → value) carries each referenced definition
once. Names are stable — a value keeps the name the last restoreWorkspace()
gave it when the values match, reuses a theme entry's name when it matches one
(--psp-user--<kind>-1, -2, … are discovered by contiguous numbering), and
otherwise takes a fresh --psp-user--<kind>-N. restoreWorkspace() applies
palette to the element as inline custom properties (replacing any previously
restored palette) before the panels' references resolve — which also makes it
the way to inject a brand or theme variation for a workspace to draw on.
By default only the values the panels reference are serialized; a restored
palette's unused entries, and values pinned during a session, are in-session
state. Pass { full_palette: true } to emit the element's whole set — in-use
values unioned with the last restored palette and anything pinned since — for a
symmetric round trip:
const used_only = await elem.saveWorkspace();
const everything = await elem.saveWorkspace({ full_palette: true });
In the column style tab, each color field's Load control lists the element's set (plus theme entries) for every panel and applies a chosen entry's value to the field; Pin — offered while the field holds a value the restored set lacks — adds that value to the set for the rest of the session.
await elem.restoreWorkspace({
palette: {
"--psp-user--gradient-heat":
"linear-gradient(to right, #0366d6, #ff7f0e)",
"--psp-user--palette-brand":
"linear-gradient(to right, #2771a8, #8b86ff, #ff471e)",
},
panels: {
sales: {
table: "superstore",
plugin: "Heatmap",
columns: ["Sales"],
columns_config: {
Sales: { gradient: "var(--psp-user--gradient-heat)" },
},
},
},
});
A malformed palette entry (a key outside
--psp-user--{gradient,palette,color}-, or a value its kind rejects) fails the
whole restoreWorkspace() before any panel changes.