0
# Expression System
1
2
Vega's expression system provides a custom expression language for dynamic specifications with function registration, parsing, code generation, and runtime evaluation capabilities.
3
4
## Capabilities
5
6
### Expression Functions
7
8
Custom function registration and management for the expression language.
9
10
```typescript { .api }
11
/**
12
* Register or access expression functions
13
* @param name - Function name to register or access
14
* @param fn - Function implementation (if registering)
15
* @param visitor - Optional AST visitor function for optimization
16
* @returns Function implementation or registration result
17
*/
18
function expressionFunction(name: string, fn?: Function, visitor?: Function): any;
19
20
/**
21
* Register multiple expression functions at once
22
* @param functions - Object mapping function names to implementations
23
*/
24
function registerExpressionFunctions(functions: { [name: string]: Function }): void;
25
26
/**
27
* Get all registered expression functions
28
* @returns Object containing all registered functions
29
*/
30
function getExpressionFunctions(): { [name: string]: Function };
31
32
/**
33
* Check if expression function is registered
34
* @param name - Function name to check
35
* @returns True if function is registered
36
*/
37
function hasExpressionFunction(name: string): boolean;
38
```
39
40
### Expression Parsing
41
42
Expression parsing and AST generation.
43
44
```typescript { .api }
45
/**
46
* Parse expression string into AST
47
* @param expression - Expression string to parse
48
* @param options - Optional parsing configuration
49
* @returns Expression AST node
50
*/
51
function parseExpression(expression: string, options?: ParseOptions): ExpressionNode;
52
53
interface ParseOptions {
54
/** Allow assignment expressions */
55
assignment?: boolean;
56
57
/** Function whitelist */
58
functions?: string[];
59
60
/** Constant values */
61
constants?: { [name: string]: any };
62
63
/** Global scope variables */
64
globals?: string[];
65
66
/** Field accessors */
67
fields?: string[];
68
}
69
70
interface ExpressionNode {
71
/** Node type */
72
type: string;
73
74
/** Node value (for literals) */
75
value?: any;
76
77
/** Node name (for identifiers) */
78
name?: string;
79
80
/** Child nodes */
81
arguments?: ExpressionNode[];
82
83
/** Left operand (for binary expressions) */
84
left?: ExpressionNode;
85
86
/** Right operand (for binary expressions) */
87
right?: ExpressionNode;
88
89
/** Operand (for unary expressions) */
90
argument?: ExpressionNode;
91
92
/** Operator symbol */
93
operator?: string;
94
95
/** Object for member expressions */
96
object?: ExpressionNode;
97
98
/** Property for member expressions */
99
property?: ExpressionNode;
100
101
/** Computed property flag */
102
computed?: boolean;
103
104
/** Test condition (for conditional expressions) */
105
test?: ExpressionNode;
106
107
/** Consequent (for conditional expressions) */
108
consequent?: ExpressionNode;
109
110
/** Alternate (for conditional expressions) */
111
alternate?: ExpressionNode;
112
}
113
```
114
115
### Code Generation
116
117
Expression compilation and code generation.
118
119
```typescript { .api }
120
/**
121
* Generate executable JavaScript function from expression AST
122
* @param ast - Expression AST node
123
* @param options - Optional code generation options
124
* @returns Compiled JavaScript function
125
*/
126
function codegenExpression(ast: ExpressionNode, options?: CodegenOptions): Function;
127
128
/**
129
* Generate JavaScript code string from expression AST
130
* @param ast - Expression AST node
131
* @param options - Optional code generation options
132
* @returns JavaScript code string
133
*/
134
function codegenExpressionString(ast: ExpressionNode, options?: CodegenOptions): string;
135
136
interface CodegenOptions {
137
/** Variable whitelist */
138
whitelist?: string[];
139
140
/** Global variables */
141
globals?: { [name: string]: any };
142
143
/** Function registry */
144
functions?: { [name: string]: Function };
145
146
/** Field accessor function */
147
fieldFunction?: string;
148
149
/** Generate debug information */
150
debug?: boolean;
151
}
152
```
153
154
### Built-in Expression Functions
155
156
Core functions available in the expression language.
157
158
```typescript { .api }
159
/** Mathematical functions */
160
interface MathFunctions {
161
/** Absolute value */
162
abs(x: number): number;
163
164
/** Arc cosine */
165
acos(x: number): number;
166
167
/** Arc sine */
168
asin(x: number): number;
169
170
/** Arc tangent */
171
atan(x: number): number;
172
173
/** Arc tangent of y/x */
174
atan2(y: number, x: number): number;
175
176
/** Ceiling function */
177
ceil(x: number): number;
178
179
/** Clamp value to range */
180
clamp(value: number, min: number, max: number): number;
181
182
/** Cosine */
183
cos(x: number): number;
184
185
/** Exponential function */
186
exp(x: number): number;
187
188
/** Floor function */
189
floor(x: number): number;
190
191
/** Natural logarithm */
192
log(x: number): number;
193
194
/** Maximum value */
195
max(...values: number[]): number;
196
197
/** Minimum value */
198
min(...values: number[]): number;
199
200
/** Power function */
201
pow(x: number, y: number): number;
202
203
/** Random number [0,1) */
204
random(): number;
205
206
/** Round to nearest integer */
207
round(x: number): number;
208
209
/** Sine function */
210
sin(x: number): number;
211
212
/** Square root */
213
sqrt(x: number): number;
214
215
/** Tangent */
216
tan(x: number): number;
217
}
218
219
/** String functions */
220
interface StringFunctions {
221
/** Get string length */
222
length(str: string): number;
223
224
/** Convert to lowercase */
225
lower(str: string): string;
226
227
/** Pad string to length */
228
pad(str: string, length: number, character?: string, align?: 'left' | 'right' | 'center'): string;
229
230
/** Test regular expression */
231
test(str: string, regexp: string | RegExp, flags?: string): boolean;
232
233
/** Extract substring */
234
substring(str: string, start: number, end?: number): string;
235
236
/** Trim whitespace */
237
trim(str: string): string;
238
239
/** Convert to uppercase */
240
upper(str: string): string;
241
242
/** Replace text with regexp */
243
replace(str: string, pattern: string | RegExp, replacement: string): string;
244
245
/** Split string */
246
split(str: string, separator: string | RegExp, limit?: number): string[];
247
248
/** Find substring index */
249
indexof(str: string, substring: string): number;
250
251
/** Get last index of substring */
252
lastindexof(str: string, substring: string): number;
253
254
/** Extract substring by length */
255
slice(str: string, start: number, length?: number): string;
256
}
257
258
/** Date/Time functions */
259
interface DateTimeFunctions {
260
/** Create new date */
261
datetime(year?: number, month?: number, day?: number, hour?: number, minute?: number, second?: number, millisecond?: number): Date;
262
263
/** Get current timestamp */
264
now(): number;
265
266
/** Parse date string */
267
date(dateString: string): Date;
268
269
/** Get day of month */
270
day(date: Date): number;
271
272
/** Get day of year */
273
dayofyear(date: Date): number;
274
275
/** Get hours */
276
hours(date: Date): number;
277
278
/** Get milliseconds */
279
milliseconds(date: Date): number;
280
281
/** Get minutes */
282
minutes(date: Date): number;
283
284
/** Get month */
285
month(date: Date): number;
286
287
/** Get quarter */
288
quarter(date: Date): number;
289
290
/** Get seconds */
291
seconds(date: Date): number;
292
293
/** Get timestamp */
294
time(date: Date): number;
295
296
/** Get timezone offset */
297
timezoneoffset(date: Date): number;
298
299
/** Get year */
300
year(date: Date): number;
301
302
/** UTC versions of date functions */
303
utcday(date: Date): number;
304
utchours(date: Date): number;
305
utcmilliseconds(date: Date): number;
306
utcminutes(date: Date): number;
307
utcmonth(date: Date): number;
308
utcseconds(date: Date): number;
309
utcyear(date: Date): number;
310
}
311
312
/** Array functions */
313
interface ArrayFunctions {
314
/** Get array length */
315
length(array: any[]): number;
316
317
/** Extract array slice */
318
slice(array: any[], start: number, end?: number): any[];
319
320
/** Reverse array */
321
reverse(array: any[]): any[];
322
323
/** Join array elements */
324
join(array: any[], separator?: string): string;
325
326
/** Find index of element */
327
indexof(array: any[], element: any): number;
328
329
/** Check if array includes element */
330
includes(array: any[], element: any): boolean;
331
332
/** Map array elements */
333
map(array: any[], mapper: string): any[];
334
335
/** Filter array elements */
336
filter(array: any[], predicate: string): any[];
337
338
/** Reduce array */
339
reduce(array: any[], reducer: string, initial?: any): any;
340
341
/** Sort array */
342
sort(array: any[], comparator?: string): any[];
343
}
344
345
/** Type checking functions */
346
interface TypeFunctions {
347
/** Check if value is array */
348
isArray(value: any): boolean;
349
350
/** Check if value is boolean */
351
isBoolean(value: any): boolean;
352
353
/** Check if value is date */
354
isDate(value: any): boolean;
355
356
/** Check if value is finite number */
357
isFinite(value: any): boolean;
358
359
/** Check if value is NaN */
360
isNaN(value: any): boolean;
361
362
/** Check if value is number */
363
isNumber(value: any): boolean;
364
365
/** Check if value is object */
366
isObject(value: any): boolean;
367
368
/** Check if value is string */
369
isString(value: any): boolean;
370
371
/** Check if value is valid (not null/undefined) */
372
isValid(value: any): boolean;
373
}
374
375
/** Conversion functions */
376
interface ConversionFunctions {
377
/** Convert to boolean */
378
toBoolean(value: any): boolean;
379
380
/** Convert to date */
381
toDate(value: any): Date;
382
383
/** Convert to number */
384
toNumber(value: any): number;
385
386
/** Convert to string */
387
toString(value: any): string;
388
}
389
390
/** Scale functions */
391
interface ScaleFunctions {
392
/** Apply scale function */
393
scale(scaleName: string, value: any): any;
394
395
/** Invert scale function */
396
invert(scaleName: string, value: any): any;
397
398
/** Get scale bandwidth */
399
bandwidth(scaleName: string): number;
400
401
/** Get scale copy */
402
copy(scaleName: string): any;
403
404
/** Get scale domain */
405
domain(scaleName: string): any[];
406
407
/** Get scale range */
408
range(scaleName: string): any[];
409
}
410
411
/** Data access functions */
412
interface DataFunctions {
413
/** Get data array */
414
data(datasetName: string): any[];
415
416
/** Get data length */
417
length(datasetName: string): number;
418
419
/** Check if data exists */
420
indata(datasetName: string, field: string, value: any): boolean;
421
}
422
423
/** Color functions */
424
interface ColorFunctions {
425
/** Create RGB color */
426
rgb(r: number, g: number, b: number): string;
427
428
/** Create HSL color */
429
hsl(h: number, s: number, l: number): string;
430
431
/** Create Lab color */
432
lab(l: number, a: number, b: number): string;
433
434
/** Create HCL color */
435
hcl(h: number, c: number, l: number): string;
436
}
437
```
438
439
### Expression Context
440
441
Runtime context for expression evaluation.
442
443
```typescript { .api }
444
/**
445
* Expression evaluation context
446
*/
447
interface ExpressionContext {
448
/** Current data tuple */
449
datum?: any;
450
451
/** Event object (for event expressions) */
452
event?: any;
453
454
/** Item object (for mark expressions) */
455
item?: any;
456
457
/** Group object (for group expressions) */
458
group?: any;
459
460
/** Signal values */
461
signals?: { [name: string]: any };
462
463
/** Data sources */
464
data?: { [name: string]: any[] };
465
466
/** Scale functions */
467
scales?: { [name: string]: Function };
468
469
/** Custom functions */
470
functions?: { [name: string]: Function };
471
472
/** Global constants */
473
constants?: { [name: string]: any };
474
}
475
476
/**
477
* Create expression evaluator function
478
* @param expression - Expression string or AST
479
* @param context - Evaluation context
480
* @returns Evaluator function
481
*/
482
function createExpressionEvaluator(
483
expression: string | ExpressionNode,
484
context: ExpressionContext
485
): (datum?: any, event?: any) => any;
486
```
487
488
## Usage Examples
489
490
### Basic Expression Evaluation
491
492
```typescript
493
import { parseExpression, codegenExpression } from "vega";
494
495
// Parse and compile expression
496
const ast = parseExpression('datum.value * 2 + 10');
497
const evaluator = codegenExpression(ast);
498
499
// Evaluate with data
500
const result = evaluator({ datum: { value: 5 } });
501
console.log(result); // 20
502
```
503
504
### Custom Function Registration
505
506
```typescript
507
import { expressionFunction } from "vega";
508
509
// Register custom functions
510
expressionFunction('formatCurrency', (value) => {
511
return new Intl.NumberFormat('en-US', {
512
style: 'currency',
513
currency: 'USD'
514
}).format(value);
515
});
516
517
expressionFunction('clampPercent', (value) => {
518
return Math.max(0, Math.min(100, value));
519
});
520
521
// Use in expressions
522
const ast = parseExpression('formatCurrency(datum.sales)');
523
const formatter = codegenExpression(ast);
524
525
const formatted = formatter({ datum: { sales: 1234.56 } });
526
console.log(formatted); // "$1,234.56"
527
```
528
529
### Complex Expression Parsing
530
531
```typescript
532
import { parseExpression, codegenExpression } from "vega";
533
534
// Complex conditional expression
535
const expression = `
536
datum.category === 'A' ? 'red' :
537
datum.category === 'B' ? 'blue' :
538
datum.value > 100 ? 'green' : 'gray'
539
`;
540
541
const ast = parseExpression(expression);
542
const evaluator = codegenExpression(ast);
543
544
// Test with different data
545
const testData = [
546
{ category: 'A', value: 50 },
547
{ category: 'B', value: 150 },
548
{ category: 'C', value: 200 },
549
{ category: 'C', value: 25 }
550
];
551
552
testData.forEach(datum => {
553
console.log(datum, '->', evaluator({ datum }));
554
});
555
// { category: 'A', value: 50 } -> 'red'
556
// { category: 'B', value: 150 } -> 'blue'
557
// { category: 'C', value: 200 } -> 'green'
558
// { category: 'C', value: 25 } -> 'gray'
559
```
560
561
### Expression with Context
562
563
```typescript
564
import { parseExpression, codegenExpression } from "vega";
565
566
// Expression using signals and scales
567
const expression = 'scale("xscale", datum.x) + signal("offset")';
568
569
const ast = parseExpression(expression, {
570
functions: ['scale', 'signal'],
571
globals: ['datum']
572
});
573
574
const evaluator = codegenExpression(ast, {
575
functions: {
576
scale: (name, value) => {
577
if (name === 'xscale') {
578
return value * 10; // Simple linear scale
579
}
580
return value;
581
},
582
signal: (name) => {
583
if (name === 'offset') {
584
return 50;
585
}
586
return 0;
587
}
588
}
589
});
590
591
const result = evaluator({ datum: { x: 5 } });
592
console.log(result); // 100 (5 * 10 + 50)
593
```
594
595
### Array and String Operations
596
597
```typescript
598
import { parseExpression, codegenExpression } from "vega";
599
600
// Array operations
601
const arrayExpr = parseExpression('slice(datum.values, 0, 3)');
602
const arrayEval = codegenExpression(arrayExpr);
603
604
const arrayResult = arrayEval({
605
datum: { values: [1, 2, 3, 4, 5] }
606
});
607
console.log(arrayResult); // [1, 2, 3]
608
609
// String operations
610
const stringExpr = parseExpression('upper(substring(datum.name, 0, 3))');
611
const stringEval = codegenExpression(stringExpr);
612
613
const stringResult = stringEval({
614
datum: { name: 'hello world' }
615
});
616
console.log(stringResult); // 'HEL'
617
```
618
619
### Date/Time Expressions
620
621
```typescript
622
import { parseExpression, codegenExpression } from "vega";
623
624
// Date extraction
625
const dateExpr = parseExpression('year(datum.timestamp)');
626
const dateEval = codegenExpression(dateExpr);
627
628
const dateResult = dateEval({
629
datum: { timestamp: new Date('2023-06-15') }
630
});
631
console.log(dateResult); // 2023
632
633
// Date formatting
634
const formatExpr = parseExpression(`
635
month(datum.date) + '/' + day(datum.date) + '/' + year(datum.date)
636
`);
637
const formatEval = codegenExpression(formatExpr);
638
639
const formatResult = formatEval({
640
datum: { date: new Date('2023-06-15') }
641
});
642
console.log(formatResult); // '6/15/2023'
643
```
644
645
### Mathematical Expressions
646
647
```typescript
648
import { parseExpression, codegenExpression } from "vega";
649
650
// Complex mathematical expression
651
const mathExpr = parseExpression(`
652
sqrt(pow(datum.x - datum.centerX, 2) + pow(datum.y - datum.centerY, 2))
653
`);
654
const mathEval = codegenExpression(mathExpr);
655
656
// Calculate distance from center
657
const distance = mathEval({
658
datum: {
659
x: 10,
660
y: 8,
661
centerX: 5,
662
centerY: 3
663
}
664
});
665
console.log(distance); // ~7.07
666
```
667
668
### Type Checking and Conversion
669
670
```typescript
671
import { parseExpression, codegenExpression } from "vega";
672
673
// Type checking and conversion
674
const typeExpr = parseExpression(`
675
isNumber(datum.value) ? toNumber(datum.value) : 0
676
`);
677
const typeEval = codegenExpression(typeExpr);
678
679
const testValues = [
680
{ value: "123" },
681
{ value: "abc" },
682
{ value: 456 },
683
{ value: null }
684
];
685
686
testValues.forEach(datum => {
687
console.log(datum.value, '->', typeEval({ datum }));
688
});
689
// "123" -> 123
690
// "abc" -> 0
691
// 456 -> 456
692
// null -> 0
693
```
694
695
### Data Access Expressions
696
697
```typescript
698
import { expressionFunction, parseExpression, codegenExpression } from "vega";
699
700
// Register data access function
701
expressionFunction('indata', (dataset, field, value) => {
702
// Mock data lookup
703
const mockData = {
704
'categories': [
705
{ name: 'A', active: true },
706
{ name: 'B', active: false },
707
{ name: 'C', active: true }
708
]
709
};
710
711
const data = mockData[dataset] || [];
712
return data.some(d => d[field] === value);
713
});
714
715
// Use data lookup in expression
716
const expr = parseExpression(`
717
indata('categories', 'name', datum.category) ? 'valid' : 'invalid'
718
`);
719
const eval = codegenExpression(expr);
720
721
const result = eval({ datum: { category: 'A' } });
722
console.log(result); // 'valid'
723
```
724
725
### Whitelist and Security
726
727
```typescript
728
import { parseExpression, codegenExpression } from "vega";
729
730
// Restricted parsing with whitelist
731
const restrictedAST = parseExpression('datum.value + offset', {
732
functions: ['datum'], // Only allow datum access
733
globals: ['offset'], // Allow offset global
734
fields: ['value'] // Only allow value field
735
});
736
737
const restrictedEval = codegenExpression(restrictedAST, {
738
whitelist: ['datum', 'offset'], // Restrict variable access
739
globals: { offset: 10 } // Provide global values
740
});
741
742
const safeResult = restrictedEval({ datum: { value: 5 } });
743
console.log(safeResult); // 15
744
```