import { truthy, hasOwnProperty, error, stringValue, isString, isFunction, extend, isArray, isObject, field, ascending, isRegExp, peek, identity, array as array$1, zoomSymlog, zoomPow, zoomLog, zoomLinear, panSymlog, panPow, panLog, panLinear, clampRange, utcquarter, quarter, truncate, inrange, span, pad, lerp, flush, toString, toNumber, toBoolean, isNumber, isDate, isBoolean, extent, toDate } from 'vega-util';
import { Literal, codegenExpression, constants, functions, parseExpression, CallExpression } from 'vega-expression';
import { isRegisteredScale, bandSpace, scale as scale$1, scaleFraction } from 'vega-scale';
import { geoBounds as geoBounds$1, geoCentroid as geoCentroid$1, geoArea as geoArea$1 } from 'd3-geo';
import { rgb, hsl, hcl, lab } from 'd3-color';
import { isTuple } from 'vega-dataflow';
import { Gradient, pathRender, pathParse, Bounds, intersect as intersect$1 } from 'vega-scenegraph';
import { selectionVisitor, selectionTest, selectionIdTest, selectionResolve, selectionTuples } from 'vega-selections';
import { sampleUniform, sampleLogNormal, sampleNormal, quantileUniform, quantileLogNormal, quantileNormal, densityUniform, densityLogNormal, densityNormal, cumulativeUniform, cumulativeLogNormal, cumulativeNormal, random } from 'vega-statistics';
import { utcdayofyear, dayofyear, utcweek, week, timeUnitSpecifier, timeSequence, timeOffset, utcSequence, utcOffset } from 'vega-time';
import { range as range$1 } from 'd3-array';

function data(name) {
  const data = this.context.data[name];
  return data ? data.values.value : [];
}
function indata(name, field, value) {
  const index = this.context.data[name]['index:' + field],
    entry = index ? index.value.get(value) : undefined;
  return entry ? entry.count : entry;
}
function setdata(name, tuples) {
  const df = this.context.dataflow,
    data = this.context.data[name],
    input = data.input;
  df.pulse(input, df.changeset().remove(truthy).insert(tuples));
  return 1;
}

function encode (item, name, retval) {
  if (item) {
    const df = this.context.dataflow,
      target = item.mark.source;
    df.pulse(target, df.changeset().encode(item, name));
  }
  return retval !== undefined ? retval : item;
}

const wrap = method => function (value, spec) {
  const locale = this.context.dataflow.locale();
  return value === null ? 'null' : locale[method](spec)(value);
};
const format = wrap('format');
const timeFormat = wrap('timeFormat');
const utcFormat = wrap('utcFormat');
const timeParse = wrap('timeParse');
const utcParse = wrap('utcParse');
const dateObj = new Date(2000, 0, 1);
function time(month, day, specifier) {
  if (!Number.isInteger(month) || !Number.isInteger(day)) return '';
  dateObj.setYear(2000);
  dateObj.setMonth(month);
  dateObj.setDate(day);
  return timeFormat.call(this, dateObj, specifier);
}
function monthFormat(month) {
  return time.call(this, month, 1, '%B');
}
function monthAbbrevFormat(month) {
  return time.call(this, month, 1, '%b');
}
function dayFormat(day) {
  return time.call(this, 0, 2 + day, '%A');
}
function dayAbbrevFormat(day) {
  return time.call(this, 0, 2 + day, '%a');
}

const DataPrefix = ':';
const IndexPrefix = '@';
const ScalePrefix = '%';
const SignalPrefix = '$';

function dataVisitor(name, args, scope, params) {
  if (args[0].type !== Literal) {
    error('First argument to data functions must be a string literal.');
  }
  const data = args[0].value,
    dataName = DataPrefix + data;
  if (!hasOwnProperty(dataName, params)) {
    try {
      params[dataName] = scope.getData(data).tuplesRef();
    } catch (err) {
      // if data set does not exist, there's nothing to track
    }
  }
}
function indataVisitor(name, args, scope, params) {
  if (args[0].type !== Literal) error('First argument to indata must be a string literal.');
  if (args[1].type !== Literal) error('Second argument to indata must be a string literal.');
  const data = args[0].value,
    field = args[1].value,
    indexName = IndexPrefix + field;
  if (!hasOwnProperty(indexName, params)) {
    params[indexName] = scope.getData(data).indataRef(scope, field);
  }
}
function scaleVisitor(name, args, scope, params) {
  if (args[0].type === Literal) {
    // add scale dependency
    addScaleDependency(scope, params, args[0].value);
  } else {
    // indirect scale lookup; add all scales as parameters
    for (name in scope.scales) {
      addScaleDependency(scope, params, name);
    }
  }
}
function addScaleDependency(scope, params, name) {
  const scaleName = ScalePrefix + name;
  if (!hasOwnProperty(params, scaleName)) {
    try {
      params[scaleName] = scope.scaleRef(name);
    } catch (err) {
      // TODO: error handling? warning?
    }
  }
}

/**
 * nameOrFunction must be a string or function that was registered.
 * Return undefined if scale is not recognized.
 */
function getScale(nameOrFunction, ctx) {
  if (isString(nameOrFunction)) {
    const maybeScale = ctx.scales[nameOrFunction];
    return maybeScale && isRegisteredScale(maybeScale.value) ? maybeScale.value : undefined;
  } else if (isFunction(nameOrFunction)) {
    return isRegisteredScale(nameOrFunction) ? nameOrFunction : undefined;
  }
  return undefined;
}
function internalScaleFunctions(codegen, fnctx, visitors) {
  // add helper method to the 'this' expression function context
  fnctx.__bandwidth = s => s && s.bandwidth ? s.bandwidth() : 0;

  // register AST visitors for internal scale functions
  visitors._bandwidth = scaleVisitor;
  visitors._range = scaleVisitor;
  visitors._scale = scaleVisitor;

  // resolve scale reference directly to the signal hash argument
  const ref = arg => '_[' + (arg.type === Literal ? stringValue(ScalePrefix + arg.value) : stringValue(ScalePrefix) + '+' + codegen(arg)) + ']';

  // define and return internal scale function code generators
  // these internal functions are called by mark encoders
  return {
    _bandwidth: args => `this.__bandwidth(${ref(args[0])})`,
    _range: args => `${ref(args[0])}.range()`,
    _scale: args => `${ref(args[0])}(${codegen(args[1])})`
  };
}

function geoMethod(methodName, globalMethod) {
  return function (projection, geojson, group) {
    if (projection) {
      // projection defined, use it
      const p = getScale(projection, (group || this).context);
      return p && p.path[methodName](geojson);
    } else {
      // projection undefined, use global method
      return globalMethod(geojson);
    }
  };
}
const geoArea = geoMethod('area', geoArea$1);
const geoBounds = geoMethod('bounds', geoBounds$1);
const geoCentroid = geoMethod('centroid', geoCentroid$1);
function geoScale(projection, group) {
  const p = getScale(projection, (group || this).context);
  return p && p.scale();
}

function inScope (item) {
  const group = this.context.group;
  let value = false;
  if (group) while (item) {
    if (item === group) {
      value = true;
      break;
    }
    item = item.mark.group;
  }
  return value;
}

function log(df, method, args) {
  try {
    df[method].apply(df, ['EXPRESSION'].concat([].slice.call(args)));
  } catch (err) {
    df.warn(err);
  }
  return args[args.length - 1];
}
function warn() {
  return log(this.context.dataflow, 'warn', arguments);
}
function info() {
  return log(this.context.dataflow, 'info', arguments);
}
function debug() {
  return log(this.context.dataflow, 'debug', arguments);
}

// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
function channel_luminance_value(channelValue) {
  const val = channelValue / 255;
  if (val <= 0.03928) {
    return val / 12.92;
  }
  return Math.pow((val + 0.055) / 1.055, 2.4);
}
function luminance(color) {
  const c = rgb(color),
    r = channel_luminance_value(c.r),
    g = channel_luminance_value(c.g),
    b = channel_luminance_value(c.b);
  return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}

// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
function contrast(color1, color2) {
  const lum1 = luminance(color1),
    lum2 = luminance(color2),
    lumL = Math.max(lum1, lum2),
    lumD = Math.min(lum1, lum2);
  return (lumL + 0.05) / (lumD + 0.05);
}

function merge () {
  const args = [].slice.call(arguments);
  args.unshift({});
  return extend(...args);
}

function equal(a, b) {
  return a === b || a !== a && b !== b ? true : isArray(a) ? isArray(b) && a.length === b.length ? equalArray(a, b) : false : isObject(a) && isObject(b) ? equalObject(a, b) : false;
}
function equalArray(a, b) {
  for (let i = 0, n = a.length; i < n; ++i) {
    if (!equal(a[i], b[i])) return false;
  }
  return true;
}
function equalObject(a, b) {
  for (const key in a) {
    if (!equal(a[key], b[key])) return false;
  }
  return true;
}
function removePredicate(props) {
  return _ => equalObject(props, _);
}

/**
 * Modify data in a dataset.
 * @param {string} name - Dataset name
 * @param {Array|Object} insert - Data to insert
 * @param {boolean|Array|Object} remove - true to remove all, array/tuple to remove, or object to match
 * @param {Object} toggle - Data to toggle
 * @param {Object|Array} modify - Tuple or array of tuples to modify
 * @param {Object} values - Field values to update
 */
function modify (name, insert, remove, toggle, modify, values) {
  const df = this.context.dataflow,
    data = this.context.data[name],
    input = data.input,
    stamp = df.stamp();
  let changes = data.changes,
    predicate,
    key;
  if (df._trigger === false || !(input.value.length || insert || toggle)) {
    // nothing to do!
    return 0;
  }
  if (!changes || changes.stamp < stamp) {
    data.changes = changes = df.changeset();
    changes.stamp = stamp;
    df.runAfter(() => {
      data.modified = true;
      df.pulse(input, changes).run();
    }, true, 1);
  }
  if (remove) {
    predicate = remove === true ? truthy : isArray(remove) || isTuple(remove) ? remove : removePredicate(remove);
    changes.remove(predicate);
  }
  if (insert) {
    changes.insert(insert);
  }
  if (toggle) {
    predicate = removePredicate(toggle);
    if (input.value.some(predicate)) {
      changes.remove(predicate);
    } else {
      changes.insert(toggle);
    }
  }
  if (modify) {
    if (isFunction(modify)) {
      throw Error('modify parameter must be a data tuple, not a function');
    }
    for (key in values) {
      changes.modify(modify, key, values[key]);
    }
  }
  return 1;
}

function pinchDistance(event) {
  const t = event.touches,
    dx = t[0].clientX - t[1].clientX,
    dy = t[0].clientY - t[1].clientY;
  return Math.hypot(dx, dy);
}
function pinchAngle(event) {
  const t = event.touches;
  return Math.atan2(t[0].clientY - t[1].clientY, t[0].clientX - t[1].clientX);
}

// memoize accessor functions
const accessors = {};
function pluck (data, name) {
  const accessor = accessors[name] || (accessors[name] = field(name));
  return isArray(data) ? data.map(accessor) : accessor(data);
}

function array(seq) {
  return isArray(seq) || ArrayBuffer.isView(seq) ? seq : null;
}
function sequence(seq) {
  return array(seq) || (isString(seq) ? seq : null);
}
function join(seq, ...args) {
  return array(seq).join(...args);
}
function indexof(seq, ...args) {
  return sequence(seq).indexOf(...args);
}
function lastindexof(seq, ...args) {
  return sequence(seq).lastIndexOf(...args);
}
function slice(seq, ...args) {
  return sequence(seq).slice(...args);
}
function replace(str, pattern, repl) {
  if (isFunction(repl)) error('Function argument passed to replace.');
  if (!isString(pattern) && !isRegExp(pattern)) error('Please pass a string or RegExp argument to replace.');
  return String(str).replace(pattern, repl);
}
function reverse(seq) {
  return array(seq).slice().reverse();
}
function sort(seq) {
  return array(seq).slice().sort(ascending);
}

function bandspace(count, paddingInner, paddingOuter) {
  return bandSpace(count || 0, paddingInner || 0, paddingOuter || 0);
}
function bandwidth(name, group) {
  const s = getScale(name, (group || this).context);
  return s && s.bandwidth ? s.bandwidth() : 0;
}
function copy(name, group) {
  const s = getScale(name, (group || this).context);
  return s ? s.copy() : undefined;
}
function domain(name, group) {
  const s = getScale(name, (group || this).context);
  return s ? s.domain() : [];
}
function invert(name, range, group) {
  const s = getScale(name, (group || this).context);
  return !s ? undefined : isArray(range) ? (s.invertRange || s.invert)(range) : (s.invert || s.invertExtent)(range);
}
function range(name, group) {
  const s = getScale(name, (group || this).context);
  return s && s.range ? s.range() : [];
}
function scale(name, value, group) {
  const s = getScale(name, (group || this).context);
  return s ? s(value) : undefined;
}

function scaleGradient (scale, p0, p1, count, group) {
  scale = getScale(scale, (group || this).context);
  const gradient = Gradient(p0, p1);
  let stops = scale.domain(),
    min = stops[0],
    max = peek(stops),
    fraction = identity;
  if (!(max - min)) {
    // expand scale if domain has zero span, fix #1479
    scale = (scale.interpolator ? scale$1('sequential')().interpolator(scale.interpolator()) : scale$1('linear')().interpolate(scale.interpolate()).range(scale.range())).domain([min = 0, max = 1]);
  } else {
    fraction = scaleFraction(scale, min, max);
  }
  if (scale.ticks) {
    stops = scale.ticks(+count || 15);
    if (min !== stops[0]) stops.unshift(min);
    if (max !== peek(stops)) stops.push(max);
  }
  stops.forEach(_ => gradient.stop(fraction(_), scale(_)));
  return gradient;
}

function geoShape(projection, geojson, group) {
  const p = getScale(projection, (group || this).context);
  return function (context) {
    return p ? p.path.context(context)(geojson) : '';
  };
}
function pathShape(path) {
  let p = null;
  return function (context) {
    return context ? pathRender(context, p = p || pathParse(path)) : path;
  };
}

const datum = d => d.data;
function treeNodes(name, context) {
  const tree = data.call(context, name);
  return tree.root && tree.root.lookup || {};
}
function treePath(name, source, target) {
  const nodes = treeNodes(name, this),
    s = nodes[source],
    t = nodes[target];
  return s && t ? s.path(t).map(datum) : undefined;
}
function treeAncestors(name, node) {
  const n = treeNodes(name, this)[node];
  return n ? n.ancestors().map(datum) : undefined;
}

const _window = () => typeof window !== 'undefined' && window || null;
function screen() {
  const w = _window();
  return w ? w.screen : {};
}
function windowSize() {
  const w = _window();
  return w ? [w.innerWidth, w.innerHeight] : [undefined, undefined];
}
function containerSize() {
  const view = this.context.dataflow,
    el = view.container && view.container();
  return el ? [el.clientWidth, el.clientHeight] : [undefined, undefined];
}

function intersect (b, opt, group) {
  if (!b) return [];
  const [u, v] = b,
    box = new Bounds().set(u[0], u[1], v[0], v[1]),
    scene = group || this.context.dataflow.scenegraph().root;
  return intersect$1(scene, box, filter(opt));
}
function filter(opt) {
  let p = null;
  if (opt) {
    const types = array$1(opt.marktype),
      names = array$1(opt.markname);
    p = _ => (!types.length || types.some(t => _.marktype === t)) && (!names.length || names.some(s => _.name === s));
  }
  return p;
}

/**
 * Appends a new point to the lasso
 *
 * @param {*} lasso the lasso in pixel space
 * @param {*} x the x coordinate in pixel space
 * @param {*} y the y coordinate in pixel space
 * @param {*} minDist the minimum distance, in pixels, that thenew point needs to be apart from the last point
 * @returns a new array containing the lasso with the new point
 */
function lassoAppend(lasso, x, y, minDist = 5) {
  lasso = array$1(lasso);
  const last = lasso[lasso.length - 1];

  // Add point to lasso if its the first point or distance to last point exceed minDist
  return last === undefined || Math.hypot(last[0] - x, last[1] - y) > minDist ? [...lasso, [x, y]] : lasso;
}

/**
 * Generates a svg path command which draws a lasso
 *
 * @param {*} lasso the lasso in pixel space in the form [[x,y], [x,y], ...]
 * @returns the svg path command that draws the lasso
 */
function lassoPath(lasso) {
  return array$1(lasso).reduce((svg, [x, y], i) => {
    return svg += i == 0 ? `M ${x},${y} ` : i === lasso.length - 1 ? ' Z' : `L ${x},${y} `;
  }, '');
}

/**
 * Inverts the lasso from pixel space to an array of vega scenegraph tuples
 *
 * @param {*} data the dataset
 * @param {*} pixelLasso the lasso in pixel space, [[x,y], [x,y], ...]
 * @param {*} unit the unit where the lasso is defined
 *
 * @returns an array of vega scenegraph tuples
 */
function intersectLasso(markname, pixelLasso, unit) {
  const {
    x,
    y,
    mark
  } = unit;
  const bb = new Bounds().set(Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER, Number.MIN_SAFE_INTEGER);

  // Get bounding box around lasso
  for (const [px, py] of pixelLasso) {
    if (px < bb.x1) bb.x1 = px;
    if (px > bb.x2) bb.x2 = px;
    if (py < bb.y1) bb.y1 = py;
    if (py > bb.y2) bb.y2 = py;
  }

  // Translate bb against unit coordinates
  bb.translate(x, y);
  const intersection = intersect([[bb.x1, bb.y1], [bb.x2, bb.y2]], markname, mark);

  // Check every point against the lasso
  return intersection.filter(tuple => pointInPolygon(tuple.x, tuple.y, pixelLasso));
}

/**
 * Performs a test if a point is inside a polygon based on the idea from
 * https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
 *
 * This method will not need the same start/end point since it wraps around the edges of the array
 *
 * @param {*} test a point to test against
 * @param {*} polygon a polygon in the form [[x,y], [x,y], ...]
 * @returns true if the point lies inside the polygon, false otherwise
 */
function pointInPolygon(testx, testy, polygon) {
  let intersections = 0;
  for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
    const [prevX, prevY] = polygon[j];
    const [x, y] = polygon[i];

    // count intersections
    if (y > testy != prevY > testy && testx < (prevX - x) * (testy - y) / (prevY - y) + x) {
      intersections++;
    }
  }

  // point is in polygon if intersection count is odd
  return intersections & 1;
}

// Expression function context object
const functionContext = {
  random() {
    return random();
  },
  // override default
  cumulativeNormal,
  cumulativeLogNormal,
  cumulativeUniform,
  densityNormal,
  densityLogNormal,
  densityUniform,
  quantileNormal,
  quantileLogNormal,
  quantileUniform,
  sampleNormal,
  sampleLogNormal,
  sampleUniform,
  isArray,
  isBoolean,
  isDate,
  isDefined(_) {
    return _ !== undefined;
  },
  isNumber,
  isObject,
  isRegExp,
  isString,
  isTuple,
  isValid(_) {
    return _ != null && _ === _;
  },
  toBoolean,
  toDate(_) {
    return toDate(_);
  },
  // suppress extra arguments
  toNumber,
  toString,
  indexof,
  join,
  lastindexof,
  replace,
  reverse,
  sort,
  slice,
  flush,
  lerp,
  merge,
  pad,
  peek,
  pluck,
  span,
  inrange,
  truncate,
  rgb,
  lab,
  hcl,
  hsl,
  luminance,
  contrast,
  sequence: range$1,
  format,
  utcFormat,
  utcParse,
  utcOffset,
  utcSequence,
  timeFormat,
  timeParse,
  timeOffset,
  timeSequence,
  timeUnitSpecifier,
  monthFormat,
  monthAbbrevFormat,
  dayFormat,
  dayAbbrevFormat,
  quarter,
  utcquarter,
  week,
  utcweek,
  dayofyear,
  utcdayofyear,
  warn,
  info,
  debug,
  extent(_) {
    return extent(_);
  },
  // suppress extra arguments
  inScope,
  intersect,
  clampRange,
  pinchDistance,
  pinchAngle,
  screen,
  containerSize,
  windowSize,
  bandspace,
  setdata,
  pathShape,
  panLinear,
  panLog,
  panPow,
  panSymlog,
  zoomLinear,
  zoomLog,
  zoomPow,
  zoomSymlog,
  encode,
  modify,
  lassoAppend,
  lassoPath,
  intersectLasso
};
const eventFunctions = ['view', 'item', 'group', 'xy', 'x', 'y'],
  // event functions
  eventPrefix = 'event.vega.',
  // event function prefix
  thisPrefix = 'this.',
  // function context prefix
  astVisitors = {}; // AST visitors for dependency analysis

// export code generator parameters
const codegenParams = {
  forbidden: ['_'],
  allowed: ['datum', 'event', 'item'],
  fieldvar: 'datum',
  globalvar: id => `_[${stringValue(SignalPrefix + id)}]`,
  functions: buildFunctions,
  constants: constants,
  visitors: astVisitors
};

// export code generator
const codeGenerator = codegenExpression(codegenParams);

// Build expression function registry
function buildFunctions(codegen) {
  const fn = functions(codegen);
  eventFunctions.forEach(name => fn[name] = eventPrefix + name);
  for (const name in functionContext) {
    fn[name] = thisPrefix + name;
  }
  extend(fn, internalScaleFunctions(codegen, functionContext, astVisitors));
  return fn;
}

// Register an expression function
function expressionFunction(name, fn, visitor) {
  if (arguments.length === 1) {
    return functionContext[name];
  }

  // register with the functionContext
  functionContext[name] = fn;

  // if there is an astVisitor register that, too
  if (visitor) astVisitors[name] = visitor;

  // if the code generator has already been initialized,
  // we need to also register the function with it
  if (codeGenerator) codeGenerator.functions[name] = thisPrefix + name;
  return this;
}

// register expression functions with ast visitors
expressionFunction('bandwidth', bandwidth, scaleVisitor);
expressionFunction('copy', copy, scaleVisitor);
expressionFunction('domain', domain, scaleVisitor);
expressionFunction('range', range, scaleVisitor);
expressionFunction('invert', invert, scaleVisitor);
expressionFunction('scale', scale, scaleVisitor);
expressionFunction('gradient', scaleGradient, scaleVisitor);
expressionFunction('geoArea', geoArea, scaleVisitor);
expressionFunction('geoBounds', geoBounds, scaleVisitor);
expressionFunction('geoCentroid', geoCentroid, scaleVisitor);
expressionFunction('geoShape', geoShape, scaleVisitor);
expressionFunction('geoScale', geoScale, scaleVisitor);
expressionFunction('indata', indata, indataVisitor);
expressionFunction('data', data, dataVisitor);
expressionFunction('treePath', treePath, dataVisitor);
expressionFunction('treeAncestors', treeAncestors, dataVisitor);

// register Vega-Lite selection functions
expressionFunction('vlSelectionTest', selectionTest, selectionVisitor);
expressionFunction('vlSelectionIdTest', selectionIdTest, selectionVisitor);
expressionFunction('vlSelectionResolve', selectionResolve, selectionVisitor);
expressionFunction('vlSelectionTuples', selectionTuples);

function parser (expr, scope) {
  const params = {};

  // parse the expression to an abstract syntax tree (ast)
  let ast;
  try {
    expr = isString(expr) ? expr : stringValue(expr) + '';
    ast = parseExpression(expr);
  } catch (err) {
    error('Expression parse error: ' + expr);
  }

  // analyze ast function calls for dependencies
  ast.visit(node => {
    if (node.type !== CallExpression) return;
    const name = node.callee.name,
      visit = codegenParams.visitors[name];
    if (visit) visit(name, node.arguments, scope, params);
  });

  // perform code generation
  const gen = codeGenerator(ast);

  // collect signal dependencies
  gen.globals.forEach(name => {
    const signalName = SignalPrefix + name;
    if (!hasOwnProperty(params, signalName) && scope.getSignal(name)) {
      params[signalName] = scope.signalRef(name);
    }
  });

  // return generated expression code and dependencies
  return {
    $expr: extend({
      code: gen.code
    }, scope.options.ast ? {
      ast
    } : null),
    $fields: gen.fields,
    $params: params
  };
}

export { DataPrefix, IndexPrefix, ScalePrefix, SignalPrefix, bandspace, bandwidth, codeGenerator, codegenParams, containerSize, contrast, copy, data, dataVisitor, dayAbbrevFormat, dayFormat, debug, domain, encode, expressionFunction, format, functionContext, geoArea, geoBounds, geoCentroid, geoScale, geoShape, inScope, indata, indataVisitor, indexof, info, invert, join, lastindexof, luminance, merge, modify, monthAbbrevFormat, monthFormat, parser as parseExpression, pathShape, pinchAngle, pinchDistance, pluck, range, replace, reverse, scale, scaleGradient, scaleVisitor, screen, setdata, slice, sort, timeFormat, timeParse, treeAncestors, treePath, utcFormat, utcParse, warn, windowSize };
//# sourceMappingURL=vega-functions.js.map
