@uwdata/mosaic-spec 0.11.0 → 0.12.1

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 (42) hide show
  1. package/README.md +2 -0
  2. package/dist/mosaic-schema.json +9664 -2691
  3. package/dist/mosaic-spec.js +4174 -2653
  4. package/dist/mosaic-spec.min.js +24 -24
  5. package/dist/types/ast/ColumnParamRefNode.d.ts +8 -0
  6. package/dist/types/ast/ExpressionNode.d.ts +2 -4
  7. package/dist/types/constants.d.ts +1 -1
  8. package/dist/types/spec/Input.d.ts +1 -1
  9. package/dist/types/spec/PlotAttribute.d.ts +11 -5
  10. package/dist/types/spec/PlotFrom.d.ts +1 -1
  11. package/dist/types/spec/PlotInteractor.d.ts +2 -1
  12. package/dist/types/spec/PlotMark.d.ts +2 -1
  13. package/dist/types/spec/Transform.d.ts +8 -2
  14. package/dist/types/spec/interactors/BrushStyles.d.ts +27 -0
  15. package/dist/types/spec/interactors/Interval1D.d.ts +6 -27
  16. package/dist/types/spec/interactors/Interval2D.d.ts +6 -5
  17. package/dist/types/spec/interactors/Region.d.ts +32 -0
  18. package/dist/types/spec/interactors/Toggle.d.ts +3 -3
  19. package/dist/types/spec/marks/Marks.d.ts +5 -0
  20. package/dist/types/spec/marks/Waffle.d.ts +58 -0
  21. package/package.json +6 -6
  22. package/src/ast/ColumnParamRefNode.js +21 -0
  23. package/src/ast/DataNode.js +3 -3
  24. package/src/ast/ExpressionNode.js +17 -22
  25. package/src/ast/PlotFromNode.js +6 -6
  26. package/src/ast/PlotMarkNode.js +2 -2
  27. package/src/ast/TransformNode.js +14 -12
  28. package/src/config/transforms.js +1 -0
  29. package/src/constants.js +1 -1
  30. package/src/spec/Input.ts +1 -1
  31. package/src/spec/PlotAttribute.ts +13 -5
  32. package/src/spec/PlotFrom.ts +1 -1
  33. package/src/spec/PlotInteractor.ts +7 -5
  34. package/src/spec/PlotMark.ts +3 -1
  35. package/src/spec/Transform.ts +10 -1
  36. package/src/spec/interactors/BrushStyles.ts +27 -0
  37. package/src/spec/interactors/Interval1D.ts +6 -28
  38. package/src/spec/interactors/Interval2D.ts +6 -5
  39. package/src/spec/interactors/Region.ts +34 -0
  40. package/src/spec/interactors/Toggle.ts +3 -3
  41. package/src/spec/marks/Marks.ts +6 -0
  42. package/src/spec/marks/Waffle.ts +61 -0
@@ -0,0 +1,8 @@
1
+ export class ColumnParamRefNode extends ASTNode {
2
+ constructor(param: any);
3
+ param: any;
4
+ instantiate(ctx: any): any;
5
+ codegen(ctx: any): string;
6
+ toJSON(): string;
7
+ }
8
+ import { ASTNode } from './ASTNode.js';
@@ -1,15 +1,13 @@
1
1
  export function parseExpression(spec: any, ctx: any): ExpressionNode;
2
2
  export class ExpressionNode extends ASTNode {
3
- constructor(value: any, spans: any, params: any, label: any, aggregate: any);
3
+ constructor(value: any, spans: any, params: any);
4
4
  value: any;
5
5
  spans: any;
6
6
  params: any;
7
- label: any;
8
- aggregate: any;
9
7
  instantiate(ctx: any): any;
10
8
  codegen(ctx: any): string;
11
9
  toJSON(): {
12
- [x: string]: any;
10
+ sql: any;
13
11
  };
14
12
  }
15
13
  import { ASTNode } from './ASTNode.js';
@@ -4,6 +4,7 @@ export const DATAREF: "dataref";
4
4
  export const OPTIONS: "options";
5
5
  export const SELECTION: "selection";
6
6
  export const PARAMREF: "paramref";
7
+ export const COLUMPARAMREF: "columnparamref";
7
8
  export const PARAM: "param";
8
9
  export const INCLUDE: "include";
9
10
  export const SELECT: "select";
@@ -15,7 +16,6 @@ export const SINGLE: "single";
15
16
  export const DATA: "data";
16
17
  export const EXPRESSION: "expression";
17
18
  export const SQL: "sql";
18
- export const AGG: "agg";
19
19
  export const INPUT: "input";
20
20
  export const HCONCAT: "hconcat";
21
21
  export const VCONCAT: "vconcat";
@@ -170,7 +170,7 @@ export interface Table {
170
170
  /**
171
171
  * The name of a database table to use as a data source for this widget.
172
172
  */
173
- from: string;
173
+ from: string | ParamRef;
174
174
  /**
175
175
  * A list of column names to include in the table grid.
176
176
  * If unspecified, all table columns are included.
@@ -148,13 +148,19 @@ export interface PlotAttributes {
148
148
  */
149
149
  grid?: boolean | string | ParamRef;
150
150
  /**
151
- * A textual label to show on the axis or legend; if null, show no label. By
152
- * default the scale label is inferred from channel definitions, possibly with
153
- * an arrow (↑, →, ↓, or ←) to indicate the direction of increasing value.
151
+ * The [aria-label attribute][1] on the SVG root.
154
152
  *
155
- * For axes and legends only.
153
+ * [1]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label
154
+ */
155
+ ariaLabel?: string | null;
156
+ /**
157
+ * The [aria-description attribute][1] on the SVG root.
158
+ *
159
+ * [1]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-description
156
160
  */
157
- label?: string | null | ParamRef;
161
+ ariaDescription?: string | null;
162
+ /** The default clip for all marks. */
163
+ clip?: 'frame' | 'sphere' | boolean | null | ParamRef;
158
164
  /**
159
165
  * The *x* scale type, affecting how the scale encodes abstract data, say by
160
166
  * applying a mathematical transformation. If null, the scale is disabled.
@@ -7,7 +7,7 @@ export type PlotDataInline = any[];
7
7
  /** Input data specification for a plot mark. */
8
8
  export interface PlotFrom {
9
9
  /** The name of the backing data table. */
10
- from: string;
10
+ from: string | ParamRef;
11
11
  /** A selection that filters the mark data. */
12
12
  filterBy?: ParamRef;
13
13
  /**
@@ -3,6 +3,7 @@ import { IntervalX, IntervalY } from './interactors/Interval1D.js';
3
3
  import { IntervalXY } from './interactors/Interval2D.js';
4
4
  import { NearestX, NearestY } from './interactors/Nearest.js';
5
5
  import { Pan, PanX, PanY, PanZoom, PanZoomX, PanZoomY } from './interactors/PanZoom.js';
6
+ import { Region } from './interactors/Region.js';
6
7
  import { Toggle, ToggleColor, ToggleX, ToggleY } from './interactors/Toggle.js';
7
8
  /** A plot interactor entry. */
8
- export type PlotInteractor = Highlight | IntervalX | IntervalY | IntervalXY | NearestX | NearestY | Toggle | ToggleX | ToggleY | ToggleColor | Pan | PanX | PanY | PanZoom | PanZoomX | PanZoomY;
9
+ export type PlotInteractor = Highlight | IntervalX | IntervalY | IntervalXY | NearestX | NearestY | Pan | PanX | PanY | PanZoom | PanZoomX | PanZoomY | Region | Toggle | ToggleX | ToggleY | ToggleColor;
@@ -23,5 +23,6 @@ import { RuleX, RuleY } from './marks/Rule.js';
23
23
  import { Text, TextX, TextY } from './marks/Text.js';
24
24
  import { TickX, TickY } from './marks/Tick.js';
25
25
  import { Spike, Vector, VectorX, VectorY } from './marks/Vector.js';
26
+ import { WaffleX, WaffleY } from './marks/Waffle.js';
26
27
  /** A plot mark entry. */
27
- export type PlotMark = Area | AreaX | AreaY | Arrow | AxisX | AxisY | AxisFx | AxisFy | GridX | GridY | GridFx | GridFy | BarX | BarY | Cell | CellX | CellY | Contour | DelaunayLink | DelaunayMesh | Hull | Voronoi | VoronoiMesh | DenseLine | Density | DensityX | DensityY | Dot | DotX | DotY | Circle | Hexagon | ErrorBarX | ErrorBarY | Frame | Geo | Graticule | Sphere | Hexbin | Hexgrid | Image | Line | LineX | LineY | Link | Raster | Heatmap | RasterTile | Rect | RectX | RectY | RegressionY | RuleX | RuleY | Text | TextX | TextY | TickX | TickY | Vector | VectorX | VectorY | Spike;
28
+ export type PlotMark = Area | AreaX | AreaY | Arrow | AxisX | AxisY | AxisFx | AxisFy | GridX | GridY | GridFx | GridFy | BarX | BarY | Cell | CellX | CellY | Contour | DelaunayLink | DelaunayMesh | Hull | Voronoi | VoronoiMesh | DenseLine | Density | DensityX | DensityY | Dot | DotX | DotY | Circle | Hexagon | ErrorBarX | ErrorBarY | Frame | Geo | Graticule | Sphere | Hexbin | Hexgrid | Image | Line | LineX | LineY | Link | Raster | Heatmap | RasterTile | Rect | RectX | RectY | RegressionY | RuleX | RuleY | Text | TextX | TextY | TickX | TickY | Vector | VectorX | VectorY | Spike | WaffleX | WaffleY;
@@ -13,7 +13,7 @@ export interface AggregateOptions {
13
13
  distinct?: boolean;
14
14
  }
15
15
  /** A transform argument. */
16
- type Arg = string | number | boolean;
16
+ type Arg = string | number | boolean | ParamRef;
17
17
  /** A zero argument transform signature. */
18
18
  type Arg0 = null | [];
19
19
  /** A single argument transform signature. */
@@ -80,6 +80,12 @@ export interface Bin {
80
80
  */
81
81
  offset?: number;
82
82
  }
83
+ export interface Column {
84
+ /**
85
+ * Intpret a string or param-value as a column reference.
86
+ */
87
+ column: Arg1;
88
+ }
83
89
  export interface DateMonth {
84
90
  /**
85
91
  * Transform a Date value to a month boundary for cyclic comparison.
@@ -325,7 +331,7 @@ export interface NthValue extends WindowOptions {
325
331
  nth_value: Arg2Opt;
326
332
  }
327
333
  /** A data transform that maps one column value to another. */
328
- export type ColumnTransform = Bin | DateMonth | DateMonthDay | DateDay | Centroid | CentroidX | CentroidY | GeoJSON;
334
+ export type ColumnTransform = Bin | Column | DateMonth | DateMonthDay | DateDay | Centroid | CentroidX | CentroidY | GeoJSON;
329
335
  /** An aggregate transform that combines multiple values. */
330
336
  export type AggregateTransform = Argmax | Argmin | Avg | Count | Max | Min | First | Last | Max | Min | Median | Mode | Product | Quantile | Stddev | StddevPop | Sum | Variance | VarPop;
331
337
  export type WindowTransform = RowNumber | Rank | DenseRank | PercentRank | CumeDist | NTile | Rank | Lag | Lead | FirstValue | LastValue | NthValue;
@@ -0,0 +1,27 @@
1
+ /** Styles for rectangular selection brushes. */
2
+ export interface BrushStyles {
3
+ /**
4
+ * The overall opacity of the brush rectangle.
5
+ */
6
+ opacity?: number;
7
+ /**
8
+ * The fill opacity of the brush rectangle.
9
+ */
10
+ fillOpacity?: number;
11
+ /**
12
+ * The stroke opacity of the brush rectangle.
13
+ */
14
+ strokeOpacity?: number;
15
+ /**
16
+ * The fill color of the brush rectangle.
17
+ */
18
+ fill?: string;
19
+ /**
20
+ * The stroke color of the brush rectangle.
21
+ */
22
+ stroke?: string;
23
+ /**
24
+ * The stroke dash array of the brush rectangle.
25
+ */
26
+ strokeDasharray?: string;
27
+ }
@@ -1,27 +1,5 @@
1
1
  import { ParamRef } from '../Param.js';
2
- /** Styles for rectangular selection brushes. */
3
- export interface BrushStyles {
4
- /**
5
- * The overall opacity of the brush rectangle.
6
- */
7
- opacity?: number;
8
- /**
9
- * The fill opacity of the brush rectangle.
10
- */
11
- fillOpacity?: number;
12
- /**
13
- * The stroke opacity of the brush rectangle.
14
- */
15
- strokeOpacity?: number;
16
- /**
17
- * The fill color of the brush rectangle.
18
- */
19
- fill?: string;
20
- /**
21
- * The stroke color of the brush rectangle.
22
- */
23
- stroke?: string;
24
- }
2
+ import { BrushStyles } from './BrushStyles.js';
25
3
  /** Options for 1D interval interactors. */
26
4
  export interface Interval1DOptions {
27
5
  /**
@@ -37,13 +15,14 @@ export interface Interval1DOptions {
37
15
  field?: string;
38
16
  /**
39
17
  * The size of an interative pixel (default `1`). Larger pixel sizes reduce
40
- * the brush resolution, which can reduce the size of data cube indexes.
18
+ * the brush resolution, which can reduce the size of pre-aggregated
19
+ * materialized views.
41
20
  */
42
21
  pixelSize?: number;
43
22
  /**
44
- * A flag indicating if peer (sibling) marks are when cross-filtering
45
- * (default `true`). If set, peer marks will not be filtered by this
46
- * interactor's selection in cross-filtering setups.
23
+ * A flag indicating if peer (sibling) marks are excluded when
24
+ * cross-filtering (default `true`). If set, peer marks will not be
25
+ * filtered by this interactor's selection in cross-filtering setups.
47
26
  */
48
27
  peers?: boolean;
49
28
  /**
@@ -1,5 +1,5 @@
1
1
  import { ParamRef } from '../Param.js';
2
- import { BrushStyles } from './Interval1D.js';
2
+ import { BrushStyles } from './BrushStyles.js';
3
3
  /** Options for 2D interval interactors. */
4
4
  export interface Interval2DOptions {
5
5
  /**
@@ -22,13 +22,14 @@ export interface Interval2DOptions {
22
22
  yfield?: string;
23
23
  /**
24
24
  * The size of an interative pixel (default `1`). Larger pixel sizes reduce
25
- * the brush resolution, which can reduce the size of data cube indexes.
25
+ * the brush resolution, which can reduce the size of pre-aggregated
26
+ * materialized views.
26
27
  */
27
28
  pixelSize?: number;
28
29
  /**
29
- * A flag indicating if peer (sibling) marks are when cross-filtering
30
- * (default `true`). If set, peer marks will not be filtered by this
31
- * interactor's selection in cross-filtering setups.
30
+ * A flag indicating if peer (sibling) marks are excluded when
31
+ * cross-filtering (default `true`). If set, peer marks will not be
32
+ * filtered by this interactor's selection in cross-filtering setups.
32
33
  */
33
34
  peers?: boolean;
34
35
  /**
@@ -0,0 +1,32 @@
1
+ import { ParamRef } from '../Param.js';
2
+ import { BrushStyles } from './BrushStyles.js';
3
+ /** Options for region interactors. */
4
+ export interface RegionOptions {
5
+ /**
6
+ * The output selection. A clause of the form
7
+ * `(field = value1) OR (field = value2) ...`
8
+ * is added for the currently selected values.
9
+ */
10
+ as: ParamRef;
11
+ /**
12
+ * The encoding channels over which to select values.
13
+ * For a selected mark, selection clauses will cover
14
+ * the backing data fields for each channel.
15
+ */
16
+ channels: string[];
17
+ /**
18
+ * A flag indicating if peer (sibling) marks are excluded when
19
+ * cross-filtering (default `true`). If set, peer marks will not be
20
+ * filtered by this interactor's selection in cross-filtering setups.
21
+ */
22
+ peers?: boolean;
23
+ /**
24
+ * CSS styles for the brush (SVG `rect`) element.
25
+ */
26
+ brush?: BrushStyles;
27
+ }
28
+ /** A rectangular region interactor. */
29
+ export interface Region extends RegionOptions {
30
+ /** Select aspects of individual marks within a 2D range. */
31
+ select: 'region';
32
+ }
@@ -8,9 +8,9 @@ export interface ToggleOptions {
8
8
  */
9
9
  as: ParamRef;
10
10
  /**
11
- * A flag indicating if peer (sibling) marks are when cross-filtering
12
- * (default `true`). If set, peer marks will not be filtered by this
13
- * interactor's selection in cross-filtering setups.
11
+ * A flag indicating if peer (sibling) marks are excluded when
12
+ * cross-filtering (default `true`). If set, peer marks will not be
13
+ * filtered by this interactor's selection in cross-filtering setups.
14
14
  */
15
15
  peers?: boolean;
16
16
  }
@@ -337,6 +337,11 @@ export interface MarkOptions {
337
337
  tip?: boolean | TipPointer | (TipOptions & {
338
338
  pointer?: TipPointer;
339
339
  }) | ParamRef;
340
+ /**
341
+ * Additional named channels, for example to include in a tooltip.
342
+ * Consists of (channel name, data field name) key-value pairs.
343
+ */
344
+ channels?: Record<string, string>;
340
345
  /**
341
346
  * How to clip the mark; one of:
342
347
  *
@@ -0,0 +1,58 @@
1
+ import { ParamRef } from '../Param.js';
2
+ import { BarXOptions, BarYOptions } from './Bar.js';
3
+ import { MarkData } from './Marks.js';
4
+ /** Options for the waffleX and waffleY mark. */
5
+ export interface WaffleOptions {
6
+ /** The number of cells per row or column; defaults to undefined for automatic. */
7
+ multiple?: number | ParamRef;
8
+ /** The quantity each cell represents; defaults to 1. */
9
+ unit?: number | ParamRef;
10
+ /** The gap in pixels between cells; defaults to 1. */
11
+ gap?: number | ParamRef;
12
+ /** If true, round to integers to avoid partial cells. */
13
+ round?: boolean | ParamRef;
14
+ }
15
+ /** The waffleX mark. */
16
+ export interface WaffleX extends MarkData, BarXOptions, WaffleOptions {
17
+ /**
18
+ * A horizontal waffle mark. The required *x* values should be quantitative,
19
+ * and the optional *y* values should be ordinal.
20
+ *
21
+ * If neither **x1** nor **x2** nor **interval** is specified, an implicit
22
+ * stackX transform is applied and **x** defaults to the identity function,
23
+ * assuming that *data* = [*x₀*, *x₁*, *x₂*, …]. Otherwise if an **interval**
24
+ * is specified, then **x1** and **x2** are derived from **x**, representing
25
+ * the lower and upper bound of the containing interval, respectively.
26
+ * Otherwise, if only one of **x1** or **x2** is specified, the other
27
+ * defaults to **x**, which defaults to zero.
28
+ *
29
+ * The optional **y** ordinal channel specifies the vertical position; it is
30
+ * typically bound to the *y* scale, which must be a *band* scale. If the
31
+ * **y** channel is not specified, the bar will span the vertical extent of
32
+ * the plot’s frame. Because a waffle represents a discrete number of square
33
+ * cells, it may not use all of the available bandwidth.
34
+ */
35
+ mark: 'waffleX';
36
+ }
37
+ /** The waffleY mark. */
38
+ export interface WaffleY extends MarkData, BarYOptions, WaffleOptions {
39
+ /**
40
+ * A vertical waffle mark. The required *y* values should be quantitative,
41
+ * and the optional *x* values should be ordinal.
42
+ *
43
+ * If neither **y1** nor **y2** nor **interval** is specified, an implicit
44
+ * stackY transform is applied and **y** defaults to the identity function,
45
+ * assuming that *data* = [*y₀*, *y₁*, *y₂*, …]. Otherwise if an **interval**
46
+ * is specified, then **y1** and **y2** are derived from **y**, representing
47
+ * the lower and upper bound of the containing interval, respectively.
48
+ * Otherwise, if only one of **y1** or **y2** is specified, the other
49
+ * defaults to **y**, which defaults to zero.
50
+ *
51
+ * The optional **x** ordinal channel specifies the horizontal position; it
52
+ * is typically bound to the *x* scale, which must be a *band* scale. If the
53
+ * **x** channel is not specified, the bar will span the horizontal extent of
54
+ * the plot’s frame. Because a waffle represents a discrete number of square
55
+ * cells, it may not use all of the available bandwidth.
56
+ */
57
+ mark: 'waffleY';
58
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uwdata/mosaic-spec",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "description": "Declarative specification of Mosaic-powered applications.",
5
5
  "keywords": [
6
6
  "mosaic",
@@ -25,7 +25,7 @@
25
25
  "prebuild": "rimraf dist && mkdir dist",
26
26
  "build": "npm run types && node ../../esbuild.js mosaic-spec",
27
27
  "lint": "eslint src test",
28
- "types": "tsc -p tsconfig.json && npm run schema",
28
+ "types": "tsc && npm run schema",
29
29
  "schema": "ts-json-schema-generator -f tsconfig.json -p src/spec/Spec.ts -t Spec --no-type-check --no-ref-encode --functions hide > dist/mosaic-schema.json",
30
30
  "pretest": "npm run prebuild && npm run types",
31
31
  "test": "vitest run && tsc -p jsconfig.json",
@@ -33,10 +33,10 @@
33
33
  "prepublishOnly": "npm run test && npm run lint && npm run build"
34
34
  },
35
35
  "dependencies": {
36
- "@uwdata/mosaic-core": "^0.11.0",
37
- "@uwdata/mosaic-sql": "^0.11.0",
38
- "@uwdata/vgplot": "^0.11.0",
36
+ "@uwdata/mosaic-core": "^0.12.1",
37
+ "@uwdata/mosaic-sql": "^0.12.1",
38
+ "@uwdata/vgplot": "^0.12.1",
39
39
  "ts-json-schema-generator": "^2.3.0"
40
40
  },
41
- "gitHead": "861d616f39926a1d2aee83b59dbdd70b0b3caf12"
41
+ "gitHead": "fe3a7c34352da54ec36a1ebf557846f46a649782"
42
42
  }
@@ -0,0 +1,21 @@
1
+ import { ASTNode } from './ASTNode.js';
2
+ import { COLUMPARAMREF } from '../constants.js';
3
+
4
+ export class ColumnParamRefNode extends ASTNode {
5
+ constructor(param) {
6
+ super(COLUMPARAMREF);
7
+ this.param = param;
8
+ }
9
+
10
+ instantiate(ctx) {
11
+ return ctx.api.column(this.param.instantiate(ctx));
12
+ }
13
+
14
+ codegen(ctx) {
15
+ return `${ctx.ns()}column(${this.param.codegen(ctx)})`;
16
+ }
17
+
18
+ toJSON() {
19
+ return `$${this.param.toJSON}`;
20
+ }
21
+ }
@@ -1,4 +1,4 @@
1
- import { create } from '@uwdata/mosaic-sql';
1
+ import { createTable } from '@uwdata/mosaic-sql';
2
2
  import { DATA } from '../constants.js';
3
3
  import { isArray, isString } from '../util.js';
4
4
  import { ASTNode } from './ASTNode.js';
@@ -162,7 +162,7 @@ export class TableDataNode extends QueryDataNode {
162
162
  instantiateQuery(ctx) {
163
163
  const { name, query, options } = this;
164
164
  if (query) {
165
- return ctx.api.create(name, query, options.instantiate(ctx));
165
+ return ctx.api.createTable(name, query, options.instantiate(ctx));
166
166
  }
167
167
  }
168
168
 
@@ -174,7 +174,7 @@ export class TableDataNode extends QueryDataNode {
174
174
  codegenQuery(ctx) {
175
175
  const { name, query, options } = this;
176
176
  if (query) {
177
- return `\`${create(name, query, options.instantiate(ctx))}\``;
177
+ return `\`${createTable(name, query, options.instantiate(ctx))}\``;
178
178
  }
179
179
  }
180
180
 
@@ -1,50 +1,47 @@
1
- import { AGG, EXPRESSION, SQL } from '../constants.js';
1
+ import { EXPRESSION, SQL } from '../constants.js';
2
2
  import { ASTNode } from './ASTNode.js';
3
+ import { ColumnParamRefNode } from './ColumnParamRefNode.js';
3
4
 
4
- export function parseExpression(spec, ctx) {
5
- const { label } = spec;
6
- const key = spec[SQL] ? SQL
7
- : spec[AGG] ? AGG
8
- : ctx.error('Unrecognized expression type', spec);
5
+ const tokenRegExp = /(\\'|\\"|"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\${1,2}\w+)/g;
9
6
 
10
- const expr = spec[key];
11
- const tokens = expr.split(/(\\'|\\"|"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\$\w+)/g);
7
+ export function parseExpression(spec, ctx) {
8
+ const expr = spec[SQL];
9
+ const tokens = expr.split(tokenRegExp);
12
10
  const spans = [''];
13
11
  const params = [];
14
12
 
15
13
  for (let i = 0, k = 0; i < tokens.length; ++i) {
16
14
  const tok = tokens[i];
17
15
  if (tok.startsWith('$')) {
18
- params[k] = ctx.maybeParam(tok);
16
+ params[k] = tok.startsWith('$$')
17
+ ? new ColumnParamRefNode(ctx.paramRef(tok.slice(2)))
18
+ : ctx.maybeParam(tok);
19
19
  spans[++k] = '';
20
20
  } else {
21
21
  spans[k] += tok;
22
22
  }
23
23
  }
24
24
 
25
- return new ExpressionNode(expr, spans, params, label, key === AGG);
25
+ return new ExpressionNode(expr, spans, params);
26
26
  }
27
27
 
28
28
  export class ExpressionNode extends ASTNode {
29
- constructor(value, spans, params, label, aggregate) {
29
+ constructor(value, spans, params) {
30
30
  super(EXPRESSION);
31
31
  this.value = value;
32
32
  this.spans = spans;
33
33
  this.params = params;
34
- this.label = label;
35
- this.aggregate = aggregate;
36
34
  }
37
35
 
38
36
  instantiate(ctx) {
39
- const { spans, params, label, aggregate } = this;
40
- const tag = ctx.api[aggregate ? AGG : SQL];
37
+ const { spans, params } = this;
38
+ const tag = ctx.api[SQL];
41
39
  const args = params.map(e => e.instantiate(ctx));
42
- return tag(spans, ...args).annotate({ label });
40
+ return tag(spans, ...args);
43
41
  }
44
42
 
45
43
  codegen(ctx) {
46
- const { spans, params, label, aggregate } = this;
47
- const method = aggregate ? AGG : SQL;
44
+ const { spans, params } = this;
48
45
 
49
46
  // reconstitute expression string
50
47
  let str = '';
@@ -54,12 +51,10 @@ export class ExpressionNode extends ASTNode {
54
51
  }
55
52
  str += spans[n];
56
53
 
57
- return `${ctx.ns()}${method}\`${str}\``
58
- + (label ? `.annotate({ label: ${JSON.stringify(label)} })` : '');
54
+ return `${ctx.ns()}${SQL}\`${str}\``;
59
55
  }
60
56
 
61
57
  toJSON() {
62
- const key = this.aggregate ? AGG : SQL;
63
- return { [key]: this.value };
58
+ return { [SQL]: this.value };
64
59
  }
65
60
  }
@@ -16,7 +16,7 @@ export function parseMarkData(spec, ctx) {
16
16
  }
17
17
 
18
18
  const { from: table, ...options } = spec;
19
- return new PlotFromNode(table, parseOptions(options, ctx));
19
+ return new PlotFromNode(ctx.maybeParam(table), parseOptions(options, ctx));
20
20
  }
21
21
 
22
22
  export class PlotFromNode extends ASTNode {
@@ -28,17 +28,17 @@ export class PlotFromNode extends ASTNode {
28
28
 
29
29
  instantiate(ctx) {
30
30
  const { table, options } = this;
31
- return ctx.api[FROM](table, options.instantiate(ctx));
31
+ return ctx.api[FROM](table.instantiate(ctx), options.instantiate(ctx));
32
32
  }
33
33
 
34
34
  codegen(ctx) {
35
- const { type, table, options } = this;
36
- const opt = options.codegen(ctx);
37
- return `${ctx.ns()}${type}("${table}"${opt ? ', ' + opt : ''})`;
35
+ const table = this.table.codegen(ctx);
36
+ const opt = this.options.codegen(ctx);
37
+ return `${ctx.ns()}${this.type}(${table}${opt ? ', ' + opt : ''})`;
38
38
  }
39
39
 
40
40
  toJSON() {
41
41
  const { type, table, options } = this;
42
- return { [type]: table, ...options.toJSON() };
42
+ return { [type]: table.toJSON(), ...options.toJSON() };
43
43
  }
44
44
  }
@@ -1,4 +1,4 @@
1
- import { AGG, MARK, SQL } from '../constants.js';
1
+ import { MARK, SQL } from '../constants.js';
2
2
  import { isObject } from '../util.js';
3
3
  import { ASTNode } from './ASTNode.js';
4
4
  import { parseExpression } from './ExpressionNode.js';
@@ -8,7 +8,7 @@ import { parseTransform } from './TransformNode.js';
8
8
 
9
9
  function maybeTransform(value, ctx) {
10
10
  if (isObject(value)) {
11
- return (value[SQL] || value[AGG])
11
+ return value[SQL]
12
12
  ? parseExpression(value, ctx)
13
13
  : parseTransform(value, ctx);
14
14
  }
@@ -2,8 +2,10 @@ import { ASTNode } from './ASTNode.js';
2
2
  import { TRANSFORM } from '../constants.js';
3
3
  import { parseOptions } from './OptionsNode.js';
4
4
 
5
- function toArray(value) {
6
- return value == null ? [] : [value].flat();
5
+ function toArray(value, ctx) {
6
+ return value == null
7
+ ? []
8
+ : [value].flat().map(v => ctx.maybeParam(v));
7
9
  }
8
10
 
9
11
  export function parseTransform(spec, ctx) {
@@ -20,14 +22,14 @@ export function parseTransform(spec, ctx) {
20
22
 
21
23
  if (name === 'bin') {
22
24
  const { bin, ...options } = spec;
23
- const [arg] = toArray(bin);
25
+ const [arg] = toArray(bin, ctx);
24
26
  return new BinTransformNode(name, arg, parseOptions(options, ctx));
25
27
  } else {
26
- const args = name === 'count' && !spec[name] ? [] : toArray(spec[name]);
28
+ const args = name === 'count' && !spec[name] ? [] : toArray(spec[name], ctx);
27
29
  const options = {
28
30
  distinct: spec.distinct,
29
- orderby: toArray(spec.orderby).map(v => ctx.maybeParam(v)),
30
- partitionby: toArray(spec.partitionby).map(v => ctx.maybeParam(v)),
31
+ orderby: toArray(spec.orderby, ctx),
32
+ partitionby: toArray(spec.partitionby, ctx),
31
33
  rows: spec.rows ? ctx.maybeParam(spec.rows) : null,
32
34
  range: spec.range ? ctx.maybeParam(spec.range) : null
33
35
  };
@@ -47,7 +49,7 @@ export class TransformNode extends ASTNode {
47
49
  const { name, args, options } = this;
48
50
  const { distinct, orderby, partitionby, rows, range } = options;
49
51
 
50
- let expr = ctx.api[name](...args);
52
+ let expr = ctx.api[name](...args.map(a => a.instantiate(ctx)));
51
53
  if (distinct) {
52
54
  expr = expr.distinct();
53
55
  }
@@ -70,7 +72,7 @@ export class TransformNode extends ASTNode {
70
72
  const { distinct, orderby, partitionby, rows, range } = options;
71
73
 
72
74
  let str = `${ctx.ns()}${name}(`
73
- + args.map(v => JSON.stringify(v)).join(', ')
75
+ + args.map(v => v.codegen(ctx)).join(', ')
74
76
  + ')';
75
77
 
76
78
  if (distinct) {
@@ -97,7 +99,7 @@ export class TransformNode extends ASTNode {
97
99
  const { name, args, options } = this;
98
100
  const { distinct, orderby, partitionby, rows, range } = options;
99
101
 
100
- const json = { [name]: simplify(args) };
102
+ const json = { [name]: simplify(args.map(v => v.toJSON())) };
101
103
 
102
104
  if (distinct) {
103
105
  json.distinct = true;
@@ -128,21 +130,21 @@ export class BinTransformNode extends ASTNode {
128
130
 
129
131
  instantiate(ctx) {
130
132
  const { name, arg, options } = this;
131
- return ctx.api[name](arg, options.instantiate(ctx));
133
+ return ctx.api[name](arg.instantiate(ctx), options.instantiate(ctx));
132
134
  }
133
135
 
134
136
  codegen(ctx) {
135
137
  const { name, arg, options } = this;
136
138
  const opt = options.codegen(ctx);
137
139
  return `${ctx.ns()}${name}(`
138
- + JSON.stringify(arg)
140
+ + arg.codegen(ctx)
139
141
  + (opt ? `, ${opt}` : '')
140
142
  + ')';
141
143
  }
142
144
 
143
145
  toJSON() {
144
146
  const { name, arg, options } = this;
145
- return { [name]: arg, ...options.toJSON() };
147
+ return { [name]: arg.toJSON(), ...options.toJSON() };
146
148
  }
147
149
  }
148
150