A declarative visualization grammar for creating interactive data visualizations through JSON specifications.
—
Vega's expression system provides a custom expression language for dynamic specifications with function registration, parsing, code generation, and runtime evaluation capabilities.
Custom function registration and management for the expression language.
/**
* Register or access expression functions
* @param name - Function name to register or access
* @param fn - Function implementation (if registering)
* @param visitor - Optional AST visitor function for optimization
* @returns Function implementation or registration result
*/
function expressionFunction(name: string, fn?: Function, visitor?: Function): any;
/**
* Register multiple expression functions at once
* @param functions - Object mapping function names to implementations
*/
function registerExpressionFunctions(functions: { [name: string]: Function }): void;
/**
* Get all registered expression functions
* @returns Object containing all registered functions
*/
function getExpressionFunctions(): { [name: string]: Function };
/**
* Check if expression function is registered
* @param name - Function name to check
* @returns True if function is registered
*/
function hasExpressionFunction(name: string): boolean;Expression parsing and AST generation.
/**
* Parse expression string into AST
* @param expression - Expression string to parse
* @param options - Optional parsing configuration
* @returns Expression AST node
*/
function parseExpression(expression: string, options?: ParseOptions): ExpressionNode;
interface ParseOptions {
/** Allow assignment expressions */
assignment?: boolean;
/** Function whitelist */
functions?: string[];
/** Constant values */
constants?: { [name: string]: any };
/** Global scope variables */
globals?: string[];
/** Field accessors */
fields?: string[];
}
interface ExpressionNode {
/** Node type */
type: string;
/** Node value (for literals) */
value?: any;
/** Node name (for identifiers) */
name?: string;
/** Child nodes */
arguments?: ExpressionNode[];
/** Left operand (for binary expressions) */
left?: ExpressionNode;
/** Right operand (for binary expressions) */
right?: ExpressionNode;
/** Operand (for unary expressions) */
argument?: ExpressionNode;
/** Operator symbol */
operator?: string;
/** Object for member expressions */
object?: ExpressionNode;
/** Property for member expressions */
property?: ExpressionNode;
/** Computed property flag */
computed?: boolean;
/** Test condition (for conditional expressions) */
test?: ExpressionNode;
/** Consequent (for conditional expressions) */
consequent?: ExpressionNode;
/** Alternate (for conditional expressions) */
alternate?: ExpressionNode;
}Expression compilation and code generation.
/**
* Generate executable JavaScript function from expression AST
* @param ast - Expression AST node
* @param options - Optional code generation options
* @returns Compiled JavaScript function
*/
function codegenExpression(ast: ExpressionNode, options?: CodegenOptions): Function;
/**
* Generate JavaScript code string from expression AST
* @param ast - Expression AST node
* @param options - Optional code generation options
* @returns JavaScript code string
*/
function codegenExpressionString(ast: ExpressionNode, options?: CodegenOptions): string;
interface CodegenOptions {
/** Variable whitelist */
whitelist?: string[];
/** Global variables */
globals?: { [name: string]: any };
/** Function registry */
functions?: { [name: string]: Function };
/** Field accessor function */
fieldFunction?: string;
/** Generate debug information */
debug?: boolean;
}Core functions available in the expression language.
/** Mathematical functions */
interface MathFunctions {
/** Absolute value */
abs(x: number): number;
/** Arc cosine */
acos(x: number): number;
/** Arc sine */
asin(x: number): number;
/** Arc tangent */
atan(x: number): number;
/** Arc tangent of y/x */
atan2(y: number, x: number): number;
/** Ceiling function */
ceil(x: number): number;
/** Clamp value to range */
clamp(value: number, min: number, max: number): number;
/** Cosine */
cos(x: number): number;
/** Exponential function */
exp(x: number): number;
/** Floor function */
floor(x: number): number;
/** Natural logarithm */
log(x: number): number;
/** Maximum value */
max(...values: number[]): number;
/** Minimum value */
min(...values: number[]): number;
/** Power function */
pow(x: number, y: number): number;
/** Random number [0,1) */
random(): number;
/** Round to nearest integer */
round(x: number): number;
/** Sine function */
sin(x: number): number;
/** Square root */
sqrt(x: number): number;
/** Tangent */
tan(x: number): number;
}
/** String functions */
interface StringFunctions {
/** Get string length */
length(str: string): number;
/** Convert to lowercase */
lower(str: string): string;
/** Pad string to length */
pad(str: string, length: number, character?: string, align?: 'left' | 'right' | 'center'): string;
/** Test regular expression */
test(str: string, regexp: string | RegExp, flags?: string): boolean;
/** Extract substring */
substring(str: string, start: number, end?: number): string;
/** Trim whitespace */
trim(str: string): string;
/** Convert to uppercase */
upper(str: string): string;
/** Replace text with regexp */
replace(str: string, pattern: string | RegExp, replacement: string): string;
/** Split string */
split(str: string, separator: string | RegExp, limit?: number): string[];
/** Find substring index */
indexof(str: string, substring: string): number;
/** Get last index of substring */
lastindexof(str: string, substring: string): number;
/** Extract substring by length */
slice(str: string, start: number, length?: number): string;
}
/** Date/Time functions */
interface DateTimeFunctions {
/** Create new date */
datetime(year?: number, month?: number, day?: number, hour?: number, minute?: number, second?: number, millisecond?: number): Date;
/** Get current timestamp */
now(): number;
/** Parse date string */
date(dateString: string): Date;
/** Get day of month */
day(date: Date): number;
/** Get day of year */
dayofyear(date: Date): number;
/** Get hours */
hours(date: Date): number;
/** Get milliseconds */
milliseconds(date: Date): number;
/** Get minutes */
minutes(date: Date): number;
/** Get month */
month(date: Date): number;
/** Get quarter */
quarter(date: Date): number;
/** Get seconds */
seconds(date: Date): number;
/** Get timestamp */
time(date: Date): number;
/** Get timezone offset */
timezoneoffset(date: Date): number;
/** Get year */
year(date: Date): number;
/** UTC versions of date functions */
utcday(date: Date): number;
utchours(date: Date): number;
utcmilliseconds(date: Date): number;
utcminutes(date: Date): number;
utcmonth(date: Date): number;
utcseconds(date: Date): number;
utcyear(date: Date): number;
}
/** Array functions */
interface ArrayFunctions {
/** Get array length */
length(array: any[]): number;
/** Extract array slice */
slice(array: any[], start: number, end?: number): any[];
/** Reverse array */
reverse(array: any[]): any[];
/** Join array elements */
join(array: any[], separator?: string): string;
/** Find index of element */
indexof(array: any[], element: any): number;
/** Check if array includes element */
includes(array: any[], element: any): boolean;
/** Map array elements */
map(array: any[], mapper: string): any[];
/** Filter array elements */
filter(array: any[], predicate: string): any[];
/** Reduce array */
reduce(array: any[], reducer: string, initial?: any): any;
/** Sort array */
sort(array: any[], comparator?: string): any[];
}
/** Type checking functions */
interface TypeFunctions {
/** Check if value is array */
isArray(value: any): boolean;
/** Check if value is boolean */
isBoolean(value: any): boolean;
/** Check if value is date */
isDate(value: any): boolean;
/** Check if value is finite number */
isFinite(value: any): boolean;
/** Check if value is NaN */
isNaN(value: any): boolean;
/** Check if value is number */
isNumber(value: any): boolean;
/** Check if value is object */
isObject(value: any): boolean;
/** Check if value is string */
isString(value: any): boolean;
/** Check if value is valid (not null/undefined) */
isValid(value: any): boolean;
}
/** Conversion functions */
interface ConversionFunctions {
/** Convert to boolean */
toBoolean(value: any): boolean;
/** Convert to date */
toDate(value: any): Date;
/** Convert to number */
toNumber(value: any): number;
/** Convert to string */
toString(value: any): string;
}
/** Scale functions */
interface ScaleFunctions {
/** Apply scale function */
scale(scaleName: string, value: any): any;
/** Invert scale function */
invert(scaleName: string, value: any): any;
/** Get scale bandwidth */
bandwidth(scaleName: string): number;
/** Get scale copy */
copy(scaleName: string): any;
/** Get scale domain */
domain(scaleName: string): any[];
/** Get scale range */
range(scaleName: string): any[];
}
/** Data access functions */
interface DataFunctions {
/** Get data array */
data(datasetName: string): any[];
/** Get data length */
length(datasetName: string): number;
/** Check if data exists */
indata(datasetName: string, field: string, value: any): boolean;
}
/** Color functions */
interface ColorFunctions {
/** Create RGB color */
rgb(r: number, g: number, b: number): string;
/** Create HSL color */
hsl(h: number, s: number, l: number): string;
/** Create Lab color */
lab(l: number, a: number, b: number): string;
/** Create HCL color */
hcl(h: number, c: number, l: number): string;
}Runtime context for expression evaluation.
/**
* Expression evaluation context
*/
interface ExpressionContext {
/** Current data tuple */
datum?: any;
/** Event object (for event expressions) */
event?: any;
/** Item object (for mark expressions) */
item?: any;
/** Group object (for group expressions) */
group?: any;
/** Signal values */
signals?: { [name: string]: any };
/** Data sources */
data?: { [name: string]: any[] };
/** Scale functions */
scales?: { [name: string]: Function };
/** Custom functions */
functions?: { [name: string]: Function };
/** Global constants */
constants?: { [name: string]: any };
}
/**
* Create expression evaluator function
* @param expression - Expression string or AST
* @param context - Evaluation context
* @returns Evaluator function
*/
function createExpressionEvaluator(
expression: string | ExpressionNode,
context: ExpressionContext
): (datum?: any, event?: any) => any;import { parseExpression, codegenExpression } from "vega";
// Parse and compile expression
const ast = parseExpression('datum.value * 2 + 10');
const evaluator = codegenExpression(ast);
// Evaluate with data
const result = evaluator({ datum: { value: 5 } });
console.log(result); // 20import { expressionFunction } from "vega";
// Register custom functions
expressionFunction('formatCurrency', (value) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(value);
});
expressionFunction('clampPercent', (value) => {
return Math.max(0, Math.min(100, value));
});
// Use in expressions
const ast = parseExpression('formatCurrency(datum.sales)');
const formatter = codegenExpression(ast);
const formatted = formatter({ datum: { sales: 1234.56 } });
console.log(formatted); // "$1,234.56"import { parseExpression, codegenExpression } from "vega";
// Complex conditional expression
const expression = `
datum.category === 'A' ? 'red' :
datum.category === 'B' ? 'blue' :
datum.value > 100 ? 'green' : 'gray'
`;
const ast = parseExpression(expression);
const evaluator = codegenExpression(ast);
// Test with different data
const testData = [
{ category: 'A', value: 50 },
{ category: 'B', value: 150 },
{ category: 'C', value: 200 },
{ category: 'C', value: 25 }
];
testData.forEach(datum => {
console.log(datum, '->', evaluator({ datum }));
});
// { category: 'A', value: 50 } -> 'red'
// { category: 'B', value: 150 } -> 'blue'
// { category: 'C', value: 200 } -> 'green'
// { category: 'C', value: 25 } -> 'gray'import { parseExpression, codegenExpression } from "vega";
// Expression using signals and scales
const expression = 'scale("xscale", datum.x) + signal("offset")';
const ast = parseExpression(expression, {
functions: ['scale', 'signal'],
globals: ['datum']
});
const evaluator = codegenExpression(ast, {
functions: {
scale: (name, value) => {
if (name === 'xscale') {
return value * 10; // Simple linear scale
}
return value;
},
signal: (name) => {
if (name === 'offset') {
return 50;
}
return 0;
}
}
});
const result = evaluator({ datum: { x: 5 } });
console.log(result); // 100 (5 * 10 + 50)import { parseExpression, codegenExpression } from "vega";
// Array operations
const arrayExpr = parseExpression('slice(datum.values, 0, 3)');
const arrayEval = codegenExpression(arrayExpr);
const arrayResult = arrayEval({
datum: { values: [1, 2, 3, 4, 5] }
});
console.log(arrayResult); // [1, 2, 3]
// String operations
const stringExpr = parseExpression('upper(substring(datum.name, 0, 3))');
const stringEval = codegenExpression(stringExpr);
const stringResult = stringEval({
datum: { name: 'hello world' }
});
console.log(stringResult); // 'HEL'import { parseExpression, codegenExpression } from "vega";
// Date extraction
const dateExpr = parseExpression('year(datum.timestamp)');
const dateEval = codegenExpression(dateExpr);
const dateResult = dateEval({
datum: { timestamp: new Date('2023-06-15') }
});
console.log(dateResult); // 2023
// Date formatting
const formatExpr = parseExpression(`
month(datum.date) + '/' + day(datum.date) + '/' + year(datum.date)
`);
const formatEval = codegenExpression(formatExpr);
const formatResult = formatEval({
datum: { date: new Date('2023-06-15') }
});
console.log(formatResult); // '6/15/2023'import { parseExpression, codegenExpression } from "vega";
// Complex mathematical expression
const mathExpr = parseExpression(`
sqrt(pow(datum.x - datum.centerX, 2) + pow(datum.y - datum.centerY, 2))
`);
const mathEval = codegenExpression(mathExpr);
// Calculate distance from center
const distance = mathEval({
datum: {
x: 10,
y: 8,
centerX: 5,
centerY: 3
}
});
console.log(distance); // ~7.07import { parseExpression, codegenExpression } from "vega";
// Type checking and conversion
const typeExpr = parseExpression(`
isNumber(datum.value) ? toNumber(datum.value) : 0
`);
const typeEval = codegenExpression(typeExpr);
const testValues = [
{ value: "123" },
{ value: "abc" },
{ value: 456 },
{ value: null }
];
testValues.forEach(datum => {
console.log(datum.value, '->', typeEval({ datum }));
});
// "123" -> 123
// "abc" -> 0
// 456 -> 456
// null -> 0import { expressionFunction, parseExpression, codegenExpression } from "vega";
// Register data access function
expressionFunction('indata', (dataset, field, value) => {
// Mock data lookup
const mockData = {
'categories': [
{ name: 'A', active: true },
{ name: 'B', active: false },
{ name: 'C', active: true }
]
};
const data = mockData[dataset] || [];
return data.some(d => d[field] === value);
});
// Use data lookup in expression
const expr = parseExpression(`
indata('categories', 'name', datum.category) ? 'valid' : 'invalid'
`);
const eval = codegenExpression(expr);
const result = eval({ datum: { category: 'A' } });
console.log(result); // 'valid'import { parseExpression, codegenExpression } from "vega";
// Restricted parsing with whitelist
const restrictedAST = parseExpression('datum.value + offset', {
functions: ['datum'], // Only allow datum access
globals: ['offset'], // Allow offset global
fields: ['value'] // Only allow value field
});
const restrictedEval = codegenExpression(restrictedAST, {
whitelist: ['datum', 'offset'], // Restrict variable access
globals: { offset: 10 } // Provide global values
});
const safeResult = restrictedEval({ datum: { value: 5 } });
console.log(safeResult); // 15Install with Tessl CLI
npx tessl i tessl/npm-vega