@malloydata/render 0.0.422 → 0.0.424
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.
- package/dist/module/index.mjs +29991 -29887
- package/dist/module/index.umd.js +453 -440
- package/docs/plans/field-creation-analysis.md +178 -0
- package/docs/plugin-api-reference.md +238 -0
- package/docs/plugin-quick-start.md +103 -0
- package/docs/plugin-system.md +321 -0
- package/docs/renderer_tag_cheatsheet.md +72 -0
- package/docs/renderer_tags_overview.md +916 -0
- package/docs/testing.md +67 -0
- package/docs/validation.md +139 -0
- package/package.json +8 -8
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# Malloy Render Plugin System
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Malloy Render plugin system provides a flexible way to extend the visualization capabilities of Malloy. Plugins allow you to create custom renderers for specific field types or data patterns, enabling rich, interactive visualizations beyond the built-in rendering options.
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
The plugin system consists of three main components:
|
|
10
|
+
|
|
11
|
+
1. **Plugin Factories** - Create plugin instances for matching fields
|
|
12
|
+
2. **Plugin Instances** - Handle the actual rendering of data
|
|
13
|
+
3. **Plugin Registry** - Manages available plugins and their lifecycle
|
|
14
|
+
|
|
15
|
+
## Plugin Types
|
|
16
|
+
|
|
17
|
+
### SolidJS Plugins
|
|
18
|
+
Use SolidJS components for reactive, efficient rendering.
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
interface SolidJSRenderPluginInstance {
|
|
22
|
+
readonly renderMode: 'solidjs';
|
|
23
|
+
renderComponent(props: RenderProps): JSXElement;
|
|
24
|
+
// ... other methods
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### DOM Plugins
|
|
29
|
+
Direct DOM manipulation for custom rendering needs.
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
interface DOMRenderPluginInstance {
|
|
33
|
+
readonly renderMode: 'dom';
|
|
34
|
+
renderToDOM(container: HTMLElement, props: RenderProps): void;
|
|
35
|
+
cleanup?(container: HTMLElement): void;
|
|
36
|
+
// ... other methods
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Plugin API
|
|
41
|
+
|
|
42
|
+
### RenderPluginFactory
|
|
43
|
+
|
|
44
|
+
The factory interface defines how plugins are matched and instantiated:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
interface RenderPluginFactory<TInstance extends RenderPluginInstance> {
|
|
48
|
+
readonly name: string;
|
|
49
|
+
|
|
50
|
+
// Determine if this plugin should handle a field
|
|
51
|
+
matches(field: Field, fieldTag: Tag, fieldType: FieldType): boolean;
|
|
52
|
+
|
|
53
|
+
// Create a plugin instance for a matched field
|
|
54
|
+
create(field: Field, pluginOptions?: unknown, modelTag?: Tag): TInstance;
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### RenderPluginInstance
|
|
59
|
+
|
|
60
|
+
Base interface for all plugin instances:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
interface BaseRenderPluginInstance<TMetadata = unknown> {
|
|
64
|
+
readonly name: string;
|
|
65
|
+
readonly field: Field;
|
|
66
|
+
readonly sizingStrategy: 'fixed' | 'fill';
|
|
67
|
+
|
|
68
|
+
// Return plugin-specific metadata
|
|
69
|
+
getMetadata(): TMetadata;
|
|
70
|
+
|
|
71
|
+
// Optional: Process data before rendering
|
|
72
|
+
processData?(field: NestField, cell: NestCell): void;
|
|
73
|
+
|
|
74
|
+
// Optional: Prepare for rendering
|
|
75
|
+
beforeRender?(metadata: RenderMetadata, options: GetResultMetadataOptions): void;
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Core Visualization Plugins
|
|
80
|
+
|
|
81
|
+
Core visualization plugins extend the base interface with additional methods:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
interface CoreVizPluginMethods {
|
|
85
|
+
getSchema(): JSONSchemaObject;
|
|
86
|
+
getSettings(): Record<string, unknown>;
|
|
87
|
+
getDefaultSettings(): Record<string, unknown>;
|
|
88
|
+
settingsToTag(settings: Record<string, unknown>): Tag;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
type CoreVizPluginInstance = SolidJSRenderPluginInstance & CoreVizPluginMethods;
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Writing a Plugin
|
|
95
|
+
|
|
96
|
+
### 1. Basic SolidJS Plugin
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import type { RenderPluginFactory, SolidJSRenderPluginInstance } from '@/api/plugin-types';
|
|
100
|
+
import { Field, FieldType } from '@/data_tree';
|
|
101
|
+
import type { Tag } from '@malloydata/malloy-tag';
|
|
102
|
+
|
|
103
|
+
const MyPluginFactory: RenderPluginFactory<SolidJSRenderPluginInstance> = {
|
|
104
|
+
name: 'my_plugin',
|
|
105
|
+
|
|
106
|
+
matches: (field: Field, fieldTag: Tag, fieldType: FieldType): boolean => {
|
|
107
|
+
// Match fields with #my_plugin tag
|
|
108
|
+
return fieldTag.has('my_plugin');
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
create: (field: Field): SolidJSRenderPluginInstance => {
|
|
112
|
+
return {
|
|
113
|
+
name: 'my_plugin',
|
|
114
|
+
field,
|
|
115
|
+
renderMode: 'solidjs',
|
|
116
|
+
sizingStrategy: 'fixed',
|
|
117
|
+
|
|
118
|
+
renderComponent: (props) => {
|
|
119
|
+
const value = props.dataColumn.value;
|
|
120
|
+
return <div class="my-plugin">{value}</div>;
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
getMetadata: () => ({
|
|
124
|
+
type: 'my_plugin',
|
|
125
|
+
fieldName: field.name
|
|
126
|
+
})
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 2. DOM Plugin Example
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
const MyDOMPluginFactory: RenderPluginFactory<DOMRenderPluginInstance> = {
|
|
136
|
+
name: 'my_dom_plugin',
|
|
137
|
+
|
|
138
|
+
matches: (field, fieldTag, fieldType) => {
|
|
139
|
+
return fieldTag.has('my_dom_plugin');
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
create: (field): DOMRenderPluginInstance => {
|
|
143
|
+
return {
|
|
144
|
+
name: 'my_dom_plugin',
|
|
145
|
+
field,
|
|
146
|
+
renderMode: 'dom',
|
|
147
|
+
sizingStrategy: 'fixed',
|
|
148
|
+
|
|
149
|
+
renderToDOM: (container, props) => {
|
|
150
|
+
container.innerHTML = `<div>Value: ${props.dataColumn.value}</div>`;
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
cleanup: (container) => {
|
|
154
|
+
container.innerHTML = '';
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
getMetadata: () => ({ type: 'my_dom_plugin' })
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 3. Advanced Visualization Plugin
|
|
164
|
+
|
|
165
|
+
For complex visualizations with settings and data processing:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
const AdvancedVizFactory: RenderPluginFactory<CoreVizPluginInstance> = {
|
|
169
|
+
name: 'advanced_viz',
|
|
170
|
+
|
|
171
|
+
matches: (field, fieldTag, fieldType) => {
|
|
172
|
+
return fieldType === FieldType.RepeatedRecord && fieldTag.has('advanced_viz');
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
create: (field, pluginOptions, modelTag) => {
|
|
176
|
+
let processedData: any = null;
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
name: 'advanced_viz',
|
|
180
|
+
field,
|
|
181
|
+
renderMode: 'solidjs',
|
|
182
|
+
sizingStrategy: 'fill',
|
|
183
|
+
|
|
184
|
+
processData: (field, cell) => {
|
|
185
|
+
// Pre-process data for efficiency
|
|
186
|
+
processedData = analyzeData(cell);
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
beforeRender: (metadata, options) => {
|
|
190
|
+
// Prepare rendering context
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
renderComponent: (props) => {
|
|
194
|
+
return <MyComplexVisualization data={processedData} />;
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
getMetadata: () => ({ type: 'advanced_viz' }),
|
|
198
|
+
|
|
199
|
+
// Core viz methods
|
|
200
|
+
getSchema: () => mySettingsSchema,
|
|
201
|
+
getSettings: () => currentSettings,
|
|
202
|
+
getDefaultSettings: () => defaultSettings,
|
|
203
|
+
settingsToTag: (settings) => convertToTag(settings)
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Using Plugins
|
|
210
|
+
|
|
211
|
+
### 1. Register Plugins with Renderer
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import { MalloyRenderer } from '@malloydata/malloy-render';
|
|
215
|
+
|
|
216
|
+
const renderer = new MalloyRenderer({
|
|
217
|
+
plugins: [
|
|
218
|
+
MyPluginFactory,
|
|
219
|
+
MyDOMPluginFactory,
|
|
220
|
+
AdvancedVizFactory
|
|
221
|
+
],
|
|
222
|
+
pluginOptions: {
|
|
223
|
+
'my_plugin': { color: 'blue' },
|
|
224
|
+
'advanced_viz': { theme: 'dark' }
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### 2. Tag Fields in Malloy
|
|
230
|
+
|
|
231
|
+
```malloy
|
|
232
|
+
source: users is table('users') {
|
|
233
|
+
dimension:
|
|
234
|
+
status # my_plugin
|
|
235
|
+
age # advanced_viz { "max_value": 100 }
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Plugin Lifecycle
|
|
240
|
+
|
|
241
|
+
1. **Registration** - Plugins are registered when creating a MalloyRenderer
|
|
242
|
+
2. **Matching** - For each field, the system checks all plugins' `matches()` method
|
|
243
|
+
3. **Instantiation** - Matching plugins are instantiated via `create()`
|
|
244
|
+
4. **Data Processing** - Optional `processData()` is called during result processing
|
|
245
|
+
5. **Pre-render** - Optional `beforeRender()` prepares the rendering context
|
|
246
|
+
6. **Rendering** - `renderComponent()` or `renderToDOM()` displays the visualization
|
|
247
|
+
7. **Cleanup** - For DOM plugins, `cleanup()` is called when removing the visualization
|
|
248
|
+
|
|
249
|
+
## Best Practices
|
|
250
|
+
|
|
251
|
+
### 1. Field Type Validation
|
|
252
|
+
Throw from `matches()` when the user asked for this plugin (tag present) but the field cannot support it — the user sees a red error tile in place of the visualization:
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
matches: (field, fieldTag, fieldType) => {
|
|
256
|
+
if (fieldTag.has('my_chart') && fieldType !== FieldType.RepeatedRecord) {
|
|
257
|
+
throw new Error('my_chart requires a repeated record field');
|
|
258
|
+
}
|
|
259
|
+
return fieldTag.has('my_chart') && fieldType === FieldType.RepeatedRecord;
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
For tag-setting errors that shouldn't fail the whole render (e.g. invalid enum value, wrong field type for one setting), use `validateFieldTags()` with `log.error(msg, tagRef)` instead so the user gets a source-located error and the plugin still renders with defaults. See [validation.md](validation.md) for the full throw-vs-log rule.
|
|
264
|
+
|
|
265
|
+
### 2. Efficient Data Processing
|
|
266
|
+
Use `processData()` for expensive computations to avoid re-processing during re-renders:
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
processData: (field, cell) => {
|
|
270
|
+
// Calculate once during data loading
|
|
271
|
+
this.aggregatedData = expensiveCalculation(cell);
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### 3. Sizing Strategy
|
|
276
|
+
- Use `'fixed'` for plugins with predetermined dimensions
|
|
277
|
+
- Use `'fill'` for plugins that adapt to container size
|
|
278
|
+
|
|
279
|
+
### 4. Error Handling
|
|
280
|
+
The right place to catch tag/configuration mistakes is setup time (in `create()` or in a resolver), not render time. A try/catch in `renderComponent()` is a last resort for defensive coverage of unexpected runtime failures — see [validation.md](validation.md) for the setup-time pattern.
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
renderComponent: (props) => {
|
|
284
|
+
try {
|
|
285
|
+
return <MyVisualization {...props} />;
|
|
286
|
+
} catch (error) {
|
|
287
|
+
return <div class="error">Visualization error: {error.message}</div>;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### 5. Plugin Options
|
|
293
|
+
Accept configuration through pluginOptions for flexibility:
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
create: (field, pluginOptions) => {
|
|
297
|
+
const options = pluginOptions as MyPluginOptions || defaultOptions;
|
|
298
|
+
// Use options in plugin implementation
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Built-in Plugins
|
|
303
|
+
|
|
304
|
+
Malloy Render includes several built-in plugins:
|
|
305
|
+
|
|
306
|
+
- **LineChartPlugin** - Time series and trend visualizations
|
|
307
|
+
- **BarChartPlugin** - Categorical data comparisons
|
|
308
|
+
|
|
309
|
+
These serve as excellent examples for creating custom plugins.
|
|
310
|
+
|
|
311
|
+
## Debugging
|
|
312
|
+
|
|
313
|
+
Plugin throws from `matches()` and `create()` are caught by `RenderFieldMetadata.instantiatePluginsForField` and rendered as red error tiles (via `ErrorPlugin`). They also appear in `MalloyViz.getLogs()` as errors. Tag validation errors emitted via `log.error(msg, tagRef)` appear in the same log stream with source locations. See [validation.md](validation.md) for the full error flow.
|
|
314
|
+
|
|
315
|
+
## Future Considerations
|
|
316
|
+
|
|
317
|
+
The plugin system is designed to be extensible. Future enhancements may include:
|
|
318
|
+
- Plugin communication mechanisms
|
|
319
|
+
- Shared plugin state management
|
|
320
|
+
- Plugin composition patterns
|
|
321
|
+
- Enhanced lifecycle hooks
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
| Tag | Description | Details | Example |
|
|
2
|
+
| :-------------------------------- | :----------------------------------------- | :------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------- |
|
|
3
|
+
| **Charts & Visualization** | | | |
|
|
4
|
+
| `# bar_chart` | Render data as a bar chart. | Base tag for bar chart configuration. Axes/series often inferred. | `view: my_view is { # bar_chart ... }` |
|
|
5
|
+
| `# bar_chart.stack` | Stack bars in a series. | Boolean property. Use when a `series` field is defined. | `view: my_view is { # bar_chart.stack ... }` |
|
|
6
|
+
| `# bar_chart.size` | Set preset chart size. | Values: `spark`, `xs`, `sm`, `md`, `lg`, `xl`, `2xl`. | `view: my_view is { # bar_chart { size=lg } ... }` |
|
|
7
|
+
| `# bar_chart.size.width` | Set specific chart width. | Value is in pixels. Overrides preset width. | `view: my_view is { # bar_chart { size.width=450 } ... }` |
|
|
8
|
+
| `# bar_chart.size.height` | Set specific chart height. | Value is in pixels. Overrides preset height. | `view: my_view is { # bar_chart { size.height=300 } ... }` |
|
|
9
|
+
| `# bar_chart.x` | Define x-axis field. | Assigns a field to the category axis. | `view: my_view is { # bar_chart { x=category } ... }` |
|
|
10
|
+
| `# bar_chart.x.limit` | Limit categories on x-axis. | Numeric value. Limits bars shown. Auto-calculated if not set. | `view: my_view is { # bar_chart { x.limit=10 } ... }` |
|
|
11
|
+
| `# bar_chart.x.independent` | Control x-axis scale sharing. | Boolean (`true`/`false`). Overrides auto-sharing based on cardinality (default shares if <=20). | `view: my_nest is { # bar_chart { x.independent } ...}` |
|
|
12
|
+
| `# bar_chart.y` | Define y-axis field(s). | Assigns field(s) to the value axis. Use array `['m1', 'm2']` for measure series. | `view: my_view is { # bar_chart { y=total_sales } ... }` <br> `view: my_view is { # bar_chart { y=['Sales', 'Cost'] } ... }` |
|
|
13
|
+
| `# bar_chart.y.independent` | Control y-axis scale sharing. | Boolean (`true`/`false`). Default is shared. Use `true` for independent scales in nested charts. | `view: my_nest is { # bar_chart { y.independent } ... }` |
|
|
14
|
+
| `# bar_chart.series` | Define series field. | Assigns a field for grouping/coloring bars. | `view: my_view is { # bar_chart { series=department } ... }` |
|
|
15
|
+
| `# bar_chart.series.limit` | Limit number of series shown. | Numeric value or `auto`. Default `auto` uses 20. Series ranked by sum of Y-values. | `view: my_view is { # bar_chart { series.limit=5 } ... }` <br> `view: my_view is { # bar_chart { series.limit=auto } ... }` |
|
|
16
|
+
| `# bar_chart.series.independent` | Control series legend/color sharing. | Boolean (`true`/`false`). Overrides auto-sharing based on cardinality (default shares if <=20). | `view: my_nest is { # bar_chart { series.independent } ... }` |
|
|
17
|
+
| `# bar_chart.title` | Set chart title. | String value. | `view: my_view is { # bar_chart { title='Sales Distribution' } ... }` |
|
|
18
|
+
| `# bar_chart.subtitle` | Set chart subtitle. | String value. | `view: my_view is { # bar_chart { subtitle='Q1 Data' } ... }` |
|
|
19
|
+
| `# line_chart` | Render data as a line chart. | Base tag for line chart configuration. Axes/series often inferred. | `view: my_view is { # line_chart ... }` |
|
|
20
|
+
| `# line_chart.zero_baseline` | Control if y-axis includes zero. | Boolean (`true`/`false`). Default `false` (unlike bar charts). `true` forces axis to start at zero. | `view: my_view is { # line_chart { zero_baseline=true } ... }` |
|
|
21
|
+
| `# line_chart.size` | Set preset chart size. | Values: `spark`, `xs`...`2xl`. | `view: my_view is { # line_chart { size=spark } ... }` |
|
|
22
|
+
| `# line_chart.interpolate` | Set line interpolation mode. | E.g., `step`. | `view: my_view is { # line_chart { interpolate=step } ... }` |
|
|
23
|
+
| `# line_chart.x` | Define x-axis field. | Assigns a field (often time-based) to the x-axis. | `view: my_view is { # line_chart { x=sale_date } ... }` |
|
|
24
|
+
| `# line_chart.x.independent` | Control x-axis scale sharing. | Boolean (`true`/`false`). Overrides auto-sharing (default shares if <=20). | `view: my_nest is { # line_chart { x.independent } ... }` |
|
|
25
|
+
| `# line_chart.y` | Define y-axis field(s). | Assigns field(s) to the value axis. Use array `['m1', 'm2']` for multiple lines from measures. | `view: my_view is { # line_chart { y=value } ... }` <br> `view: my_view is { # line_chart { y=['MetricA', 'MetricB'] } ... }` |
|
|
26
|
+
| `# line_chart.y.independent` | Control y-axis scale sharing. | Boolean (`true`/`false`). Default is shared, unless `series.limit` is used. | `view: my_nest is { # line_chart { y.independent } ... }` |
|
|
27
|
+
| `# line_chart.series` | Define series field. | Assigns a field for multiple lines. | `view: my_view is { # line_chart { series=category } ... }` |
|
|
28
|
+
| `# line_chart.series.limit` | Limit number of series lines. | Numeric value or `auto`. Default `auto` uses 12. Series ranked by sum of Y-values. Implies `y.independent=true`. | `view: my_view is { # line_chart { series.limit=5 } ... }` <br> `view: my_view is { # line_chart { series.limit=auto } ... }` |
|
|
29
|
+
| `# line_chart.series.independent` | Control series legend/color sharing. | Boolean (`true`/`false`). Overrides auto-sharing (default shares if <=20). | `view: my_nest is { # line_chart { series.independent } ... }` |
|
|
30
|
+
| `# line_chart.title` | Set chart title. | String value. | `view: my_view is { # line_chart { title='Trend Over Time' } ... }` |
|
|
31
|
+
| `# line_chart.subtitle` | Set chart subtitle. | String value. | `view: my_view is { # line_chart { subtitle='Daily Values' } ... }` |
|
|
32
|
+
| `# scatter_chart` | Render as scatter plot. | Legacy renderer. Uses field order (x, y, color, size, shape). | `view: my_view is { # scatter_chart ... }` |
|
|
33
|
+
| **Layout & Structure** | | | |
|
|
34
|
+
| `# dashboard` | Render nested views as a dashboard. | Base tag for dashboard layout. | `view: my_dashboard is { # dashboard nest: ... }` |
|
|
35
|
+
| `# dashboard.table.max_height` | Set max height for tables in dashboard. | Numeric pixel value or `'none'`. | `view: my_dashboard is { # dashboard { table.max_height=400 } ... }` |
|
|
36
|
+
| `# dashboard.columns` | Columns mode: N equal-width columns. | Integer. A tile takes one column by default; tiles wrap onto new rows. Use `# colspan` to widen a tile. | `view: my_dashboard is { # dashboard { columns=3 } nest: ... }` |
|
|
37
|
+
| `# dashboard.gap` | Spacing between tiles (pixels). | Applies in flex mode and columns mode. Default 16. | `view: my_dashboard is { # dashboard { gap=24 } nest: ... }` |
|
|
38
|
+
| `# colspan` | Tile width in columns mode (in columns). | Applied to a `nest:`. Makes the tile span N columns (one by default); ignored in flex mode. | `view: my_dashboard is { # dashboard { columns=12 } # colspan=8 nest: a is {...} # colspan=4 nest: b is {...} }` |
|
|
39
|
+
| `# break` | Start a new row in a dashboard. | Applied to a `nest:` definition. Tiles after it begin a fresh row. | `view: my_dashboard is { nest: chart1 is {...} # break nest: chart2 is {...} }` |
|
|
40
|
+
| `# table` | Render data as a table. | Often the default for nested data. | `view: my_table is { # table ... }` |
|
|
41
|
+
| `# table.pivot` | Pivot table dimensions into columns. | Use `# pivot` for automatic, or `# pivot { dimensions=["d1"] }` for specific dimensions. | `nest: my_pivot is { # pivot ... }` <br> `nest: my_pivot is { # pivot { dimensions=["country"] } ... }` |
|
|
42
|
+
| `# table.flatten` | Flatten nested record into columns. | Applied to a `nest:` definition. Nested query must not have `group_by`. | `nest: metrics is { # flatten ... }` |
|
|
43
|
+
| `# table.size=fill` | Make table fill container width. | Boolean property applied via value. | `view: full_width_table is { # table.size=fill ... }` |
|
|
44
|
+
| `# table.column.width` | Set specific column width. | Apply to a field. Value: `xs`, `sm`, `md`, `lg`, `xl`, `2xl` or pixels. | `dimension: my_col is ... # column { width=lg }` <br> `dimension: my_col is ... # column { width=150 }` |
|
|
45
|
+
| `# table.column.height` | Set specific row height for column cells. | Apply to a field. Value in pixels. | `dimension: my_col is ... # column { height=50 }` |
|
|
46
|
+
| `# table.column.word_break` | Control word breaking in cells. | Value: `break_all`. Apply to a field. | `dimension: long_text is ... # column { word_break=break_all }` |
|
|
47
|
+
| `# list` | Render first field as list. | Comma-separated values from the first non-hidden field of a nest. | `nest: top_items is { # list ... }` |
|
|
48
|
+
| `# list_detail` | Render first two fields as list. | Comma-separated `value (detail)` from first two non-hidden fields. | `nest: item_details is { # list_detail ... }` |
|
|
49
|
+
| `# transpose` | Transpose table rows/columns. | Applied at the view level. Default limit of 20 columns; use `.limit=N` to increase. | `view: metrics_row is { # transpose ... }` <br> `# transpose.limit=100` |
|
|
50
|
+
| **Field Formatting/Rendering** | | | |
|
|
51
|
+
| `# currency` | Format number as currency. | Default is USD ($). Specify units like `euro` or `pound`. | `measure: revenue is ... # currency` <br> `measure: revenue_eur is ... # currency=euro` |
|
|
52
|
+
| `# percent` | Format number as percentage. | Multiplies by 100, adds '%'. | `measure: margin is ... # percent` |
|
|
53
|
+
| `# number` | Format number with `ssf` string. | Provide format string as value. | `measure: my_num is ... # number="#,##0.0"` |
|
|
54
|
+
| `# duration` | Format number as duration. | Default input unit: `seconds`. Specify units (`nanoseconds`..`days`). | `measure: avg_time is ... # duration` <br> `measure: compute is ... # duration=milliseconds` |
|
|
55
|
+
| `# duration.terse` | Use abbreviated duration units. | E.g., `ns`, `s`, `m`, `h`, `d`. | `measure: short_time is ... # duration.terse` |
|
|
56
|
+
| `# duration.number` | Format numeric parts of duration. | Uses `ssf` format string. | `measure: precise_duration is ... # duration { number="0.00" }` |
|
|
57
|
+
| `# image` | Render string as image URL. | Applied to a string field containing a URL. | `dimension: logo_url is ... # image` |
|
|
58
|
+
| `# image.height` | Set image height. | CSS value (e.g., `40px`). | `dimension: logo_url is ... # image { height=40px }` |
|
|
59
|
+
| `# image.width` | Set image width. | CSS value (e.g., `100px`). | `dimension: logo_url is ... # image { width=100px }` |
|
|
60
|
+
| `# image.alt` | Set image alt text. | Literal string value. | `dimension: logo_url is ... # image { alt='Company Logo' }` |
|
|
61
|
+
| `# image.alt.field` | Use another field for alt text. | Value is relative path to field (e.g., `field_name`, `'../parent_field'`). | `dimension: logo_url is ... # image { alt.field=product_name }` |
|
|
62
|
+
| `# link` | Render field as hyperlink. | Applied to a field whose value is the link text. | `dimension: url is ... # link` |
|
|
63
|
+
| `# link.url_template` | Template for link href. | String where `$$` is replaced by value (from this field or `.field`). Appends if `$$` is missing. | `dimension: name is ... # link { url_template="https://example.com/$$" }` |
|
|
64
|
+
| `# link.field` | Use another field's value for href. | Value is relative path to the field containing the href data. | `dimension: link_text is 'Search' # link { field=query_term url_template="https://google.com/search?q=$$" }` |
|
|
65
|
+
| **Utilities & Configuration** | | | |
|
|
66
|
+
| `# hidden` | Hide field from rendering. | Field remains in data, just not displayed in tables/dashboards. | `dimension: internal_id is ... # hidden` |
|
|
67
|
+
| `# label` | Override display name/title. | String value. Applied to fields or dashboard items. | `measure: total_revenue is ... # label="Total Sales ($)"` |
|
|
68
|
+
| `# tooltip` | Include nested view in tooltip. | Applied to a `nest:`. Can contain render hints for the tooltip itself. | `nest: details is { # tooltip ... }` <br> `nest: chart_tip is { # tooltip bar_chart.size=xs ... }` |
|
|
69
|
+
| `# size` | Set preset size (legacy). | Prefer `.size` on specific renderer tags. Values: `spark`, `xs`...`2xl`. Applied to view/nest. | `view: my_view is { # size=lg ... }` |
|
|
70
|
+
| `# theme` | Apply theme style overrides (View level). | Contains CSS-like properties (e.g., `tableBodyColor`, `tableRowHeight`). | `view: my_view is { # theme { tableBodyColor=red } ... }` |
|
|
71
|
+
| `## theme` | Apply theme style overrides (Model level). | Contains CSS-like properties. Sets defaults for the model. | `## theme { tableBodyColor=blue }` |
|
|
72
|
+
| `## renderer_legacy` | Use legacy HTML renderer for model. | Model-level tag. No properties. | `## renderer_legacy` |
|