@malloydata/render 0.0.324 → 0.0.326

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/CONTEXT.md ADDED
@@ -0,0 +1,93 @@
1
+ # Malloy Render
2
+
3
+ The `malloy-render` package handles visualization and rendering of Malloy query results. It transforms query results into rich, interactive visualizations and tables.
4
+
5
+ ## Purpose
6
+
7
+ Malloy queries return structured data with metadata about how it should be displayed. The render package:
8
+ - Interprets query result metadata
9
+ - Generates appropriate visualizations
10
+ - Provides interactive data exploration
11
+ - Supports multiple rendering formats
12
+
13
+ ## Technology Stack
14
+
15
+ ### Solid.js
16
+ The rendering system uses **Solid.js** as its reactive UI framework.
17
+
18
+ ### Vega
19
+ The package uses **Vega** for declarative data visualizations.
20
+
21
+ ## Rendering Process
22
+
23
+ ```
24
+ Query Results + Metadata → Renderer → Visualization Selection → Vega Spec / Table → UI Output
25
+ ```
26
+
27
+ **Steps:**
28
+ 1. Query results arrive with rendering metadata
29
+ 2. Renderer examines result structure and annotations
30
+ 3. Appropriate visualization type is selected
31
+ 4. Vega specification is generated (for charts) or table is formatted
32
+ 5. UI components render the visualization
33
+
34
+ ## Rendering Hints
35
+
36
+ The renderer respects annotations and metadata from Malloy models:
37
+
38
+ - **Renderer annotations** (prefix `# `) provide display hints
39
+ - **Field types** influence default visualizations
40
+ - **Data structure** (nested, flat, aggregated) affects rendering choices
41
+ - **User preferences** can override defaults
42
+
43
+ ## Visualization Types
44
+
45
+ The renderer supports various output formats:
46
+
47
+ - **Tables** - Default for most query results
48
+ - **Bar charts** - For categorical comparisons
49
+ - **Line charts** - For time series data
50
+ - **Scatter plots** - For correlation analysis
51
+ - **Nested tables** - For hierarchical data
52
+ - **Sparklines** - For inline visualizations
53
+ - And more...
54
+
55
+ ## Package Integration
56
+
57
+ The render package integrates with:
58
+ - **malloy-core** - Consumes query results and metadata
59
+ - **VS Code extension** - Provides visualization in editor
60
+ - **Web applications** - Can be embedded in web apps
61
+ - **Reports** - Generates static visualizations
62
+
63
+ ## Architecture
64
+
65
+ ### Two Renderers
66
+ - **New renderer** (`src/component/`) - Solid.js-based, the default
67
+ - **Legacy renderer** (`src/html/`) - HTML string-based, activated with `## renderer_legacy` model tag
68
+
69
+ ### Tag System
70
+ Render annotations use the Malloy Tag API to check for rendering hints. For tag language syntax, see [packages/malloy-tag/CONTEXT.md](../malloy-tag/CONTEXT.md).
71
+
72
+ Common API patterns in the renderer:
73
+ - `field.tag.has('pivot')` - Check if a tag exists
74
+ - `field.tag.text('label')` - Get a text property
75
+ - `field.tag.textArray('pivot', 'dimensions')` - Get array property with path
76
+ - `field.tag.tag('table', 'size')` - Navigate nested tag properties
77
+
78
+ ### Table Layout
79
+ The table uses CSS Grid with subgrid. Layout is calculated in `table-layout.ts`:
80
+ - `getTableLayout()` - Creates base layout from field structure
81
+ - `adjustLayoutForPivots()` - Adjusts column counts for pivot expansion
82
+ - Column ranges are tracked for each field to support nested tables
83
+
84
+ ### Testing
85
+ Use Storybook (`npm run storybook`) to test visual changes. Stories are in `src/stories/*.stories.malloy`.
86
+
87
+ ## Important Notes
88
+
89
+ - Rendering is **separate from query execution** - it only processes results
90
+ - The renderer is **stateless** - same results produce same visualizations
91
+ - Visualizations respect **user accessibility** preferences where possible
92
+ - The package is designed to be **embeddable** in various contexts
93
+ - **Never run the full test suite** without restrictions - use targeted tests or storybook
@@ -0,0 +1,13 @@
1
+ import { JSX } from 'solid-js';
2
+ import { RecordCell } from '../../data_tree';
3
+ import { PivotConfig } from './pivot-utils';
4
+ /**
5
+ * Renders all pivot cells for one row's nested pivot field.
6
+ */
7
+ export declare const PivotFieldCells: (props: {
8
+ row: RecordCell;
9
+ pivotConfig: PivotConfig;
10
+ rowPath: number[];
11
+ startColumn: number;
12
+ }) => JSX.Element;
13
+ export default PivotFieldCells;
@@ -0,0 +1,22 @@
1
+ import { Field } from '../../data_tree';
2
+ import { PivotConfig } from './pivot-utils';
3
+ /**
4
+ * Info about a sibling field (non-pivot field at same depth as pivot).
5
+ */
6
+ export type SiblingFieldInfo = {
7
+ field: Field;
8
+ startColumn: number;
9
+ endColumn: number;
10
+ };
11
+ /**
12
+ * Renders all header rows for a pivot field.
13
+ * Row 1: Empty for siblings + dimension values with colspan
14
+ * Row 2: Sibling headers + measure names repeated
15
+ */
16
+ export declare const PivotHeaders: (props: {
17
+ pivotConfig: PivotConfig;
18
+ pivotStartColumn: number;
19
+ totalColumns: number;
20
+ siblingFields: SiblingFieldInfo[];
21
+ }) => import("solid-js").JSX.Element;
22
+ export default PivotHeaders;
@@ -0,0 +1,127 @@
1
+ import { Cell, Field, NestField, RecordOrRepeatedRecordCell, RecordOrRepeatedRecordField, SortableField } from '../../data_tree';
2
+ /**
3
+ * Represents a unique combination of dimension values in a pivot.
4
+ * Each PivotedField corresponds to one set of column headers.
5
+ */
6
+ export type PivotedField = {
7
+ /** JSON-stringified key for lookup (includes parent field name and dimension values) */
8
+ key: string;
9
+ /** The dimension cell values for this pivot combination */
10
+ values: Cell[];
11
+ /** The parent nested field being pivoted */
12
+ parentField: RecordOrRepeatedRecordField;
13
+ /** Number of non-dimension (measure) columns this spans */
14
+ span: number;
15
+ /** Map from dimension field name to cell value for quick lookup */
16
+ fieldValueMap: Map<string, Cell>;
17
+ };
18
+ /**
19
+ * Represents a single column in a pivoted table.
20
+ * Combines a specific dimension value combination with a measure field.
21
+ */
22
+ export type PivotedColumnField = {
23
+ /** The pivot dimension combination */
24
+ pivotedField: PivotedField;
25
+ /** The actual measure field to render */
26
+ field: Field;
27
+ /** User-defined dimension fields (if specified) */
28
+ userDefinedPivotDimensions?: string[];
29
+ };
30
+ export type { SortableField };
31
+ /**
32
+ * Complete configuration for a pivot field.
33
+ */
34
+ export type PivotConfig = {
35
+ /** The nested field being pivoted */
36
+ field: NestField;
37
+ /** Dimension fields (used for column headers) */
38
+ dimensions: SortableField[];
39
+ /** Non-dimension fields (measures to display) */
40
+ nonDimensions: SortableField[];
41
+ /** All unique dimension value combinations, sorted */
42
+ pivotedFields: PivotedField[];
43
+ /** Expanded column list: pivotedFields * nonDimensions */
44
+ columnFields: PivotedColumnField[];
45
+ /** Max number of dimension levels (for header row depth) */
46
+ pivotDepth: number;
47
+ };
48
+ /** Maximum number of pivot columns allowed */
49
+ export declare const PIVOT_COLUMN_LIMIT = 30;
50
+ /** Maximum number of transpose columns allowed */
51
+ export declare const TRANSPOSE_COLUMN_LIMIT = 20;
52
+ /**
53
+ * Creates a unique key for a pivot dimension combination.
54
+ */
55
+ export declare function createPivotKey(parentField: RecordOrRepeatedRecordField, values: Cell[]): string;
56
+ /**
57
+ * Separates fields into dimensions and non-dimensions (measures).
58
+ *
59
+ * @param field The nested field being pivoted
60
+ * @param userDimensions Optional explicit dimension field names
61
+ * @returns Object with dimensions and nonDimensions arrays
62
+ */
63
+ export declare function calculatePivotDimensions(field: NestField, userDimensions?: string[]): {
64
+ dimensions: SortableField[];
65
+ nonDimensions: SortableField[];
66
+ };
67
+ /**
68
+ * Collects all unique dimension value combinations across all rows of data.
69
+ *
70
+ * @param field The nested field being pivoted
71
+ * @param data The parent table data
72
+ * @param dimensions The dimension fields to extract values from
73
+ * @returns Map of pivot keys to PivotedField objects
74
+ */
75
+ export declare function collectPivotedFields(field: NestField, data: RecordOrRepeatedRecordCell, dimensions: SortableField[], nonDimensionCount: number): Map<string, PivotedField>;
76
+ /**
77
+ * Sorts pivoted fields by their dimension values.
78
+ * Uses Cell.compareTo() for type-appropriate sorting.
79
+ *
80
+ * @param pivotedFields Array of PivotedField objects
81
+ * @param dimensions Dimension fields with sort direction
82
+ * @returns Sorted array
83
+ */
84
+ export declare function sortPivotedFields(pivotedFields: PivotedField[], dimensions: SortableField[]): PivotedField[];
85
+ /**
86
+ * Expands sorted pivot fields into individual column fields.
87
+ * Each pivot combination * each non-dimension = one column.
88
+ *
89
+ * @param pivotedFields Sorted array of PivotedField objects
90
+ * @param nonDimensions Non-dimension (measure) fields
91
+ * @param userDimensions Optional user-specified dimension names
92
+ * @returns Array of PivotedColumnField objects
93
+ */
94
+ export declare function expandPivotColumns(pivotedFields: PivotedField[], nonDimensions: SortableField[], userDimensions?: string[]): PivotedColumnField[];
95
+ /**
96
+ * Builds a complete pivot configuration for a nested field.
97
+ *
98
+ * @param field The nested field with # pivot tag
99
+ * @param data The parent table data
100
+ * @param userDimensions Optional explicit dimension field names
101
+ * @returns PivotConfig or null if pivot cannot be created
102
+ */
103
+ export declare function buildPivotConfig(field: NestField, data: RecordOrRepeatedRecordCell, userDimensions?: string[]): PivotConfig;
104
+ /**
105
+ * Generates a map of pivot keys to cell values for a single row's nested data.
106
+ * Used during rendering to look up the correct cell for each pivot column.
107
+ *
108
+ * @param nestedCell The nested record data for one row
109
+ * @param pivotConfig The pivot configuration
110
+ * @returns Map from pivot key to map of field name to cell
111
+ */
112
+ export declare function generatePivotedCellsMap(nestedCell: RecordOrRepeatedRecordCell, pivotConfig: PivotConfig): Map<string, Map<string, Cell>>;
113
+ /**
114
+ * Gets the pivot dimension values to display in a cell.
115
+ * Used for rendering dimension value header cells.
116
+ */
117
+ export declare function getPivotDimensionValue(pivotedField: PivotedField, dimensionIndex: number): Cell | undefined;
118
+ /**
119
+ * Checks if a field should be rendered as a pivot table.
120
+ * Syntax: # pivot
121
+ */
122
+ export declare function shouldPivot(field: Field): boolean;
123
+ /**
124
+ * Gets user-defined pivot dimensions from the tag.
125
+ * Syntax: # pivot { dimensions=[d1, d2] }
126
+ */
127
+ export declare function getUserDefinedDimensions(field: Field): string[] | undefined;
@@ -1,5 +1,6 @@
1
1
  import { SetStoreFunction, Store, createStore } from 'solid-js/store';
2
2
  import { TableLayout } from './table-layout';
3
+ import { PivotConfig } from './pivot-utils';
3
4
  type TableStore = {
4
5
  headerSizes: Record<string, number>;
5
6
  columnWidths: Record<string, number>;
@@ -15,6 +16,8 @@ export type TableContext = {
15
16
  headerSizeStore: ReturnType<typeof createStore<Record<string, number>>>;
16
17
  currentRow: number[];
17
18
  currentExplore: string[];
19
+ /** Map of field key to pivot configuration */
20
+ pivotConfigs: Map<string, PivotConfig>;
18
21
  };
19
22
  export declare const TableContext: import('solid-js').Context<TableContext | undefined>;
20
23
  export declare const useTableContext: () => TableContext | undefined;
@@ -1,5 +1,6 @@
1
1
  import { Field, NestField } from '../../data_tree';
2
2
  import { FieldHeaderRangeMap } from '../types';
3
+ import { PivotConfig } from './pivot-utils';
3
4
  type LayoutEntry = {
4
5
  field: Field;
5
6
  width: number | null;
@@ -16,4 +17,13 @@ export type TableLayout = {
16
17
  maxDepth: number;
17
18
  };
18
19
  export declare function getTableLayout(rootField: NestField): TableLayout;
20
+ /**
21
+ * Adjusts a table layout to account for pivot column expansion.
22
+ * Pivot fields may have more columns than their original nested structure.
23
+ *
24
+ * @param layout The original table layout
25
+ * @param pivotConfigs Map of field keys to their pivot configurations
26
+ * @returns Adjusted layout with correct column counts for pivot fields
27
+ */
28
+ export declare function adjustLayoutForPivots(layout: TableLayout, pivotConfigs: Map<string, PivotConfig>): TableLayout;
19
29
  export {};
@@ -0,0 +1,16 @@
1
+ import { Component } from 'solid-js';
2
+ import { RecordOrRepeatedRecordCell, Field } from '../../data_tree';
3
+ /**
4
+ * TransposeTable renders a table with rows and columns swapped.
5
+ * Each field becomes a row, and each data record becomes a column.
6
+ */
7
+ declare const TransposeTable: Component<{
8
+ data: RecordOrRepeatedRecordCell;
9
+ rowLimit?: number;
10
+ }>;
11
+ export default TransposeTable;
12
+ /**
13
+ * Checks if a field should be rendered as a transposed table.
14
+ * Syntax: # transpose
15
+ */
16
+ export declare function shouldTranspose(field: Field): boolean;