mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
85 lines
4.8 KiB
Markdown
85 lines
4.8 KiB
Markdown
# Multi-view data model — scope & plan
|
|
|
|
Astrolabe authors arbitrary Vega-Lite, including **composed** specs (`layer`,
|
|
`hconcat`/`vconcat`/`concat`, `facet`, `repeat`). Most of the spec-structure
|
|
machinery already handles composition; the data-facing features carried
|
|
single-view assumptions. This memo records the assessment, the data-model
|
|
contract that anchors the work, and the milestone plan to make multi-view support
|
|
durable. The guiding constraint: **extend Vega-Lite, never break it** — every
|
|
native data form must keep working.
|
|
|
|
## The data-model contract
|
|
|
|
A dataset reference is exactly Vega-Lite **named data**: a `data` block with a
|
|
string `name` and no `values`/`url`/generator key, whose name the spec does not
|
|
self-define via top-level `datasets`. This mirrors Vega-Lite's `isNamedData`
|
|
(`reference/vega-lite/src/data.ts`). The classification is owned by
|
|
`core/spec-data` (`classifyData`, `libraryRefName`); reference extraction
|
|
(`spec-refs`), rename (`spec-refs`), and render-time resolution (`rendering`) all
|
|
route through it. See `docs/architecture/07` §3.1.
|
|
|
|
Library references resolve to inline data before embedding
|
|
(`core/rendering` → `prepareSpecForRender`); a self-defined `datasets` name is
|
|
left for Vega-Lite to resolve natively.
|
|
|
|
## Assessment: already multi-view vs. single-view assumptions
|
|
|
|
**Already composition-aware** (recurse all view operators): ref extraction/rename
|
|
(`spec-refs`), reference resolution + fit-mode (`rendering`), structural wrap/
|
|
unwrap/add-view (`spec-transforms`, `spec-cursor`, `spec-insert`), derived-field
|
|
collection (`spec-fields`), config baking (`spec-config`), standalone export
|
|
(`chart-export`, reuses `prepareSpecForRender`).
|
|
|
|
**Single-view assumptions** (the work):
|
|
|
|
- **Editor data context** (`app/services/active-dataset`) resolves _one_ dataset
|
|
for the whole draft (first ref, or first inline data), with no notion of which
|
|
view the cursor sits in. Completion/hover/inlay (`spec-dataset-hints`) and the
|
|
facet/repeat field defaults (`spec-transform-actions`) therefore offer the wrong
|
|
view's columns in a composition whose views bind different datasets.
|
|
- **Extract inline data** (`app/stores/ExtractStore`) lifts only the top-level
|
|
`data` block.
|
|
- **Data inspector** (`core/result-data`, `DataInspector`) surfaces one input +
|
|
one resolved table; a composition produces several `source_<n>`/`data_<n>`.
|
|
- **`deriveSnippetName`** (`core/snippet`) reads top-level `mark`/`encoding` only,
|
|
so a composed spec falls back to the default name (graceful, not a bug).
|
|
- **Chart builder** is single-view by design; its strict round-trip hydration
|
|
returns `null` for composed specs, so they stay Monaco-only (correct).
|
|
|
|
## Vega-Lite fidelity clashes
|
|
|
|
1. **Named inline/url data misread as a reference** — classifying on "has a
|
|
string `name`" alone caught named-inline (`{ name, values }`) and named-url,
|
|
breaking valid specs (spurious `DatasetNotFoundError`, or clobbered inline
|
|
values). _Resolved_ by the `core/spec-data` classifier (M1).
|
|
2. **Shadowing** — a library dataset whose name equals a self-defined `datasets`
|
|
key is silently ignored (self-defined wins). Documented precedence; candidate
|
|
for a user-facing note, no code change required.
|
|
3. **Case-rule split** — library matching is case-insensitive (`naming.ts`);
|
|
self-defined exclusion and Vega-Lite's own named-data lookup are case-sensitive.
|
|
These are distinct namespaces, so the split is defensible; minor.
|
|
4. **Runtime-injected named data** — Vega-Lite allows binding `{ name }` at
|
|
runtime; Astrolabe always pre-resolves, so an imported spec relying on runtime
|
|
injection won't render. Out of scope.
|
|
|
|
## Milestone plan
|
|
|
|
- **M1 — data-model foundation** ✅ — `core/spec-data` classifier mirroring
|
|
`isNamedData`; `spec-refs` + `rendering` routed through it. Closes clash 1.
|
|
- **M2 — view-scoped editor context** — pure `dataContextAtPath(spec, path)`:
|
|
climb the cursor's JSON path to the nearest enclosing `data` (honoring
|
|
Vega-Lite's parent→child data inheritance), classify it, and collect
|
|
ancestor-chain derived fields. Rework `active-dataset` to be cursor-scoped and
|
|
thread the offset through the three Monaco providers and the facet/repeat
|
|
defaults. Resolve columns for every form: library ref, inline `values`,
|
|
named-inline, self-defined `datasets`, url (no static rows), generator (none).
|
|
- **M3 — view-scoped extract** — seed Extract from the focused view's inline data
|
|
(reusing the cursor-scope machinery) and rewrite that view's `data`.
|
|
- **M4 — multi-view inspection** — group the dataflow's datasets into per-view
|
|
input/resolved pairs (`result-data`) and add a view selector to `DataInspector`
|
|
(new interactive widget → `/council`).
|
|
|
|
Delivery is incremental, one milestone per commit, verified against real behavior.
|
|
The consolidated data-model contract write-up into `docs/architecture` (05/08)
|
|
lands once the shape is final.
|