@astrato-io/visualizations
v0.6.5
Published
Framework-neutral browser package for rendering Astrato visualizations in custom applications.
Downloads
5,486
Readme
@astrato-io/visualizations
Framework-neutral browser package for rendering Astrato visualizations in custom applications.
The package provides a visualization registry, runtime-owned instances, web components, direct mounting, vSpec operations and getters, metadata discovery, and lazy-loaded visualization implementations. Platform access is provided by @astrato-io/sdk.
Installation
npm install @astrato-io/sdk @astrato-io/visualizationsRxJS and Zod are installed as runtime dependencies.
Import the package stylesheet once in the application:
import '@astrato-io/visualizations/styles.css';Supported Visualizations
Use these exact visualization IDs:
| ID | Family |
| ------------------------ | ------ |
| bar | Chart |
| combo | Chart |
| distribution-gauge | Chart |
| dot | Chart |
| gauge | Chart |
| heatMap | Chart |
| histogram | Chart |
| line | Chart |
| pie | Chart |
| radar | Chart |
| sankey | Chart |
| scatter | Chart |
| table | Chart |
| tree | Chart |
| treemap | Chart |
| waterfall | Chart |
| word-cloud | Chart |
| date-picker | Filter |
| astrato-app-filter-bar | Filter |
| filter | Filter |
| slider | Filter |
| iframe | Object |
| image | Object |
| number | Object |
| shape | Object |
| simpleTextObject | Object |
Astrato-specific panels are not part of this package.
Implementations are loaded lazily when an instance is mounted.
Quick Start
Create one SDK client and one visualization runtime for the host application:
import { createAstratoClient } from '@astrato-io/sdk';
import { createVisualizationRuntime, registerAstratoElements, registry } from '@astrato-io/visualizations';
import '@astrato-io/visualizations/styles.css';
const client = createAstratoClient({
baseUrl: 'https://your-astrato-instance.example/',
tenantId: 'tenant-id',
auth: { token: 'personal-access-token' },
visualizationEngine: true,
});
const runtime = createVisualizationRuntime({
client,
registry,
});
runtime.createInstance({
id: 'sales-by-manufacturer',
type: 'bar',
groupId: 'dashboard',
dataBinding: {
tenantId: 'tenant-id',
semanticLayerId: 'semantic-layer-id',
datasourceType: 'snowflake',
},
});
registerAstratoElements({ runtime });Add the visualization to the page:
<astrato-visualization instance-id="sales-by-manufacturer"></astrato-visualization>Every mount container must have non-zero dimensions. The visualization measures its own element and fills the available space:
astrato-visualization {
display: block;
width: 100%;
height: 400px;
}App Filter Bar
astrato-app-filter-bar provides filter chips and editors for applications using the standalone library. It has its own vSpec and no property panel. The existing Astrato filter-pane object is unchanged.
Create it in the same selection group and with the same data binding as the charts it controls:
const chart = runtime.getInstance('sales-by-manufacturer');
runtime.createInstance({
id: 'dashboard-filters',
type: 'astrato-app-filter-bar',
groupId: chart.groupId,
dataBinding: chart.dataBinding,
});<astrato-visualization class="app-filters" instance-id="dashboard-filters"></astrato-visualization>.app-filters {
display: block;
width: 100%;
height: auto;
}The bar reads applied and pending filters from the selection service, including selections made by other charts. New filter searches engine field metadata. Empty new filters stay local until values are chosen. Manual edits show Apply and Cancel; auto edits apply immediately. Clear all clears the entire selection group, including filters outside the list of fields allowed for creation. Locking and single-select policies remain owned by the selection service.
This object is standalone-only: it has no Astrato workbook registration, property panel, Actions manifests, or build-astrato target. Its operations and getters are available through the standalone API and AI metadata. The private @astrato/objects-app-filter-bar source workspace can be versioned by Changesets, but is distributed through @astrato-io/visualizations, not published as an Astrato component.
Settings live in vSpec.appFilterBarSettings and are also available through operations and getters:
| Setting | Default | Meaning |
| ------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| overflow | "wrap" | Wrap chips, collapse behind +N, use a Filters (N) modal button, or switch responsively between these modes. |
| orientation | horizontal | Horizontal bar or vertical sidebar with stacked filter rows. |
| selectionMode | "inherit" | inherit, auto, or manual. A bar override affects only edits made by that bar; it does not change the host service's mode. |
| enableAddingNewFilters | true | Show the field picker. |
| enableAssociativeFilter | true | Allow associative filter creation when the runtime supports it and eligible fields exist. Set false to hide creation. |
| showClearAll | true | Show the group-wide clear action. |
| showChartBreakdown | false | Opt in to Chart breakdown for apps using dimension groups. Only shown when a group has at least two available fields. |
| selectableFieldKeys | unrestricted | Limit fields available for creating filters. Omitted or empty means all fields; existing selections are always visible. |
The bar supports value lists with search and paging, available and excluded values, text matching, numeric and date ranges, relative date periods, associative conditions, and dimension group switching. It uses the engine's configured time zone for timestamp editing. The value editor keeps search, sort, and the selection count above its scrollable list. Select all affects loaded values; Actions exposes deselect, invert, select available, and clear. Pending selections can be applied or cancelled from the bar or the editor footer.
Host integration requires the existing synchronous field metadata getters (or dataBinding.fieldMetadata), engine.getData or data.query, and selection services including getSelection, setNewSelectionState, pending apply/cancel, and clear-all. Provide the selection change observable so changes made elsewhere appear immediately. getSelectionMode(groupId) or the host's selectionMode supplies inherited behavior; the standalone adapter defaults to manual when neither is provided. Field locking is offered when toggleFieldLocked or setFieldLocked is available.
Associative filter creation requires an explicit hostServices.engine.capabilities.associativeFiltering: true. This capability defaults to false; advertise it only when the host integration supports both association discovery and executing associative selections. A discovery method's presence, ordinary field connectivity, or enableAssociativeFilter: true alone does not establish support. The app setting can disable creation but cannot override missing runtime support.
When an enabled field picker opens, the bar discovers eligible targets using hostServices.engine.getAssociatedViews(fieldKey, dataBinding). Otherwise the adapter reads viewStates from a one-row engine query, using alfStatus or compatibility flags. Hosts that omit this query metadata must supply the explicit discovery method before opting in. Discovery checks at most four fields concurrently, stops scheduling when the picker closes, and retries on the next opening. The option appears only if a target has an associated view containing an allowed condition field; empty or failed lookups never make a target eligible. No discovery runs on mount or when the feature is disabled or unsupported.
Existing associative chips remain visible and removable (subject to locking and single-select policies). Disabling creation does not prevent editing existing filters on a capable host. Without runtime support, opening a chip explains that editing is unavailable instead of launching a broken editor.
Dimension group changes are shared with mounted visualizations in the same runtime group and retained for later mounts. Apps that use those groups can expose Chart breakdown with settings.showChartBreakdown: true in their visualization definition, or the setAppFilterBarChartBreakdownEnabled operation. The control is hidden by default and when no group offers multiple available fields.
Wrapping Above the Charts
In "wrap" mode, put the bar in an auto-height section above the chart area. Extra chip rows increase the section height and move the charts down. The app owns the mount container and surrounding layout; a fixed-height or absolutely positioned header cannot grow with the bar.
<section class="dashboard-filter-section">
<astrato-visualization class="app-filters" instance-id="dashboard-filters"></astrato-visualization>
</section>
<section class="dashboard-charts">…</section>.dashboard-filter-section {
position: static;
flex: 0 0 auto;
}
.dashboard-filter-section .app-filters {
display: block;
width: 100%;
height: auto;
}
.dashboard-charts {
margin-top: 12px;
}Place the filter section and charts in normal document flow. Apps switching overflow modes should also switch the mount container between height: auto for wrap and the configured fixed height for collapse or button.
Header Overflow
For a fixed-height header, use the setAppFilterBarOverflow operation with payload "collapse". getAppFilterBarOverflow returns the configured mode. The default "wrap" preserves existing layouts. Set the mode through the standalone operations API after creating the instance; the visualization definition's settings retain their existing fields.
Collapse mode displays as many chips as fit, then +N for the hidden filters. Clicking +N opens a scrollable list of all filters, where each can be edited or removed under the same lock and single-select policies. Editors opened from that list offer All filters to return. Apply and Cancel work from the bar, list, or editor. Pending hidden filters give +N a dashed border and badge, with an accessible pending-changes label. The popover traps Tab focus, Escape closes it, and closing restores focus to the trigger (or a visible bar control if the trigger disappeared).
"collapse" keeps the action buttons on the row and uses --afb-height as the fixed bar height. For a 40px header:
.app-filters {
display: block;
width: 100%;
height: 40px;
--afb-height: 40px;
--afb-padding: 3px 8px;
--afb-control-height: 32px;
}Allow enough header width for New filter, optional Chart breakdown, the action buttons, and +N; disabling optional controls can help compact headers. Vertical orientation keeps stacked rows for wrap and collapse. The button mode applies in both orientations.
Use payload "button" for a fully collapsed Filters (N) control. The control fills the available width, with no surrounding well, border, or padding. It opens the complete filter bar in a native modal with a stacked list, New filter, optional Chart breakdown, and Apply / Cancel / Clear all. Pending selections are indicated on the button. The modal makes the rest of the page inert, traps keyboard focus, closes on Escape or a backdrop click, and restores focus to the button. Editors open inside the modal's top layer; closing an editor returns to the expanded bar. This mode also works with no selections so users can add their first filter.
Use payload "collapse-button" to show the single row with +N at wider widths and the Filters (N) button below 480px. Use "wrap-collapse-button" to wrap at 960px and above, collapse from 480px to below 960px, and show the button below 480px. These modes use the filter bar's width, rather than the viewport. Override --afb-button-breakpoint: 480px and --afb-collapse-breakpoint: 960px on the host to customize the transitions. Keep the mount container at height: auto for responsive modes so wrapping pushes the charts down. Responsive modes apply to horizontal bars; vertical bars retain their stacked layout. Resizing out of button mode closes the modal.
The new controls share the existing button hover, keyboard focus, disabled, chip pending, locked, and excluded styles. The modal and overflow popover inherit the popup surface, border, radius, padding, shadow, font, and text tokens. The list leaves space for focus rings; all scrolling overflow surfaces use thin, theme-colored scrollbars and contain scrolling at their boundaries. The pending badge follows the inline end in either text direction and uses system colors in forced-color mode.
| Token | Default / fallback | Applies to |
| ----------------------------- | ------------------------------------------------------- | -------------------------------------------- |
| --afb-overflow-surface | --afb-chip-surface, #f4f4f5 | +N and Filters (N) backgrounds |
| --afb-overflow-text | --afb-text, #27272a | Control text and icons |
| --afb-overflow-border | --afb-chip-border, #e4e4e7 | Control borders |
| --afb-overflow-radius | --afb-chip-radius, 8px | Control corners |
| --afb-overflow-hover | --afb-hover, #f4f4f5 | Hovered controls |
| --afb-overflow-open-surface | --afb-selected-surface, then --afb-hover, #f4f4f5 | Controls with an open list or modal |
| --afb-overflow-badge-size | 6px | Pending badge; color uses --afb-pending |
| --afb-overflow-list-height | --afb-list-height, 260px | Scrolling filter list |
| --afb-modal-width | 560px | Modal width, constrained to the viewport |
| --afb-modal-backdrop | #0006 | Modal backdrop |
| --afb-scrollbar-thumb | --afb-border, #e4e4e7 | Overflow list, popover, and modal scrollbars |
| --afb-scrollbar-track | transparent | Scrolling surface tracks |
| --afb-button-breakpoint | 480px | Responsive transition to button |
| --afb-collapse-breakpoint | 960px | Responsive transition from wrap to collapse |
The full-width button has the stable __trigger class and its own optional overrides. --afb-trigger-surface, --afb-trigger-text, --afb-trigger-border, --afb-trigger-radius, --afb-trigger-hover, and --afb-trigger-open-surface fall back to their corresponding overflow tokens. Size and layout use --afb-trigger-height (falls back to --afb-height, 48px), --afb-trigger-padding (falls back to --afb-button-padding, 5px 10px), --afb-trigger-align (center), --afb-trigger-gap (6px), --afb-trigger-font-weight (500), --afb-trigger-border-width (1px), and --afb-trigger-shadow (none). These apply to explicit button mode and the button phase of responsive modes. Pending borders and badges still use --afb-pending.
.app-filter-host {
--afb-trigger-surface: #2563eb;
--afb-trigger-text: #fff;
--afb-trigger-hover: #1d4ed8;
--afb-trigger-open-surface: #1e40af;
--afb-trigger-border: #2563eb;
--afb-trigger-radius: 8px;
--afb-trigger-height: 40px;
--afb-trigger-padding: 8px 16px;
--afb-trigger-align: flex-start;
}Use the stable __overflow, __overflow-popover, __overflow-list, and __modal classes for structural overrides. [data-overflow="wrap"], [data-overflow="collapse"], and [data-overflow="button"] reflect the currently rendered mode, including responsive transitions.
Vertical Sidebar
Set settings.orientation to "vertical" in the visualization definition (or use setAppFilterBarOrientation at runtime):
{
"version": 1,
"type": "astrato-app-filter-bar",
"settings": { "orientation": "vertical", "selectionMode": "manual" }
}Give the host a sidebar width and let its height follow its contents, for example width: 280px; max-width: 100%; height: auto. Filters become full-width rows with their values below the field name. Editors open to the right, flip left when necessary, and fall back above or below on narrow screens. Apply, Cancel, Clear all, and the selection group behave identically in both orientations. Horizontal remains the default for existing definitions.
Both layouts use the same styling tokens. Target [data-orientation="vertical"] for sidebar-specific overrides, and use --afb-vertical-chip-padding (9px 10px) to adjust row spacing. The stable __chip-summary class styles the value preview.
Styling the Bar and Its Editors
The bar uses neutral defaults and inherits CSS custom properties from its mount container. Popups keep their DOM ancestry while using the browser's top layer, so the same tokens apply to editors. Styles do not depend on generated Emotion class names.
.app-filters {
--afb-font-family: Inter, system-ui, sans-serif;
--afb-font-size: 13px;
--afb-surface: #ffffff;
--afb-text: #172554;
--afb-muted: #64748b;
--afb-border: #dbeafe;
--afb-accent: #2563eb;
--afb-on-accent: #ffffff;
--afb-focus: #60a5fa;
--afb-chip-surface: #eff6ff;
--afb-chip-border: #bfdbfe;
--afb-radius: 12px;
--afb-control-radius: 8px;
--afb-popup-width: 400px;
}
.dark-app .app-filters {
--afb-surface: #18181b;
--afb-popup-surface: #27272a;
--afb-control-surface: #27272a;
--afb-text: #fafafa;
--afb-muted: #a1a1aa;
--afb-border: #3f3f46;
--afb-hover: #3f3f46;
--afb-chip-surface: #27272a;
--afb-chip-border: #52525b;
--afb-selected-surface: #3f3f46;
}Additional tokens include --afb-padding, --afb-gap, --afb-height, --afb-border-width, --afb-control-height, --afb-button-padding, --afb-chip-radius, --afb-chip-max-width, --afb-pending, --afb-excluded, --afb-error, --afb-popup-padding, --afb-popup-radius, --afb-popup-shadow, --afb-list-height, and --afb-z-index (fallback for browsers without the Popover API).
Primary buttons darken the accent background by 10% on hover, keeping their text color unchanged. Override --afb-accent-hover to provide a custom hover background.
The field picker uses compact rows with the source view beside the field name. Its defaults are a 340px popup, 12px padding, and a 224px scrolling list; the existing popup and list tokens override these. Use --afb-field-row-height (36px) and --afb-field-icon-size (24px) to adjust field density. The stable __field-picker and __field-label classes allow targeted layout overrides; full names remain available to assistive technology and on hover when truncated.
The value editor has a direct mode button: click the list icon to switch to text matching, and the text icon to return to values. Numeric and date fields cycle through their additional range mode. The icon indicates the current mode; its tooltip and accessible label also name the next mode. Search or text matching shares a compact row with the mode button and Exclude, while sorting sits beside the selection actions. Target the stable __mode-toggle, __mode-label, and __text-query classes to customize this toolbar. The secondary Actions menu retains --afb-menu-width (220px) and the __menu-label class.
The values area reserves --afb-list-height (260px) through loading, results, and empty states. Values load in pages of 100, with the next page requested automatically within 120px of the bottom. Existing rows remain selectable during pagination, with a small spinner at the bottom; failed page requests offer a retry without discarding loaded values. Search is debounced by 300ms and resets pagination, as do sorting and changes to the applied connected-filter context. "Select all" selects the currently loaded values.
A centered spinner keeps existing rows visible but disabled during full refreshes. Loading indicators announce progress to screen readers and respect reduced motion. Customize them with --afb-spinner-size (16px), --afb-spinner-color (defaults to the muted text color), or the stable __loader, __spinner, and __next-page classes.
For structural overrides, use .astrato-app-filter-bar and its stable __chips, __chip, __chip-label, __actions, __button, __input, __select, __popover, __popover-header, __heading, __heading-icon, __editor, __editor-options, __editor-footer, __search, __search-row, __selection-toolbar, __menu, __menu-items, __field-option, __field-icon, __field-name, __value-list, __toolbar, __row, __field, __values, __value, __group, __muted, and __error classes. Buttons expose data-variant="ghost", data-icon-only, and data-primary for appearance overrides; icons inherit the text color. Chips expose data-pending, data-excluded, and data-locked; value rows expose data-selected and data-excluded.
Visualization Instances
The runtime owns visualization configuration and lifecycle. A VisualizationInstance contains:
| Property | Purpose |
| ------------- | ------------------------------------------------------------- |
| id | Stable runtime identifier |
| type | Visualization ID from the registry |
| vSpec | Visualization configuration |
| preset | Optional preset used during initialization |
| groupId | Selection and filter interaction scope |
| dataBinding | Astrato tenant, semantic layer, datasource, and query context |
If vSpec is omitted, the runtime creates a fresh default vSpec for the selected type. Requirement metadata is advisory: an empty visualization can mount before dimensions or measures are configured.
The runtime exposes:
runtime.createInstance(instance);
runtime.updateInstance(instanceId, update);
runtime.getInstance(instanceId);
runtime.listInstances();
runtime.removeInstance(instanceId);
runtime.validateInstanceRequirements(instanceId);Creating A Configured Visualization
Use a VisualizationDefinition to create the initial vSpec from exact engine fieldKey values. Each supported type has
a strict schema; see the Visualization Definition Reference for its fields,
constraints, and example. The Markdown reference is also available through the package export
@astrato-io/visualizations/VISUALIZATION_DEFINITIONS.md.
import {
compileVisualizationDefinition,
createVisualizationAvailableFields,
type VisualizationDefinition,
} from '@astrato-io/visualizations/definitions';
import { registry } from '@astrato-io/visualizations';
const metadata = await client.hostServices.engine.fields.getFieldMetadata?.({
tenantId: 'tenant-id',
semanticLayerId: 'semantic-layer-id',
datasourceType: 'snowflake',
});
const availableFields = createVisualizationAvailableFields(metadata ?? {});
const definition: Extract<VisualizationDefinition, { type: 'bar' }> = {
version: 1,
type: 'bar',
data: {
dimensions: [{ ref: 'manufacturer', fieldKey: '[Cars].[Manufacturer]' }],
measures: [
{
ref: 'sales',
fieldKey: '[Cars].[Sales In Thousands]',
aggregation: 'SUM',
},
],
},
sorting: [{ by: 'sales', direction: 'desc' }],
};
const compilation = await compileVisualizationDefinition({
definition,
availableFields,
baseVSpec: registry.bar.createDefaultVSpec(),
});
if (!compilation.success) {
throw new Error(JSON.stringify(compilation.issues));
}
runtime.createInstance({
id: 'sales-by-manufacturer',
type: 'bar',
vSpec: compilation.vSpec,
dataBinding: {
tenantId: 'tenant-id',
semanticLayerId: 'semantic-layer-id',
datasourceType: 'snowflake',
},
});Compilation requires a fresh default or preset vSpec and returns either a complete vSpec or structured issues. The caller must supply fields compatible with the current semantic-layer context. Raw fields used as measures require an aggregation; semantic measures and calculations keep their engine-defined behavior.
Use getVisualizationDefinitionDescriptor(type) for the selected type's JSON Schema, documentation, examples, and
feature metadata. listVisualizationDefinitionDescriptors() returns discovery metadata for all supported types. AI
integrations should give the selected descriptor and compatible field catalog to the model, compile its response, and
return structured issues for correction.
A definition covers a subset of each type's operations. To set anything else, apply operations to compilation.vSpec
with applyOperations in one batch before calling createInstance; see
Transforming A vSpec Without A Runtime. Do not finish a chart by applying
operations one at a time after it is mounted: each vSpec change on a mounted instance can trigger a new data request.
VSpec-only instance updates are applied to connected elements in place. Changes to the visualization type, preset, selection group, or data binding remount connected web components. Removing an instance unmounts it and discards its in-memory state.
Web Components
registerAstratoElements() registers astrato-visualization by default. Registration is idempotent for a given custom-element registry.
registerAstratoElements({ runtime });A custom tag name can be used when required by the host application:
registerAstratoElements({
runtime,
tagName: 'customer-visualization',
});The element can also receive a runtime and instance ID through JavaScript:
import type { AstratoVisualizationElement } from '@astrato-io/visualizations';
const element = document.createElement('astrato-visualization') as AstratoVisualizationElement;
element.runtime = runtime;
element.instanceId = 'sales-by-manufacturer';
container.appendChild(element);
const renderResult = await element.ready;
console.log(renderResult?.status);The element mounts when connected, applies vSpec-only runtime changes in place, remounts when instance-id or
structural runtime instance state changes, and unmounts when disconnected.
element.ready uses the same render deadline as direct mounting: it resolves to rendered, error, unmounted, or
timeout.
Direct Mounting
Applications that own DOM lifecycle directly can mount without the public web component:
const container = document.getElementById('chart-slot');
if (!container) {
throw new Error('Missing chart container.');
}
const handle = await runtime.mount(container, 'sales-by-manufacturer');
console.log(handle.status);
console.log(handle.requirements);
const renderResult = await handle.ready;
console.log(renderResult.status);
handle.unmount();Direct mounting and web-component mounting use the same runtime, instance state, SDK client, and visualization implementation.
runtime.mount() reports structural mounting through handle.status; handle.ready is the separate render-completion signal.
Readiness waits for the chart's existing render-completion or error event, with a default deadline of 40 seconds.
Set readyTimeoutMs when creating the runtime to customize that deadline for all direct and web-component mounts:
const runtime = createVisualizationRuntime({
client,
registry,
readyTimeoutMs: 60_000, // optional; defaults to 40_000
});The value must be an integer from 1 to 2,147,483,647 milliseconds. The deadline starts when chart initialization begins,
after metadata preloading and lazy module loading; it does not bound those preparatory steps of runtime.mount().
If no terminal event arrives in time, readiness resolves with { instanceId, status: 'timeout' }. The chart remains
mounted and may finish rendering later, but that readiness result stays timeout. Check for status === 'rendered'
before treating a chart as ready. Unmounting before the deadline resolves readiness as unmounted.
Fonts And Icons
Charts draw axis labels and legends on <canvas>, which only uses fonts that have finished loading. The runtime picks
the chart font for you, so no host CSS is needed:
createVisualizationRuntime({ theme: { fontFamily } }), when set.- Otherwise the host page's font:
--astrato-font-family, then--font-family(both read from:root), then thefont-familyofbody.
The runtime adds system-ui, sans-serif when the selected stack has no sans-serif fallback. It preserves your font
choices, including an explicitly selected serif family. Declare your webfont (for example with a <link> or
@font-face) before calling runtime.mount(). The runtime waits up to 3 seconds for the first registered webfont
in the stack, skipping unavailable families. Mounts in the same document share that preparation. The initial load
uses Latin sample text; other scripts and weights may load when chart text first uses them. Mounted charts redraw
when a face in their selected stack finishes loading, including after the initial wait times out. Unmounting removes
the font listener.
const runtime = createVisualizationRuntime({
client,
registry,
theme: { fontFamily: 'Inter' }, // optional; defaults to the page font
});All supported table indicator icons render as inline SVG through the shared icon catalog, including legacy aliases.
They need no icon font and are not affected by host font-family rules. The package includes the Material Icons
license in THIRD_PARTY_NOTICES.md.
Exporting A Chart's Data
A mounted chart exposes the query it runs, so an app can offer show-as-table or CSV/Excel export that matches the chart. The query is the chart's own: it keeps Top N, sorting, grouping and the chart's filters, without pagination. It runs through the same data service as the chart, so the group's selections apply to it the same way.
const handle = await runtime.mount(container, 'sales-by-manufacturer');
await handle.ready;
const exportQuery = await handle.getExportQuery(); // { instanceId, query, config } or null
const result = await handle.getData({ limit: 5000 }); // { instanceId, query, config, data } or nullgetData() runs the export query through the same engine.getData or data.query service the chart uses, and data
is that service's response. config carries the chart's export settings (limit, nullAs, csvFormatValues, and
pivotConfig for pivot tables). The row limit defaults to the chart's export limit, or 1,000, and is capped at 100,000.
Both resolve to null when the chart has nothing to export, for example when its requirements are not met, and when
the instance is unmounted. Call them after handle.ready: while the chart is fetching, they wait for that fetch's query.
Apps using the web component can call runtime.getExportQuery(instanceId) and runtime.getData(instanceId, options).
Updating vSpec With Operations
The Operations Reference lists the complete standalone operation catalog by visualization
type, with descriptions and payload schemas. It is generated from the same manifests as runtime metadata and shipped
inside this package. Humans and agents can read node_modules/@astrato-io/visualizations/docs/operations/index.md without
loading a chart or importing browser JavaScript. Use the bundled reference to match your installed package version.
Enum values are part of each payload schema. Breaking changes per version are in CHANGELOG.md.
Use a visualization definition once to create the initial configured vSpec. After runtime.createInstance(), keep that
runtime instance and use operations for incremental changes. Do not repeatedly compile definitions or replace the
complete vSpec with a host-side copy: that can discard chart-originated state such as table column widths and sorting.
Choosing An Operations API
The operations package exposes two levels of API:
| API | State ownership | Concurrency protection | Recommended use |
| ------------------------ | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- |
| applyRuntimeOperations | Reads and updates a runtime instance | Serializes calls per instance and retries after concurrent local vSpec changes | Normal SDK and browser integrations |
| applyOperations | Transforms the explicitly supplied instance and only returns the result | None across separate calls | Tests, previews, server-side/offline transformation, or custom state adapters |
Use applyRuntimeOperations unless the application deliberately owns vSpec state without the visualization runtime.
Applying Operations To A Runtime Instance
applyRuntimeOperations reads the latest runtime-owned vSpec, applies the requests, and writes the resulting vSpec back
to the same instance. If the instance is mounted, the connected visualization receives the update in place. Operations
also work while the instance is unmounted; a later mount uses the updated runtime vSpec.
import { applyRuntimeOperations } from '@astrato-io/visualizations/operations';
const result = await applyRuntimeOperations({
runtime,
instanceId: 'sales-by-manufacturer',
availableFields,
operationRequests: [
{
id: 'add-dimension',
name: 'addDimension',
payload: { key: '[Cars].[Manufacturer]' },
},
{
id: 'add-measure',
name: 'addMeasure',
payload: {
aggregation: 'SUM',
key: '[Cars].[Sales In Thousands]',
},
},
],
});
console.log(result.results);For one runtime instance, this API:
- serializes separate calls so that concurrent callers cannot overwrite each other's results;
- starts every call from the latest runtime vSpec;
- retries if the chart changes its local vSpec, for example after sorting or resizing a table column, while an operation is running; and
- rejects a stale result if the instance is removed, recreated, or structurally replaced while work is pending.
Calls for different instances are independent and may run concurrently.
Supply availableFields for operations that add or replace dimensions, measures, calculations, or raw fields. The
catalog must belong to the same semantic-layer context as the instance. Operations that only modify existing visual
properties may not require it. Most integrations do not need the advanced variables, vTheme, changesQueue, or
custom registry inputs.
Batches, Results, And Errors
operationRequests is a batch. Requests in one batch execute sequentially, and each request sees the successful vSpec
changes produced by earlier requests in that batch. A batch is not transactional: one request may fail while later
requests continue. Provide a stable id for every request so its response is easy to correlate. If it is omitted, the
operations engine generates an ID and returns it in the result.
The returned results object contains one result per request ID, while vSpec contains the final transformed vSpec.
Invalid payloads and operation-specific failures are normally reported as { success: false, error: ... } entries.
The returned promise rejects for call-level failures, such as an unknown instance, an unsupported operation name, a
missing source vSpec, or an instance lifecycle change that makes the pending result stale.
Use visualization metadata to discover operation names and payload schemas instead of importing internal Astrato operation packages:
import { getVisualizationMetadata } from '@astrato-io/visualizations/metadata';
const tableOperations = getVisualizationMetadata('table').operations;
console.log(tableOperations.names);
console.log(tableOperations.items);Transforming A vSpec Without A Runtime
applyOperations is the lower-level, state-independent operation engine. It accepts an explicit instance and returns a
new vSpec without updating a runtime, mounted element, or external store:
import { applyOperations } from '@astrato-io/visualizations/operations';
const transformed = await applyOperations({
instance,
availableFields,
operationRequests: [
{
id: 'add-manufacturer',
name: 'addDimension',
payload: { key: '[Cars].[Manufacturer]' },
},
],
});
console.log(transformed.vSpec);
console.log(transformed.results);This is the supported way to finish a compiled VisualizationDefinition before mounting: pass
{ id, type, vSpec: compilation.vSpec } as the instance and use compilation.resolvedRefs for the data-item identity of
each definition ref. It runs in memory and makes no engine requests. It is also useful when validating a vSpec,
running operations in tests, or building an adapter with intentional custom state ownership. It does not serialize separate calls or protect against a
newer runtime/chart vSpec. Avoid applying its result to a live runtime from an older instance snapshot; use
applyRuntimeOperations for that case.
Table Column Operations
For tables, create the configured table once and then keep the same runtime instance while adding, removing, or reordering columns:
await applyRuntimeOperations({
runtime,
instanceId: 'sales-table',
availableFields,
operationRequests: [
{
id: 'add-sales-column',
name: 'addColumn',
payload: {
columnType: 'measure',
sourceKind: 'field',
key: '[Cars].[Sales In Thousands]',
aggregation: 'SUM',
},
},
],
});
await applyRuntimeOperations({
runtime,
instanceId: 'sales-table',
operationRequests: [
{
id: 'remove-manufacturer-column',
name: 'removeColumn',
payload: {
columnType: 'dimension',
key: '[Cars].[Manufacturer]',
},
},
],
});
await applyRuntimeOperations({
runtime,
instanceId: 'sales-table',
operationRequests: [
{
id: 'move-model-column',
name: 'reorderColumn',
payload: {
source: { columnType: 'dimension', key: '[Cars].[Model]' },
target: { columnType: 'dimension', key: '[Cars].[Manufacturer]' },
},
},
],
});Engine-key selectors use match: 'only' by default: they match exactly one column and fail rather than guessing when
duplicates exist. Table removeColumn also accepts match: 'all' for intentional bulk removal. Raw field measures
include their aggregation; predefined measures and calculations include sourceKind. Pivot dimensions may include
topDimension. The existing internal-key selector remains available for integrations that already have exact vSpec
identifiers.
Operations are called against the runtime or an explicit instance, not against the astrato-visualization element.
Do not use the internal Astrato VISUALIZATION_OPERATIONS_REQUESTED event as a standalone integration API; it is tied
to Astrato's wrapper transport and persistence lifecycle. Similarly, reserve runtime.updateInstance(instanceId,
{ vSpec }) for an intentional complete vSpec replacement, not ordinary incremental changes.
Reading vSpec With Getters
import { processRuntimeGetterRequests } from '@astrato-io/visualizations/getters';
const result = processRuntimeGetterRequests({
runtime,
instanceId: 'sales-by-manufacturer',
getterRequests: [{ name: 'getOrientation' }],
});
console.log(result.results);Getter names and result schemas are available from visualization metadata. Unsupported operations and getters fail with an explicit error.
Metadata And Registry
Metadata describes visualization types, presets, requirements, vSpec schemas, operations, and getters:
import { getVisualizationMetadata, visualizationMetadata } from '@astrato-io/visualizations/metadata';
console.log(visualizationMetadata);
console.log(getVisualizationMetadata('bar'));Registry helpers are available from the root package:
import {
createDefaultVSpec,
getVisualizationRegistryEntry,
listVisualizationRegistryEntries,
} from '@astrato-io/visualizations';Selection Groups
Instances with the same groupId share SDK selection and filter state. Instances without a group use default.
runtime.createInstance({
id: 'country-filter',
type: 'filter',
groupId: 'dashboard',
dataBinding,
});
runtime.createInstance({
id: 'sales-chart',
type: 'bar',
groupId: 'dashboard',
dataBinding,
});Selection identity is based on engine fieldKey values.
Brushing Without Selecting
brush highlights values across every instance of a group without selecting them: the brushed values keep their
look and every other value takes the chart's inactive style, exactly as a selection is drawn. A brush never becomes a
pending or applied selection, never shows as a filter, and sends no query.
runtime.brush('dashboard', [{ fieldKey: growthGroupFieldKey, values: ['Rocket Ship'] }]);
runtime.clearBrush('dashboard');Each call replaces the group's previous brush. Instances mounted into the group later show the current brush. A chart shows a brush only for fields it has bound as dimensions; a line chart dims by its series dimension, so brushing an axis value does not fade a line.
Runtime State And Persistence
The runtime keeps one current vSpec for each live instance. Chart-originated local changes, including table column widths, update that in-memory vSpec so later operations preserve them. This synchronization belongs to the visualization runtime and does not make the vSpec durable.
Create the SDK client and visualization runtime at application scope, create each visualization instance once, and use
operations after initialization. Do not repeatedly compile definitions or replace the full vSpec with an older host
copy. Unmounting may retain an instance for a later remount; removeInstance, runtime recreation, and page reload
discard its state. The default SDK model save hooks remain no-ops, and the package does not write visualization state
to browser storage.
Package Exports
| Export | Contents |
| ---------------------------------------- | -------------------------------------------------------------------- |
| @astrato-io/visualizations | Runtime, registry, web-component bootstrap, and shared runtime types |
| @astrato-io/visualizations/operations | Runtime-managed and low-level vSpec operation execution |
| @astrato-io/visualizations/getters | Optional vSpec getter execution |
| @astrato-io/visualizations/metadata | Optional serializable visualization metadata |
| @astrato-io/visualizations/definitions | Optional initial-creation schemas and compiler |
| @astrato-io/visualizations/styles.css | Required package styles |
