Repository navigation
Expand file tree
/
Copy pathperspective-docs.json
More file actions
1 lines (1 loc) · 230 KB
/
Copy pathperspective-docs.json
File metadata and controls
1 lines (1 loc) · 230 KB
1
{"schemas":{"ViewerConfigUpdate":{"$schema":"http://json-schema.org/draft-07/schema#","$ref":"#/definitions/ViewerConfigUpdate","definitions":{"ViewerConfigUpdate":{"type":"object","properties":{"version":{"type":["null","string"],"description":"The `@perspective-dev/viewer` version a saved config was written by, used to migrate older tokens. Omit it — the viewer stamps the current version on save."},"plugin":{"type":["null","string"],"description":"Name of the visualization plugin to switch to, from the set registered on the page (the `list_plugins` agent tool, or the plugin picker). Changing it changes what the view fields MEAN: `columns` is positional and every plugin reads the positions differently, and `group_by`/`split_by` draw different things per plugin — so read the new plugin's roles before writing `columns` for it."},"title":{"type":["null","string"],"description":"Panel title, shown in its tab. `null` restores the default title; omitting the field leaves the current one."},"table":{"anyOf":[{"type":"null"},{"$ref":"#/definitions/TableRef"}],"description":"The `Table` to render: a name as hosted on this panel's `Client`, or an inline derived-table definition to materialize (reusing a structurally identical derived `Table` when one exists). Rebinding an existing panel keeps the rest of the config, so column names that do not exist in the new table will fail validation."},"theme":{"type":["null","string"],"description":"Theme NAME (e.g. `\"Pro Dark\"`) — not a CSS value. Valid names are the Perspective themes loaded on the page, re-scanned by `resetThemes()`. `null` selects the default."},"settings":{"type":["null","boolean"],"description":"Whether the settings sidebar is OPEN. Purely cosmetic chrome — it does not affect what the viewer renders, and it is element-level rather than per-panel."},"plugin_config":{"anyOf":[{"type":"null"},{"type":"object","additionalProperties":{"$ref":"#/definitions/JsonValue"}}],"description":"Plugin-wide settings (as opposed to the per-column [`Self::columns_config`]). The viewer passes these through opaquely — their valid keys are defined by the ACTIVE plugin and vary by plugin and by state, so query them with the `get_style_schema` agent tool rather than guessing."},"columns_config":{"anyOf":[{"type":"null"},{"type":"object","additionalProperties":{"type":"object","additionalProperties":{"$ref":"#/definitions/JsonValue"}}}],"description":"Per-column styling — formatting, colors, and other per-column controls — keyed by column name. Opaque to the viewer and plugin-defined like [`Self::plugin_config`]; `get_style_schema` reports the valid keys for a given column under the active plugin."},"group_by":{"type":"array","items":{"type":"string"},"description":"A group by _groups_ the dataset by the unique values of each column used as a group by - a close analogue in SQL to the `GROUP BY` statement. The underlying dataset is aggregated to show the values belonging to each group, and a total row is calculated for each group, showing the currently selected aggregated value (e.g. `sum`) of the column. Group by are useful for hierarchies, categorizing data and attributing values, i.e. showing the number of units sold based on State and City. In Perspective, group by are represented as an array of string column names to pivot, are applied in the order provided; For example, a group by of `[\"State\", \"City\", \"Postal Code\"]` shows the values for each Postal Code, which are grouped by City, which are in turn grouped by State."},"split_by":{"type":"array","items":{"type":"string"},"description":"A split by _splits_ the dataset by the unique values of each column used as a split by. The underlying dataset is not aggregated, and a new column is created for each unique value of the split by. Each newly created column contains the parts of the dataset that correspond to the column header, i.e. a `View` that has `[\"State\"]` as its split by will have a new column for each state. In Perspective, Split By are represented as an array of string column names to pivot."},"columns":{"type":"array","items":{"type":["string","null"]},"description":"The `columns` property specifies which columns should be included in the [`crate::View`]'s output. This allows users to show or hide a specific subset of columns, as well as control the order in which columns appear to the user. This is represented in Perspective as an array of string column names."},"filter":{"type":"array","items":{"$ref":"#/definitions/Filter"},"description":"The `filter` property specifies columns on which the query can be filtered, returning rows that pass the specified filter condition. This is analogous to the `WHERE` clause in SQL. There is no limit on the number of columns where `filter` is applied, but the resulting dataset is one that passes all the filter conditions, i.e. the filters are joined with an `AND` condition.\n\nPerspective represents `filter` as an array of arrays, with the values of each inner array being a string column name, a string filter operator, and a filter operand in the type of the column."},"sort":{"type":"array","items":{"$ref":"#/definitions/Sort"},"description":"The `sort` property specifies columns on which the query should be sorted, analogous to `ORDER BY` in SQL. A column can be sorted regardless of its data type, and sorts can be applied in ascending or descending order. Perspective represents `sort` as an array of arrays, with the values of each inner array being a string column name and a string sort direction. When `column-pivots` are applied, the additional sort directions `\"col asc\"` and `\"col desc\"` will determine the order of pivot columns groups.\n\n`sort` is the ONLY thing that orders a `View`'s rows — without it they keep the `Table`'s natural (insertion) order, which any consumer reading rows sequentially will reflect. Not to be confused with a window column's `order_by`, which orders rows WITHIN a window frame and does not reorder the `View`."},"expressions":{"$ref":"#/definitions/Expressions","description":"The `expressions` property specifies _new_ columns in Perspective that are created using existing column values or arbitary scalar values defined within the expression. In `<perspective-viewer>`, expressions are added using the \"New Column\" button in the side panel."},"windows":{"$ref":"#/definitions/Windows","description":"The `windows` property declares ordered, partitioned rolling computations (moving aggregates, cumulative sums) as _new_ columns keyed by output alias (`{\"name\": {...spec}}`, symmetric with `expressions`), analogous to SQL window functions. See [`crate::config::WindowSpec`]."},"aggregates":{"type":"object","additionalProperties":{"$ref":"#/definitions/Aggregate"},"description":"Aggregates perform a calculation over an entire column, and are displayed when one or more [Group By](#group-by) are applied to the `View`. Aggregates can be specified by the user, or Perspective will use the following sensible default aggregates based on column type:\n\n- \"sum\" for `integer` and `float` columns\n- \"count\" for all other columns\n\nPerspective provides a selection of aggregate functions that can be applied to columns in the `View` constructor using a dictionary of column name to aggregate function name.\n\nAn aggregate also determines the column's RESULT TYPE, which need not match the input: `\"count\"` yields an `integer` whatever it counts, so a `date` column left on the default `\"count\"` is an `integer` in the resulting `View` — no longer a date. Set an aggregate that preserves the type (e.g. `\"any\"`, `\"last\"`) when the original type matters, such as a date used as a chart axis."},"group_by_depth":{"type":"number"},"filter_op":{"$ref":"#/definitions/FilterReducer"},"group_rollup_mode":{"$ref":"#/definitions/GroupRollupMode"},"split_rollup_mode":{"$ref":"#/definitions/SplitRollupMode"}},"additionalProperties":false},"TableRef":{"anyOf":[{"type":"string"},{"type":"object","additionalProperties":false,"properties":{"table":{"$ref":"#/definitions/TableRef"},"group_by":{"type":"array","items":{"type":"string"}},"split_by":{"type":"array","items":{"type":"string"}},"sort":{"type":"array","items":{"$ref":"#/definitions/Sort"}},"filter":{"type":"array","items":{"$ref":"#/definitions/Filter"}},"group_rollup_mode":{"$ref":"#/definitions/GroupRollupMode"},"split_rollup_mode":{"$ref":"#/definitions/SplitRollupMode"},"filter_op":{"$ref":"#/definitions/FilterReducer"},"expressions":{"$ref":"#/definitions/Expressions"},"windows":{"$ref":"#/definitions/Windows"},"columns":{"type":"array","items":{"type":["string","null"]}},"aggregates":{"type":"object","additionalProperties":{"$ref":"#/definitions/Aggregate"}},"group_by_depth":{"type":["number","null"]}},"required":["aggregates","columns","expressions","filter","group_by","group_rollup_mode","sort","split_by","split_rollup_mode","table"]}],"description":"What may occupy a `table` position in a single-panel config: a hosted `Table`'s name, or an inline derived-table definition recursing through its own `table` field until a concrete name."},"Sort":{"type":"array","minItems":2,"items":[{"type":"string"},{"$ref":"#/definitions/SortDir"}],"maxItems":2},"SortDir":{"type":"string","enum":["none","desc","asc","col desc","col asc","desc abs","asc abs","col desc abs","col asc abs"]},"Filter":{"type":"array","minItems":3,"items":[{"type":"string"},{"type":"string"},{"$ref":"#/definitions/FilterTerm"}],"maxItems":3},"FilterTerm":{"anyOf":[{"type":"array","items":{"$ref":"#/definitions/Scalar"}},{"$ref":"#/definitions/Scalar"}]},"Scalar":{"type":["number","string","boolean","null"],"description":"This type represents the ViewConfig serializable type, which must be JSON safe."},"GroupRollupMode":{"type":"string","enum":["rollup","flat","total"]},"SplitRollupMode":{"type":"string","enum":["flat","rollup"],"description":"The `split_by` corollary to [`GroupRollupMode`]. `Flat` (the default, matching this crate's historical behavior) emits only full-depth split combinations as columns; `Rollup` additionally emits grand-total and subtotal column groups in \"totals before\" order. There is no `Total` variant - an empty `split_by` already expresses a single grand-total column group."},"FilterReducer":{"type":"string","enum":["and","or"]},"Expressions":{"type":"object","additionalProperties":{"type":"string"}},"Windows":{"type":"object","additionalProperties":{"$ref":"#/definitions/WindowSpec"},"description":"The window columns of a `ViewConfig`, keyed by output column alias - symmetric with `expressions` (`{\"name\": {...spec}}`). An alias must not collide with a `Table` column, expression alias, or another window's key."},"WindowSpec":{"type":"object","properties":{"column":{"type":"string","description":"The input column - either a real `Table` column or an expression alias from the same `ViewConfig`."},"aggregate":{"type":"string"},"partition_by":{"type":"array","items":{"type":"string"},"description":"Columns whose distinct value tuples partition the rows; empty partitions the whole `Table` as one group."},"order_by":{"anyOf":[{"$ref":"#/definitions/WindowSort"},{"type":"null"}],"description":"The `Table` column which orders each partition, and the direction. This orders rows WITHIN the window frame only — it does not reorder the `View`, which is what the view-level `sort` does."},"rows":{"type":["number","null"],"description":"A frame of the `rows` preceding each row, plus the row itself. Mutually exclusive with `range` and `cumulative`."},"range":{"type":["number","null"],"description":"A frame of the rows whose `order_by` value lies within `range` of each row's, requiring a numeric or temporal `order_by`. Mutually exclusive with `rows` and `cumulative`."},"cumulative":{"type":["boolean","null"],"description":"`true` frames all rows from the partition start through each row. Default can be omitted."},"offset":{"type":["number","null"],"description":"Row offset for `lag`/`lead` (default 1)."},"alpha":{"type":["number","null"],"description":"Smoothing factor in `(0, 1]` for `ema`."}},"required":["column","aggregate"],"additionalProperties":false,"description":"The serialized form of [`WindowSpec`]."},"WindowSort":{"type":"array","minItems":2,"items":[{"type":"string"},{"$ref":"#/definitions/WindowSortDir"}],"maxItems":2,"description":"The `Table` column which orders each partition, with its direction - serialized as a two-element array (`[\"ts\", \"desc\"]`), symmetric with the `ViewConfig`'s `sort` field."},"WindowSortDir":{"type":"string","enum":["asc","desc"],"description":"A window's order direction. A dedicated two-variant enum rather than [`crate::config::SortDir`] - the column-sort extras (`col asc`, `abs`, `none`) are meaningless inside a window frame and unrepresentable here."},"Aggregate":{"anyOf":[{"type":"string"},{"type":"array","minItems":2,"items":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"maxItems":2}]},"JsonValue":{"anyOf":[{"type":"number"},{"type":"string"},{"type":"boolean"},{"type":"array","items":{"$ref":"#/definitions/JsonValue"}},{"type":"object","additionalProperties":{"$ref":"#/definitions/JsonValue"}},{"type":"null"}]}}},"ViewerConfigInitial":{"$schema":"http://json-schema.org/draft-07/schema#","$ref":"#/definitions/ViewerConfigInitial","definitions":{"ViewerConfigInitial":{"type":"object","properties":{"table":{"type":"string","description":"Name of the `Table` the new panel renders, as hosted on the `Client`. REQUIRED: a placed panel with no table binding would be permanently blank."},"version":{"type":"string","description":"The `@perspective-dev/viewer` version a saved config was written by. Omit it when creating a panel."},"plugin":{"type":"string","description":"Name of the visualization plugin, from the set registered on the page (the `list_plugins` agent tool, or the plugin picker). Decides what the view fields MEAN: `columns` is positional and every plugin reads the positions differently, and `group_by`/`split_by` draw different things per plugin. Absent uses the default plugin."},"title":{"type":"string","description":"Panel title, shown in its tab. Absent renders the default title."},"theme":{"type":"string","description":"Theme NAME (e.g. `\"Pro Dark\"`) — not a CSS value. Valid names are the Perspective themes loaded on the page. Absent uses the default."},"plugin_config":{"type":"object","additionalProperties":{"$ref":"#/definitions/JsonValue"},"description":"Plugin-wide settings (as opposed to the per-column [`Self::columns_config`]). Opaque to the viewer and defined by the ACTIVE plugin; query the valid keys with `get_style_schema` rather than guessing."},"columns_config":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{"$ref":"#/definitions/JsonValue"}},"description":"Per-column styling — formatting, colors, and other per-column controls — keyed by column name. Plugin-defined like [`Self::plugin_config`]; see `get_style_schema`."},"group_by":{"type":"array","items":{"type":"string"},"description":"A group by _groups_ the dataset by the unique values of each column used as a group by - a close analogue in SQL to the `GROUP BY` statement. The underlying dataset is aggregated to show the values belonging to each group, and a total row is calculated for each group, showing the currently selected aggregated value (e.g. `sum`) of the column. Group by are useful for hierarchies, categorizing data and attributing values, i.e. showing the number of units sold based on State and City. In Perspective, group by are represented as an array of string column names to pivot, are applied in the order provided; For example, a group by of `[\"State\", \"City\", \"Postal Code\"]` shows the values for each Postal Code, which are grouped by City, which are in turn grouped by State."},"split_by":{"type":"array","items":{"type":"string"},"description":"A split by _splits_ the dataset by the unique values of each column used as a split by. The underlying dataset is not aggregated, and a new column is created for each unique value of the split by. Each newly created column contains the parts of the dataset that correspond to the column header, i.e. a `View` that has `[\"State\"]` as its split by will have a new column for each state. In Perspective, Split By are represented as an array of string column names to pivot."},"columns":{"type":"array","items":{"type":["string","null"]},"description":"The `columns` property specifies which columns should be included in the [`crate::View`]'s output. This allows users to show or hide a specific subset of columns, as well as control the order in which columns appear to the user. This is represented in Perspective as an array of string column names."},"filter":{"type":"array","items":{"$ref":"#/definitions/Filter"},"description":"The `filter` property specifies columns on which the query can be filtered, returning rows that pass the specified filter condition. This is analogous to the `WHERE` clause in SQL. There is no limit on the number of columns where `filter` is applied, but the resulting dataset is one that passes all the filter conditions, i.e. the filters are joined with an `AND` condition.\n\nPerspective represents `filter` as an array of arrays, with the values of each inner array being a string column name, a string filter operator, and a filter operand in the type of the column."},"sort":{"type":"array","items":{"$ref":"#/definitions/Sort"},"description":"The `sort` property specifies columns on which the query should be sorted, analogous to `ORDER BY` in SQL. A column can be sorted regardless of its data type, and sorts can be applied in ascending or descending order. Perspective represents `sort` as an array of arrays, with the values of each inner array being a string column name and a string sort direction. When `column-pivots` are applied, the additional sort directions `\"col asc\"` and `\"col desc\"` will determine the order of pivot columns groups.\n\n`sort` is the ONLY thing that orders a `View`'s rows — without it they keep the `Table`'s natural (insertion) order, which any consumer reading rows sequentially will reflect. Not to be confused with a window column's `order_by`, which orders rows WITHIN a window frame and does not reorder the `View`."},"expressions":{"$ref":"#/definitions/Expressions","description":"The `expressions` property specifies _new_ columns in Perspective that are created using existing column values or arbitary scalar values defined within the expression. In `<perspective-viewer>`, expressions are added using the \"New Column\" button in the side panel."},"windows":{"$ref":"#/definitions/Windows","description":"The `windows` property declares ordered, partitioned rolling computations (moving aggregates, cumulative sums) as _new_ columns keyed by output alias (`{\"name\": {...spec}}`, symmetric with `expressions`), analogous to SQL window functions. See [`crate::config::WindowSpec`]."},"aggregates":{"type":"object","additionalProperties":{"$ref":"#/definitions/Aggregate"},"description":"Aggregates perform a calculation over an entire column, and are displayed when one or more [Group By](#group-by) are applied to the `View`. Aggregates can be specified by the user, or Perspective will use the following sensible default aggregates based on column type:\n\n- \"sum\" for `integer` and `float` columns\n- \"count\" for all other columns\n\nPerspective provides a selection of aggregate functions that can be applied to columns in the `View` constructor using a dictionary of column name to aggregate function name.\n\nAn aggregate also determines the column's RESULT TYPE, which need not match the input: `\"count\"` yields an `integer` whatever it counts, so a `date` column left on the default `\"count\"` is an `integer` in the resulting `View` — no longer a date. Set an aggregate that preserves the type (e.g. `\"any\"`, `\"last\"`) when the original type matters, such as a date used as a chart axis."},"group_by_depth":{"type":"number"},"filter_op":{"$ref":"#/definitions/FilterReducer"},"group_rollup_mode":{"$ref":"#/definitions/GroupRollupMode"},"split_rollup_mode":{"$ref":"#/definitions/SplitRollupMode"}},"required":["table"],"additionalProperties":false,"description":"The initial configuration of a NEW panel (`addPanel`, `restore`'s panel-creating upsert, `restoreWorkspace` `panels` entries). Unlike [`ViewerConfigUpdate`] — a patch against existing state — creation has no prior state: `table` is REQUIRED (a placed panel without a table binding would be permanently blank), absent fields mean \"default\" rather than \"leave unchanged\" (so no [`OptionalUpdate`] tri-state), and there is no `settings` field (element-level, not per-panel)."},"JsonValue":{"anyOf":[{"type":"number"},{"type":"string"},{"type":"boolean"},{"type":"array","items":{"$ref":"#/definitions/JsonValue"}},{"type":"object","additionalProperties":{"$ref":"#/definitions/JsonValue"}},{"type":"null"}]},"Filter":{"type":"array","minItems":3,"items":[{"type":"string"},{"type":"string"},{"$ref":"#/definitions/FilterTerm"}],"maxItems":3},"FilterTerm":{"anyOf":[{"type":"array","items":{"$ref":"#/definitions/Scalar"}},{"$ref":"#/definitions/Scalar"}]},"Scalar":{"type":["number","string","boolean","null"],"description":"This type represents the ViewConfig serializable type, which must be JSON safe."},"Sort":{"type":"array","minItems":2,"items":[{"type":"string"},{"$ref":"#/definitions/SortDir"}],"maxItems":2},"SortDir":{"type":"string","enum":["none","desc","asc","col desc","col asc","desc abs","asc abs","col desc abs","col asc abs"]},"Expressions":{"type":"object","additionalProperties":{"type":"string"}},"Windows":{"type":"object","additionalProperties":{"$ref":"#/definitions/WindowSpec"},"description":"The window columns of a `ViewConfig`, keyed by output column alias - symmetric with `expressions` (`{\"name\": {...spec}}`). An alias must not collide with a `Table` column, expression alias, or another window's key."},"WindowSpec":{"type":"object","properties":{"column":{"type":"string","description":"The input column - either a real `Table` column or an expression alias from the same `ViewConfig`."},"aggregate":{"type":"string"},"partition_by":{"type":"array","items":{"type":"string"},"description":"Columns whose distinct value tuples partition the rows; empty partitions the whole `Table` as one group."},"order_by":{"anyOf":[{"$ref":"#/definitions/WindowSort"},{"type":"null"}],"description":"The `Table` column which orders each partition, and the direction. This orders rows WITHIN the window frame only — it does not reorder the `View`, which is what the view-level `sort` does."},"rows":{"type":["number","null"],"description":"A frame of the `rows` preceding each row, plus the row itself. Mutually exclusive with `range` and `cumulative`."},"range":{"type":["number","null"],"description":"A frame of the rows whose `order_by` value lies within `range` of each row's, requiring a numeric or temporal `order_by`. Mutually exclusive with `rows` and `cumulative`."},"cumulative":{"type":["boolean","null"],"description":"`true` frames all rows from the partition start through each row. Default can be omitted."},"offset":{"type":["number","null"],"description":"Row offset for `lag`/`lead` (default 1)."},"alpha":{"type":["number","null"],"description":"Smoothing factor in `(0, 1]` for `ema`."}},"required":["column","aggregate"],"additionalProperties":false,"description":"The serialized form of [`WindowSpec`]."},"WindowSort":{"type":"array","minItems":2,"items":[{"type":"string"},{"$ref":"#/definitions/WindowSortDir"}],"maxItems":2,"description":"The `Table` column which orders each partition, with its direction - serialized as a two-element array (`[\"ts\", \"desc\"]`), symmetric with the `ViewConfig`'s `sort` field."},"WindowSortDir":{"type":"string","enum":["asc","desc"],"description":"A window's order direction. A dedicated two-variant enum rather than [`crate::config::SortDir`] - the column-sort extras (`col asc`, `abs`, `none`) are meaningless inside a window frame and unrepresentable here."},"Aggregate":{"anyOf":[{"type":"string"},{"type":"array","minItems":2,"items":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"maxItems":2}]},"FilterReducer":{"type":"string","enum":["and","or"]},"GroupRollupMode":{"type":"string","enum":["rollup","flat","total"]},"SplitRollupMode":{"type":"string","enum":["flat","rollup"],"description":"The `split_by` corollary to [`GroupRollupMode`]. `Flat` (the default, matching this crate's historical behavior) emits only full-depth split combinations as columns; `Rollup` additionally emits grand-total and subtotal column groups in \"totals before\" order. There is no `Total` variant - an empty `split_by` already expresses a single grand-total column group."}}},"WorkspaceConfigUpdate":{"$schema":"http://json-schema.org/draft-07/schema#","$ref":"#/definitions/WorkspaceConfigUpdate","definitions":{"WorkspaceConfigUpdate":{"type":"object","properties":{"active":{"type":["null","string"],"description":"The panel to activate with the settings sidebar open, by `panels` key or existing panel id; `null` closes the sidebar."},"layout":{"$ref":"#/definitions/Layout","description":"The layout tree to stage, naming every `panels` key (or, without `panels`, every existing panel id) exactly once."},"panels":{"type":"object","additionalProperties":{"$ref":"#/definitions/ViewerConfigInitial"},"description":"The complete replacement panel set, each [`ViewerConfigInitial`] entry creating a new panel (`{}` empties the element); absent keeps the existing panels."},"derived_tables":{"type":"object","additionalProperties":{"type":"object","properties":{"table":{"type":"string","description":"The `Table` this derivation reads, which may itself be another derived `Table`."},"group_by":{"type":"array","items":{"type":"string"}},"split_by":{"type":"array","items":{"type":"string"}},"sort":{"type":"array","items":{"$ref":"#/definitions/Sort"}},"filter":{"type":"array","items":{"$ref":"#/definitions/Filter"}},"group_rollup_mode":{"$ref":"#/definitions/GroupRollupMode"},"split_rollup_mode":{"$ref":"#/definitions/SplitRollupMode"},"filter_op":{"$ref":"#/definitions/FilterReducer"},"expressions":{"$ref":"#/definitions/Expressions"},"windows":{"$ref":"#/definitions/Windows"},"columns":{"type":"array","items":{"type":["string","null"]}},"aggregates":{"type":"object","additionalProperties":{"$ref":"#/definitions/Aggregate"}},"group_by_depth":{"type":["number","null"]}},"required":["table","group_by","split_by","sort","filter","group_rollup_mode","split_rollup_mode","expressions","columns","aggregates"],"additionalProperties":false,"description":"A derived `Table`'s definition: the `Table` its source `View` reads, and the config of that `View`; generic over the `table` slot, which is a name in the workspace `derived_tables` map and a recursive [`TableRef`] when inlined in a single-panel config."},"description":"The complete replacement set of derived `Table`s, in any order (they are built parent-first); absent keeps the current ones, `{}` deletes them all."},"global_filters":{"anyOf":[{"type":"null"},{"type":"array","items":{"$ref":"#/definitions/Filter"}}],"description":"The element-level cross-filter set applied to every detail panel as one unattributed bucket, replacing the current set; `null` clears it."},"masters":{"anyOf":[{"type":"null"},{"type":"array","items":{"type":"string"}}],"description":"The master (filter-source) panels by `panels` key or existing panel id, unknown ids dropped with a warning; `null` demotes every panel."},"palette":{"anyOf":[{"type":"null"},{"type":"object","additionalProperties":{"type":"string"}}],"description":"Named color-scale definitions applied to the host (see [`WorkspaceConfig::palette`]), replacing the previous palette; `null` clears it."}},"additionalProperties":false,"description":"The [`PerspectiveViewerElement::restoreWorkspace`] argument: a field-wise update of the element's workspace state, like [`ViewerConfigUpdate`] for a panel, where an absent key is unchanged, `null` resets and a value replaces."},"Layout":{"anyOf":[{"type":"object","additionalProperties":false,"properties":{"children":{"type":"array","items":{"$ref":"#/definitions/Layout"},"description":"Child layouts, in left-to-right or top-to-bottom order."},"sizes":{"type":"array","items":{"type":"number"},"description":"Each child's fraction of the split, one per child, summing to 1."},"orientation":{"$ref":"#/definitions/Orientation","description":"Whether children sit side by side (`horizontal`) or stacked (`vertical`)."},"type":{"type":"string","const":"split-layout"}},"required":["children","orientation","sizes","type"]},{"type":"object","additionalProperties":false,"properties":{"tabs":{"type":"array","items":{"type":"string"},"description":"Panel ids (slot names) in this stack."},"selected":{"type":"number","description":"Index of the selected tab within `tabs`."},"type":{"type":"string","const":"tab-layout"}},"required":["tabs","type"]}],"description":"A dashboard's panel arrangement: nested splits whose leaves are tab stacks of panel ids, naming every panel exactly once."},"Orientation":{"type":"string","enum":["horizontal","vertical"],"description":"The direction a [`SplitLayout`] divides its space in: `horizontal` places children side by side as columns, `vertical` stacks them as rows."},"ViewerConfigInitial":{"type":"object","properties":{"table":{"type":"string","description":"Name of the `Table` the new panel renders, as hosted on the `Client`. REQUIRED: a placed panel with no table binding would be permanently blank."},"version":{"type":"string","description":"The `@perspective-dev/viewer` version a saved config was written by. Omit it when creating a panel."},"plugin":{"type":"string","description":"Name of the visualization plugin, from the set registered on the page (the `list_plugins` agent tool, or the plugin picker). Decides what the view fields MEAN: `columns` is positional and every plugin reads the positions differently, and `group_by`/`split_by` draw different things per plugin. Absent uses the default plugin."},"title":{"type":"string","description":"Panel title, shown in its tab. Absent renders the default title."},"theme":{"type":"string","description":"Theme NAME (e.g. `\"Pro Dark\"`) — not a CSS value. Valid names are the Perspective themes loaded on the page. Absent uses the default."},"plugin_config":{"type":"object","additionalProperties":{"$ref":"#/definitions/JsonValue"},"description":"Plugin-wide settings (as opposed to the per-column [`Self::columns_config`]). Opaque to the viewer and defined by the ACTIVE plugin; query the valid keys with `get_style_schema` rather than guessing."},"columns_config":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{"$ref":"#/definitions/JsonValue"}},"description":"Per-column styling — formatting, colors, and other per-column controls — keyed by column name. Plugin-defined like [`Self::plugin_config`]; see `get_style_schema`."},"group_by":{"type":"array","items":{"type":"string"},"description":"A group by _groups_ the dataset by the unique values of each column used as a group by - a close analogue in SQL to the `GROUP BY` statement. The underlying dataset is aggregated to show the values belonging to each group, and a total row is calculated for each group, showing the currently selected aggregated value (e.g. `sum`) of the column. Group by are useful for hierarchies, categorizing data and attributing values, i.e. showing the number of units sold based on State and City. In Perspective, group by are represented as an array of string column names to pivot, are applied in the order provided; For example, a group by of `[\"State\", \"City\", \"Postal Code\"]` shows the values for each Postal Code, which are grouped by City, which are in turn grouped by State."},"split_by":{"type":"array","items":{"type":"string"},"description":"A split by _splits_ the dataset by the unique values of each column used as a split by. The underlying dataset is not aggregated, and a new column is created for each unique value of the split by. Each newly created column contains the parts of the dataset that correspond to the column header, i.e. a `View` that has `[\"State\"]` as its split by will have a new column for each state. In Perspective, Split By are represented as an array of string column names to pivot."},"columns":{"type":"array","items":{"type":["string","null"]},"description":"The `columns` property specifies which columns should be included in the [`crate::View`]'s output. This allows users to show or hide a specific subset of columns, as well as control the order in which columns appear to the user. This is represented in Perspective as an array of string column names."},"filter":{"type":"array","items":{"$ref":"#/definitions/Filter"},"description":"The `filter` property specifies columns on which the query can be filtered, returning rows that pass the specified filter condition. This is analogous to the `WHERE` clause in SQL. There is no limit on the number of columns where `filter` is applied, but the resulting dataset is one that passes all the filter conditions, i.e. the filters are joined with an `AND` condition.\n\nPerspective represents `filter` as an array of arrays, with the values of each inner array being a string column name, a string filter operator, and a filter operand in the type of the column."},"sort":{"type":"array","items":{"$ref":"#/definitions/Sort"},"description":"The `sort` property specifies columns on which the query should be sorted, analogous to `ORDER BY` in SQL. A column can be sorted regardless of its data type, and sorts can be applied in ascending or descending order. Perspective represents `sort` as an array of arrays, with the values of each inner array being a string column name and a string sort direction. When `column-pivots` are applied, the additional sort directions `\"col asc\"` and `\"col desc\"` will determine the order of pivot columns groups.\n\n`sort` is the ONLY thing that orders a `View`'s rows — without it they keep the `Table`'s natural (insertion) order, which any consumer reading rows sequentially will reflect. Not to be confused with a window column's `order_by`, which orders rows WITHIN a window frame and does not reorder the `View`."},"expressions":{"$ref":"#/definitions/Expressions","description":"The `expressions` property specifies _new_ columns in Perspective that are created using existing column values or arbitary scalar values defined within the expression. In `<perspective-viewer>`, expressions are added using the \"New Column\" button in the side panel."},"windows":{"$ref":"#/definitions/Windows","description":"The `windows` property declares ordered, partitioned rolling computations (moving aggregates, cumulative sums) as _new_ columns keyed by output alias (`{\"name\": {...spec}}`, symmetric with `expressions`), analogous to SQL window functions. See [`crate::config::WindowSpec`]."},"aggregates":{"type":"object","additionalProperties":{"$ref":"#/definitions/Aggregate"},"description":"Aggregates perform a calculation over an entire column, and are displayed when one or more [Group By](#group-by) are applied to the `View`. Aggregates can be specified by the user, or Perspective will use the following sensible default aggregates based on column type:\n\n- \"sum\" for `integer` and `float` columns\n- \"count\" for all other columns\n\nPerspective provides a selection of aggregate functions that can be applied to columns in the `View` constructor using a dictionary of column name to aggregate function name.\n\nAn aggregate also determines the column's RESULT TYPE, which need not match the input: `\"count\"` yields an `integer` whatever it counts, so a `date` column left on the default `\"count\"` is an `integer` in the resulting `View` — no longer a date. Set an aggregate that preserves the type (e.g. `\"any\"`, `\"last\"`) when the original type matters, such as a date used as a chart axis."},"group_by_depth":{"type":"number"},"filter_op":{"$ref":"#/definitions/FilterReducer"},"group_rollup_mode":{"$ref":"#/definitions/GroupRollupMode"},"split_rollup_mode":{"$ref":"#/definitions/SplitRollupMode"}},"required":["table"],"additionalProperties":false,"description":"The initial configuration of a NEW panel (`addPanel`, `restore`'s panel-creating upsert, `restoreWorkspace` `panels` entries). Unlike [`ViewerConfigUpdate`] — a patch against existing state — creation has no prior state: `table` is REQUIRED (a placed panel without a table binding would be permanently blank), absent fields mean \"default\" rather than \"leave unchanged\" (so no [`OptionalUpdate`] tri-state), and there is no `settings` field (element-level, not per-panel)."},"JsonValue":{"anyOf":[{"type":"number"},{"type":"string"},{"type":"boolean"},{"type":"array","items":{"$ref":"#/definitions/JsonValue"}},{"type":"object","additionalProperties":{"$ref":"#/definitions/JsonValue"}},{"type":"null"}]},"Filter":{"type":"array","minItems":3,"items":[{"type":"string"},{"type":"string"},{"$ref":"#/definitions/FilterTerm"}],"maxItems":3},"FilterTerm":{"anyOf":[{"type":"array","items":{"$ref":"#/definitions/Scalar"}},{"$ref":"#/definitions/Scalar"}]},"Scalar":{"type":["number","string","boolean","null"],"description":"This type represents the ViewConfig serializable type, which must be JSON safe."},"Sort":{"type":"array","minItems":2,"items":[{"type":"string"},{"$ref":"#/definitions/SortDir"}],"maxItems":2},"SortDir":{"type":"string","enum":["none","desc","asc","col desc","col asc","desc abs","asc abs","col desc abs","col asc abs"]},"Expressions":{"type":"object","additionalProperties":{"type":"string"}},"Windows":{"type":"object","additionalProperties":{"$ref":"#/definitions/WindowSpec"},"description":"The window columns of a `ViewConfig`, keyed by output column alias - symmetric with `expressions` (`{\"name\": {...spec}}`). An alias must not collide with a `Table` column, expression alias, or another window's key."},"WindowSpec":{"type":"object","properties":{"column":{"type":"string","description":"The input column - either a real `Table` column or an expression alias from the same `ViewConfig`."},"aggregate":{"type":"string"},"partition_by":{"type":"array","items":{"type":"string"},"description":"Columns whose distinct value tuples partition the rows; empty partitions the whole `Table` as one group."},"order_by":{"anyOf":[{"$ref":"#/definitions/WindowSort"},{"type":"null"}],"description":"The `Table` column which orders each partition, and the direction. This orders rows WITHIN the window frame only — it does not reorder the `View`, which is what the view-level `sort` does."},"rows":{"type":["number","null"],"description":"A frame of the `rows` preceding each row, plus the row itself. Mutually exclusive with `range` and `cumulative`."},"range":{"type":["number","null"],"description":"A frame of the rows whose `order_by` value lies within `range` of each row's, requiring a numeric or temporal `order_by`. Mutually exclusive with `rows` and `cumulative`."},"cumulative":{"type":["boolean","null"],"description":"`true` frames all rows from the partition start through each row. Default can be omitted."},"offset":{"type":["number","null"],"description":"Row offset for `lag`/`lead` (default 1)."},"alpha":{"type":["number","null"],"description":"Smoothing factor in `(0, 1]` for `ema`."}},"required":["column","aggregate"],"additionalProperties":false,"description":"The serialized form of [`WindowSpec`]."},"WindowSort":{"type":"array","minItems":2,"items":[{"type":"string"},{"$ref":"#/definitions/WindowSortDir"}],"maxItems":2,"description":"The `Table` column which orders each partition, with its direction - serialized as a two-element array (`[\"ts\", \"desc\"]`), symmetric with the `ViewConfig`'s `sort` field."},"WindowSortDir":{"type":"string","enum":["asc","desc"],"description":"A window's order direction. A dedicated two-variant enum rather than [`crate::config::SortDir`] - the column-sort extras (`col asc`, `abs`, `none`) are meaningless inside a window frame and unrepresentable here."},"Aggregate":{"anyOf":[{"type":"string"},{"type":"array","minItems":2,"items":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"maxItems":2}]},"FilterReducer":{"type":"string","enum":["and","or"]},"GroupRollupMode":{"type":"string","enum":["rollup","flat","total"]},"SplitRollupMode":{"type":"string","enum":["flat","rollup"],"description":"The `split_by` corollary to [`GroupRollupMode`]. `Flat` (the default, matching this crate's historical behavior) emits only full-depth split combinations as columns; `Rollup` additionally emits grand-total and subtotal column groups in \"totals before\" order. There is no `Total` variant - an empty `split_by` already expresses a single grand-total column group."}}}},"chunks":[{"title":"view » View","path":"docs/md/explanation/view.md","text":"The [`View`] struct is Perspective's query and serialization interface. It\nrepresents a query on the `Table`'s dataset and is always created from an\nexisting `Table` instance via the [`Table::view`] method.\n\n[`View`]s are immutable with respect to the arguments provided to the\n[`Table::view`] method; to change these parameters, you must create a new\n[`View`] on the same [`Table`]. However, each [`View`] is _live_ with respect to\nthe [`Table`]'s data, and will (within a conflation window) update with the\nlatest state as its parent [`Table`] updates, including incrementally\nrecalculating all aggregates, pivots, filters, etc. [`View`] query parameters\nare composable, in that each parameter works independently _and_ in conjunction\nwith each other, and there is no limit to the number of pivots, filters, etc.\nwhich can be applied.\n\n<div class=\"javascript\">\n<div class=\"warning\">\nThe examples in this module are in JavaScript. See <a href=\"https://docs.rs/crate/perspective/latest\"><code>perspective</code></a> docs for the Rust API.\n</div>\n</div>\n<div class=\"python\">\n<div class=\"warning\">\nThe examples in this module are in Python. See <a href=\"https://docs.rs/crate/perspective/latest\"><code>perspective</code></a> docs for the Rust API.\n</div>\n</div>"},{"title":"view » Examples","path":"docs/md/explanation/view.md","text":"<div class=\"javascript\">\n\n```javascript\nconst table = await perspective.table({\n id: [1, 2, 3, 4],\n name: [\"a\", \"b\", \"c\", \"d\"],\n});\n\nconst view = await table.view({ columns: [\"name\"] });\nconst json = await view.to_json();\nawait view.delete();\n```\n\n</div>\n<div class=\"python\">\n\n```python\ntable = perspective.Table({\n \"id\": [1, 2, 3, 4],\n \"name\": [\"a\", \"b\", \"c\", \"d\"]\n});\n\nview = table.view(columns=[\"name\"])\narrow = view.to_arrow()\nview.delete()\n```\n\n</div>\n<div class=\"rust\">\n\n```rust\nlet opts = TableInitOptions::default();\nlet data = TableData::Update(UpdateData::Csv(\"x,y\\n1,2\\n3,4\".into()));\nlet table = client.table(data, opts).await?;\n\nlet view = table.view(None).await?;\nlet arrow = view.to_arrow().await?;\nview.delete().await?;\n```\n\n</div>"},{"title":"querying » Querying data","path":"docs/md/explanation/view/querying.md","text":"To query the table, create a [`Table::view`] on the table instance with an\noptional configuration object. A [`Table`] can have as many [`View`]s associated\nwith it as you need - Perspective conserves memory by relying on a single\n[`Table`] to power multiple [`View`]s concurrently:\n\n<div class=\"javascript\">\n\n```javascript\nconst view = await table.view({\n columns: [\"Sales\"],\n aggregates: { Sales: \"sum\" },\n group_by: [\"Region\", \"Country\"],\n filter: [[\"Category\", \"in\", [\"Furniture\", \"Technology\"]]],\n});\n```\n\n</div>\n<div class=\"python\">\n\n```python\nview = table.view(\n columns=[\"Sales\"],\n aggregates={\"Sales\": \"sum\"},\n group_by=[\"Region\", \"Country\"],\n filter=[[\"Category\", \"in\", [\"Furniture\", \"Technology\"]]]\n)\n```\n\n</div>\n<div class=\"rust\">\n\n```rust\nuse crate::config::*;\nlet view = table\n .view(Some(ViewConfigUpdate {\n columns: Some(vec![Some(\"Sales\".into())]),\n aggregates: Some(HashMap::from_iter(vec![(\"Sales\".into(), \"sum\".into())])),\n group_by: Some(vec![\"Region\".into(), \"Country\".into()]),\n filter: Some(vec![Filter::new(\"Category\", \"in\", &[\n \"Furniture\",\n \"Technology\",\n ])]),\n ..ViewConfigUpdate::default()\n }))\n .await?;\n```\n\n</div>"},{"title":"grouping_and_pivots » Grouping and Pivots » Group By","path":"docs/md/explanation/view/config/grouping_and_pivots.md","text":"A group by _groups_ the dataset by the unique values of each column used as a\ngroup by - a close analogue in SQL to the `GROUP BY` statement. The underlying\ndataset is aggregated to show the values belonging to each group, and a total\nrow is calculated for each group, showing the currently selected aggregated\nvalue (e.g. `sum`) of the column. Group by are useful for hierarchies,\ncategorizing data and attributing values, i.e. showing the number of units sold\nbased on State and City. In Perspective, group by are represented as an array of\nstring column names to pivot, are applied in the order provided; For example, a\ngroup by of `[\"State\", \"City\", \"Postal Code\"]` shows the values for each Postal\nCode, which are grouped by City, which are in turn grouped by State.\n\n<div class=\"javascript\">\n\n```javascript\nconst view = await table.view({ group_by: [\"a\", \"c\"] });\n```\n\n</div>\n<div class=\"python\">\n\n```python\nview = table.view(group_by=[\"a\", \"c\"])\n```\n\n</div>\n<div class=\"rust\">\n\n```rust\nlet view = table.view(Some(ViewConfigUpdate {\n group_by: Some(vec![\"a\".into(), \"c\".into()]),\n ..ViewConfigUpdate::default()\n})).await?;\n```\n\n</div>"},{"title":"grouping_and_pivots » Grouping and Pivots » Group By » group_rollup_mode","path":"docs/md/explanation/view/config/grouping_and_pivots.md","text":"The `group_rollup_mode` option controls how the grouped rows themselves render:\n\n- `\"rollup\"` (the default) - the full hierarchy, with a subtotal row for\n every group at every level and a grand total row, each addressable by its\n `__ROW_PATH__`.\n- `\"flat\"` - leaf rows only, one row per deepest-level group, with no\n subtotal or grand total rows. Useful for chart plugins and exports where\n subtotal rows would double-count.\n- `\"total\"` - the grand total row _only_. `\"total\"` is mutually exclusive\n with `group_by` (which is cleared when it is set) - it is the one shape\n an empty `group_by` cannot express, since no `group_by` at all yields the\n unaggregated dataset.\n\n<div class=\"javascript\">\n\n```javascript\nconst view = await table.view({\n group_by: [\"a\"],\n group_rollup_mode: \"flat\",\n});\n```\n\n</div>\n<div class=\"python\">\n\n```python\nview = table.view(group_by=[\"a\"], group_rollup_mode=\"flat\")\n```\n\n</div>\n<div class=\"rust\">\n\n```rust\nlet view = table.view(Some(ViewConfigUpdate {\n group_by: Some(vec![\"a\".into()]),\n group_rollup_mode: Some(GroupRollupMode::Flat),\n ..ViewConfigUpdate::default()\n})).await?;\n```\n\n</div>"},{"title":"grouping_and_pivots » Grouping and Pivots » Split By","path":"docs/md/explanation/view/config/grouping_and_pivots.md","text":"A split by _splits_ the dataset by the unique values of each column used as a\nsplit by. The underlying dataset is not aggregated, and a new column is created\nfor each unique value of the split by. Each newly created column contains the\nparts of the dataset that correspond to the column header, i.e. a `View` that\nhas `[\"State\"]` as its split by will have a new column for each state. In\nPerspective, Split By are represented as an array of string column names to\npivot:\n\n<div class=\"javascript\">\n\n```javascript\nconst view = await table.view({ split_by: [\"a\", \"c\"] });\n```\n\n</div>\n<div class=\"python\">\n\n```python\nview = table.view(split_by=[\"a\", \"c\"])\n```\n\n</div>\n<div class=\"rust\">\n\n```rust\nlet view = table.view(Some(ViewConfigUpdate {\n split_by: Some(vec![\"a\".into(), \"c\".into()]),\n ..ViewConfigUpdate::default()\n})).await?;\n```\n\n</div>"},{"title":"grouping_and_pivots » Grouping and Pivots » Split By » split_rollup_mode","path":"docs/md/explanation/view/config/grouping_and_pivots.md","text":"The `split_rollup_mode` option is the `split_by` counterpart to\n[`group_rollup_mode`](#group_rollup_mode), controlling whether subtotal\n_column groups_ are emitted:\n\n- `\"flat\"` (the default) - only full-depth split combinations appear as\n columns, e.g. `\"CA|Sales\"`. This is Perspective's historical behavior.\n- `\"rollup\"` - additionally emits a grand-total column per aggregate (named\n by the bare column name, e.g. `\"Sales\"`, aggregating across every split\n group) and, when more than one `split_by` column is applied, a subtotal\n column per intermediate split group (e.g. `\"CA|Sales\"` alongside\n `\"CA|First Class|Sales\"`). Total and subtotal columns precede their\n groups, in pre-order.\n\n<div class=\"javascript\">\n\n```javascript\nconst view = await table.view({\n group_by: [\"State\"],\n split_by: [\"Ship Mode\"],\n split_rollup_mode: \"rollup\",\n});\n```\n\n</div>\n<div class=\"python\">\n\n```python\nview = table.view(\n group_by=[\"State\"],\n split_by=[\"Ship Mode\"],\n split_rollup_mode=\"rollup\",\n)\n```\n\n</div>\n<div class=\"rust\">\n\n```rust\nlet view = table.view(Some(ViewConfigUpdate {\n group_by: Some(vec![\"State\".into()]),\n split_by: Some(vec![\"Ship Mode\".into()]),\n split_rollup_mode: Some(SplitRollupMode::Rollup),\n ..ViewConfigUpdate::default()\n})).await?;\n```\n\n</div>"},{"title":"grouping_and_pivots » Grouping and Pivots » Aggregates","path":"docs/md/explanation/view/config/grouping_and_pivots.md","text":"Aggregates perform a calculation over an entire column, and are displayed when\none or more [Group By](#group-by) are applied to the `View`. Aggregates can be\nspecified by the user, or Perspective will use the following sensible default\naggregates based on column type:\n\n- \"sum\" for `integer` and `float` columns\n- \"count\" for all other columns\n\nPerspective provides a selection of aggregate functions that can be applied to\ncolumns in the `View` constructor using a dictionary of column name to aggregate\nfunction name.\n\n<div class=\"javascript\">\n\n```javascript\nconst view = await table.view({\n aggregates: {\n a: \"avg\",\n b: \"distinct count\",\n },\n});\n```\n\n</div>\n<div class=\"python\">\n\n```python\nview = table.view(\n aggregates={\n \"a\": \"avg\",\n \"b\": \"distinct count\"\n }\n)\n```\n\n</div>\n<div class=\"rust\">\n\n```rust\nuse std::collections::HashMap;\nlet view = table.view(Some(ViewConfigUpdate {\n aggregates: Some(HashMap::from([\n (\"a\".into(), \"avg\".into()),\n (\"b\".into(), \"distinct count\".into()),\n ])),\n ..ViewConfigUpdate::default()\n})).await?;\n```\n\n</div>\n\nEvery aggregate is described below, grouped by what it computes. Which of them\na given column accepts depends on its type — see\n[Availability by column type](#availability-by-column-type)."},{"title":"grouping_and_pivots » Grouping and Pivots » Aggregates » Sums and products","path":"docs/md/explanation/view/config/grouping_and_pivots.md","text":"| Aggregate | Description | Result type |\n| --- | --- | --- |\n| `sum` | Total of the group's values, or `null` when the group has no non-null values | `integer` or `float` |\n| `sum or zero` | As `sum`, but reports `0` rather than `null` for a group with no non-null values | `integer` or `float` |\n| `sum abs` | Sum of the absolute values — `Σ abs(v)`; `null` when the group has no non-null values | `integer` or `float` |\n| `abs sum` | Absolute value of the sum — `abs(Σ v)`; `null` when the group has no non-null values | `integer` or `float` |\n| `mul` | Product of the group's values | `integer` or `float` |\n| `gmv` | Gross market value — leaf rows are a plain `sum`, parent rows sum the _absolute_ subtotal of each immediate child group | `integer` or `float` |\n| `pct sum parent` | The group's `sum` as a percentage of its parent row's, `0`–`100`; `100` at the root, and `null` when the parent's sum is `0` | `float` |\n| `pct sum total` | The group's `sum` as a percentage of the grand total, `0`–`100` | `float` |\n\n`sum not null` is accepted as a legacy alias for `sum`.\n\nA numeric aggregate widens to the input's numeric class — `integer` columns\naccumulate as `integer`, `float` columns as `float`."},{"title":"grouping_and_pivots » Grouping and Pivots » Aggregates » Averages and dispersion","path":"docs/md/explanation/view/config/grouping_and_pivots.md","text":"| Aggregate | Description | Result type |\n| --- | --- | --- |\n| `avg` | Arithmetic mean of the non-null values | `float` |\n| `weighted mean` | `Σ(value × weight) / Σ(weight)`, over rows where both the value and the weight are non-null and finite; `null` when the weights sum to `0`. Takes a **weight column** as an argument | `float` |\n| `stddev` | Population standard deviation | `float` |\n| `var` | Population variance — divides by `N`, not `N - 1` | `float` |\n\n`stddev` and `var` are `null` for a group of fewer than two non-null values."},{"title":"grouping_and_pivots » Grouping and Pivots » Aggregates » Extrema and order statistics","path":"docs/md/explanation/view/config/grouping_and_pivots.md","text":"| Aggregate | Description | Result type |\n| --- | --- | --- |\n| `min`, `max` | Smallest and largest of the group's current values | input type |\n| `min by`, `max by` | The value from the row at which a **second column**, supplied as an argument, is smallest or largest | input type |\n| `high`, `low` | High and low _water mark_ — the largest and smallest value this `View` has ever observed for the group, which never moves back when rows are updated or removed | input type |\n| `high minus low` | `max - min` of the group's current values, i.e. its range. Despite the name this uses `min`/`max`, not the water marks | input type |\n| `median`, `q1`, `q3` | The value at the 50%, 25% and 75% position of the group's values; on `float` columns an exact split averages the two adjacent values | input type |"},{"title":"grouping_and_pivots » Grouping and Pivots » Aggregates » Positional","path":"docs/md/explanation/view/config/grouping_and_pivots.md","text":"| Aggregate | Description | Result type |\n| --- | --- | --- |\n| `first` | Value from the group's earliest row | input type |\n| `last by index` | Value from the group's latest row | input type |\n| `last minus first` | `last by index` minus `first` | input type |\n| `last` | Value from the group's most recently _updated_ row | input type |\n\n\"Earliest\" and \"latest\" are by the `Table`'s `index` column, or by row order\nwhen the `Table` is unindexed. This is not the same as `last`, which tracks\nupdate recency rather than position."},{"title":"grouping_and_pivots » Grouping and Pivots » Aggregates » Cardinality and identity","path":"docs/md/explanation/view/config/grouping_and_pivots.md","text":"| Aggregate | Description | Result type |\n| --- | --- | --- |\n| `count` | Number of rows in the group | `integer` |\n| `distinct count` | Number of distinct values in the group | `integer` |\n| `unique` | The group's value when every row shares one, otherwise `null` | input type |\n| `distinct leaf` | As `unique`, but only on leaf rows — parent rows are blank | input type |\n| `dominant` | The most frequent non-null value, i.e. the mode; a tie resolves to whichever value reached the winning count first | input type |\n| `any` | The group's first _truthy_ value — any non-null value for `string` columns, the first non-zero for numbers and dates, the first `true` for `boolean` — or `null` if it has none | input type |\n| `or` | Identical to `any` | input type |\n| `and` | `true` when every value in the group is truthy, else `false` | `boolean` |\n| `join` | The group's distinct values, sorted and rendered as a `\", \"`-delimited string, truncated at 280 characters. Nulls render as `null` | `string` |"},{"title":"grouping_and_pivots » Grouping and Pivots » Aggregates » Nulls","path":"docs/md/explanation/view/config/grouping_and_pivots.md","text":"Null handling is not uniform, and is usually what makes two similar-looking\naggregates differ:\n\n- `count` counts **rows**, not values — a group of 3 rows whose value is\n `null` counts `3`. This is not the same as the `count` [window\n aggregate](./windows.md#aggregates), which counts non-null values.\n- `distinct count` counts `null` as **one distinct value**, so a group of\n `[1, null, null]` counts `2`.\n- `sum`, `avg`, `stddev`, `var`, `dominant` and `weighted mean` skip nulls\n entirely. `avg` divides by the count of non-null values, so a group of all\n nulls is `null` rather than `0`.\n- `any` and `or` return the first _truthy_ value, not the first non-null one —\n a numeric group of all `0`, or a `boolean` group of all `false`, aggregates\n to `null`.\n- `join` renders nulls into its output as the literal text `null`."},{"title":"grouping_and_pivots » Grouping and Pivots » Aggregates » Availability by column type","path":"docs/md/explanation/view/config/grouping_and_pivots.md","text":"The aggregates a column accepts depend on its type:\n\n**Numeric columns** (`integer`, `float`): `sum`, `abs sum`, `sum abs`,\n`sum or zero`, `mul`, `gmv`, `any`, `avg`, `mean`, `count`, `distinct count`,\n`distinct leaf`, `dominant`, `first`, `last`, `last by index`, `high`, `low`,\n`max`, `min`, `min by`, `max by`, `high minus low`, `last minus first`,\n`median`, `q1`, `q3`, `pct sum parent`, `pct sum total`, `stddev`, `var`,\n`unique`, `weighted mean`.\n\n**String columns**: `count`, `any`, `distinct count`, `distinct leaf`,\n`dominant`, `first`, `last`, `last by index`, `join`, `median`, `q1`, `q3`,\n`unique`, `min by`, `max by`.\n\n**Date/Datetime columns**: `count`, `any`, `avg`, `distinct count`,\n`distinct leaf`, `dominant`, `first`, `last`, `last by index`, `high`, `low`,\n`max`, `min`, `median`, `q1`, `q3`, `unique`.\n\n**Boolean columns**: `count`, `any`, `and`, `or`, `distinct count`,\n`distinct leaf`, `dominant`, `first`, `last`, `last by index`, `unique`.\n\n<div class=\"warning\"><code>avg</code> on a <code>date</code> or\n<code>datetime</code> column returns a <code>float</code> — the mean of the\ncolumn's underlying numeric representation — not a date.</div>"},{"title":"grouping_and_pivots » Grouping and Pivots » Aggregates » Argument-taking aggregates","path":"docs/md/explanation/view/config/grouping_and_pivots.md","text":"`weighted mean`, `min by` and `max by` each read a second column, and are\nwritten as a `[name, [argument]]` pair rather than a bare string:\n\n<div class=\"javascript\">\n\n```javascript\nconst view = await table.view({\n aggregates: { a: [\"weighted mean\", [\"b\"]] },\n});\n```\n\n</div>\n<div class=\"python\">\n\n```python\nview = table.view(aggregates={\"a\": (\"weighted mean\", [\"b\"])})\n```\n\n</div>\n<div class=\"rust\">\n\n```rust\nlet view = table.view(Some(ViewConfigUpdate {\n aggregates: Some(HashMap::from([(\n \"a\".into(),\n Aggregate::MultiAggregate(\"weighted mean\".into(), vec![\"b\".into()]),\n )])),\n ..ViewConfigUpdate::default()\n})).await?;\n```\n\n</div>\n\n<div class=\"warning\">In Rust, <code>Aggregate::from(&str)</code> splits on\n<code>\" by \"</code> to build a <code>MultiAggregate</code>. Single aggregates\nwhose names contain that substring — <code>\"last by index\"</code> — must\ntherefore be constructed as\n<code>Aggregate::SingleAggregate(\"last by index\".into())</code> rather than\n<code>\"last by index\".into()</code>, which silently resolves to\n<code>last</code>.</div>"},{"title":"grouping_and_pivots » Grouping and Pivots » Aggregates » Aliases","path":"docs/md/explanation/view/config/grouping_and_pivots.md","text":"Several aggregates answer to more than one name. Every name below is accepted\nanywhere an aggregate is, and each group refers to one function:\n\n| Canonical | Also accepted |\n| --- | --- |\n| `avg` | `mean` |\n| `distinct count` | `distinct`, `distinctcount`, `distinct_count` |\n| `first` | `first by index` |\n| `last` | `last_value` |\n| `high` | `high_water_mark` |\n| `low` | `low_water_mark` |\n| `pct sum total` | `pct sum grand total`, `pct_sum_grand_total` |\n| `var` | `variance` |\n| `stddev` | `standard deviation` |\n| `sum` | `sum not null`, `sum_not_null` (legacy name) |\n\nMost multi-word aggregates also answer to a snake_case spelling —\n`sum_or_zero`, `sum_abs`, `abs_sum`, `weighted_mean`, `distinct_leaf`,\n`pct_sum_parent`, `pct_sum_total`, `min_by`, `max_by`. Three do not, and are\nonly accepted spelled with spaces: `high minus low`, `last minus first` and\n`last by index`.\n\nA few names the engine parses are _not implemented_ — `identity`,\n`mean by count`, and `div`/`add`, which have no way to receive their operands\nfrom a `ViewConfig`. Naming one is rejected exactly as a misspelled aggregate\nis: the `View` fails to construct with an error naming the aggregate and the\ncolumn it was given for, and the `Table` is left untouched."},{"title":"selection_and_ordering » Selection and Ordering » Columns","path":"docs/md/explanation/view/config/selection_and_ordering.md","text":"The `columns` property specifies which columns should be included in the\n`View`'s output. This allows users to show or hide a specific subset of columns,\nas well as control the order in which columns appear to the user. This is\nrepresented in Perspective as an array of string column names:\n\n<div class=\"javascript\">\n\n```javascript\nconst view = await table.view({\n columns: [\"a\"],\n});\n```\n\n</div>\n<div class=\"python\">\n\n```python\nview = table.view(columns=[\"a\"])\n```\n\n</div>\n<div class=\"rust\">\n\n```rust\nlet view = table.view(Some(ViewConfigUpdate {\n columns: Some(vec![Some(\"a\".into())]),\n ..ViewConfigUpdate::default()\n})).await?;\n```\n\n</div>"},{"title":"selection_and_ordering » Selection and Ordering » Sort","path":"docs/md/explanation/view/config/selection_and_ordering.md","text":"The `sort` property specifies columns on which the query should be sorted,\nanalogous to `ORDER BY` in SQL. A column can be sorted regardless of its data\ntype, and sorts can be applied in ascending or descending order. Perspective\nrepresents `sort` as an array of arrays, with the values of each inner array\nbeing a string column name and a string sort direction. When `split_by` are\napplied, the additional sort directions `\"col asc\"` and `\"col desc\"` will\ndetermine the order of pivot column groups.\n\n<div class=\"javascript\">\n\n```javascript\nconst view = await table.view({\n sort: [[\"a\", \"asc\"]],\n});\n```\n\n</div>\n<div class=\"python\">\n\n```python\nview = table.view(sort=[[\"a\", \"asc\"]])\n```\n\n</div>\n<div class=\"rust\">\n\n```rust\nlet view = table.view(Some(ViewConfigUpdate {\n sort: Some(vec![Sort(\"a\".into(), SortDir::Asc)]),\n ..ViewConfigUpdate::default()\n})).await?;\n```\n\n</div>\n\nThe available sort directions are:\n\n| Direction | Description |\n|---|---|\n| `\"asc\"` | Ascending order |\n| `\"desc\"` | Descending order |\n| `\"asc abs\"` | Ascending by absolute value |\n| `\"desc abs\"` | Descending by absolute value |\n| `\"col asc\"` | Ascending order for pivot column groups (requires `split_by`) |\n| `\"col desc\"` | Descending order for pivot column groups (requires `split_by`) |\n| `\"col asc abs\"` | Ascending by absolute value for pivot column groups |\n| `\"col desc abs\"` | Descending by absolute value for pivot column groups |"},{"title":"selection_and_ordering » Selection and Ordering » Filter","path":"docs/md/explanation/view/config/selection_and_ordering.md","text":"The `filter` property specifies columns on which the query can be filtered,\nreturning rows that pass the specified filter condition. This is analogous to\nthe `WHERE` clause in SQL. There is no limit on the number of columns where\n`filter` is applied, but the resulting dataset is one that passes all the filter\nconditions, i.e. the filters are joined with an `AND` condition. The join\ncondition can be changed to `OR` via the `filter_op` property.\n\nPerspective represents `filter` as an array of arrays, with the values of each\ninner array being a string column name, a string filter operator, and a filter\noperand in the type of the column:\n\n<div class=\"javascript\">\n\n```javascript\nconst view = await table.view({\n filter: [[\"a\", \"<\", 100]],\n});\n```\n\n</div>\n<div class=\"python\">\n\n```python\nview = table.view(filter=[[\"a\", \"<\", 100]])\n```\n\n</div>\n<div class=\"rust\">\n\n```rust\nlet view = table.view(Some(ViewConfigUpdate {\n filter: Some(vec![Filter::new(\"a\", \"<\", FilterTerm::Scalar(Scalar::Float(100.0)))]),\n ..ViewConfigUpdate::default()\n})).await?;\n```\n\n</div>\n\nThe available filter operators depend on the column type:\n\n**String columns**: `==`, `!=`, `>`, `>=`, `<`, `<=`, `begins with`,\n`not begins with`, `contains`, `not contains`, `ends with`, `not ends with`,\n`matches`, `not matches`, `in`, `not in`, `is not null`, `is null`.\n\nThe string matching operators (`begins with`, `contains`, `ends with` and\ntheir negations) are case-insensitive, and `matches` / `not matches` are\ncase-sensitive partial-match [RE2](https://github.com/google/re2) regular\nexpressions. Null cells match none of these operators, including the negated\nforms - filter on `is null` to select them.\n\n**Numeric columns** (`integer`, `float`): `==`, `!=`, `>`, `>=`, `<`, `<=`,\n`is not null`, `is null`.\n\n**Boolean columns**: `==`, `is not null`, `is null`.\n\n**Date/Datetime columns**: `==`, `!=`, `>`, `>=`, `<`, `<=`, `is not null`,\n`is null`."},{"title":"expressions » Expressions","path":"docs/md/explanation/view/config/expressions.md","text":"The `expressions` property specifies _new_ columns in Perspective that are\ncreated using existing column values or arbitrary scalar values defined within\nthe expression. In `<perspective-viewer>`, expressions are added using the \"New\nColumn\" button in the side panel.\n\nExpressions are strings parsed by Perspective's expression engine (based on\n[ExprTK](https://github.com/ArashPartow/exprtk)). Column names are referenced by\nwrapping them in double quotes, e.g. `\"Sales\"`:\n\n<div class=\"javascript\">\n\n```javascript\nconst view = await table.view({\n expressions: {\n \"Profit Ratio\": '\"Profit\" / \"Sales\"',\n },\n});\n```\n\n</div>\n<div class=\"python\">\n\n```python\nview = table.view(expressions={'Profit Ratio': '\"Profit\" / \"Sales\"'})\n```\n\n</div>\n<div class=\"rust\">\n\n```rust\nlet view = table.view(Some(ViewConfigUpdate {\n expressions: Some(Expressions([\n (\"Profit Ratio\", \"\\\"Profit\\\" / \\\"Sales\\\"\".into())\n ].into_iter().collect())),\n ..ViewConfigUpdate::default()\n})).await?;\n```\n\n</div>"},{"title":"expressions » Expressions » Type Conversion and Coercion","path":"docs/md/explanation/view/config/expressions.md","text":"Perspective expressions are typed: every column, literal and function result has\na fixed type, and the validator reports an error before the expression is ever\ncomputed if the types do not fit the operator. To move between types explicitly,\nuse the conversion functions:\n\n| Function | Description |\n| --------------- | ------------------------------------------------------------ |\n| `to_string(x)` | Convert any type to string |\n| `to_integer(x)` | Convert to integer (null if not parsable) |\n| `to_float(x)` | Convert to float (null if not parsable) |\n| `to_boolean(x)` | Convert to boolean (truthy/falsy) |\n| `integer(x)` | Alias for `to_integer(x)` |\n| `float(x)` | Alias for `to_float(x)` |\n| `datetime(x)` | Construct a datetime from a POSIX timestamp (ms since epoch) |\n| `date(y, m, d)` | Construct a date from year, month, day |"},{"title":"expressions » Expressions » Type Conversion and Coercion » How coercion works","path":"docs/md/explanation/view/config/expressions.md","text":"Numeric types promote to each other. Arithmetic on any mix of `integer` and\n`float` operands is computed in floating point and produces a `float`. The\ncomparison operators compare values across every numeric type: integers are\ncompared exactly (including signed against unsigned), and as soon as one side is\na `float` both sides are compared as doubles. Numeric literals are `float`, so\n`\"Quantity\" > 3` works on an `integer` column without a cast.\n\nNo other implicit coercion exists. `boolean`, `string`, `date` and `datetime`\nvalues can only be compared with values of the same type; comparing a `string`\ncolumn to a number, a `boolean` to `1`, or a `date` to a `datetime` is a\nvalidation error that names the operator and both types, for example\n`Type Error - cannot compare string and float with '=='`. Similarly, `datetime`\nand `date` values are not numeric: to perform arithmetic on them, you must first\nconvert to a numeric representation, do the math, then convert back.\n\nBoolean contexts cast instead. The condition of `if` and `? :`, and the operands\nof `and`, `or`, `not`, `xor`, `nand`, `nor` and `xnor`, accept any type: `null`\nis `false`, a `boolean` is its own value, a number is `true` when non-zero, and\na `string` is `true` when non-null. `x == null` and `x != null` test `x` for\nnull and return `boolean`, the same as `is_null(x)` and `is_not_null(x)`; the\n`null` literal is otherwise a value like any null cell: `\"x\" > 2 ? null : \"x\"`\nyields null in the first case, and `\"x\" + null` or `\"x\" < null` are null, exactly\nas they would be for a column with a null value.\n\nInternally, `datetime` values are stored as milliseconds since the Unix epoch\n(1970-01-01T00:00:00Z). Converting a `datetime` to a `float` yields this\nmillisecond timestamp, and `datetime()` accepts a millisecond timestamp to\nproduce a `datetime`."},{"title":"expressions » Expressions » Type Conversion and Coercion » Example: offsetting a datetime by 7 days","path":"docs/md/explanation/view/config/expressions.md","text":"This expression takes a `\"Shipped Date\"` column, converts it to its\nmillisecond-epoch representation, adds 7 days worth of milliseconds (7 ×\n24 × 60 × 60 × 1000 = 604800000), and converts the result back\nto a `datetime`:\n\n```\n// Due Date\ndatetime(float(\"Shipped Date\") + 604800000)\n```\n\nexpressions » Expressions » Operators\n\nStandard arithmetic and comparison operators are supported:\n\n| Operator | Description |\n| -------------------------------- | ----------- |\n| `+`, `-`, `*`, `/` | Arithmetic |\n| `%` | Modulo |\n| `==`, `!=`, `<`, `>`, `<=`, `>=` | Comparison |\n| `and`, `or`, `not` | Logical |\n| `if ... else ...` | Conditional |"},{"title":"expressions » Expressions » Numeric Functions","path":"docs/md/explanation/view/config/expressions.md","text":"ExprTK provides a rich set of built-in numeric functions including `abs`,\n`ceil`, `floor`, `round`, `exp`, `log`, `log10`, `sqrt`, `min`, `max`, `pow`,\n`clamp`, `iclamp`, `inrange`, and trigonometric functions (`sin`, `cos`, `tan`,\n`asin`, `acos`, `atan`).\n\nexpressions » Expressions » String Functions\n\n| Function | Description |\n| ------------------------------- | ------------------------------------------------------- |\n| `concat(a, b, ...)` | Concatenate strings |\n| `upper(s)` | Convert to uppercase |\n| `lower(s)` | Convert to lowercase |\n| `length(s)` | String length |\n| `contains(s, substr)` | Whether `s` contains `substr` |\n| `order(col, 'B', 'C', 'A')` | Custom sort order for a string column |\n| `match(s, pattern)` | Regex partial match (returns boolean) |\n| `match_all(s, pattern)` | Regex full match (returns boolean) |\n| `search(s, pattern)` | First capturing group match |\n| `indexof(s, pattern)` | Start index of first regex match |\n| `substring(s, start, end)` | Substring from `start` (inclusive) to `end` (exclusive) |\n| `replace(s, repl, pattern)` | Replace first regex match |\n| `replace_all(s, repl, pattern)` | Replace all regex matches |"},{"title":"expressions » Expressions » Date/Datetime Functions","path":"docs/md/explanation/view/config/expressions.md","text":"| Function | Description |\n| ------------------------ | ------------------------------------------------------------------------ |\n| `today()` | Current date |\n| `now()` | Current datetime |\n| `date(year, month, day)` | Construct a date |\n| `datetime(timestamp_ms)` | Construct a datetime from a POSIX timestamp (ms since epoch) |\n| `hour_of_day(dt)` | Hour component (0-23) |\n| `day_of_week(dt)` | Day of the week as a string |\n| `month_of_year(dt)` | Month of the year as a string |\n| `bucket(dt, unit)` | Bucket datetime by unit: `'s'`, `'m'`, `'h'`, `'D'`, `'W'`, `'M'`, `'Y'` |\n\n`bucket` also works on numeric columns: `bucket(\"Price\", 10)` rounds values down\nto the nearest multiple of 10."},{"title":"expressions » Expressions » Other Functions","path":"docs/md/explanation/view/config/expressions.md","text":"| Function | Description |\n| ------------------------- | ----------------------------------------------------- |\n| `is_null(x)` | Whether the value is null |\n| `is_not_null(x)` | Whether the value is not null |\n| `percent_of(a, b)` | `a` as a percentage of `b` |\n| `inrange(low, val, high)` | Whether `val` is between `low` and `high` (inclusive) |\n| `min(a, b, ...)` | Minimum of inputs |\n| `max(a, b, ...)` | Maximum of inputs |\n| `random()` | Random float between 0.0 and 1.0 |\n| `col(name)` | Look up a column by string name at runtime |\n| `vlookup(col, key)` | Look up a value in another column by row key |"},{"title":"expressions » Expressions » See also","path":"docs/md/explanation/view/config/expressions.md","text":"Expressions are row-local — each output cell is computed from that row's\nvalues alone. For calculations which span rows, such as moving averages,\ncumulative sums or period-over-period differences, see\n[Window Columns](./windows.md)."},{"title":"windows » Window Columns","path":"docs/md/explanation/view/config/windows.md","text":"The `windows` property declares _ordered, partitioned rolling computations_\nover the rows of a `Table` — moving averages, cumulative sums,\nperiod-over-period differences — analogous to SQL window functions.\n\nWindow Columns are declared per-`View`, keyed by output alias, exactly as\n[`expressions`](./expressions.md) are:\n\n<div class=\"javascript\">\n\n```javascript\nconst view = await table.view({\n columns: [\"10-tick avg Sales\"],\n windows: {\n \"10-tick avg Sales\": {\n column: \"Sales\",\n aggregate: \"avg\",\n rows: 10,\n },\n },\n});\n```\n\n</div>\n<div class=\"python\">\n\n```python\nview = table.view(\n columns=[\"10-tick avg Sales\"],\n windows={\n \"10-tick avg Sales\": {\n \"column\": \"Sales\",\n \"aggregate\": \"avg\",\n \"rows\": 10,\n }\n },\n)\n```\n\n</div>\n\nEach window produces a new column which may be used anywhere a `Table` column\ncan — in `columns`, `filter`, `sort`, `group_by`, and so on. An alias must not\ncollide with a `Table` column, an expression alias, or another window's key.\n\nWindow Columns update incrementally as the `Table` updates, including rows\n_outside_ an update batch whose window frames were affected by it."},{"title":"windows » Window Columns » Spec fields","path":"docs/md/explanation/view/config/windows.md","text":"| Field | Type | Description |\n| --- | --- | --- |\n| `column` | `string` | The input column — a `Table` column or an expression alias from the same config |\n| `aggregate` | `string` | The window function to apply (see below) |\n| `partition_by` | `string[]` | Columns whose distinct value tuples partition the rows; omitted partitions the whole `Table` as one group |\n| `order_by` | `[string, \"asc\" \\| \"desc\"]` | The column which orders each partition, and its direction |\n| `rows` | `integer` | Frame of the N rows preceding each row, plus the row itself |\n| `range` | `number` | Frame of rows whose `order_by` value lies within `range` of each row's |\n| `cumulative` | `true` | Frame of all rows from the partition start through each row |\n| `offset` | `integer` | Row offset for `lag`/`lead` (default `1`) |\n| `alpha` | `number` | Smoothing factor in `(0, 1]` for `ema` |\n\n`rows`, `range` and `cumulative` are **mutually exclusive** — supplying more\nthan one is an error. `range` requires a numeric or temporal `order_by`.\n\n<div class=\"warning\"><code>order_by</code> orders rows <em>within the window\nframe</em> only. It does not reorder the <code>View</code> — that is what the\nview-level <a href=\"./selection_and_ordering.md#sort\"><code>sort</code></a>\nproperty does.</div>"},{"title":"windows » Window Columns » Aggregates","path":"docs/md/explanation/view/config/windows.md","text":"| Aggregate | Description | Result type |\n| --- | --- | --- |\n| `sum`, `avg` | Rolling sum and mean over the frame | `float` |\n| `stddev`, `var` | Rolling standard deviation and variance | `float` |\n| `count` | Number of non-null values in the frame | `integer` |\n| `min`, `max` | Smallest and largest value in the frame | input type |\n| `lag`, `lead` | Value `offset` rows behind or ahead | input type |\n| `diff` | This row's value minus the value `offset` rows behind | `float` |\n| `rate` | Rate of change across the frame | `float` |\n| `ema` | Exponential moving average, smoothed by `alpha` | `float` |\n| `row_number` | 1-based position of the row in its partition's order | `integer` |\n\n`sum`, `avg`, `stddev`, `var`, `diff`, `rate` and `ema` require a numeric\ninput column.\n\n`row_number` numbers rows, not values: it ignores `column` (any column\nsatisfies it), never skips a row whose input is null, and breaks ties on\n`order_by` by primary key."},{"title":"windows » Window Columns » Aggregates » Frame compatibility","path":"docs/md/explanation/view/config/windows.md","text":"- `sum`, `avg`, `count`, `min`, `max`, `stddev` and `var` accept any frame.\n- `lag`, `lead`, `diff`, `ema` and `row_number` are frame-independent — they\n are computed from row positions rather than a frame.\n- **`rate` requires a `range` frame**, and is invalid with `rows` or\n `cumulative`.\n\n<div class=\"warning\">The <code>first</code> and <code>last</code> window\naggregates are declared in the type definitions but are <em>not yet\nimplemented</em> by the engine; a <code>View</code> which uses them will be\nrejected.</div>"},{"title":"windows » Window Columns » Examples » Moving average over a fixed row count","path":"docs/md/explanation/view/config/windows.md","text":"A 10-tick moving average, over the whole table in its natural order:\n\n```json\n{\n \"columns\": [\"10-tick avg Sales\"],\n \"windows\": {\n \"10-tick avg Sales\": {\n \"column\": \"Sales\",\n \"aggregate\": \"avg\",\n \"rows\": 10\n }\n }\n}\n```\n\nwindows » Window Columns » Examples » Moving average over a time range\n\nA 5-second moving average, framing rows by their `Order Date` rather than by\ncount:\n\n```json\n{\n \"columns\": [\"5s avg Sales\"],\n \"windows\": {\n \"5s avg Sales\": {\n \"column\": \"Sales\",\n \"aggregate\": \"avg\",\n \"order_by\": [\"Order Date\", \"asc\"],\n \"range\": 5000\n }\n }\n}\n```"},{"title":"windows » Window Columns » Examples » Cumulative sum","path":"docs/md/explanation/view/config/windows.md","text":"A running total from the start of each partition:\n\n```json\n{\n \"columns\": [\"Cumulative Sales\"],\n \"windows\": {\n \"Cumulative Sales\": {\n \"column\": \"Sales\",\n \"aggregate\": \"sum\",\n \"order_by\": [\"Order Date\", \"asc\"],\n \"cumulative\": true\n }\n }\n}\n```\n\nwindows » Window Columns » Examples » Period-over-period change, per group\n\n`partition_by` restarts the window at each new `Region`, so each region's\nfirst row has no predecessor to difference against:\n\n```json\n{\n \"columns\": [\"Region\", \"Sales\", \"Sales Δ\"],\n \"windows\": {\n \"Sales Δ\": {\n \"column\": \"Sales\",\n \"aggregate\": \"diff\",\n \"partition_by\": [\"Region\"],\n \"order_by\": [\"Order Date\", \"asc\"]\n }\n }\n}\n```"},{"title":"windows » Window Columns » Support","path":"docs/md/explanation/view/config/windows.md","text":"Window Columns are implemented by Perspective's built-in engine, by the\nDuckDB, ClickHouse, PostgreSQL and Polars\n[Virtual Servers](../../virtual_servers.md), and by the\n`<perspective-viewer>` UI. Virtual Servers advertise support through their\n_features_ declaration, so the UI control is hidden for backends which do not\nimplement it."},{"title":"advanced » Advanced View Operations","path":"docs/md/explanation/view/advanced.md","text":"Beyond the standard query configuration, `View` provides additional methods for\ninteracting with hierarchical results and introspecting data.\n\nadvanced » Advanced View Operations » Tree Hierarchy Operations\n\nWhen a `View` has `group_by` applied, the results form a tree hierarchy.\nPerspective provides methods to control which levels of the tree are expanded or\ncollapsed:\n\n<div class=\"javascript\">\n\n```javascript\nconst view = await table.view({ group_by: [\"Region\", \"Country\", \"City\"] });\n\n// Collapse the tree at row index 5\nawait view.collapse(5);\n\n// Expand the tree at row index 5\nawait view.expand(5);\n\n// Set the expansion depth (0 = fully collapsed, 1 = first level, etc.)\nawait view.set_depth(1);\n```\n\n</div>\n<div class=\"python\">\n\nUsing the sync API\n\n```python\nview = table.view(group_by=[\"Region\", \"Country\", \"City\"])\n\nview.collapse(5)\nview.expand(5)\nview.set_depth(1)\n```\n\n</div>\n<div class=\"rust\">\n\n```rust\nlet view = table.view(Some(ViewConfigUpdate {\n group_by: Some(vec![\"Region\".into(), \"Country\".into(), \"City\".into()]),\n ..ViewConfigUpdate::default()\n})).await?;\n\nview.collapse(5).await?;\nview.expand(5).await?;\nview.set_depth(1).await?;\n```\n\n</div>\n\n<span class=\"warning\">Perspective's built-in engine is lazy — aggregates for\ncollapsed rows are not recalculated when the underlying `Table` is updated.\nUpdates are only computed for rows that are currently visible (expanded). When a\ncollapsed row is later expanded, its aggregates are calculated at that\npoint.</span>"},{"title":"advanced » Advanced View Operations » Column Range Queries","path":"docs/md/explanation/view/advanced.md","text":"`View::get_min_max` returns the minimum and maximum values for a given column,\nwhich is useful for setting up scales in custom visualizations:\n\n<div class=\"javascript\">\n\n```javascript\nconst [min, max] = await view.get_min_max(\"Sales\");\n```\n\n</div>\n<div class=\"python\">\n\n```python\nmin_val, max_val = view.get_min_max(\"Sales\")\n```\n\n</div>\n\nadvanced » Advanced View Operations » Describing a View Config\n\n`Table::describe` validates a complete view config against a table and reports\nthe schema a `View` built from it would have - without creating one. `describe`\nreports a `view_schema` if and only if `Table::view` with the same config would\nsucceed, and the two schemas are equal. It costs no engine resources, so it is\nthe right way to check a config before applying it.\n\n<div class=\"javascript\">\n\n```javascript\nconst verdict = await table.describe({\n columns: [\"Sales\", \"margin\"],\n group_by: [\"Region\"],\n expressions: { margin: '\"Profit\" / \"Sales\"' },\n aggregates: { margin: \"avg\" },\n});\n\nif (\"view_schema\" in verdict) {\n // { expression_schema: { margin: \"float\" },\n // view_schema: { Sales: \"float\", margin: \"float\" } }\n} else if (\"expression_errors\" in verdict) {\n // { expression_schema: {...}, expression_errors: { margin: { error_message, line, column } } }\n} else {\n // { config_error: \"Invalid column 'Sales' found in View columns.\" }\n}\n```\n\n</div>\n<div class=\"python\">\n\n```python\nverdict = table.describe(\n columns=[\"Sales\", \"margin\"],\n group_by=[\"Region\"],\n expressions={\"margin\": '\"Profit\" / \"Sales\"'},\n aggregates={\"margin\": \"avg\"},\n)\n\nif \"view_schema\" in verdict:\n ...\nelif \"expression_errors\" in verdict:\n ...\nelse:\n verdict[\"config_error\"]\n```\n\n</div>"},{"title":"advanced » Advanced View Operations » Expression Validation","path":"docs/md/explanation/view/advanced.md","text":"`Table::validate_expressions` is a specialization of `Table::describe` over a\nconfig that selects no columns, so only the expressions are checked. It returns\nwhich expressions are valid and their inferred types, plus an `expression_alias`\nmap echoing the request:\n\n<div class=\"javascript\">\n\n```javascript\nconst result = await table.validate_expressions({\n expr1: '\"Sales\" + \"Profit\"',\n expr2: \"invalid_column + 1\",\n});\n// result.expression_schema contains valid expressions and their types\n// result.errors contains invalid expressions and error messages\n```\n\n</div>\n<div class=\"python\">\n\n```python\nresult = table.validate_expressions(['\"Sales\" + \"Profit\"', 'invalid + 1'])\n```\n\n</div>"},{"title":"advanced » Advanced View Operations » View Dimensions","path":"docs/md/explanation/view/advanced.md","text":"`View::dimensions` returns the number of rows and columns in the current view,\nincluding information about group-by header rows:\n\n<div class=\"javascript\">\n\n```javascript\nconst dims = await view.dimensions();\n// { num_view_rows, num_view_columns, num_table_rows, num_table_columns, ... }\n```\n\n</div>\n<div class=\"python\">\n\n```python\ndims = view.dimensions()\n```\n\n</div>\n\nadvanced » Advanced View Operations » View Configuration Introspection\n\n`View::get_config` returns the full configuration used to create the view:\n\n<div class=\"javascript\">\n\n```javascript\nconst config = await view.get_config();\n// { group_by: [...], split_by: [...], sort: [...], filter: [...], ... }\n```\n\n</div>\n<div class=\"python\">\n\n```python\nconfig = view.get_config()\n```\n\n</div>"},{"title":"advanced » Advanced View Operations » Update Callbacks","path":"docs/md/explanation/view/advanced.md","text":"Register a callback to be notified whenever the underlying `Table` is updated\nand the `View` has been recalculated:\n\n<div class=\"javascript\">\n\n```javascript\nview.on_update(\n (updated) => {\n console.log(\"View updated\", updated.port_id);\n },\n { mode: \"row\" },\n);\n\n// Later, remove the callback\nview.remove_update(callback);\n```\n\n</div>\n<div class=\"python\">\n\n```python\ndef on_update(port_id, delta):\n print(\"View updated\", port_id)\n\nview.on_update(on_update, mode=\"row\")\nview.remove_update(on_update)\n```\n\n</div>\n\nWhen `mode` is set to `\"row\"`, the callback receives a delta of only the rows\nthat changed (as Apache Arrow), which is useful for efficiently synchronizing\ntables across clients."},{"title":"advanced » Advanced View Operations » Remove Callbacks","path":"docs/md/explanation/view/advanced.md","text":"Register a callback to be notified whenever rows are removed from the underlying\n`Table` by `remove()`, which requires an `index`. The callback receives the\n`port_id` and the removed `index` column values as an Apache Arrow of a single\ncolumn named after the index. It fires once per update step, only for rows which\nexisted before that step; `replace()` reports the keys it does not re-supply,\nand `clear()` reports every key:\n\n<div class=\"javascript\">\n\n```javascript\nconst callback = await view.on_remove(({ indices, port_id }) => {\n replica.remove(indices);\n});\n\n// Later, remove the callback\nawait view.remove_remove(callback);\n```\n\n</div>\n<div class=\"python\">\n\n```python\ndef on_remove(port_id, indices):\n replica.remove(indices)\n\ncallback = view.on_remove(on_remove)\nview.remove_remove(callback)\n```\n\n</div>"},{"title":"advanced » Advanced View Operations » Constructing a Table from a View","path":"docs/md/explanation/view/advanced.md","text":"A `Table` can be constructed on a `View`, producing a read-only `Table` of that\n`View`'s output which the engine keeps current as the source is updated. See\n[Derived `Table`s](../derived_table.md) for its columns, its constraints and\nwhat it is good for.\n\n<div class=\"javascript\">\n\n```javascript\nconst worker = await perspective.worker();\nconst table = await worker.table(data);\nconst view = await table.view({ filter: [[\"State\", \"==\", \"Texas\"]] });\nconst table2 = await worker.table(view);\ntable.update([{ State: \"Texas\", City: \"Austin\" }]);\n```\n\n</div>\n<div class=\"python\">\n\n```python\ntable = client.table(data)\nview = table.view(filter=[[\"State\", \"==\", \"Texas\"]])\ntable2 = client.table(view)\ntable.update([{\"State\": \"Texas\", \"City\": \"Austin\"}])\n```\n\n</div>\n<div class=\"rust\">\n\n```rust\nlet opts = TableInitOptions::default();\nlet data = TableData::Update(UpdateData::Csv(\"x,y\\n1,2\\n3,4\".into()));\nlet table = client.table(data, opts).await?;\nlet view = table.view(None).await?;\nlet table2 = client.table(TableData::View(view), TableInitOptions::default()).await?;\ntable.update(data).await?;\n```\n\n</div>"},{"title":"derived_table » Derived Tables","path":"docs/md/explanation/derived_table.md","text":"A `Table` can be constructed from a `View`, producing a read-only `Table` whose\nrows are that `View`'s output. The engine maintains it in place: an update to\nthe source `Table` is reflected in the derived `Table`, and in any `View` of it,\nwithin the same update cycle.\n\nThis is how you query the result of a query — filter on an aggregate, group an\naggregate by a coarser key, or feed a pivot into a window function. It is also\nthe mechanism behind a\n[Client/Server Replicated](./architecture/client_server.md) design, where it\nhandles `View` serialization and `on_update` forwarding for you.\n\n<div class=\"javascript\">\n\n```javascript\nconst view = await table.view({\n group_by: [\"Region\"],\n columns: [\"Sales\"],\n aggregates: { Sales: \"sum\" },\n group_rollup_mode: \"flat\",\n});\n\nconst totals = await client.table(view);\n```\n\n</div>\n<div class=\"python\">\n\n```python\nview = table.view(\n group_by=[\"Region\"],\n columns=[\"Sales\"],\n aggregates={\"Sales\": \"sum\"},\n group_rollup_mode=\"flat\",\n)\n\ntotals = client.table(view)\n```\n\n</div>\n<div class=\"rust\">\n\n```rust\nlet view = table.view(Some(ViewConfigUpdate {\n group_by: Some(vec![\"Region\".into()]),\n columns: Some(vec![Some(\"Sales\".into())]),\n group_rollup_mode: Some(GroupRollupMode::Flat),\n ..ViewConfigUpdate::default()\n})).await?;\n\nlet totals = client.table(TableData::View(view), TableInitOptions::default()).await?;\n```\n\n</div>"},{"title":"derived_table » Derived Tables » Columns","path":"docs/md/explanation/derived_table.md","text":"An unpivoted `View` yields a `Table` with the `View`'s own columns, including\n`expressions` and window columns, and one row for every row passing its\n`filter`. The source's `index` is inherited when the index column is among the\n`View`'s `columns`, and its `limit` is inherited as well.\n\nA pivoted `View` yields the table you see on screen. Each `group_by` column\nbecomes a typed key column of the same name, and each data column takes the name\n`View::column_paths` gives it. A column that is both a `group_by` key and one of\nthe `View`'s `columns` would collide with itself, so that aggregate is qualified\nby its aggregate, as in `\"Region (count)\"` or\n`\"Sales (weighted mean by Weight)\"`; the key is always the bare column name:\n\n```\nRegion,Central|Sales,East|Sales,South|Sales,West|Sales\nFurniture,163797.16,208291.20,117298.68,252612.74\nOffice Supplies,167026.41,205516.05,125651.31,220853.25\n```\n\nKey columns are `null` below the depth of their row, which is how subtotal and\ngrand-total rows are represented."},{"title":"derived_table » Derived Tables » Rollup modes are mirrored","path":"docs/md/explanation/derived_table.md","text":"A derived `Table` has exactly the rows its source `View` displays. Under the\ndefault `group_rollup_mode` of `\"rollup\"` that includes every subtotal row and\nthe grand total; under `\"flat\"` it is the leaves only; under `\"total\"` it is the\nsingle total row. `split_rollup_mode` selects the data columns the same way.\n\n<span class=\"warning\">Set `group_rollup_mode: \"flat\"` on the source `View`\nbefore grouping or filtering a derived `Table`, or its subtotal rows will be\ncounted alongside the rows they already summarize.</span>\n\n`sort`, `expand`/`collapse` and `group_by_depth` describe how a `View` is\npresented rather than what it contains, and do not affect the derived `Table`."},{"title":"derived_table » Derived Tables » Read-only","path":"docs/md/explanation/derived_table.md","text":"The engine owns a derived `Table`'s rows, so `update()`, `remove()`, `clear()`\nand `replace()` all raise. Change the source `Table` instead. The `index` and\n`limit` options are rejected for the same reason — identity comes from the\nsource.\n\nDeleting a `View` that a derived `Table` still reads raises as well; delete the\nderived `Table` first.\n\nderived_table » Derived Tables » The schema is fixed\n\nA derived `Table`'s columns are decided when it is created, which matters for\n`split_by`, whose columns depend on the data. A split value that first appears\nlater has no column to land in and its cells are dropped; one that disappears\nleaves its column in place, filled with `null`.\n\nDeclare the columns up front to accept values that have not arrived yet:\n\n<div class=\"javascript\">\n\n```javascript\nconst derived = await client.table(view, {\n schema: {\n Region: \"string\",\n \"Mon|Sales\": \"float\",\n \"Tue|Sales\": \"float\",\n \"Wed|Sales\": \"float\",\n },\n});\n```\n\n</div>\n<div class=\"python\">\n\n```python\nderived = client.table(\n view,\n schema={\n \"Region\": \"string\",\n \"Mon|Sales\": float,\n \"Tue|Sales\": float,\n \"Wed|Sales\": float,\n },\n)\n```\n\n</div>\n<div class=\"rust\">\n\n```rust\nlet derived = client.table(TableData::View(view), TableInitOptions {\n schema: Some(IndexMap::from([\n (\"Region\".into(), ColumnType::String),\n (\"Mon|Sales\".into(), ColumnType::Float),\n ])),\n ..TableInitOptions::default()\n}).await?;\n```\n\n</div>\n\nA declared column that the `View` does not have is `null` until its values\narrive; a column the `View` has but the schema omits is dropped. A declared type\nwhich disagrees with the `View`'s is an error."},{"title":"derived_table » Derived Tables » Filtering on an aggregate","path":"docs/md/explanation/derived_table.md","text":"SQL's `HAVING` — show only the groups whose total passes a predicate — is a\n`filter` on a derived `Table`:\n\n<div class=\"javascript\">\n\n```javascript\nconst totals = await table.view({\n group_by: [\"Category\", \"Sub-Category\"],\n columns: [\"Sales\"],\n aggregates: { Sales: \"sum\" },\n group_rollup_mode: \"flat\",\n});\n\nconst derived = await client.table(totals);\nconst large = await derived.view({ filter: [[\"Sales\", \">\", 100000]] });\n```\n\n</div>\n<div class=\"python\">\n\n```python\ntotals = table.view(\n group_by=[\"Category\", \"Sub-Category\"],\n columns=[\"Sales\"],\n aggregates={\"Sales\": \"sum\"},\n group_rollup_mode=\"flat\",\n)\n\nderived = client.table(totals)\nlarge = derived.view(filter=[[\"Sales\", \">\", 100000]])\n```\n\n</div>\n<div class=\"rust\">\n\n```rust\nlet derived = client.table(TableData::View(totals), TableInitOptions::default()).await?;\nlet large = derived.view(Some(ViewConfigUpdate {\n filter: Some(vec![Filter::new(\"Sales\", \">\", 100000)]),\n ..ViewConfigUpdate::default()\n})).await?;\n```\n\n</div>"},{"title":"derived_table » Derived Tables » Change between pivot columns","path":"docs/md/explanation/derived_table.md","text":"To compare an aggregate against the previous column of a pivot — day-over-day\nchange of an accruing balance, for example — group by the pivot key rather than\nsplitting on it, then apply a `diff` [window](./view/config/windows.md) to the\nderived `Table` and split there:\n\n<div class=\"javascript\">\n\n```javascript\nconst daily = await table.view({\n group_by: [\"Portfolio\", \"Date\"],\n columns: [\"PnL\"],\n aggregates: { PnL: \"last\" },\n group_rollup_mode: \"flat\",\n});\n\nconst derived = await client.table(daily);\nconst change = await derived.view({\n group_by: [\"Portfolio\"],\n split_by: [\"Date\"],\n columns: [\"change\"],\n aggregates: { change: \"sum\" },\n group_rollup_mode: \"flat\",\n windows: {\n change: {\n column: \"PnL\",\n aggregate: \"diff\",\n partition_by: [\"Portfolio\"],\n order_by: [\"Date\", \"asc\"],\n },\n },\n});\n```\n\n</div>\n<div class=\"python\">\n\n```python\ndaily = table.view(\n group_by=[\"Portfolio\", \"Date\"],\n columns=[\"PnL\"],\n aggregates={\"PnL\": \"last\"},\n group_rollup_mode=\"flat\",\n)\n\nderived = client.table(daily)\nchange = derived.view(\n group_by=[\"Portfolio\"],\n split_by=[\"Date\"],\n columns=[\"change\"],\n aggregates={\"change\": \"sum\"},\n group_rollup_mode=\"flat\",\n windows={\n \"change\": {\n \"column\": \"PnL\",\n \"aggregate\": \"diff\",\n \"partition_by\": [\"Portfolio\"],\n \"order_by\": [\"Date\", \"asc\"],\n }\n },\n)\n```\n\n</div>\n\nA window runs down rows, so the axis it walks has to be a `group_by` of the\nsource. When each row of the source `Table` is already one observation per key —\none row per portfolio per day — no derived `Table` is needed, and the same\nwindow applied directly to that `Table` gives the same result."},{"title":"derived_table » Derived Tables » Limitations","path":"docs/md/explanation/derived_table.md","text":"A derived `Table` re-aggregates its own rows, so an expression over aggregate\ncolumns is only correct at the granularity those rows already have. A ratio of\nsums, for instance, cannot be re-derived correctly for a coarser `group_by` of\nthe derived `Table`, because the ratios are averaged rather than recomputed.\n\nConstructing a `Table` from a `View` hosted by a _different_ `Client` copies the\n`View`'s data across that boundary instead, which appends rather than updates in\nplace for a pivoted `View`.\n\nOn a [Virtual Server](./virtual_servers.md) a derived `Table` is a snapshot of\nthe `View` taken when it is created, since a virtual `View` is itself a\nsnapshot."},{"title":"derived_table » Derived Tables » In the viewer","path":"docs/md/explanation/derived_table.md","text":"`<perspective-viewer>` exposes derivation from the settings sidebar's query tab\nand from the panel's right-click context menu. **Derive table** (at the bottom\nof the settings panel) and the context menu's **Derive Table** each create a\nderived `Table` under a generated name — `\"superstore GROUP BY Region\"`-style —\nfrom whatever the panel currently displays, then rebind the panel to it. Both\nrequire a pivoted view.\n\nA derived `Table` created this way is an ordinary named table in the workspace:\nit appears in the table pickers and the \"New\" menu, other panels may bind to it,\nand it outlives the panel that created it. The \"New\" menu marks derived rows,\nshows how many panels and tables read each one, and offers a delete button once\nnothing does.\n\nThe panel's derivation chain appears above the control, one row per table from\nthe base up. The row for the panel's own table carries a close button:\n\n- **Click** rebinds the panel to the parent table, restoring the config the\n derived table was created with. The derived table itself is untouched.\n- **Shift+click** rebinds _and_ deletes the derived table, which is offered only\n when that panel is the only thing reading it.\n\nA \"full\" reset — shift-clicking the status bar's **Reset** button, or\n`reset(true)` / `resetPanel` with `reset_all` from the API — tears the whole\nderived state down: every panel bound to a derived `Table` is released to its\nroot table, and every derivation (pending ones included) is deleted.\n\nA backend which cannot promote a `View` to a `Table` reports so through the\n`view_derivations` capability, and the control is hidden for panels on that\nclient. Where a backend can derive but does not stream — DuckDB and PostgreSQL —\neach level is badged as a snapshot, because its data is fixed at the moment it\nwas derived.\n\nDerived tables persist in `saveWorkspace()` output under the top-level\n`derived_tables` key, each entry naming the `table` its source `View` reads and\nthat view's config. Workspace panels refer to tables by name; the single-panel\n`save()` format instead inlines the definition recursively in its `table` field,\nso the token is self-contained, and `restore()` rebinds to a structurally\nidentical derived table or creates one under a generated name. See\n[Save & restore](../how_to/javascript/save_restore.md)."},{"title":"derived_table » Derived Tables » Changes to client.table(view)","path":"docs/md/explanation/derived_table.md","text":"Previous releases copied the `View`'s data into an ordinary `Table` and\nforwarded updates to it. Derived `Table`s are maintained by the engine, which\nchanges several behaviors for code written against the old implementation:\n\n- A derived `Table` is read-only, where the copy accepted writes that the next\n update would overwrite.\n- Deleting the source `View` raises, where the copy became a frozen snapshot.\n- Rows follow the source's insertion order; the source `View`'s `sort` no longer\n orders the copy.\n- A row updated out of the source `View`'s `filter` is removed, where the copy\n retained it with stale values.\n- Updates arrive in the same cycle as the source rather than eventually.\n- A `View` which omits its source's index column is keyed by row identity, so\n updates apply in place rather than appending.\n- A pivoted `View` yields named key columns and only the rows the `View`\n displays, where the copy appended its changed rows on every update."},{"title":"viewer » <perspective-viewer> Custom Element library","path":"docs/md/how_to/javascript/viewer.md","text":"`<perspective-viewer>` provides a complete graphical UI for configuring the\n`perspective` library and formatting its output to the provided visualization\nplugins.\n\nOnce imported and initialized in JavaScript, the `<perspective-viewer>` Web\nComponent will be available in any standard HTML on your site. A simple example:\n\n```html\n<perspective-viewer id=\"view1\"></perspective-viewer>\n<script type=\"module\">\n import perspective from \"@perspective-dev/client\";\n import \"@perspective-dev/viewer\";\n\n const viewer = document.getElementById(\"view1\");\n const worker = await perspective.worker();\n await worker.table(data, { name: \"my_table\" });\n\n await viewer.load(worker);\n await viewer.restore({ table: \"my_table\" });\n</script>\n```\n\n`load()` binds the viewer to a `Client`, and `restore()` selects which of that\nclient's `Table`s to show via the `table` field. Because `load()` alone\nselects no table, it does not render — the pair guarantees exactly one atomic\nrender.\n\nPassing a `Table` directly is still supported as a legacy shorthand,\n\n```javascript\nawait viewer.load(table);\n```\n\n... which is internally equivalent to:\n\n```javascript\nawait viewer.load(await table.get_client());\nawait viewer.restore({ table: await table.get_name() });\n```\n\n<div class=\"warning\">Always give your <code>Table</code> a <code>name</code>.\nWhen <code>name</code> is omitted a random one is assigned, so the\n<code>table</code> field in a token from <code>save()</code> will not match\nthe table after a page reload, and <code>restore()</code> will fail.</div>"},{"title":"viewer » <perspective-viewer> Custom Element library » Attributes","path":"docs/md/how_to/javascript/viewer.md","text":"`<perspective-viewer>` can be configured via HTML attributes or JavaScript\nproperties. When set as attributes, the viewer will apply the configuration on\ninitialization:\n\n```html\n<perspective-viewer\n columns='[\"Sales\", \"Profit\"]'\n group-by='[\"Region\"]'\n sort='[[\"Sales\", \"desc\"]]'>\n</perspective-viewer>\n```\n\nviewer » <perspective-viewer> Custom Element library » UI Features\n\nThe viewer provides an interactive side panel with:\n\n- **Column list** - drag and drop columns to configure `group_by`, `split_by`,\n `sort`, and `filter` fields.\n- **New Column** button - opens an expression editor for creating computed\n columns via the [expression language](../../explanation/view/config/expressions.md).\n- **Plugin selector** - switch between the visualization plugins registered on\n the page. `@perspective-dev/viewer-datagrid` provides `Datagrid`;\n `@perspective-dev/viewer-charts` provides `X Bar`, `Y Bar`, `Y Line`,\n `Y Scatter`, `Y Area`, `X/Y Scatter`, `X/Y Line`, `Density`, `Treemap`,\n `Sunburst`, `Heatmap`, `Candlestick`, `OHLC`, `Map Scatter`, `Map Line` and\n `Map Density`.\n- **Theme** selector - toggle between available themes.\n- **Export** - download the current view as CSV or Arrow.\n- **Copy** - copy the current view to the clipboard.\n- **Reset** - restore the viewer to its default configuration."},{"title":"viewer » <perspective-viewer> Custom Element library » Methods","path":"docs/md/how_to/javascript/viewer.md","text":"A `<perspective-viewer>` hosts one or more _panels_. Methods which address a\nsingle panel take an options-dict with an optional `panel` id, defaulting to\nthe _active_ panel — e.g. `await viewer.save({ panel: \"PANEL_ID_0\" })`.\n\nviewer » <perspective-viewer> Custom Element library » Methods » Binding\n\n| Method | Description |\n|---|---|\n| `load(client)` | Bind a `Client` (or, legacy, a `Table`) to the viewer |\n| `eject(options?)` | Remove a `Client` and dispose every panel bound to it |\n| `delete()` | Release the element's resources |\n| `getClient(options?)` | Get a bound `Client` |\n| `getTable(options?)` | Get a panel's `Table` |\n| `getView(options?)` | Get a panel's `View` — `mode` selects `\"live\"` (default), `\"clone\"` or `\"auto\"` |\n| `getViewConfig(options?)` | Get a panel's `ViewConfig` |"},{"title":"viewer » <perspective-viewer> Custom Element library » Methods » Binding » View lifecycle","path":"docs/md/how_to/javascript/viewer.md","text":"By default `getView()` returns the panel's own _live_ `View`. The viewer owns\nit: every config change replaces it, and auto-pause (the element scrolled out\nof view, `display: none`, or a backgrounded tab) or `delete()` deletes it. Any\ncall on a live `View` can therefore fail with \"View already deleted\", even one\nalready in flight, and it must never be `delete()`d by the caller. While a\npanel is auto-paused or has not rendered yet it has no live `View`, and\n`getView()` rejects with `No View for panel \"<name>\"`; `setAutoPause(false)`\nforces one to exist for every panel, at the cost of a live subscription each.\n\nA reference that has to outlive the render lifecycle should not borrow the\nviewer's `View` at all. `mode: \"clone\"` builds a new `View` from the panel's\neffective config (the element's global filter included) which is independent\nof rendering and pause, and which the caller owns and must `delete()`:\n\n```javascript\nfor (const panel of viewer.getPanelNames()) {\n const view = await viewer.getView({ panel, mode: \"clone\" });\n const arrow = await view.to_arrow();\n await view.delete();\n}\n```\n\n`mode: \"auto\"` returns the live `View` when one exists and a clone otherwise.\nOwnership follows whichever was returned, so use it only where the caller\nnever deletes the result."},{"title":"viewer » <perspective-viewer> Custom Element library » Methods » Configuration","path":"docs/md/how_to/javascript/viewer.md","text":"| Method | Description |\n|---|---|\n| `save(options?)` | Serialize one panel's configuration |\n| `restore(config, options?)` | Apply a configuration to one panel |\n| `saveWorkspace()` | Serialize the whole element — every panel, plus layout and global filters |\n| `restoreWorkspace(config)` | Apply a workspace config update; absent fields are left unchanged |\n| `reset(all?, options?)` | Reset configuration (pass `true` to also reset expressions) |\n| `resetError()` | Clear the error overlay |\n\nviewer » <perspective-viewer> Custom Element library » Methods » Panels\n\n| Method | Description |\n|---|---|\n| `addPanel(config)` | Add a panel, returning its generated id |\n| `removePanel(id)` | Remove a panel |\n| `getPanelNames()` | List panel ids |\n| `getActivePanel()` / `setActivePanel(id)` | Get or set the active panel |"},{"title":"viewer » <perspective-viewer> Custom Element library » Methods » Output","path":"docs/md/how_to/javascript/viewer.md","text":"| Method | Description |\n|---|---|\n| `export(options?)` | Export a panel — see the export methods below |\n| `download(options?)` | Export and download as a file |\n| `copy(options?)` | Copy a panel to the clipboard |\n\n`export()`, `download()` and `copy()` all take a `method`, one of `\"csv\"`,\n`\"json\"`, `\"ndjson\"`, `\"arrow\"`, `\"arrow-lz4\"` or `\"arrow-zstd\"` — each with\n`-all` and `-selected` variants (e.g. `\"csv-selected\"`, `\"arrow-zstd-all\"`) —\nplus `\"html\"`, `\"json-config\"`, and `\"plugin\"`. The `\"arrow-lz4\"` and\n`\"arrow-zstd\"` methods write the Arrow IPC stream with LZ4 or ZSTD body\ncompression.\nThe `\"plugin\"` method asks the plugin to render itself, which produces a PNG\nfor charts and text for the datagrid.\n| `getSelection(options?)` / `setSelection(...)` | Get or set the selected region |\n| `getEditPort(options?)` | Get a panel's edit port |\n| `getRenderStats(options?)` | Get render timing statistics |"},{"title":"viewer » <perspective-viewer> Custom Element library » Methods » Rendering and chrome","path":"docs/md/how_to/javascript/viewer.md","text":"| Method | Description |\n|---|---|\n| `flush()` | Wait for any pending UI updates to complete |\n| `resize(options?)` | Redraw, optionally at a `{dimensions: {width, height}}` size hint |\n| `setAutoSize(bool)` / `setAutoPause(bool)` / `setThrottle(ms)` | Render policy |\n| `toggleConfig(force?)` | Toggle the settings sidebar |\n| `toggleColumnSettings(...)` | Toggle the column settings sidebar |\n| `resetThemes(themes?)` | Re-detect or explicitly set available themes |\n| `restyleElement()` | Re-read CSS and repaint |\n| `getPlugin(name?)` / `getAllPlugins()` | Look up registered plugins |\n\nSee [Saving and restoring UI state](./save_restore.md) for the `save`/`restore`\nformats and the panel selector, and\n[Plugin render limits](./plugin_settings.md) for `getPlugin`."},{"title":"loading_data » Loading data from a Table","path":"docs/md/how_to/javascript/loading_data.md","text":"Data can be loaded into `<perspective-viewer>` in the form of a `Table()` or a\n`Promise<Table>` via the `load()` method.\n\n```javascript\n// Create a new worker, then a new table promise on that worker.\nconst worker = await perspective.worker();\nconst table = await worker.table(data);\n\n// Bind a viewer element to this table.\nawait viewer.load(table);\n```\n\nloading_data » Loading data from a Table » Sharing a Table between multiple <perspective-viewer>s\n\nMultiple `<perspective-viewer>`s can share a `table()` by passing the `table()`\ninto the `load()` method of each viewer. Each `perspective-viewer` will update\nwhen the underlying `table()` is updated, but `table.delete()` will fail until\nall `perspective-viewer` instances referencing it are also deleted:\n\n```javascript\nconst viewer1 = document.getElementById(\"viewer1\");\nconst viewer2 = document.getElementById(\"viewer2\");\n\n// Create a new WebWorker\nconst worker = await perspective.worker();\n\n// Create a table in this worker\nconst table = await worker.table(data);\n\n// Load the same table in 2 different <perspective-viewer> elements\nawait viewer1.load(table);\nawait viewer2.load(table);\n\n// Both `viewer1` and `viewer2` will reflect this update\nawait table.update([{ x: 5, y: \"e\", z: true }]);\n```"},{"title":"loading_data » Loading data from a Table » Loading from a virtual Table","path":"docs/md/how_to/javascript/loading_data.md","text":"Loading a virtual (server-only) `Table` works just like loading a local/Web\nWorker `Table` — just pass the virtual `Table` to `viewer.load()`. In the\nbrowser:\n\n```javascript\nconst elem = document.getElementsByTagName(\"perspective-viewer\")[0];\n\n// Bind to the server's worker instead of instantiating a Web Worker.\nconst websocket = await perspective.websocket(\n window.location.origin.replace(\"http\", \"ws\")\n);\n\n// Bind the viewer to the preloaded data source. `table` and `view` objects\n// live on the server.\nconst server_table = await websocket.open_table(\"table_one\");\nawait elem.load(server_table);\n```\n\nAlternatively, data can be _cloned_ from a server-side virtual `Table` into a\nclient-side WebAssembly `Table`. The browser clone will be synced via delta\nupdates transferred via Apache Arrow IPC format, but local `View`s created will\nbe calculated locally on the client browser.\n\n```javascript\nconst worker = await perspective.worker();\nconst server_view = await server_table.view();\nconst client_table = worker.table(server_view);\nawait elem.load(client_table);\n```\n\n`<perspective-viewer>` instances bound in this way are otherwise no different\nthan `<perspective-viewer>`s which rely on a Web Worker, and can even share a\nhost application with Web Worker-bound `table()`s. The same `promise`-based API\nis used to communicate with the server-instantiated `view()`, only in this case\nit is over a websocket."},{"title":"theming » Theming","path":"docs/md/how_to/javascript/theming.md","text":"Theming is supported in `perspective-viewer` and its accompanying plugins. A\nnumber of themes come bundled with `perspective-viewer`; you can import any of\nthese themes directly into your app, and the `perspective-viewer`s will be\nthemed accordingly:\n\n```javascript\n// Themes based on Thought Merchants's Prospective design\nimport \"@perspective-dev/viewer/dist/css/pro.css\";\nimport \"@perspective-dev/viewer/dist/css/pro-dark.css\";\n\n// Other themes\nimport \"@perspective-dev/viewer/dist/css/solarized.css\";\nimport \"@perspective-dev/viewer/dist/css/monokai.css\";\nimport \"@perspective-dev/viewer/dist/css/vaporwave.css\";\n\n// ...\n\n```\n\nAlternatively, you may use `themes.css`, which bundles all default themes\n\n```javascript\nimport \"@perspective-dev/viewer/dist/css/themes.css\";\n```\n\nIf you choose not to bundle the themes yourself, they are available through\n[CDN](https://cdn.jsdelivr.net/npm/@perspective-dev/viewer/dist/css/). These can\nbe directly linked in your HTML file:\n\n```html\n<link\n rel=\"stylesheet\"\n crossorigin=\"anonymous\"\n href=\"https://cdn.jsdelivr.net/npm/@perspective-dev/viewer/dist/css/pro.css\"\n/>\n```\n\nNote the `crossorigin=\"anonymous\"` attribute. When including a theme from a\ncross-origin context, this attribute may be required to allow\n`<perspective-viewer>` to detect the theme. If this fails, additional themes are\nadded to the `document` after `<perspective-viewer>` init, or for any other\nreason theme auto-detection fails, you may manually inform\n`<perspective-viewer>` of the available theme names with the `.resetThemes()`\nmethod.\n\n```javascript\n// re-auto-detect themes\nviewer.resetThemes();\n\n// Set available themes explicitly (they still must be imported as CSS!)\nviewer.resetThemes([\"Pro Light\", \"Pro Dark\"]);\n```\n\n`<perspective-viewer>` will default to the first loaded theme when initialized.\nYou may override this via `.restore()`, or provide an initial theme by setting\nthe `theme` attribute:\n\n```html\n<perspective-viewer theme=\"Pro Light\"></perspective-viewer>\n```\n\nor\n\n```javascript\nconst viewer = document.querySelector(\"perspective-viewer\");\nawait viewer.restore({ theme: \"Pro Dark\" });\n```"},{"title":"theming » Theming » Custom Themes","path":"docs/md/how_to/javascript/theming.md","text":"The best way to write a new theme is to\n[fork and modify an existing theme](https://github.com/perspective-dev/perspective/tree/master/rust/perspective-viewer/src/themes),\nwhich are _just_ collections of regular CSS variables — Perspective's own\nthemes are plain `.css` files, with no preprocessor involved.\n`<perspective-viewer>` is\nnot \"themed\" by default and will lack icons and label text in addition to colors\nand fonts, so starting from an empty theme forces you to define _every_\ntheme-able variable to get a functional UI."},{"title":"theming » Theming » Custom Themes » Icons and Translation","path":"docs/md/how_to/javascript/theming.md","text":"UI icons are defined by CSS variables provided by\n[`@perspective-dev/viewer/dist/css/icons.css`](https://github.com/perspective-dev/perspective/blob/master/rust/perspective-viewer/src/themes/icons.css).\nThese variables must be defined for the UI icons to work - there are no default\nicons without a theme.\n\nUI text is also defined in CSS variables provided by\n[`@perspective-dev/viewer/dist/css/intl.css`](https://github.com/perspective-dev/perspective/blob/master/rust/perspective-viewer/src/themes/intl.css),\nand has identical import requirements. Some _example definitions_\n(automatically-translated sans-editing) can be found\n[`@perspective-dev/viewer/dist/css/intl/` folder](https://github.com/perspective-dev/perspective/tree/master/rust/perspective-viewer/src/themes/intl).\n\nImporting the pre-built `themes.css` stylesheet as well as a custom theme will\ndefine Icons and Translation globally as a side-effect. You can still customize\nicons in this mode with rules (of the appropriate specificity), _but_ if you do\nnot still remember to define these variables yourself, your theme will not work\nwithout the base `themes.css` package available."},{"title":"save_restore » Saving and restoring UI state.","path":"docs/md/how_to/javascript/save_restore.md","text":"`<perspective-viewer>` is _persistent_, in that its entire state (sans the data\nitself) can be serialized or deserialized. This include all column, filter,\npivot, expressions, etc. properties, as well as datagrid style settings, config\npanel visibility, and more. This overloaded feature covers a range of use cases:\n\n- Setting a `<perspective-viewer>`'s initial state after a `load()` call.\n- Updating a single or subset of properties, without modifying others.\n- Resetting some or all properties to their data-relative default.\n- Persisting a user's configuration to `localStorage` or a server."},{"title":"save_restore » Saving and restoring UI state. » Serializing and deserializing the viewer state","path":"docs/md/how_to/javascript/save_restore.md","text":"To retrieve the entire state as a JSON-ready JavaScript object, use the `save()`\nmethod. `save()` also supports a few other formats such as `\"arraybuffer\"` and\n`\"string\"` (base64, not JSON), which you may choose for size at the expense of\neasy migration/manual-editing.\n\n```javascript\nconst json_token = await elem.save();\nconst string_token = await elem.save(\"string\");\n```\n\nFor any format, the serialized token can be restored to any\n`<perspective-viewer>` with a `Table` of identical schema, via the `restore()`\nmethod. Note that while the data for a token returned from `save()` may differ,\ngenerally its schema may not, as many other settings depend on column names and\ntypes.\n\n```javascript\nawait elem.restore(json_token);\nawait elem.restore(string_token);\n```\n\nAs `restore()` dispatches on the token's type, it is important to make sure that\nthese types match! A common source of error occurs when passing a\nJSON-stringified token to `restore()`, which will assume base64-encoded msgpack\nwhen a string token is used.\n\n```javascript\n// This will error!\nawait elem.restore(JSON.stringify(json_token));\n```"},{"title":"save_restore » Saving and restoring UI state. » Serializing and deserializing the viewer state » Updating individual properties","path":"docs/md/how_to/javascript/save_restore.md","text":"Using the JSON format, every facet of a `<perspective-viewer>`'s configuration\ncan be manipulated from JavaScript using the `restore()` method. The valid\nstructure of properties is described via the\n[`ViewerConfigUpdate`](https://github.com/perspective-dev/perspective/blob/master/rust/perspective-viewer/src/ts/ts-rs/ViewerConfigUpdate.ts)\nand embedded\n[`ViewConfigUpdate`](https://github.com/perspective-dev/perspective/blob/master/rust/perspective-js/src/ts/ts-rs/ViewConfigUpdate.ts)\ntype declarations (both generated from the Rust definitions), and the\n[`View`](../../explanation/view.md) chapter of the documentation which has\nseveral examples for each `ViewConfig` property.\n\n```javascript\n// Set the plugin (will also update `columns` to plugin-defaults)\nawait elem.restore({ plugin: \"X Bar\" });\n\n// Update plugin and columns (only draws once)\nawait elem.restore({ plugin: \"X Bar\", columns: [\"Sales\"] });\n\n// Open the config panel\nawait elem.restore({ settings: true });\n\n// Create an expression\nawait elem.restore({\n columns: ['\"Sales\" + 100'],\n expressions: { \"New Column\": '\"Sales\" + 100' },\n});\n\n// ERROR if the column does not exist in the schema or expressions\n// await elem.restore({columns: [\"\\\"Sales\\\" + 100\"], expressions: {}});\n\n// Add a filter\nawait elem.restore({ filter: [[\"Sales\", \"<\", 100]] });\n\n// Add a sort, don't remove filter\nawait elem.restore({ sort: [[\"Prodit\", \"desc\"]] });\n\n// Reset just filter, preserve sort\nawait elem.restore({ filter: undefined });\n\n// Reset all properties to default e.g. after `load()`\nawait elem.reset();\n```\n\nAnother effective way to quickly create a token for a desired configuration is\nto simply copy the token returned from `save()` after settings the view manually\nin the browser. The JSON format is human-readable and should be quite easy to\ntweak once generated, as `save()` will return even the default settings for all\nproperties. You can call `save()` in your application code, or e.g. through the\nChrome developer console:\n\n```javascript\n// Copy to clipboard\ncopy(await document.querySelector(\"perspective-viewer\").save());\n```"},{"title":"save_restore » Saving and restoring UI state. » Multi-panel viewers","path":"docs/md/how_to/javascript/save_restore.md","text":"`save()` and `restore()` operate on a _single_ panel — the _active_ one by\ndefault, or a specific panel via their optional `{ panel }` selector (e.g.\n`await elem.save({ panel: \"my-panel\" })`). If `restore()`'s `panel` names no\nexisting panel, a new panel is created with that id.\n\nA `<perspective-viewer>` may host multiple panels. To serialize or restore the\n_whole element_ — every panel plus the layout and cross-filter state — use\n`saveWorkspace()` and `restoreWorkspace()` instead:\n\n```javascript\nconst workspace_token = await elem.saveWorkspace();\nawait elem.restoreWorkspace(workspace_token);\n```\n\nA `saveWorkspace()` token is a `WorkspaceConfig`\n(`{ version, layout, panels, ... }`), not a `ViewerConfig` — passing it to the\nsingle-panel `restore()` will _not_ restore the layout (its `panels`/`layout`\nkeys are ignored).\n\nThe `layout` field is the tree arranging the panels into a grid of rows and\ncolumns or stacks of tabs, naming every panel id exactly once. See\n[Arranging panels: layouts and dashboards](./layout.md) for its format and for\nrearranging, resizing and stacking existing panels.\n\nLike `restore()`, `restoreWorkspace()` applies an _update_: a field that is\nabsent leaves that part of the element unchanged, `null` resets it to its\ndefault, and a value replaces it. `panels` is the whole panel set — when present\nevery existing panel is replaced (`{}` empties the element); when absent the\npanels are kept, and `layout`, `active` and `masters` name them by their\nexisting ids. So a cross-filter can be applied to the current panels without\nre-creating them:\n\n```javascript\nawait elem.restoreWorkspace({ global_filters: [[\"Region\", \"==\", \"West\"]] });\nawait elem.restoreWorkspace({ global_filters: null }); // clear\n```"},{"title":"save_restore » Saving and restoring UI state. » Multi-panel viewers » Derived tables","path":"docs/md/how_to/javascript/save_restore.md","text":"A workspace token carries a top-level `derived_tables` map, one entry per\nderived `Table` the element owns, each naming the `table` its source `View`\nreads plus that view's config:\n\n```javascript\nawait elem.restoreWorkspace({\n panels: { one: { table: \"Region Totals\", plugin: \"Datagrid\" } },\n derived_tables: {\n \"Region Totals\": { table: \"superstore\", group_by: [\"Region\"] },\n },\n});\n```\n\nWithin a workspace token, panels refer to tables by name, so a panel entry never\ncontains a derivation of its own — `one` above simply binds `\"Region Totals\"`.\nEntries may name another derived table as their `table`, and they are built\nparent-first regardless of the order they appear in.\n\n`derived_tables` follows the same update rule as `panels`: absent keeps the\ncurrent set, a value replaces it entirely, and `{}` deletes every derived table.\nAn entry whose definition has not changed is reused rather than rebuilt.\n\nA derived table whose `table` is not hosted yet does not fail the restore — it\nand anything under it wait, along with the panels bound to them, and are built\nwhen the parent table appears. A dependency cycle is rejected outright.\n\nThe single-panel format inlines instead: `save()` of a panel bound to a derived\ntable writes the whole definition recursively in the `table` field, so the token\nis self-contained:\n\n```javascript\nawait elem.restore({\n plugin: \"Datagrid\",\n table: { table: \"superstore\", group_by: [\"Region\"] },\n});\n```\n\n`restore()` rebinds to an existing derived table when one was built from a\nstructurally identical definition, and otherwise creates one under a generated\nname. Only the single-panel `restore()` accepts the inline form — in\n`restoreWorkspace()` every `table` field is a name.\n\nSee [Derived tables](../../explanation/derived_table.md)."},{"title":"save_restore » Saving and restoring UI state. » Colors, palettes and gradients (1)","path":"docs/md/how_to/javascript/save_restore.md","text":"Per-column color styling lives in a panel's `columns_config`, keyed by column\nname, and every color-scale value is a string usable verbatim in CSS:\n\n| Kind | Value |\n| -------- | ----------------------------------------------------------------------------------- |\n| color | `\"#rrggbb\"` (`#rgb`, `rgb()` and `rgba()` are accepted on input) |\n| palette | `\"linear-gradient(to right, #rrggbb, #rrggbb, …)\"` — N colors, **no** positions |\n| gradient | `\"linear-gradient(to right, #rrggbb 0%, #rrggbb 37.5%, …)\"` — every stop positioned |\n\nWhich reader applies is decided by the style control's kind, never by inspecting\nthe string. The datagrid's `fg_color`/`bg_color` are read according to the\ncolumn's type and its `fg_mode`/`bg_mode`: a gradient for numeric columns, a\ncolor for string and datetime columns in `\"color\"` mode, and a palette for\nstring columns in `\"series\"` mode; the charts' `gradient` is a gradient and\n`palette` a palette. A position anywhere in a palette is rejected, while a\ngradient may omit positions on input (the CSS implicit-position rules fill them)\nand may carry any direction token, which is normalized to `to right`. A mode the\ncolumn's type does not accept (say `fg_mode: \"series\"` on a float column)\nrejects the `restore()` call, as does a value that does not parse under the\nmode's reader. Values equal to the plugin's default are not serialized.\n\n```javascript\nawait viewer.restore({\n plugin: \"Datagrid\",\n columns_config: {\n Profit: {\n bg_mode: \"gradient\",\n bg_color: \"linear-gradient(to right, #ff0000, #ffffff, #0000ff)\",\n },\n },\n});\n```\n\nAny of these may instead be a reference to a CSS custom property of the same\nkind — `\"var(--psp-user--color-<name>)\"`, `\"var(--psp-user--palette-<name>)\"` or\n`\"var(--psp-user--gradient-<name>)\"`. References are resolved when the config is\nwritten, against the element's computed style: the `palette` of the last\n`restoreWorkspace()` (below) takes precedence, then any `--psp-user--*` property\na theme or the page defines on the element. An unresolvable reference is dropped\n(the plugin's default renders). Panels hold literals from then on — `save()`\nalways emits literals, and the column style tab always edits a literal."},{"title":"save_restore » Saving and restoring UI state. » Colors, palettes and gradients (2)","path":"docs/md/how_to/javascript/save_restore.md","text":"`saveWorkspace()` emits a **palette**: every color value in use across the\npanels is written in `panels` as a `var()` reference, and the top-level\n`palette` map (custom property name → value) carries each referenced definition\nonce. Names are stable — a value keeps the name the last `restoreWorkspace()`\ngave it when the values match, reuses a theme entry's name when it matches one\n(`--psp-user--<kind>-1`, `-2`, … are discovered by contiguous numbering), and\notherwise takes a fresh `--psp-user--<kind>-N`. `restoreWorkspace()` applies\n`palette` to the element as inline custom properties (replacing any previously\nrestored palette) before the panels' references resolve — which also makes it\nthe way to inject a brand or theme variation for a workspace to draw on.\n\nBy default only the values the panels reference are serialized; a restored\npalette's unused entries, and values pinned during a session, are in-session\nstate. Pass `{ full_palette: true }` to emit the element's whole set — in-use\nvalues unioned with the last restored palette and anything pinned since — for a\nsymmetric round trip:\n\n```javascript\nconst used_only = await elem.saveWorkspace();\nconst everything = await elem.saveWorkspace({ full_palette: true });\n```\n\nIn the column style tab, each color field's **Load** control lists the element's\nset (plus theme entries) for every panel and applies a chosen entry's value to\nthe field; **Pin** — offered while the field holds a value the restored set\nlacks — adds that value to the set for the rest of the session.\n\n```javascript\nawait elem.restoreWorkspace({\n palette: {\n \"--psp-user--gradient-heat\":\n \"linear-gradient(to right, #0366d6, #ff7f0e)\",\n \"--psp-user--palette-brand\":\n \"linear-gradient(to right, #2771a8, #8b86ff, #ff471e)\",\n },\n panels: {\n sales: {\n table: \"superstore\",\n plugin: \"Heatmap\",\n columns: [\"Sales\"],\n columns_config: {\n Sales: { gradient: \"var(--psp-user--gradient-heat)\" },\n },\n },\n },\n});\n```\n\nA malformed `palette` entry (a key outside\n`--psp-user--{gradient,palette,color}-`, or a value its kind rejects) fails the\nwhole `restoreWorkspace()` before any panel changes."},{"title":"layout » Arranging panels: layouts and dashboards","path":"docs/md/how_to/javascript/layout.md","text":"A multi-panel `<perspective-viewer>` is a dashboard. Its panels are arranged by\na _layout_: a tree that places each panel in a grid of rows and columns, or\nstacks several panels as tabs in one cell. The layout is the `layout` field of a\n[`saveWorkspace()`](./save_restore.md#multi-panel-viewers) token, and\n`restoreWorkspace()` applies one.\n\nlayout » Arranging panels: layouts and dashboards » The layout tree\n\nA layout is built from two kinds of node:\n\n- `split-layout` divides its space among its `children`. The `orientation`\n decides the direction: `\"horizontal\"` places the children side by side as\n columns, left to right, and `\"vertical\"` stacks them as rows, top to bottom.\n `sizes` gives each child's fraction of the split, one number per child, and\n the sizes must sum to 1 (`[0.5, 0.5]` is an even split, `[0.25, 0.75]` a\n narrow column next to a wide one).\n- `tab-layout` is a leaf cell holding a stack of panels, listed by panel id in\n `tabs`. Only one panel of a stack is visible at a time; `selected` is the\n index of the visible tab (default `0`). A cell with one panel is a\n `tab-layout` with a single entry in `tabs`.\n\nNesting splits makes a grid: a `vertical` split whose first child is a\n`horizontal` split puts two panels side by side in the top row and one panel\nacross the bottom row. A split nested directly inside a split of the same\norientation is flattened into its parent, so `saveWorkspace()` may return an\nequivalent but flatter tree than the one you restored.\n\nEvery panel must appear in the layout exactly once.\n\n```javascript\nconst layout = {\n type: \"split-layout\",\n orientation: \"vertical\",\n sizes: [0.55, 0.45],\n children: [\n {\n type: \"split-layout\",\n orientation: \"horizontal\",\n sizes: [0.5, 0.5],\n children: [\n { type: \"tab-layout\", tabs: [\"sales\"], selected: 0 },\n { type: \"tab-layout\", tabs: [\"regions\"], selected: 0 },\n ],\n },\n { type: \"tab-layout\", tabs: [\"grid\"], selected: 0 },\n ],\n};\n```"},{"title":"layout » Arranging panels: layouts and dashboards » Building a grid dashboard","path":"docs/md/how_to/javascript/layout.md","text":"A complete dashboard is one `restoreWorkspace()` call: `panels` defines each\npanel's view (keyed by an id of your choosing), and `layout` arranges those ids\ninto a grid. This one puts a bar chart and a treemap side by side above a pivot\ngrid that spans the bottom row. Give each panel a `title`; it is shown in the\npanel's tab header.\n\n```javascript\nawait viewer.restoreWorkspace({\n layout: {\n type: \"split-layout\",\n orientation: \"vertical\",\n sizes: [0.55, 0.45],\n children: [\n {\n type: \"split-layout\",\n orientation: \"horizontal\",\n sizes: [0.5, 0.5],\n children: [\n { type: \"tab-layout\", tabs: [\"sales\"] },\n { type: \"tab-layout\", tabs: [\"regions\"] },\n ],\n },\n { type: \"tab-layout\", tabs: [\"grid\"] },\n ],\n },\n panels: {\n sales: {\n table: \"superstore\",\n title: \"Sales by Sub-Category\",\n plugin: \"Y Bar\",\n group_by: [\"Sub-Category\"],\n split_by: [\"Segment\"],\n sort: [[\"Sales\", \"desc\"]],\n columns: [\"Sales\"],\n },\n regions: {\n table: \"superstore\",\n title: \"Sales by Region\",\n plugin: \"Treemap\",\n group_by: [\"Region\", \"State\"],\n columns: [\"Sales\", \"Profit\"],\n },\n grid: {\n table: \"superstore\",\n title: \"Profit by Region\",\n plugin: \"Datagrid\",\n group_by: [\"Region\", \"State\"],\n split_by: [\"Category\"],\n columns: [\"Sales\", \"Profit\"],\n },\n },\n});\n```\n\nPanels added one at a time with `addPanel()` are each placed as a new column,\nside by side; arrange them afterwards by restoring a `layout`, as below."},{"title":"layout » Arranging panels: layouts and dashboards » Rearranging existing panels","path":"docs/md/how_to/javascript/layout.md","text":"A `restoreWorkspace()` call with a `layout` but no `panels` keeps every panel\nand only moves them. Name the panels by their existing ids, from\n`getPanelNames()` or a `saveWorkspace()` token. This is how to move or swap a\npanel, resize a row or column, change a row into a column, or stack panels as\ntabs; nothing is re-created or re-queried.\n\n```javascript\nconst [a, b, c] = viewer.getPanelNames();\n\n// Three columns, the first twice as wide as the others.\nawait viewer.restoreWorkspace({\n layout: {\n type: \"split-layout\",\n orientation: \"horizontal\",\n sizes: [0.5, 0.25, 0.25],\n children: [\n { type: \"tab-layout\", tabs: [a] },\n { type: \"tab-layout\", tabs: [b] },\n { type: \"tab-layout\", tabs: [c] },\n ],\n },\n});\n\n// Stack all three panels as tabs in one cell, showing the second.\nawait viewer.restoreWorkspace({\n layout: { type: \"tab-layout\", tabs: [a, b, c], selected: 1 },\n});\n```\n\nTo resize panels, change `sizes` and keep the tree; to swap two panels, exchange\ntheir ids in `tabs`."},{"title":"layout » Arranging panels: layouts and dashboards » Layout validation errors","path":"docs/md/how_to/javascript/layout.md","text":"`restoreWorkspace()` rejects a malformed `layout` before changing anything, so\nthe viewer is left exactly as it was. A layout is rejected when:\n\n- a panel id in `tabs` is unknown — without `panels` the ids must be existing\n panel ids, and with `panels` they must be `panels` keys;\n- a panel is missing from the layout, or appears more than once — every panel\n must be placed exactly once;\n- a `split-layout` has no `children`, or its `sizes` do not have one entry per\n child, or a size is zero or negative, or the sizes do not sum to 1;\n- a `tab-layout` has empty `tabs`, or its `selected` index is past the end of\n `tabs`.\n\nThe error message names the offending node by its path, e.g.\n`layout.children[1].tabs`, and lists the valid panel ids."},{"title":"layout » Arranging panels: layouts and dashboards » Master/detail cross-filtering","path":"docs/md/how_to/javascript/layout.md","text":"Panels can be linked so that clicking a row in one filters the others. A\n_master_ panel is a filter source: selecting a row in it (for example a group in\na pivoted grid) writes a filter clause for that row's group-by values into the\nelement's `global_filters`. Every other, _detail_, panel applies the\n`global_filters` as a transient overlay on its own `filter`, which is never\nwritten into the detail panel's saved config. A master panel is not filtered by\n`global_filters` itself.\n\nMasters are listed by panel id in a workspace token's `masters` field, and the\ncross-filter set is its `global_filters` field. Both can be set without\nre-creating the panels:\n\n```javascript\nconst [master, detail] = viewer.getPanelNames();\n\n// Make the first panel a master; selecting a row filters the detail panel.\nawait viewer.restoreWorkspace({ masters: [master] });\n\n// Apply a cross-filter directly, or clear it.\nawait viewer.restoreWorkspace({ global_filters: [[\"Region\", \"==\", \"West\"]] });\nawait viewer.restoreWorkspace({ global_filters: null });\n\n// Demote every master.\nawait viewer.restoreWorkspace({ masters: null });\n```\n\nThe `perspective-global-filter-update` event fires whenever the cross-filter set\nchanges; see [Listening for events](./events.md#global-filter-events)."},{"title":"layout » Arranging panels: layouts and dashboards » Panel titles","path":"docs/md/how_to/javascript/layout.md","text":"Each panel's tab header shows its `title`, a field of the panel's own config.\nSet it with the panel's other fields in `restoreWorkspace()` or `addPanel()`, or\nchange it later with `restore()`:\n\n```javascript\nawait viewer.restore({ title: \"Sales by Region\" }, { panel: \"regions\" });\n```"},{"title":"events » Listening for events","path":"docs/md/how_to/javascript/events.md","text":"The `<perspective-viewer>` Custom Element fires all the same HTML `Event`s that\nstandard DOM `HTMLElement` objects fire, in addition to a few custom\n`CustomEvent`s which relate to UI updates including those initiaed through user\ninteraction.\n\nevents » Listening for events » Update events\n\nWhenever a `<perspective-viewer>`s underlying `table()` is changed via the\n`load()` or `update()` methods, a `perspective-view-update` DOM event is fired.\nSimilarly, `view()` updates instigated either through the Attribute API or\nthrough user interaction will fire a `perspective-config-update` event:\n\n```javascript\nelem.addEventListener(\"perspective-config-update\", function (event) {\n var config = elem.save();\n console.log(\"The view() config has changed to \" + JSON.stringify(config));\n});\n```"},{"title":"events » Listening for events » Click events","path":"docs/md/how_to/javascript/events.md","text":"Whenever a `<perspective-viewer>`'s grid or chart is clicked, a\n`perspective-click` DOM event is fired containing a detail object with\n`config`, `column_names`, `row` and `panel`.\n\nThe `config` object contains an array of `filters` that can be applied to a\n`<perspective-viewer>` through the use of `restore()` updating it to show the\nfiltered subset of data.\n\nThe `column_names` property contains an array of matching columns, the `row`\nproperty returns the associated row data, and `panel` identifies the panel\nwhich fired the event in a multi-panel viewer.\n\n```javascript\nelem.addEventListener(\"perspective-click\", function (event) {\n const { config, panel } = event.detail;\n elem.restore(config, { panel });\n});\n```"},{"title":"events » Listening for events » Selection events","path":"docs/md/how_to/javascript/events.md","text":"`perspective-select` fires when a plugin's selection changes. Its detail is a\n`PerspectiveSelectDetail`, exported from `@perspective-dev/viewer`:\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `selected` | `boolean` | Whether anything is currently selected |\n| `row` | `object` | The associated row data |\n| `column_names` | `string[]` | Matching column names |\n| `removeConfigs` | `ViewConfigUpdate[]` | Configs whose filters should be _removed_ |\n| `insertConfigs` | `ViewConfigUpdate[]` | Configs whose filters should be _applied_ |\n| `panel` | `string?` | The originating panel, in a multi-panel viewer |\n\n`removeConfigs` is applied first, then `insertConfigs`. The\n`removeFilters` and `insertFilters` getters flatten each to a plain `Filter[]`.\n\n```javascript\nimport { PerspectiveSelectDetail } from \"@perspective-dev/viewer\";\n\nelem.addEventListener(\"perspective-select\", function (event) {\n const { insertFilters, removeFilters } = event.detail;\n console.log(\"apply\", insertFilters, \"clear\", removeFilters);\n});\n```\n\n<div class=\"warning\">The <code>detail.config</code> field on\n<code>perspective-select</code> was replaced by <code>insertConfigs</code> and\n<code>removeConfigs</code>. Without an explicit <code>removeConfigs</code>,\na filter on a column outside the source's <code>group_by</code>,\n<code>split_by</code> or <code>filter</code> cannot be cleared.</div>"},{"title":"events » Listening for events » Global filter events","path":"docs/md/how_to/javascript/events.md","text":"In a multi-panel viewer, panels toggled to _Master_ contribute filter clauses\nto an element-level global filter set, which is applied as a transient overlay\nto every _detail_ panel (and never written into their saved configs).\n\n- `perspective-global-filter` fires on a master panel's selection.\n- `perspective-global-filter-update` fires whenever the global filter set\n changes, with a `Filter[]` detail.\n\n```javascript\nelem.addEventListener(\"perspective-global-filter-update\", function (event) {\n console.log(\"Global filters are now\", event.detail);\n});\n```"},{"title":"events » Listening for events » Layout events","path":"docs/md/how_to/javascript/events.md","text":"A multi-panel `<perspective-viewer>` reports changes to its panel _collection_\non two separate channels. They are distinct facts — which panels exist, and\nwhich one is selected — so neither event implies the other.\n\n- `perspective-layout-update` fires when a panel is added to or removed from\n the layout. Its `detail.panels` is the placed panel ids in insertion order,\n identical to what [`getPanelNames()`](#) returns.\n- `perspective-active-panel-update` fires when the active panel changes, with\n a `detail.panel` of the new panel's id — or `null` at zero panels.\n\n```javascript\nelem.addEventListener(\"perspective-layout-update\", function (event) {\n console.log(\"Panels are now\", event.detail.panels);\n});\n```\n\nGeometry changes — dragging a split divider, reordering tabs — do **not** fire\nthese events, because they change the layout tree without changing the panel\nset. Use `saveWorkspace()` to read the current geometry.\n\n<div class=\"warning\">The <code>workspace-layout-update</code> and\n<code>workspace-new-view</code> events from the removed\n<code>@perspective-dev/workspace</code> package no longer exist.\n<code>perspective-layout-update</code> is the closest replacement for the\nformer; for per-panel config changes use\n<code>perspective-config-update</code>.</div>"},{"title":"plugin_settings » Plugin render limits","path":"docs/md/how_to/javascript/plugin_settings.md","text":"`<perspective-viewer>` plugins (especially charts) may in some cases generate\nextremely large output which may lock up the browser. In order to prevent\naccidents (which generally require a browser refresh to fix), each plugin has a\n`max_cells` and `max_columns` heuristic which requires the user to opt-in to\nfully rendering `View`s which exceed these limits. To override this behavior,\nset these values for each plugin type individually, _before_ the plugin itself\nis rendered (e.g. calling `HTMLPerspectiveViewerElement::restore` with the\nrespective `plugin` name).\n\nIf you have a `<perspective-viewer>` instance, you can configure plugins via\n`HTMLPerspectiveViewerElement::getPlugin` and\n`HTMLPerspectiveViewerElement::getAllPlugins`:\n\n```javascript\nconst viewer = document.querySelector(\"perspective-viewer\");\nconst plugin = viewer.getPlugin(\"Treemap\");\nplugin.max_cells = 1_000_000;\nplugin.max_columns = 1000;\n```\n\n... Or alternatively, you can look up the Custom Element classes and set the\nstatic variants if you know the element name (you can e.g. look this up in your\nbrowser's DOM inspector):\n\n```javascript\nconst plugin = customElements.get(\"perspective-viewer-charts-treemap\");\nplugin.max_cells = 1_000_000;\nplugin.max_columns = 1000;\n```"},{"title":"map_tile_sources » Map tile sources","path":"docs/md/how_to/javascript/map_tile_sources.md","text":"The map plugins (`Map Scatter`, `Map Line`, `Map Density`) draw their glyphs\nover a raster XYZ basemap. Which basemap is used is controlled by the\n`map_tile_provider` `plugin_config` field, an enum of _tile sources_ — each a\nsmall metadata record describing where to fetch tiles and how to attribute them.\nTwo providers ship with `@perspective-dev/viewer-charts`:\n\n- `osm` OpenStreetMap's standard raster tiles (the default)\n- `versatiles-satellite` Global satellite imagery from\n [VersaTiles](https://versatiles.org)"},{"title":"map_tile_sources » Map tile sources » Registering a custom tile source","path":"docs/md/how_to/javascript/map_tile_sources.md","text":"Any raster XYZ provider can be added at runtime with `registerTileSource`,\nexported from the `@perspective-dev/viewer-charts` module. Registered sources\nappear in the settings panel's \"Map provider\" control alongside the bundled ones\nand are available to every current and future map chart. For example, the\n[CARTO](https://carto.com) basemaps:\n\n```javascript\nimport { registerTileSource } from \"@perspective-dev/viewer-charts\";\n\nfor (const [id, label, path] of [\n [\"carto-positron\", \"Light (Positron)\", \"light_all\"],\n [\"carto-dark-matter\", \"Dark Matter\", \"dark_all\"],\n [\"carto-voyager\", \"Voyager\", \"rastertiles/voyager\"],\n]) {\n registerTileSource({\n id,\n label,\n template: `https://{s}.basemaps.cartocdn.com/${path}/{z}/{x}/{y}.png?api_key=CARTO_API_KEY`,\n subdomains: [\"a\", \"b\", \"c\", \"d\"],\n attribution: \"© OpenStreetMap contributors © CARTO\",\n tile_size: 256,\n max_zoom: 19,\n });\n}\n```\n\nIf you consume the plugin as a registered Custom Element rather than an ES\nmodule, the same function is available as a static on the plugin element class:\n\n```javascript\ncustomElements\n .get(\"perspective-viewer-charts-map-scatter\")\n .registerTileSource({ ... });\n```\n\n`tileSources()` (also exported, and available as a static) returns the current\nlist of registered specs."},{"title":"agent » Configuring the LLM agent","path":"docs/md/how_to/javascript/agent.md","text":"`<perspective-viewer>` ships with an embedded LLM agent which drives the viewer\nthrough its public API — reading the schema, writing the `ViewerConfig`,\nchoosing a plugin, authoring ExprTK expressions and managing panels. It is\n**opt-in**: the **Chat** tab in the settings sidebar stays hidden, and no\nnetwork request is ever made, until you call\n`HTMLPerspectiveViewerElement::agentConfig`.\n\n```javascript\nimport { providers } from \"@perspective-dev/viewer\";\n\nconst viewer = document.querySelector(\"perspective-viewer\");\nviewer.agentConfig({\n ...providers.anthropic,\n apiKey: \"sk-ant-...\",\n});\n```"},{"title":"agent » Configuring the LLM agent » Connecting to a model","path":"docs/md/how_to/javascript/agent.md","text":"The agent core connects via OpenAI chat-completions conventional API, over\nprimitive connection fields. Exactly one of `url` or `engine` is required:\n\n- `url` — a full chat-completions endpoint. Any OpenAI-compatible service works:\n the Anthropic and Gemini compatibility endpoints, OpenRouter, LM Studio,\n Ollama, llama.cpp, vLLM, or your own proxy.\n- `engine` — an in-page engine object exposing\n `chat.completions.create(request)`, e.g.\n [WebLLM](https://github.com/mlc-ai/web-llm)'s `MLCEngine`. Mutually exclusive\n with `url`.\n\nThe remaining connection fields are `headers`, `apiKey` (sugar for an\n`Authorization: Bearer` header), `model`, and `name`. The `providers` export\nsupplies presets for the common ones — `anthropic`, `gemini`, `openai`,\n`openrouter`, `lmstudio` and `ollama` — and spread order is override order:\n\n```javascript\nviewer.agentConfig({\n ...providers.anthropic,\n apiKey: \"sk-ant-...\",\n model: \"claude-haiku-4-5\", // overrides the preset's default\n});\n```\n\nLocal servers usually need their CORS opt-in enabled first:\n[LM Studio](https://lmstudio.ai/) has a setting in its developer server panel,\nand Ollama reads `OLLAMA_ORIGINS`.\n\n> **A key in `agentConfig` is a key in the browser tab.** It is sent directly to\n> the provider from the page, which is fine for local development and internal\n> tools, but for anything shared you should point `url` at a proxy you control\n> and keep the credential on the server.\n\nTool-calling quality varies more than general chat quality does. Frontier models\nhandle the viewer's tool surface reliably; among local models, recent Qwen and\nLlama instruct builds are the ones to try first."},{"title":"agent » Configuring the LLM agent » Connecting to a model » In-page engines","path":"docs/md/how_to/javascript/agent.md","text":"An `engine` runs the model in the tab, so no prompt and no data leave the\nmachine and no key is involved. [WebLLM ](https://github.com/mlc-ai/web-llm) for\nexample:\n\n```javascript\nimport * as webllm from \"@mlc-ai/web-llm\";\n\nconst engine = await webllm.CreateMLCEngine(\n \"Hermes-3-Llama-3.1-8B-q4f16_1-MLC\",\n { initProgressCallback: (x) => console.log(x.text) },\n { context_window_size: 16384 },\n);\n\nviewer.agentConfig({\n name: \"webllm\",\n engine,\n systemRole: \"user\",\n});\n```\n\nagent » Configuring the LLM agent » What the agent can do\n\nThe agent acts only through tools, each gated by an _entitlement_. Pass\n`entitlements` to `agentConfig` to grant a subset; the default grants every\nentitlement except `read_data`. A tool whose entitlement is withheld is never\noffered to the model.\n\n| Entitlement | Tools |\n| ---------------- | ------------------------------------------------------------------------------------------------------------------------- |\n| `read_view` | `get_schema`, `get_view_config`, `list_plugins`, `validate_expression`, `get_style_schema`, `list_panels`, `get_workspace` |\n| `configure_view` | `set_view_config` |\n| `manage_layout` | `add_panel`, `remove_panel`, `activate_panel`, `set_workspace` |\n| `read_docs` | `search_docs` |\n\n`manage_layout` covers the whole dashboard: adding and removing panels, and\nthrough `set_workspace` arranging them into a grid, defining derived tables,\nlinking master/detail panels and setting the shared palette. Withhold it to\nkeep the agent to configuring the existing panels:\n\n```javascript\nviewer.agentConfig({\n ...providers.anthropic,\n apiKey: \"sk-ant-...\",\n entitlements: [\"read_view\", \"configure_view\", \"read_docs\"],\n});\n```"},{"title":"agent » Configuring the LLM agent » The documentation bundle","path":"docs/md/how_to/javascript/agent.md","text":"`<perspective-viewer>` publishes a metadata bundle at\n`dist/docs/perspective-docs.json` containing a searchable corpus of the\nPerspective documentation on configuring the viewer, plus generated JSON\nschemas for the viewer's config types. Passing it as `docs` is optional, but without it the agent will not be\nvery capable — it is what lets the agent look things up rather than guess:\n\n```javascript\nimport docs from \"@perspective-dev/viewer/dist/docs/perspective-docs.json\" with { type: \"json\" };\n\nviewer.agentConfig({ ...providers.anthropic, apiKey: \"sk-ant-...\", docs });\n\n// ... or ...\n\nviewer.agentConfig({\n ...providers.anthropic,\n apiKey: \"sk-ant-...\",\n docs: fetch(\n \"node_modules/@perspective-dev/viewer/dist/docs/perspective-docs.json\",\n ),\n});\n```\n\nWithout it the agent still works: `search_docs` searches an empty corpus and the\ntool parameter schemas degrade to permissive objects. The practical difference\nis how often a weaker model invents a field name that doesn't exist, or writes\nan ExprTK expression against syntax Perspective doesn't have."},{"title":"agent » Configuring the LLM agent » The documentation bundle » Telling the agent about your data","path":"docs/md/how_to/javascript/agent.md","text":"The agent learns column names and types from `get_schema`, but not what they\n_mean_ — that `Discount` is a ratio rather than a percent, or that a negative\n`Profit` is a return rather than an error. Add those notes as extra corpus\nentries:\n\n```javascript\nimport bundle from \"@perspective-dev/viewer/dist/docs/perspective-docs.json\" with { type: \"json\" };\n\nconst DATASET_DOCS = [\n {\n title: \"Superstore columns\",\n text: \"`Discount` is a ratio in [0, 1], not a percent. `Profit` is net of `Discount` and is negative for returns.\",\n },\n];\n\nviewer.agentConfig({\n ...providers.anthropic,\n apiKey: \"sk-ant-...\",\n docs: { ...bundle, chunks: [...bundle.chunks, ...DATASET_DOCS] },\n});\n```\n\nAn inline `[{title?, text}]` array may also be passed as `docs` on its own, when\nyou have host notes but no packaged bundle."},{"title":"glossary » Glossary","path":"docs/md/glossary.md","text":"<!-- description: Definitions of the terms used by Perspective and by data grid, pivot table and streaming analytics tools generally: Table, View, group by, split by, virtual server, replicated and server-only modes, virtual scrolling, Apache Arrow, WebAssembly and Memory64. -->\n\nShort definitions of the terms used throughout this guide.\n\nglossary » Glossary » Data grid\n\nA scrollable, sortable table of rows and columns rendered in a user interface.\nPerspective's data grid, the\n[Datagrid plugin](https://www.npmjs.com/package/@perspective-dev/viewer-datagrid),\nis _virtualized_: it renders only the visible cells, so its cost is independent\nof the number of rows."},{"title":"glossary » Glossary » Pivot table","path":"docs/md/glossary.md","text":"A table which groups rows by one or more columns (row pivots), optionally\nsplits them across the distinct values of other columns (column pivots), and\nshows an aggregate in each cell. In Perspective, row pivots are `group_by` and\ncolumn pivots are `split_by`. See\n[Grouping and Pivots](./explanation/view/config/grouping_and_pivots.md).\n\nglossary » Glossary » Streaming pivot table\n\nA pivot table whose groups and aggregates are updated incrementally as rows\nare inserted, updated or removed, rather than recomputed. See\n[Streaming pivot tables](./use_cases/streaming_pivot_table.md)."},{"title":"glossary » Glossary » Table","path":"docs/md/glossary.md","text":"Perspective's columnar, typed data store. A [`Table`](./explanation/table.md)\nis created from a schema or a dataset (Apache Arrow, CSV, JSON, or a DataFrame\nin Python), and modified with `update()`, `remove()`, `clear()` and\n`replace()`.\n\nglossary » Glossary » View\n\nA continuous query over a `Table`: a combination of `group_by`, `split_by`,\n`columns`, `aggregates`, `filter`, `sort` and `expressions`. A\n[`View`](./explanation/view.md) stays current as its `Table` changes and\nnotifies `on_update` subscribers."},{"title":"glossary » Glossary » group_by","path":"docs/md/glossary.md","text":"The columns whose distinct values become the rows of a pivot. Multiple levels\nform an expandable tree with subtotals.\n\nglossary » Glossary » split_by\n\nThe columns whose distinct values become the column headers of a pivot.\n\nglossary » Glossary » Aggregate\n\nThe function which reduces a group's values to one cell — `sum`, `avg`,\n`count`, `distinct count`, `median`, `weighted mean`, `first`, `last` and\nothers. Chosen per column.\n\nglossary » Glossary » Expression column\n\nA computed column defined in Perspective's\n[expression language](./explanation/view/config/expressions.md), based on\nExprTK. Expressions are evaluated column-wise inside the engine and can be\ngrouped, filtered, sorted and aggregated like stored columns."},{"title":"glossary » Glossary » Index","path":"docs/md/glossary.md","text":"A `Table` option naming a primary key column. Updates to an indexed table\nreplace the row with the matching key (and may be partial); without an index,\nupdates append. See [`index` and `limit`](./explanation/table/options.md).\n\nglossary » Glossary » Limit\n\nA `Table` option which keeps only the most recent _n_ rows, for rolling\nwindows over unbounded streams.\n\nglossary » Glossary » page_to_disk\n\nA `Table` option which backs the table's columns with on-disk storage — the\nOrigin Private File System in the browser, memory-mapped files natively — so\na `Table` can exceed the engine's memory. See\n[`page_to_disk`](./explanation/table/options.md#page_to_disk)."},{"title":"glossary » Glossary » <perspective-viewer>","path":"docs/md/glossary.md","text":"The Web Component (Custom Element) providing Perspective's user interface:\nconfiguration panel, plugins, themes, multi-panel layout and save/restore.\n\nglossary » Glossary » Plugin\n\nA visualization hosted by `<perspective-viewer>` — the Datagrid, or one of\nthe WebGL charts (bar, line, area, scatter, heatmap, treemap, sunburst,\ncandlestick, OHLC, maps).\n\nglossary » Glossary » Panel\n\nOne view inside a `<perspective-viewer>`: a plugin showing one `View` of one\n`Table`, with its own configuration and `title`. A viewer holds one or more\npanels, arranged by its layout."},{"title":"glossary » Glossary » Workspace","path":"docs/md/glossary.md","text":"The whole state of a multi-panel `<perspective-viewer>` — every panel, the\nlayout, derived tables, master/detail roles and the palette — as saved by\n`saveWorkspace()` and applied by `restoreWorkspace()`. See\n[Saving and restoring UI state](./how_to/javascript/save_restore.md#multi-panel-viewers).\n\nglossary » Glossary » Layout\n\nThe arrangement of a viewer's panels into a dashboard grid: nested\n`split-layout` rows and columns whose cells are `tab-layout` stacks of panels.\nSee [Arranging panels](./how_to/javascript/layout.md)."},{"title":"glossary » Glossary » Master/detail","path":"docs/md/glossary.md","text":"Linked panels: selecting a row in a _master_ panel cross-filters every\n_detail_ panel through the viewer's `global_filters`. See\n[Master/detail cross-filtering](./how_to/javascript/layout.md#masterdetail-cross-filtering).\n\nglossary » Glossary » Derived table\n\nA `Table` built from a `View` of another table — a pivoted, filtered or\naggregated copy that panels can bind by name and that stays current as its\nsource changes. See [Derived `Table`s](./explanation/derived_table.md).\n\nglossary » Glossary » Palette\n\nNamed colors, palettes and gradients shared by every panel of a workspace, as\n`--psp-user--*` CSS custom properties referenced from column styles. See\n[Colors, palettes and gradients](./how_to/javascript/save_restore.md#colors-palettes-and-gradients)."},{"title":"glossary » Glossary » Client, Server","path":"docs/md/glossary.md","text":"A `Server` owns tables and executes queries. A `Client` is a handle to a\n`Server`, whether that server is in the same process, in a Web Worker, or\nacross a WebSocket. The API is the same in every case.\n\nglossary » Glossary » Client-only mode\n\nThe engine runs in the browser as WebAssembly in a Web Worker; no server is\ninvolved. See [Client-only](./explanation/architecture/client_only.md).\n\nglossary » Glossary » Client/server replicated mode\n\nA server owns the authoritative `Table`; each browser keeps a synchronized\ncopy and queries it locally. See\n[Client/Server replicated](./explanation/architecture/client_server.md)."},{"title":"glossary » Glossary » Server-only mode","path":"docs/md/glossary.md","text":"Queries run on the server and the browser receives only the rows it is\ndisplaying. See [Server only](./explanation/architecture/server_only.md).\n\nglossary » Glossary » Virtual server\n\nAn implementation of Perspective's protocol over an external query engine,\nsuch as DuckDB, ClickHouse, PostgreSQL or Polars, which translates view\nconfigurations into that engine's native queries. See\n[Virtual Servers](./explanation/virtual_servers.md).\n\nglossary » Glossary » Virtual scrolling\n\nRendering only the rows and columns currently in the viewport, and fetching\nmore as the user scrolls."},{"title":"glossary » Glossary » Apache Arrow","path":"docs/md/glossary.md","text":"A language-independent columnar memory format. It is Perspective's preferred\ninterchange format: it loads without parsing and preserves types.\n\nglossary » Glossary » WebAssembly\n\nA portable binary instruction format which runs at near-native speed in\nbrowsers. Perspective's C++ query engine and Rust UI are both compiled to\nWebAssembly.\n\nglossary » Glossary » Memory64\n\nA WebAssembly extension for 64-bit memory addressing. Perspective ships an\noptional Memory64 engine build which raises the in-browser heap limit from\n4GB to 16GB."},{"title":"expression_gen (1)","path":"rust/perspective-client/docs/expression_gen.md","text":"<br/>\n\nexpression_gen » Perspective ExprTK Extensions"},{"title":"expression_gen (2)","path":"rust/perspective-client/docs/expression_gen.md","text":"- `var ${1:x := 1}` Declare a new local variable\n- `abs(${1:x})` Absolute value of x\n- `avg(${1:x})` Average of all inputs\n- `bucket(${1:x}, ${2:y})` Bucket x by y\n- `ceil(${1:x})` Smallest integer >= x\n- `exp(${1:x})` Natural exponent of x (e ^ x)\n- `floor(${1:x})` Largest integer <= x\n- `frac(${1:x})` Fractional portion (after the decimal) of x\n- `iclamp(${1:x})` Inverse clamp x within a range\n- `inrange(${1:x})` Returns whether x is within a range\n- `log(${1:x})` Natural log of x\n- `log10(${1:x})` Base 10 log of x\n- `log1p(${1:x})` Natural log of 1 + x where x is very small\n- `log2(${1:x})` Base 2 log of x\n- `logn(${1:x}, ${2:N})` Base N log of x where N >= 0\n- `max(${1:x})` Maximum value of all inputs\n- `min(${1:x})` Minimum value of all inputs\n- `mul(${1:x})` Product of all inputs\n- `percent_of(${1:x})` Percent y of x\n- `pow(${1:x}, ${2:y})` x to the power of y\n- `root(${1:x}, ${2:N})` N-th root of x where N >= 0\n- `round(${1:x})` Round x to the nearest integer\n- `sgn(${1:x})` Sign of x: -1, 1, or 0\n- `sqrt(${1:x})` Square root of x\n- `sum(${1:x})` Sum of all inputs\n- `trunc(${1:x})` Integer portion of x\n- `acos(${1:x})` Arc cosine of x in radians\n- `acosh(${1:x})` Inverse hyperbolic cosine of x in radians\n- `asin(${1:x})` Arc sine of x in radians\n- `asinh(${1:x})` Inverse hyperbolic sine of x in radians\n- `atan(${1:x})` Arc tangent of x in radians\n- `atanh(${1:x})` Inverse hyperbolic tangent of x in radians\n- `cos(${1:x})` Cosine of x\n- `cosh(${1:x})` Hyperbolic cosine of x\n- `cot(${1:x})` Cotangent of x\n- `sin(${1:x})` Sine of x\n- `sinc(${1:x})` Sine cardinal of x\n- `sinh(${1:x})` Hyperbolic sine of x\n- `tan(${1:x})` Tangent of x\n- `tanh(${1:x})` Hyperbolic tangent of x\n- `deg2rad(${1:x})` Convert x from degrees to radians\n- `deg2grad(${1:x})` Convert x from degrees to gradians\n- `rad2deg(${1:x})` Convert x from radians to degrees\n- `grad2deg(${1:x})` Convert x from gradians to degrees\n- `concat(${1:x}, ${2:y})` Concatenate string columns and string literals, such as: concat(\"State\" ', ', \"City\")\n- `order(${1:input column}, ${2:value}, ...)` Generates a sort order for a string column based on the input order of the parameters, such as: order(\"State\", 'Texas', 'New York')\n- `upper(${1:x})` Uppercase of x\n- `lower(${1:x})` Lowercase of x\n- `hour_of_day(${1:x})` Return a datetime's hour of the day as a string\n- `month_of_year(${1:x})` Return a datetime's month of the year as a string\n- `day_of_week(${1:x})` Return a datetime's day of week as a string\n- `now()` The current datetime in local time\n- `today()` The current date in local time\n- `is_null(${1:x})` Whether x is a null value\n- `is_not_null(${1:x})` Whether x is not a null value\n- `coalesce(${1:x}, ${2:y})` Returns the first non-null argument.\n- `contains(${1:x}, ${2:'substr'})` Whether the string column or value contains the literal substring.\n- `not(${1:x})` not x\n- `true` Boolean value true\n- `false` Boolean value false\n- `if (${1:condition}) {} else if (${2:condition}) {} else {}` An if/else conditional, which evaluates a condition such as: if (\"Sales\" > 100) { true } else { false }\n- `for (${1:expression}) {}` A for loop, which repeatedly evaluates an incrementing expression such as: var x := 0; var y := 1; for (x < 10; x += 1) { y := x + y }\n- `string(${1:x})` Converts the given argument to a string\n- `integer(${1:x})` Converts the given argument to a 32-bit integer. If the result over/under-flows, null is returned\n- `float(${1:x})` Converts the argument to a float\n- `date(${1:year}, ${1:month}, ${1:day})` Given a year, month (1-12) and day, create a new date\n- `datetime(${1:timestamp})` Given a POSIX timestamp of milliseconds since epoch, create a new datetime\n- `boolean(${1:x})` Converts the given argument to a boolean\n- `random()` Returns a random float between 0 and 1, inclusive.\n- `match(${1:string}, ${2:pattern})` Returns True if any part of string matches pattern, and False otherwise.\n- `match_all(${1:string}, ${2:pattern})` Returns True if the whole string matches pattern, and False otherwise.\n- `search(${1:string}, ${2:pattern})` Returns the substring that matches the first capturing group in pattern, or null if there are no capturing groups in the pattern or if there are no matches.\n- `indexof(${1:string}, ${2:pattern}, ${3:output_vector})` Writes into index 0 and 1 of output_vector the start and end indices of the substring that matches the first capturing group in pattern. Returns true if there is a match and output was written, or false if there are no capturing groups in the pattern, if there are no matches, or if the indices are invalid.\n- `substring(${1:string}, ${2:start_idx}, ${3:length})` Returns a substring of string from start_idx with the given length. If length is not passed in, returns substring from start_idx to the end of the string. Returns null if the string or any indices are invalid.\n- `replace(${1:string}, ${2:pattern}, ${3:replacer})` Replaces the first match of pattern in string with replacer, or return the original string if no replaces were made.\n- `replace_all(${1:string}, ${2:pattern}, ${3:replacer})` Replaces all non-overlapping matches of pattern in string with replacer, or return the original string if no replaces were made.\n- `index()` Looks up the index value of the current row\n- `col(${1:string})` Looks up a column value by name\n- `vlookup(${1:string}, ${2:uint64})` Looks up a value in another column by index"},{"title":"JS API » PerspectiveViewerElement","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"export class PerspectiveViewerElement\n\nThe `<perspective-viewer>` custom element.\n\n# JavaScript Examples\n\nCreate a new `<perspective-viewer>`:\n\n```javascript\nconst viewer = document.createElement(\"perspective-viewer\");\nwindow.body.appendChild(viewer);\n```\n\nComplete example including loading and restoring the [`Table`]:\n\n```javascript\nimport perspective from \"@perspective-dev/viewer\";\nimport perspective from \"@perspective-dev/client\";\n\nconst viewer = document.createElement(\"perspective-viewer\");\nconst worker = await perspective.worker();\n\nawait worker.table(\"x\\n1\", {name: \"table_one\"});\nawait viewer.load(worker);\nawait viewer.restore({table: \"table_one\"});\n```"},{"title":"JS API » __get_model","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"__get_model(): PerspectiveViewerElement\n\nCreate a new JavaScript Heap reference for this model instance.\n\nJS API » addPanel\n\naddPanel(config: ViewerConfigInitial, options?: AddPanelOptions | null): Promise<any>\n\nAdd a new, independent panel to this viewer's layout, rendering the\nsupplied [`ViewerConfigInitial`] into it. Unlike [`Self::restore`]'s\nupdate-shaped argument, a new panel has no prior state, so `table`\nis REQUIRED — a table-less call rejects before the layout is\ntouched. The panel uses the default [`perspective_client::Client`]\n(the first passed to [`Self::load`]) to resolve its `table`. Returns\nthe generated panel id.\n\nThe element-level `settings` field does not exist on the argument\ntype (it is shared across the element, not per-panel).\n\n# Arguments\n\n- `config` - The new panel's initial config, whose `table` is required.\n- `options.wait_for_table` - when `true`, a `table` no loaded client\n hosts yet creates the panel empty and pending until the table is\n created, instead of the default rejection."},{"title":"JS API » agentConfig (1)","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"agentConfig(config: any): void\n\nConfigure the embedded LLM agent (see `prompt()`), replacing any prior\nconfiguration and conversation.\n\nThe agent core is provider-agnostic: one OpenAI-chat-completions\nprotocol over primitive connection fields. Exactly one of\n`config.url` or `config.engine` is required; the `providers` presets\nexported by this package are plain spreadable collections of these\nfields (`{...providers.anthropic, apiKey}`)."},{"title":"JS API » agentConfig (2)","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"- `config.url` - a full chat-completions endpoint URL (any\n OpenAI-compatible service: Anthropic/Gemini compatibility endpoints,\n LM Studio, Ollama, OpenRouter, a proxy...).\n- `config.engine` - an in-page engine object with an OpenAI-compatible\n `chat.completions.create(request)` method (e.g. WebLLM's `MLCEngine`);\n mutually exclusive with `url`.\n- `config.headers` - extra request headers, sent verbatim.\n- `config.apiKey` - sugar for the `Authorization: Bearer` header.\n- `config.model` - model id sent with each request; local servers and\n engines generally answer with whatever model is loaded.\n- `config.name` - cosmetic label for the chat badge (presets set this).\n- `config.systemPrompt` - extra system-prompt context appended to the\n agent's built-in instructions.\n- `config.maxTurns` - max model turns (tool-call rounds + the final\n answer) per `prompt()` call. Defaults to 16.\n- `config.docs` - the agent metadata bundle, which supplies the\n `search_docs` corpus and the rich tool parameter schemas: the packaged\n `dist/docs/perspective-docs.json` asset as a parsed object (`import\n docs from \"…json\" with { type: \"json\" }`), a `fetch()` `Response`, an\n `ArrayBuffer`, a JSON string, or a `Promise` of any of those — and/or\n an inline array of `{title?, text}` entries for host data definitions.\n Omitted, `search_docs` searches an empty corpus and the parameter\n schemas degrade to permissive objects.\n- `config.systemRole` - where the preamble (plus `systemPrompt`) is\n placed: `\"system\"` (default) or `\"user\"`. Some engines refuse a system\n message alongside `tools` because they substitute their own — WebLLM's\n Hermes function calling throws `CustomSystemPromptError` on ANY system\n message — and those need `\"user\"`, which folds the same text into the\n opening user turn.\n- `config.entitlements` - access grants limiting which tools the agent\n is offered (and may call): any of `\"read_view\"`, `\"configure_view\"`,\n `\"manage_layout\"`, `\"read_docs\"`, `\"read_data\"`. Omitted, all but\n `\"read_data\"` are granted; `[\"read_view\", \"read_docs\"]` yields a\n read-only agent.\n\n# JavaScript Examples\n\n```javascript\nimport { providers } from \"@perspective-dev/viewer\";\n\nviewer.agentConfig({\n ...providers.anthropic,\n apiKey: \"sk-ant-...\",\n docs: fetch(\n \"node_modules/@perspective-dev/viewer/dist/docs/perspective-docs.json\",\n ),\n});\n```"},{"title":"JS API » agentPrompt","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"agentPrompt(prompt: string): Promise<any>\n\nRun one conversational turn of the embedded LLM agent (configured via\n`agentConfig()`), resolving with the agent's final text response after\nany tool calls have been applied to this element. Turns share a\nconversation history (and the chat sidebar's transcript) until\n`agentReset()`; a call made while a turn is already running rejects.\nTool activity is emitted as `perspective-agent-tool` CustomEvents on\nthis element.\n\n# JavaScript Examples\n\n```javascript\nawait viewer.agentPrompt(\"Show me sales by region as a bar chart\");\n```"},{"title":"JS API » agentReset","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"agentReset(): Promise<any>\n\nClear the agent's conversation (history and chat transcript), keeping\nits configuration. Cancels any in-flight turn.\n\nJS API » copy\n\ncopy(options?: ExportOptions | null): Promise<any>\n\nCopy this viewer's `View` or `Table` data as CSV to the system\nclipboard.\n\n# Arguments\n\n- `method` - The `ExportMethod` (serialized as a `String`) to use to\n render the data to the Clipboard.\n\n# JavaScript Examples\n\n```javascript\nmyDownloadButton.addEventListener(\"click\", async () => {\n await viewer.copy();\n})\n```"},{"title":"JS API » delete","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"delete(): Promise<any>\n\nDelete all internal [`View`]s and all associated state, rendering this\n`<perspective-viewer>` unusable and freeing all associated resources.\nDoes not delete any supplied [`Table`] (as this is constructed by the\ncallee).\n\nCalling _any_ method on a `<perspective-viewer>` after [`Self::delete`]\nwill throw.\n\n<div class=\"warning\">\n\nAllowing a `<perspective-viewer>` to be garbage-collected\nwithout calling [`PerspectiveViewerElement::delete`] will leak WASM\nmemory!\n\n</div>\n\n# JavaScript Examples\n\n```javascript\nawait viewer.delete();\n```"},{"title":"JS API » download","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"download(options?: ExportOptions | null): Promise<any>\n\nDownload this viewer's internal [`View`] data via a browser download\nevent.\n\n# Arguments\n\n- `method` - The `ExportMethod` to use to render the data to download.\n\n# JavaScript Examples\n\n```javascript\nmyDownloadButton.addEventListener(\"click\", async () => {\n await viewer.download();\n})\n```\n\nJS API » eject\n\neject(options?: ClientOptions | null): Promise<any>\n\nRemove a [`Client`] from this `<perspective-viewer>` and dispose every\npanel bound to it (each panel's `View` is deleted and its `Table`\nreference released).\n\n# Arguments\n\n- `options` - An optional `{client?: string}` dict naming the client to\n eject; the active panel's client when omitted.\n\n# JavaScript Examples\n\n```javascript\nawait viewer.eject();\nawait viewer.eject({client: \"remote\"});\n```"},{"title":"JS API » export","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"export(options?: ExportOptions | null): Promise<any>\n\nExports this viewer's internal [`View`] as a JavaSript data, the\nexact type of which depends on the `method` but defaults to `String`\nin CSV format.\n\nThis method is only really useful for the `\"plugin\"` method, which\nwill use the configured plugin's export (e.g. PNG for\n`@perspective-dev/viewer-charts`). Otherwise, prefer to call the\nequivalent method on the underlying [`View`] directly.\n\n# Arguments\n\n- `method` - The `ExportMethod` to use to render the data to download.\n\n# JavaScript Examples\n\n```javascript\nconst data = await viewer.export(\"plugin\");\n```"},{"title":"JS API » flush","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"flush(): Promise<any>\n\nFlush any pending modifications to this `<perspective-viewer>`. Since\n`<perspective-viewer>`'s API is almost entirely `async`, it may take\nsome milliseconds before any user-initiated changes to the [`View`]\naffects the rendered element. If you want to make sure all pending\nactions have been rendered, call and await [`Self::flush`].\n\n[`Self::flush`] will resolve immediately if there is no [`Table`] set.\n\n# JavaScript Examples\n\nIn this example, [`Self::restore`] is called without `await`, but the\neventual render which results from this call can still be awaited by\nimmediately awaiting [`Self::flush`] instead.\n\n```javascript\nviewer.restore(config);\nawait viewer.flush();\n```"},{"title":"JS API » getActivePanel","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"getActivePanel(): any\n\nThe id of the active panel — the one the settings panel and status-bar\ntoolbar target — or `null` when the element has zero panels.\n\nJS API » getAllPlugins\n\ngetAllPlugins(): Array<any>\n\nGet an `Array` of all of the plugin custom elements registered for this\nelement. This may not include plugins which called\n[`registerPlugin`] after the host has rendered for the first time.\n\nJS API » getClient\n\ngetClient(options?: GetClientOptions | null): Promise<any>\n\nGet the underlying [`Client`] for this viewer (as passed to, or\nassociated with the [`Table`] passed to,\n[`PerspectiveViewerElement::load`]).\n\n# Arguments\n\n- `wait_for_client` - whether to wait for\n [`PerspectiveViewerElement::load`] to be called, or fail immediately\n if [`PerspectiveViewerElement::load`] has not yet been called.\n\n# JavaScript Examples\n\n```javascript\nconst client = await viewer.getClient();\n```"},{"title":"JS API » getEditPort","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"getEditPort(options?: PanelOptions | null): number\n\nGet this viewer's edit port for the named panel's [`Table`] (see\n[`Table::update`] for details on ports), or the active panel when\n`panel` is omitted.\n\nJS API » getPanelNames\n\ngetPanelNames(): Array<any>\n\nGet the ids of all panels in this viewer's layout, in insertion order.\n\nJS API » getPlugin\n\ngetPlugin(name?: string | null): any\n\nGets a plugin Custom Element with the `name` field, or get the active\nplugin if no `name` is provided.\n\n# Arguments\n\n- `name` - The `name` property of a perspective plugin Custom Element,\n or `None` for the active plugin's Custom Element."},{"title":"JS API » getRenderStats","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"getRenderStats(options?: PanelOptions | null): any\n\nGet render statistics. Some fields of the returned stats object are\nrelative to the last time [`PerspectiveViewerElement::getRenderStats`]\nwas called, ergo calling this method resets these fields.\n\n# JavaScript Examples\n\n```javascript\nconst {virtual_fps, actual_fps} = await viewer.getRenderStats();\n```\n\nJS API » getSelection\n\ngetSelection(options?: PanelOptions | null): ViewWindow | undefined\n\nReturn a [`perspective_js::JsViewWindow`] for the currently selected\nregion of the named panel, or the active panel when `panel` is omitted."},{"title":"JS API » getTable","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"getTable(options?: GetTableOptions | null): Promise<any>\n\nGet the underlying [`Table`] for this viewer (as passed to\n[`PerspectiveViewerElement::load`] or as the `table` field to\n[`PerspectiveViewerElement::restore`]).\n\n# Arguments\n\n- `wait_for_table` - whether to wait for\n [`PerspectiveViewerElement::load`] to be called, or fail immediately\n if [`PerspectiveViewerElement::load`] has not yet been called.\n\n# JavaScript Examples\n\n```javascript\nconst table = await viewer.getTable();\n```\n\nJS API » getView\n\ngetView(options?: GetViewOptions | null): Promise<any>\n\nGet a [`View`] for a panel of this viewer, as currently configured by\nthe user.\n\n# Arguments\n\n- `mode` - which [`View`] to return:\n - `\"live\"` (default) - the panel's own [`View`], which the viewer\n replaces on every config change and deletes when auto-paused or\n [`PerspectiveViewerElement::delete`]d, so calls on it may fail with\n \"View already deleted\", it must never be deleted by the caller, and\n it rejects with `No View for panel \"<name>\"` while none is bound\n (auto-paused or not yet rendered).\n - `\"clone\"` - a new [`View`] built from the panel's effective config\n (global filter included), independent of the render lifecycle, which\n the caller owns and must [`View::delete`].\n - `\"auto\"` - `\"live\"` when the panel has a bound [`View`], else\n `\"clone\"`.\n- `panel` - the target panel, or the active panel when omitted.\n\nPlugins which rely on the live [`View`] for their\n[`HTMLPerspectiveViewerPluginElement::draw`] implementations should\ntreat \"View already deleted\" as a _cancellation_ by silently aborting.\n\n# JavaScript Examples\n\n```javascript\nconst view = await viewer.getView();\n```\n\nExport every panel without borrowing the viewer's own [`View`]:\n\n```javascript\nfor (const panel of viewer.getPanelNames()) {\n const view = await viewer.getView({ panel, mode: \"clone\" });\n const arrow = await view.to_arrow();\n await view.delete();\n}\n```"},{"title":"JS API » getViewConfig (1)","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"getViewConfig(options?: PanelOptions | null): Promise<any>\n\nGet a copy of the [`ViewConfig`] for the current [`View`]. This is\nnon-blocking as it does not need to access the plugin (unlike\n[`PerspectiveViewerElement::save`]), and also makes no API calls to the\nserver (unlike [`PerspectiveViewerElement::getView`] followed by\n[`View::get_config`])\n\nJS API » load\n\nload(client: Client | Table | Promise<Client | Table>): Promise<any>\n\nLoads a [`Client`], or optionally [`Table`], or optionally a Javascript\n`Promise` which returns a [`Client`] or [`Table`], in this viewer.\n\nLoading a [`Client`] does not render, but subsequent calls to\n[`PerspectiveViewerElement::restore`] will use this [`Client`] to look\nup the proviced `table` name field for the provided\n[`ViewerConfigUpdate`].\n\nLoading a [`Table`] is equivalent to subsequently calling\n[`Self::restore`] with the `table` field set to [`Table::get_name`], and\nwill render the UI in its default state when [`Self::load`] resolves.\nIf you plan to call [`Self::restore`] anyway, prefer passing a\n[`Client`] argument to [`Self::load`] as it will conserve one render.\n\nWhen [`PerspectiveViewerElement::load`] resolves, the first frame of the\nUI + visualization is guaranteed to have been drawn. Awaiting the result\nof this method in a `try`/`catch` block will capture any errors\nthrown during the loading process, or from the [`Client`] `Promise`\nitself.\n\n[`PerspectiveViewerElement::load`] may also be called with a [`Table`],\nwhich is equivalent to:\n\n```javascript\nawait viewer.load(await table.get_client());\nawait viewer.restore({name: await table.get_name()})\n```\n\nIf you plan to call [`PerspectiveViewerElement::restore`] immediately\nafter [`PerspectiveViewerElement::load`] yourself, as is commonly\ndone when loading and configuring a new `<perspective-viewer>`, you\nshould use a [`Client`] as an argument and set the `table` field in the\nrestore call as\n\nA [`Table`] can be created using the\n[`@perspective-dev/client`](https://www.npmjs.com/package/@perspective-dev/client)\nlibrary from NPM (see [`perspective_js`] documentation for details).\n\n# JavaScript Examples\n\n```javascript\nimport perspective from \"@perspective-dev/client\";\n\nconst worker = await perspective.worker();\nviewer.load(worker);\n```\n\n... or\n\n```javascript\nconst table = await worker.table(data, {name: \"superstore\"});\nviewer.load(table);\n```\n\nComplete example:"},{"title":"JS API » getViewConfig (2)","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"```javascript\nconst viewer = document.createElement(\"perspective-viewer\");\nconst worker = await perspective.worker();\n\nawait worker.table(\"x\\n1\", {name: \"table_one\"});\nawait viewer.load(worker);\nawait viewer.restore({table: \"table_one\", columns: [\"x\"]});\n```\n\n... or, if you don't want to pass your own arguments to `restore`:\n\n```javascript\nconst viewer = document.createElement(\"perspective-viewer\");\nconst worker = await perspective.worker();\n\nconst table = await worker.table(\"x\\n1\", {name: \"table_one\"});\nawait viewer.load(table);\n```"},{"title":"JS API » removePanel","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"removePanel(name: string): Promise<any>\n\nRemove the panel with id `name` from the layout, disposing its engines\n(its `View` is deleted and its `Table` reference released). The last\nremaining panel cannot be removed (resolves as a no-op). Resolves\nafter the panel's teardown run completes, carrying any teardown\nerror — previously fire-and-forget and silently dropped (invariant\nI6). See also [`Self::addPanel`].\n\nJS API » reset\n\nreset(reset_all?: boolean | null, options?: PanelOptions | null): Promise<any>\n\nReset a panel's `ViewerConfig` to its data-relative default.\n\nWithout a `panel`, this is ELEMENT-LEVEL: EVERY panel is reset and the\ncross-filter overlay cleared (symmetric with\n[`Self::saveWorkspace`] / [`Self::restoreWorkspace`]). With `{panel}`,\nonly that panel is reset — the other panels and the overlay are left\nuntouched.\n\n# Arguments\n\n- `reset_all` - If set, will clear expressions and column settings as\n well, and tear down the element's ENTIRE derived-table state: every\n panel bound to a derived `Table` is released to its root table, and\n every derivation (pending ones included) is deleted.\n- `options` - An optional `{panel?: string}`; the panel to reset, or\n every panel when omitted.\n\n# JavaScript Examples\n\n```javascript\nawait viewer.reset(); // every panel\nawait viewer.reset(true, {panel: \"p1\"}); // just \"p1\", + expressions\n```"},{"title":"JS API » resetError","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"resetError(): Promise<any>\n\nIf this element is in an _errored_ state, this method will clear it and\nre-render. Calling this method is equivalent to clicking the error reset\nbutton in the UI.\n\nJS API » resetThemes\n\nresetThemes(themes?: any[] | null): Promise<any>\n\nSet the available theme names available in the status bar UI.\n\nCalling [`Self::resetThemes`] may cause the current theme to switch,\nif e.g. the new theme set does not contain the current theme.\n\n# JavaScript Examples\n\nRestrict `<perspective-viewer>` theme options to _only_ default light\nand dark themes, regardless of what is auto-detected from the page's\nCSS:\n\n```javascript\nviewer.resetThemes([\"Pro Light\", \"Pro Dark\"])\n```"},{"title":"JS API » resize","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"resize(options?: any | null): Promise<any>\n\nRecalculate the viewer's dimensions and redraw.\n\nUse this method to tell `<perspective-viewer>` its dimensions have\nchanged when auto-size mode has been disabled via [`Self::setAutoSize`].\n[`Self::resize`] resolves when the resize-initiated redraw of this\nelement has completed.\n\n# Arguments\n\n- `options` - An optional object with the following fields:\n - `dimensions` - An optional object `{width, height}` providing\n explicit size hints (in pixels) for the plugin container. When\n provided, the plugin element will be temporarily sized to these\n dimensions during resize, then reset.\n\n# JavaScript Examples\n\n```javascript\nawait viewer.resize()\nawait viewer.resize({dimensions: {width: 800, height: 600}})\n```"},{"title":"JS API » restore","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"restore(update: ViewerConfigUpdate, options?: RestoreOptions | null): Promise<void>\n\nRestore a single panel from a full/partial\n[`perspective_js::JsViewConfig`] (its user-configurable state, including\nthe `Table` name) — the active panel, or a specific panel via the\noptional `{panel}` selector.\n\nIf `panel` names no existing panel, a NEW panel is created with that id\nand the config restored into it (an upsert). Creation REQUIRES a\n`table` — the same rule [`Self::addPanel`] enforces in its argument\ntype — and a would-create call without one REJECTS before any state\n(including `settings`) is applied: with no panel to target and no\n`table`, the patch has no data arrival path. In particular, on an\nelement with zero panels every `restore` must carry a `table`.\n\nOn an empty element with a pending [`Self::load`] whose payload is not\nyet classified, the active-target form (no `panel`) instead claims and\nrestores into that load's reserved first panel — see [`Self::load`].\n\nThis restores a SINGLE panel; a workspace config (with a `panels`\nmap) must be applied via [`Self::restoreWorkspace`] — its `panels` /\n`layout` keys are ignored here.\n\nOne of the best ways to use [`Self::restore`] is by first configuring\na `<perspective-viewer>` as you wish, then using either the `Debug`\npanel or \"Copy\" -> \"config.json\" from the toolbar menu to snapshot\nthe [`Self::restore`] argument as JSON.\n\n# Arguments\n\n- `update` - The config to restore to, as returned by [`Self::save`] in\n either \"json\", \"string\" or \"arraybuffer\" format.\n- `options.panel` - The panel to target, or the active panel when\n omitted.\n- `options.suppress_errors` - when `true`, a config that was applied and\n then failed to render only rejects the returned `Promise`, without\n raising the panel's visible error state.\n- `options.wait_for_table` - when `true`, a `table` no loaded client\n hosts yet leaves the panel empty and pending until the table is\n created, instead of the default error.\n\n# JavaScript Examples\n\nLoads a default plugin for the table named `\"superstore\"`:\n\n```javascript\nawait viewer.restore({table: \"superstore\"});\n```\n\nApply a `group_by` to the same `viewer` element, without\nmodifying/resetting other fields - you can omit the `table` field,\nthis has already been set once and is not modified:\n\n```javascript\nawait viewer.restore({group_by: [\"State\"]});\n```"},{"title":"JS API » restoreWorkspace","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"restoreWorkspace(update: WorkspaceConfigUpdate, options?: RestoreWorkspaceOptions | null): Promise<void>\n\nApply a [`WorkspaceConfigUpdate`] to the element — the multi-panel\ncounterpart of [`Self::restore`] and like it a field-wise update, where\nan absent field is unchanged, `null` resets and a value replaces.\n\n# Arguments\n\n- `update` - The workspace config update: a [`Self::saveWorkspace`]\n token replaces every panel (`panels` is the whole set), while a token\n without `panels` keeps them and `layout`, `active` and `masters` name\n them by their existing ids.\n- `options.wait_for_table` - when `true`, a panel whose `table` no\n loaded client hosts yet is created empty and pending until the table\n is created, instead of the default error.\n\n# JavaScript Examples\n\n```javascript\nawait viewer.restoreWorkspace(await otherViewer.saveWorkspace());\n```\n\nCross-filter the current panels without re-creating them:\n\n```javascript\nawait viewer.restoreWorkspace({ global_filters: [[\"Region\", \"==\", \"West\"]] });\n```"},{"title":"JS API » restyleElement","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"restyleElement(): Promise<any>\n\nRestyle all plugins from current document.\n\n<div class=\"warning\">\n\n[`Self::restyleElement`] _must_ be called for many runtime changes to\nCSS properties to be reflected in an already-rendered\n`<perspective-viewer>`.\n\n</div>\n\n# JavaScript Examples\n\n```javascript\nviewer.style = \"--psp--color: red\";\nawait viewer.restyleElement();\n```\n\nJS API » save\n\nsave(options?: PanelOptions | null): Promise<ViewerConfig>\n\nSave a single panel's user-configurable state as a [`ViewerConfig`], one\nwhich can be restored via [`Self::restore`] — the active panel, or a\nspecific panel via the optional `{panel}` selector.\n\nThis saves a SINGLE panel; to snapshot the ENTIRE element (every panel +\nlayout + cross-filters) use [`Self::saveWorkspace`].\n\n# Arguments\n\n- `options` - An optional `{panel?: string}`; the panel to save, or the\n active panel when omitted.\n\n# JavaScript Examples\n\nGet the current `group_by` setting:\n\n```javascript\nconst {group_by} = await viewer.save();\n```\n\nReset workflow attached to an external button `myResetButton`:\n\n```javascript\nconst token = await viewer.save();\nmyResetButton.addEventListener(\"click\", async () => {\n await viewer.restore(token);\n});\n```"},{"title":"JS API » saveWorkspace","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"saveWorkspace(options?: SaveWorkspaceOptions | null): Promise<WorkspaceConfig>\n\nSave the ENTIRE element to a [`WorkspaceConfig`]\n(`{version, active?, layout, panels, palette?, ...}`) — the\nmulti-panel counterpart of [`Self::save`]. Unlike [`Self::save`]\n(which emits a single `ViewerConfig` for one panel), this ALWAYS\nemits the workspace format, restorable via\n[`Self::restoreWorkspace`].\n\n# JavaScript Examples\n\n```javascript\nconst token = await viewer.saveWorkspace();\nawait viewer.restoreWorkspace(token);\n```"},{"title":"JS API » setActivePanel","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"setActivePanel(name: string): Promise<any>\n\nMake the panel with id `name` the active panel, re-targeting the\nsettings panel and status-bar toolbar (and the root's\nsession/renderer subscriptions) to its engines. Resolves after the\nactivation-chrome redraws on both sides of the switch have completed\n(invariant I6).\n\nJS API » setAutoPause\n\nsetAutoPause(autopause: boolean): Promise<any>\n\nSets the auto-pause behavior of this component.\n\nWhen `true`, this `<perspective-viewer>` will skip rendering\nwhenever it cannot be seen — tracked via an `IntersectionObserver`\non itself (scrolled out of the viewport, `display: none`) combined\nwith the document's page visibility (backgrounded browser tab,\nminimized window). Auto-pause is enabled by default.\n\n# Arguments\n\n- `autopause` Whether to enable `auto-pause` behavior or not.\n\n# JavaScript Examples\n\nDisable auto-size behavior:\n\n```javascript\nviewer.setAutoPause(false);\n```"},{"title":"JS API » setAutoSize","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"setAutoSize(autosize: boolean): void\n\nSets the auto-size behavior of this component.\n\nWhen `true`, this `<perspective-viewer>` will register a\n`ResizeObserver` on itself and call [`Self::resize`] whenever its own\ndimensions change. However, when embedded in a larger application\ncontext, you may want to call [`Self::resize`] manually to avoid\nover-rendering; in this case auto-sizing can be disabled via this\nmethod. Auto-size behavior is enabled by default.\n\n# Arguments\n\n- `autosize` - Whether to enable `auto-size` behavior or not.\n\n# JavaScript Examples\n\nDisable auto-size behavior:\n\n```javascript\nviewer.setAutoSize(false);\n```"},{"title":"JS API » setSelection","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"setSelection(window?: ViewWindow | null, options?: PanelOptions | null): void\n\nSet the selection [`perspective_js::JsViewWindow`] for the named panel,\nor the active panel when `panel` is omitted.\n\nJS API » setThrottle\n\nsetThrottle(val?: number | null): void\n\nDetermines the render throttling behavior. Can be an integer, for\nmillisecond window to throttle render event; or, if `None`, adaptive\nthrottling will be calculated from the measured render time of the\nlast 5 frames.\n\n# Arguments\n\n- `throttle` - The throttle rate in milliseconds (f64), or `None` for\n adaptive throttling.\n\n# JavaScript Examples\n\nOnly draws at most 1 frame/sec:\n\n```rust\nviewer.setThrottle(1000);\n```"},{"title":"JS API » toggleColumnSettings","path":"rust/perspective-viewer/dist/wasm/perspective-viewer.d.ts","text":"toggleColumnSettings(column_name: string, options?: PanelOptions | null): Promise<any>\n\nAsynchronously opens the column settings for a specific column.\nWhen finished, the `<perspective-viewer>` element will emit a\n\"perspective-toggle-column-settings\" CustomEvent.\nThe event's details property has two fields: `{open: bool, column_name?:\nstring}`. The CustomEvent is also fired whenever the user toggles the\nsidebar manually.\n\nJS API » toggleConfig\n\ntoggleConfig(force?: boolean | null): Promise<any>\n\nToggle (or force) the config panel open/closed.\n\n# Arguments\n\n- `force` - Force the state of the panel open or closed, or `None` to\n toggle.\n\n# JavaScript Examples\n\n```javascript\nawait viewer.toggleConfig();\n```"},{"title":"Datagrid Plugin » EditMode","path":"packages/viewer-datagrid/src/ts/types.ts","text":"export type EditMode =\n\nDatagrid cell interaction mode (`plugin_config.edit_mode`):\n`\"READ_ONLY\"` (default), `\"EDIT\"` (cells editable, writing back to the\n`Table` - requires an editable table), or the `\"SELECT_*\"` modes which\nemit selection events instead of editing.\n\nDatagrid Plugin » Align\n\nexport type Align =\n\nA cell of the 3×3 alignment grid: corners `\"top-left\"` etc., edges\n`\"top\"`/`\"left\"`/`\"right\"`/`\"bottom\"`, or `\"center\"`.\n\nDatagrid Plugin » ColumnConfig\n\nexport interface ColumnConfig\n\nDatagrid per-column style configuration - one value of the\n`columns_config` map of a `ViewerConfigUpdate` when the Datagrid plugin\nis active. Valid keys depend on the column's type; the authoritative,\nvalue-dependent declaration is `column_config_schema()` (surfaced at\nruntime via the agent's `get_style_schema` tool and the Style tab)."},{"title":"Datagrid Plugin » fg_mode","path":"packages/viewer-datagrid/src/ts/types.ts","text":"fg_mode?: FgMode\n\nForeground treatment. Numeric columns: `\"color\"` (default, text\ncolored by sign), `\"bar\"` (proportional bar), `\"label-bar\"` (bar\nwith label) or `\"disabled\"`. String columns: `\"disabled\"` (default),\n`\"color\"` or `\"series\"` (one `fg_color` palette entry per distinct\nvalue). Date / datetime columns: `\"disabled\"` (default) or\n`\"color\"`. A mode the column's type does not accept is rejected on\ninput.\n\nDatagrid Plugin » bg_mode\n\nbg_mode?: BgMode\n\nBackground treatment. Numeric columns: `\"disabled\"` (default),\n`\"color\"` (solid fill by sign), `\"gradient\"` (fill intensity scaled\nto the value) or `\"pulse\"` (flash on change). String columns:\n`\"disabled\"`, `\"color\"` or `\"series\"`. Date / datetime columns:\n`\"disabled\"` or `\"color\"`."},{"title":"Datagrid Plugin » fg_color","path":"packages/viewer-datagrid/src/ts/types.ts","text":"fg_color?: string\n\nThe foreground color value, a CSS string whose grammar follows the\ncolumn's type and `fg_mode` - not advertised while the mode is\n`\"disabled\"`:\n\n- numeric columns: sign-split stops, `linear-gradient(to right,\n #rrggbb 0%, #rrggbb 100%)`, t-ordered (the first stop is the\n negative color, the last the positive);\n- string / datetime columns in `\"color\"` mode: one color, `#rrggbb`;\n- string columns in `\"series\"` mode: a palette assigned to distinct\n values in encounter order, `linear-gradient(to right, #rrggbb, …)`\n with no positions."},{"title":"Datagrid Plugin » bg_color","path":"packages/viewer-datagrid/src/ts/types.ts","text":"bg_color?: string\n\nThe background color value, as `fg_color` but keyed by `bg_mode`.\nNumeric `\"gradient\"` and `\"pulse\"` modes take a color scale with the\nsign pivot at offset 0.5.\n\nDatagrid Plugin » fg_gradient\n\nfg_gradient?: number\n\nNumeric columns: the absolute value at which bar/gradient\nforeground modes reach full scale.\n\nDatagrid Plugin » bg_gradient\n\nbg_gradient?: number\n\nNumeric columns: the absolute value at which gradient background\nmode reaches full scale.\n\nDatagrid Plugin » aggregate_depth\n\naggregate_depth?: number\n\nGroup-by rollup depth override for this column when the view is\npivoted in `Rollup` mode."},{"title":"Datagrid Plugin » column_size_override","path":"packages/viewer-datagrid/src/ts/types.ts","text":"column_size_override?: number\n\nPixel width override, written when a user drag-resizes a column.\n\nDatagrid Plugin » font_family\n\nfont_family?: string\n\nFont family for this column's body cells, overriding\n`plugin_config.font_family`.\n\nDatagrid Plugin » font_size\n\nfont_size?: number\n\nFont size in CSS pixels for this column's body cells, overriding\n`plugin_config.font_size`.\n\nDatagrid Plugin » word_wrap\n\nword_wrap?: boolean\n\nWrap this column's clipped text (`true`) or clip it with an ellipsis\n(`false`), overriding `plugin_config.word_wrap`."},{"title":"Datagrid Plugin » align","path":"packages/viewer-datagrid/src/ts/types.ts","text":"align?: Align\n\nAlignment of this column's body cells, overriding\n`plugin_config.align`.\n\nDatagrid Plugin » link\n\nlink?: boolean\n\nString columns: render each value as a hyperlink to itself, which is\nthen not text-editable.\n\nDatagrid Plugin » bold\n\nbold?: boolean\n\nBold body text for this column, overriding `plugin_config.bold`.\n\nDatagrid Plugin » italic\n\nitalic?: boolean\n\nItalic body text for this column, overriding `plugin_config.italic`.\n\nDatagrid Plugin » date_format\n\ndate_format?: DateFormatConfig\n\nDatetime columns: display format preset or custom fields."},{"title":"Datagrid Plugin » number_format","path":"packages/viewer-datagrid/src/ts/types.ts","text":"number_format?: NumberFormatConfig\n\nNumeric columns: `Intl.NumberFormat`-style options controlling\ndigits, notation, currency, etc.\n\nDatagrid Plugin » DatagridPluginConfig\n\nexport interface DatagridPluginConfig\n\nDatagrid plugin-level configuration - the `plugin_config` slot of a\n`ViewerConfigUpdate` when the Datagrid plugin is active (the\n`save()`/`restore()` token).\n\nDatagrid Plugin » scroll_lock\n\nscroll_lock?: boolean\n\nWhen `true`, the Datagrid keeps its scroll position pinned during\ndata updates instead of following appended rows."},{"title":"Datagrid Plugin » column_menus","path":"packages/viewer-datagrid/src/ts/types.ts","text":"column_menus?: boolean\n\nWhether the header shows the per-column edit-button row while the\nsettings panel is open (default `true`).\n\nDatagrid Plugin » edit_mode\n\nedit_mode?: EditMode\n\nCell interaction mode - see {@link EditMode}.\n\nDatagrid Plugin » font_family\n\nfont_family?: string\n\nFont family for every cell: `\"inherit\"` (the default, and the\nmeaning of an omitted key), a CSS generic family, or a local font\nfamily name as listed by the browser's Local Font Access API.\n\nDatagrid Plugin » font_size\n\nfont_size?: number\n\nCell font size in CSS pixels, defaulting to the theme's."},{"title":"Datagrid Plugin » bold","path":"packages/viewer-datagrid/src/ts/types.ts","text":"bold?: boolean\n\nBold text in every cell unless a column's own `bold` overrides it.\n\nDatagrid Plugin » italic\n\nitalic?: boolean\n\nItalic text in every cell unless a column's own `italic` overrides it.\n\nDatagrid Plugin » word_wrap\n\nword_wrap?: boolean\n\nWhen `true`, text in a column narrower than its content wraps onto\nfurther lines instead of being clipped with an ellipsis.\n\nDatagrid Plugin » align\n\nalign?: Align\n\nAlignment of every body cell, defaulting to the column type's (numbers\nright, others left, vertically centered)."},{"title":"Datagrid Plugin » row_height","path":"packages/viewer-datagrid/src/ts/types.ts","text":"row_height?: number\n\nRow height in CSS pixels, defaulting to the theme's.\n\nDatagrid Plugin » zebra_rows\n\nzebra_rows?: number\n\nZebra striping period: every `zebra_rows` rows alternate between the\nplain background and `zebra_color`, with `0` (default) disabling\nstriping.\n\nDatagrid Plugin » zebra_color\n\nzebra_color?: string\n\nStripe color for zebra rows as `#rrggbb`, present only while\n`zebra_rows >= 1`."},{"title":"Charts Plugin » facet_mode","path":"packages/viewer-charts/src/ts/charts/chart.ts","text":"facet_mode: \"grid\" | \"overlay\"\n\n\"grid\" = small multiples (default); \"overlay\" = legacy single-plot.\n\nCharts Plugin » shared_x_axis\n\nshared_x_axis: boolean\n\nShare one bottom X axis across all columns of facets.\n\nCharts Plugin » shared_y_axis\n\nshared_y_axis: boolean\n\nShare one left Y axis across all rows of facets.\n\nCharts Plugin » coordinated_tooltip\n\ncoordinated_tooltip: boolean\n\nPaint a tooltip in every facet (otherwise only the source facet).\n\nCharts Plugin » zoom_mode\n\nzoom_mode: \"shared\" | \"independent\"\n\n\"shared\" = one viewport for all facets; \"independent\" = per-facet."},{"title":"Charts Plugin » facet_padding","path":"packages/viewer-charts/src/ts/charts/chart.ts","text":"facet_padding: number\n\nPixel gap between adjacent facet cells in grid mode.\n\nCharts Plugin » PluginConfig\n\nexport interface PluginConfig\n\nPlugin-scoped global configuration — the user-facing settings backing\n`plugin_config_schema()` / the `plugin_config` slot in `restore`.\n\nEach chart type's `plugin_config_schema` returns only the fields that\nare applicable for that chart (see `applicable_plugin_fields` on\n`ChartTypeConfig`); inapplicable fields are hidden in the UI. The\nchart impl receives the full struct on `setPluginConfig` and reads\nonly the fields its render / build pipeline cares about.\n\nSome fields overlap with {@link FacetConfig} (`facet_mode`,\n`facet_zoom_mode`); the base `AbstractChart.setPluginConfig` syncs\nthose onto `_facetConfig` so deep render code keeps reading the\nsingle facet struct it already does. `series_zoom_mode` toggles the\ncategorical-Y chart base's `_autoFitValue` flag."},{"title":"Charts Plugin » auto_alt_y_axis","path":"packages/viewer-charts/src/ts/charts/chart.ts","text":"auto_alt_y_axis: boolean\n\nAuto-detect Y dual-axis splits when aggregate magnitudes differ\nby more than `DUAL_Y_RATIO_THRESHOLD`×. Series charts only.\nReplaces the `AUTO_ALT_Y_AXIS` compile-time toggle.\n\nCharts Plugin » facet_mode\n\nfacet_mode: \"grid\" | \"overlay\"\n\nFaceting strategy when `split_by` is non-empty.\n\n- `\"grid\"` — one small-multiple sub-plot per split group.\n- `\"overlay\"` — a single plot: cartesian charts differentiate\n split groups by color; the categorical band pipeline (series\n charts) stacks splits within each aggregate's band slot.\n Synced into `_facetConfig.facet_mode`.\n\nThe default differs by family via\n`ChartTypeConfig.plugin_field_defaults`: cartesian / density /\nmap chart types default to `\"grid\"`, the series / financial\nband-pipeline types to `\"overlay\"` (their historical split\nrendering). Series charts REBUILD on a mode change — grid mode\nkeys the stack ladder per split (`facetSplits` in\n`buildSeriesPipeline`) so each facet grows from its own\nbaseline."},{"title":"Charts Plugin » facet_zoom_mode","path":"packages/viewer-charts/src/ts/charts/chart.ts","text":"facet_zoom_mode: \"shared\" | \"independent\"\n\nFaceted-cartesian zoom routing. `\"shared\"` — one viewport across\nall facets; `\"independent\"` — wheel/pan routes to the facet under\nthe cursor with its own viewport. Synced into\n`_facetConfig.zoom_mode`.\n\nCharts Plugin » series_zoom_mode\n\nseries_zoom_mode: \"fixed\" | \"dynamic\"\n\nSeries-chart value-axis behavior on zoom.\n\n- `\"dynamic\"` — value axis refits to the visible categorical\n slice (current default; `CategoricalYChart._autoFitValue` =\n true).\n- `\"fixed\"` — value axis stays pinned to the full-data extent."},{"title":"Charts Plugin » include_zero","path":"packages/viewer-charts/src/ts/charts/chart.ts","text":"include_zero: boolean\n\nAnchor the value axis to zero — when true, `0` is forced into\nthe rendered domain even if all data sits well above or below\nit. Natural for bar / area glyphs (which grow from the zero\nbaseline) and surprising for line / scatter (where the\ninteresting variation often lives far from zero). Per-chart-type\ndefaults route through `ChartTypeConfig.plugin_field_defaults`:\n`true` for Y Bar / Y Area / X Bar, `false` elsewhere.\n\nCharts Plugin » domain_mode\n\ndomain_mode: \"fit\" | \"expand\"\n\nDomain accumulation policy across successive `View` updates.\n\n- `\"fit\"` — every update recomputes the affected domains from\n the current data extent. Can grow or shrink frame-to-frame.\n- `\"expand\"` — the affected domains monotonically *grow*: each\n update unions the new data extent with the previously rendered\n extent, so once a value is in scope it stays in scope. Reset\n by the \"Reset Zoom\" button, view-config changes (group_by /\n split_by / column-slot / column-type), or toggling back to\n `\"fit\"`.\n\nAXIS SCOPE differs by family: cartesian charts (X/Y Scatter,\nX/Y Line, Density, Maps) apply it to BOTH axes plus the\ncolor/size scales (categorical string axes opt out — slot\nindices are frame-local); the categorical band pipeline (series\n/ financial) applies it to the VALUE axis only — Y for the\nY-family, X for X Bar — while the category axis always fits, so\na streaming numeric/datetime `group_by` axis releases departed\ncategories instead of pinning to its history."},{"title":"Charts Plugin » line_width_px","path":"packages/viewer-charts/src/ts/charts/chart.ts","text":"line_width_px: number\n\nWidth of polyline glyphs in CSS pixels (multiplied by DPR at GL\nupload). Replaces the duplicated `LINE_WIDTH_PX` constants in\nthe cartesian + series line glyphs.\n\nCharts Plugin » point_size_px\n\npoint_size_px: number\n\nDiameter of scatter point glyphs in CSS pixels. Replaces\n`POINT_SIZE_PX`.\n\nCharts Plugin » band_inner_frac\n\nband_inner_frac: number\n\nFraction of each category band occupied by its slot(s). Replaces\n`BAND_INNER_FRAC`. Affects buffer contents — takes effect on\nnext data load."},{"title":"Charts Plugin » bar_inner_pad","path":"packages/viewer-charts/src/ts/charts/chart.ts","text":"bar_inner_pad: number\n\nRelative inner padding between adjacent slots within a band.\nReplaces `BAR_INNER_PAD`. Affects buffer contents — takes effect\non next data load.\n\nCharts Plugin » group_pad\n\ngroup_pad: number\n\nExtra category gap, in leaf-band units, added per outer `group_by` level that changes across a category boundary.\n\nCharts Plugin » wick_width_px\n\nwick_width_px: number\n\nCandlestick wick stroke width in CSS pixels. Replaces\n`WICK_WIDTH_PX`.\n\nCharts Plugin » ohlc_line_width_px\n\nohlc_line_width_px: number\n\nOHLC bar stroke width in CSS pixels. Replaces\n`OHLC_LINE_WIDTH_PX`."},{"title":"Charts Plugin » gradient_radius_px","path":"packages/viewer-charts/src/ts/charts/chart.ts","text":"gradient_radius_px: number\n\ndensity splat radius in CSS pixels. Each data point is\nrasterized as a soft disk of this radius into the accumulation\nFBO before the gradient LUT pass resolves to a heat color.\n\nCharts Plugin » gradient_intensity\n\ngradient_intensity: number\n\ndensity per-splat intensity multiplier. Controls how\nfast the density grows when points overlap (low values produce\na smoother, more diffuse field; high values produce sharper\npeaks).\n\nCharts Plugin » gradient_heat_max\n\ngradient_heat_max: number\n\ndensity clamp on the maximum accumulated heat used for\nthe gradient-LUT lookup. Lower values saturate sooner (more of\nthe LUT's hot stops show up); higher values stay cooler. In\nevery mode this controls the alpha (intensity) ramp; in\n`density` mode it also drives the hue, and in `signed` mode it\nscales the signed-sum-to-hue mapping."},{"title":"Charts Plugin » gradient_color_mode","path":"packages/viewer-charts/src/ts/charts/chart.ts","text":"gradient_color_mode: \"mean\" | \"density\" | \"extreme\" | \"signed\"\n\ndensity color-reduction mode. Controls how each pixel's\nstack of overlapping splats is reduced to a single LUT-t / alpha\npair in the resolve pass.\n\n- `mean` (default) — hue is the density-weighted average of\n per-point color-t. Reads as \"the typical color-column value\n in this region.\" Uses robust (5th/95th-percentile) bounds so\n one outlier can't compress the rest of the data toward the\n gradient midpoint.\n- `density` — ignore the color column even when wired; hue and\n alpha both follow density. Reads as \"where do points cluster.\"\n Useful when the color column is attached for tooltip lookup\n only.\n- `extreme` — keep the per-pixel maximum signed deviation of\n `t - 0.5` (split into positive and negative channels, MAX-\n blended). Reads as \"where are the outliers.\" Density still\n drives alpha so a single-point extreme fades naturally.\n Requires a second framebuffer; uses MRT on WebGL2 hardware\n with `OES_draw_buffers_indexed`, two passes otherwise.\n- `signed` — accumulate signed `t - 0.5` and let positive vs\n negative cancel out. Reads as \"net positive vs net negative\n in each region.\" Requires a float-capable framebuffer; on\n `UNSIGNED_BYTE` fallback hardware degrades silently to\n `mean` with a one-line console warning."},{"title":"Charts Plugin » map_tile_provider","path":"packages/viewer-charts/src/ts/charts/chart.ts","text":"map_tile_provider: string\n\nMap basemap tile provider — a `TileSourceSpec` id from the\ntile-source registry ([map/tile-sources.json] entries plus any\nruntime `registerTileSource` additions). Applies only to map\nplugin tags (`map-scatter`, `map-line`, `map-density`); other\ncharts ignore the field. The default is the JSON's FIRST entry\n(reordering the file changes the default), and unknown ids fall\nback to that same entry rather than blanking the map.\n\nCharts Plugin » map_tile_alpha\n\nmap_tile_alpha: number\n\nMap basemap alpha (0..1). Pre-multiplied into the tile fragment\nshader's output so the chart's glyph layer composites over a\ndimmer or brighter version of the basemap. `1.0` (default)\nshows the tiles at full opacity."},{"title":"Charts Plugin » numeric_axes","path":"packages/viewer-charts/src/ts/charts/chart.ts","text":"numeric_axes: boolean\n\nMap plugins only. `true` (default): standard numeric axes in the\nusual cartesian gutters, with tick labels in degrees\nlongitude/latitude (`122.4°W`) rather than Mercator meters.\n`false`: no axes, and the plot is full-bleed — the basemap fills\nthe entire canvas, minus only the sidebar legend gutter when a\nlegend is actually shown. Gridlines are never drawn in map mode\n— the gridline canvas composites BELOW the GL layer, so opaque\nbasemap tiles would hide them.\n\nCharts Plugin » legend_mode\n\nlegend_mode: \"auto\" | \"sidebar\" | \"none\" | \"floating\"\n\nLegend presentation mode. `\"auto\"` (default) resolves per frame\nto `\"floating\"` when every entry fits the default floating panel\nwithout scrolling (≤ 7 entries; continuous gradient legends\nalways qualify) and to `\"sidebar\"` otherwise — see\n`resolveLegendMode`. Treemap overrides the default to\n`\"sidebar\"` (a floating panel over edge-to-edge tiles always\noccludes data)."},{"title":"Charts Plugin » legend_size_mode","path":"packages/viewer-charts/src/ts/charts/chart.ts","text":"legend_size_mode: LegendSizeMode\n\nFloating-panel sizing regime. `\"auto\"` (default) sizes the panel\nto its CONTENT every frame — the height hugs the entry rows\nexactly, and the width hugs the widest entry label (measured on\nthe chrome canvas). `\"fixed\"` uses the saved `legend_width_px` /\n`legend_height_px` verbatim.\n\nApplies to `legend_mode: \"floating\"` ONLY; the sidebar gutter is\nalways `legend_width_px` (its height is the plot's). Dragging a\nresize handle switches an auto panel to `\"fixed\"` — otherwise\nthe gesture would be undone by the next paint — and\ndouble-clicking a resize handle switches it back."},{"title":"Charts Plugin » legend_width_px","path":"packages/viewer-charts/src/ts/charts/chart.ts","text":"legend_width_px: number\n\nLegend width in CSS pixels. `0` (default) = automatic — each\nchart family keeps its historical gutter width (80–96px). In\n`\"sidebar\"` mode this is the full right-gutter width; in\n`\"floating\"` mode it is the panel width. Clamped at paint time\nto at most half the canvas width so a saved wide legend cannot\ncrush a small panel. Ignored by a floating panel in\n`legend_size_mode: \"auto\"`.\n\nCharts Plugin » legend_height_px\n\nlegend_height_px: number\n\nFloating-legend panel height in CSS pixels. Ignored in\n`\"sidebar\"` mode (the legend spans the plot height) and by a\nfloating panel in `legend_size_mode: \"auto\"`. Clamped at paint\ntime to the canvas height."},{"title":"Charts Plugin » legend_anchor","path":"packages/viewer-charts/src/ts/charts/chart.ts","text":"legend_anchor: LegendAnchor\n\nCanvas corner that `legend_x` / `legend_y` are measured FROM.\nFloating mode only. The panel keeps its distance to this corner\nacross panel resizes — anchor `\"bottom-right\"` with small\noffsets stays glued to the bottom-right.\n\nCharts Plugin » tooltip_max_column_px\n\ntooltip_max_column_px: number\n\nPer-column width cap for tooltip grid cells, in CSS pixels."},{"title":"Charts Plugin » PluginConfigField","path":"packages/viewer-charts/src/ts/plugin/charts.ts","text":"export type PluginConfigField = keyof PluginConfig\n\nSubset of `PluginConfig` keys that a chart impl actually consumes.\nDrives `plugin_config_schema()` filtering — the host only renders\nthe controls listed here, and `plugin.restore({ plugin_config })`\nstill hands the full struct to the worker (other keys are inert).\n\nCharts Plugin » applicable_plugin_fields\n\napplicable_plugin_fields: readonly PluginConfigField[]\n\nPlugin-config keys this chart type renders controls for. Empty\nfor plugins with no global settings (heatmap / treemap /\nsunburst). See {@link PluginConfig} for field semantics."},{"title":"Charts Plugin » group_by_role","path":"packages/viewer-charts/src/ts/plugin/charts.ts","text":"group_by_role?: string\n\nWhat `group_by` DRAWS in this chart, e.g. `\"X Axis\"` for the\nY-series charts. Omitted where the field has no visual role of\nits own and is a plain aggregation key — the X/Y and map charts,\nwhose axes both come from `columns`.\n\nThe `group_by` counterpart of `initial.names`: one declaration\nsays what every view field draws, so the settings UI's labels and\nthe agent's `list_plugins` contract read the same source instead\nof restating the mapping.\n\nCharts Plugin » split_by_role\n\nsplit_by_role?: string\n\nWhat `split_by` DRAWS in this chart. See {@link group_by_role}."},{"title":"Charts Plugin » connects_row_order","path":"packages/viewer-charts/src/ts/plugin/charts.ts","text":"connects_row_order?: boolean\n\nSet on the charts that CONNECT their points in row order, so the\nview's row order shows up in the drawing: without a `sort` the\nline follows the table's natural order, which reads as a tangle\nunless the rows already arrive ordered along the X axis. The point\ncharts (scatter, density) are unaffected, and a pre-ordered table\nneeds no `sort` — which is why this is declared per chart rather\nthan inferred from an empty `sort`.\n\nCharts Plugin » plugin_field_defaults\n\nplugin_field_defaults?: Partial<PluginConfig>\n\nPer-chart-type overrides for `DEFAULT_PLUGIN_CONFIG`. Used when a\nfield's sensible default differs by chart family — currently\n`include_zero` (true for Y Bar / Y Area / X Bar, false for line\n/ scatter / cartesian / financial) and `facet_mode` (\"overlay\"\nfor the band-pipeline families — series / financial — whose\nhistorical split_by rendering is the single stacked/colored\nplot; \"grid\" elsewhere). Applied at schema generation and at\n`restore({})` so the effective default matches the surfaced UI\ndefault."},{"title":"Column Format » CustomNumberFormatConfig","path":"rust/perspective-viewer/src/ts/ts-rs/CustomNumberFormatConfig.ts","text":"export type CustomNumberFormatConfig = { minimumIntegerDigits?: number | null, minimumFractionDigits?: number | null, maximumFractionDigits?: number | null, minimumSignificantDigits?: number | null, maximumSignificantDigits?: number | null, roundingPriority?: RoundingPriority | null, roundingIncrement?: number | null, roundingMode?: RoundingMode | null, trailingZeroDisplay?: TrailingZeroDisplay | null,\n\nA numeric column's `number_format` (`columns_config` value) —\n`Intl.NumberFormat`-shaped options, written by the Style tab's number\nformat editor and read by `createNumberFormatter`. The `style` and\n`notation` families ([`NumberFormatStyle`] / [`Notation`]) are serde-\nFLATTENED into this object but `#[ts(skip)]`'d (ts-rs cannot flatten\n`Option<enum>`) — the package's `NumberFormatConfig` re-composes the\nfull wire type as `CustomNumberFormatConfig & Partial<NumberFormatStyle>\n& Partial<Notation>` (see `column-format.ts`)."},{"title":"Column Format » useGrouping","path":"rust/perspective-viewer/src/ts/ts-rs/CustomNumberFormatConfig.ts","text":"useGrouping?: UseGrouping | null, signDisplay?: SignDisplay | null, }\n\nNOTE (audit 2026-08-05): serialized values are the STRINGS\n`\"always\"`/`\"auto\"`/`\"min2\"` or the untagged BOOLEAN `false` —\nthe former hand-written TS `useGrouping?: boolean` was wrong for\nthe string cases."},{"title":"Column Format » NumberFormatStyle","path":"rust/perspective-viewer/src/ts/ts-rs/NumberFormatStyle.ts","text":"export type NumberFormatStyle = { \"style\": \"decimal\" } | { \"style\": \"currency\" } & CurrencyNumberFormatStyle | { \"style\": \"percent\" } | { \"style\": \"unit\" } & UnitNumberFormatStyle\n\nThe `style` family of a numeric column's `number_format` — serialized\nFLATTENED into [`CustomNumberFormatConfig`]'s object, discriminated by\nthe `style` key (`\"decimal\"` default, `\"currency\"` + `currency`/\n`currencyDisplay`/`currencySign`, `\"percent\"`, `\"unit\"` + `unit`/\n`unitDisplay`), mirroring `Intl.NumberFormat` options."},{"title":"Column Format » Notation","path":"rust/perspective-viewer/src/ts/ts-rs/Notation.ts","text":"export type Notation = { \"notation\": \"standard\" } | { \"notation\": \"scientific\" } | { \"notation\": \"engineering\" } | { \"notation\": \"compact\" } & CompactDisplay\n\nThe `notation` family of a numeric column's `number_format` —\nserialized FLATTENED into [`CustomNumberFormatConfig`]'s object,\ndiscriminated by the `notation` key (`\"standard\"` default,\n`\"scientific\"`, `\"engineering\"`, `\"compact\"` + `compactDisplay`),\nmirroring `Intl.NumberFormat` options."},{"title":"Column Format » CustomDatetimeStyleConfig","path":"rust/perspective-viewer/src/ts/ts-rs/CustomDatetimeStyleConfig.ts","text":"export type CustomDatetimeStyleConfig = { format: FormatUnit,\n\nA datetime column's `date_format` in its `Custom` form: per-part\n`Intl.DateTimeFormatOptions` overrides, discriminated from the `Simple`\npreset form by the required `format: \"custom\"` key. Each part is a\n[`CustomDatetimeFormat`] variant valid for that `Intl` option;\n`\"disabled\"` omits the part from the formatted output entirely.\n\nColumn Format » timeZone\n\ntimeZone?: string | null,\n\nAn IANA time zone name (e.g. `\"America/New_York\"`); defaults to the\nbrowser's local time zone."},{"title":"Column Format » fractionalSecondDigits","path":"rust/perspective-viewer/src/ts/ts-rs/CustomDatetimeStyleConfig.ts","text":"fractionalSecondDigits?: number,\n\nSub-second digits to display, 0-3 (0 = none).\n\nColumn Format » weekday\n\nweekday?: CustomDatetimeFormat,\n\nA name form (`\"long\"`/`\"short\"`/`\"narrow\"`); defaults to\n`\"disabled\"` (weekday not shown).\n\nColumn Format » month\n\nmonth?: CustomDatetimeFormat,\n\nNumeric or name form; defaults to `\"numeric\"`."},{"title":"Column Format » SimpleDatetimeStyleConfig","path":"rust/perspective-viewer/src/ts/ts-rs/SimpleDatetimeStyleConfig.ts","text":"export type SimpleDatetimeStyleConfig =\n\nA datetime column's `date_format` in its `Simple` preset form:\n`Intl.DateTimeFormatOptions`' `dateStyle`/`timeStyle` presets. This is\nthe default form (no `format` key); setting `format: \"custom\"` selects\nthe per-part `CustomDatetimeStyleConfig` instead.\n\nColumn Format » timeZone\n\ntimeZone?: string | null,\n\nAn IANA time zone name (e.g. `\"America/New_York\"`); defaults to the\nbrowser's local time zone.\n\nColumn Format » dateStyle\n\ndateStyle?: SimpleDatetimeFormat,\n\nDate preset breadth, or `\"disabled\"` to omit the date entirely;\ndefaults to `\"short\"`."},{"title":"Column Format » timeStyle","path":"rust/perspective-viewer/src/ts/ts-rs/SimpleDatetimeStyleConfig.ts","text":"timeStyle?: SimpleDatetimeFormat, }\n\nTime preset breadth, or `\"disabled\"` to omit the time entirely;\ndefaults to `\"medium\"`."},{"title":"Workspace » WorkspaceConfigUpdate","path":"rust/perspective-viewer/src/ts/ts-rs/WorkspaceConfigUpdate.ts","text":"export type WorkspaceConfigUpdate =\n\nThe [`PerspectiveViewerElement::restoreWorkspace`] argument: a field-wise\nupdate of the element's workspace state, like [`ViewerConfigUpdate`] for a\npanel, where an absent key is unchanged, `null` resets and a value\nreplaces.\n\nWorkspace » active\n\nactive?: OptionalUpdate<string>,\n\nThe panel to activate with the settings sidebar open, by `panels` key\nor existing panel id; `null` closes the sidebar.\n\nWorkspace » layout\n\nlayout?: Layout,\n\nThe layout tree to stage, naming every `panels` key (or, without\n`panels`, every existing panel id) exactly once."},{"title":"Workspace » panels","path":"rust/perspective-viewer/src/ts/ts-rs/WorkspaceConfigUpdate.ts","text":"panels?: { [key in string]?: ViewerConfigInitial },\n\nThe complete replacement panel set, each [`ViewerConfigInitial`] entry\ncreating a new panel (`{}` empties the element); absent keeps the\nexisting panels.\n\nWorkspace » derived_tables\n\nderived_tables?: { [key in string]?: DerivedTableConfig<string> },\n\nThe complete replacement set of derived `Table`s, in any order (they\nare built parent-first); absent keeps the current ones, `{}` deletes\nthem all.\n\nWorkspace » global_filters\n\nglobal_filters?: OptionalUpdate<Array<Filter>>,\n\nThe element-level cross-filter set applied to every detail panel as\none unattributed bucket, replacing the current set; `null` clears it."},{"title":"Workspace » masters","path":"rust/perspective-viewer/src/ts/ts-rs/WorkspaceConfigUpdate.ts","text":"masters?: OptionalUpdate<Array<string>>,\n\nThe master (filter-source) panels by `panels` key or existing panel\nid, unknown ids dropped with a warning; `null` demotes every panel.\n\nWorkspace » palette\n\npalette?: OptionalUpdate<{ [key in string]?: string }>, }\n\nNamed color-scale definitions applied to the host (see\n[`WorkspaceConfig::palette`]), replacing the previous palette; `null`\nclears it."},{"title":"Workspace » Layout","path":"rust/perspective-viewer/src/ts/ts-rs/Layout.ts","text":"export type Layout = { \"type\": \"split-layout\" } & SplitLayout | { \"type\": \"tab-layout\" } & TabLayout\n\nA dashboard's panel arrangement: nested splits whose leaves are tab stacks\nof panel ids, naming every panel exactly once."},{"title":"Workspace » SplitLayout","path":"rust/perspective-viewer/src/ts/ts-rs/SplitLayout.ts","text":"export type SplitLayout =\n\nA row or column of child layouts, nested to form a grid.\n\nWorkspace » children\n\nchildren: Array<Layout>,\n\nChild layouts, in left-to-right or top-to-bottom order.\n\nWorkspace » sizes\n\nsizes: Array<number>,\n\nEach child's fraction of the split, one per child, summing to 1.\n\nWorkspace » orientation\n\norientation: Orientation, }\n\nWhether children sit side by side (`horizontal`) or stacked\n(`vertical`)."},{"title":"Workspace » TabLayout","path":"rust/perspective-viewer/src/ts/ts-rs/TabLayout.ts","text":"export type TabLayout =\n\nA stack of panels shown as tabs, one visible at a time.\n\nWorkspace » selected\n\nselected?: number, }\n\nIndex of the selected tab within `tabs`."},{"title":"Workspace » Orientation","path":"rust/perspective-viewer/src/ts/ts-rs/Orientation.ts","text":"export type Orientation = \"horizontal\" | \"vertical\"\n\nThe direction a [`SplitLayout`] divides its space in: `horizontal` places\nchildren side by side as columns, `vertical` stacks them as rows."},{"title":"Workspace » DerivedTableConfig","path":"rust/perspective-viewer/src/ts/ts-rs/DerivedTableConfig.ts","text":"export type DerivedTableConfig<T = string> =\n\nA derived `Table`'s definition: the `Table` its source `View` reads, and\nthe config of that `View`; generic over the `table` slot, which is a name\nin the workspace `derived_tables` map and a recursive [`TableRef`] when\ninlined in a single-panel config.\n\nWorkspace » table\n\ntable: T, group_by: Array<string>, split_by: Array<string>, sort: Array<Sort>, filter: Array<Filter>, group_rollup_mode: GroupRollupMode, split_rollup_mode: SplitRollupMode, filter_op?: FilterReducer, expressions: Expressions, windows?: Windows, columns: Array<string | null>, aggregates: { [key in string]?: Aggregate }, group_by_depth?: number | null, }\n\nThe `Table` this derivation reads, which may itself be another\nderived `Table`."}]}