CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vega

A declarative visualization grammar for creating interactive data visualizations through JSON specifications.

Pending
Overview
Eval results
Files

expressions.mddocs/

Expression System

Vega's expression system provides a custom expression language for dynamic specifications with function registration, parsing, code generation, and runtime evaluation capabilities.

Capabilities

Expression Functions

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

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;
}

Code Generation

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;
}

Built-in Expression Functions

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;
}

Expression Context

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;

Usage Examples

Basic Expression Evaluation

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); // 20

Custom Function Registration

import { 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"

Complex Expression Parsing

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'

Expression with Context

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)

Array and String Operations

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'

Date/Time Expressions

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'

Mathematical Expressions

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.07

Type Checking and Conversion

import { 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 -> 0

Data Access Expressions

import { 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'

Whitelist and Security

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); // 15

Install with Tessl CLI

npx tessl i tessl/npm-vega

docs

data-loading.md

dataflow.md

events.md

expressions.md

index.md

parsing.md

scales.md

scenegraph.md

statistics.md

time.md

utilities.md

view.md

tile.json