@astrato-io/visualizations 0.0.0-stage → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/LICENSE +93 -0
  3. package/README.md +843 -2
  4. package/THIRD_PARTY_NOTICES.md +8477 -0
  5. package/VISUALIZATION_DEFINITIONS.md +2378 -0
  6. package/chunks/ClickAwayListener-BbeNMAvj.js +52 -0
  7. package/chunks/LinearProgress-B1FeWaen.js +253 -0
  8. package/chunks/accessors-DemrDevR.js +128 -0
  9. package/chunks/app-filter-bar.compiler-BGJ7HAbr.js +64 -0
  10. package/chunks/bar.compiler-CT0ZbQKc.js +374 -0
  11. package/chunks/builder-B6dWSIdD.js +12289 -0
  12. package/chunks/color-settings-ZIvgNTq1.js +10 -0
  13. package/chunks/coloring-B1XAfg6b.js +664 -0
  14. package/chunks/debounce-fn-CUixuz45.js +7 -0
  15. package/chunks/definitions-D3OVqmXl.js +2102 -0
  16. package/chunks/escobar-eDjtGpkC.js +34606 -0
  17. package/chunks/events-WZut5Ow6.js +4 -0
  18. package/chunks/extend-BKUkc4_i.js +37 -0
  19. package/chunks/filter.compiler-DBsyRBJt.js +70 -0
  20. package/chunks/filtering-tqjAyJH5.js +1072 -0
  21. package/chunks/grouping-type-On4s9Thj.js +3 -0
  22. package/chunks/handlers-VTBfIZYZ.js +13072 -0
  23. package/chunks/isEqual-BDxAhUo8.js +7514 -0
  24. package/chunks/line.compiler-CzdM5Ld0.js +198 -0
  25. package/chunks/metadata-BGElBy5Z.js +28 -0
  26. package/chunks/number.compiler-G-R7y16P.js +53 -0
  27. package/chunks/operations-CTNUX6Sy.js +26249 -0
  28. package/chunks/range-CiwTbMig.js +22 -0
  29. package/chunks/registry-CsQQzukD.js +920 -0
  30. package/chunks/requirements-DYjZCFyf.js +6 -0
  31. package/chunks/rolldown-runtime-DleVn1XH.js +28 -0
  32. package/chunks/set-coloring-datafield-B3uIS1Pe.js +8 -0
  33. package/chunks/shared-CBvcC5YV.js +1218 -0
  34. package/chunks/src-BOS6SqkZ.js +67 -0
  35. package/chunks/src-D9XogOrc.js +422 -0
  36. package/chunks/standalone-0M4HlmDW2.js +3944 -0
  37. package/chunks/standalone-0ashe9w1.js +411 -0
  38. package/chunks/standalone-B7jax8wa.js +339 -0
  39. package/chunks/standalone-BK0VYU-l.js +463 -0
  40. package/chunks/standalone-BMIbT1b2.js +4619 -0
  41. package/chunks/standalone-BZV5vOwA.js +6 -0
  42. package/chunks/standalone-BhDrWj4p.js +1980 -0
  43. package/chunks/standalone-BtSHCp2U2.js +673 -0
  44. package/chunks/standalone-BvaKdky9.js +344 -0
  45. package/chunks/standalone-BwtM31-f.js +2 -0
  46. package/chunks/standalone-C3gg736V.js +1129 -0
  47. package/chunks/standalone-CC1VUjO32.js +299 -0
  48. package/chunks/standalone-C_rYnZ0N.js +741 -0
  49. package/chunks/standalone-CoLrAxRV.js +13198 -0
  50. package/chunks/standalone-DC-aUgXC.js +3855 -0
  51. package/chunks/standalone-DCtRoTWg.js +675 -0
  52. package/chunks/standalone-DUDFTBqL.js +833 -0
  53. package/chunks/standalone-DhZkX9Td.js +2 -0
  54. package/chunks/standalone-DtLElfuM.js +512 -0
  55. package/chunks/standalone-G9Q4Uczu.js +726 -0
  56. package/chunks/standalone-JuLF-OoH.js +1762 -0
  57. package/chunks/standalone-_DeX21JJ.js +383 -0
  58. package/chunks/standalone-gFhKZh8m.js +179 -0
  59. package/chunks/standalone-tDNy-SFH.js +281 -0
  60. package/chunks/standalone-v1oxfU20.js +68 -0
  61. package/chunks/standalone-vzBY6q0I2.js +140 -0
  62. package/chunks/stroke-dash-UgMEcCS4.js +6 -0
  63. package/chunks/table.compiler-tb914oj3.js +222 -0
  64. package/chunks/typeof-CJDtcySz.js +13 -0
  65. package/chunks/ui-DQWtv2iM.js +63551 -0
  66. package/chunks/url-utils-B17gj_mr.js +5501 -0
  67. package/chunks/useFieldName-BvdcoJRl.js +7 -0
  68. package/chunks/utils-Bg7K-t6K.js +32379 -0
  69. package/chunks/view-Bn83C8d9.js +12466 -0
  70. package/definitions.js +3 -0
  71. package/docs/operations/astrato-app-filter-bar.md +161 -0
  72. package/docs/operations/bar.md +6968 -0
  73. package/docs/operations/combo.md +5804 -0
  74. package/docs/operations/date-picker.md +644 -0
  75. package/docs/operations/distribution-gauge.md +3082 -0
  76. package/docs/operations/dot.md +5570 -0
  77. package/docs/operations/filter.md +1483 -0
  78. package/docs/operations/gauge.md +3316 -0
  79. package/docs/operations/heatMap.md +5944 -0
  80. package/docs/operations/histogram.md +1856 -0
  81. package/docs/operations/iframe.md +56 -0
  82. package/docs/operations/image.md +177 -0
  83. package/docs/operations/index.md +46 -0
  84. package/docs/operations/line.md +6391 -0
  85. package/docs/operations/number.md +3903 -0
  86. package/docs/operations/pie.md +5956 -0
  87. package/docs/operations/radar.md +4566 -0
  88. package/docs/operations/sankey.md +3088 -0
  89. package/docs/operations/scatter.md +5969 -0
  90. package/docs/operations/shape.md +315 -0
  91. package/docs/operations/simpleTextObject.md +699 -0
  92. package/docs/operations/slider.md +220 -0
  93. package/docs/operations/table.md +8800 -0
  94. package/docs/operations/tree.md +2867 -0
  95. package/docs/operations/treemap.md +3527 -0
  96. package/docs/operations/waterfall.md +2928 -0
  97. package/docs/operations/word-cloud.md +3000 -0
  98. package/getters.js +18 -0
  99. package/index.js +1183 -0
  100. package/metadata.js +20 -0
  101. package/operations.js +64 -0
  102. package/package.json +185 -4
  103. package/src/definitions/index.d.ts +2 -0
  104. package/src/definitions/model/definitions/available-fields.d.ts +35 -0
  105. package/src/definitions/model/definitions/compilers/compile.d.ts +7 -0
  106. package/src/definitions/model/definitions/data-item-resolution.d.ts +19 -0
  107. package/src/definitions/model/definitions/descriptors/descriptor-metadata.d.ts +1341 -0
  108. package/src/definitions/model/definitions/descriptors/json-schema.d.ts +2 -0
  109. package/src/definitions/model/definitions/diagnostics.d.ts +5 -0
  110. package/src/definitions/model/definitions/field-suggestions.d.ts +2 -0
  111. package/src/definitions/model/definitions/index.d.ts +23 -0
  112. package/src/definitions/model/definitions/registry/compile.d.ts +2 -0
  113. package/src/definitions/model/definitions/registry/descriptor-registry.d.ts +3 -0
  114. package/src/definitions/model/definitions/schema-registry.d.ts +660 -0
  115. package/src/definitions/model/definitions/schemas/app-filter-bar.schema.d.ts +22 -0
  116. package/src/definitions/model/definitions/schemas/bar.schema.d.ts +73 -0
  117. package/src/definitions/model/definitions/schemas/broad-chart.schema.d.ts +68 -0
  118. package/src/definitions/model/definitions/schemas/common.schema.d.ts +303 -0
  119. package/src/definitions/model/definitions/schemas/filter.schema.d.ts +22 -0
  120. package/src/definitions/model/definitions/schemas/index.d.ts +9 -0
  121. package/src/definitions/model/definitions/schemas/line.schema.d.ts +72 -0
  122. package/src/definitions/model/definitions/schemas/number.schema.d.ts +21 -0
  123. package/src/definitions/model/definitions/schemas/object.schema.d.ts +215 -0
  124. package/src/definitions/model/definitions/schemas/table.schema.d.ts +458 -0
  125. package/src/definitions/model/definitions/types/compilation.types.d.ts +36 -0
  126. package/src/definitions/model/definitions/types/definition.types.d.ts +2 -0
  127. package/src/definitions/model/definitions/types/descriptor.types.d.ts +39 -0
  128. package/src/definitions/model/definitions/types/index.d.ts +3 -0
  129. package/src/definitions/model/definitions/validation.d.ts +3 -0
  130. package/src/getters/index.d.ts +30 -0
  131. package/src/index.d.ts +4 -0
  132. package/src/metadata/index.d.ts +4 -0
  133. package/src/operations/index.d.ts +30 -0
  134. package/src/registry/index.d.ts +6 -0
  135. package/src/runtime/index.d.ts +2 -0
  136. package/src/types.d.ts +307 -0
  137. package/src/visualization-catalog.d.ts +82 -0
  138. package/src/web-components/index.d.ts +12 -0
  139. package/styles.css +2 -0
package/README.md CHANGED
@@ -1,3 +1,844 @@
1
- # Temporary Holding Version
1
+ # @astrato-io/visualizations
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Framework-neutral browser package for rendering Astrato visualizations in custom applications.
4
+
5
+ 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`.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @astrato-io/sdk @astrato-io/visualizations
11
+ ```
12
+
13
+ RxJS and Zod are installed as runtime dependencies.
14
+
15
+ Import the package stylesheet once in the application:
16
+
17
+ ```ts
18
+ import '@astrato-io/visualizations/styles.css';
19
+ ```
20
+
21
+ ## Supported Visualizations
22
+
23
+ Use these exact visualization IDs:
24
+
25
+ | ID | Family |
26
+ | ------------------------ | ------ |
27
+ | `bar` | Chart |
28
+ | `combo` | Chart |
29
+ | `distribution-gauge` | Chart |
30
+ | `dot` | Chart |
31
+ | `gauge` | Chart |
32
+ | `heatMap` | Chart |
33
+ | `histogram` | Chart |
34
+ | `line` | Chart |
35
+ | `pie` | Chart |
36
+ | `radar` | Chart |
37
+ | `sankey` | Chart |
38
+ | `scatter` | Chart |
39
+ | `table` | Chart |
40
+ | `tree` | Chart |
41
+ | `treemap` | Chart |
42
+ | `waterfall` | Chart |
43
+ | `word-cloud` | Chart |
44
+ | `date-picker` | Filter |
45
+ | `astrato-app-filter-bar` | Filter |
46
+ | `filter` | Filter |
47
+ | `slider` | Filter |
48
+ | `iframe` | Object |
49
+ | `image` | Object |
50
+ | `number` | Object |
51
+ | `shape` | Object |
52
+ | `simpleTextObject` | Object |
53
+
54
+ Astrato-specific panels are not part of this package.
55
+
56
+ Implementations are loaded lazily when an instance is mounted.
57
+
58
+ ## Quick Start
59
+
60
+ Create one SDK client and one visualization runtime for the host application:
61
+
62
+ ```ts
63
+ import { createAstratoClient } from '@astrato-io/sdk';
64
+ import { createVisualizationRuntime, registerAstratoElements, registry } from '@astrato-io/visualizations';
65
+ import '@astrato-io/visualizations/styles.css';
66
+
67
+ const client = createAstratoClient({
68
+ baseUrl: 'https://your-astrato-instance.example/',
69
+ tenantId: 'tenant-id',
70
+ auth: { token: 'personal-access-token' },
71
+ visualizationEngine: true,
72
+ });
73
+
74
+ const runtime = createVisualizationRuntime({
75
+ client,
76
+ registry,
77
+ });
78
+
79
+ runtime.createInstance({
80
+ id: 'sales-by-manufacturer',
81
+ type: 'bar',
82
+ groupId: 'dashboard',
83
+ dataBinding: {
84
+ tenantId: 'tenant-id',
85
+ semanticLayerId: 'semantic-layer-id',
86
+ datasourceType: 'snowflake',
87
+ },
88
+ });
89
+
90
+ registerAstratoElements({ runtime });
91
+ ```
92
+
93
+ Add the visualization to the page:
94
+
95
+ ```html
96
+ <astrato-visualization instance-id="sales-by-manufacturer"></astrato-visualization>
97
+ ```
98
+
99
+ Every mount container must have non-zero dimensions. The visualization measures its own element and fills the available space:
100
+
101
+ ```css
102
+ astrato-visualization {
103
+ display: block;
104
+ width: 100%;
105
+ height: 400px;
106
+ }
107
+ ```
108
+
109
+ ## App Filter Bar
110
+
111
+ `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.
112
+
113
+ Create it in the same selection group and with the same data binding as the charts it controls:
114
+
115
+ ```ts
116
+ const chart = runtime.getInstance('sales-by-manufacturer');
117
+ runtime.createInstance({
118
+ id: 'dashboard-filters',
119
+ type: 'astrato-app-filter-bar',
120
+ groupId: chart.groupId,
121
+ dataBinding: chart.dataBinding,
122
+ });
123
+ ```
124
+
125
+ ```html
126
+ <astrato-visualization class="app-filters" instance-id="dashboard-filters"></astrato-visualization>
127
+ ```
128
+
129
+ ```css
130
+ .app-filters {
131
+ display: block;
132
+ width: 100%;
133
+ height: auto;
134
+ }
135
+ ```
136
+
137
+ 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.
138
+
139
+ 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.
140
+
141
+ Settings live in `vSpec.appFilterBarSettings` and are also available through [operations and getters](./docs/operations/astrato-app-filter-bar.md):
142
+
143
+ | Setting | Default | Meaning |
144
+ | ------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------- |
145
+ | `overflow` | `"wrap"` | Wrap chips, collapse behind **+N**, use a **Filters (N)** modal button, or switch responsively between these modes. |
146
+ | `orientation` | `horizontal` | Horizontal bar or vertical sidebar with stacked filter rows. |
147
+ | `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. |
148
+ | `enableAddingNewFilters` | `true` | Show the field picker. |
149
+ | `enableAssociativeFilter` | `true` | Allow associative filter creation when the runtime supports it and eligible fields exist. Set `false` to hide creation. |
150
+ | `showClearAll` | `true` | Show the group-wide clear action. |
151
+ | `showChartBreakdown` | `false` | Opt in to **Chart breakdown** for apps using dimension groups. Only shown when a group has at least two available fields. |
152
+ | `selectableFieldKeys` | unrestricted | Limit fields available for creating filters. Omitted or empty means all fields; existing selections are always visible. |
153
+
154
+ 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.
155
+
156
+ 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.
157
+
158
+ 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.
159
+
160
+ 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.
161
+
162
+ 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.
163
+
164
+ 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.
165
+
166
+ ### Wrapping Above the Charts
167
+
168
+ 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.
169
+
170
+ ```html
171
+ <section class="dashboard-filter-section">
172
+ <astrato-visualization class="app-filters" instance-id="dashboard-filters"></astrato-visualization>
173
+ </section>
174
+ <section class="dashboard-charts">…</section>
175
+ ```
176
+
177
+ ```css
178
+ .dashboard-filter-section {
179
+ position: static;
180
+ flex: 0 0 auto;
181
+ }
182
+ .dashboard-filter-section .app-filters {
183
+ display: block;
184
+ width: 100%;
185
+ height: auto;
186
+ }
187
+ .dashboard-charts {
188
+ margin-top: 12px;
189
+ }
190
+ ```
191
+
192
+ 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.
193
+
194
+ ### Header Overflow
195
+
196
+ 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.
197
+
198
+ 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).
199
+
200
+ `"collapse"` keeps the action buttons on the row and uses `--afb-height` as the fixed bar height. For a 40px header:
201
+
202
+ ```css
203
+ .app-filters {
204
+ display: block;
205
+ width: 100%;
206
+ height: 40px;
207
+ --afb-height: 40px;
208
+ --afb-padding: 3px 8px;
209
+ --afb-control-height: 32px;
210
+ }
211
+ ```
212
+
213
+ 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.
214
+
215
+ 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.
216
+
217
+ 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.
218
+
219
+ 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.
220
+
221
+ | Token | Default / fallback | Applies to |
222
+ | ----------------------------- | ------------------------------------------------------- | -------------------------------------------- |
223
+ | `--afb-overflow-surface` | `--afb-chip-surface`, `#f4f4f5` | **+N** and **Filters (N)** backgrounds |
224
+ | `--afb-overflow-text` | `--afb-text`, `#27272a` | Control text and icons |
225
+ | `--afb-overflow-border` | `--afb-chip-border`, `#e4e4e7` | Control borders |
226
+ | `--afb-overflow-radius` | `--afb-chip-radius`, `8px` | Control corners |
227
+ | `--afb-overflow-hover` | `--afb-hover`, `#f4f4f5` | Hovered controls |
228
+ | `--afb-overflow-open-surface` | `--afb-selected-surface`, then `--afb-hover`, `#f4f4f5` | Controls with an open list or modal |
229
+ | `--afb-overflow-badge-size` | `6px` | Pending badge; color uses `--afb-pending` |
230
+ | `--afb-overflow-list-height` | `--afb-list-height`, `260px` | Scrolling filter list |
231
+ | `--afb-modal-width` | `560px` | Modal width, constrained to the viewport |
232
+ | `--afb-modal-backdrop` | `#0006` | Modal backdrop |
233
+ | `--afb-scrollbar-thumb` | `--afb-border`, `#e4e4e7` | Overflow list, popover, and modal scrollbars |
234
+ | `--afb-scrollbar-track` | `transparent` | Scrolling surface tracks |
235
+ | `--afb-button-breakpoint` | `480px` | Responsive transition to button |
236
+ | `--afb-collapse-breakpoint` | `960px` | Responsive transition from wrap to collapse |
237
+
238
+ 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`.
239
+
240
+ ```css
241
+ .app-filter-host {
242
+ --afb-trigger-surface: #2563eb;
243
+ --afb-trigger-text: #fff;
244
+ --afb-trigger-hover: #1d4ed8;
245
+ --afb-trigger-open-surface: #1e40af;
246
+ --afb-trigger-border: #2563eb;
247
+ --afb-trigger-radius: 8px;
248
+ --afb-trigger-height: 40px;
249
+ --afb-trigger-padding: 8px 16px;
250
+ --afb-trigger-align: flex-start;
251
+ }
252
+ ```
253
+
254
+ 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.
255
+
256
+ ### Vertical Sidebar
257
+
258
+ Set `settings.orientation` to `"vertical"` in the visualization definition (or use `setAppFilterBarOrientation` at runtime):
259
+
260
+ ```json
261
+ {
262
+ "version": 1,
263
+ "type": "astrato-app-filter-bar",
264
+ "settings": { "orientation": "vertical", "selectionMode": "manual" }
265
+ }
266
+ ```
267
+
268
+ 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.
269
+
270
+ 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.
271
+
272
+ ### Styling the Bar and Its Editors
273
+
274
+ 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.
275
+
276
+ ```css
277
+ .app-filters {
278
+ --afb-font-family: Inter, system-ui, sans-serif;
279
+ --afb-font-size: 13px;
280
+ --afb-surface: #ffffff;
281
+ --afb-text: #172554;
282
+ --afb-muted: #64748b;
283
+ --afb-border: #dbeafe;
284
+ --afb-accent: #2563eb;
285
+ --afb-on-accent: #ffffff;
286
+ --afb-focus: #60a5fa;
287
+ --afb-chip-surface: #eff6ff;
288
+ --afb-chip-border: #bfdbfe;
289
+ --afb-radius: 12px;
290
+ --afb-control-radius: 8px;
291
+ --afb-popup-width: 400px;
292
+ }
293
+
294
+ .dark-app .app-filters {
295
+ --afb-surface: #18181b;
296
+ --afb-popup-surface: #27272a;
297
+ --afb-control-surface: #27272a;
298
+ --afb-text: #fafafa;
299
+ --afb-muted: #a1a1aa;
300
+ --afb-border: #3f3f46;
301
+ --afb-hover: #3f3f46;
302
+ --afb-chip-surface: #27272a;
303
+ --afb-chip-border: #52525b;
304
+ --afb-selected-surface: #3f3f46;
305
+ }
306
+ ```
307
+
308
+ 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).
309
+
310
+ 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.
311
+
312
+ 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.
313
+
314
+ 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.
315
+
316
+ 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.
317
+
318
+ 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.
319
+
320
+ 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`.
321
+
322
+ ## Visualization Instances
323
+
324
+ The runtime owns visualization configuration and lifecycle. A `VisualizationInstance` contains:
325
+
326
+ | Property | Purpose |
327
+ | ------------- | ------------------------------------------------------------- |
328
+ | `id` | Stable runtime identifier |
329
+ | `type` | Visualization ID from the registry |
330
+ | `vSpec` | Visualization configuration |
331
+ | `preset` | Optional preset used during initialization |
332
+ | `groupId` | Selection and filter interaction scope |
333
+ | `dataBinding` | Astrato tenant, semantic layer, datasource, and query context |
334
+
335
+ 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.
336
+
337
+ The runtime exposes:
338
+
339
+ ```ts
340
+ runtime.createInstance(instance);
341
+ runtime.updateInstance(instanceId, update);
342
+ runtime.getInstance(instanceId);
343
+ runtime.listInstances();
344
+ runtime.removeInstance(instanceId);
345
+ runtime.validateInstanceRequirements(instanceId);
346
+ ```
347
+
348
+ ## Creating A Configured Visualization
349
+
350
+ Use a `VisualizationDefinition` to create the initial vSpec from exact engine `fieldKey` values. Each supported type has
351
+ a strict schema; see the [Visualization Definition Reference](./VISUALIZATION_DEFINITIONS.md) for its fields,
352
+ constraints, and example. The Markdown reference is also available through the package export
353
+ `@astrato-io/visualizations/VISUALIZATION_DEFINITIONS.md`.
354
+
355
+ ```ts
356
+ import {
357
+ compileVisualizationDefinition,
358
+ createVisualizationAvailableFields,
359
+ type VisualizationDefinition,
360
+ } from '@astrato-io/visualizations/definitions';
361
+ import { registry } from '@astrato-io/visualizations';
362
+
363
+ const metadata = await client.hostServices.engine.fields.getFieldMetadata?.({
364
+ tenantId: 'tenant-id',
365
+ semanticLayerId: 'semantic-layer-id',
366
+ datasourceType: 'snowflake',
367
+ });
368
+ const availableFields = createVisualizationAvailableFields(metadata ?? {});
369
+
370
+ const definition: Extract<VisualizationDefinition, { type: 'bar' }> = {
371
+ version: 1,
372
+ type: 'bar',
373
+ data: {
374
+ dimensions: [{ ref: 'manufacturer', fieldKey: '[Cars].[Manufacturer]' }],
375
+ measures: [
376
+ {
377
+ ref: 'sales',
378
+ fieldKey: '[Cars].[Sales In Thousands]',
379
+ aggregation: 'SUM',
380
+ },
381
+ ],
382
+ },
383
+ sorting: [{ by: 'sales', direction: 'desc' }],
384
+ };
385
+
386
+ const compilation = await compileVisualizationDefinition({
387
+ definition,
388
+ availableFields,
389
+ baseVSpec: registry.bar.createDefaultVSpec(),
390
+ });
391
+
392
+ if (!compilation.success) {
393
+ throw new Error(JSON.stringify(compilation.issues));
394
+ }
395
+
396
+ runtime.createInstance({
397
+ id: 'sales-by-manufacturer',
398
+ type: 'bar',
399
+ vSpec: compilation.vSpec,
400
+ dataBinding: {
401
+ tenantId: 'tenant-id',
402
+ semanticLayerId: 'semantic-layer-id',
403
+ datasourceType: 'snowflake',
404
+ },
405
+ });
406
+ ```
407
+
408
+ Compilation requires a fresh default or preset vSpec and returns either a complete vSpec or structured issues. The
409
+ caller must supply fields compatible with the current semantic-layer context. Raw fields used as measures require an
410
+ aggregation; semantic measures and calculations keep their engine-defined behavior.
411
+
412
+ Use `getVisualizationDefinitionDescriptor(type)` for the selected type's JSON Schema, documentation, examples, and
413
+ feature metadata. `listVisualizationDefinitionDescriptors()` returns discovery metadata for all supported types. AI
414
+ integrations should give the selected descriptor and compatible field catalog to the model, compile its response, and
415
+ return structured issues for correction.
416
+
417
+ A definition covers a subset of each type's operations. To set anything else, apply operations to `compilation.vSpec`
418
+ with `applyOperations` in one batch before calling `createInstance`; see
419
+ [Transforming A vSpec Without A Runtime](#transforming-a-vspec-without-a-runtime). Do not finish a chart by applying
420
+ operations one at a time after it is mounted: each vSpec change on a mounted instance can trigger a new data request.
421
+
422
+ VSpec-only instance updates are applied to connected elements in place. Changes to the visualization type, preset,
423
+ selection group, or data binding remount connected web components. Removing an instance unmounts it and discards its
424
+ in-memory state.
425
+
426
+ ## Web Components
427
+
428
+ `registerAstratoElements()` registers `astrato-visualization` by default. Registration is idempotent for a given custom-element registry.
429
+
430
+ ```ts
431
+ registerAstratoElements({ runtime });
432
+ ```
433
+
434
+ A custom tag name can be used when required by the host application:
435
+
436
+ ```ts
437
+ registerAstratoElements({
438
+ runtime,
439
+ tagName: 'customer-visualization',
440
+ });
441
+ ```
442
+
443
+ The element can also receive a runtime and instance ID through JavaScript:
444
+
445
+ ```ts
446
+ import type { AstratoVisualizationElement } from '@astrato-io/visualizations';
447
+
448
+ const element = document.createElement('astrato-visualization') as AstratoVisualizationElement;
449
+
450
+ element.runtime = runtime;
451
+ element.instanceId = 'sales-by-manufacturer';
452
+ container.appendChild(element);
453
+
454
+ const renderResult = await element.ready;
455
+ console.log(renderResult?.status);
456
+ ```
457
+
458
+ The element mounts when connected, applies vSpec-only runtime changes in place, remounts when `instance-id` or
459
+ structural runtime instance state changes, and unmounts when disconnected.
460
+ `element.ready` uses the same render deadline as direct mounting: it resolves to `rendered`, `error`, `unmounted`, or
461
+ `timeout`.
462
+
463
+ ## Direct Mounting
464
+
465
+ Applications that own DOM lifecycle directly can mount without the public web component:
466
+
467
+ ```ts
468
+ const container = document.getElementById('chart-slot');
469
+
470
+ if (!container) {
471
+ throw new Error('Missing chart container.');
472
+ }
473
+
474
+ const handle = await runtime.mount(container, 'sales-by-manufacturer');
475
+
476
+ console.log(handle.status);
477
+ console.log(handle.requirements);
478
+
479
+ const renderResult = await handle.ready;
480
+ console.log(renderResult.status);
481
+
482
+ handle.unmount();
483
+ ```
484
+
485
+ Direct mounting and web-component mounting use the same runtime, instance state, SDK client, and visualization implementation.
486
+ `runtime.mount()` reports structural mounting through `handle.status`; `handle.ready` is the separate render-completion signal.
487
+
488
+ Readiness waits for the chart's existing render-completion or error event, with a default deadline of **40 seconds**.
489
+ Set `readyTimeoutMs` when creating the runtime to customize that deadline for all direct and web-component mounts:
490
+
491
+ ```ts
492
+ const runtime = createVisualizationRuntime({
493
+ client,
494
+ registry,
495
+ readyTimeoutMs: 60_000, // optional; defaults to 40_000
496
+ });
497
+ ```
498
+
499
+ The value must be an integer from 1 to 2,147,483,647 milliseconds. The deadline starts when chart initialization begins,
500
+ after metadata preloading and lazy module loading; it does not bound those preparatory steps of `runtime.mount()`.
501
+ If no terminal event arrives in time, readiness resolves with `{ instanceId, status: 'timeout' }`. The chart remains
502
+ mounted and may finish rendering later, but that readiness result stays `timeout`. Check for `status === 'rendered'`
503
+ before treating a chart as ready. Unmounting before the deadline resolves readiness as `unmounted`.
504
+
505
+ ## Fonts And Icons
506
+
507
+ Charts draw axis labels and legends on `<canvas>`, which only uses fonts that have finished loading. The runtime picks
508
+ the chart font for you, so no host CSS is needed:
509
+
510
+ 1. `createVisualizationRuntime({ theme: { fontFamily } })`, when set.
511
+ 2. Otherwise the host page's font: `--astrato-font-family`, then `--font-family` (both read from `:root`), then the
512
+ `font-family` of `body`.
513
+
514
+ The runtime adds `system-ui, sans-serif` when the selected stack has no sans-serif fallback. It preserves your font
515
+ choices, including an explicitly selected serif family. Declare your webfont (for example with a `<link>` or
516
+ `@font-face`) before calling `runtime.mount()`. The runtime waits up to 3 seconds for the first registered webfont
517
+ in the stack, skipping unavailable families. Mounts in the same document share that preparation. The initial load
518
+ uses Latin sample text; other scripts and weights may load when chart text first uses them. Mounted charts redraw
519
+ when a face in their selected stack finishes loading, including after the initial wait times out. Unmounting removes
520
+ the font listener.
521
+
522
+ ```ts
523
+ const runtime = createVisualizationRuntime({
524
+ client,
525
+ registry,
526
+ theme: { fontFamily: 'Inter' }, // optional; defaults to the page font
527
+ });
528
+ ```
529
+
530
+ All supported table indicator icons render as inline SVG through the shared icon catalog, including legacy aliases.
531
+ They need no icon font and are not affected by host `font-family` rules. The package includes the Material Icons
532
+ license in `THIRD_PARTY_NOTICES.md`.
533
+
534
+ ## Exporting A Chart's Data
535
+
536
+ A mounted chart exposes the query it runs, so an app can offer show-as-table or CSV/Excel export that matches the
537
+ chart. The query is the chart's own: it keeps Top N, sorting, grouping and the chart's filters, without pagination.
538
+ It runs through the same data service as the chart, so the group's selections apply to it the same way.
539
+
540
+ ```ts
541
+ const handle = await runtime.mount(container, 'sales-by-manufacturer');
542
+ await handle.ready;
543
+
544
+ const exportQuery = await handle.getExportQuery(); // { instanceId, query, config } or null
545
+ const result = await handle.getData({ limit: 5000 }); // { instanceId, query, config, data } or null
546
+ ```
547
+
548
+ `getData()` runs the export query through the same `engine.getData` or `data.query` service the chart uses, and `data`
549
+ is that service's response. `config` carries the chart's export settings (`limit`, `nullAs`, `csvFormatValues`, and
550
+ `pivotConfig` for pivot tables). The row limit defaults to the chart's export limit, or 1,000, and is capped at 100,000.
551
+
552
+ Both resolve to `null` when the chart has nothing to export, for example when its requirements are not met, and when
553
+ the instance is unmounted. Call them after `handle.ready`: while the chart is fetching, they wait for that fetch's query.
554
+ Apps using the web component can call `runtime.getExportQuery(instanceId)` and `runtime.getData(instanceId, options)`.
555
+
556
+ ## Updating vSpec With Operations
557
+
558
+ The [Operations Reference](./docs/operations/index.md) lists the complete standalone operation catalog by visualization
559
+ type, with descriptions and payload schemas. It is generated from the same manifests as runtime metadata and shipped
560
+ inside this package. Humans and agents can read `node_modules/@astrato-io/visualizations/docs/operations/index.md` without
561
+ loading a chart or importing browser JavaScript. Use the bundled reference to match your installed package version.
562
+ Enum values are part of each payload schema. Breaking changes per version are in [CHANGELOG.md](./CHANGELOG.md).
563
+
564
+ Use a visualization definition once to create the initial configured vSpec. After `runtime.createInstance()`, keep that
565
+ runtime instance and use operations for incremental changes. Do not repeatedly compile definitions or replace the
566
+ complete vSpec with a host-side copy: that can discard chart-originated state such as table column widths and sorting.
567
+
568
+ ### Choosing An Operations API
569
+
570
+ The operations package exposes two levels of API:
571
+
572
+ | API | State ownership | Concurrency protection | Recommended use |
573
+ | ------------------------ | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- |
574
+ | `applyRuntimeOperations` | Reads and updates a runtime instance | Serializes calls per instance and retries after concurrent local vSpec changes | Normal SDK and browser integrations |
575
+ | `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 |
576
+
577
+ Use `applyRuntimeOperations` unless the application deliberately owns vSpec state without the visualization runtime.
578
+
579
+ ### Applying Operations To A Runtime Instance
580
+
581
+ `applyRuntimeOperations` reads the latest runtime-owned vSpec, applies the requests, and writes the resulting vSpec back
582
+ to the same instance. If the instance is mounted, the connected visualization receives the update in place. Operations
583
+ also work while the instance is unmounted; a later mount uses the updated runtime vSpec.
584
+
585
+ ```ts
586
+ import { applyRuntimeOperations } from '@astrato-io/visualizations/operations';
587
+
588
+ const result = await applyRuntimeOperations({
589
+ runtime,
590
+ instanceId: 'sales-by-manufacturer',
591
+ availableFields,
592
+ operationRequests: [
593
+ {
594
+ id: 'add-dimension',
595
+ name: 'addDimension',
596
+ payload: { key: '[Cars].[Manufacturer]' },
597
+ },
598
+ {
599
+ id: 'add-measure',
600
+ name: 'addMeasure',
601
+ payload: {
602
+ aggregation: 'SUM',
603
+ key: '[Cars].[Sales In Thousands]',
604
+ },
605
+ },
606
+ ],
607
+ });
608
+
609
+ console.log(result.results);
610
+ ```
611
+
612
+ For one runtime instance, this API:
613
+
614
+ - serializes separate calls so that concurrent callers cannot overwrite each other's results;
615
+ - starts every call from the latest runtime vSpec;
616
+ - retries if the chart changes its local vSpec, for example after sorting or resizing a table column, while an operation
617
+ is running; and
618
+ - rejects a stale result if the instance is removed, recreated, or structurally replaced while work is pending.
619
+
620
+ Calls for different instances are independent and may run concurrently.
621
+
622
+ Supply `availableFields` for operations that add or replace dimensions, measures, calculations, or raw fields. The
623
+ catalog must belong to the same semantic-layer context as the instance. Operations that only modify existing visual
624
+ properties may not require it. Most integrations do not need the advanced `variables`, `vTheme`, `changesQueue`, or
625
+ custom `registry` inputs.
626
+
627
+ ### Batches, Results, And Errors
628
+
629
+ `operationRequests` is a batch. Requests in one batch execute sequentially, and each request sees the successful vSpec
630
+ changes produced by earlier requests in that batch. A batch is not transactional: one request may fail while later
631
+ requests continue. Provide a stable `id` for every request so its response is easy to correlate. If it is omitted, the
632
+ operations engine generates an ID and returns it in the result.
633
+
634
+ The returned `results` object contains one result per request ID, while `vSpec` contains the final transformed vSpec.
635
+ Invalid payloads and operation-specific failures are normally reported as `{ success: false, error: ... }` entries.
636
+ The returned promise rejects for call-level failures, such as an unknown instance, an unsupported operation name, a
637
+ missing source vSpec, or an instance lifecycle change that makes the pending result stale.
638
+
639
+ Use visualization metadata to discover operation names and payload schemas instead of importing internal Astrato
640
+ operation packages:
641
+
642
+ ```ts
643
+ import { getVisualizationMetadata } from '@astrato-io/visualizations/metadata';
644
+
645
+ const tableOperations = getVisualizationMetadata('table').operations;
646
+
647
+ console.log(tableOperations.names);
648
+ console.log(tableOperations.items);
649
+ ```
650
+
651
+ ### Transforming A vSpec Without A Runtime
652
+
653
+ `applyOperations` is the lower-level, state-independent operation engine. It accepts an explicit instance and returns a
654
+ new vSpec without updating a runtime, mounted element, or external store:
655
+
656
+ ```ts
657
+ import { applyOperations } from '@astrato-io/visualizations/operations';
658
+
659
+ const transformed = await applyOperations({
660
+ instance,
661
+ availableFields,
662
+ operationRequests: [
663
+ {
664
+ id: 'add-manufacturer',
665
+ name: 'addDimension',
666
+ payload: { key: '[Cars].[Manufacturer]' },
667
+ },
668
+ ],
669
+ });
670
+
671
+ console.log(transformed.vSpec);
672
+ console.log(transformed.results);
673
+ ```
674
+
675
+ This is the supported way to finish a compiled `VisualizationDefinition` before mounting: pass
676
+ `{ id, type, vSpec: compilation.vSpec }` as the instance and use `compilation.resolvedRefs` for the data-item identity of
677
+ each definition `ref`. It runs in memory and makes no engine requests. It is also useful when validating a vSpec,
678
+ running operations in tests, or building an adapter with intentional custom state ownership. It does not serialize separate calls or protect against a
679
+ newer runtime/chart vSpec. Avoid applying its result to a live runtime from an older instance snapshot; use
680
+ `applyRuntimeOperations` for that case.
681
+
682
+ ### Table Column Operations
683
+
684
+ For tables, create the configured table once and then keep the same runtime instance while adding, removing, or
685
+ reordering columns:
686
+
687
+ ```ts
688
+ await applyRuntimeOperations({
689
+ runtime,
690
+ instanceId: 'sales-table',
691
+ availableFields,
692
+ operationRequests: [
693
+ {
694
+ id: 'add-sales-column',
695
+ name: 'addColumn',
696
+ payload: {
697
+ columnType: 'measure',
698
+ sourceKind: 'field',
699
+ key: '[Cars].[Sales In Thousands]',
700
+ aggregation: 'SUM',
701
+ },
702
+ },
703
+ ],
704
+ });
705
+
706
+ await applyRuntimeOperations({
707
+ runtime,
708
+ instanceId: 'sales-table',
709
+ operationRequests: [
710
+ {
711
+ id: 'remove-manufacturer-column',
712
+ name: 'removeColumn',
713
+ payload: {
714
+ columnType: 'dimension',
715
+ key: '[Cars].[Manufacturer]',
716
+ },
717
+ },
718
+ ],
719
+ });
720
+
721
+ await applyRuntimeOperations({
722
+ runtime,
723
+ instanceId: 'sales-table',
724
+ operationRequests: [
725
+ {
726
+ id: 'move-model-column',
727
+ name: 'reorderColumn',
728
+ payload: {
729
+ source: { columnType: 'dimension', key: '[Cars].[Model]' },
730
+ target: { columnType: 'dimension', key: '[Cars].[Manufacturer]' },
731
+ },
732
+ },
733
+ ],
734
+ });
735
+ ```
736
+
737
+ Engine-key selectors use `match: 'only'` by default: they match exactly one column and fail rather than guessing when
738
+ duplicates exist. Table `removeColumn` also accepts `match: 'all'` for intentional bulk removal. Raw field measures
739
+ include their aggregation; predefined measures and calculations include `sourceKind`. Pivot dimensions may include
740
+ `topDimension`. The existing internal-key selector remains available for integrations that already have exact vSpec
741
+ identifiers.
742
+
743
+ Operations are called against the runtime or an explicit instance, not against the `astrato-visualization` element.
744
+ Do not use the internal Astrato `VISUALIZATION_OPERATIONS_REQUESTED` event as a standalone integration API; it is tied
745
+ to Astrato's wrapper transport and persistence lifecycle. Similarly, reserve `runtime.updateInstance(instanceId,
746
+ { vSpec })` for an intentional complete vSpec replacement, not ordinary incremental changes.
747
+
748
+ ## Reading vSpec With Getters
749
+
750
+ ```ts
751
+ import { processRuntimeGetterRequests } from '@astrato-io/visualizations/getters';
752
+
753
+ const result = processRuntimeGetterRequests({
754
+ runtime,
755
+ instanceId: 'sales-by-manufacturer',
756
+ getterRequests: [{ name: 'getOrientation' }],
757
+ });
758
+
759
+ console.log(result.results);
760
+ ```
761
+
762
+ Getter names and result schemas are available from visualization metadata. Unsupported operations and getters fail with an explicit error.
763
+
764
+ ## Metadata And Registry
765
+
766
+ Metadata describes visualization types, presets, requirements, vSpec schemas, operations, and getters:
767
+
768
+ ```ts
769
+ import { getVisualizationMetadata, visualizationMetadata } from '@astrato-io/visualizations/metadata';
770
+
771
+ console.log(visualizationMetadata);
772
+ console.log(getVisualizationMetadata('bar'));
773
+ ```
774
+
775
+ Registry helpers are available from the root package:
776
+
777
+ ```ts
778
+ import {
779
+ createDefaultVSpec,
780
+ getVisualizationRegistryEntry,
781
+ listVisualizationRegistryEntries,
782
+ } from '@astrato-io/visualizations';
783
+ ```
784
+
785
+ ## Selection Groups
786
+
787
+ Instances with the same `groupId` share SDK selection and filter state. Instances without a group use `default`.
788
+
789
+ ```ts
790
+ runtime.createInstance({
791
+ id: 'country-filter',
792
+ type: 'filter',
793
+ groupId: 'dashboard',
794
+ dataBinding,
795
+ });
796
+
797
+ runtime.createInstance({
798
+ id: 'sales-chart',
799
+ type: 'bar',
800
+ groupId: 'dashboard',
801
+ dataBinding,
802
+ });
803
+ ```
804
+
805
+ Selection identity is based on engine `fieldKey` values.
806
+
807
+ ### Brushing Without Selecting
808
+
809
+ `brush` highlights values across every instance of a group without selecting them: the brushed values keep their
810
+ look and every other value takes the chart's inactive style, exactly as a selection is drawn. A brush never becomes a
811
+ pending or applied selection, never shows as a filter, and sends no query.
812
+
813
+ ```ts
814
+ runtime.brush('dashboard', [{ fieldKey: growthGroupFieldKey, values: ['Rocket Ship'] }]);
815
+
816
+ runtime.clearBrush('dashboard');
817
+ ```
818
+
819
+ Each call replaces the group's previous brush. Instances mounted into the group later show the current brush. A chart
820
+ shows a brush only for fields it has bound as dimensions; a line chart dims by its series dimension, so brushing an
821
+ axis value does not fade a line.
822
+
823
+ ## Runtime State And Persistence
824
+
825
+ The runtime keeps one current vSpec for each live instance. Chart-originated local changes, including table column
826
+ widths, update that in-memory vSpec so later operations preserve them. This synchronization belongs to the
827
+ visualization runtime and does not make the vSpec durable.
828
+
829
+ Create the SDK client and visualization runtime at application scope, create each visualization instance once, and use
830
+ operations after initialization. Do not repeatedly compile definitions or replace the full vSpec with an older host
831
+ copy. Unmounting may retain an instance for a later remount; `removeInstance`, runtime recreation, and page reload
832
+ discard its state. The default SDK model save hooks remain no-ops, and the package does not write visualization state
833
+ to browser storage.
834
+
835
+ ## Package Exports
836
+
837
+ | Export | Contents |
838
+ | ---------------------------------------- | -------------------------------------------------------------------- |
839
+ | `@astrato-io/visualizations` | Runtime, registry, web-component bootstrap, and shared runtime types |
840
+ | `@astrato-io/visualizations/operations` | Runtime-managed and low-level vSpec operation execution |
841
+ | `@astrato-io/visualizations/getters` | Optional vSpec getter execution |
842
+ | `@astrato-io/visualizations/metadata` | Optional serializable visualization metadata |
843
+ | `@astrato-io/visualizations/definitions` | Optional initial-creation schemas and compiler |
844
+ | `@astrato-io/visualizations/styles.css` | Required package styles |