Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .cargo/release.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
perspective-client = { path = "rust/perspective-client" }
perspective-server = { path = "rust/perspective-server" }
perspective-js = { path = "rust/perspective-js" }
perspective-migrate = { path = "rust/perspective-migrate" }
perspective-python = { path = "rust/perspective-python" }
perspective-viewer = { path = "rust/perspective-viewer" }
perspective = { path = "rust/perspective" }
2 changes: 1 addition & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -721,7 +721,7 @@ jobs:

- name: Package
if: ${{ steps.config-step.outputs.FULL_RUN }}
run: cargo package --config .cargo/release.toml --no-verify --allow-dirty -p perspective -p perspective-viewer -p perspective-js -p perspective-client -p perspective-server -p perspective-python
run: cargo package --config .cargo/release.toml --no-verify --allow-dirty -p perspective -p perspective-viewer -p perspective-js -p perspective-client -p perspective-server -p perspective-migrate -p perspective-python

- uses: actions/upload-artifact@v4
if: ${{ steps.config-step.outputs.FULL_RUN }}
Expand Down
22 changes: 22 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ members = [
"rust/perspective",
"rust/perspective-client",
"rust/perspective-js",
"rust/perspective-migrate",
"rust/perspective-migrate-js",
"rust/perspective-python",
"rust/perspective-server",
"rust/perspective-viewer",
Expand Down Expand Up @@ -58,6 +60,7 @@ protobuf-src = { git = "https://github.com/carlocorradini/rust-protobuf-native.g
perspective-client = { path = "rust/perspective-client" }
perspective-server = { path = "rust/perspective-server" }
perspective-js = { path = "rust/perspective-js" }
perspective-migrate = { path = "rust/perspective-migrate" }
perspective = { path = "rust/perspective" }
perspective-viewer = { path = "rust/perspective-viewer" }
perspective-python = { path = "rust/perspective-python" }
1 change: 1 addition & 0 deletions docs/md/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
# Rust

- [Getting Started](./how_to/rust.md)
- [Migrating saved configs](./how_to/rust/migrate.md)

# Integrations

Expand Down
5 changes: 4 additions & 1 deletion docs/md/explanation/view/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,10 @@ const result = await table.validate_expressions({
<div class="python">

```python
result = table.validate_expressions(['"Sales" + "Profit"', 'invalid + 1'])
result = table.validate_expressions({
"expr1": '"Sales" + "Profit"',
"expr2": "invalid_column + 1",
})
```

</div>
Expand Down
4 changes: 0 additions & 4 deletions docs/md/explanation/view/config/grouping_and_pivots.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,6 @@ a given column accepts depends on its type — see
| `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` |
| `pct sum total` | The group's `sum` as a percentage of the grand total, `0`–`100` | `float` |

`sum not null` is accepted as a legacy alias for `sum`.

A numeric aggregate widens to the input's numeric class — `integer` columns
accumulate as `integer`, `float` columns as `float`.

Expand Down Expand Up @@ -390,10 +388,8 @@ anywhere an aggregate is, and each group refers to one function:
| `last` | `last_value` |
| `high` | `high_water_mark` |
| `low` | `low_water_mark` |
| `pct sum total` | `pct sum grand total`, `pct_sum_grand_total` |
| `var` | `variance` |
| `stddev` | `standard deviation` |
| `sum` | `sum not null`, `sum_not_null` (legacy name) |

Most multi-word aggregates also answer to a snake_case spelling —
`sum_or_zero`, `sum_abs`, `abs_sum`, `weighted_mean`, `distinct_leaf`,
Expand Down
38 changes: 17 additions & 21 deletions docs/md/how_to/javascript/save_restore.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,25 @@ panel visibility, and more. This overloaded feature covers a range of use cases:
## Serializing and deserializing the viewer state

To retrieve the entire state as a JSON-ready JavaScript object, use the `save()`
method. `save()` also supports a few other formats such as `"arraybuffer"` and
`"string"` (base64, not JSON), which you may choose for size at the expense of
easy migration/manual-editing.
method.

```javascript
const json_token = await elem.save();
const string_token = await elem.save("string");
const token = await elem.save();
```

For any format, the serialized token can be restored to any
`<perspective-viewer>` with a `Table` of identical schema, via the `restore()`
method. Note that while the data for a token returned from `save()` may differ,
generally its schema may not, as many other settings depend on column names and
types.
The token can be restored to any `<perspective-viewer>` with a `Table` of
identical schema, via the `restore()` method. Note that while the data for a
token returned from `save()` may differ, generally its schema may not, as many
other settings depend on column names and types.

```javascript
await elem.restore(json_token);
await elem.restore(string_token);
await elem.restore(token);
```

As `restore()` dispatches on the token's type, it is important to make sure that
these types match! A common source of error occurs when passing a
JSON-stringified token to `restore()`, which will assume base64-encoded msgpack
when a string token is used.
`restore()` validates its argument, rejecting for example a key the config
format does not define; when a rejected token is stamped with another release's
`version`, the error names that release. Tokens saved by older releases should
be upgraded first — see [Migrating saved configs](../rust/migrate.md).

```javascript
// This will error!
Expand Down Expand Up @@ -117,8 +112,8 @@ await elem.restoreWorkspace(workspace_token);

A `saveWorkspace()` token is a `WorkspaceConfig`
(`{ version, layout, panels, ... }`), not a `ViewerConfig` — passing it to the
single-panel `restore()` will _not_ restore the layout (its `panels`/`layout`
keys are ignored).
single-panel `restore()` is rejected (its `panels`/`layout` keys are not part
of a `ViewerConfig`).

The `layout` field is the tree arranging the panels into a grid of rows and
columns or stacks of tabs, naming every panel id exactly once. See
Expand Down Expand Up @@ -205,9 +200,10 @@ inherits `timeZone` and `hour12` individually.

Configs saved before 5.7.0 used a `"disabled"` value and a `format: "custom"`
marker, and a missing preset field meant `short` date or `medium` time.
`restore()` rewrites these into the current form with the same rendering. A
hand-written config with no `version` and a missing preset field takes the
current meaning: `{ dateStyle: "long" }` now shows the date alone.
`restore()` rejects the removed forms, and restores any other `date_format`
with its current meaning — `{ dateStyle: "long" }` shows the date alone,
whatever its `version`. [`perspective-migrate`](../rust/migrate.md) rewrites
older tokens into the current form with their original rendering.

## Colors, palettes and gradients

Expand Down
136 changes: 136 additions & 0 deletions docs/md/how_to/rust/migrate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# Migrating saved configs

`<perspective-viewer>`'s `restore()`, `addPanel()` and `restoreWorkspace()`
accept only the current config schema: a config which fails validation (for
example, a key the format does not define) is rejected, and when it is stamped
with another release's `version` the error names that release. Engine
`ViewConfig`s passed to `table.view()` likewise accept only the current form
(for example, `expressions` must be a map of names to expressions).

Configs persisted by an older release — `save()` and `saveWorkspace()` tokens,
`view.get_config()` output, or hand-written `ViewConfig`s — are upgraded by
`migrate()`, which rewrites any JSON config written by a release since 2.0.0
into the current schema. It is available from `@perspective-dev/client` (in the
browser and in Node.js), from `@perspective-dev/viewer`, from the `perspective`
Python package, and as the `perspective-migrate` Rust crate.

## Usage

The release that wrote a config is read from its `version` field, which `save()`
and `saveWorkspace()` have stamped since 2.7.0. A config without one takes the
optional `from_version` argument, and without either it is treated as written by
2.0.0. Each workspace panel's own `version` decides how that panel is upgraded,
so a 2.x–4.x `<perspective-workspace>` layout, whose envelope was never stamped,
needs no `from_version` when its panels were.

In the browser, the migration WebAssembly binary is registered alongside the
engine binaries and downloaded only by the first `migrate()` call (the CDN
bundles register it automatically):

```javascript
import perspective from "@perspective-dev/client";
import perspective_viewer from "@perspective-dev/viewer";

await perspective_viewer.init({
server32: () => fetch(SERVER_WASM),
server64: () => fetch(SERVER_WASM64),
viewer: () => fetch(VIEWER_WASM),
migrate: () => fetch(MIGRATE_WASM),
});

const { value, notices } = await perspective_viewer.migrate("workspace", saved);
await viewer.restoreWorkspace(value);
```

`MIGRATE_WASM` is
`@perspective-dev/client/dist/wasm/perspective-migrate-js.wasm`. In Node.js,
`perspective.migrate()` needs no registration.

```python
import perspective

result = perspective.migrate("viewer", token)
viewer_config = result["value"]
```

```bash
cargo add perspective-migrate
```

```rust
use perspective_migrate::{Kind, Migrator};

let migrated = Migrator::new().migrate(Kind::Viewer, None, token)?;
for notice in &migrated.notices {
eprintln!("{notice:?}");
}

save(migrated.value);
```

The first argument names the document being upgraded (`Kind` in Rust):

| JavaScript / Python | Rust | Document |
| ------------------- | ----------------- | ----------------------------------------------------------------- |
| `"view"` | `Kind::View` | An engine `ViewConfig`, as passed to `table.view()` |
| `"viewer"` | `Kind::Viewer` | A `<perspective-viewer>` `save()` / `restore()` token |
| `"workspace"` | `Kind::Workspace` | A `saveWorkspace()` token, or a 2.x–4.x `<perspective-workspace>` |

Viewer and workspace output is stamped with the current `version`, so migrating
it again changes nothing; `ViewConfig`s have no `version`, but every
`ViewConfig` upgrade leaves a current config unchanged. Each upgrade also
reports `notices` for information which has no current representation
(`Dropped`, with the removed value) or which could only be approximated
(`Approximated`), and `steps`, the ids of the schema changes that applied.

A config is rejected with a `MigrateError` only when no valid current form
exists — for example a pre-5.2 workspace panel with no `table`, or a 2.x
`ViewConfig` which names both an alias and its canonical key (`row_pivot` and
`group_by`).

## Third-party plugins

A plugin's `plugin_config` and `columns_config` payloads are migrated by a
`PluginMigration`, keyed by the Perspective release that wrote the token:

```rust
use perspective_migrate::{Migrator, PluginMigration, Step, Version};

struct Sparkline;

impl PluginMigration for Sparkline {
fn plugins(&self) -> &[&'static str] {
&["Sparkline"]
}

fn steps(&self) -> Vec<Step> {
vec![Step {
at: Version::new(3, 0, 0),
id: "sparkline.line_width",
apply: rename_width,
}]
}
}

let migrator = Migrator::new().with_plugin(Sparkline);
```

## What is not migrated

`perspective-migrate` produces a config which is _valid_ for the current release
and keeps the information the old config expressed. Changes to defaults and
behavior are not reversed, so a migrated config renders as a new config with the
same settings would today:

- Engine: 2.x ignored filters with a `null` term; date filter terms captured
from 3.0.x – 3.4.2 `get_config()` used seconds and 0-based months; an
all-`null` group's `sum` is `null` rather than `0` since 5.6.0 (use
`sum or zero`).
- Column styles: string `"series"` colors derived from a hue rotation (before
5.3.0); float fraction-digit defaults merged rather than replaced (5.3.1).
- Charts: the `domain_mode`, `interpolate`, `facet_mode` and `legend_mode`
defaults, and the map `numeric_axes` default.
- Derived tables created from grouped views before 5.6.0, whose key columns were
named `"<column> (Group by N)"`.
- Binary `"string"` / `"arraybuffer"` tokens written by 2.0 – 4.0, which must be
re-saved as JSON by the release that wrote them.
4 changes: 1 addition & 3 deletions packages/viewer-charts/src/ts/charts/series/series-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ export interface ColumnChartConfig {

/**
* Interpolation mode for line / area glyphs. See
* {@link InterpolateMode}. Legacy values `true` / `false` are also
* accepted by {@link resolveInterpolate} (mapped to `"solid"` /
* `"skip"`). Default `"skip"`. No effect on bar / scatter.
* {@link InterpolateMode}. Default `"skip"`. No effect on bar / scatter.
*/
interpolate?: InterpolateMode;

Expand Down
4 changes: 0 additions & 4 deletions packages/viewer-charts/src/ts/plugin/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -951,10 +951,6 @@ export class HTMLPerspectiveViewerWebGLPluginElement
}

restore(config: any, columns_config?: Record<string, any>) {
if (config?.zoom) {
this._renderer?.restoreZoom(config.zoom);
}

// Merge incoming plugin_config on top of the `chart_type`
// effective defaults so a partial restore (UI emits only
// changed fields) keeps untouched defaults in place — and
Expand Down
7 changes: 0 additions & 7 deletions packages/viewer-datagrid/src/ts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,6 @@ export interface ColumnConfig {
*/
bg_gradient?: number;

fixed?: number;

/**
* Group-by rollup depth override for this column when the view is
* pivoted in `Rollup` mode.
Expand Down Expand Up @@ -391,9 +389,6 @@ export type ResolvedColumnsConfig = Record<string, ResolvedColumnStyle>;
* `save()`/`restore()` token).
*/
export interface DatagridPluginConfig {
/** Legacy alias for `edit_mode: "EDIT"`. */
editable?: boolean;

/**
* When `true`, the Datagrid keeps its scroll position pinned during
* data updates instead of following appended rows.
Expand Down Expand Up @@ -452,8 +447,6 @@ export interface DatagridPluginConfig {
* `zebra_rows >= 1`.
*/
zebra_color?: string;

column_size_override?: Record<string, number>;
}

export type Schema = Record<string, ColumnType>;
Expand Down
Loading
Loading