PageSourceSearch

https://terrancelaw.github.io/iQuery/vender/vega-lite.js

js terrancelaw.github.io collected 2026-10-03 10:05:05 UTC 675,939 bytes, 16,974 lines download raw bytes

1(function (global, factory) {
2    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3    typeof define === 'function' && define.amd ? define(['exports'], factory) :
4    (global = global || self, factory(global.vl = {}));
5}(this, function (exports) { 'use strict';
6
7    var name = "vega-lite";
8    var author = "Jeffrey Heer, Dominik Moritz, Kanit \"Ham\" Wongsuphasawat";
9    var version = "3.0.0-rc12";
10    var collaborators = [
11    	"Kanit Wongsuphasawat <[email protected]> (http://kanitw.yellowpigz.com)",
12    	"Dominik Moritz <[email protected]> (https://www.domoritz.de)",
13    	"Jeffrey Heer <[email protected]> (http://jheer.org)"
14    ];
15    var homepage = "https://vega.github.io/vega-lite/";
16    var description = "Vega-Lite is a concise high-level language for interactive visualization.";
17    var main = "build/vega-lite.js";
18    var unpkg = "build/vega-lite.min.js";
19    var jsdelivr = "build/vega-lite.min.js";
20    var module$1 = "build/src/index";
21    var types = "build/src/index.d.ts";
22    var bin = {
23    	vl2png: "./bin/vl2png",
24    	vl2svg: "./bin/vl2svg",
25    	vl2vg: "./bin/vl2vg"
26    };
27    var directories = {
28    	test: "test"
29    };
30    var scripts = {
31    	prebuild: "mkdir -p build/src",
32    	build: "npm run build:only",
33    	"build:only": "tsc && rollup -c",
34    	postbuild: "terser build/vega-lite.js -cm --source-map content=build/vega-lite.js.map,filename=build/vega-lite.min.js.map -o build/vega-lite.min.js && npm run schema",
35    	"build:examples": "npm run build:only",
36    	"postbuild:examples": "npm run data && TZ=America/Los_Angeles scripts/build-examples.sh",
37    	"build:examples-full": "npm run build:only",
38    	"postbuild:examples-full": "TZ=America/Los_Angeles scripts/build-examples.sh 1",
39    	"build:example": "TZ=America/Los_Angeles scripts/build-example.sh",
40    	"build:toc": "bundle exec jekyll build -q && scripts/generate-toc",
41    	"build:site": "tsc -p site && webpack --config site/webpack.config.js",
42    	"build:versions": "scripts/update-version.sh",
43    	clean: "rm -rf build && rm -f examples/compiled/*.png && find site/examples ! -name 'index.md' -type f -delete",
44    	data: "rsync -r node_modules/vega-datasets/data/* data",
45    	deploy: "scripts/deploy.sh",
46    	"deploy:gh": "scripts/deploy-gh.sh",
47    	"deploy:schema": "scripts/deploy-schema.sh",
48    	preschema: "npm run prebuild",
49    	schema: "node --stack-size=2800 ./node_modules/.bin/ts-json-schema-generator --no-type-check --path tsconfig.json --type TopLevelSpec > build/vega-lite-schema.json && npm run renameschema && cp build/vega-lite-schema.json _data/",
50    	renameschema: "scripts/rename-schema.sh",
51    	presite: "npm run prebuild && npm run data && npm run build:site && npm run build:toc && npm run build:versions && scripts/create-example-pages",
52    	site: "bundle exec jekyll serve --incremental",
53    	prettierbase: "prettier '{src,test,test-runtime,site,typings}/**/*.{ts,md,css}'",
54    	format: "tslint -p . --fix -e 'package.json' && npm run prettierbase -- --write",
55    	lint: "tslint -p . -e 'package.json' && npm run prettierbase -- --list-different",
56    	test: "jest test/ && npm run lint && npm run schema && jest examples/ && npm run test:runtime",
57    	"test:inspect": "node --inspect-brk ./node_modules/.bin/jest --runInBand test",
58    	"test:runtime": "TZ=America/Los_Angeles TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' wdio wdio.conf.js",
59    	"test:runtime:generate": "rm -Rf test-runtime/resources && VL_GENERATE_TESTS=true npm run test:runtime",
60    	"watch:build": "npm run build:only && concurrently --kill-others -n Typescript,Rollup 'tsc -w' 'rollup -c -w'",
61    	"watch:site": "concurrently --kill-others -n Typescript,Webpack 'tsc -p site --watch' 'webpack --config site/webpack.config.js --mode development --watch'",
62    	"watch:test": "jest --watch"
63    };
64    var repository = {
65    	type: "git",
66    	url: "https://github.com/vega/vega-lite.git"
67    };
68    var license = "BSD-3-Clause";
69    var bugs = {
70    	url: "https://github.com/vega/vega-lite/issues"
71    };
72    var devDependencies = {
73    	"@types/chai": "^4.1.7",
74    	"@types/d3": "^5.5.0",
75    	"@types/highlight.js": "^9.12.3",
76    	"@types/jest": "^23.3.10",
77    	"@types/mkdirp": "^0.5.2",
78    	"@types/node": "10.12.18",
79    	"@types/webdriverio": "^4.13.0",
80    	ajv: "^6.6.2",
81    	chai: "^4.2.0",
82    	cheerio: "^1.0.0-rc.2",
83    	chromedriver: "^2.45.0",
84    	codecov: "^3.1.0",
85    	concurrently: "^4.1.0",
86    	d3: "^5.7.0",
87    	"highlight.js": "^9.13.1",
88    	jest: "^23.6.0",
89    	mkdirp: "^0.5.1",
90    	prettier: "^1.15.3",
91    	rollup: "^1.0.0",
92    	"rollup-plugin-commonjs": "^9.2.0",
93    	"rollup-plugin-json": "^3.1.0",
94    	"rollup-plugin-node-resolve": "^4.0.0",
95    	"rollup-plugin-sourcemaps": "^0.4.2",
96    	"source-map-support": "^0.5.9",
97    	"svg2png-many": "^0.0.7",
98    	terser: "^3.13.1",
99    	"ts-jest": "^23.10.5",
100    	"ts-json-schema-generator": "^0.38.1",
101    	"ts-node": "^7.0.1",
102    	tslint: "5.12.0",
103    	"tslint-config-prettier": "^1.17.0",
104    	"tslint-eslint-rules": "^5.4.0",
105    	typescript: "^3.2.2",
106    	vega: "^4.4.0",
107    	"vega-datasets": "^1.20.0",
108    	"vega-embed": "^3.28.0",
109    	"vega-tooltip": "^0.15.0",
110    	"wdio-chromedriver-service": "^0.1.5",
111    	"wdio-dot-reporter": "0.0.10",
112    	"wdio-mocha-framework": "^0.6.4",
113    	"wdio-static-server-service": "^1.0.1",
114    	webdriverio: "^4.14.0",
115    	webpack: "^4.28.2",
116    	"webpack-cli": "^3.1.2",
117    	"yaml-front-matter": "^4.0.0"
118    };
119    var dependencies = {
120    	"@types/clone": "^0.1.30",
121    	clone: "^2.1.2",
122    	"fast-deep-equal": "^2.0.1",
123    	"fast-json-stable-stringify": "^2.0.0",
124    	"json-stringify-pretty-compact": "^1.2.0",
125    	tslib: "^1.9.3",
126    	"vega-event-selector": "^2.0.0",
127    	"vega-expression": "^2.4.0",
128    	"vega-typings": "0.3.53",
129    	"vega-util": "^1.7.1",
130    	yargs: "^12.0.5"
131    };
132    var jest = {
133    	testURL: "http://localhost/",
134    	transform: {
135    		"^.+\\.tsx?$": "ts-jest"
136    	},
137    	testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
138    	moduleFileExtensions: [
139    		"ts",
140    		"tsx",
141    		"js",
142    		"jsx",
143    		"json",
144    		"node"
145    	],
146    	testPathIgnorePatterns: [
147    		"node_modules",
148    		"test-runtime",
149    		"<rootDir>/build",
150    		"_site",
151    		"src"
152    	],
153    	coverageDirectory: "./coverage/",
154    	collectCoverage: false
155    };
156    var pkg = {
157    	name: name,
158    	author: author,
159    	version: version,
160    	collaborators: collaborators,
161    	homepage: homepage,
162    	description: description,
163    	main: main,
164    	unpkg: unpkg,
165    	jsdelivr: jsdelivr,
166    	module: module$1,
167    	types: types,
168    	bin: bin,
169    	directories: directories,
170    	scripts: scripts,
171    	repository: repository,
172    	license: license,
173    	bugs: bugs,
174    	devDependencies: devDependencies,
175    	dependencies: dependencies,
176    	jest: jest
177    };
178
179    function accessor(fn, fields, name) {
180      fn.fields = fields || [];
181      fn.fname = name;
182      return fn;
183    }
184
185    function error(message) {
186      throw Error(message);
187    }
188
189    function splitAccessPath(p) {
190      var path = [],
191          q = null,
192          b = 0,
193          n = p.length,
194          s = '',
195          i, j, c;
196
197      p = p + '';
198
199      function push() {
200        path.push(s + p.substring(i, j));
201        s = '';
202        i = j + 1;
203      }
204
205      for (i=j=0; j<n; ++j) {
206        c = p[j];
207        if (c === '\\') {
208          s += p.substring(i, j);
209          i = ++j;
210        } else if (c === q) {
211          push();
212          q = null;
213          b = -1;
214        } else if (q) {
215          continue;
216        } else if (i === b && c === '"') {
217          i = j + 1;
218          q = c;
219        } else if (i === b && c === "'") {
220          i = j + 1;
221          q = c;
222        } else if (c === '.' && !b) {
223          if (j > i) {
224            push();
225          } else {
226            i = j + 1;
227          }
228        } else if (c === '[') {
229          if (j > i) push();
230          b = i = j + 1;
231        } else if (c === ']') {
232          if (!b) error('Access path missing open bracket: ' + p);
233          if (b > 0) push();
234          b = 0;
235          i = j + 1;
236        }
237      }
238
239      if (b) error('Access path missing closing bracket: ' + p);
240      if (q) error('Access path missing closing quote: ' + p);
241
242      if (j > i) {
243        j++;
244        push();
245      }
246
247      return path;
248    }
249
250    var isArray = Array.isArray;
251
252    function isObject(_) {
253      return _ === Object(_);
254    }
255
256    function isString(_) {
257      return typeof _ === 'string';
258    }
259
260    function $(x) {
261      return isArray(x) ? '[' + x.map($) + ']'
262        : isObject(x) || isString(x) ?
263          // Output valid JSON and JS source strings.
264          // See http://timelessrepo.com/json-isnt-a-javascript-subset
265          JSON.stringify(x).replace('\u2028','\\u2028').replace('\u2029', '\\u2029')
266        : x;
267    }
268
269    function field(field, name) {
270      var path = splitAccessPath(field),
271          code = 'return _[' + path.map($).join('][') + '];';
272
273      return accessor(
274        Function('_', code),
275        [(field = path.length===1 ? path[0] : field)],
276        name || field
277      );
278    }
279
280    var empty = [];
281
282    var id = field('id');
283
284    var identity = accessor(function(_) { return _; }, empty, 'identity');
285
286    var zero = accessor(function() { return 0; }, empty, 'zero');
287
288    var one = accessor(function() { return 1; }, empty, 'one');
289
290    var truthy = accessor(function() { return true; }, empty, 'true');
291
292    var falsy = accessor(function() { return false; }, empty, 'false');
293
294    function log(method, level, input) {
295      var args = [level].concat([].slice.call(input));
296      console[method].apply(console, args); // eslint-disable-line no-console
297    }
298
299    var None  = 0;
300    var Error$1 = 1;
301    var Warn  = 2;
302    var Info  = 3;
303    var Debug = 4;
304
305    function logger(_) {
306      var level = _ || None;
307      return {
308        level: function(_) {
309          if (arguments.length) {
310            level = +_;
311            return this;
312          } else {
313            return level;
314          }
315        },
316        error: function() {
317          if (level >= Error$1) log('error', 'ERROR', arguments);
318          return this;
319        },
320        warn: function() {
321          if (level >= Warn) log('warn', 'WARN', arguments);
322          return this;
323        },
324        info: function() {
325          if (level >= Info) log('log', 'INFO', arguments);
326          return this;
327        },
328        debug: function() {
329          if (level >= Debug) log('log', 'DEBUG', arguments);
330          return this;
331        }
332      }
333    }
334
335    function array(_) {
336      return _ != null ? (isArray(_) ? _ : [_]) : [];
337    }
338
339    function isFunction(_) {
340      return typeof _ === 'function';
341    }
342
343    function isBoolean(_) {
344      return typeof _ === 'boolean';
345    }
346
347    function isNumber(_) {
348      return typeof _ === 'number';
349    }
350
351    function toSet(_) {
352      for (var s={}, i=0, n=_.length; i<n; ++i) s[_[i]] = true;
353      return s;
354    }
355
356    function createCommonjsModule(fn, module) {
357    	return module = { exports: {} }, fn(module, module.exports), module.exports;
358    }
359
360    var clone_1 = createCommonjsModule(function (module) {
361    var clone = (function() {
362
vendor: 8,613 bytes, lines 363-635
363    function _instanceof(obj, type) {
364      return type != null && obj instanceof type;
365    }
366
367    var nativeMap;
368    try {
369      nativeMap = Map;
370    } catch(_) {
371      // maybe a reference error because no `Map`. Give it a dummy value that no
372      // value will ever be an instanceof.
373      nativeMap = function() {};
374    }
375
376    var nativeSet;
377    try {
378      nativeSet = Set;
379    } catch(_) {
380      nativeSet = function() {};
381    }
382
383    var nativePromise;
384    try {
385      nativePromise = Promise;
386    } catch(_) {
387      nativePromise = function() {};
388    }
389
390    /**
391     * Clones (copies) an Object using deep copying.
392     *
393     * This function supports circular references by default, but if you are certain
394     * there are no circular references in your object, you can save some CPU time
395     * by calling clone(obj, false).
396     *
397     * Caution: if `circular` is false and `parent` contains circular references,
398     * your program may enter an infinite loop and crash.
399     *
400     * @param `parent` - the object to be cloned
401     * @param `circular` - set to true if the object to be cloned may contain
402     *    circular references. (optional - true by default)
403     * @param `depth` - set to a number if the object is only to be cloned to
404     *    a particular depth. (optional - defaults to Infinity)
405     * @param `prototype` - sets the prototype to be used when cloning an object.
406     *    (optional - defaults to parent prototype).
407     * @param `includeNonEnumerable` - set to true if the non-enumerable properties
408     *    should be cloned as well. Non-enumerable properties on the prototype
409     *    chain will be ignored. (optional - false by default)
410    */
411    function clone(parent, circular, depth, prototype, includeNonEnumerable) {
412      if (typeof circular === 'object') {
413        depth = circular.depth;
414        prototype = circular.prototype;
415        includeNonEnumerable = circular.includeNonEnumerable;
416        circular = circular.circular;
417      }
418      // maintain two arrays for circular references, where corresponding parents
419      // and children have the same index
420      var allParents = [];
421      var allChildren = [];
422
423      var useBuffer = typeof Buffer != 'undefined';
424
425      if (typeof circular == 'undefined')
426        circular = true;
427
428      if (typeof depth == 'undefined')
429        depth = Infinity;
430
431      // recurse this function so we don't reset allParents and allChildren
432      function _clone(parent, depth) {
433        // cloning null always returns null
434        if (parent === null)
435          return null;
436
437        if (depth === 0)
438          return parent;
439
440        var child;
441        var proto;
442        if (typeof parent != 'object') {
443          return parent;
444        }
445
446        if (_instanceof(parent, nativeMap)) {
447          child = new nativeMap();
448        } else if (_instanceof(parent, nativeSet)) {
449          child = new nativeSet();
450        } else if (_instanceof(parent, nativePromise)) {
451          child = new nativePromise(function (resolve, reject) {
452            parent.then(function(value) {
453              resolve(_clone(value, depth - 1));
454            }, function(err) {
455              reject(_clone(err, depth - 1));
456            });
457          });
458        } else if (clone.__isArray(parent)) {
459          child = [];
460        } else if (clone.__isRegExp(parent)) {
461          child = new RegExp(parent.source, __getRegExpFlags(parent));
462          if (parent.lastIndex) child.lastIndex = parent.lastIndex;
463        } else if (clone.__isDate(parent)) {
464          child = new Date(parent.getTime());
465        } else if (useBuffer && Buffer.isBuffer(parent)) {
466          if (Buffer.allocUnsafe) {
467            // Node.js >= 4.5.0
468            child = Buffer.allocUnsafe(parent.length);
469          } else {
470            // Older Node.js versions
471            child = new Buffer(parent.length);
472          }
473          parent.copy(child);
474          return child;
475        } else if (_instanceof(parent, Error)) {
476          child = Object.create(parent);
477        } else {
478          if (typeof prototype == 'undefined') {
479            proto = Object.getPrototypeOf(parent);
480            child = Object.create(proto);
481          }
482          else {
483            child = Object.create(prototype);
484            proto = prototype;
485          }
486        }
487
488        if (circular) {
489          var index = allParents.indexOf(parent);
490
491          if (index != -1) {
492            return allChildren[index];
493          }
494          allParents.push(parent);
495          allChildren.push(child);
496        }
497
498        if (_instanceof(parent, nativeMap)) {
499          parent.forEach(function(value, key) {
500            var keyChild = _clone(key, depth - 1);
501            var valueChild = _clone(value, depth - 1);
502            child.set(keyChild, valueChild);
503          });
504        }
505        if (_instanceof(parent, nativeSet)) {
506          parent.forEach(function(value) {
507            var entryChild = _clone(value, depth - 1);
508            child.add(entryChild);
509          });
510        }
511
512        for (var i in parent) {
513          var attrs;
514          if (proto) {
515            attrs = Object.getOwnPropertyDescriptor(proto, i);
516          }
517
518          if (attrs && attrs.set == null) {
519            continue;
520          }
521          child[i] = _clone(parent[i], depth - 1);
522        }
523
524        if (Object.getOwnPropertySymbols) {
525          var symbols = Object.getOwnPropertySymbols(parent);
526          for (var i = 0; i < symbols.length; i++) {
527            // Don't need to worry about cloning a symbol because it is a primitive,
528            // like a number or string.
529            var symbol = symbols[i];
530            var descriptor = Object.getOwnPropertyDescriptor(parent, symbol);
531            if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {
532              continue;
533            }
534            child[symbol] = _clone(parent[symbol], depth - 1);
535            if (!descriptor.enumerable) {
536              Object.defineProperty(child, symbol, {
537                enumerable: false
538              });
539            }
540          }
541        }
542
543        if (includeNonEnumerable) {
544          var allPropertyNames = Object.getOwnPropertyNames(parent);
545          for (var i = 0; i < allPropertyNames.length; i++) {
546            var propertyName = allPropertyNames[i];
547            var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName);
548            if (descriptor && descriptor.enumerable) {
549              continue;
550            }
551            child[propertyName] = _clone(parent[propertyName], depth - 1);
552            Object.defineProperty(child, propertyName, {
553              enumerable: false
554            });
555          }
556        }
557
558        return child;
559      }
560
561      return _clone(parent, depth);
562    }
563
564    /**
565     * Simple flat clone using prototype, accepts only objects, usefull for property
566     * override on FLAT configuration object (no nested props).
567     *
568     * USE WITH CAUTION! This may not behave as you wish if you do not know how this
569     * works.
570     */
571    clone.clonePrototype = function clonePrototype(parent) {
572      if (parent === null)
573        return null;
574
575      var c = function () {};
576      c.prototype = parent;
577      return new c();
578    };
579
580    // private utility functions
581
582    function __objToStr(o) {
583      return Object.prototype.toString.call(o);
584    }
585    clone.__objToStr = __objToStr;
586
587    function __isDate(o) {
588      return typeof o === 'object' && __objToStr(o) === '[object Date]';
589    }
590    clone.__isDate = __isDate;
591
592    function __isArray(o) {
593      return typeof o === 'object' && __objToStr(o) === '[object Array]';
594    }
595    clone.__isArray = __isArray;
596
597    function __isRegExp(o) {
598      return typeof o === 'object' && __objToStr(o) === '[object RegExp]';
599    }
600    clone.__isRegExp = __isRegExp;
601
602    function __getRegExpFlags(re) {
603      var flags = '';
604      if (re.global) flags += 'g';
605      if (re.ignoreCase) flags += 'i';
606      if (re.multiline) flags += 'm';
607      return flags;
608    }
609    clone.__getRegExpFlags = __getRegExpFlags;
610
611    return clone;
612    })();
613
614    if (module.exports) {
615      module.exports = clone;
616    }
617    });
618
619    var isArray$1 = Array.isArray;
620    var keyList = Object.keys;
621    var hasProp = Object.prototype.hasOwnProperty;
622
623    var fastDeepEqual = function equal(a, b) {
624      if (a === b) return true;
625
626      if (a && b && typeof a == 'object' && typeof b == 'object') {
627        var arrA = isArray$1(a)
628          , arrB = isArray$1(b)
629          , i
630          , length
631          , key;
632
633        if (arrA && arrB) {
634          length = a.length;
635          if (length != b.length) return false;
636          for (i = length; i-- !== 0;)
637            if (!equal(a[i], b[i])) return false;
638          return true;
639        }
640
641        if (arrA != arrB) return false;
642
643        var dateA = a instanceof Date
644          , dateB = b instanceof Date;
645        if (dateA != dateB) return false;
646        if (dateA && dateB) return a.getTime() == b.getTime();
647
648        var regexpA = a instanceof RegExp
649          , regexpB = b instanceof RegExp;
650        if (regexpA != regexpB) return false;
651        if (regexpA && regexpB) return a.toString() == b.toString();
652
653        var keys = keyList(a);
654        length = keys.length;
655
656        if (length !== keyList(b).length)
657          return false;
658
659        for (i = length; i-- !== 0;)
660          if (!hasProp.call(b, keys[i])) return false;
661
662        for (i = length; i-- !== 0;) {
663          key = keys[i];
664          if (!equal(a[key], b[key])) return false;
665        }
666
667        return true;
668      }
669
670      return a!==a && b!==b;
671    };
672
673    var fastJsonStableStringify = function (data, opts) {
674        if (!opts) opts = {};
675        if (typeof opts === 'function') opts = { cmp: opts };
676        var cycles = (typeof opts.cycles === 'boolean') ? opts.cycles : false;
677
678        var cmp = opts.cmp && (function (f) {
679            return function (node) {
680                return function (a, b) {
681                    var aobj = { key: a, value: node[a] };
682                    var bobj = { key: b, value: node[b] };
683                    return f(aobj, bobj);
684                };
685            };
686        })(opts.cmp);
687
688        var seen = [];
689        return (function stringify (node) {
690            if (node && node.toJSON && typeof node.toJSON === 'function') {
691                node = node.toJSON();
692            }
693
694            if (node === undefined) return;
695            if (typeof node == 'number') return isFinite(node) ? '' + node : 'null';
696            if (typeof node !== 'object') return JSON.stringify(node);
697
698            var i, out;
699            if (Array.isArray(node)) {
700                out = '[';
701                for (i = 0; i < node.length; i++) {
702                    if (i) out += ',';
703                    out += stringify(node[i]) || 'null';
704                }
705                return out + ']';
706            }
707
708            if (node === null) return 'null';
709
710            if (seen.indexOf(node) !== -1) {
711                if (cycles) return JSON.stringify('__cycle__');
712                throw new TypeError('Converting circular structure to JSON');
713            }
714
715            var seenIndex = seen.push(node) - 1;
716            var keys = Object.keys(node).sort(cmp && cmp(node));
717            out = '';
718            for (i = 0; i < keys.length; i++) {
719                var key = keys[i];
720                var value = stringify(node[key]);
721
722                if (!value) continue;
723                if (out) out += ',';
724                out += JSON.stringify(key) + ':' + value;
725            }
726            seen.splice(seenIndex, 1);
727            return '{' + out + '}';
728        })(data);
729    };
730
731    function isLogicalOr(op) {
732        return !!op.or;
733    }
734    function isLogicalAnd(op) {
735        return !!op.and;
736    }
737    function isLogicalNot(op) {
738        return !!op.not;
739    }
740    function forEachLeaf(op, fn) {
741        if (isLogicalNot(op)) {
742            forEachLeaf(op.not, fn);
743        }
744        else if (isLogicalAnd(op)) {
745            for (const subop of op.and) {
746                forEachLeaf(subop, fn);
747            }
748        }
749        else if (isLogicalOr(op)) {
750            for (const subop of op.or) {
751                forEachLeaf(subop, fn);
752            }
753        }
754        else {
755            fn(op);
756        }
757    }
758    function normalizeLogicalOperand(op, normalizer) {
759        if (isLogicalNot(op)) {
760            return { not: normalizeLogicalOperand(op.not, normalizer) };
761        }
762        else if (isLogicalAnd(op)) {
763            return { and: op.and.map(o => normalizeLogicalOperand(o, normalizer)) };
764        }
765        else if (isLogicalOr(op)) {
766            return { or: op.or.map(o => normalizeLogicalOperand(o, normalizer)) };
767        }
768        else {
769            return normalizer(op);
770        }
771    }
772
773    const deepEqual = fastDeepEqual;
774    const duplicate = clone_1;
775    /**
776     * Make a regular expression that matches a whole word of the given string
777     */
778    function globalWholeWordRegExp(word) {
779        // `\b` = word boundary
780        // https://stackoverflow.com/questions/2232934/whole-word-match-in-javascript
781        return new RegExp(`\\b${word}\\b`, 'g');
782    }
783    /**
784     * Creates an object composed of the picked object properties.
785     *
786     * var object = {'a': 1, 'b': '2', 'c': 3};
787     * pick(object, ['a', 'c']);
788     * // → {'a': 1, 'c': 3}
789     *
790     */
791    function pick(obj, props) {
792        const copy = {};
793        for (const prop of props) {
794            if (obj.hasOwnProperty(prop)) {
795                copy[prop] = obj[prop];
796            }
797        }
798        return copy;
799    }
800    /**
801     * The opposite of _.pick; this method creates an object composed of the own
802     * and inherited enumerable string keyed properties of object that are not omitted.
803     */
804    function omit(obj, props) {
805        const copy = Object.assign({}, obj);
806        for (const prop of props) {
807            delete copy[prop];
808        }
809        return copy;
810    }
811    /**
812     * Monkey patch Set so that `stringify` produces a string representation of sets.
813     */
814    Set.prototype['toJSON'] = function () {
815        return `Set(${[...this].map(fastJsonStableStringify).join(',')})`;
816    };
817    /**
818     * Converts any object to a string representation that can be consumed by humans.
819     */
820    const stringify = fastJsonStableStringify;
821    /**
822     * Converts any object to a string of limited size, or a number.
823     */
824    function hash(a) {
825        if (isNumber(a)) {
826            return a;
827        }
828        const str = isString(a) ? a : fastJsonStableStringify(a);
829        // short strings can be used as hash directly, longer strings are hashed to reduce memory usage
830        if (str.length < 250) {
831            return str;
832        }
833        // from http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
834        let h = 0;
835        for (let i = 0; i < str.length; i++) {
836            const char = str.charCodeAt(i);
837            h = (h << 5) - h + char;
838            h = h & h; // Convert to 32bit integer
839        }
840        return h;
841    }
842    function contains(array$$1, item) {
843        return array$$1.indexOf(item) > -1;
844    }
845    /** Returns the array without the elements in item */
846    function without(array$$1, excludedItems) {
847        return array$$1.filter(item => !contains(excludedItems, item));
848    }
849    function union(array$$1, other) {
850        return array$$1.concat(without(other, array$$1));
851    }
852    /**
853     * Returns true if any item returns true.
854     */
855    function some(arr, f) {
856        let i = 0;
857        for (let k = 0; k < arr.length; k++) {
858            if (f(arr[k], k, i++)) {
859                return true;
860            }
861        }
862        return false;
863    }
864    /**
865     * Returns true if all items return true.
866     */
867    function every(arr, f) {
868        let i = 0;
869        for (let k = 0; k < arr.length; k++) {
870            if (!f(arr[k], k, i++)) {
871                return false;
872            }
873        }
874        return true;
875    }
876    function flatten(arrays) {
877        return [].concat(...arrays);
878    }
879    function fill(val, len) {
880        const arr = new Array(len);
881        for (let i = 0; i < len; ++i) {
882            arr[i] = val;
883        }
884        return arr;
885    }
886    /**
887     * recursively merges src into dest
888     */
889    function mergeDeep(dest, ...src) {
890        for (const s of src) {
891            dest = deepMerge_(dest, s);
892        }
893        return dest;
894    }
895    // recursively merges src into dest
896    function deepMerge_(dest, src) {
897        if (typeof src !== 'object' || src === null) {
898            return dest;
899        }
900        for (const p in src) {
901            if (!src.hasOwnProperty(p)) {
902                continue;
903            }
904            if (src[p] === undefined) {
905                continue;
906            }
907            if (typeof src[p] !== 'object' || isArray(src[p]) || src[p] === null) {
908                dest[p] = src[p];
909            }
910            else if (typeof dest[p] !== 'object' || dest[p] === null) {
911                dest[p] = mergeDeep(isArray(src[p].constructor) ? [] : {}, src[p]);
912            }
913            else {
914                mergeDeep(dest[p], src[p]);
915            }
916        }
917        return dest;
918    }
919    function unique(values, f) {
920        const results = [];
921        const u = {};
922        let v;
923        for (const val of values) {
924            v = f(val);
925            if (v in u) {
926                continue;
927            }
928            u[v] = 1;
929            results.push(val);
930        }
931        return results;
932    }
933    /**
934     * Returns true if the two dictionaries disagree. Applies only to defined values.
935     */
936    function isEqual(dict, other) {
937        const dictKeys = keys(dict);
938        const otherKeys = keys(other);
939        if (dictKeys.length !== otherKeys.length) {
940            return false;
941        }
942        for (const key$$1 of dictKeys) {
943            if (dict[key$$1] !== other[key$$1]) {
944                return false;
945            }
946        }
947        return true;
948    }
949    function setEqual(a, b) {
950        if (a.size !== b.size) {
951            return false;
952        }
953        for (const e of a) {
954            if (!b.has(e)) {
955                return false;
956            }
957        }
958        return true;
959    }
960    function hasIntersection(a, b) {
961        for (const key$$1 of a) {
962            if (b.has(key$$1)) {
963                return true;
964            }
965        }
966        return false;
967    }
968    function prefixGenerator(a) {
969        const prefixes = new Set();
970        for (const x of a) {
971            const splitField = splitAccessPath(x);
972            // Wrap every element other than the first in `[]`
973            const wrappedWithAccessors = splitField.map((y, i) => (i === 0 ? y : `[${y}]`));
974            const computedPrefixes = wrappedWithAccessors.map((_, i) => wrappedWithAccessors.slice(0, i + 1).join(''));
975            computedPrefixes.forEach(y => prefixes.add(y));
976        }
977        return prefixes;
978    }
979    function fieldIntersection(a, b) {
980        return hasIntersection(prefixGenerator(a), prefixGenerator(b));
981    }
982    function isNumeric(num) {
983        return !isNaN(num);
984    }
985    function differArray(array$$1, other) {
986        if (array$$1.length !== other.length) {
987            return true;
988        }
989        array$$1.sort();
990        other.sort();
991        for (let i = 0; i < array$$1.length; i++) {
992            if (other[i] !== array$$1[i]) {
993                return true;
994            }
995        }
996        return false;
997    }
998    // This is a stricter version of Object.keys but with better types. See https://github.com/Microsoft/TypeScript/pull/12253#issuecomment-263132208
999    const keys = Object.keys;
1000    function vals(x) {
1001        const _vals = [];
1002        for (const k in x) {
1003            if (x.hasOwnProperty(k)) {
1004                _vals.push(x[k]);
1005            }
1006        }
1007        return _vals;
1008    }
1009    function entries(x) {
1010        const _entries = [];
1011        for (const k in x) {
1012            if (x.hasOwnProperty(k)) {
1013                _entries.push({
1014                    key: k,
1015                    value: x[k]
1016                });
1017            }
1018        }
1019        return _entries;
1020    }
1021    function flagKeys(f) {
1022        return keys(f);
1023    }
1024    function isBoolean$1(b) {
1025        return b === true || b === false;
1026    }
1027    /**
1028     * Convert a string into a valid variable name
1029     */
1030    function varName(s) {
1031        // Replace non-alphanumeric characters (anything besides a-zA-Z0-9_) with _
1032        const alphanumericS = s.replace(/\W/g, '_');
1033        // Add _ if the string has leading numbers.
1034        return (s.match(/^\d+/) ? '_' : '') + alphanumericS;
1035    }
1036    function logicalExpr(op, cb) {
1037        if (isLogicalNot(op)) {
1038            return '!(' + logicalExpr(op.not, cb) + ')';
1039        }
1040        else if (isLogicalAnd(op)) {
1041            return '(' + op.and.map((and) => logicalExpr(and, cb)).join(') && (') + ')';
1042        }
1043        else if (isLogicalOr(op)) {
1044            return '(' + op.or.map((or) => logicalExpr(or, cb)).join(') || (') + ')';
1045        }
1046        else {
1047            return cb(op);
1048        }
1049    }
1050    /**
1051     * Delete nested property of an object, and delete the ancestors of the property if they become empty.
1052     */
1053    function deleteNestedProperty(obj, orderedProps) {
1054        if (orderedProps.length === 0) {
1055            return true;
1056        }
1057        const prop = orderedProps.shift();
1058        if (deleteNestedProperty(obj[prop], orderedProps)) {
1059            delete obj[prop];
1060        }
1061        return keys(obj).length === 0;
1062    }
1063    function titlecase(s) {
1064        return s.charAt(0).toUpperCase() + s.substr(1);
1065    }
1066    /**
1067     * Converts a path to an access path with datum.
1068     * @param path The field name.
1069     * @param datum The string to use for `datum`.
1070     */
1071    function accessPathWithDatum(path, datum = 'datum') {
1072        const pieces = splitAccessPath(path);
1073        const prefixes = [];
1074        for (let i = 1; i <= pieces.length; i++) {
1075            const prefix = `[${pieces
1076            .slice(0, i)
1077            .map($)
1078            .join('][')}]`;
1079            prefixes.push(`${datum}${prefix}`);
1080        }
1081        return prefixes.join(' && ');
1082    }
1083    /**
1084     * Return access with datum to the flattened field.
1085     *
1086     * @param path The field name.
1087     * @param datum The string to use for `datum`.
1088     */
1089    function flatAccessWithDatum(path, datum = 'datum') {
1090        return `${datum}[${$(splitAccessPath(path).join('.'))}]`;
1091    }
1092    /**
1093     * Replaces path accesses with access to non-nested field.
1094     * For example, `foo["bar"].baz` becomes `foo\\.bar\\.baz`.
1095     */
1096    function replacePathInField(path) {
1097        return `${splitAccessPath(path)
1098        .map(p => p.replace('.', '\\.'))
1099        .join('\\.')}`;
1100    }
1101    /**
1102     * Remove path accesses with access from field.
1103     * For example, `foo["bar"].baz` becomes `foo.bar.baz`.
1104     */
1105    function removePathFromField(path) {
1106        return `${splitAccessPath(path).join('.')}`;
1107    }
1108    /**
1109     * Count the depth of the path. Returns 1 for fields that are not nested.
1110     */
1111    function accessPathDepth(path) {
1112        if (!path) {
1113            return 0;
1114        }
1115        return splitAccessPath(path).length;
1116    }
1117    /**
1118     * This is a replacement for chained || for numeric properties or properties that respect null so that 0 will be included.
1119     */
1120    function getFirstDefined(...args) {
1121        for (const arg of args) {
1122            if (arg !== undefined) {
1123                return arg;
1124            }
1125        }
1126        return undefined;
1127    }
1128    // variable used to generate id
1129    let idCounter = 42;
1130    /**
1131     * Returns a new random id every time it gets called.
1132     *
1133     * Has side effect!
1134     */
1135    function uniqueId(prefix) {
1136        const id$$1 = ++idCounter;
1137        return prefix ? String(prefix) + id$$1 : id$$1;
1138    }
1139    /**
1140     * Resets the id counter used in uniqueId. This can be useful for testing.
1141     */
1142    function resetIdCounter() {
1143        idCounter = 42;
1144    }
1145
1146    var util = /*#__PURE__*/Object.freeze({
1147        deepEqual: deepEqual,
1148        duplicate: duplicate,
1149        globalWholeWordRegExp: globalWholeWordRegExp,
1150        pick: pick,
1151        omit: omit,
1152        stringify: stringify,
1153        hash: hash,
1154        contains: contains,
1155        without: without,
1156        union: union,
1157        some: some,
1158        every: every,
1159        flatten: flatten,
1160        fill: fill,
1161        mergeDeep: mergeDeep,
1162        unique: unique,
1163        isEqual: isEqual,
1164        setEqual: setEqual,
1165        hasIntersection: hasIntersection,
1166        prefixGenerator: prefixGenerator,
1167        fieldIntersection: fieldIntersection,
1168        isNumeric: isNumeric,
1169        differArray: differArray,
1170        keys: keys,
1171        vals: vals,
1172        entries: entries,
1173        flagKeys: flagKeys,
1174        isBoolean: isBoolean$1,
1175        varName: varName,
1176        logicalExpr: logicalExpr,
1177        deleteNestedProperty: deleteNestedProperty,
1178        titlecase: titlecase,
1179        accessPathWithDatum: accessPathWithDatum,
1180        flatAccessWithDatum: flatAccessWithDatum,
1181        replacePathInField: replacePathInField,
1182        removePathFromField: removePathFromField,
1183        accessPathDepth: accessPathDepth,
1184        getFirstDefined: getFirstDefined,
1185        uniqueId: uniqueId,
1186        resetIdCounter: resetIdCounter
1187    });
1188
1189    const AGGREGATE_OP_INDEX = {
1190        argmax: 1,
1191        argmin: 1,
1192        average: 1,
1193        count: 1,
1194        distinct: 1,
1195        max: 1,
1196        mean: 1,
1197        median: 1,
1198        min: 1,
1199        missing: 1,
1200        q1: 1,
1201        q3: 1,
1202        ci0: 1,
1203        ci1: 1,
1204        stderr: 1,
1205        stdev: 1,
1206        stdevp: 1,
1207        sum: 1,
1208        valid: 1,
1209        values: 1,
1210        variance: 1,
1211        variancep: 1
1212    };
1213    const AGGREGATE_OPS = flagKeys(AGGREGATE_OP_INDEX);
1214    function isAggregateOp(a) {
1215        return !!AGGREGATE_OP_INDEX[a];
1216    }
1217    const COUNTING_OPS = ['count', 'valid', 'missing', 'distinct'];
1218    function isCountingAggregateOp(aggregate) {
1219        return aggregate && contains(COUNTING_OPS, aggregate);
1220    }
1221    function isMinMaxOp(aggregate) {
1222        return aggregate && contains(['min', 'max'], aggregate);
1223    }
1224    /** Additive-based aggregation operations.  These can be applied to stack. */
1225    const SUM_OPS = ['count', 'sum', 'distinct', 'valid', 'missing'];
1226    /**
1227     * Aggregation operators that always produce values within the range [domainMin, domainMax].
1228     */
1229    const SHARED_DOMAIN_OPS = ['mean', 'average', 'median', 'q1', 'q3', 'min', 'max'];
1230    const SHARED_DOMAIN_OP_INDEX = toSet(SHARED_DOMAIN_OPS);
1231
1232    var aggregate = /*#__PURE__*/Object.freeze({
1233        AGGREGATE_OPS: AGGREGATE_OPS,
1234        isAggregateOp: isAggregateOp,
1235        COUNTING_OPS: COUNTING_OPS,
1236        isCountingAggregateOp: isCountingAggregateOp,
1237        isMinMaxOp: isMinMaxOp,
1238        SUM_OPS: SUM_OPS,
1239        SHARED_DOMAIN_OPS: SHARED_DOMAIN_OPS,
1240        SHARED_DOMAIN_OP_INDEX: SHARED_DOMAIN_OP_INDEX
1241    });
1242
1243    const AXIS_PARTS = ['domain', 'grid', 'labels', 'ticks', 'title'];
1244    /**
1245     * A dictionary listing whether a certain axis property is applicable for only main axes or only grid axes.
1246     * (Properties not listed are applicable for both)
1247     */
1248    const AXIS_PROPERTY_TYPE = {
1249        grid: 'grid',
1250        gridColor: 'grid',
1251        gridDash: 'grid',
1252        gridOpacity: 'grid',
1253        gridScale: 'grid',
1254        gridWidth: 'grid',
1255        orient: 'main',
1256        bandPosition: 'both',
1257        domain: 'main',
1258        domainColor: 'main',
1259        domainOpacity: 'main',
1260        domainWidth: 'main',
1261        format: 'main',
1262        labelAlign: 'main',
1263        labelAngle: 'main',
1264        labelBaseline: 'main',
1265        labelBound: 'main',
1266        labelColor: 'main',
1267        labelFlush: 'main',
1268        labelFlushOffset: 'main',
1269        labelFont: 'main',
1270        labelFontSize: 'main',
1271        labelFontWeight: 'main',
1272        labelLimit: 'main',
1273        labelOpacity: 'main',
1274        labelOverlap: 'main',
1275        labelPadding: 'main',
1276        labels: 'main',
1277        maxExtent: 'main',
1278        minExtent: 'main',
1279        offset: 'main',
1280        position: 'main',
1281        tickColor: 'main',
1282        tickExtra: 'main',
1283        tickOffset: 'both',
1284        tickOpacity: 'main',
1285        tickRound: 'main',
1286        ticks: 'main',
1287        tickSize: 'main',
1288        title: 'main',
1289        titleAlign: 'main',
1290        titleAngle: 'main',
1291        titleBaseline: 'main',
1292        titleColor: 'main',
1293        titleFont: 'main',
1294        titleFontSize: 'main',
1295        titleFontWeight: 'main',
1296        titleLimit: 'main',
1297        titleOpacity: 'main',
1298        titlePadding: 'main',
1299        titleX: 'main',
1300        titleY: 'main',
1301        tickWidth: 'both',
1302        tickCount: 'both',
1303        values: 'both',
1304        scale: 'both',
1305        zindex: 'both' // this is actually set afterward, so it doesn't matter
1306    };
1307    const COMMON_AXIS_PROPERTIES_INDEX = {
1308        orient: 1,
1309        bandPosition: 1,
1310        domain: 1,
1311        domainColor: 1,
1312        domainOpacity: 1,
1313        domainWidth: 1,
1314        format: 1,
1315        grid: 1,
1316        gridColor: 1,
1317        gridDash: 1,
1318        gridOpacity: 1,
1319        gridWidth: 1,
1320        labelAlign: 1,
1321        labelAngle: 1,
1322        labelBaseline: 1,
1323        labelBound: 1,
1324        labelColor: 1,
1325        labelFlush: 1,
1326        labelFlushOffset: 1,
1327        labelFont: 1,
1328        labelFontSize: 1,
1329        labelFontWeight: 1,
1330        labelLimit: 1,
1331        labelOpacity: 1,
1332        labelOverlap: 1,
1333        labelPadding: 1,
1334        labels: 1,
1335        maxExtent: 1,
1336        minExtent: 1,
1337        offset: 1,
1338        position: 1,
1339        tickColor: 1,
1340        tickCount: 1,
1341        tickExtra: 1,
1342        tickOffset: 1,
1343        tickOpacity: 1,
1344        tickRound: 1,
1345        ticks: 1,
1346        tickSize: 1,
1347        tickWidth: 1,
1348        title: 1,
1349        titleAlign: 1,
1350        titleAngle: 1,
1351        titleBaseline: 1,
1352        titleColor: 1,
1353        titleFont: 1,
1354        titleFontSize: 1,
1355        titleFontWeight: 1,
1356        titleLimit: 1,
1357        titleOpacity: 1,
1358        titlePadding: 1,
1359        titleX: 1,
1360        titleY: 1,
1361        values: 1,
1362        zindex: 1
1363    };
1364    const AXIS_PROPERTIES_INDEX = Object.assign({}, COMMON_AXIS_PROPERTIES_INDEX, { encoding: 1, labelAngle: 1, tickStep: 1 });
1365    const VG_AXIS_PROPERTIES_INDEX = Object.assign({ gridScale: 1, scale: 1 }, COMMON_AXIS_PROPERTIES_INDEX, { encode: 1 });
1366    function isAxisProperty(prop) {
1367        return !!AXIS_PROPERTIES_INDEX[prop];
1368    }
1369    const VG_AXIS_PROPERTIES = flagKeys(VG_AXIS_PROPERTIES_INDEX);
1370    // Export for dependent projects
1371    const AXIS_PROPERTIES = flagKeys(AXIS_PROPERTIES_INDEX);
1372
1373    var axis = /*#__PURE__*/Object.freeze({
1374        AXIS_PARTS: AXIS_PARTS,
1375        AXIS_PROPERTY_TYPE: AXIS_PROPERTY_TYPE,
1376        isAxisProperty: isAxisProperty,
1377        VG_AXIS_PROPERTIES: VG_AXIS_PROPERTIES,
1378        AXIS_PROPERTIES: AXIS_PROPERTIES
1379    });
1380
1381    /*! *****************************************************************************
1382    Copyright (c) Microsoft Corporation. All rights reserved.
1383    Licensed under the Apache License, Version 2.0 (the "License");
1383 you may not use
1384    this file except in compliance with the License. You may obtain a copy of the
1385    License at http://www.apache.org/licenses/LICENSE-2.0
1386
1387    THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
1388    KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
1389    WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
1390    MERCHANTABLITY OR NON-INFRINGEMENT.
1391
1392    See the Apache Version 2.0 License for specific language governing permissions
1393    and limitations under the License.
1394    ***************************************************************************** */
1395
1396    function __rest(s, e) {
1397        var t = {};
1398        for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
1399            t[p] = s[p];
1400        if (s != null && typeof Object.getOwnPropertySymbols === "function")
1401            for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
1402                t[p[i]] = s[p[i]];
1403        return t;
1404    }
1405
1406    /*
1407     * Constants and utilities for encoding channels (Visual variables)
1408     * such as 'x', 'y', 'color'.
1409     */
1410    var Channel;
1411    (function (Channel) {
1412        // Facet
1413        Channel.ROW = 'row';
1414        Channel.COLUMN = 'column';
1415        // Position
1416        Channel.X = 'x';
1417        Channel.Y = 'y';
1418        Channel.X2 = 'x2';
1419        Channel.Y2 = 'y2';
1420        Channel.XERROR = 'xError';
1421        Channel.YERROR = 'yError';
1422        Channel.XERROR2 = 'xError2';
1423        Channel.YERROR2 = 'yError2';
1424        // Geo Position
1425        Channel.LATITUDE = 'latitude';
1426        Channel.LONGITUDE = 'longitude';
1427        Channel.LATITUDE2 = 'latitude2';
1428        Channel.LONGITUDE2 = 'longitude2';
1429        // Mark property with scale
1430        Channel.COLOR = 'color';
1431        Channel.FILL = 'fill';
1432        Channel.STROKE = 'stroke';
1433        Channel.SHAPE = 'shape';
1434        Channel.SIZE = 'size';
1435        Channel.OPACITY = 'opacity';
1436        Channel.FILLOPACITY = 'fillOpacity';
1437        Channel.STROKEOPACITY = 'strokeOpacity';
1438        Channel.STROKEWIDTH = 'strokeWidth';
1439        // Non-scale channel
1440        Channel.TEXT = 'text';
1441        Channel.ORDER = 'order';
1442        Channel.DETAIL = 'detail';
1443        Channel.KEY = 'key';
1444        Channel.TOOLTIP = 'tooltip';
1445        Channel.HREF = 'href';
1446    })(Channel || (Channel = {}));
1447    const X = Channel.X;
1448    const Y = Channel.Y;
1449    const X2 = Channel.X2;
1450    const Y2 = Channel.Y2;
1451    const XERROR = Channel.XERROR;
1452    const YERROR = Channel.YERROR;
1453    const XERROR2 = Channel.XERROR2;
1454    const YERROR2 = Channel.YERROR2;
1455    const LATITUDE = Channel.LATITUDE;
1456    const LATITUDE2 = Channel.LATITUDE2;
1457    const LONGITUDE = Channel.LONGITUDE;
1458    const LONGITUDE2 = Channel.LONGITUDE2;
1459    const ROW = Channel.ROW;
1460    const COLUMN = Channel.COLUMN;
1461    const SHAPE = Channel.SHAPE;
1462    const SIZE = Channel.SIZE;
1463    const COLOR = Channel.COLOR;
1464    const FILL = Channel.FILL;
1465    const STROKE = Channel.STROKE;
1466    const TEXT = Channel.TEXT;
1467    const DETAIL = Channel.DETAIL;
1468    const KEY = Channel.KEY;
1469    const ORDER = Channel.ORDER;
1470    const OPACITY = Channel.OPACITY;
1471    const FILLOPACITY = Channel.FILLOPACITY;
1472    const STROKEOPACITY = Channel.STROKEOPACITY;
1473    const STROKEWIDTH = Channel.STROKEWIDTH;
1474    const TOOLTIP = Channel.TOOLTIP;
1475    const HREF = Channel.HREF;
1476    const GEOPOSITION_CHANNEL_INDEX = {
1477        longitude: 1,
1478        longitude2: 1,
1479        latitude: 1,
1480        latitude2: 1
1481    };
1482    const GEOPOSITION_CHANNELS = flagKeys(GEOPOSITION_CHANNEL_INDEX);
1483    const UNIT_CHANNEL_INDEX = Object.assign({ 
1484        // position
1485        x: 1, y: 1, x2: 1, y2: 1, xError: 1, yError: 1, xError2: 1, yError2: 1 }, GEOPOSITION_CHANNEL_INDEX, { 
1486        // color
1487        color: 1, fill: 1, stroke: 1, 
1488        // other non-position with scale
1489        opacity: 1, fillOpacity: 1, strokeOpacity: 1, strokeWidth: 1, size: 1, shape: 1, 
1490        // channels without scales
1491        order: 1, text: 1, detail: 1, key: 1, tooltip: 1, href: 1 });
1492    function isColorChannel(channel) {
1493        return channel === 'color' || channel === 'fill' || channel === 'stroke';
1494    }
1495    const FACET_CHANNEL_INDEX = {
1496        row: 1,
1497        column: 1
1498    };
1499    const CHANNEL_INDEX = Object.assign({}, UNIT_CHANNEL_INDEX, FACET_CHANNEL_INDEX);
1500    const CHANNELS = flagKeys(CHANNEL_INDEX);
1501    const SINGLE_DEF_CHANNEL_INDEX = __rest(CHANNEL_INDEX, ["order", "detail"]);
1502    /**
1503     * Channels that cannot have an array of channelDef.
1504     * model.fieldDef, getFieldDef only work for these channels.
1505     *
1506     * (The only two channels that can have an array of channelDefs are "detail" and "order".
1507     * Since there can be multiple fieldDefs for detail and order, getFieldDef/model.fieldDef
1508     * are not applicable for them.  Similarly, selection projection won't work with "detail" and "order".)
1509     */
1510    const SINGLE_DEF_CHANNELS = flagKeys(SINGLE_DEF_CHANNEL_INDEX);
1511    function isChannel(str) {
1512        return !!CHANNEL_INDEX[str];
1513    }
1514    function isSecondaryRangeChannel(c) {
1515        const main = getMainRangeChannel(c);
1516        return main !== c;
1517    }
1518    function getMainRangeChannel(channel) {
1519        switch (channel) {
1520            case 'x2':
1521                return 'x';
1522            case 'y2':
1523                return 'y';
1524            case 'latitude2':
1525                return 'latitude';
1526            case 'longitude2':
1527                return 'longitude';
1528        }
1529        return channel;
1530    }
1531    // CHANNELS without COLUMN, ROW
1532    const UNIT_CHANNELS = flagKeys(UNIT_CHANNEL_INDEX);
1533    // NONPOSITION_CHANNELS = UNIT_CHANNELS without X, Y, X2, Y2;
1534    const // The rest of unit channels then have scale
1535    NONPOSITION_CHANNEL_INDEX = __rest(UNIT_CHANNEL_INDEX, ["x", "y", "x2", "y2", "xError", "yError", "xError2", "yError2", "latitude", "longitude", "latitude2", "longitude2"]);
1536    const NONPOSITION_CHANNELS = flagKeys(NONPOSITION_CHANNEL_INDEX);
1537    // POSITION_SCALE_CHANNELS = X and Y;
1538    const POSITION_SCALE_CHANNEL_INDEX = { x: 1, y: 1 };
1539    const POSITION_SCALE_CHANNELS = flagKeys(POSITION_SCALE_CHANNEL_INDEX);
1540    // NON_POSITION_SCALE_CHANNEL = SCALE_CHANNELS without X, Y
1541    const NONPOSITION_SCALE_CHANNEL_INDEX = __rest(NONPOSITION_CHANNEL_INDEX, ["text", "tooltip", "href", "detail", "key", "order"]);
1542    const NONPOSITION_SCALE_CHANNELS = flagKeys(NONPOSITION_SCALE_CHANNEL_INDEX);
1543    function isNonPositionScaleChannel(channel) {
1544        return !!NONPOSITION_CHANNEL_INDEX[channel];
1545    }
1546    // Declare SCALE_CHANNEL_INDEX
1547    const SCALE_CHANNEL_INDEX = Object.assign({}, POSITION_SCALE_CHANNEL_INDEX, NONPOSITION_SCALE_CHANNEL_INDEX);
1548    /** List of channels with scales */
1549    const SCALE_CHANNELS = flagKeys(SCALE_CHANNEL_INDEX);
1550    function isScaleChannel(channel) {
1551        return !!SCALE_CHANNEL_INDEX[channel];
1552    }
1553    /**
1554     * Return whether a channel supports a particular mark type.
1555     * @param channel  channel name
1556     * @param mark the mark type
1557     * @return whether the mark supports the channel
1558     */
1559    function supportMark(channel, mark) {
1560        return getSupportedMark(channel)[mark];
1561    }
1562    /**
1563     * Return a dictionary showing whether a channel supports mark type.
1564     * @param channel
1565     * @return A dictionary mapping mark types to 'always', 'binned', or undefined
1566     */
1567    function getSupportedMark(channel) {
1568        switch (channel) {
1569            case COLOR:
1570            case FILL:
1571            case STROKE:
1572            case DETAIL:
1573            case KEY:
1574            case TOOLTIP:
1575            case HREF:
1576            case ORDER: // TODO: revise (order might not support rect, which is not stackable?)
1577            case OPACITY:
1578            case FILLOPACITY:
1579            case STROKEOPACITY:
1580            case STROKEWIDTH:
1581            case ROW:
1582            case COLUMN:
1583                return {
1584                    // all marks
1585                    point: 'always',
1586                    tick: 'always',
1587                    rule: 'always',
1588                    circle: 'always',
1589                    square: 'always',
1590                    bar: 'always',
1591                    rect: 'always',
1592                    line: 'always',
1593                    trail: 'always',
1594                    area: 'always',
1595                    text: 'always',
1596                    geoshape: 'always'
1597                };
1598            case X:
1599            case Y:
1600            case LATITUDE:
1601            case LONGITUDE:
1602                return {
1603                    // all marks except geoshape. geoshape does not use X, Y -- it uses a projection
1604                    point: 'always',
1605                    tick: 'always',
1606                    rule: 'always',
1607                    circle: 'always',
1608                    square: 'always',
1609                    bar: 'always',
1610                    rect: 'always',
1611                    line: 'always',
1612                    trail: 'always',
1613                    area: 'always',
1614                    text: 'always'
1615                };
1616            case X2:
1617            case Y2:
1618            case LATITUDE2:
1619            case LONGITUDE2:
1620                return {
1621                    rule: 'always',
1622                    bar: 'always',
1623                    rect: 'always',
1624                    area: 'always',
1625                    circle: 'binned',
1626                    point: 'binned',
1627                    square: 'binned',
1628                    tick: 'binned'
1629                };
1630            case SIZE:
1631                return {
1632                    point: 'always',
1633                    tick: 'always',
1634                    rule: 'always',
1635                    circle: 'always',
1636                    square: 'always',
1637                    bar: 'always',
1638                    text: 'always',
1639                    line: 'always',
1640                    trail: 'always'
1641                };
1642            case SHAPE:
1643                return { point: 'always', geoshape: 'always' };
1644            case TEXT:
1645                return { text: 'always' };
1646            case XERROR:
1647            case YERROR:
1648            case XERROR2:
1649            case YERROR2:
1650                return {};
1651        }
1652    }
1653    function rangeType(channel) {
1654        switch (channel) {
1655            case X:
1656            case Y:
1657            case SIZE:
1658            case STROKEWIDTH:
1659            case OPACITY:
1660            case FILLOPACITY:
1661            case STROKEOPACITY:
1662            // X2 and Y2 use X and Y scales, so they similarly have continuous range.
1663            case X2:
1664            case Y2:
1665            case XERROR:
1666            case YERROR:
1667            case XERROR2:
1668            case YERROR2:
1669                return undefined;
1670            case ROW:
1671            case COLUMN:
1672            case SHAPE:
1673            // TEXT, TOOLTIP, and HREF have no scale but have discrete output
1674            case TEXT:
1675            case TOOLTIP:
1676            case HREF:
1677                return 'discrete';
1678            // Color can be either continuous or discrete, depending on scale type.
1679            case COLOR:
1680            case FILL:
1681            case STROKE:
1682                return 'flexible';
1683            // No scale, no range type.
1684            case LATITUDE:
1685            case LONGITUDE:
1686            case LATITUDE2:
1687            case LONGITUDE2:
1688            case DETAIL:
1689            case KEY:
1690            case ORDER:
1691                return undefined;
1692        }
1693        /* istanbul ignore next: should never reach here. */
1694        throw new Error('rangeType not implemented for ' + channel);
1695    }
1696
1697    var channel = /*#__PURE__*/Object.freeze({
1698        get Channel () { return Channel; },
1699        X: X,
1700        Y: Y,
1701        X2: X2,
1702        Y2: Y2,
1703        XERROR: XERROR,
1704        YERROR: YERROR,
1705        XERROR2: XERROR2,
1706        YERROR2: YERROR2,
1707        LATITUDE: LATITUDE,
1708        LATITUDE2: LATITUDE2,
1709        LONGITUDE: LONGITUDE,
1710        LONGITUDE2: LONGITUDE2,
1711        ROW: ROW,
1712        COLUMN: COLUMN,
1713        SHAPE: SHAPE,
1714        SIZE: SIZE,
1715        COLOR: COLOR,
1716        FILL: FILL,
1717        STROKE: STROKE,
1718        TEXT: TEXT,
1719        DETAIL: DETAIL,
1720        KEY: KEY,
1721        ORDER: ORDER,
1722        OPACITY: OPACITY,
1723        FILLOPACITY: FILLOPACITY,
1724        STROKEOPACITY: STROKEOPACITY,
1725        STROKEWIDTH: STROKEWIDTH,
1726        TOOLTIP: TOOLTIP,
1727        HREF: HREF,
1728        GEOPOSITION_CHANNEL_INDEX: GEOPOSITION_CHANNEL_INDEX,
1729        GEOPOSITION_CHANNELS: GEOPOSITION_CHANNELS,
1730        isColorChannel: isColorChannel,
1731        CHANNELS: CHANNELS,
1732        SINGLE_DEF_CHANNELS: SINGLE_DEF_CHANNELS,
1733        isChannel: isChannel,
1734        isSecondaryRangeChannel: isSecondaryRangeChannel,
1735        getMainRangeChannel: getMainRangeChannel,
1736        UNIT_CHANNELS: UNIT_CHANNELS,
1737        NONPOSITION_CHANNELS: NONPOSITION_CHANNELS,
1738        POSITION_SCALE_CHANNELS: POSITION_SCALE_CHANNELS,
1739        NONPOSITION_SCALE_CHANNELS: NONPOSITION_SCALE_CHANNELS,
1740        isNonPositionScaleChannel: isNonPositionScaleChannel,
1741        SCALE_CHANNELS: SCALE_CHANNELS,
1742        isScaleChannel: isScaleChannel,
1743        supportMark: supportMark,
1744        rangeType: rangeType
1745    });
1746
1747    function binToString(bin) {
1748        if (isBoolean(bin)) {
1749            return 'bin';
1750        }
1751        return ('bin' +
1752            keys(bin)
1753                .map(p => varName(`_${p}_${bin[p]}`))
1754                .join(''));
1755    }
1756    function isBinning(bin) {
1757        return bin === true || isBinParams(bin);
1758    }
1759    function isBinned(bin) {
1760        return bin === 'binned';
1761    }
1762    function isBinParams(bin) {
1763        return isObject(bin);
1764    }
1765    function autoMaxBins(channel) {
1766        switch (channel) {
1767            case ROW:
1768            case COLUMN:
1769            case SIZE:
1770            case COLOR:
1771            case FILL:
1772            case STROKE:
1773            case STROKEWIDTH:
1774            case OPACITY:
1775            case FILLOPACITY:
1776            case STROKEOPACITY:
1777            // Facets and Size shouldn't have too many bins
1778            // We choose 6 like shape to simplify the rule
1779            case SHAPE:
1780                return 6; // Vega's "shape" has 6 distinct values
1781            default:
1782                return 10;
1783        }
1784    }
1785
1786    var bin$1 = /*#__PURE__*/Object.freeze({
1787        binToString: binToString,
1788        isBinning: isBinning,
1789        isBinned: isBinned,
1790        isBinParams: isBinParams,
1791        autoMaxBins: autoMaxBins
1792    });
1793
1794    var Mark;
1795    (function (Mark) {
1796        Mark.AREA = 'area';
1797        Mark.BAR = 'bar';
1798        Mark.LINE = 'line';
1799        Mark.POINT = 'point';
1800        Mark.RECT = 'rect';
1801        Mark.RULE = 'rule';
1802        Mark.TEXT = 'text';
1803        Mark.TICK = 'tick';
1804        Mark.TRAIL = 'trail';
1805        Mark.CIRCLE = 'circle';
1806        Mark.SQUARE = 'square';
1807        Mark.GEOSHAPE = 'geoshape';
1808    })(Mark || (Mark = {}));
1809    const AREA = Mark.AREA;
1810    const BAR = Mark.BAR;
1811    const LINE = Mark.LINE;
1812    const POINT = Mark.POINT;
1813    const TEXT$1 = Mark.TEXT;
1814    const TICK = Mark.TICK;
1815    const TRAIL = Mark.TRAIL;
1816    const RECT = Mark.RECT;
1817    const RULE = Mark.RULE;
1818    const GEOSHAPE = Mark.GEOSHAPE;
1819    const CIRCLE = Mark.CIRCLE;
1820    const SQUARE = Mark.SQUARE;
1821    // Using mapped type to declare index, ensuring we always have all marks when we add more.
1822    const MARK_INDEX = {
1823        area: 1,
1824        bar: 1,
1825        line: 1,
1826        point: 1,
1827        text: 1,
1828        tick: 1,
1829        trail: 1,
1830        rect: 1,
1831        geoshape: 1,
1832        rule: 1,
1833        circle: 1,
1834        square: 1
1835    };
1836    function isMark(m) {
1837        return !!MARK_INDEX[m];
1838    }
1839    function isPathMark(m) {
1840        return contains(['line', 'area', 'trail'], m);
1841    }
1842    const PRIMITIVE_MARKS = flagKeys(MARK_INDEX);
1843    function isMarkDef(mark) {
1844        return mark['type'];
1845    }
1846    const PRIMITIVE_MARK_INDEX = toSet(PRIMITIVE_MARKS);
1847    function isPrimitiveMark(mark) {
1848        const markType = isMarkDef(mark) ? mark.type : mark;
1849        return markType in PRIMITIVE_MARK_INDEX;
1850    }
1851    const STROKE_CONFIG = [
1852        'stroke',
1853        'strokeWidth',
1854        'strokeDash',
1855        'strokeDashOffset',
1856        'strokeOpacity',
1857        'strokeJoin',
1858        'strokeMiterLimit'
1859    ];
1860    const FILL_CONFIG = ['fill', 'fillOpacity'];
1861    const FILL_STROKE_CONFIG = [].concat(STROKE_CONFIG, FILL_CONFIG);
1862    const VL_ONLY_MARK_CONFIG_PROPERTIES = ['filled', 'color', 'tooltip'];
1863    const VL_ONLY_MARK_SPECIFIC_CONFIG_PROPERTY_INDEX = {
1864        area: ['line', 'point'],
1865        bar: ['binSpacing', 'continuousBandSize', 'discreteBandSize'],
1866        line: ['point'],
1867        text: ['shortTimeLabels'],
1868        tick: ['bandSize', 'thickness']
1869    };
1870    const defaultMarkConfig = {
1871        color: '#4c78a8',
1872        tooltip: { content: 'encoding' }
1873    };
1874    const defaultBarConfig = {
1875        binSpacing: 1,
1876        continuousBandSize: 5
1877    };
1878    const defaultTickConfig = {
1879        thickness: 1
1880    };
1881
1882    var mark = /*#__PURE__*/Object.freeze({
1883        get Mark () { return Mark; },
1884        AREA: AREA,
1885        BAR: BAR,
1886        LINE: LINE,
1887        POINT: POINT,
1888        TEXT: TEXT$1,
1889        TICK: TICK,
1890        TRAIL: TRAIL,
1891        RECT: RECT,
1892        RULE: RULE,
1893        GEOSHAPE: GEOSHAPE,
1894        CIRCLE: CIRCLE,
1895        SQUARE: SQUARE,
1896        isMark: isMark,
1897        isPathMark: isPathMark,
1898        PRIMITIVE_MARKS: PRIMITIVE_MARKS,
1899        isMarkDef: isMarkDef,
1900        isPrimitiveMark: isPrimitiveMark,
1901        STROKE_CONFIG: STROKE_CONFIG,
1902        FILL_CONFIG: FILL_CONFIG,
1903        FILL_STROKE_CONFIG: FILL_STROKE_CONFIG,
1904        VL_ONLY_MARK_CONFIG_PROPERTIES: VL_ONLY_MARK_CONFIG_PROPERTIES,
1905        VL_ONLY_MARK_SPECIFIC_CONFIG_PROPERTY_INDEX: VL_ONLY_MARK_SPECIFIC_CONFIG_PROPERTY_INDEX,
1906        defaultMarkConfig: defaultMarkConfig,
1907        defaultBarConfig: defaultBarConfig,
1908        defaultTickConfig: defaultTickConfig
1909    });
1910
1911    /**
1912     * Vega-Lite's singleton logger utility.
1913     */
1914    /**
1915     * Main (default) Vega Logger instance for Vega-Lite
1916     */
1917    const main$1 = logger(Warn);
1918    let current = main$1;
1919    /**
1920     * Set the singleton logger to be a custom logger
1921     */
1922    function set(newLogger) {
1923        current = newLogger;
1924        return current;
1925    }
1926    /**
1927     * Reset the main logger to use the default Vega Logger
1928     */
1929    function reset() {
1930        current = main$1;
1931        return current;
1932    }
1933    function warn(..._) {
1934        current.warn.apply(current, arguments);
1935    }
1936    function debug(..._) {
1937        current.debug.apply(current, arguments);
1938    }
1939    /**
1940     * Collection of all Vega-Lite Error Messages
1941     */
1942    var message;
1943    (function (message) {
1944        message.INVALID_SPEC = 'Invalid spec';
1945        // FIT
1946        message.FIT_NON_SINGLE = 'Autosize "fit" only works for single views and layered views.';
1947        message.CANNOT_FIX_RANGE_STEP_WITH_FIT = 'Cannot use a fixed value of "rangeStep" when "autosize" is "fit".';
1948        // SELECTION
1949        function cannotProjectOnChannelWithoutField(channel) {
1950            return `Cannot project a selection on encoding channel "${channel}", which has no field.`;
1951        }
1952        message.cannotProjectOnChannelWithoutField = cannotProjectOnChannelWithoutField;
1953        function nearestNotSupportForContinuous(mark) {
1954            return `The "nearest" transform is not supported for ${mark} marks.`;
1955        }
1956        message.nearestNotSupportForContinuous = nearestNotSupportForContinuous;
1957        function selectionNotSupported(mark) {
1958            return `Selection not supported for ${mark} yet`;
1959        }
1960        message.selectionNotSupported = selectionNotSupported;
1961        function selectionNotFound(name) {
1962            return `Cannot find a selection named "${name}"`;
1963        }
1964        message.selectionNotFound = selectionNotFound;
1965        message.SCALE_BINDINGS_CONTINUOUS = 'Scale bindings are currently only supported for scales with unbinned, continuous domains.';
1966        // REPEAT
1967        function noSuchRepeatedValue(field$$1) {
1968            return `Unknown repeated value "${field$$1}".`;
1969        }
1970        message.noSuchRepeatedValue = noSuchRepeatedValue;
1971        // CONCAT
1972        message.CONCAT_CANNOT_SHARE_AXIS = 'Axes cannot be shared in concatenated views.';
1973        // REPEAT
1974        message.REPEAT_CANNOT_SHARE_AXIS = 'Axes cannot be shared in repeated views.';
1975        // TITLE
1976        function cannotSetTitleAnchor(type) {
1977            return `Cannot set title "anchor" for a ${type} spec`;
1978        }
1979        message.cannotSetTitleAnchor = cannotSetTitleAnchor;
1980        // DATA
1981        function unrecognizedParse(p) {
1982            return `Unrecognized parse "${p}".`;
1983        }
1984        message.unrecognizedParse = unrecognizedParse;
1985        function differentParse(field$$1, local, ancestor) {
1986            return `An ancestor parsed field "${field$$1}" as ${ancestor} but a child wants to parse the field as ${local}.`;
1987        }
1988        message.differentParse = differentParse;
1989        // TRANSFORMS
1990        function invalidTransformIgnored(transform) {
1991            return `Ignoring an invalid transform: ${stringify(transform)}.`;
1992        }
1993        message.invalidTransformIgnored = invalidTransformIgnored;
1994        message.NO_FIELDS_NEEDS_AS = 'If "from.fields" is not specified, "as" has to be a string that specifies the key to be used for the data from the secondary source.';
1995        // ENCODING & FACET
1996        function encodingOverridden(channels) {
1997            return `Layer's shared ${channels.join(',')} channel ${channels.length === 1 ? 'is' : 'are'} overriden`;
1998        }
1999        message.encodingOverridden = encodingOverridden;
2000        function projectionOverridden(opt) {
2001            const { parentProjection, projection } = opt;
2002            return `Layer's shared projection ${stringify(parentProjection)} is overridden by a child projection ${stringify(projection)}.`;
2003        }
2004        message.projectionOverridden = projectionOverridden;
2005        function primitiveChannelDef(channel, type, value) {
2006            return `Channel ${channel} is a ${type}. Converted to {value: ${stringify(value)}}.`;
2007        }
2008        message.primitiveChannelDef = primitiveChannelDef;
2009        function invalidFieldType(type) {
2010            return `Invalid field type "${type}"`;
2011        }
2012        message.invalidFieldType = invalidFieldType;
2013        function nonZeroScaleUsedWithLengthMark(mark, channel, opt) {
2014            const scaleText = opt.scaleType
2015                ? `${opt.scaleType} scale`
2016                : opt.zeroFalse
2017                    ? 'scale with zero=false'
2018                    : 'scale with custom domain that excludes zero';
2019            return `A ${scaleText} is used to encode ${mark}'s ${channel}. This can be misleading as the ${channel === 'x' ? 'width' : 'height'} of the ${mark} can be arbitrary based on the scale domain. You may want to use point mark instead.`;
2020        }
2021        message.nonZeroScaleUsedWithLengthMark = nonZeroScaleUsedWithLengthMark;
2022        function invalidFieldTypeForCountAggregate(type, aggregate) {
2023            return `Invalid field type "${type}" for aggregate: "${aggregate}", using "quantitative" instead.`;
2024        }
2025        message.invalidFieldTypeForCountAggregate = invalidFieldTypeForCountAggregate;
2026        function invalidAggregate(aggregate) {
2027            return `Invalid aggregation operator "${aggregate}"`;
2028        }
2029        message.invalidAggregate = invalidAggregate;
2030        function missingFieldType(channel, newType) {
2031            return `Missing type for channel "${channel}", using "${newType}" instead.`;
2032        }
2033        message.missingFieldType = missingFieldType;
2034        function droppingColor(type, opt) {
2035            const { fill: fill$$1, stroke } = opt;
2036            return (`Dropping color ${type} as the plot also has ` + (fill$$1 && stroke ? 'fill and stroke' : fill$$1 ? 'fill' : 'stroke'));
2037        }
2038        message.droppingColor = droppingColor;
2039        function emptyFieldDef(fieldDef, channel) {
2040            return `Dropping ${stringify(fieldDef)} from channel "${channel}" since it does not contain data field or value.`;
2041        }
2042        message.emptyFieldDef = emptyFieldDef;
2043        function latLongDeprecated(channel, type, newChannel) {
2044            return `${channel}-encoding with type ${type} is deprecated. Replacing with ${newChannel}-encoding.`;
2045        }
2046        message.latLongDeprecated = latLongDeprecated;
2047        message.LINE_WITH_VARYING_SIZE = 'Line marks cannot encode size with a non-groupby field. You may want to use trail marks instead.';
2048        function incompatibleChannel(channel, markOrFacet, when) {
2049            return `${channel} dropped as it is incompatible with "${markOrFacet}"${when ? ` when ${when}` : ''}.`;
2050        }
2051        message.incompatibleChannel = incompatibleChannel;
2052        function invalidEncodingChannel(channel) {
2053            return `${channel}-encoding is dropped as ${channel} is not a valid encoding channel.`;
2054        }
2055        message.invalidEncodingChannel = invalidEncodingChannel;
2056        function facetChannelShouldBeDiscrete(channel) {
2057            return `${channel} encoding should be discrete (ordinal / nominal / binned).`;
2058        }
2059        message.facetChannelShouldBeDiscrete = facetChannelShouldBeDiscrete;
2060        function discreteChannelCannotEncode(channel, type) {
2061            return `Using discrete channel "${channel}" to encode "${type}" field can be misleading as it does not encode ${type === 'ordinal' ? 'order' : 'magnitude'}.`;
2062        }
2063        message.discreteChannelCannotEncode = discreteChannelCannotEncode;
2064        // Mark
2065        message.BAR_WITH_POINT_SCALE_AND_RANGESTEP_NULL = 'Bar mark should not be used with point scale when rangeStep is null. Please use band scale instead.';
2066        function lineWithRange(hasX2, hasY2) {
2067            const channels = hasX2 && hasY2 ? 'x2 and y2' : hasX2 ? 'x2' : 'y2';
2068            return `Line mark is for continuous lines and thus cannot be used with ${channels}. We will use the rule mark (line segments) instead.`;
2069        }
2070        message.lineWithRange = lineWithRange;
2071        function orientOverridden(original, actual) {
2072            return `Specified orient "${original}" overridden with "${actual}"`;
2073        }
2074        message.orientOverridden = orientOverridden;
2075        // SCALE
2076        message.CANNOT_UNION_CUSTOM_DOMAIN_WITH_FIELD_DOMAIN = 'custom domain scale cannot be unioned with default field-based domain';
2077        function cannotUseScalePropertyWithNonColor(prop) {
2078            return `Cannot use the scale property "${prop}" with non-color channel.`;
2079        }
2080        message.cannotUseScalePropertyWithNonColor = cannotUseScalePropertyWithNonColor;
2081        function unaggregateDomainHasNoEffectForRawField(fieldDef) {
2082            return `Using unaggregated domain with raw field has no effect (${stringify(fieldDef)}).`;
2083        }
2084        message.unaggregateDomainHasNoEffectForRawField = unaggregateDomainHasNoEffectForRawField;
2085        function unaggregateDomainWithNonSharedDomainOp(aggregate) {
2086            return `Unaggregated domain not applicable for "${aggregate}" since it produces values outside the origin domain of the source data.`;
2087        }
2088        message.unaggregateDomainWithNonSharedDomainOp = unaggregateDomainWithNonSharedDomainOp;
2089        function unaggregatedDomainWithLogScale(fieldDef) {
2090            return `Unaggregated domain is currently unsupported for log scale (${stringify(fieldDef)}).`;
2091        }
2092        message.unaggregatedDomainWithLogScale = unaggregatedDomainWithLogScale;
2093        function cannotApplySizeToNonOrientedMark(mark) {
2094            return `Cannot apply size to non-oriented mark "${mark}".`;
2095        }
2096        message.cannotApplySizeToNonOrientedMark = cannotApplySizeToNonOrientedMark;
2097        function rangeStepDropped(channel) {
2098            return `rangeStep for "${channel}" is dropped as top-level ${channel === 'x' ? 'width' : 'height'} is provided.`;
2099        }
2100        message.rangeStepDropped = rangeStepDropped;
2101        function scaleTypeNotWorkWithChannel(channel, scaleType, defaultScaleType) {
2102            return `Channel "${channel}" does not work with "${scaleType}" scale. We are using "${defaultScaleType}" scale instead.`;
2103        }
2104        message.scaleTypeNotWorkWithChannel = scaleTypeNotWorkWithChannel;
2105        function scaleTypeNotWorkWithFieldDef(scaleType, defaultScaleType) {
2106            return `FieldDef does not work with "${scaleType}" scale. We are using "${defaultScaleType}" scale instead.`;
2107        }
2108        message.scaleTypeNotWorkWithFieldDef = scaleTypeNotWorkWithFieldDef;
2109        function scalePropertyNotWorkWithScaleType(scaleType, propName, channel) {
2110            return `${channel}-scale's "${propName}" is dropped as it does not work with ${scaleType} scale.`;
2111        }
2112        message.scalePropertyNotWorkWithScaleType = scalePropertyNotWorkWithScaleType;
2113        function scaleTypeNotWorkWithMark(mark, scaleType) {
2114            return `Scale type "${scaleType}" does not work with mark "${mark}".`;
2115        }
2116        message.scaleTypeNotWorkWithMark = scaleTypeNotWorkWithMark;
2117        function mergeConflictingProperty(property, propertyOf, v1, v2) {
2118            return `Conflicting ${propertyOf.toString()} property "${property.toString()}" (${stringify(v1)}
2118 and ${stringify(v2)}).  Using ${stringify(v1)}.`;
2119        }
2120        message.mergeConflictingProperty = mergeConflictingProperty;
2121        function independentScaleMeansIndependentGuide(channel) {
2122            return `Setting the scale to be independent for "${channel}" means we also have to set the guide (axis or legend) to be independent.`;
2123        }
2124        message.independentScaleMeansIndependentGuide = independentScaleMeansIndependentGuide;
2125        function domainSortDropped(sort) {
2126            return `Dropping sort property ${stringify(sort)} as unioned domains only support boolean or op 'count'.`;
2127        }
2128        message.domainSortDropped = domainSortDropped;
2129        message.UNABLE_TO_MERGE_DOMAINS = 'Unable to merge domains';
2130        message.MORE_THAN_ONE_SORT = 'Domains that should be unioned has conflicting sort properties. Sort will be set to true.';
2131        // AXIS
2132        message.INVALID_CHANNEL_FOR_AXIS = 'Invalid channel for axis.';
2133        // STACK
2134        function cannotStackRangedMark(channel) {
2135            return `Cannot stack "${channel}" if there is already "${channel}2"`;
2136        }
2137        message.cannotStackRangedMark = cannotStackRangedMark;
2138        function cannotStackNonLinearScale(scaleType) {
2139            return `Cannot stack non-linear scale (${scaleType})`;
2140        }
2141        message.cannotStackNonLinearScale = cannotStackNonLinearScale;
2142        function stackNonSummativeAggregate(aggregate) {
2143            return `Stacking is applied even though the aggregate function is non-summative ("${aggregate}")`;
2144        }
2145        message.stackNonSummativeAggregate = stackNonSummativeAggregate;
2146        // TIMEUNIT
2147        function invalidTimeUnit(unitName, value) {
2148            return `Invalid ${unitName}: ${stringify(value)}`;
2149        }
2150        message.invalidTimeUnit = invalidTimeUnit;
2151        function dayReplacedWithDate(fullTimeUnit) {
2152            return `Time unit "${fullTimeUnit}" is not supported. We are replacing it with ${fullTimeUnit.replace('day', 'date')}.`;
2153        }
2154        message.dayReplacedWithDate = dayReplacedWithDate;
2155        function droppedDay(d) {
2156            return `Dropping day from datetime ${stringify(d)} as day cannot be combined with other units.`;
2157        }
2158        message.droppedDay = droppedDay;
2159        function errorBarCenterAndExtentAreNotNeeded(center, extent) {
2160            return `${extent ? 'extent ' : ''}${extent && center ? 'and ' : ''}${center ? 'center ' : ''}${extent && center ? 'are ' : 'is '}not needed when data are aggregated.`;
2161        }
2162        message.errorBarCenterAndExtentAreNotNeeded = errorBarCenterAndExtentAreNotNeeded;
2163        function errorBarCenterIsUsedWithWrongExtent(center, extent, mark) {
2164            return `${center} is not usually used with ${extent} for ${mark}.`;
2165        }
2166        message.errorBarCenterIsUsedWithWrongExtent = errorBarCenterIsUsedWithWrongExtent;
2167        function errorBarContinuousAxisHasCustomizedAggregate(aggregate, compositeMark) {
2168            return `Continuous axis should not have customized aggregation function ${aggregate}; ${compositeMark} already agregates the axis.`;
2169        }
2170        message.errorBarContinuousAxisHasCustomizedAggregate = errorBarContinuousAxisHasCustomizedAggregate;
2171        function errorBarCenterIsNotNeeded(extent, mark) {
2172            return `Center is not needed to be specified in ${mark} when extent is ${extent}.`;
2173        }
2174        message.errorBarCenterIsNotNeeded = errorBarCenterIsNotNeeded;
2175        function errorBand1DNotSupport(property) {
2176            return `1D error band does not support ${property}`;
2177        }
2178        message.errorBand1DNotSupport = errorBand1DNotSupport;
2179        // CHANNEL
2180        function channelRequiredForBinned(channel) {
2181            return `Channel ${channel} is required for "binned" bin`;
2182        }
2183        message.channelRequiredForBinned = channelRequiredForBinned;
2184        function domainRequiredForThresholdScale(channel) {
2185            return `Domain for ${channel} is required for threshold scale`;
2186        }
2187        message.domainRequiredForThresholdScale = domainRequiredForThresholdScale;
2188    })(message || (message = {}));
2189
2190    // DateTime definition object
2191    /*
2192     * A designated year that starts on Sunday.
2193     */
2194    const SUNDAY_YEAR = 2006;
2195    function isDateTime(o) {
2196        return (!!o &&
2197            (!!o.year ||
2198                !!o.quarter ||
2199                !!o.month ||
2200                !!o.date ||
2201                !!o.day ||
2202                !!o.hours ||
2203                !!o.minutes ||
2204                !!o.seconds ||
2205                !!o.milliseconds));
2206    }
2207    const MONTHS = [
2208        'january',
2209        'february',
2210        'march',
2211        'april',
2212        'may',
2213        'june',
2214        'july',
2215        'august',
2216        'september',
2217        'october',
2218        'november',
2219        'december'
2220    ];
2221    const SHORT_MONTHS = MONTHS.map(m => m.substr(0, 3));
2222    const DAYS = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
2223    const SHORT_DAYS = DAYS.map(d => d.substr(0, 3));
2224    function normalizeQuarter(q) {
2225        if (isNumber(q)) {
2226            if (q > 4) {
2227                warn(message.invalidTimeUnit('quarter', q));
2228            }
2229            // We accept 1-based quarter, so need to readjust to 0-based quarter
2230            return (q - 1).toString();
2231        }
2232        else {
2233            // Invalid quarter
2234            throw new Error(message.invalidTimeUnit('quarter', q));
2235        }
2236    }
2237    function normalizeMonth(m) {
2238        if (isNumber(m)) {
2239            // We accept 1-based month, so need to readjust to 0-based month
2240            return (m - 1).toString();
2241        }
2242        else {
2243            const lowerM = m.toLowerCase();
2244            const monthIndex = MONTHS.indexOf(lowerM);
2245            if (monthIndex !== -1) {
2246                return monthIndex + ''; // 0 for january, ...
2247            }
2248            const shortM = lowerM.substr(0, 3);
2249            const shortMonthIndex = SHORT_MONTHS.indexOf(shortM);
2250            if (shortMonthIndex !== -1) {
2251                return shortMonthIndex + '';
2252            }
2253            // Invalid month
2254            throw new Error(message.invalidTimeUnit('month', m));
2255        }
2256    }
2257    function normalizeDay(d) {
2258        if (isNumber(d)) {
2259            // mod so that this can be both 0-based where 0 = sunday
2260            // and 1-based where 7=sunday
2261            return (d % 7) + '';
2262        }
2263        else {
2264            const lowerD = d.toLowerCase();
2265            const dayIndex = DAYS.indexOf(lowerD);
2266            if (dayIndex !== -1) {
2267                return dayIndex + ''; // 0 for january, ...
2268            }
2269            const shortD = lowerD.substr(0, 3);
2270            const shortDayIndex = SHORT_DAYS.indexOf(shortD);
2271            if (shortDayIndex !== -1) {
2272                return shortDayIndex + '';
2273            }
2274            // Invalid day
2275            throw new Error(message.invalidTimeUnit('day', d));
2276        }
2277    }
2278    /**
2279     * Return Vega Expression for a particular date time.
2280     * @param d
2281     * @param normalize whether to normalize quarter, month, day.
2282     */
2283    function dateTimeExpr(d, normalize = false) {
2284        const units = [];
2285        if (normalize && d.day !== undefined) {
2286            if (keys(d).length > 1) {
2287                warn(message.droppedDay(d));
2288                d = duplicate(d);
2289                delete d.day;
2290            }
2291        }
2292        if (d.year !== undefined) {
2293            units.push(d.year);
2294        }
2295        else if (d.day !== undefined) {
2296            // Set year to 2006 for working with day since January 1 2006 is a Sunday
2297            units.push(SUNDAY_YEAR);
2298        }
2299        else {
2300            units.push(0);
2301        }
2302        if (d.month !== undefined) {
2303            const month = normalize ? normalizeMonth(d.month) : d.month;
2304            units.push(month);
2305        }
2306        else if (d.quarter !== undefined) {
2307            const quarter = normalize ? normalizeQuarter(d.quarter) : d.quarter;
2308            units.push(quarter + '*3');
2309        }
2310        else {
2311            units.push(0); // months start at zero in JS
2312        }
2313        if (d.date !== undefined) {
2314            units.push(d.date);
2315        }
2316        else if (d.day !== undefined) {
2317            // HACK: Day only works as a standalone unit
2318            // This is only correct because we always set year to 2006 for day
2319            const day = normalize ? normalizeDay(d.day) : d.day;
2320            units.push(day + '+1');
2321        }
2322        else {
2323            units.push(1); // Date starts at 1 in JS
2324        }
2325        // Note: can't use TimeUnit enum here as importing it will create
2326        // circular dependency problem!
2327        for (const timeUnit of ['hours', 'minutes', 'seconds', 'milliseconds']) {
2328            if (d[timeUnit] !== undefined) {
2329                units.push(d[timeUnit]);
2330            }
2331            else {
2332                units.push(0);
2333            }
2334        }
2335        if (d.utc) {
2336            return `utc(${units.join(', ')})`;
2337        }
2338        else {
2339            return `datetime(${units.join(', ')})`;
2340        }
2341    }
2342
2343    var datetime = /*#__PURE__*/Object.freeze({
2344        isDateTime: isDateTime,
2345        MONTHS: MONTHS,
2346        SHORT_MONTHS: SHORT_MONTHS,
2347        DAYS: DAYS,
2348        SHORT_DAYS: SHORT_DAYS,
2349        dateTimeExpr: dateTimeExpr
2350    });
2351
2352    function isFacetFieldDef(channelDef) {
2353        return !!channelDef && !!channelDef['header'];
2354    }
2355    function isFacetSpec(spec) {
2356        return spec['facet'] !== undefined;
2357    }
2358
2359    var TimeUnit;
2360    (function (TimeUnit) {
2361        TimeUnit.YEAR = 'year';
2362        TimeUnit.MONTH = 'month';
2363        TimeUnit.DAY = 'day';
2364        TimeUnit.DATE = 'date';
2365        TimeUnit.HOURS = 'hours';
2366        TimeUnit.MINUTES = 'minutes';
2367        TimeUnit.SECONDS = 'seconds';
2368        TimeUnit.MILLISECONDS = 'milliseconds';
2369        TimeUnit.YEARMONTH = 'yearmonth';
2370        TimeUnit.YEARMONTHDATE = 'yearmonthdate';
2371        TimeUnit.YEARMONTHDATEHOURS = 'yearmonthdatehours';
2372        TimeUnit.YEARMONTHDATEHOURSMINUTES = 'yearmonthdatehoursminutes';
2373        TimeUnit.YEARMONTHDATEHOURSMINUTESSECONDS = 'yearmonthdatehoursminutesseconds';
2374        // MONTHDATE and MONTHDATEHOURS always include 29 February since we use year 0th (which is a leap year);
2375        TimeUnit.MONTHDATE = 'monthdate';
2376        TimeUnit.MONTHDATEHOURS = 'monthdatehours';
2377        TimeUnit.HOURSMINUTES = 'hoursminutes';
2378        TimeUnit.HOURSMINUTESSECONDS = 'hoursminutesseconds';
2379        TimeUnit.MINUTESSECONDS = 'minutesseconds';
2380        TimeUnit.SECONDSMILLISECONDS = 'secondsmilliseconds';
2381        TimeUnit.QUARTER = 'quarter';
2382        TimeUnit.YEARQUARTER = 'yearquarter';
2383        TimeUnit.QUARTERMONTH = 'quartermonth';
2384        TimeUnit.YEARQUARTERMONTH = 'yearquartermonth';
2385        TimeUnit.UTCYEAR = 'utcyear';
2386        TimeUnit.UTCMONTH = 'utcmonth';
2387        TimeUnit.UTCDAY = 'utcday';
2388        TimeUnit.UTCDATE = 'utcdate';
2389        TimeUnit.UTCHOURS = 'utchours';
2390        TimeUnit.UTCMINUTES = 'utcminutes';
2391        TimeUnit.UTCSECONDS = 'utcseconds';
2392        TimeUnit.UTCMILLISECONDS = 'utcmilliseconds';
2393        TimeUnit.UTCYEARMONTH = 'utcyearmonth';
2394        TimeUnit.UTCYEARMONTHDATE = 'utcyearmonthdate';
2395        TimeUnit.UTCYEARMONTHDATEHOURS = 'utcyearmonthdatehours';
2396        TimeUnit.UTCYEARMONTHDATEHOURSMINUTES = 'utcyearmonthdatehoursminutes';
2397        TimeUnit.UTCYEARMONTHDATEHOURSMINUTESSECONDS = 'utcyearmonthdatehoursminutesseconds';
2398        // UTCMONTHDATE and UTCMONTHDATEHOURS always include 29 February since we use year 0th (which is a leap year);
2399        TimeUnit.UTCMONTHDATE = 'utcmonthdate';
2400        TimeUnit.UTCMONTHDATEHOURS = 'utcmonthdatehours';
2401        TimeUnit.UTCHOURSMINUTES = 'utchoursminutes';
2402        TimeUnit.UTCHOURSMINUTESSECONDS = 'utchoursminutesseconds';
2403        TimeUnit.UTCMINUTESSECONDS = 'utcminutesseconds';
2404        TimeUnit.UTCSECONDSMILLISECONDS = 'utcsecondsmilliseconds';
2405        TimeUnit.UTCQUARTER = 'utcquarter';
2406        TimeUnit.UTCYEARQUARTER = 'utcyearquarter';
2407        TimeUnit.UTCQUARTERMONTH = 'utcquartermonth';
2408        TimeUnit.UTCYEARQUARTERMONTH = 'utcyearquartermonth';
2409    })(TimeUnit || (TimeUnit = {}));
2410    /** Time Unit that only corresponds to only one part of Date objects. */
2411    const LOCAL_SINGLE_TIMEUNIT_INDEX = {
2412        year: 1,
2413        quarter: 1,
2414        month: 1,
2415        day: 1,
2416        date: 1,
2417        hours: 1,
2418        minutes: 1,
2419        seconds: 1,
2420        milliseconds: 1
2421    };
2422    const TIMEUNIT_PARTS = flagKeys(LOCAL_SINGLE_TIMEUNIT_INDEX);
2423    function isLocalSingleTimeUnit(timeUnit) {
2424        return !!LOCAL_SINGLE_TIMEUNIT_INDEX[timeUnit];
2425    }
2426    const UTC_SINGLE_TIMEUNIT_INDEX = {
2427        utcyear: 1,
2428        utcquarter: 1,
2429        utcmonth: 1,
2430        utcday: 1,
2431        utcdate: 1,
2432        utchours: 1,
2433        utcminutes: 1,
2434        utcseconds: 1,
2435        utcmilliseconds: 1
2436    };
2437    function isUtcSingleTimeUnit(timeUnit) {
2438        return !!UTC_SINGLE_TIMEUNIT_INDEX[timeUnit];
2439    }
2440    const LOCAL_MULTI_TIMEUNIT_INDEX = {
2441        yearquarter: 1,
2442        yearquartermonth: 1,
2443        yearmonth: 1,
2444        yearmonthdate: 1,
2445        yearmonthdatehours: 1,
2446        yearmonthdatehoursminutes: 1,
2447        yearmonthdatehoursminutesseconds: 1,
2448        quartermonth: 1,
2449        monthdate: 1,
2450        monthdatehours: 1,
2451        hoursminutes: 1,
2452        hoursminutesseconds: 1,
2453        minutesseconds: 1,
2454        secondsmilliseconds: 1
2455    };
2456    const UTC_MULTI_TIMEUNIT_INDEX = {
2457        utcyearquarter: 1,
2458        utcyearquartermonth: 1,
2459        utcyearmonth: 1,
2460        utcyearmonthdate: 1,
2461        utcyearmonthdatehours: 1,
2462        utcyearmonthdatehoursminutes: 1,
2463        utcyearmonthdatehoursminutesseconds: 1,
2464        utcquartermonth: 1,
2465        utcmonthdate: 1,
2466        utcmonthdatehours: 1,
2467        utchoursminutes: 1,
2468        utchoursminutesseconds: 1,
2469        utcminutesseconds: 1,
2470        utcsecondsmilliseconds: 1
2471    };
2472    const UTC_TIMEUNIT_INDEX = Object.assign({}, UTC_SINGLE_TIMEUNIT_INDEX, UTC_MULTI_TIMEUNIT_INDEX);
2473    function isUTCTimeUnit(t) {
2474        return !!UTC_TIMEUNIT_INDEX[t];
2475    }
2476    function getLocalTimeUnit(t) {
2477        return t.substr(3);
2478    }
2479    const TIMEUNIT_INDEX = Object.assign({}, LOCAL_SINGLE_TIMEUNIT_INDEX, UTC_SINGLE_TIMEUNIT_INDEX, LOCAL_MULTI_TIMEUNIT_INDEX, UTC_MULTI_TIMEUNIT_INDEX);
2480    const TIMEUNITS = flagKeys(TIMEUNIT_INDEX);
2481    function isTimeUnit(t) {
2482        return !!TIMEUNIT_INDEX[t];
2483    }
2484    const SET_DATE_METHOD = {
2485        year: 'setFullYear',
2486        month: 'setMonth',
2487        date: 'setDate',
2488        hours: 'setHours',
2489        minutes: 'setMinutes',
2490        seconds: 'setSeconds',
2491        milliseconds: 'setMilliseconds',
2492        // Day and quarter have their own special cases
2493        quarter: null,
2494        day: null
2495    };
2496    /**
2497     * Converts a date to only have the measurements relevant to the specified unit
2498     * i.e. ('yearmonth', '2000-12-04 07:58:14') -> '2000-12-01 00:00:00'
2499     * Note: the base date is Jan 01 1900 00:00:00
2500     */
2501    function convert(unit, date) {
2502        const isUTC = isUTCTimeUnit(unit);
2503        const result = isUTC
2504            ? // start with uniform date
2505                new Date(Date.UTC(1972, 0, 1, 0, 0, 0, 0)) // 1972 is the first leap year after 1970, the start of unix time
2506            : new Date(1972, 0, 1, 0, 0, 0, 0);
2507        for (const timeUnitPart of TIMEUNIT_PARTS) {
2508            if (containsTimeUnit(unit, timeUnitPart)) {
2509                switch (timeUnitPart) {
2510                    case TimeUnit.DAY:
2511                        throw new Error("Cannot convert to TimeUnits containing 'day'");
2512                    case TimeUnit.QUARTER: {
2513                        const { getDateMethod, setDateMethod } = dateMethods('month', isUTC);
2514                        // indicate quarter by setting month to be the first of the quarter i.e. may (4) -> april (3)
2515                        result[setDateMethod](Math.floor(date[getDateMethod]() / 3) * 3);
2516                        break;
2517                    }
2518                    default:
2519                        const { getDateMethod, setDateMethod } = dateMethods(timeUnitPart, isUTC);
2520                        result[setDateMethod](date[getDateMethod]());
2521                }
2522            }
2523        }
2524        return result;
2525    }
2526    function dateMethods(singleUnit, isUtc) {
2527        const rawSetDateMethod = SET_DATE_METHOD[singleUnit];
2528        const setDateMethod = isUtc ? 'setUTC' + rawSetDateMethod.substr(3) : rawSetDateMethod;
2529        const getDateMethod = 'get' + (isUtc ? 'UTC' : '') + rawSetDateMethod.substr(3);
2530        return { setDateMethod, getDateMethod };
2531    }
2532    function getTimeUnitParts(timeUnit) {
2533        return TIMEUNIT_PARTS.reduce((parts, part) => {
2534            if (containsTimeUnit(timeUnit, part)) {
2535                return parts.concat(part);
2536            }
2537            return parts;
2538        }, []);
2539    }
2540    /** Returns true if fullTimeUnit contains the timeUnit, false otherwise. */
2541    function containsTimeUnit(fullTimeUnit, timeUnit) {
2542        const index = fullTimeUnit.indexOf(timeUnit);
2543        return (index > -1 && (timeUnit !== TimeUnit.SECONDS || index === 0 || fullTimeUnit.charAt(index - 1) !== 'i') // exclude milliseconds
2544        );
2545    }
2546    /**
2547     * Returns Vega expresssion for a given timeUnit and fieldRef
2548     */
2549    function fieldExpr(fullTimeUnit, field) {
2550        const fieldRef = accessPathWithDatum(field);
2551        const utc = isUTCTimeUnit(fullTimeUnit) ? 'utc' : '';
2552        function func(timeUnit) {
2553            if (timeUnit === TimeUnit.QUARTER) {
2554                // quarter starting at 0 (0,3,6,9).
2555                return `(${utc}quarter(${fieldRef})-1)`;
2556            }
2557            else {
2558                return `${utc}${timeUnit}(${fieldRef})`;
2559            }
2560        }
2561        const d = TIMEUNIT_PARTS.reduce((dateExpr, tu) => {
2562            if (containsTimeUnit(fullTimeUnit, tu)) {
2563                dateExpr[tu] = func(tu);
2564            }
2565            return dateExpr;
2566        }, {});
2567        return dateTimeExpr(d);
2568    }
2569    function getDateTimeComponents(timeUnit, shortTimeLabels) {
2570        if (!timeUnit) {
2571            return undefined;
2572        }
2573        const dateComponents = [];
2574        const hasYear = containsTimeUnit(timeUnit, TimeUnit.YEAR);
2575        if (containsTimeUnit(timeUnit, TimeUnit.MONTH)) {
2576            // By default use short month name
2577            dateComponents.push(shortTimeLabels !== false ? '%b' : '%B');
2578        }
2579        if (containsTimeUnit(timeUnit, TimeUnit.DAY)) {
2580            dateComponents.push(shortTimeLabels ? '%a' : '%A');
2581        }
2582        else if (containsTimeUnit(timeUnit, TimeUnit.DATE)) {
2583            dateComponents.push('%d' + (hasYear ? ',' : '')); // add comma if there is year
2584        }
2585        if (hasYear) {
2586            dateComponents.push(shortTimeLabels ? '%y' : '%Y');
2587        }
2588        const timeComponents = [];
2589        if (containsTimeUnit(timeUnit, TimeUnit.HOURS)) {
2590            timeComponents.push('%H');
2591        }
2592        if (containsTimeUnit(timeUnit, TimeUnit.MINUTES)) {
2593            timeComponents.push('%M');
2594        }
2595        if (containsTimeUnit(timeUnit, TimeUnit.SECONDS)) {
2596            timeComponents.push('%S');
2597        }
2598        if (containsTimeUnit(timeUnit, TimeUnit.MILLISECONDS)) {
2599            timeComponents.push('%L');
2600        }
2601        const dateTimeComponents = [];
2602        if (dateComponents.length > 0) {
2603            dateTimeComponents.push(dateComponents.join(' '));
2604        }
2605        if (timeComponents.length > 0) {
2606            dateTimeComponents.push(timeComponents.join(':'));
2607        }
2608        return dateTimeComponents;
2609    }
2610    /**
2611     * returns the signal expression used for axis labels for a time unit
2612     */
2613    function formatExpression(timeUnit, field, shortTimeLabels, isUTCScale) {
2614        if (!timeUnit) {
2615            return undefined;
2616        }
2617        const dateTimeComponents = getDateTimeComponents(timeUnit, shortTimeLabels);
2618        let expression = '';
2619        if (containsTimeUnit(timeUnit, TimeUnit.QUARTER)) {
2620            // special expression for quarter as prefix
2621            expression = `'Q' + quarter(${field})`;
2622        }
2623        if (dateTimeComponents.length > 0) {
2624            if (expression) {
2625                // Add space between quarter and main time format
2626                expression += ` + ' ' + `;
2627            }
2628            // We only use utcFormat for utc scale
2629            // For utc time units, the data is already converted as a part of timeUnit transform.
2630            // Thus, utc time units should use timeFormat to avoid shifting the time twice.
2631            if (isUTCScale) {
2632                expression += `utcFormat(${field}, '${dateTimeComponents.join(' ')}')`;
2633            }
2634            else {
2635                expression += `timeFormat(${field}, '${dateTimeComponents.join(' ')}')`;
2636            }
2637        }
2638        // If expression is still an empty string, return undefined instead.
2639        return expression || undefined;
2640    }
2641    function normalizeTimeUnit(timeUnit) {
2642        if (timeUnit !== 'day' && timeUnit.indexOf('day') >= 0) {
2643            warn(message.dayReplacedWithDate(timeUnit));
2644            return timeUnit.replace('day', 'date');
2645        }
2646        return timeUnit;
2647    }
2648
2649    var timeunit = /*#__PURE__*/Object.freeze({
2650        get TimeUnit () { return TimeUnit; },
2651        TIMEUNIT_PARTS: TIMEUNIT_PARTS,
2652        isLocalSingleTimeUnit: isLocalSingleTimeUnit,
2653        isUtcSingleTimeUnit: isUtcSingleTimeUnit,
2654        isUTCTimeUnit: isUTCTimeUnit,
2655        getLocalTimeUnit: getLocalTimeUnit,
2656        TIMEUNITS: TIMEUNITS,
2657        isTimeUnit: isTimeUnit,
2658        convert: convert,
2659        getTimeUnitParts: getTimeUnitParts,
2660        containsTimeUnit: containsTimeUnit,
2661        fieldExpr: fieldExpr,
2662        getDateTimeComponents: getDateTimeComponents,
2663        formatExpression: formatExpression,
2664        normalizeTimeUnit: normalizeTimeUnit
2665    });
2666
2667    /** Constants and utilities for data type */
2668    /** Data type based on level of measurement */
2669    var Type;
2670    (function (Type) {
2671        Type.QUANTITATIVE = 'quantitative';
2672        Type.ORDINAL = 'ordinal';
2673        Type.TEMPORAL = 'temporal';
2674        Type.NOMINAL = 'nominal';
2675        Type.GEOJSON = 'geojson';
2676    })(Type || (Type = {}));
2677    const TYPE_INDEX = {
2678        quantitative: 1,
2679        ordinal: 1,
2680        temporal: 1,
2681        nominal: 1,
2682        geojson: 1
2683    };
2684    function isType(t) {
2685        return !!TYPE_INDEX[t];
2686    }
2687    const QUANTITATIVE = Type.QUANTITATIVE;
2688    const ORDINAL = Type.ORDINAL;
2689    const TEMPORAL = Type.TEMPORAL;
2690    const NOMINAL = Type.NOMINAL;
2691    const GEOJSON = Type.GEOJSON;
2692    /**
2693     * Get full, lowercase type name for a given type.
2694     * @param  type
2695     * @return Full type name.
2696     */
2697    function getFullName(type) {
2698        if (type) {
2699            type = type.toLowerCase();
2700            switch (type) {
2701                case 'q':
2702                case QUANTITATIVE:
2703                    return 'quantitative';
2704                case 't':
2705                case TEMPORAL:
2706                    return 'temporal';
2707                case 'o':
2708                case ORDINAL:
2709                    return 'ordinal';
2710                case 'n':
2711                case NOMINAL:
2712                    return 'nominal';
2713                case GEOJSON:
2714                    return 'geojson';
2715            }
2716        }
2717        // If we get invalid input, return undefined type.
2718        return undefined;
2719    }
2720
2721    var type = /*#__PURE__*/Object.freeze({
2722        get Type () { return Type; },
2723        TYPE_INDEX: TYPE_INDEX,
2724        isType: isType,
2725        QUANTITATIVE: QUANTITATIVE,
2726        ORDINAL: ORDINAL,
2727        TEMPORAL: TEMPORAL,
2728        NOMINAL: NOMINAL,
2729        GEOJSON: GEOJSON,
2730        getFullName: getFullName
2731    });
2732
2733    function isConditionalSelection(c) {
2734        return c['selection'];
2735    }
2736    function isRepeatRef(field$$1) {
2737        return field$$1 && !isString(field$$1) && 'repeat' in field$$1;
2738    }
2739    function toFieldDefBase(fieldDef) {
2740        const { field: field$$1, timeUnit, bin, aggregate } = fieldDef;
2741        return Object.assign({}, (timeUnit ? { timeUnit } : {}), (bin ? { bin } : {}), (aggregate ? { aggregate } : {}), { field: field$$1 });
2742    }
2743    function isSortableFieldDef(fieldDef) {
2744        return isTypedFieldDef(fieldDef) && !!fieldDef['sort'];
2745    }
2746    function isConditionalDef(channelDef) {
2747        return !!channelDef && !!channelDef.condition;
2748    }
2749    /**
2750     * Return if a channelDef is a ConditionalValueDef with ConditionFieldDef
2751     */
2752    function hasConditionalFieldDef(channelDef) {
2753        return !!channelDef && !!channelDef.condition && !isArray(channelDef.condition) && isFieldDef(channelDef.condition);
2754    }
2755    function hasConditionalValueDef(channelDef) {
2756        return !!channelDef && !!channelDef.condition && (isArray(channelDef.condition) || isValueDef(channelDef.condition));
2757    }
2758    function isFieldDef(channelDef) {
2759        return !!channelDef && (!!channelDef['field'] || channelDef['aggregate'] === 'count');
2760    }
2761    function isTypedFieldDef(channelDef) {
2762        return !!channelDef && ((!!channelDef['field'] && !!channelDef['type']) || channelDef['aggregate'] === 'count');
2763    }
2764    function isStringFieldDef(channelDef) {
2765        return isFieldDef(channelDef) && isString(channelDef.field);
2766    }
2767    function isValueDef(channelDef) {
2768        return channelDef && 'value' in channelDef && channelDef['value'] !== undefined;
2769    }
2770    function isScaleFieldDef(channelDef) {
2771        return !!channelDef && (!!channelDef['scale'] || !!channelDef['sort']);
2772    }
2773    function isPositionFieldDef(channelDef) {
2774        return !!channelDef && (!!channelDef['axis'] || !!channelDef['stack'] || !!channelDef['impute']);
2775    }
2776    function isMarkPropFieldDef(channelDef) {
2777        return !!channelDef && !!channelDef['legend'];
2778    }
2779    function isTextFieldDef(channelDef) {
2780        return !!channelDef && !!channelDef['format'];
2781    }
2782    function isOpFieldDef(fieldDef) {
2783        return !!fieldDef['op'];
2784    }
2785    /**
2786     * Get a Vega field reference from a Vega-Lite field def.
2787     */
2788    function vgField(fieldDef, opt = {}) {
2789        let field$$1 = fieldDef.field;
2790        const prefix = opt.prefix;
2791        let suffix = opt.suffix;
2792        if (isCount(fieldDef)) {
2793            field$$1 = 'count_*';
2794        }
2795        else {
2796            let fn;
2797            if (!opt.nofn) {
2798                if (isOpFieldDef(fieldDef)) {
2799                    fn = fieldDef.op;
2800                }
2801                else if (isBinning(fieldDef.bin)) {
2802                    fn = binToString(fieldDef.bin);
2803                    suffix = (opt.binSuffix || '') + (opt.suffix || '');
2804                }
2805                else if (fieldDef.aggregate) {
2806                    fn = String(fieldDef.aggregate);
2807                }
2808                else if (fieldDef.timeUnit) {
2809                    fn = String(fieldDef.timeUnit);
2810                }
2811            }
2812            if (fn) {
2813                field$$1 = field$$1 ? `${fn}_${field$$1}` : fn;
2814            }
2815        }
2816        if (suffix) {
2817            field$$1 = `${field$$1}_${suffix}`;
2818        }
2819        if (prefix) {
2820            field$$1 = `${prefix}_${field$$1}`;
2821        }
2822        if (opt.forAs) {
2823            return field$$1;
2824        }
2825        else if (opt.expr) {
2826            // Expression to access flattened field. No need to escape dots.
2827            return flatAccessWithDatum(field$$1, opt.expr);
2828        }
2829        else {
2830            // We flattened all fields so paths should have become dot.
2831            return replacePathInField(field$$1);
2832        }
2833    }
2834    function isDiscrete(fieldDef) {
2835        switch (fieldDef.type) {
2836            case 'nominal':
2837            case 'ordinal':
2838            case 'geojson':
2839                return true;
2840            case 'quantitative':
2841                return !!fieldDef.bin;
2842            case 'temporal':
2843                return false;
2844        }
2845        throw new Error(message.invalidFieldType(fieldDef.type));
2846    }
2847    function isContinuous(fieldDef) {
2848        return !isDiscrete(fieldDef);
2849    }
2850    function isCount(fieldDef) {
2851        return fieldDef.aggregate === 'count';
2852    }
2853    function verbalTitleFormatter(fieldDef, config) {
2854        const { field: field$$1, bin, timeUnit, aggregate } = fieldDef;
2855        if (aggregate === 'count') {
2856            return config.countTitle;
2857        }
2858        else if (isBinning(bin)) {
2859            return `${field$$1} (binned)`;
2860        }
2861        else if (timeUnit) {
2862            const units = getTimeUnitParts(timeUnit).join('-');
2863            return `${field$$1} (${units})`;
2864        }
2865        else if (aggregate) {
2866            return `${titlecase(aggregate)} of ${field$$1}`;
2867        }
2868        return field$$1;
2869    }
2870    function functionalTitleFormatter(fieldDef, config) {
2871        const fn = fieldDef.aggregate || fieldDef.timeUnit || (isBinning(fieldDef.bin) && 'bin');
2872        if (fn) {
2873            return fn.toUpperCase() + '(' + fieldDef.field + ')';
2874        }
2875        else {
2876            return fieldDef.field;
2877        }
2878    }
2879    const defaultTitleFormatter = (fieldDef, config) => {
2880        switch (config.fieldTitle) {
2881            case 'plain':
2882                return fieldDef.field;
2883            case 'functional':
2884                return functionalTitleFormatter(fieldDef, config);
2885            default:
2886                return verbalTitleFormatter(fieldDef, config);
2887        }
2888    };
2889    let titleFormatter = defaultTitleFormatter;
2890    function setTitleFormatter(formatter) {
2891        titleFormatter = formatter;
2892    }
2893    function resetTitleFormatter() {
2894        setTitleFormatter(defaultTitleFormatter);
2895    }
2896    function title(fieldDef, config, { allowDisabling }) {
2897        const guide = getGuide(fieldDef) || {};
2898        const guideTitle = guide.title;
2899        if (allowDisabling) {
2900            return getFirstDefined(guideTitle, fieldDef.title, defaultTitle(fieldDef, config));
2901        }
2902        else {
2903            return guideTitle || fieldDef.title || defaultTitle(fieldDef, config);
2904        }
2905    }
2906    function getGuide(fieldDef) {
2907        if (isPositionFieldDef(fieldDef) && fieldDef.axis) {
2908            return fieldDef.axis;
2909        }
2910        else if (isMarkPropFieldDef(fieldDef) && fieldDef.legend) {
2911            return fieldDef.legend;
2912        }
2913        else if (isFacetFieldDef(fieldDef) && fieldDef.header) {
2914            return fieldDef.header;
2915        }
2916        return undefined;
2917    }
2918    function defaultTitle(fieldDef, config) {
2919        return titleFormatter(fieldDef, config);
2920    }
2921    function format(fieldDef) {
2922        if (isTextFieldDef(fieldDef) && fieldDef.format) {
2923            return fieldDef.format;
2924        }
2925        else {
2926            const guide = getGuide(fieldDef) || {};
2927            return guide.format;
2928        }
2929    }
2930    function defaultType(fieldDef, channel) {
2931        if (fieldDef.timeUnit) {
2932            return 'temporal';
2933        }
2934        if (isBinning(fieldDef.bin)) {
2935            return 'quantitative';
2936        }
2937        switch (rangeType(channel)) {
2938            case 'continuous':
2939                return 'quantitative';
2940            case 'discrete':
2941                return 'nominal';
2942            case 'flexible': // color
2943                return 'nominal';
2944            default:
2945                return 'quantitative';
2946        }
2947    }
2948    /**
2949     * Returns the fieldDef -- either from the outer channelDef or from the condition of channelDef.
2950     * @param channelDef
2951     */
2952    function getFieldDef(channelDef) {
2953        if (isFieldDef(channelDef)) {
2954            return channelDef;
2955        }
2956        else if (hasConditionalFieldDef(channelDef)) {
2957            return channelDef.condition;
2958        }
2959        return undefined;
2960    }
2961    function getTypedFieldDef(channelDef) {
2962        if (isFieldDef(channelDef)) {
2963            return channelDef;
2964        }
2965        else if (hasConditionalFieldDef(channelDef)) {
2966            return channelDef.condition;
2967        }
2968        return undefined;
2969    }
2970    /**
2971     * Convert type to full, lowercase type, or augment the fieldDef with a default type if missing.
2972     */
2973    function normalize(channelDef, channel) {
2974        if (isString(channelDef) || isNumber(channelDef) || isBoolean(channelDef)) {
2975            const primitiveType = isString(channelDef) ? 'string' : isNumber(channelDef) ? 'number' : 'boolean';
2976            warn(message.primitiveChannelDef(channel, primitiveType, channelDef));
2977            return { value: channelDef };
2978        }
2979        // If a fieldDef contains a field, we need type.
2980        if (isFieldDef(channelDef)) {
2981            return normalizeFieldDef(channelDef, channel);
2982        }
2983        else if (hasConditionalFieldDef(channelDef)) {
2984            return Object.assign({}, channelDef, { 
2985                // Need to cast as normalizeFieldDef normally return FieldDef, but here we know that it is definitely Condition<FieldDef>
2986                condition: normalizeFieldDef(channelDef.condition, channel) });
2987        }
2988        return channelDef;
2989    }
2990    function normalizeFieldDef(fieldDef, channel) {
2991        // Drop invalid aggregate
2992        if (fieldDef.aggregate && !isAggregateOp(fieldDef.aggregate)) {
2993            const fieldDefWithoutAggregate = __rest(fieldDef, ["aggregate"]);
2994            warn(message.invalidAggregate(fieldDef.aggregate));
2995            fieldDef = fieldDefWithoutAggregate;
2996        }
2997        // Normalize Time Unit
2998        if (fieldDef.timeUnit) {
2999            fieldDef = Object.assign({}, fieldDef, { timeUnit: normalizeTimeUnit(fieldDef.timeUnit) });
3000        }
3001        // Normalize bin
3002        if (isBinning(fieldDef.bin)) {
3003            fieldDef = Object.assign({}, fieldDef, { bin: normalizeBin(fieldDef.bin, channel) });
3004        }
3005        if (isBinned(fieldDef.bin) && !contains(POSITION_SCALE_CHANNELS, channel)) {
3006            warn(`Channel ${channel} should not be used with "binned" bin`);
3007        }
3008        // Normalize Type
3009        if (isTypedFieldDef(fieldDef)) {
3010            const fullType = getFullName(fieldDef.type);
3011            if (fieldDef.type !== fullType) {
3012                // convert short type to full type
3013                fieldDef = Object.assign({}, fieldDef, { type: fullType });
3014            }
3015            if (fieldDef.type !== 'quantitative') {
3016                if (isCountingAggregateOp(fieldDef.aggregate)) {
3017                    warn(message.invalidFieldTypeForCountAggregate(fieldDef.type, fieldDef.aggregate));
3018                    fieldDef = Object.assign({}, fieldDef, { type: 'quantitative' });
3019                }
3020            }
3021        }
3022        else if (!isSecondaryRangeChannel(channel)) {
3023            // If type is empty / invalid, then augment with default type
3024            const newType = defaultType(fieldDef, channel);
3025            warn(message.missingFieldType(channel, newType));
3026            fieldDef = Object.assign({}, fieldDef, { type: newType });
3027        }
3028        if (isTypedFieldDef(fieldDef)) {
3029            const { compatible, warning } = channelCompatibility(fieldDef, channel);
3030            if (!compatible) {
3031                warn(warning);
3032            }
3033        }
3034        return fieldDef;
3035    }
3036    function normalizeBin(bin, channel) {
3037        if (isBoolean(bin)) {
3038            return { maxbins: autoMaxBins(channel) };
3039        }
3040        else if (!bin.maxbins && !bin.step) {
3041            return Object.assign({}, bin, { maxbins: autoMaxBins(channel) });
3042        }
3043        else {
3044            return bin;
3045        }
3046    }
3047    const COMPATIBLE = { compatible: true };
3048    function channelCompatibility(fieldDef, channel) {
3049        const type = fieldDef.type;
3050        if (type === 'geojson' && channel !== 'shape') {
3051            return {
3052                compatible: false,
3053                warning: `Channel ${channel} should not be used with a geojson data.`
3054            };
3055        }
3056        switch (channel) {
3057            case 'row':
3058            case 'column':
3059                if (isContinuous(fieldDef)) {
3060                    return {
3061                        compatible: false,
3062                        warning: message.facetChannelShouldBeDiscrete(channel)
3063                    };
3064                }
3065                return COMPATIBLE;
3066            case 'x':
3067            case 'y':
3068            case 'color':
3069            case 'fill':
3070            case 'stroke':
3071            case 'text':
3072            case 'detail':
3073            case 'key':
3074            case 'tooltip':
3075            case 'href':
3076                return COMPATIBLE;
3077            case 'longitude':
3078            case 'longitude2':
3079            case 'latitude':
3080            case 'latitude2':
3081                if (type !== QUANTITATIVE) {
3082                    return {
3083                        compatible: false,
3084                        warning: `Channel ${channel} should be used with a quantitative field only, not ${fieldDef.type} field.`
3085                    };
3086                }
3087                return COMPATIBLE;
3088            case 'opacity':
3089            case 'fillOpacity':
3090            case 'strokeOpacity':
3091            case 'strokeWidth':
3092            case 'size':
3093            case 'x2':
3094            case 'y2':
3095                if (type === 'nominal' && !fieldDef['sort']) {
3096                    return {
3097                        compatible: false,
3098                        warning: `Channel ${channel} should not be used with an unsorted discrete field.`
3099                    };
3100                }
3101                return COMPATIBLE;
3102            case 'shape':
3103                if (!contains(['ordinal', 'nominal', 'geojson'], fieldDef.type)) {
3104                    return {
3105                        compatible: false,
3106                        warning: 'Shape channel should be used with only either discrete or geojson data.'
3107                    };
3108                }
3109                return COMPATIBLE;
3110            case 'order':
3111                if (fieldDef.type === 'nominal' && !('sort' in fieldDef)) {
3112                    return {
3113                        compatible: false,
3114                        warning: `Channel order is inappropriate for nominal field, which has no inherent order.`
3115                    };
3116                }
3117                return COMPATIBLE;
3118        }
3119        throw new Error('channelCompatability not implemented for channel ' + channel);
3120    }
3121    function isNumberFieldDef(fieldDef) {
3122        return fieldDef.type === 'quantitative' || isBinning(fieldDef.bin);
3123    }
3124    function isTimeFieldDef(fieldDef) {
3125        return fieldDef.type === 'temporal' || !!fieldDef.timeUnit;
3126    }
3127    /**
3128     * Getting a value associated with a fielddef.
3129     * Convert the value to Vega expression if applicable (for datetime object, or string if the field def is temporal or has t
3129imeUnit)
3130     */
3131    function valueExpr(v, { timeUnit, type, time, undefinedIfExprNotRequired }) {
3132        let expr;
3133        if (isDateTime(v)) {
3134            expr = dateTimeExpr(v, true);
3135        }
3136        else if (isString(v) || isNumber(v)) {
3137            if (timeUnit || type === 'temporal') {
3138                if (isLocalSingleTimeUnit(timeUnit)) {
3139                    expr = dateTimeExpr({ [timeUnit]: v }, true);
3140                }
3141                else if (isUtcSingleTimeUnit(timeUnit)) {
3142                    // FIXME is this really correct?
3143                    expr = valueExpr(v, { timeUnit: getLocalTimeUnit(timeUnit) });
3144                }
3145                else {
3146                    // just pass the string to date function (which will call JS Date.parse())
3147                    expr = `datetime(${JSON.stringify(v)})`;
3148                }
3149            }
3150        }
3151        if (expr) {
3152            return time ? `time(${expr})` : expr;
3153        }
3154        // number or boolean or normal string
3155        return undefinedIfExprNotRequired ? undefined : JSON.stringify(v);
3156    }
3157    /**
3158     * Standardize value array -- convert each value to Vega expression if applicable
3159     */
3160    function valueArray(fieldDef, values) {
3161        const { timeUnit, type } = fieldDef;
3162        return values.map(v => {
3163            const expr = valueExpr(v, { timeUnit, type, undefinedIfExprNotRequired: true });
3164            // return signal for the expression if we need an expression
3165            if (expr !== undefined) {
3166                return { signal: expr };
3167            }
3168            // otherwise just return the original value
3169            return v;
3170        });
3171    }
3172    /**
3173     * Checks whether a fieldDef for a particular channel requires a computed bin range.
3174     */
3175    function binRequiresRange(fieldDef, channel) {
3176        if (!isBinning(fieldDef.bin)) {
3177            console.warn('Only use this method with binned field defs');
3178            return false;
3179        }
3180        // We need the range only when the user explicitly forces a binned field to be use discrete scale. In this case, bin range is used in axis and legend labels.
3181        // We could check whether the axis or legend exists (not disabled) but that seems overkill.
3182        return isScaleChannel(channel) && contains(['ordinal', 'nominal'], fieldDef.type);
3183    }
3184
3185    var fielddef = /*#__PURE__*/Object.freeze({
3186        isConditionalSelection: isConditionalSelection,
3187        isRepeatRef: isRepeatRef,
3188        toFieldDefBase: toFieldDefBase,
3189        isSortableFieldDef: isSortableFieldDef,
3190        isConditionalDef: isConditionalDef,
3191        hasConditionalFieldDef: hasConditionalFieldDef,
3192        hasConditionalValueDef: hasConditionalValueDef,
3193        isFieldDef: isFieldDef,
3194        isTypedFieldDef: isTypedFieldDef,
3195        isStringFieldDef: isStringFieldDef,
3196        isValueDef: isValueDef,
3197        isScaleFieldDef: isScaleFieldDef,
3198        isPositionFieldDef: isPositionFieldDef,
3199        isMarkPropFieldDef: isMarkPropFieldDef,
3200        isTextFieldDef: isTextFieldDef,
3201        vgField: vgField,
3202        isDiscrete: isDiscrete,
3203        isContinuous: isContinuous,
3204        isCount: isCount,
3205        verbalTitleFormatter: verbalTitleFormatter,
3206        functionalTitleFormatter: functionalTitleFormatter,
3207        defaultTitleFormatter: defaultTitleFormatter,
3208        setTitleFormatter: setTitleFormatter,
3209        resetTitleFormatter: resetTitleFormatter,
3210        title: title,
3211        getGuide: getGuide,
3212        defaultTitle: defaultTitle,
3213        format: format,
3214        defaultType: defaultType,
3215        getFieldDef: getFieldDef,
3216        getTypedFieldDef: getTypedFieldDef,
3217        normalize: normalize,
3218        normalizeFieldDef: normalizeFieldDef,
3219        normalizeBin: normalizeBin,
3220        channelCompatibility: channelCompatibility,
3221        isNumberFieldDef: isNumberFieldDef,
3222        isTimeFieldDef: isTimeFieldDef,
3223        valueExpr: valueExpr,
3224        valueArray: valueArray,
3225        binRequiresRange: binRequiresRange
3226    });
3227
3228    function channelHasField(encoding, channel) {
3229        const channelDef = encoding && encoding[channel];
3230        if (channelDef) {
3231            if (isArray(channelDef)) {
3232                return some(channelDef, fieldDef => !!fieldDef.field);
3233            }
3234            else {
3235                return isFieldDef(channelDef) || hasConditionalFieldDef(channelDef);
3236            }
3237        }
3238        return false;
3239    }
3240    function isAggregate(encoding) {
3241        return some(CHANNELS, channel => {
3242            if (channelHasField(encoding, channel)) {
3243                const channelDef = encoding[channel];
3244                if (isArray(channelDef)) {
3245                    return some(channelDef, fieldDef => !!fieldDef.aggregate);
3246                }
3247                else {
3248                    const fieldDef = getFieldDef(channelDef);
3249                    return fieldDef && !!fieldDef.aggregate;
3250                }
3251            }
3252            return false;
3253        });
3254    }
3255    function extractTransformsFromEncoding(oldEncoding, config) {
3256        const groupby = [];
3257        const bins = [];
3258        const timeUnits = [];
3259        const aggregate = [];
3260        const encoding = {};
3261        forEach(oldEncoding, (channelDef, channel) => {
3262            // Extract potential embedded transformations along with remaining properties
3263            if (isFieldDef(channelDef)) {
3264                const { field: field$$1, aggregate: aggOp, timeUnit, bin } = channelDef, remaining = __rest(channelDef, ["field", "aggregate", "timeUnit", "bin"]);
3265                if (aggOp || timeUnit || bin) {
3266                    const guide = getGuide(channelDef);
3267                    const isTitleDefined = guide && guide.title;
3268                    const newField = vgField(channelDef, { forAs: true });
3269                    const newChannelDef = Object.assign({}, (isTitleDefined ? [] : { title: title(channelDef, config, { allowDisabling: true }) }), remaining, { 
3270                        // Always overwrite field
3271                        field: newField });
3272                    const isPositionChannel = channel === Channel.X || channel === Channel.Y;
3273                    if (aggOp && isAggregateOp(aggOp)) {
3274                        const aggregateEntry = {
3275                            op: aggOp,
3276                            as: newField
3277                        };
3278                        if (field$$1) {
3279                            aggregateEntry.field = field$$1;
3280                        }
3281                        aggregate.push(aggregateEntry);
3282                    }
3283                    else if (isTypedFieldDef(channelDef) && isBinning(bin)) {
3284                        bins.push({ bin, field: field$$1, as: newField });
3285                        // Add additional groupbys for range and end of bins
3286                        groupby.push(vgField(channelDef, { binSuffix: 'end' }));
3287                        if (binRequiresRange(channelDef, channel)) {
3288                            groupby.push(vgField(channelDef, { binSuffix: 'range' }));
3289                        }
3290                        // Create accompanying 'x2' or 'y2' field if channel is 'x' or 'y' respectively
3291                        if (isPositionChannel) {
3292                            const secondaryChannel = {
3293                                field: newField + '_end',
3294                                type: Type.QUANTITATIVE
3295                            };
3296                            encoding[channel + '2'] = secondaryChannel;
3297                        }
3298                        newChannelDef['bin'] = 'binned';
3299                        if (!isSecondaryRangeChannel(channel)) {
3300                            newChannelDef['type'] = Type.QUANTITATIVE;
3301                        }
3302                    }
3303                    else if (timeUnit) {
3304                        timeUnits.push({ timeUnit, field: field$$1, as: newField });
3305                        // Add formatting to appropriate property based on the type of channel we're processing
3306                        const format$$1 = getDateTimeComponents(timeUnit, config.axis.shortTimeLabels).join(' ');
3307                        if (isNonPositionScaleChannel(channel)) {
3308                            newChannelDef['legend'] = Object.assign({ format: format$$1 }, newChannelDef['legend']);
3309                        }
3310                        else if (isPositionChannel) {
3311                            newChannelDef['axis'] = Object.assign({ format: format$$1 }, newChannelDef['axis']);
3312                        }
3313                        else if (channel === 'text' || channel === 'tooltip') {
3314                            newChannelDef['format'] = newChannelDef['format'] || format$$1;
3315                        }
3316                    }
3317                    if (!aggOp) {
3318                        groupby.push(newField);
3319                    }
3320                    // now the field should refer to post-transformed field instead
3321                    encoding[channel] = newChannelDef;
3322                }
3323                else {
3324                    groupby.push(field$$1);
3325                    encoding[channel] = oldEncoding[channel];
3326                }
3327            }
3328            else {
3329                // For value def, just copy
3330                encoding[channel] = oldEncoding[channel];
3331            }
3332        });
3333        return {
3334            bins,
3335            timeUnits,
3336            aggregate,
3337            groupby,
3338            encoding
3339        };
3340    }
3341    function markChannelCompatible(encoding, channel, mark) {
3342        const markSupported = supportMark(channel, mark);
3343        if (!markSupported) {
3344            return false;
3345        }
3346        else if (markSupported === 'binned') {
3347            const primaryFieldDef = encoding[channel === 'x2' ? 'x' : 'y'];
3348            // circle, point, square and tick only support x2/y2 when their corresponding x/y fieldDef
3349            // has "binned" data and thus need x2/y2 to specify the bin-end field.
3350            if (isFieldDef(primaryFieldDef) && isFieldDef(encoding[channel]) && primaryFieldDef.bin === 'binned') {
3351                return true;
3352            }
3353            else {
3354                return false;
3355            }
3356        }
3357        return true;
3358    }
3359    function normalizeEncoding(encoding, mark) {
3360        return keys(encoding).reduce((normalizedEncoding, channel) => {
3361            if (!isChannel(channel)) {
3362                // Drop invalid channel
3363                warn(message.invalidEncodingChannel(channel));
3364                return normalizedEncoding;
3365            }
3366            if (!markChannelCompatible(encoding, channel, mark)) {
3367                // Drop unsupported channel
3368                warn(message.incompatibleChannel(channel, mark));
3369                return normalizedEncoding;
3370            }
3371            // Drop line's size if the field is aggregated.
3372            if (channel === 'size' && mark === 'line') {
3373                const fieldDef = getTypedFieldDef(encoding[channel]);
3374                if (fieldDef && fieldDef.aggregate) {
3375                    warn(message.LINE_WITH_VARYING_SIZE);
3376                    return normalizedEncoding;
3377                }
3378            }
3379            // Drop color if either fill or stroke is specified
3380            if (channel === 'color' && ('fill' in encoding || 'stroke' in encoding)) {
3381                warn(message.droppingColor('encoding', { fill: 'fill' in encoding, stroke: 'stroke' in encoding }));
3382                return normalizedEncoding;
3383            }
3384            const channelDef = encoding[channel];
3385            if (channel === 'detail' ||
3386                (channel === 'order' && !isArray(channelDef) && !isValueDef(channelDef)) ||
3387                (channel === 'tooltip' && isArray(channelDef))) {
3388                if (channelDef) {
3389                    // Array of fieldDefs for detail channel (or production rule)
3390                    normalizedEncoding[channel] = (isArray(channelDef) ? channelDef : [channelDef]).reduce((defs, fieldDef) => {
3391                        if (!isFieldDef(fieldDef)) {
3392                            warn(message.emptyFieldDef(fieldDef, channel));
3393                        }
3394                        else {
3395                            defs.push(normalizeFieldDef(fieldDef, channel));
3396                        }
3397                        return defs;
3398                    }, []);
3399                }
3400            }
3401            else {
3402                if (channel === 'tooltip' && channelDef === null) {
3403                    // Preserve null so we can use it to disable tooltip
3404                    normalizedEncoding[channel] = null;
3405                }
3406                else if (!isFieldDef(channelDef) && !isValueDef(channelDef) && !isConditionalDef(channelDef)) {
3407                    warn(message.emptyFieldDef(channelDef, channel));
3408                    return normalizedEncoding;
3409                }
3410                normalizedEncoding[channel] = normalize(channelDef, channel);
3411            }
3412            return normalizedEncoding;
3413        }, {});
3414    }
3415    function isRanged(encoding) {
3416        return encoding && ((!!encoding.x && !!encoding.x2) || (!!encoding.y && !!encoding.y2));
3417    }
3418    function fieldDefs(encoding) {
3419        const arr = [];
3420        for (const channel of keys(encoding)) {
3421            if (channelHasField(encoding, channel)) {
3422                const channelDef = encoding[channel];
3423                (isArray(channelDef) ? channelDef : [channelDef]).forEach(def => {
3424                    if (isFieldDef(def)) {
3425                        arr.push(def);
3426                    }
3427                    else if (hasConditionalFieldDef(def)) {
3428                        arr.push(def.condition);
3429                    }
3430                });
3431            }
3432        }
3433        return arr;
3434    }
3435    function forEach(mapping, f, thisArg) {
3436        if (!mapping) {
3437            return;
3438        }
3439        for (const channel of keys(mapping)) {
3440            if (isArray(mapping[channel])) {
3441                mapping[channel].forEach((channelDef) => {
3442                    f.call(thisArg, channelDef, channel);
3443                });
3444            }
3445            else {
3446                f.call(thisArg, mapping[channel], channel);
3447            }
3448        }
3449    }
3450    function reduce(mapping, f, init, thisArg) {
3451        if (!mapping) {
3452            return init;
3453        }
3454        return keys(mapping).reduce((r, channel) => {
3455            const map = mapping[channel];
3456            if (isArray(map)) {
3457                return map.reduce((r1, channelDef) => {
3458                    return f.call(thisArg, r1, channelDef, channel);
3459                }, r);
3460            }
3461            else {
3462                return f.call(thisArg, r, map, channel);
3463            }
3464        }, init);
3465    }
3466
3467    var encoding = /*#__PURE__*/Object.freeze({
3468        channelHasField: channelHasField,
3469        isAggregate: isAggregate,
3470        extractTransformsFromEncoding: extractTransformsFromEncoding,
3471        markChannelCompatible: markChannelCompatible,
3472        normalizeEncoding: normalizeEncoding,
3473        isRanged: isRanged,
3474        fieldDefs: fieldDefs,
3475        forEach: forEach,
3476        reduce: reduce
3477    });
3478
3479    function getCompositeMarkTooltip(tooltipSummary, continuousAxisChannelDef, encodingWithoutContinuousAxis, withFieldName = true) {
3480        const fiveSummaryTooltip = tooltipSummary.map(({ fieldPrefix, titlePrefix }) => ({
3481            field: fieldPrefix + continuousAxisChannelDef.field,
3482            type: continuousAxisChannelDef.type,
3483            title: titlePrefix + (withFieldName ? ' of ' + continuousAxisChannelDef.field : '')
3484        }));
3485        return {
3486            tooltip: [
3487                ...fiveSummaryTooltip,
3488                // need to cast because TextFieldDef support fewer types of bin
3489                ...fieldDefs(encodingWithoutContinuousAxis)
3490            ]
3491        };
3492    }
3493    function makeCompositeAggregatePartFactory(compositeMarkDef, continuousAxis, continuousAxisChannelDef, sharedEncoding, compositeMarkConfig) {
3494        const { scale, axis } = continuousAxisChannelDef;
3495        return ({ partName, mark, positionPrefix, endPositionPrefix = undefined, extraEncoding = {} }) => {
3496            const title$$1 = axis && axis.title !== undefined
3497                ? undefined
3498                : continuousAxisChannelDef.title !== undefined
3499                    ? continuousAxisChannelDef.title
3500                    : continuousAxisChannelDef.field;
3501            return partLayerMixins(compositeMarkDef, partName, compositeMarkConfig, {
3502                mark,
3503                encoding: Object.assign({ [continuousAxis]: Object.assign({ field: positionPrefix + '_' + continuousAxisChannelDef.field, type: continuousAxisChannelDef.type }, (title$$1 ? { title: title$$1 } : {}), (scale ? { scale } : {}), (axis ? { axis } : {})) }, (isString(endPositionPrefix)
3504                    ? {
3505                        [continuousAxis + '2']: {
3506                            field: endPositionPrefix + '_' + continuousAxisChannelDef.field,
3507                            type: continuousAxisChannelDef.type
3508                        }
3509                    }
3510                    : {}), sharedEncoding, extraEncoding)
3511            });
3512        };
3513    }
3514    function partLayerMixins(markDef, part, compositeMarkConfig, partBaseSpec) {
3515        const { clip, color, opacity } = markDef;
3516        const mark = markDef.type;
3517        if (markDef[part] || (markDef[part] === undefined && compositeMarkConfig[part])) {
3518            return [
3519                Object.assign({}, partBaseSpec, { mark: Object.assign({}, compositeMarkConfig[part], (clip ? { clip } : {}), (color ? { color } : {}), (opacity ? { opacity } : {}), (isMarkDef(partBaseSpec.mark) ? partBaseSpec.mark : { type: partBaseSpec.mark }), { style: `${mark}-${part}` }, (isBoolean(markDef[part]) ? {} : markDef[part])) })
3520            ];
3521        }
3522        return [];
3523    }
3524    function compositeMarkContinuousAxis(spec, orient, compositeMark) {
3525        const { encoding } = spec;
3526        const continuousAxis = orient === 'vertical' ? 'y' : 'x';
3527        const continuousAxisChannelDef = encoding[continuousAxis]; // Safe to cast because if x is not continuous fielddef, the orient would not be horizontal.
3528        const continuousAxisChannelDef2 = encoding[continuousAxis + '2'];
3529        const continuousAxisChannelDefError = encoding[continuousAxis + 'Error'];
3530        const continuousAxisChannelDefError2 = encoding[continuousAxis + 'Error2'];
3531        return {
3532            continuousAxisChannelDef: filterAggregateFromChannelDef(continuousAxisChannelDef, compositeMark),
3533            continuousAxisChannelDef2: filterAggregateFromChannelDef(continuousAxisChannelDef2, compositeMark),
3534            continuousAxisChannelDefError: filterAggregateFromChannelDef(continuousAxisChannelDefError, compositeMark),
3535            continuousAxisChannelDefError2: filterAggregateFromChannelDef(continuousAxisChannelDefError2, compositeMark),
3536            continuousAxis
3537        };
3538    }
3539    function filterAggregateFromChannelDef(continuousAxisChannelDef, compositeMark) {
3540        if (isFieldDef(continuousAxisChannelDef) && continuousAxisChannelDef && continuousAxisChannelDef.aggregate) {
3541            const { aggregate } = continuousAxisChannelDef, continuousAxisWithoutAggregate = __rest(continuousAxisChannelDef, ["aggregate"]);
3542            if (aggregate !== compositeMark) {
3543                warn(message.errorBarContinuousAxisHasCustomizedAggregate(aggregate, compositeMark));
3544            }
3545            return continuousAxisWithoutAggregate;
3546        }
3547        else {
3548            return continuousAxisChannelDef;
3549        }
3550    }
3551    function compositeMarkOrient(spec, compositeMark) {
3552        const { mark, encoding } = spec;
3553        if (isFieldDef(encoding.x) && isContinuous(encoding.x)) {
3554            // x is continuous
3555            if (isFieldDef(encoding.y) && isContinuous(encoding.y)) {
3556                // both x and y are continuous
3557                if (encoding.x.aggregate === undefined && encoding.y.aggregate === compositeMark) {
3558                    return 'vertical';
3559                }
3560                else if (encoding.y.aggregate === undefined && encoding.x.aggregate === compositeMark) {
3561                    return 'horizontal';
3562                }
3563                else if (encoding.x.aggregate === compositeMark && encoding.y.aggregate === compositeMark) {
3564                    throw new Error('Both x and y cannot have aggregate');
3565                }
3566                else {
3567                    if (isMarkDef(mark) && mark.orient) {
3568                        return mark.orient;
3569                    }
3570                    // default orientation = vertical
3571                    return 'vertical';
3572                }
3573            }
3574            // x is continuous but y is not
3575            return 'horizontal';
3576        }
3577        else if (isFieldDef(encoding.y) && isContinuous(encoding.y)) {
3578            // y is continuous but x is not
3579            return 'vertical';
3580        }
3581        else {
3582            // Neither x nor y is continuous.
3583            throw new Error('Need a valid continuous axis for ' + compositeMark + 's');
3584        }
3585    }
3586    function filterUnsupportedChannels(spec, supportedChannels, compositeMark) {
3587        return Object.assign({}, spec, { encoding: reduce(spec.encoding, (newEncoding, fieldDef, channel) => {
3588                if (supportedChannels.indexOf(channel) > -1) {
3589                    newEncoding[channel] = fieldDef;
3590                }
3591                else {
3592                    warn(message.incompatibleChannel(channel, compositeMark));
3593                }
3594                return newEncoding;
3595            }, {}) });
3596    }
3597
3598    const BOXPLOT = 'boxplot';
3599    const BOXPLOT_PART_INDEX = {
3600        box: 1,
3601        median: 1,
3602        outliers: 1,
3603        rule: 1,
3604        ticks: 1
3605    };
3606    const BOXPLOT_PARTS = keys(BOXPLOT_PART_INDEX);
3607    const boxPlotSupportedChannels = ['x', 'y', 'color', 'detail', 'opacity', 'size'];
3608    function normalizeBoxPlot(spec, config) {
3609        spec = filterUnsupportedChannels(spec, boxPlotSupportedChannels, BOXPLOT);
3610        // TODO: use selection
3611        const { mark, encoding: _encoding, selection, projection: _p } = spec, outerSpec = __rest(spec, ["mark", "encoding", "selection", "projection"]);
3612        const markDef = isMarkDef(mark) ? mark : { type: mark };
3613        // TODO(https://github.com/vega/vega-lite/issues/3702): add selection support
3614        if (selection) {
3615            warn(message.selectionNotSupported('boxplot'));
3616        }
3617        const extent = markDef.extent || config.boxplot.extent;
3618        const sizeValue = getFirstDefined(markDef.size, config.boxplot.size);
3619        const isMinMax = !isNumber(extent);
3620        const { transform, continuousAxisChannelDef, continuousAxis, groupby, encodingWithoutContinuousAxis, ticksOrient, tooltipEncoding } = boxParams(spec, extent, config);
3621        const { color, size } = encodingWithoutContinuousAxis, encodingWithoutSizeColorAndContinuousAxis = __rest(encodingWithoutContinuousAxis, ["color", "size"]);
3622        const makeBoxPlotPart = (sharedEncoding) => {
3623            return makeCompositeAggregatePartFactory(markDef, continuousAxis, continuousAxisChannelDef, sharedEncoding, config.boxplot);
3624        };
3625        const makeBoxPlotExtent = makeBoxPlotPart(encodingWithoutSizeColorAndContinuousAxis);
3626        const makeBoxPlotBox = makeBoxPlotPart(encodingWithoutContinuousAxis);
3627        const makeBoxPlotMidTick = makeBoxPlotPart(Object.assign({}, encodingWithoutSizeColorAndContinuousAxis, (size ? { size } : {})));
3628        const endTick = { type: 'tick', color: 'black', opacity: 1, orient: ticksOrient };
3629        const bar = Object.assign({ type: 'bar' }, (sizeValue ? { size: sizeValue } : {}));
3630        const midTick = Object.assign({ type: 'tick' }, (isObject(config.boxplot.median) && config.boxplot.median.color ? { color: config.boxplot.median.color } : {}), (sizeValue ? { size: sizeValue } : {}), { orient: ticksOrient });
3631        // TODO: support hiding certain mark parts
3632        const boxLayer = [
3633            ...makeBoxPlotExtent({
3634                partName: 'rule',
3635                mark: 'rule',
3636                positionPrefix: 'lower_whisker',
3637                endPositionPrefix: 'lower_box',
3638                extraEncoding: tooltipEncoding
3639            }),
3640            ...makeBoxPlotExtent({
3641                partName: 'rule',
3642                mark: 'rule',
3643                positionPrefix: 'upper_box',
3644                endPositionPrefix: 'upper_whisker',
3645                extraEncoding: tooltipEncoding
3646            }),
3647            ...makeBoxPlotExtent({
3648                partName: 'ticks',
3649                mark: endTick,
3650                positionPrefix: 'lower_whisker',
3651                extraEncoding: tooltipEncoding
3652            }),
3653            ...makeBoxPlotExtent({
3654                partName: 'ticks',
3655                mark: endTick,
3656                positionPrefix: 'upper_whisker',
3657                extraEncoding: tooltipEncoding
3658            }),
3659            ...makeBoxPlotBox({
3660                partName: 'box',
3661                mark: bar,
3662                positionPrefix: 'lower_box',
3663                endPositionPrefix: 'upper_box',
3664                extraEncoding: tooltipEncoding
3665            }),
3666            ...makeBoxPlotMidTick({
3667                partName: 'median',
3668                mark: midTick,
3669                positionPrefix: 'mid_box',
3670                extraEncoding: tooltipEncoding
3671            })
3672        ];
3673        let outliersLayerMixins = [];
3674        if (!isMinMax) {
3675            const lowerBoxExpr = 'datum.lower_box_' + continuousAxisChannelDef.field;
3676            const upperBoxExpr = 'datum.upper_box_' + continuousAxisChannelDef.field;
3677            const iqrExpr = `(${upperBoxExpr} - ${lowerBoxExpr})`;
3678            const lowerWhiskerExpr = `${lowerBoxExpr} - ${extent} * ${iqrExpr}`;
3679            const upperWhiskerExpr = `${upperBoxExpr} + ${extent} * ${iqrExpr}`;
3680            const fieldExpr = `datum.${continuousAxisChannelDef.field}`;
3681            outliersLayerMixins = partLayerMixins(markDef, 'outliers', config.boxplot, {
3682                transform: [
3683                    {
3684                        window: boxParamsQuartiles(continuousAxisChannelDef.field),
3685                        frame: [null, null],
3686                        groupby
3687                    },
3688                    {
3689                        filter: `(${fieldExpr} < ${lowerWhiskerExpr}) || (${fieldExpr} > ${upperWhiskerExpr})`
3690                    }
3691                ],
3692                mark: 'point',
3693                encoding: Object.assign({ [continuousAxis]: {
3694                        field: continuousAxisChannelDef.field,
3695                        type: continuousAxisChannelDef.type
3696                    } }, encodingWithoutSizeColorAndContinuousAxis)
3697            });
3698        }
3699        if (outliersLayerMixins.length > 0) {
3700            // tukey box plot with outliers included
3701            return Object.assign({}, outerSpec, { layer: [
3702                    {
3703                        // boxplot
3704                        transform,
3705                        layer: boxLayer
3706                    },
3707                    ...outliersLayerMixins
3708                ] });
3709        }
3710        return Object.assign({}, outerSpec, { transform: (outerSpec.transform || []).concat(transform), layer: boxLayer });
3711    }
3712    function boxParamsQuartiles(continousAxisField) {
3713        return [
3714            {
3715                op: 'q1',
3716                field: continousAxisField,
3717                as: 'lower_box_' + continousAxisField
3718            },
3719            {
3720                op: 'q3',
3721                field: continousAxisField,
3722                as: 'upper_box_' + continousAxisField
3723            }
3724        ];
3725    }
3726    function boxParams(spec, extent, config) {
3727        const orient = compositeMarkOrient(spec, BOXPLOT);
3728        const { continuousAxisChannelDef, continuousAxis } = compositeMarkContinuousAxis(spec, orient, BOXPLOT);
3729        const continuousFieldName = continuousAxisChannelDef.field;
3730        const isMinMax = !isNumber(extent);
3731        const boxplotSpecificAggregate = [
3732            ...boxParamsQuartiles(continuousFieldName),
3733            {
3734                op: 'median',
3735                field: continuousFieldName,
3736                as: 'mid_box_' + continuousFieldName
3737            },
3738            {
3739                op: 'min',
3740                field: continuousFieldName,
3741                as: (isMinMax ? 'lower_whisker_' : 'min_') + continuousFieldName
3742            },
3743            {
3744                op: 'max',
3745                field: continuousFieldName,
3746                as: (isMinMax ? 'upper_whisker_' : 'max_') + continuousFieldName
3747            }
3748        ];
3749        const postAggregateCalculates = isMinMax
3750            ? []
3751            : [
3752                {
3753                    calculate: `datum.upper_box_${continuousFieldName} - datum.lower_box_${continuousFieldName}`,
3754                    as: 'iqr_' + continuousFieldName
3755                },
3756                {
3757                    calculate: `min(datum.upper_box_${continuousFieldName} + datum.iqr_${continuousFieldName} * ${extent}, datum.max_${continuousFieldName})`,
3758                    as: 'upper_whisker_' + continuousFieldName
3759                },
3760                {
3761                    calculate: `max(datum.lower_box_${continuousFieldName} - datum.iqr_${continuousFieldName} * ${extent}, datum.min_${continuousFieldName})`,
3762                    as: 'lower_whisker_' + continuousFieldName
3763                }
3764            ];
3765        const _a = spec.encoding, _b = continuousAxis, oldContinuousAxisChannelDef = _a[_b], oldEncodingWithoutContinuousAxis = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]);
3766        const { bins, timeUnits, aggregate, groupby, encoding: encodingWithoutContinuousAxis } = extractTransformsFromEncoding(oldEncodingWithoutContinuousAxis, config);
3767        const ticksOrient = orient === 'vertical' ? 'horizontal' : 'vertical';
3768        const tooltipSummary = [
3769            { fieldPrefix: 'upper_whisker_', titlePrefix: isMinMax ? 'Max' : 'Upper Whisker' },
3770            { fieldPrefix: 'upper_box_', titlePrefix: 'Q3' },
3771            { fieldPrefix: 'mid_box_', titlePrefix: 'Median' },
3772            { fieldPrefix: 'lower_box_', titlePrefix: 'Q1' },
3773            { fieldPrefix: 'lower_whisker_', titlePrefix: isMinMax ? 'Min' : 'Lower Whisker' }
3774        ];
3775        const tooltipEncoding = getCompositeMarkTooltip(tooltipSummary, continuousAxisChannelDef, encodingWithoutContinuousAxis);
3776        return {
3777            transform: [
3778                ...bins,
3779                ...timeUnits,
3780                {
3781                    aggregate: [...aggregate, ...boxplotSpecificAggregate],
3782                    groupby
3783                },
3784                ...postAggregateCalculates
3785            ],
3786            groupby,
3787            continuousAxisChannelDef,
3788            continuousAxis,
3789            encodingWithoutContinuousAxis,
3790            ticksOrient,
3791            tooltipEncoding
3792        };
3793    }
3794
3795    const ERRORBAR = 'errorbar';
3796    const ERRORBAR_PART_INDEX = {
3797        ticks: 1,
3798        rule: 1
3799    };
3800    const ERRORBAR_PARTS = keys(ERRORBAR_PART_INDEX);
3801    function normalizeErrorBar(spec, config) {
3802        const { transform, continuousAxisChannelDef, continuousAxis, encodingWithoutContinuousAxis, ticksOrient, markDef, outerSpec, tooltipEncoding } = errorBarParams(spec, ERRORBAR, config);
3803        const makeErrorBarPart = makeCompositeAggregatePartFactory(markDef, continuousAxis, continuousAxisChannelDef, encodingWithoutContinuousAxis, config.errorbar);
3804        const tick = { type: 'tick', orient: ticksOrient };
3805        return Object.assign({}, outerSpec, { transform, layer: [
3806                ...makeErrorBarPart({
3807                    partName: 'ticks',
3808                    mark: tick,
3809                    positionPrefix: 'lower',
3810                    extraEncoding: tooltipEncoding
3811                }),
3812                ...makeErrorBarPart({
3813                    partName: 'ticks',
3814                    mark: tick,
3815                    positionPrefix: 'upper',
3816                    extraEncoding: tooltipEncoding
3817                }),
3818                ...makeErrorBarPart({
3819                    partName: 'rule',
3820                    mark: 'rule',
3821                    positionPrefix: 'lower',
3822                    endPositionPrefix: 'upper',
3823                    extraEncoding: tooltipEncoding
3824                })
3825            ] });
3826    }
3827    function errorBarOrientAndInputType(spec, compositeMark) {
3828        const { encoding } = spec;
3829        if (errorBarIsInputTypeRaw(encoding)) {
3830            return {
3831                orient: compositeMarkOrient(spec, compositeMark),
3832                inputType: 'raw'
3833            };
3834        }
3835        const isTypeAggregatedUpperLower = errorBarIsInputTypeAggregatedUpperLower(encoding);
3836        const isTypeAggregatedError = errorBarIsInputTypeAggregatedError(encoding);
3837        const x = encoding.x;
3838        const y = encoding.y;
3839        if (isTypeAggregatedUpperLower) {
3840            // type is aggregated-upper-lower
3841            if (isTypeAggregatedError) {
3842                throw new Error(compositeMark + ' cannot be both type aggregated-upper-lower and aggregated-error');
3843            }
3844            const x2 = encoding.x2;
3845            const y2 = encoding.y2;
3846            if (isFieldDef(x2) && isFieldDef(y2)) {
3847                // having both x, x2 and y, y2
3848                throw new Error(compositeMark + ' cannot have both x2 and y2');
3849            }
3850            else if (isFieldDef(x2)) {
3851                if (isFieldDef(x) && isContinuous(x)) {
3852                    // having x, x2 quantitative and field y, y2 are not specified
3853                    return { orient: 'horizontal', inputType: 'aggregated-upper-lower' };
3854                }
3855                else {
3856                    // having x, x2 that are not both quantitative
3857                    throw new Error('Both x and x2 have to be quantitative in ' + compositeMark);
3858                }
3859            }
3860            else if (isFieldDef(y2)) {
3861                // y2 is a FieldDef
3862                if (isFieldDef(y) && isContinuous(y)) {
3863                    // having y, y2 quantitative and field x, x2 are not specified
3864                    return { orient: 'vertical', inputType: 'aggregated-upper-lower' };
3865                }
3866                else {
3867                    // having y, y2 that are not both quantitative
3868                    throw new Error('Both y and y2 have to be quantitative in ' + compositeMark);
3869                }
3870            }
3871            throw new Error('No ranged axis');
3872        }
3873        else {
3874            // type is aggregated-error
3875            const xError = encoding.xError;
3876            const xError2 = encoding.xError2;
3877            const yError = encoding.yError;
3878            const yError2 = encoding.yError2;
3879            if (isFieldDef(xError2) && !isFieldDef(xError)) {
3880                // having xError2 without xError
3881                throw new Error(compositeMark + ' cannot have xError2 without xError');
3882            }
3883            if (isFieldDef(yError2) && !isFieldDef(yError)) {
3884                // having yError2 without yError
3885                throw new Error(compositeMark + ' cannot have yError2 without yError');
3886            }
3887            if (isFieldDef(xError) && isFieldDef(yError)) {
3888                // having both xError and yError
3889                throw new Error(compositeMark + ' cannot have both xError and yError with both are quantiative');
3890            }
3891            else if (isFieldDef(xError)) {
3892                if (isFieldDef(x) && isContinuous(x)) {
3893                    // having x and xError that are all quantitative
3894                    return { orient: 'horizontal', inputType: 'aggregated-error' };
3895                }
3896                else {
3897                    // having x, xError, and xError2 that are not all quantitative
3898                    throw new Error('All x, xError, and xError2 (if exist) have to be quantitative');
3899                }
3900            }
3901            else if (isFieldDef(yError)) {
3902                if (isFieldDef(y) && isContinuous(y)) {
3903                    // having y and yError that are all quantitative
3904                    return { orient: 'vertical', inputType: 'aggregated-error' };
3905                }
3906                else {
3907                    // having y, yError, and yError2 that are not all quantitative
3908                    throw new Error('All y, yError, and yError2 (if exist) have to be quantitative');
3909                }
3910            }
3911            throw new Error('No ranged axis');
3912        }
3913    }
3914    function errorBarIsInputTypeRaw(encoding) {
3915        return ((isFieldDef(encoding.x) || isFieldDef(encoding.y)) &&
3916            !isFieldDef(encoding.x2) &&
3917            !isFieldDef(encoding.y2) &&
3918            !isFieldDef(encoding.xError) &&
3919            !isFieldDef(encoding.xError2) &&
3920            !isFieldDef(encoding.yError) &&
3921            !isFieldDef(encoding.yError2));
3922    }
3923    function errorBarIsInputTypeAggregatedUpperLower(encoding) {
3924        return isFieldDef(encoding.x2) || isFieldDef(encoding.y2);
3925    }
3926    function errorBarIsInputTypeAggregatedError(encoding) {
3927        return (isFieldDef(encoding.xError) ||
3928            isFieldDef(encoding.xError2) ||
3929            isFieldDef(encoding.yError) ||
3930            isFieldDef(encoding.yError2));
3931    }
3932    const errorBarSupportedChannels = [
3933        'x',
3934        'y',
3935        'x2',
3936        'y2',
3937        'xError',
3938        'yError',
3939        'xError2',
3940        'yError2',
3941        'color',
3942        'detail',
3943        'opacity'
3944    ];
3945    function errorBarParams(spec, compositeMark, config) {
3946        spec = filterUnsupportedChannels(spec, errorBarSupportedChannels, compositeMark);
3947        // TODO: use selection
3948        const { mark, encoding, selection, projection: _p } = spec, outerSpec = __rest(spec, ["mark", "encoding", "selection", "projection"]);
3949        const markDef = isMarkDef(mark) ? mark : { type: mark };
3950        // TODO(https://github.com/vega/vega-lite/issues/3702): add selection support
3951        if (selection) {
3952            warn(message.selectio
3952nNotSupported(compositeMark));
3953        }
3954        const { orient, inputType } = errorBarOrientAndInputType(spec, compositeMark);
3955        const { continuousAxisChannelDef, continuousAxisChannelDef2, continuousAxisChannelDefError, continuousAxisChannelDefError2, continuousAxis } = compositeMarkContinuousAxis(spec, orient, compositeMark);
3956        const { errorBarSpecificAggregate, postAggregateCalculates, tooltipSummary, tooltipTitleWithFieldName } = errorBarAggregationAndCalculation(markDef, continuousAxisChannelDef, continuousAxisChannelDef2, continuousAxisChannelDefError, continuousAxisChannelDefError2, inputType, compositeMark, config);
3957        const _a = continuousAxis, oldContinuousAxisChannelDef = encoding[_a], _b = continuousAxis === 'x' ? 'x2' : 'y2', oldContinuousAxisChannelDef2 = encoding[_b], _c = continuousAxis === 'x' ? 'xError' : 'yError', oldContinuousAxisChannelDefError = encoding[_c], _d = continuousAxis === 'x' ? 'xError2' : 'yError2', oldContinuousAxisChannelDefError2 = encoding[_d], oldEncodingWithoutContinuousAxis = __rest(encoding, [typeof _a === "symbol" ? _a : _a + "", typeof _b === "symbol" ? _b : _b + "", typeof _c === "symbol" ? _c : _c + "", typeof _d === "symbol" ? _d : _d + ""]);
3958        const { bins, timeUnits, aggregate: oldAggregate, groupby: oldGroupBy, encoding: encodingWithoutContinuousAxis } = extractTransformsFromEncoding(oldEncodingWithoutContinuousAxis, config);
3959        const aggregate = [...oldAggregate, ...errorBarSpecificAggregate];
3960        const groupby = inputType !== 'raw' ? [] : oldGroupBy;
3961        const tooltipEncoding = getCompositeMarkTooltip(tooltipSummary, continuousAxisChannelDef, encodingWithoutContinuousAxis, tooltipTitleWithFieldName);
3962        return {
3963            transform: [
3964                ...(outerSpec.transform || []),
3965                ...bins,
3966                ...timeUnits,
3967                ...(!aggregate.length ? [] : [{ aggregate, groupby }]),
3968                ...postAggregateCalculates
3969            ],
3970            groupby,
3971            continuousAxisChannelDef,
3972            continuousAxis,
3973            encodingWithoutContinuousAxis,
3974            ticksOrient: orient === 'vertical' ? 'horizontal' : 'vertical',
3975            markDef,
3976            outerSpec,
3977            tooltipEncoding
3978        };
3979    }
3980    function errorBarAggregationAndCalculation(markDef, continuousAxisChannelDef, continuousAxisChannelDef2, continuousAxisChannelDefError, continuousAxisChannelDefError2, inputType, compositeMark, config) {
3981        let errorBarSpecificAggregate = [];
3982        let postAggregateCalculates = [];
3983        const continuousFieldName = continuousAxisChannelDef.field;
3984        let tooltipSummary;
3985        let tooltipTitleWithFieldName = false;
3986        if (inputType === 'raw') {
3987            const center = markDef.center
3988                ? markDef.center
3989                : markDef.extent
3990                    ? markDef.extent === 'iqr'
3991                        ? 'median'
3992                        : 'mean'
3993                    : config.errorbar.center;
3994            const extent = markDef.extent ? markDef.extent : center === 'mean' ? 'stderr' : 'iqr';
3995            if ((center === 'median') !== (extent === 'iqr')) {
3996                warn(message.errorBarCenterIsUsedWithWrongExtent(center, extent, compositeMark));
3997            }
3998            if (extent === 'stderr' || extent === 'stdev') {
3999                errorBarSpecificAggregate = [
4000                    { op: extent, field: continuousFieldName, as: 'extent_' + continuousFieldName },
4001                    { op: center, field: continuousFieldName, as: 'center_' + continuousFieldName }
4002                ];
4003                postAggregateCalculates = [
4004                    {
4005                        calculate: `datum.center_${continuousFieldName} + datum.extent_${continuousFieldName}`,
4006                        as: 'upper_' + continuousFieldName
4007                    },
4008                    {
4009                        calculate: `datum.center_${continuousFieldName} - datum.extent_${continuousFieldName}`,
4010                        as: 'lower_' + continuousFieldName
4011                    }
4012                ];
4013                tooltipSummary = [
4014                    { fieldPrefix: 'center_', titlePrefix: titlecase(center) },
4015                    { fieldPrefix: 'upper_', titlePrefix: getTitlePrefix(center, extent, '+') },
4016                    { fieldPrefix: 'lower_', titlePrefix: getTitlePrefix(center, extent, '-') }
4017                ];
4018                tooltipTitleWithFieldName = true;
4019            }
4020            else {
4021                if (markDef.center && markDef.extent) {
4022                    warn(message.errorBarCenterIsNotNeeded(markDef.extent, compositeMark));
4023                }
4024                let centerOp;
4025                let lowerExtentOp;
4026                let upperExtentOp;
4027                if (extent === 'ci') {
4028                    centerOp = 'mean';
4029                    lowerExtentOp = 'ci0';
4030                    upperExtentOp = 'ci1';
4031                }
4032                else {
4033                    centerOp = 'median';
4034                    lowerExtentOp = 'q1';
4035                    upperExtentOp = 'q3';
4036                }
4037                errorBarSpecificAggregate = [
4038                    { op: lowerExtentOp, field: continuousFieldName, as: 'lower_' + continuousFieldName },
4039                    { op: upperExtentOp, field: continuousFieldName, as: 'upper_' + continuousFieldName },
4040                    { op: centerOp, field: continuousFieldName, as: 'center_' + continuousFieldName }
4041                ];
4042                tooltipSummary = [
4043                    {
4044                        fieldPrefix: 'upper_',
4045                        titlePrefix: title({ field: continuousFieldName, aggregate: upperExtentOp, type: 'quantitative' }
4045, config, {
4046                            allowDisabling: false
4047                        })
4048                    },
4049                    {
4050                        fieldPrefix: 'lower_',
4051                        titlePrefix: title({ field: continuousFieldName, aggregate: lowerExtentOp, type: 'quantitative' }, config, {
4052                            allowDisabling: false
4053                        })
4054                    },
4055                    {
4056                        fieldPrefix: 'center_',
4057                        titlePrefix: title({ field: continuousFieldName, aggregate: centerOp, type: 'quantitative' }, config, {
4058                            allowDisabling: false
4059                        })
4060                    }
4061                ];
4062            }
4063        }
4064        else {
4065            if (markDef.center || markDef.extent) {
4066                warn(message.errorBarCenterAndExtentAreNotNeeded(markDef.center, markDef.extent));
4067            }
4068            if (inputType === 'aggregated-upper-lower') {
4069                tooltipSummary = [];
4070                postAggregateCalculates = [
4071                    { calculate: `datum.${continuousAxisChannelDef2.field}`, as: `upper_` + continuousFieldName },
4072                    { calculate: `datum.${continuousFieldName}`, as: `lower_` + continuousFieldName }
4073                ];
4074            }
4075            else if (inputType === 'aggregated-error') {
4076                tooltipSummary = [{ fieldPrefix: '', titlePrefix: continuousFieldName }];
4077                postAggregateCalculates = [
4078                    {
4079                        calculate: `datum.${continuousFieldName} + datum.${continuousAxisChannelDefError.field}`,
4080                        as: `upper_` + continuousFieldName
4081                    }
4082                ];
4083                if (continuousAxisChannelDefError2) {
4084                    postAggregateCalculates.push({
4085                        calculate: `datum.${continuousFieldName} + datum.${continuousAxisChannelDefError2.field}`,
4086                        as: `lower_` + continuousFieldName
4087                    });
4088                }
4089                else {
4090                    postAggregateCalculates.push({
4091                        calculate: `datum.${continuousFieldName} - datum.${continuousAxisChannelDefError.field}`,
4092                        as: `lower_` + continuousFieldName
4093                    });
4094                }
4095            }
4096            for (const postAggregateCalculate of postAggregateCalculates) {
4097                tooltipSummary.push({
4098                    fieldPrefix: postAggregateCalculate.as.substring(0, 6),
4099                    titlePrefix: postAggregateCalculate.calculate.replace(new RegExp('datum.', 'g'), '')
4100                });
4101            }
4102        }
4103        return { postAggregateCalculates, errorBarSpecificAggregate, tooltipSummary, tooltipTitleWithFieldName };
4104    }
4105    function getTitlePrefix(center, extent, operation) {
4106        return titlecase(center) + ' ' + operation + ' ' + extent;
4107    }
4108
4109    const ERRORBAND = 'errorband';
4110    const ERRORBAND_PART_INDEX = {
4111        band: 1,
4112        borders: 1
4113    };
4114    const ERRORBAND_PARTS = keys(ERRORBAND_PART_INDEX);
4115    function normalizeErrorBand(spec, config) {
4116        const { transform, continuousAxisChannelDef, continuousAxis, encodingWithoutContinuousAxis, markDef, outerSpec, tooltipEncoding } = errorBarParams(spec, ERRORBAND, config);
4117        const makeErrorBandPart = makeCompositeAggregatePartFactory(markDef, continuousAxis, continuousAxisChannelDef, encodingWithoutContinuousAxis, config.errorband);
4118        const is2D = spec.encoding.x !== undefined && spec.encoding.y !== undefined;
4119        let bandMark = { type: is2D ? 'area' : 'rect' };
4120        let bordersMark = { type: is2D ? 'line' : 'rule' };
4121        const interpolate = Object.assign({}, (markDef.interpolate ? { interpolate: markDef.interpolate } : {}), (markDef.tension && markDef.interpolate ? { interpolate: markDef.tension } : {}));
4122        if (is2D) {
4123            bandMark = Object.assign({}, bandMark, interpolate);
4124            bordersMark = Object.assign({}, bordersMark, interpolate);
4125        }
4126        else if (markDef.interpolate) {
4127            warn(message.errorBand1DNotSupport('interpolate'));
4128        }
4129        else if (markDef.tension) {
4130            warn(message.errorBand1DNotSupport('tension'));
4131        }
4132        return Object.assign({}, outerSpec, { transform, layer: [
4133                ...makeErrorBandPart({
4134                    partName: 'band',
4135                    mark: bandMark,
4136                    positionPrefix: 'lower',
4137                    endPositionPrefix: 'upper',
4138                    extraEncoding: tooltipEncoding
4139                }),
4140                ...makeErrorBandPart({
4141                    partName: 'borders',
4142                    mark: bordersMark,
4143                    positionPrefix: 'lower',
4144                    extraEncoding: tooltipEncoding
4145                }),
4146                ...makeErrorBandPart({
4147                    partName: 'borders',
4148                    mark: bordersMark,
4149                    positionPrefix: 'upper',
4150                    extraEncoding: tooltipEncoding
4151                })
4152            ] });
4153    }
4154
4155    /**
4156     * Registry index for all composite mark's normalizer
4157     */
4158    const compositeMarkRegistry = {};
4159    function add(mark, normalizer, parts) {
4160        compositeMarkRegistry[mark] = { normalizer, parts };
4161    }
4162    function remove(mark) {
4163        delete compositeMarkRegistry[mark];
4164    }
4165    function getAllCompositeMarks() {
4166        return keys(compositeMarkRegistry);
4167    }
4168    function getCompositeMarkParts(mark) {
4169        if (mark in compositeMarkRegistry) {
4170            return compositeMarkRegistry[mark].parts;
4171        }
4172        throw new Error(`Unregistered composite mark ${mark}`);
4173    }
4174    add(BOXPLOT, normalizeBoxPlot, BOXPLOT_PARTS);
4175    add(ERRORBAR, normalizeErrorBar, ERRORBAR_PARTS);
4176    add(ERRORBAND, normalizeErrorBand, ERRORBAND_PARTS);
4177    /**
4178     * Transform a unit spec with composite mark into a normal layer spec.
4179     */
4180    function normalize$1(
4181    // This GenericUnitSpec has any as Encoding because unit specs with composite mark can have additional encoding channels.
4182    spec, config) {
4183        const mark = isMarkDef(spec.mark) ? spec.mark.type : spec.mark;
4184        if (mark in compositeMarkRegistry) {
4185            const { normalizer } = compositeMarkRegistry[mark];
4186            return normalizer(spec, config);
4187        }
4188        throw new Error(`Invalid mark type "${mark}"`);
4189    }
4190
4191    var index = /*#__PURE__*/Object.freeze({
4192        add: add,
4193        remove: remove,
4194        getAllCompositeMarks: getAllCompositeMarks,
4195        getCompositeMarkParts: getCompositeMarkParts,
4196        normalize: normalize$1
4197    });
4198
4199    const VL_ONLY_GUIDE_CONFIG = ['shortTimeLabels'];
4200    const VL_ONLY_LEGEND_CONFIG = [
4201        'gradientHorizontalMaxLength',
4202        'gradientHorizontalMinLength',
4203        'gradientVerticalMaxLength',
4204        'gradientVerticalMinLength'
4205    ];
4206
4207    const defaultLegendConfig = {
4208        gradientHorizontalMaxLength: 200,
4209        gradientHorizontalMinLength: 100,
4210        gradientVerticalMaxLength: 200,
4211        gradientVerticalMinLength: 64 // This is the Vega's minimum.
4212    };
4213    const COMMON_LEGEND_PROPERTY_INDEX = {
4214        clipHeight: 1,
4215        columnPadding: 1,
4216        columns: 1,
4217        cornerRadius: 1,
4218        direction: 1,
4219        fillColor: 1,
4220        format: 1,
4221        gradientLength: 1,
4222        gradientOpacity: 1,
4223        gradientStrokeColor: 1,
4224        gradientStrokeWidth: 1,
4225        gradientThickness: 1,
4226        gridAlign: 1,
4227        labelAlign: 1,
4228        labelBaseline: 1,
4229        labelColor: 1,
4230        labelFont: 1,
4231        labelFontSize: 1,
4232        labelFontWeight: 1,
4233        labelLimit: 1,
4234        labelOffset: 1,
4235        labelOpacity: 1,
4236        labelOverlap: 1,
4237        labelPadding: 1,
4238        offset: 1,
4239        orient: 1,
4240        padding: 1,
4241        rowPadding: 1,
4242        strokeColor: 1,
4243        strokeWidth: 1,
4244        symbolFillColor: 1,
4245        symbolOffset: 1,
4246        symbolOpacity: 1,
4247        symbolSize: 1,
4248        symbolStrokeColor: 1,
4249        symbolStrokeWidth: 1,
4250        symbolType: 1,
4251        tickCount: 1,
4252        title: 1,
4253        titleAlign: 1,
4254        titleBaseline: 1,
4255        titleColor: 1,
4256        titleFont: 1,
4257        titleFontSize: 1,
4258        titleFontWeight: 1,
4259        titleLimit: 1,
4260        titleOpacity: 1,
4261        titlePadding: 1,
4262        type: 1,
4263        values: 1,
4264        zindex: 1
4265    };
4266    const VG_LEGEND_PROPERTY_INDEX = Object.assign({}, COMMON_LEGEND_PROPERTY_INDEX, { 
4267        // channel scales
4268        opacity: 1, shape: 1, stroke: 1, fill: 1, size: 1, 
4269        // encode
4270        encode: 1 });
4271    const LEGEND_PROPERTIES = flagKeys(COMMON_LEGEND_PROPERTY_INDEX);
4272    const VG_LEGEND_PROPERTIES = flagKeys(VG_LEGEND_PROPERTY_INDEX);
4273
4274    var legend = /*#__PURE__*/Object.freeze({
4275        defaultLegendConfig: defaultLegendConfig,
4276        LEGEND_PROPERTIES: LEGEND_PROPERTIES,
4277        VG_LEGEND_PROPERTIES: VG_LEGEND_PROPERTIES
4278    });
4279
4280    var ScaleType;
4281    (function (ScaleType) {
4282        // Continuous - Quantitative
4283        ScaleType.LINEAR = 'linear';
4284        ScaleType.BIN_LINEAR = 'bin-linear';
4285        ScaleType.LOG = 'log';
4286        ScaleType.POW = 'pow';
4287        ScaleType.SQRT = 'sqrt';
4288        // Continuous - Time
4289        ScaleType.TIME = 'time';
4290        ScaleType.UTC = 'utc';
4291        // sequential
4292        ScaleType.SEQUENTIAL = 'sequential';
4293        // Quantile, Quantize, threshold
4294        ScaleType.QUANTILE = 'quantile';
4295        ScaleType.QUANTIZE = 'quantize';
4296        ScaleType.THRESHOLD = 'threshold';
4297        ScaleType.ORDINAL = 'ordinal';
4298        ScaleType.BIN_ORDINAL = 'bin-ordinal';
4299        ScaleType.POINT = 'point';
4300        ScaleType.BAND = 'band';
4301    })(ScaleType || (ScaleType = {}));
4302    /**
4303     * Index for scale categories -- only scale of the same categories can be merged together.
4304     * Current implementation is trying to be conservative and avoid merging scale type that might not work together
4305     */
4306    const SCALE_CATEGORY_INDEX = {
4307        linear: 'numeric',
4308        log: 'numeric',
4309        pow: 'numeric',
4310        sqrt: 'numeric',
4311        'bin-linear': 'bin-linear',
4312        time: 'time',
4313        utc: 'time',
4314        sequential: 'sequential',
4315        ordinal: 'ordinal',
4316        'bin-ordinal': 'bin-ordinal',
4317        point: 'ordinal-position',
4318        band: 'ordinal-position',
4319        quantile: 'discretizing',
4320        quantize: 'discretizing',
4321        threshold: 'discretizing'
4322    };
4323    const SCALE_TYPES = keys(SCALE_CATEGORY_INDEX);
4324    /**
4325     * Whether the two given scale types can be merged together.
4326     */
4327    function scaleCompatible(scaleType1, scaleType2) {
4328        const scaleCategory1 = SCALE_CATEGORY_INDEX[scaleType1];
4329        const scaleCategory2 = SCALE_CATEGORY_INDEX[scaleType2];
4330        return (scaleCategory1 === scaleCategory2 ||
4331            (scaleCategory1 === 'ordinal-position' && scaleCategory2 === 'time') ||
4332            (scaleCategory2 === 'ordinal-position' && scaleCategory1 === 'time'));
4333    }
4334    /**
4335     * Index for scale precedence -- high score = higher priority for merging.
4336     */
4337    const SCALE_PRECEDENCE_INDEX = {
4338        // numeric
4339        linear: 0,
4340        log: 1,
4341        pow: 1,
4342        sqrt: 1,
4343        // time
4344        time: 0,
4345        utc: 0,
4346        // ordinal-position -- these have higher precedence than continuous scales as they support more types of data
4347        point: 10,
4348        band: 11,
4349        // non grouped types
4350        'bin-linear': 0,
4351        sequential: 0,
4352        ordinal: 0,
4353        'bin-ordinal': 0,
4354        quantile: 0,
4355        quantize: 0,
4356        threshold: 0
4357    };
4358    /**
4359     * Return scale categories -- only scale of the same categories can be merged together.
4360     */
4361    function scaleTypePrecedence(scaleType) {
4362        return SCALE_PRECEDENCE_INDEX[scaleType];
4363    }
4364    const CONTINUOUS_TO_CONTINUOUS_SCALES = [
4365        'linear',
4366        'bin-linear',
4367        'log',
4368        'pow',
4369        'sqrt',
4370        'time',
4371        'utc'
4372    ];
4373    const CONTINUOUS_TO_CONTINUOUS_INDEX = toSet(CONTINUOUS_TO_CONTINUOUS_SCALES);
4374    const CONTINUOUS_TO_DISCRETE_SCALES = ['quantile', 'quantize', 'threshold'];
4375    const CONTINUOUS_TO_DISCRETE_INDEX = toSet(CONTINUOUS_TO_DISCRETE_SCALES);
4376    const CONTINUOUS_DOMAIN_SCALES = CONTINUOUS_TO_CONTINUOUS_SCALES.concat([
4377        'sequential',
4378        'quantile',
4379        'quantize',
4380        'threshold'
4381    ]);
4382    const CONTINUOUS_DOMAIN_INDEX = toSet(CONTINUOUS_DOMAIN_SCALES);
4383    const DISCRETE_DOMAIN_SCALES = ['ordinal', 'bin-ordinal', 'point', 'band'];
4384    const DISCRETE_DOMAIN_INDEX = toSet(DISCRETE_DOMAIN_SCALES);
4385    const BIN_SCALES_INDEX = toSet(['bin-linear', 'bin-ordinal']);
4386    const TIME_SCALE_TYPES = ['time', 'utc'];
4387    function hasDiscreteDomain(type) {
4388        return type in DISCRETE_DOMAIN_INDEX;
4389    }
4390    function isBinScale(type) {
4391        return type in BIN_SCALES_INDEX;
4392    }
4393    function hasContinuousDomain(type) {
4394        return type in CONTINUOUS_DOMAIN_INDEX;
4395    }
4396    function isContinuousToContinuous(type) {
4397        return type in CONTINUOUS_TO_CONTINUOUS_INDEX;
4398    }
4399    function isContinuousToDiscrete(type) {
4400        return type in CONTINUOUS_TO_DISCRETE_INDEX;
4401    }
4402    const defaultScaleConfig = {
4403        textXRangeStep: 90,
4404        rangeStep: 20,
4405        pointPadding: 0.5,
4406        barBandPaddingInner: 0.1,
4407        rectBandPaddingInner: 0,
4408        facetSpacing: 16,
4409        minBandSize: 2,
4410        minFontSize: 8,
4411        maxFontSize: 40,
4412        minOpacity: 0.3,
4413        maxOpacity: 0.8,
4414        // FIXME: revise if these *can* become ratios of rangeStep
4415        minSize: 9,
4416        minStrokeWidth: 1,
4417        maxStrokeWidth: 4,
4418        quantileCount: 4,
4419        quantizeCount: 4
4420    };
4421    function isExtendedScheme(scheme) {
4422        return scheme && !!scheme['name'];
4423    }
4424    function isSelectionDomain(domain) {
4425        return domain && domain['selection'];
4426    }
4427    const SCALE_PROPERTY_INDEX = {
4428        type: 1,
4429        domain: 1,
4430        range: 1,
4431        rangeStep: 1,
4432        scheme: 1,
4433        // Other properties
4434        reverse: 1,
4435        round: 1,
4436        // quantitative / time
4437        clamp: 1,
4438        nice: 1,
4439        // quantitative
4440        base: 1,
4441        exponent: 1,
4442        interpolate: 1,
4443        zero: 1,
4444        // band/point
4445        padding: 1,
4446        paddingInner: 1,
4447        paddingOuter: 1
4448    };
4449    const SCALE_PROPERTIES = flagKeys(SCALE_PROPERTY_INDEX);
4450    const NON_TYPE_DOMAIN_RANGE_VEGA_SCALE_PROPERTY_INDEX = __rest(SCALE_PROPERTY_INDEX, ["type", "domain", "range", "rangeStep", "scheme"]);
4451    const NON_TYPE_DOMAIN_RANGE_VEGA_SCALE_PROPERTIES = flagKeys(NON_TYPE_DOMAIN_RANGE_VEGA_SCALE_PROPERTY_INDEX);
4452    const SCALE_TYPE_INDEX = generateScaleTypeIndex();
4453    function scaleTypeSupportProperty(scaleType, propName) {
4454        switch (propName) {
4455            case 'type':
4456            case 'domain':
4457            case 'reverse':
4458            case 'range':
4459                return true;
4460            case 'scheme':
4461                return contains(['sequential', 'ordinal', 'bin-ordinal', 'quantile', 'quantize', 'threshold'], scaleType);
4462            case 'interpolate':
4463                return contains(['linear', 'bin-linear', 'pow', 'log', 'sqrt', 'utc', 'time'], scaleType);
4464            case 'round':
4465                return isContinuousToContinuous(scaleType) || scaleType === 'band' || scaleType === 'point';
4466            case 'padding':
4467                return isContinuousToContinuous(scaleType) || contains(['point', 'band'], scaleType);
4468            case 'paddingOuter':
4469            case 'rangeStep':
4470                return contains(['point', 'band'], scaleType);
4471            case 'paddingInner':
4472                return scaleType === 'band';
4473            case 'clamp':
4474                return isContinuousToContinuous(scaleType) || scaleType === 'sequential';
4475            case 'nice':
4476                return isContinuousToContinuous(scaleType) || scaleType === 'sequential' || scaleType === 'quantize';
4477            case 'exponent':
4478                return scaleType === 'pow';
4479            case 'base':
4480                return scaleType === 'log';
4481            case 'zero':
4482                return (hasContinuousDomain(scaleType) &&
4483                    !contains([
4484                        'log',
4485                        'time',
4486                        'utc',
4487                        'bin-linear',
4488                        'threshold',
4489                        'quantile' // quantile depends on distribution so zero does not matter
4490                    ], scaleType));
4491        }
4492        /* istanbul ignore next: should never reach here*/
4493        throw new Error(`Invalid scale property ${propName}.`);
4494    }
4495    /**
4496     * Returns undefined if the input channel supports the input scale property name
4497     */
4498    function channelScalePropertyIncompatability(channel, propName) {
4499        switch (propName) {
4500            case 'interpolate':
4501            case 'scheme':
4502                if (!isColorChannel(channel)) {
4503                    return message.cannotUseScalePropertyWithNonColor(channel);
4504                }
4505                return undefined;
4506            case 'type':
4507            case 'domain':
4508            case 'range':
4509            case 'base':
4510            case 'exponent':
4511            case 'nice':
4512            case 'padding':
4513            case 'paddingInner':
4514            case 'paddingOuter':
4515            case 'rangeStep':
4516            case 'reverse':
4517            case 'round':
4518            case 'clamp':
4519            case 'zero':
4520                return undefined; // GOOD!
4521        }
4522        /* istanbul ignore next: it should never reach here */
4523        throw new Error(`Invalid scale property "${propName}".`);
4524    }
4525    function scaleTypeSupportDataType(specifiedType, fieldDefType, bin) {
4526        if (contains([Type.ORDINAL, Type.NOMINAL], fieldDefType)) {
4527            return specifiedType === undefined || hasDiscreteDomain(specifiedType);
4528        }
4529        else if (fieldDefType === Type.TEMPORAL) {
4530            return contains([ScaleType.TIME, ScaleType.UTC, ScaleType.SEQUENTIAL, undefined], specifiedType);
4531        }
4532        else if (fieldDefType === Type.QUANTITATIVE) {
4533            if (bin) {
4534                return contains([ScaleType.BIN_LINEAR, ScaleType.BIN_ORDINAL, ScaleType.LINEAR], specifiedType);
4535            }
4536            return contains([
4537                ScaleType.LOG,
4538                ScaleType.POW,
4539                ScaleType.SQRT,
4540                ScaleType.QUANTILE,
4541                ScaleType.QUANTIZE,
4542                ScaleType.THRESHOLD,
4543                ScaleType.LINEAR,
4544                ScaleType.SEQUENTIAL,
4545                undefined
4546            ], specifiedType);
4547        }
4548        return true;
4549    }
4550    function channelSupportScaleType(channel, scaleType) {
4551        switch (channel) {
4552            case Channel.X:
4553            case Channel.Y:
4554                return isContinuousToContinuous(scaleType) || contains(['band', 'point'], scaleType);
4555            case Channel.SIZE: // TODO: size and opacity can support ordinal with more modification
4556            case Channel.STROKEWIDTH:
4557            case Channel.OPACITY:
4558            case Channel.FILLOPACITY:
4559            case Channel.STROKEOPACITY:
4560                // Although it generally doesn't make sense to use band with size and opacity,
4561                // it can also work since we use band: 0.5 to get midpoint.
4562                return (isContinuousToContinuous(scaleType) ||
4563                    isContinuousToDiscrete(scaleType) ||
4564                    contains(['band', 'point'], scaleType));
4565            case Channel.COLOR:
4566            case Channel.FILL:
4567            case Channel.STROKE:
4568                return scaleType !== 'band'; // band does not make sense with color
4569            case Channel.SHAPE:
4570                return scaleType === 'ordinal'; // shape = lookup only
4571        }
4572        /* istanbul ignore next: it should never reach here */
4573        return false;
4574    }
4575    function getSupportedScaleType(channel, fieldDefType, bin) {
4576        return SCALE_TYPE_INDEX[generateScaleTypeIndexKey(channel, fieldDefType, bin)];
4577    }
4578    // generates ScaleTypeIndex where keys are encoding channels and values are list of valid ScaleTypes
4579    function generateScaleTypeIndex() {
4580        const index = {};
4581        for (const channel of CHANNELS) {
4582            for (const fieldDefType of keys(TYPE_INDEX)) {
4583                for (const scaleType of SCALE_TYPES) {
4584                    for (const bin of [false, true]) {
4585                        const key$$1 = generateScaleTypeIndexKey(channel, fieldDefType, bin);
4586                        if (channelSupportScaleType(channel, scaleType) && scaleTypeSupportDataType(scaleType, fieldDefType, bin)) {
4587                            index[key$$1] = index[key$$1] || [];
4588                            index[key$$1].push(scaleType);
4589                        }
4590                    }
4591                }
4592            }
4593        }
4594        return index;
4595    }
4596    function generateScaleTypeIndexKey(channel, fieldDefType, bin) {
4597        const key$$1 = channel + '_' + fieldDefType;
4598        return bin ? key$$1 + '_bin' : key$$1;
4599    }
4600
4601    var scale = /*#__PURE__*/Object.freeze({
4602        get ScaleType () { return ScaleType; },
4603        SCALE_TYPES: SCALE_TYPES,
4604        scaleCompatible: scaleCompatible,
4605        scaleTypePrecedence: scaleTypePrecedence,
4606        CONTINUOUS_TO_CONTINUOUS_SCALES: CONTINUOUS_TO_CONTINUOUS_SCALES,
4607        CONTINUOUS_TO_DISCRETE_SCALES: CONTINUOUS_TO_DISCRETE_SCALES,
4608        CONTINUOUS_DOMAIN_SCALES: CONTINUOUS_DOMAIN_SCALES,
4609        DISCRETE_DOMAIN_SCALES: DISCRETE_DOMAIN_SCALES,
4610        TIME_SCALE_TYPES: TIME_SCALE_TYPES,
4611        hasDiscreteDomain: hasDiscreteDomain,
4612        isBinScale: isBinScale,
4613        hasContinuousDomain: hasContinuousDomain,
4614        isContinuousToContinuous: isContinuousToContinuous,
4615        isContinuousToDiscrete: isContinuousToDiscrete,
4616        defaultScaleConfig: defaultScaleConfig,
4617        isExtendedScheme: isExtendedScheme,
4618        isSelectionDomain: isSelectionDomain,
4619        SCALE_PROPERTIES: SCALE_PROPERTIES,
4620        NON_TYPE_DOMAIN_RANGE_VEGA_SCALE_PROPERTIES: NON_TYPE_DOMAIN_RANGE_VEGA_SCALE_PROPERTIES,
4621        SCALE_TYPE_INDEX: SCALE_TYPE_INDEX,
4622        scaleTypeSupportProperty: scaleTypeSupportProperty,
4623        channelScalePropertyIncompatability: channelScalePropertyIncompatability,
4624        scaleTypeSupportDataType: scaleTypeSupportDataType,
4625        channelSupportScaleType: channelSupportScaleType,
4626        getSupportedScaleType: getSupportedScaleType
4627    });
4628
4629    const SELECTION_ID = '_vgsid_';
4630    const defaultConfig = {
4631        single: {
4632            on: 'click',
4633            fields: [SELECTION_ID],
4634            resolve: 'global',
4635            empty: 'all'
4636        },
4637        multi: {
4638            on: 'click',
4639            fields: [SELECTION_ID],
4640            toggle: 'event.shiftKey',
4641            resolve: 'global',
4642            empty: 'all'
4643        },
4644        interval: {
4645            on: '[mousedown, window:mouseup] > window:mousemove!',
4646            encodings: ['x', 'y'],
4647            translate: '[mousedown, window:mouseup] > window:mousemove!',
4648            zoom: 'wheel!',
4649            mark: { fill: '#333', fillOpacity: 0.125, stroke: 'white' },
4650            resolve: 'global'
4651        }
4652    };
4653
4654    function extractTitleConfig(titleConfig) {
4655        const { 
4656        // These are non-mark title config that need to be hardcoded
4657        anchor, frame, offset, orient, 
4658        // color needs to be redirect to fill
4659        color } = titleConfig, 
4660        // The rest are mark config.
4661        titleMarkConfig = __rest(titleConfig, ["anchor", "frame", "offset", "orient", "color"]);
4662        const mark = Object.assign({}, titleMarkConfig, (color ? { fill: color } : {}));
4663        const nonMark = Object.assign({}, (anchor ? { anchor } : {}), (offset ? { offset } : {}), (orient ? { orient } : {}));
4664        return { mark, nonMark };
4665    }
4666
4667    const defaultViewConfig = {
4668        width: 200,
4669        height: 200
4670    };
4671    function isVgScheme(rangeConfig) {
4672        return rangeConfig && !!rangeConfig['scheme'];
4673    }
4674    const defaultConfig$1 = {
4675        padding: 5,
4676        timeFormat: '%b %d, %Y',
4677        countTitle: 'Count of Records',
4678        invalidValues: 'filter',
4679        view: defaultViewConfig,
4680        mark: defaultMarkConfig,
4681        area: {},
4682        bar: defaultBarConfig,
4683        circle: {},
4684        geoshape: {},
4685        line: {},
4686        point: {},
4687        rect: {},
4688        rule: { color: 'black' },
4689        square: {},
4690        text: { color: 'black' },
4691        tick: defaultTickConfig,
4692        trail: {},
4693        boxplot: {
4694            size: 14,
4695            extent: 1.5,
4696            box: {},
4697            median: { color: 'white' },
4698            outliers: {},
4699            rule: {},
4700            ticks: null
4701        },
4702        errorbar: {
4703            center: 'mean',
4704            rule: true,
4705            ticks: false
4706        },
4707        errorband: {
4708            band: {
4709                opacity: 0.3
4710            },
4711            borders: false
4712        },
4713        scale: defaultScaleConfig,
4714        projection: {},
4715        axis: {},
4716        axisX: {},
4717        axisY: { minExtent: 30 },
4718        axisLeft: {},
4719        axisRight: {},
4720        axisTop: {},
4721        axisBottom: {},
4722        axisBand: {},
4723        legend: defaultLegendConfig,
4724        selection: defaultConfig,
4725        style: {},
4726        title: {}
4727    };
4728    function initConfig(config) {
4729        return mergeDeep(duplicate(defaultConfig$1), config);
4730    }
4731    const MARK_STYLES = ['view', ...PRIMITIVE_MARKS];
4732    const VL_ONLY_CONFIG_PROPERTIES = [
4733        'padding',
4734        'numberFormat',
4735        'timeFormat',
4736        'countTitle',
4737        'stack',
4738        'scale',
4739        'selection',
4740        'invalidValues',
4741        'overlay' // FIXME: Redesign and unhide this
4742    ];
4743    const VL_ONLY_ALL_MARK_SPECIFIC_CONFIG_PROPERTY_INDEX = Object.assign({ view: ['width', 'height'] }, VL_ONLY_MARK_SPECIFIC_CONFIG_PROPERTY_INDEX);
4744    function stripAndRedirectConfig(config) {
4745        config = duplicate(config);
4746        for (const prop of VL_ONLY_CONFIG_PROPERTIES) {
4747            delete config[prop];
4748        }
4749        // Remove Vega-Lite only axis/legend config
4750        if (config.axis) {
4751            for (const prop of VL_ONLY_GUIDE_CONFIG) {
4752                delete config.axis[prop];
4753            }
4754        }
4755        if (config.legend) {
4756            for (const prop of VL_ONLY_GUIDE_CONFIG) {
4757                delete config.legend[prop];
4758            }
4759            for (const prop of VL_ONLY_LEGEND_CONFIG) {
4760                delete config.legend[prop];
4761            }
4762        }
4763        // Remove Vega-Lite only generic mark config
4764        if (config.mark) {
4765            for (const prop of VL_ONLY_MARK_CONFIG_PROPERTIES) {
4766                delete config.mark[prop];
4767            }
4768        }
4769        for (const markType of MARK_STYLES) {
4770            // Remove Vega-Lite-only mark config
4771            for (const prop of VL_ONLY_MARK_CONFIG_PROPERTIES) {
4772                delete config[markType][prop];
4773            }
4774            // Remove Vega-Lite only mark-specific config
4775            const vlOnlyMarkSpecificConfigs = VL_ONLY_ALL_MARK_SPECIFIC_CONFIG_PROPERTY_INDEX[markType];
4776            if (vlOnlyMarkSpecificConfigs) {
4777                for (const prop of vlOnlyMarkSpecificConfigs) {
4778                    delete config[markType][prop];
4779                }
4780            }
4781            // Redirect mark config to config.style so that mark config only affect its own mark type
4782            // without affecting other marks that share the same underlying Vega marks.
4783            // For example, config.rect should not affect bar marks.
4784            redirectConfig(config, markType);
4785        }
4786        for (const m of getAllCompositeMarks()) {
4787            // Clean up the composite mark config as we don't need them in the output specs anymore
4788            delete config[m];
4789        }
4790        // Redirect config.title -- so that title config do not
4791        // affect header labels, which also uses `title` directive to implement.
4792        redirectConfig(config, 'title', 'group-title');
4793        // Remove empty config objects
4794        for (const prop in config) {
4795            if (isObject(config[prop]) && keys(config[prop]).length === 0) {
4796                delete config[prop];
4797            }
4798        }
4799        return keys(config).length > 0 ? config : undefined;
4800    }
4801    function redirectConfig(config, prop, // string = composite mark
4802    toProp, compositeMarkPart) {
4803        const propConfig = prop === 'title'
4804            ? extractTitleConfig(config.title).mark
4805            : compositeMarkPart
4806                ? config[prop][compositeMarkPart]
4807                : config[prop];
4808        if (prop === 'view') {
4809            toProp = 'cell'; // View's default style is "cell"
4810        }
4811        const style = Object.assign({}, propConfig, config.style[prop]);
4812        // set config.style if it is not an empty object
4813        if (keys(style).length > 0) {
4814            config.style[toProp || prop] = style;
4815        }
4816        if (!compositeMarkPart) {
4817            // For composite mark, so don't delete the whole config yet as we have to do multiple redirections.
4818            delete config[prop];
4819        }
4820    }
4821
4822    var config = /*#__PURE__*/Object.freeze({
4823        defaultViewConfig: defaultViewConfig,
4824        isVgScheme: isVgScheme,
4825        defaultConfig: defaultConfig$1,
4826        initConfig: initConfig,
4827        stripAndRedirectConfig: stripAndRedirectConfig
4828    });
4829
4830    function isUrlData(data) {
4831        return !!data['url'];
4832    }
4833    function isInlineData(data) {
4834        return !!data['values'];
4835    }
4836    function isNamedData(data) {
4837        return !!data['name'] && !isUrlData(data) && !isInlineData(data);
4838    }
4839    const MAIN = 'main';
4840    const RAW = 'raw';
4841
4842    var data = /*#__PURE__*/Object.freeze({
4843        isUrlData: isUrlData,
4844        isInlineData: isInlineData,
4845        isNamedData: isNamedData,
4846        MAIN: MAIN,
4847        RAW: RAW
4848    });
4849
4850    const HEADER_TITLE_PROPERTIES_MAP = {
4851        titleAnchor: 'anchor',
4852        titleAngle: 'angle',
4853        titleBaseline: 'baseline',
4854        titleColor: 'color',
4855        titleFont: 'font',
4856        titleFontSize: 'fontSize',
4857        titleFontWeight: 'fontWeight',
4858        titleLimit: 'limit',
4859        titlePadding: 'offset'
4860    };
4861    const HEADER_LABEL_PROPERTIES_MAP = {
4862        labelAngle: 'angle',
4863        labelColor: 'color',
4864        labelFont: 'font',
4865        labelFontSize: 'fontSize',
4866        labelLimit: 'limit',
4867        labelPadding: 'offset'
4868    };
4869    const HEADER_TITLE_PROPERTIES = keys(HEADER_TITLE_PROPERTIES_MAP);
4870    const HEADER_LABEL_PROPERTIES = keys(HEADER_LABEL_PROPERTIES_MAP);
4871
4872    var header = /*#__PURE__*/Object.freeze({
4873        HEADER_TITLE_PROPERTIES_MAP: HEADER_TITLE_PROPERTIES_MAP,
4874        HEADER_LABEL_PROPERTIES_MAP: HEADER_LABEL_PROPERTIES_MAP,
4875        HEADER_TITLE_PROPERTIES: HEADER_TITLE_PROPERTIES,
4876        HEADER_LABEL_PROPERTIES: HEADER_LABEL_PROPERTIES
4877    });
4878
4879    function isSortField(sort) {
4880        return !!sort && (sort['op'] === 'count' || !!sort['field']) && !!sort['op'];
4881    }
4882    function isSortArray(sort) {
4883        return !!sort && isArray(sort);
4884    }
4885
4886    var sort = /*#__PURE__*/Object.freeze({
4887        isSortField: isSortField,
4888        isSortArray: isSortArray
4889    });
4890
4891    const STACK_OFFSET_INDEX = {
4892        zero: 1,
4893        center: 1,
4894        normalize: 1
4895    };
4896    function isStackOffset(s) {
4897        return !!STACK_OFFSET_INDEX[s];
4898    }
4899    const STACKABLE_MARKS = [BAR, AREA, RULE, POINT, CIRCLE, SQUARE, LINE, TEXT$1, TICK];
4900    const STACK_BY_DEFAULT_MARKS = [BAR, AREA];
4901    function potentialStackedChannel(encoding) {
4902        const xDef = encoding.x;
4903        const yDef = encoding.y;
4904        if (isFieldDef(xDef) && isFieldDef(yDef)) {
4905            if (xDef.type === 'quantitative' && yDef.type === 'quantitative') {
4906                if (xDef.stack) {
4907                    return 'x';
4908                }
4909                else if (yDef.stack) {
4910                    return 'y';
4911                }
4912                // if there is no explicit stacking, only apply stack if there is only one aggregate for x or y
4913                if (!!xDef.aggregate !== !!yDef.aggregate) {
4914                    return xDef.aggregate ? 'x' : 'y';
4915                }
4916            }
4917            else if (xDef.type === 'quantitative') {
4918                return 'x';
4919            }
4920            else if (yDef.type === 'quantitative') {
4921                return 'y';
4922            }
4923        }
4924        else if (isFieldDef(xDef) && xDef.type === 'quantitative') {
4925            return 'x';
4926        }
4927        else if (isFieldDef(yDef) && yDef.type === 'quantitative') {
4928            return 'y';
4929        }
4930        return undefined;
4931    }
4932    // Note: CompassQL uses this method and only pass in required properties of each argument object.
4933    // If required properties change, make sure to update CompassQL.
4934    function stack(m, encoding, stackConfig) {
4935        const mark = isMarkDef(m) ? m.type : m;
4936        // Should have stackable mark
4937        if (!contains(STACKABLE_MARKS, mark)) {
4938            return null;
4939        }
4940        const fieldChannel = potentialStackedChannel(encoding);
4941        if (!fieldChannel) {
4942            return null;
4943        }
4944        const stackedFieldDef = encoding[fieldChannel];
4945        const stackedField = isStringFieldDef(stackedFieldDef) ? vgField(stackedFieldDef, {}) : undefined;
4946        const dimensionChannel = fieldChannel === 'x' ? 'y' : 'x';
4947        const dimensionDef = encoding[dimensionChannel];
4948        const dimensionField = isStringFieldDef(dimensionDef) ? vgField(dimensionDef, {}) : undefined;
4949        // Should have grouping level of detail that is different from the dimension field
4950        const stackBy = NONPOSITION_CHANNELS.reduce((sc, channel) => {
4951            if (channelHasField(encoding, channel)) {
4952                const channelDef = encoding[channel];
4953                (isArray(channelDef) ? channelDef : [channelDef]).forEach(cDef => {
4954                    const fieldDef = getTypedFieldDef(cDef);
4955                    if (fieldDef.aggregate) {
4956                        return;
4957                    }
4958                    // Check whether the channel's field is identical to x/y's field or if the channel is a repeat
4959                    const f = isStringFieldDef(fieldDef) ? vgField(fieldDef, {}) : undefined;
4960                    if (
4961                    // if fielddef is a repeat, just include it in the stack by
4962                    !f ||
4963                        // otherwise, the field must be different from x and y fields.
4964                        (f !== dimensionField && f !== stackedField)) {
4965                        sc.push({ channel, fieldDef });
4966                    }
4967                });
4968            }
4969            return sc;
4970        }, []);
4971        if (stackBy.length === 0) {
4972            return null;
4973        }
4974        // Automatically determine offset
4975        let offset;
4976        if (stackedFieldDef.stack !== undefined) {
4977            offset = stackedFieldDef.stack;
4978        }
4979        else if (contains(STACK_BY_DEFAULT_MARKS, mark)) {
4980            // Bar and Area with sum ops are automatically stacked by default
4981            offset = getFirstDefined(stackConfig, 'zero');
4982        }
4983        else {
4984            offset = stackConfig;
4985        }
4986        if (!offset || !isStackOffset(offset)) {
4987            return null;
4988        }
4989        // warn when stacking non-linear
4990        if (stackedFieldDef.scale && stackedFieldDef.scale.type && stackedFieldDef.scale.type !== ScaleType.LINEAR) {
4991            warn(message.cannotStackNonLinearScale(stackedFieldDef.scale.type));
4992        }
4993        // Check if it is a ranged mark
4994        if (channelHasField(encoding, fieldChannel === X ? X2 : Y2)) {
4995            if (stackedFieldDef.stack !== undefined) {
4996                warn(message.cannotStackRangedMark(fieldChannel));
4997            }
4998            return null;
4999        }
5000        // Warn if stacking summative aggregate
5001        if (stackedFieldDef.aggregate && !contains(SUM_OPS, stackedFieldDef.aggregate)) {
5002            warn(message.stackNonSummativeAggregate(stackedFieldDef.aggregate));
5003        }
5004        return {
5005            groupbyChannel: dimensionDef ? dimensionChannel : undefined,
5006            fieldChannel,
5007            impute: isPathMark(mark),
5008            stackBy,
5009            offset
5010        };
5011    }
5012
5013    var stack$1 = /*#__PURE__*/Object.freeze({
5014        isStackOffset: isStackOffset,
5015        STACKABLE_MARKS: STACKABLE_MARKS,
5016        STACK_BY_DEFAULT_MARKS: STACK_BY_DEFAULT_MARKS,
5017        stack: stack
5018    });
5019
5020    function isConcatSpec(spec) {
5021        return isVConcatSpec(spec) || isHConcatSpec(spec);
5022    }
5023    function isVConcatSpec(spec) {
5024        return spec['vconcat'] !== undefined;
5025    }
5026    function isHConcatSpec(spec) {
5027        return spec['hconcat'] !== undefined;
5028    }
5029
5030    function isLayerSpec(spec) {
5031        return spec['layer'] !== undefined;
5032    }
5033
5034    function isRepeatSpec(spec) {
5035        return spec['repeat'] !== undefined;
5036    }
5037
5038    function normalizeTopLevelSpec$$1(spec, config) {
5039        return normalize$2(spec, config);
5040    }
5041    /**
5042     * Decompose extended unit specs into composition of pure unit specs.
5043     */
5044    function normalize$2(spec, config) {
5045        if (isFacetSpec(spec)) {
5046            return normalizeFacet(spec, config);
5047        }
5048        if (isLayerSpec(spec)) {
5049            return normalizeLayer(spec, config);
5050        }
5051        if (isRepeatSpec(spec)) {
5052            return normalizeRepeat(spec, config);
5053        }
5054        if (isVConcatSpec(spec)) {
5055            return normalizeVConcat(spec, config);
5056        }
5057        if (isHConcatSpec(spec)) {
5058            return normalizeHConcat(spec, config);
5059        }
5060        if (isUnitSpec(spec)) {
5061            const hasRow = channelHasField(spec.encoding, ROW);
5062            const hasColumn = channelHasField(spec.encoding, COLUMN);
5063            if (hasRow || hasColumn) {
5064                return normalizeFacetedUnit(spec, config);
5065            }
5066            return normalizeNonFacetUnit(spec, config);
5067        }
5068        throw new Error(message.INVALID_SPEC);
5069    }
5070    function normalizeFacet(spec, config) {
5071        const { spec: subspec } = spec, rest = __rest(spec, ["spec"]);
5072        return Object.assign({}, rest, { 
5073            // TODO: remove "any" once we support all facet listed in https://github.com/vega/vega-lite/issues/2760
5074            spec: normalize$2(subspec, config) });
5075    }
5076    function mergeEncoding(opt) {
5077        const { parentEncoding, encoding } = opt;
5078        if (parentEncoding && encoding) {
5079            const overriden = keys(parentEncoding).reduce((o, key$$1) => {
5080                if (encoding[key$$1]) {
5081                    o.push(key$$1);
5082                }
5083                return o;
5084            }, []);
5085            if (overriden.length > 0) {
5086                warn(message.encodingOverridden(overriden));
5087            }
5088        }
5089        const merged = Object.assign({}, (parentEncoding || {}), (encoding || {}));
5090        return keys(merged).length > 0 ? merged : undefined;
5091    }
5092    function mergeProjection(opt) {
5093        const { parentProjection, projection } = opt;
5094        if (parentProjection && projection) {
5095            warn(message.projectionOverridden({ parentProjection, projection }));
5096        }
5097        return projection || parentProjection;
5098    }
5099    function normalizeLayer(spec, config, parentEncoding, parentProjection) {
5100        const { layer, encoding, projection } = spec, rest = __rest(spec, ["layer", "encoding", "projection"]);
5101        const mergedEncoding = mergeEncoding({ parentEncoding, encoding });
5102        const mergedProjection = mergeProjection({ parentProjection, projection });
5103        return Object.assign({}, rest, { layer: layer.map(subspec => {
5104                if (isLayerSpec(subspec)) {
5105                    return normalizeLayer(subspec, config, mergedEncoding, mergedProjection);
5106                }
5107                return normalizeNonFacetUnit(subspec, config, mergedEncoding, mergedProjection);
5108            }) });
5109    }
5110    function normalizeRepeat(spec, config) {
5111        const { spec: subspec } = spec, rest = __rest(spec, ["spec"]);
5112        return Object.assign({}, rest, { spec: normalize$2(subspec, config) });
5113    }
5114    function normalizeVConcat(spec, config) {
5115        const { vconcat: vconcat } = spec, rest = __rest(spec, ["vconcat"]);
5116        return Object.assign({}, rest, { vconcat: vconcat.map(subspec => normalize$2(subspec, config)) });
5117    }
5118    function normalizeHConcat(spec, config) {
5119        const { hconcat: hconcat } = spec, rest = __rest(spec, ["hconcat"]);
5120        return Object.assign({}, rest, { hconcat: hconcat.map(subspec => normalize$2(subspec, config)) });
5121    }
5122    function normalizeFacetedUnit(spec, config) {
5123        // New encoding in the inside spec should not contain row / column
5124        // as row/column should be moved to facet
5125        const _a = spec.encoding, { row: row, column: column } = _a, encoding = __rest(_a, ["row", "column"]);
5126        // Mark and encoding should be moved into the inner spec
5127        const { mark, width, projection, height, selection, encoding: _ } = spec, outerSpec = __rest(spec, ["mark", "width", "projection", "height", "selection", "encoding"]);
5128        return Object.assign({}, outerSpec, { facet: Object.assign({}, (row ? { row } : {}), (column ? { column } : {})), spec: normalizeNonFacetUnit(Object.assign({}, (projection ? { projection } : {}), { mark }, (width ? { width } : {}), (height ? { height } : {}), { encoding }, (selection ? { selection } : {})), config) });
5129    }
5130    function isNonFacetUnitSpecWithPrimitiveMark(spec) {
5131        return isPrimitiveMark(spec.mark);
5132    }
5133    function getPointOverlay(markDef, markConfig, encoding) {
5134        if (markDef.point === 'transparent') {
5135            return { opacity: 0 };
5136        }
5137        else if (markDef.point) {
5138            // truthy : true or object
5139            return isObject(markDef.point) ? markDef.point : {};
5140        }
5141        else if (markDef.point !== undefined) {
5142            // false or null
5143            return null;
5144        }
5145        else {
5146            // undefined (not disabled)
5147            if (markConfig.point || encoding.shape) {
5148                // enable point overlay if config[mark].point is truthy or if encoding.shape is provided
5149                return isObject(markConfig.point) ? markConfig.point : {};
5150            }
5151            // markDef.point is defined as falsy
5152            return null;
5153        }
5154    }
5155    function getLineOverlay(markDef, markConfig) {
5156        if (markDef.line) {
5157            // true or object
5158            return markDef.line === true ? {} : markDef.line;
5159        }
5160        else if (markDef.line !== undefined) {
5161            // false or null
5162            return null;
5163        }
5164        else {
5165            // undefined (not disabled)
5166            if (markConfig.line) {
5167                // enable line overlay if config[mark].line is truthy
5168                return markConfig.line === true ? {} : markConfig.line;
5169            }
5170            // markDef.point is defined as falsy
5171            return null;
5172        }
5173    }
5174    function normalizeNonFacetUnit(spec, config, parentEncoding, parentProjection) {
5175        const { encoding, projection } = spec;
5176        const mark = isMarkDef(spec.mark) ? spec.mark.type : spec.mark;
5177        // merge parent encoding / projection first
5178        if (parentEncoding || parentProjection) {
5179            const mergedProjection = mergeProjection({ parentProjection, projection });
5180            const mergedEncoding = mergeEncoding({ parentEncoding, encoding });
5181            return normalizeNonFacetUnit(Object.assign({}, spec, (mergedProjection ? { projection: mergedProjection } : {}), (mergedEncoding ? { encoding: mergedEncoding } : {})), config);
5182        }
5183        if (isNonFacetUnitSpecWithPrimitiveMark(spec)) {
5184            // TODO: thoroughly test
5185            if (mark === 'line' && (encoding.x2 || encoding.y2)) {
5186                warn(message.lineWithRange(!!encoding.x2, !!encoding.y2));
5187                return normalizeNonFacetUnit(Object.assign({ mark: 'rule' }, spec), config, parentEncoding, parentProjection);
5188            }
5189            if (isPathMark(mark)) {
5190                return normalizePathOverlay(spec, config);
5191            }
5192            return spec; // Nothing to normalize
5193        }
5194        else {
5195            return normalize$1(spec, config);
5196        }
5197    }
5198    function dropLineAndPoint(markDef) {
5199        const mark = __rest(markDef, ["point", "line"]);
5200        return keys(mark).length > 1 ? mark : mark.type;
5201    }
5202    function normalizePathOverlay(spec, config = {}) {
5203        // _ is used to denote a dropped property of the unit spec
5204        // which should not be carried over to the layer spec
5205        const { selection, projection, encoding, mark } = spec, outerSpec = __rest(spec, ["selection", "projection", "encoding", "mark"]);
5206        const markDef = isMarkDef(mark) ? mark : { type: mark };
5207        const pointOverlay = getPointOverlay(markDef, config[markDef.type], encoding);
5208        const lineOverlay = markDef.type === 'area' && getLineOverlay(markDef, config[markDef.type]);
5209        if (!pointOverlay && !lineOverlay) {
5210            return Object.assign({}, spec, { 
5211                // Do not include point / line overlay in the normalize spec
5212                mark: dropLineAndPoint(markDef) });
5213        }
5214        const layer = [
5215            Object.assign({}, (selection ? { selection } : {}), { 
5216                // Do not include point / line overlay in the normalize spec
5217                mark: dropLineAndPoint(Object.assign({}, markDef, (markDef.type === 'area' ? { opacity: 0.7 } : {}))), 
5218                // drop shape from encoding as this might be used to trigger point overlay
5219                encoding: omit(encoding, ['shape']) })
5220        ];
5221        // FIXME: determine rules for applying selections.
5222        // Need to copy stack config to overlayed layer
5223        const stackProps = stack(markDef, encoding, config ? config.stack : undefined);
5224        let overlayEncoding = encoding;
5225        if (stackProps) {
5226            const { fieldChannel: stackFieldChannel, offset } = stackProps;
5227            overlayEncoding = Object.assign({}, encoding, { [stackFieldChannel]: Object.assign({}, encoding[stackFieldChannel], (offset ? { stack: offset } : {})) });
5228        }
5229        if (lineOverlay) {
5230            layer.push(Object.assign({}, (projection ? { projection } : {}), { mark: Object.assign({ type: 'line' }, pick(markDef, ['clip', 'interpolate', 'tension']), lineOverlay), encoding: overlayEncoding }));
5231        }
5232        if (pointOverlay) {
5233            layer.push(Object.assign({}, (projection ? { projection } : {}), { mark: Object.assign({ type: 'point', opacity: 1, filled: true }, pick(markDef, ['clip']), pointOverlay), encoding: overlayEncoding }));
5234        }
5235        return Object.assign({}, outerSpec, { layer });
5236    }
5237
5238    function isUnitSpec(spec) {
5239        return !!spec['mark'];
5240    }
5241
5242    /* Custom type guards */
5243    // TODO: add vl.spec.validate & move stuff from vl.validate to here
5244    /* Accumulate non-duplicate fieldDefs in a dictionary */
5245    function accumulate(dict, defs) {
5246        defs.forEach(fieldDef => {
5247            // Consider only pure fieldDef properties (ignoring scale, axis, legend)
5248            const pureFieldDef = ['field', 'type', 'value', 'timeUnit', 'bin', 'aggregate'].reduce((f, key) => {
5249                if (fieldDef[key] !== undefined) {
5250                    f[key] = fieldDef[key];
5251                }
5252                return f;
5253            }, {});
5254            const key = hash(pureFieldDef);
5255            dict[key] = dict[key] || fieldDef;
5256        });
5257        return dict;
5258    }
5259    /* Recursively get fieldDefs from a spec, returns a dictionary of fieldDefs */
5260    function fieldDefIndex(spec, dict = {}) {
5261        // FIXME(https://github.com/vega/vega-lite/issues/2207): Support fieldDefIndex for repeat
5262        if (isLayerSpec(spec)) {
5263            spec.layer.forEach(layer => {
5264                if (isUnitSpec(layer)) {
5265                    accumulate(dict, fieldDefs(layer.encoding));
5266                }
5267                else {
5268                    fieldDefIndex(layer, dict);
5269                }
5270            });
5271        }
5272        else if (isFacetSpec(spec)) {
5273            accumulate(dict, fieldDefs(spec.facet));
5274            fieldDefIndex(spec.spec, dict);
5275        }
5276        else if (isRepeatSpec(spec)) {
5277            fieldDefIndex(spec.spec, dict);
5278        }
5279        else if (isConcatSpec(spec)) {
5280            const childSpec = isVConcatSpec(spec) ? spec.vconcat : spec.hconcat;
5281            childSpec.forEach(child => fieldDefIndex(child, dict));
5282        }
5283        else {
5284            // Unit Spec
5285            accumulate(dict, fieldDefs(spec.encoding));
5286        }
5287        return dict;
5288    }
5289    /* Returns all non-duplicate fieldDefs in a spec in a flat array */
5290    function fieldDefs$1(spec) {
5291        return vals(fieldDefIndex(spec));
5292    }
5293    function isStacked(spec, config) {
5294        config = config || spec.config;
5295        if (isPrimitiveMark(spec.mark)) {
5296            return stack(spec.mark, spec.encoding, config ? config.stack : undefined) !== null;
5297        }
5298        return false;
5299    }
5300    /**
5301     * Takes a spec and returns a list of fields used in encoding
5302     */
5303    function usedFields(spec) {
5304        if (isFacetSpec(spec) || isRepeatSpec(spec)) {
5305            return usedFieldsSingle(spec);
5306        }
5307        if (isLayerSpec(spec)) {
5308            return usedFieldsLayered(spec);
5309        }
5310        if (isUnitSpec(spec)) {
5311            return usedFieldsUnit(spec);
5312        }
5313        throw new Error(message.INVALID_SPEC);
5314    }
5315    function usedFieldsUnit(spec) {
5316        const fields = [];
5317        forEach(spec.encoding, (channelDef, channel) => {
5318            if (isFieldDef(channelDef)) {
5319                fields.push(channelDef.field);
5320            }
5321        });
5322        return fields;
5323    }
5324    function usedFieldsLayered(spec) {
5325        let fields = [];
5326        spec.layer.map(subspec => {
5327            fields = fields.concat(usedFields(subspec));
5328        });
5329        return fields;
5330    }
5331    function usedFieldsSingle(spec) {
5332        return usedFields(spec.spec);
5333    }
5334
5335    var index$1 = /*#__PURE__*/Object.freeze({
5336        fieldDefs: fieldDefs$1,
5337        isStacked: isStacked,
5338        usedFields: usedFields,
5339        normalize: normalizeTopLevelSpec$$1,
5340        isConcatSpec: isConcatSpec,
5341        isHConcatSpec: isHConcatSpec,
5342        isVConcatSpec: isVConcatSpec,
5343        isFacetSpec: isFacetSpec,
5344        isLayerSpec: isLayerSpec,
5345        isRepeatSpec: isRepeatSpec,
5346        isUnitSpec: isUnitSpec
5347    });
5348
5349    function isSelectionPredicate(predicate) {
5350        return predicate && predicate['selection'];
5351    }
5352    function isFieldEqualPredicate(predicate) {
5353        return predicate && !!predicate.field && predicate.equal !== undefined;
5354    }
5355    function isFieldLTPredicate(predicate) {
5356        return predicate && !!predicate.field && predicate.lt !== undefined;
5357    }
5358    function isFieldLTEPredicate(predicate) {
5359        return predicate && !!predicate.field && predicate.lte !== undefined;
5360    }
5361    function isFieldGTPredicate(predicate) {
5362        return predicate && !!predicate.field && predicate.gt !== undefined;
5363    }
5364    function isFieldGTEPredicate(predicate) {
5365        return predicate && !!predicate.field && predicate.gte !== undefined;
5366    }
5367    function isFieldRangePredicate(predicate) {
5368        if (predicate && predicate.field) {
5369            if (isArray(predicate.range) && predicate.range.length === 2) {
5370                return true;
5371            }
5372        }
5373        return false;
5374    }
5375    function isFieldOneOfPredicate(predicate) {
5376        return (predicate && !!predicate.field && (isArray(predicate.oneOf) || isArray(predicate.in)) // backward compatibility
5377        );
5378    }
5379    function isFieldValidPredicate(predicate) {
5380        return predicate && !!predicate.field && predicate.valid !== undefined;
5381    }
5382    function isFieldPredicate(predicate) {
5383        return (isFieldOneOfPredicate(predicate) ||
5384            isFieldEqualPredicate(predicate) ||
5385            isFieldRangePredicate(predicate) ||
5386            isFieldLTPredicate(predicate) ||
5387            isFieldGTPredicate(predicate) ||
5388            isFieldLTEPredicate(predicate) ||
5389            isFieldGTEPredicate(predicate));
5390    }
5391    function predicateValueExpr(v, timeUnit) {
5392        return valueExpr(v, { timeUnit, time: true });
5393    }
5394    function predicateValuesExpr(vals, timeUnit) {
5395        return vals.map(v => predicateValueExpr(v, timeUnit));
5396    }
5397    // This method is used by Voyager.  Do not change its behavior without changing Voyager.
5398    function fieldFilterExpression(predicate, useInRange = true) {
5399        const { field: field$$1, timeUnit } = predicate;
5400        const fieldExpr$$1 = timeUnit
5401            ? // For timeUnit, cast into integer with time() so we can use ===, inrange, indexOf to compare values directly.
5402                // TODO: We calculate timeUnit on the fly here. Consider if we would like to consolidate this with timeUnit pipeline
5403                // TODO: support utc
5404                'time(' + fieldExpr(timeUnit, field$$1) + ')'
5405            : vgField(predicate, { expr: 'datum' });
5406        if (isFieldEqualPredicate(predicate)) {
5407            return fieldExpr$$1 + '===' + predicateValueExpr(predicate.equal, timeUnit);
5408        }
5409        else if (isFieldLTPredicate(predicate)) {
5410            const upper = predicate.lt;
5411            return `${fieldExpr$$1}<${predicateValueExpr(upper, timeUnit)}`;
5412        }
5413        else if (isFieldGTPredicate(predicate)) {
5414            const lower = predicate.gt;
5415            return `${fieldExpr$$1}>${predicateValueExpr(lower, timeUnit)}`;
5416        }
5417        else if (isFieldLTEPredicate(predicate)) {
5418            const upper = predicate.lte;
5419            return `${fieldExpr$$1}<=${predicateValueExpr(upper, timeUnit)}`;
5420        }
5421        else if (isFieldGTEPredicate(predicate)) {
5422            const lower = predicate.gte;
5423            return `${fieldExpr$$1}>=${predicateValueExpr(lower, timeUnit)}`;
5424        }
5425        else if (isFieldOneOfPredicate(predicate)) {
5426            return `indexof([${predicateValuesExpr(predicate.oneOf, timeUnit).join(',')}], ${fieldExpr$$1}) !== -1`;
5427        }
5428        else if (isFieldValidPredicate(predicate)) {
5429            return predicate.valid ? `${fieldExpr$$1}!==null&&!isNaN(${fieldExpr$$1})` : `${fieldExpr$$1}===null||isNaN(${fieldExpr$$1})`;
5430        }
5431        else if (isFieldRangePredicate(predicate)) {
5432            const lower = predicate.range[0];
5433            const upper = predicate.range[1];
5434            if (lower !== null && upper !== null && useInRange) {
5435                return ('inrange(' +
5436                    fieldExpr$$1 +
5437                    ', [' +
5438                    predicateValueExpr(lower, timeUnit) +
5439                    ', ' +
5440                    predicateValueExpr(upper, timeUnit) +
5441                    '])');
5442            }
5443            const exprs = [];
5444            if (lower !== null) {
5445                exprs.push(`${fieldExpr$$1} >= ${predicateValueExpr(lower, timeUnit)}`);
5446            }
5447            if (upper !== null) {
5448                exprs.push(`${fieldExpr$$1} <= ${predicateValueExpr(upper, timeUnit)}`);
5449            }
5450            return exprs.length > 0 ? exprs.join(' && ') : 'true';
5451        }
5452        /* istanbul ignore next: it should never reach here */
5453        throw new Error(`Invalid field predicate: ${JSON.stringify(predicate)}`);
5454    }
5455    function normalizePredicate(f) {
5456        if (isFieldPredicate(f) && f.timeUnit) {
5457            return Object.assign({}, f, { timeUnit: normalizeTimeUnit(f.timeUnit) });
5458        }
5459        return f;
5460    }
5461
5462    function isFilter(t) {
5463        return t['filter'] !== undefined;
5464    }
5465    function isImputeSequence(t) {
5466        return t && t['start'] !== undefined && t['stop'] !== undefined;
5467    }
5468    function isLookup(t) {
5469        return t['lookup'] !== undefined;
5470    }
5471    function isSample(t) {
5472        return t['sample'] !== undefined;
5473    }
5474    function isWindow(t) {
5475        return t['window'] !== undefined;
5476    }
5477    function isFlatten(t) {
5478        return t['flatten'] !== undefined;
5479    }
5480    function isCalculate(t) {
5481        return t['calculate'] !== undefined;
5482    }
5483    function isBin(t) {
5484        return !!t['bin'];
5485    }
5486    function isImpute(t) {
5487        return t['impute'] !== undefined;
5488    }
5489    function isTimeUnit$1(t) {
5490        return t['timeUnit'] !== undefined;
5491    }
5492    function isAggregate$1(t) {
5493        return t['aggregate'] !== undefined;
5494    }
5495    function isStack(t) {
5496        return t['stack'] !== undefined;
5497    }
5498    function isFold(t) {
5499        return t['fold'] !== undefined;
5500    }
5501    function normalizeTransform(transform) {
5502        return transform.map(t => {
5503            if (isFilter(t)) {
5504                return {
5505                    filter: normalizeLogicalOperand(t.filter, normalizePredicate)
5506                };
5507            }
5508            return t;
5509        });
5510    }
5511
5512    var transform = /*#__PURE__*/Object.freeze({
5513        isFilter: isFilter,
5514        isImputeSequence: isImputeSequence,
5515        isLookup: isLookup,
5516        isSample: isSample,
5517        isWindow: isWindow,
5518        isFlatten: isFlatten,
5519        isCalculate: isCalculate,
5520        isBin: isBin,
5521        isImpute: isImpute,
5522        isTimeUnit: isTimeUnit$1,
5523        isAggregate: isAggregate$1,
5524        isStack: isStack,
5525        isFold: isFold,
5526        normalizeTransform: normalizeTransform
5527    });
5528
5529    /**
5530     * Required Encoding Channels for each mark type
5531     */
5532    const DEFAULT_REQUIRED_CHANNEL_MAP = {
5533        text: ['text'],
5534        line: ['x', 'y'],
5535        trail: ['x', 'y'],
5536        area: ['x', 'y']
5537    };
5538    /**
5539     * Supported Encoding Channel for each mark type
5540     */
5541    const DEFAULT_SUPPORTED_CHANNEL_TYPE = {
5542        bar: toSet(['row', 'column', 'x', 'y', 'size', 'color', 'fill', 'stroke', 'detail']),
5543        line: toSet(['row', 'column', 'x', 'y', 'color', 'fill', 'stroke', 'color', 'detail']),
5544        trail: toSet(['row', 'column', 'x', 'y', 'color', 'fill', 'stroke', 'color', 'detail', 'size']),
5545        area: toSet(['row', 'column', 'x', 'y', 'color', 'fill', 'stroke', 'detail']),
5546        tick: toSet(['row', 'column', 'x', 'y', 'color', 'fill', 'stroke', 'detail']),
5547        circle: toSet(['row', 'column', 'x', 'y', 'color', 'fill', 'stroke', 'size', 'detail']),
5548        square: toSet(['row', 'column', 'x', 'y', 'color', 'fill', 'stroke', 'size', 'detail']),
5549        point: toSet(['row', 'column', 'x', 'y', 'color', 'fill', 'stroke', 'size', 'detail', 'shape']),
5550        geoshape: toSet(['row', 'column', 'color', 'fill', 'stroke', 'detail', 'shape']),
5551        text: toSet(['row', 'column', 'size', 'color', 'fill', 'stroke', 'text']) // TODO(#724) revise
5552    };
5553    // TODO: consider if we should add validate method and
5554    // requires ZSchema in the main vega-lite repo
5555    /**
5556     * Further check if encoding mapping of a spec is invalid and
5557     * return error if it is invalid.
5558     *
5559     * This checks if
5560     * (1) all the required encoding channels for the mark type are specified
5561     * (2) all the specified encoding channels are supported by the mark type
5562     * @param  {[type]} spec [description]
5563     * @param  {RequiredChannelMap = DefaultRequiredChannelMap}  requiredChannelMap
5564     * @param  {SupportedChannelMap = DefaultSupportedChannelMap} supportedChannelMap
5565     * @return {String} Return one reason why the encoding is invalid,
5566     *                  or null if the encoding is valid.
5567     */
5568    function getEncodingMappingError(spec, requiredChannelMap = DEFAULT_REQUIRED_CHANNEL_MAP, supportedChannelMap = DEFAULT_SUPPORTED_CHANNEL_TYPE) {
5569        const mark = isMarkDef(spec.mark) ? spec.mark.type : spec.mark;
5570        const encoding = spec.encoding;
5571        const requiredChannels = requiredChannelMap[mark];
5572        const supportedChannels = supportedChannelMap[mark];
5573        for (const i in requiredChannels) {
5574            // all required channels are in encoding`
5575            if (!(requiredChannels[i] in encoding)) {
5576                return 'Missing encoding channel "' + requiredChannels[i] + '" for mark "' + mark + '"';
5577            }
5578        }
5579        for (const channel in encoding) {
5580            // all channels in encoding are supported
5581            if (!supportedChannels[channel]) {
5582                return 'Encoding channel "' + channel + '" is not supported by mark type "' + mark + '"';
5583            }
5584        }
5585        if (mark === BAR && !encoding.x && !encoding.y) {
5586            return 'Missing both x and y for bar';
5587        }
5588        return null;
5589    }
5590
5591    var validate = /*#__PURE__*/Object.freeze({
5592        DEFAULT_REQUIRED_CHANNEL_MAP: DEFAULT_REQUIRED_CHANNEL_MAP,
5593        DEFAULT_SUPPORTED_CHANNEL_TYPE: DEFAULT_SUPPORTED_CHANNEL_TYPE,
5594        getEncodingMappingError: getEncodingMappingError
5595    });
5596
5597    function extractCompositionLayout(layout) {
5598        const { align = undefined, center = undefined, bounds = undefined, spacing = undefined } = layout || {};
5599        return { align, bounds, center, spacing };
5600    }
5601    function _normalizeAutoSize(autosize) {
5602        return isString(autosize) ? { type: autosize } : autosize || {};
5603    }
5604    function normalizeAutoSize(topLevelAutosize, configAutosize, isUnitOrLayer = true) {
5605        const autosize = Object.assign({ type: 'pad' }, _normalizeAutoSize(configAutosize), _normalizeAutoSize(topLevelAutosize));
5606        if (autosize.type === 'fit') {
5607            if (!isUnitOrLayer) {
5608                warn(message.FIT_NON_SINGLE);
5609                autosize.type = 'pad';
5610            }
5611        }
5612        return autosize;
5613    }
5614    const TOP_LEVEL_PROPERTIES = [
5615        'background',
5616        'padding'
5617        // We do not include "autosize" here as it is supported by only unit and layer specs and thus need to be normalized
5618    ];
5619    function extractTopLevelProperties(t) {
5620        return TOP_LEVEL_PROPERTIES.reduce((o, p) => {
5621            if (t && t[p] !== undefined) {
5622                o[p] = t[p];
5623            }
5624            return o;
5625        }, {});
5626    }
5627
5628    function isSignalRef(o) {
5629        return !!o['signal'];
5630    }
5631    function isVgRangeStep(range) {
5632        return !!range['step'];
5633    }
5634    function isDataRefUnionedDomain(domain) {
5635        if (!isArray(domain)) {
5636            return 'fields' in domain && !('data' in domain);
5637        }
5638        return false;
5639    }
5640    function isFieldRefUnionDomain(domain) {
5641        if (!isArray(domain)) {
5642            return 'fields' in domain && 'data' in domain;
5643        }
5644        return false;
5645    }
5646    function isDataRefDomain(domain) {
5647        if (!isArray(domain)) {
5648            return 'field' in domain && 'data' in domain;
5649        }
5650        return false;
5651    }
5652    const VG_MARK_CONFIG_INDEX = {
5653        opacity: 1,
5654        fill: 1,
5655        fillOpacity: 1,
5656        stroke: 1,
5657        strokeCap: 1,
5658        strokeWidth: 1,
5659        strokeOpacity: 1,
5660        strokeDash: 1,
5661        strokeDashOffset: 1,
5662        strokeJoin: 1,
5663        strokeMiterLimit: 1,
5664        size: 1,
5665        shape: 1,
5666        interpolate: 1,
5667        tension: 1,
5668        orient: 1,
5669        align: 1,
5670        baseline: 1,
5671        text: 1,
5672        dir: 1,
5673        dx: 1,
5674        dy: 1,
5675        ellipsis: 1,
5676        limit: 1,
5677        radius: 1,
5678        theta: 1,
5679        angle: 1,
5680        font: 1,
5681        fontSize: 1,
5682        fontWeight: 1,
5683        fontStyle: 1,
5684        cursor: 1,
5685        href: 1,
5686        tooltip: 1,
5687        cornerRadius: 1
5688        // commented below are vg channel that do not have mark config.
5689        // 'x'|'x2'|'xc'|'width'|'y'|'y2'|'yc'|'height'
5690        // clip: 1,
5691        // endAngle: 1,
5692        // innerRadius: 1,
5693        // outerRadius: 1,
5694        // path: 1,
5695        // startAngle: 1,
5696        // url: 1,
5697    };
5698    const VG_MARK_CONFIGS = flagKeys(VG_MARK_CONFIG_INDEX);
5699
5700    function assembleTitle(title$$1, config) {
5701        if (isArray(title$$1)) {
5702            return title$$1.map(fieldDef => defaultTitle(fieldDef, config)).join(', ');
5703        }
5704        return title$$1;
5705    }
5706    function assembleAxis(axisCmpt, kind, config, opt = { header: false }) {
5707        const _a = axisCmpt.combine(), { orient, scale, title: title$$1, zindex } = _a, axis = __rest(_a, ["orient", "scale", "title", "zindex"]);
5708        // Remove properties that are not valid for this kind of axis
5709        keys(axis).forEach(key$$1 => {
5710            const propType = AXIS_PROPERTY_TYPE[key$$1];
5711            if (propType && propType !== kind && propType !== 'both') {
5712                delete axis[key$$1];
5713            }
5714        });
5715        if (kind === 'grid') {
5716            if (!axis.grid) {
5717                return undefined;
5718            }
5719            // Remove unnecessary encode block
5720            if (axis.encode) {
5721                // Only need to keep encode block for grid
5722                const { grid } = axis.encode;
5723                axis.encode = Object.assign({}, (grid ? { grid } : {}));
5724                if (keys(axis.encode).length === 0) {
5725                    delete axis.encode;
5726                }
5727            }
5728            return Object.assign({ scale,
5729                orient }, axis, { domain: false, labels: false, 
5730                // Always set min/maxExtent to 0 to ensure that `config.axis*.minExtent` and `config.axis*.maxExtent`
5731                // would not affect gridAxis
5732                maxExtent: 0, minExtent: 0, ticks: false, zindex: getFirstDefined(zindex, 0) // put grid behind marks by default
5733             });
5734        }
5735        else {
5736            // kind === 'main'
5737            if (!opt.header && axisCmpt.mainExtracted) {
5738                // if mainExtracted has been extracted to a separate facet
5739                return undefined;
5740            }
5741            // Remove unnecessary encode block
5742            if (axis.encode) {
5743                for (const part of AXIS_PARTS) {
5744                    if (!axisCmpt.hasAxisPart(part)) {
5745                        delete axis.encode[part];
5746                    }
5747                }
5748                if (keys(axis.encode).length === 0) {
5749                    delete axis.encode;
5750                }
5751            }
5752            const titleString = assembleTitle(title$$1, config);
5753            return Object.assign({ scale,
5754                orient, grid: false }, (titleString ? { title: titleString } : {}), axis, { zindex: getFirstDefined(zindex, 1) // put axis line above marks by default
5755             });
5756        }
5757    }
5758    function assembleAxes(axisComponents, config) {
5759        const { x = [], y = [] } = axisComponents;
5760        return [
5761            ...x.map(a => assembleAxis(a, 'main', config)),
5762            ...x.map(a => assembleAxis(a, 'grid', config)),
5763            ...y.map(a => assembleAxis(a, 'main', config)),
5764            ...y.map(a => assembleAxis(a, 'grid', config))
5765        ].filter(a => a); // filter undefined
5766    }
5767
5768    function applyMarkConfig(e, model, propsList) {
5769        for (const property of propsList) {
5770            const value = getMarkConfig(property, model.markDef, model.config);
5771            if (value !== undefined) {
5772                e[property] = { value: value };
5773            }
5774        }
5775        return e;
5776    }
5777    function getStyles(mark) {
5778        return [].concat(mark.type, mark.style || []);
5779    }
5780    /**
5781     * Return property value from style or mark specific config property if exists.
5782     * Otherwise, return general mark specific config.
5783     */
5784    function getMarkConfig(prop, mark, config, { skipGeneralMarkConfig = false } = {}) {
5785        return getFirstDefined(
5786        // style config has highest precedence
5787        getStyleConfig(prop, mark, config.style), 
5788        // then mark-specific config
5789        config[mark.type][prop], 
5790        // then general mark config (if not skipped)
5791        skipGeneralMarkConfig ? undefined : config.mark[prop]);
5792    }
5793    function getStyleConfig(prop, mark, styleConfigIndex) {
5794        const styles = getStyles(mark);
5795        let value;
5796        for (const style of styles) {
5797            const styleConfig = styleConfigIndex[style];
5798            // MarkConfig extends VgMarkConfig so a prop may not be a valid property for style
5799            // However here we also check if it is defined, so it is okay to cast here
5800            const p = prop;
5801            if (styleConfig && styleConfig[p] !== undefined) {
5802                value = styleConfig[p];
5803            }
5804        }
5805        return value;
5806    }
5807    function formatSignalRef(fieldDef, specifiedFormat, expr, config) {
5808        const format$$1 = numberFormat(fieldDef, specifiedFormat, config);
5809        if (isBinning(fieldDef.bin)) {
5810            const startField = vgField(fieldDef, { expr });
5811            const endField = vgField(fieldDef, { expr, binSuffix: 'end' });
5812            return {
5813                signal: binFormatExpression(startField, endField, format$$1, config)
5814            };
5815        }
5816        else if (fieldDef.type === 'quantitative') {
5817            return {
5818                signal: `${formatExpr(vgField(fieldDef, { expr, binSuffix: 'range' }), format$$1)}`
5819            };
5820        }
5821        else if (isTimeFieldDef(fieldDef)) {
5822            const isUTCScale = isScaleFieldDef(fieldDef) && fieldDef['scale'] && fieldDef['scale'].type === ScaleType.UTC;
5823            return {
5824                signal: timeFormatExpression(vgField(fieldDef, { expr }), fieldDef.timeUnit, specifiedFormat, config.text.shortTimeLabels, config.timeFormat, isUTCScale, true)
5825            };
5826        }
5827        else {
5828            return {
5829                signal: `''+${vgField(fieldDef, { expr })}`
5830            };
5831        }
5832    }
5833    /**
5834     * Returns number format for a fieldDef
5835     */
5836    function numberFormat(fieldDef, specifiedFormat, config) {
5837        if (isTimeFieldDef(fieldDef)) {
5838            return undefined;
5839        }
5840        // Specified format in axis/legend has higher precedence than fieldDef.format
5841        if (specifiedFormat) {
5842            return specifiedFormat;
5843        }
5844        if (fieldDef.type === QUANTITATIVE) {
5845            // we only apply the default if the field is quantitative
5846            return config.numberFormat;
5847        }
5848        return undefined;
5849    }
5850    function formatExpr(field$$1, format$$1) {
5851        return `format(${field$$1}, "${format$$1 || ''}")`;
5852    }
5853    function numberFormatExpr(field$$1, specifiedFormat, config) {
5854        return formatExpr(field$$1, specifiedFormat || config.numberFormat);
5855    }
5856    function binFormatExpression(startField, endField, format$$1, config) {
5857        return `${startField} === null || isNaN(${startField}) ? "null" : ${numberFormatExpr(startField, format$$1, config)} + " - " + ${numberFormatExpr(endField, format$$1, config)}`;
5858    }
5859    /**
5860     * Returns the time expression used for axis/legend labels or text mark for a temporal field
5861     */
5862    function timeFormatExpression(field$$1, timeUnit, format$$1, shortTimeLabels, rawTimeFormat, // should be provided only for actual text and headers, not axis/legend labels
5863    isUTCScale, alwaysReturn = false) {
5864        if (!timeUnit || format$$1) {
5865            // If there is not time unit, or if user explicitly specify format for axis/legend/text.
5866            format$$1 = format$$1 || rawTimeFormat; // only use provided timeFormat if there is no timeUnit.
5867            if (format$$1 || alwaysReturn) {
5868                return `${isUTCScale ? 'utc' : 'time'}Format(${field$$1}, '${format$$1}')`;
5869            }
5870            else {
5871                return undefined;
5872            }
5873        }
5874        else {
5875            return formatExpression(timeUnit, field$$1, shortTimeLabels, isUTCScale);
5876        }
5877    }
5878    /**
5879     * Return Vega sort parameters (tuple of field and order).
5880     */
5881    function sortParams(orderDef, fieldRefOption) {
5882        return (isArray(orderDef) ? orderDef : [orderDef]).reduce((s, orderChannelDef) => {
5883            s.field.push(vgField(orderChannelDef, fieldRefOption));
5884            s.order.push(orderChannelDef.sort || 'ascending');
5885            return s;
5886        }, { field: [], order: [] });
5887    }
5888    function mergeTitleFieldDefs(f1, f2) {
5889        const merged = [...f1];
5890        f2.forEach(fdToMerge => {
5891            for (const fieldDef1 of merged) {
5892                // If already exists, no need to append to merged array
5893                if (stringify(fieldDef1) === stringify(fdToMerge)) {
5894                    return;
5895                }
5896            }
5897            merged.push(fdToMerge);
5898        });
5899        return merged;
5900    }
5901    function mergeTitle(title1, title2) {
5902        if (title1 === title2 || !title2) {
5903            // if titles are the same or title2 is falsy
5904            return title1;
5905        }
5906        else if (!title1) {
5907            // if title1 is falsy
5908            return title2;
5909        }
5910        else {
5911            // join title with comma if they are different
5912            return title1 + ', ' + title2;
5913        }
5914    }
5915    function mergeTitleComponent(v1, v2) {
5916        if (isArray(v1.value) && isArray(v2.value)) {
5917            return {
5918                explicit: v1.explicit,
5919                value: mergeTitleFieldDefs(v1.value, v2.value)
5920            };
5921        }
5922        else if (!isArray(v1.value) && !isArray(v2.value)) {
5923            return {
5924                explicit: v1.explicit,
5925                value: mergeTitle(v1.value, v2.value)
5926            };
5927        }
5928        /* istanbul ignore next: Condition should not happen -- only for warning in development. */
5929        throw new Error('It should never reach here');
5930    }
5931
5932    /**
5933     * A node in the dataflow tree.
5934     */
5935    class DataFlowNode {
5936        constructor(parent, debugName) {
5937            this.debugName = debugName;
5938            this._children = [];
5939            this._parent = null;
5940            if (parent) {
5941                this.parent = parent;
5942            }
5943        }
5944        /**
5945         * Clone this node with a deep copy but don't clone links to children or parents.
5946         */
5947        clone() {
5948            throw new Error('Cannot clone node');
5949        }
5950        /**
5951         * Return a hash of the node.
5952         */
5953        hash() {
5954            if (this._hash === undefined) {
5955                this._hash = uniqueId();
5956            }
5957            return this._hash;
5958        }
5959        /**
5960         * Set of fields that are being created by this node.
5961         */
5962        producedFields() {
5963            return new Set();
5964        }
5965        dependentFields() {
5966            return new Set();
5967        }
5968        get parent() {
5969            return this._parent;
5970        }
5971        /**
5972         * Set the parent of the node and also add this not to the parent's children.
5973         */
5974        set parent(parent) {
5975            this._parent = parent;
5976            parent.addChild(this);
5977        }
5978        get children() {
5979            return this._children;
5980        }
5981        numChildren() {
5982            return this._children.length;
5983        }
5984        addChild(child, loc) {
5985            // do not add the same child twice
5986            if (this._children.indexOf(child) > -1) {
5987                console.warn('Attempt to add the same child twice.');
5988                return;
5989            }
5990            if (loc !== undefined) {
5991                this._children.splice(loc, 0, child);
5992            }
5993            else {
5994                this._children.push(child);
5995            }
5996        }
5997        removeChild(oldChild) {
5998            const loc = this._children.indexOf(oldChild);
5999            this._children.splice(loc, 1);
6000            return loc;
6001        }
6002        /**
6003         * Remove node from the dataflow.
6004         */
6005        remove() {
6006            let loc = this._parent.removeChild(this);
6007            for (const child of this._children) {
6008                // do not use the set method because we want to insert at a particular location
6009                child._parent = this._parent;
6010                this._parent.addChild(child, loc++);
6011            }
6012        }
6013        /**
6014         * Insert another node as a parent of this node.
6015         */
6016        insertAsParentOf(other) {
6017            const parent = other.parent;
6018            parent.removeChild(this);
6019            this.parent = parent;
6020            other.parent = this;
6021        }
6022        swapWithParent() {
6023            const parent = this._parent;
6024            const newParent = parent.parent;
6025            // reconnect the children
6026            for (const child of this._children) {
6027                child.parent = parent;
6028            }
6029            // remove old links
6030            this._children = []; // equivalent to removing every child link one by one
6031            parent.removeChild(this);
6032            parent.parent.removeChild(parent);
6033            // swap two nodes
6034            this.parent = newParent;
6035            parent.parent = this;
6036        }
6037    }
6038    class OutputNode extends DataFlowNode {
6039        /**
6040         * @param source The name of the source. Will change in assemble.
6041         * @param type The type of the output node.
6042         * @param refCounts A global ref counter map.
6043         */
6044        constructor(parent, source, type, refCounts) {
6045            super(parent, source);
6046            this.type = type;
6047            this.refCounts = refCounts;
6048            this._source = this._name = source;
6049            if (this.refCounts && !(this._name in this.refCounts)) {
6050                this.refCounts[this._name] = 0;
6051            }
6052        }
6053        clone() {
6054            const cloneObj = new this.constructor();
6055            cloneObj.debugName = 'clone_' + this.debugName;
6056            cloneObj._source = this._source;
6057            cloneObj._name = 'clone_' + this._name;
6058            cloneObj.type = this.type;
6059            cloneObj.refCounts = this.refCounts;
6060            cloneObj.refCounts[cloneObj._name] = 0;
6061            return cloneObj;
6062        }
6063        /**
6064         * Request the datasource name and increase the ref counter.
6065         *
6066         * During the parsing phase, this will return the simple name such as 'main' or 'raw'.
6067         * It is crucial to request the name from an output node to mark it as a required node.
6068         * If nobody ever requests the name, this datasource will not be instantiated in the assemble phase.
6069         *
6070         * In the assemble phase, this will return the correct name.
6071         */
6072        getSource() {
6073            this.refCounts[this._name]++;
6074            return this._source;
6075        }
6076        isRequired() {
6077            return !!this.refCounts[this._name];
6078        }
6079        setSource(source) {
6080            this._source = source;
6081        }
6082    }
6083
6084    function ASTNode(type) {
6085      this.type = type;
6086    }
6087
6088    ASTNode.prototype.visit = function(visitor) {
6089      var node = this, c, i, n;
6090
6091      if (visitor(node)) return 1;
6092
6093      for (c=children(node), i=0, n=c.length; i<n; ++i) {
6094        if (c[i].visit(visitor)) return 1;
6095      }
6096    };
6097
6098    function children(node) {
6099      switch (node.type) {
6100        case 'ArrayExpression':
6101          return node.elements;
6102        case 'BinaryExpression':
6103        case 'LogicalExpression':
6104          return [node.left, node.right];
6105        case 'CallExpression':
6106          var args = node.arguments.slice();
6107          args.unshift(node.callee);
6108          return args;
6109        case 'ConditionalExpression':
6110          return [node.test, node.consequent, node.alternate];
6111        case 'MemberExpression':
6112          return [node.object, node.property];
6113        case 'ObjectExpression':
6114          return node.properties;
6115        case 'Property':
6116          return [node.key, node.value];
6117        case 'UnaryExpression':
6118          return [node.argument];
6119        case 'Identifier':
6120        case 'Literal':
6121        case 'RawCode':
6122        default:
6123          return [];
6124      }
6125    }
6126
6127    /*
6128      The following expression parser is based on Esprima (http://esprima.org/).
6129      Original header comment and license for Esprima is included here:
6130
6131      Copyright (C) 2013 Ariya Hidayat <[email protected]>
6132      Copyright (C) 2013 Thaddee Tyl <[email protected]>
6133      Copyright (C) 2013 Mathias Bynens <[email protected]>
6134      Copyright (C) 2012 Ariya Hidayat <[email protected]>
6135      Copyright (C) 2012 Mathias Bynens <[email protected]>
6136      Copyright (C) 2012 Joost-Wim Boekesteijn <[email protected]>
6137      Copyright (C) 2012 Kris Kowal <[email protected]>
6138      Copyright (C) 2012 Yusuke Suzuki <[email protected]>
6139      Copyright (C) 2012 Arpad Borsos <[email protected]>
6140      Copyright (C) 2011 Ariya Hidayat <[email protected]>
6141
6142      Redistribution and use in source and binary forms, with or without
6143      modification, are permitted provided that the following conditions are met:
6144
6145        * Redistributions of source code must retain the above copyright
6146          notice, this list of conditions and the following disclaimer.
6147        * Redistributions in binary form must reproduce the above copyright
6148          notice, this list of conditions and the following disclaimer in the
6149          documentation and/or other materials provided with the distribution.
6150
6151      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
6152      AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
6153      IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
6154      ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
6155      DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
6156      (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
6157      LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
6158      ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
6159      (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
6160      THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
6161    */
6162
6163    var TokenName,
6164        source,
6165        index$2,
6166        length,
6167        lookahead;
6168
6169    var TokenBooleanLiteral = 1,
6170        TokenEOF = 2,
6171        TokenIdentifier = 3,
6172        TokenKeyword = 4,
6173        TokenNullLiteral = 5,
6174        TokenNumericLiteral = 6,
6175        TokenPunctuator = 7,
6176        TokenStringLiteral = 8,
6177        TokenRegularExpression = 9;
6178
6179    TokenName = {};
6180    TokenName[TokenBooleanLiteral] = 'Boolean';
6181    TokenName[TokenEOF] = '<end>';
6182    TokenName[TokenIdentifier] = 'Identifier';
6183    TokenName[TokenKeyword] = 'Keyword';
6184    TokenName[TokenNullLiteral] = 'Null';
6185    TokenName[TokenNumericLiteral] = 'Numeric';
6186    TokenName[TokenPunctuator] = 'Punctuator';
6187    TokenName[TokenStringLiteral] = 'String';
6188    TokenName[TokenRegularExpression] = 'RegularExpression';
6189
6190    var SyntaxArrayExpression = 'ArrayExpression',
6191        SyntaxBinaryExpression = 'BinaryExpression',
6192        SyntaxCallExpression = 'CallExpression',
6193        SyntaxConditionalExpression = 'ConditionalExpression',
6194        SyntaxIdentifier = 'Identifier',
6195        SyntaxLiteral = 'Literal',
6196        SyntaxLogicalExpression = 'LogicalExpression',
6197        SyntaxMemberExpression = 'MemberExpression',
6198        SyntaxObjectExpression = 'ObjectExpression',
6199        SyntaxProperty = 'Property',
6200        SyntaxUnaryExpression = 'UnaryExpression';
6201
6202    // Error messages should be identical to V8.
6203    var MessageUnexpectedToken = 'Unexpected token %0',
6204        MessageUnexpectedNumber = 'Unexpected number',
6205        MessageUnexpectedString = 'Unexpected string',
6206        MessageUnexpectedIdentifier = 'Unexpected identifier',
6207        MessageUnexpectedReserved = 'Unexpected reserved word',
6208        MessageUnexpectedEOS = 'Unexpected end of input',
6209        MessageInvalidRegExp = 'Invalid regular expression',
6210        MessageUnterminatedRegExp = 'Invalid regular expression: missing /',
6211        MessageStrictOctalLiteral = 'Octal literals are not allowed in strict mode.',
6212        MessageStrictDuplicateProperty = 'Duplicate data property in object literal not allowed in strict mode';
6213
6214    var ILLEGAL = 'ILLEGAL',
6215        DISABLED = 'Disabled.';
6216
6217    // See also tools/generate-unicode-regex.py.
6218    var RegexNonAsciiIdentifierStart = new RegExp('[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B2\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u
62180EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]'),
6219        RegexNonAsciiIdentifierPart = new RegExp('[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u08A0-\u08B2\u08E4-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58\u0C59\u0C60-\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D01-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D57\u0D60-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1CD0-\u1CD2\u1CD4-\u1CF6\u1CF8\u1CF9\u1D00-\u1DF5\u1DFC-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200C\u200D\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099\u309A\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA69D\uA69F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA827\uA840-\uA873\uA880-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFB
6219D3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2D\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]');
6220
6221    // Ensure the condition is true, otherwise throw an error.
6222    // This is only to have a better contract semantic, i.e. another safety net
6223    // to catch a logic error. The condition shall be fulfilled in normal case.
6224    // Do NOT use this to enforce a certain condition on any user input.
6225
6226    function assert(condition, message) {
6227      /* istanbul ignore next */
6228      if (!condition) {
6229        throw new Error('ASSERT: ' + message);
6230      }
6231    }
6232
6233    function isDecimalDigit(ch) {
6234      return (ch >= 0x30 && ch <= 0x39); // 0..9
6235    }
6236
6237    function isHexDigit(ch) {
6238      return '0123456789abcdefABCDEF'.indexOf(ch) >= 0;
6239    }
6240
6241    function isOctalDigit(ch) {
6242      return '01234567'.indexOf(ch) >= 0;
6243    }
6244
6245    // 7.2 White Space
6246
6247    function isWhiteSpace(ch) {
6248      return (ch === 0x20) || (ch === 0x09) || (ch === 0x0B) || (ch === 0x0C) || (ch === 0xA0) ||
6249        (ch >= 0x1680 && [0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF].indexOf(ch) >= 0);
6250    }
6251
6252    // 7.3 Line Terminators
6253
6254    function isLineTerminator(ch) {
6255      return (ch === 0x0A) || (ch === 0x0D) || (ch === 0x2028) || (ch === 0x2029);
6256    }
6257
6258    // 7.6 Identifier Names and Identifiers
6259
6260    function isIdentifierStart(ch) {
6261      return (ch === 0x24) || (ch === 0x5F) || // $ (dollar) and _ (underscore)
6262        (ch >= 0x41 && ch <= 0x5A) || // A..Z
6263        (ch >= 0x61 && ch <= 0x7A) || // a..z
6264        (ch === 0x5C) || // \ (backslash)
6265        ((ch >= 0x80) && RegexNonAsciiIdentifierStart.test(String.fromCharCode(ch)));
6266    }
6267
6268    function isIdentifierPart(ch) {
6269      return (ch === 0x24) || (ch === 0x5F) || // $ (dollar) and _ (underscore)
6270        (ch >= 0x41 && ch <= 0x5A) || // A..Z
6271        (ch >= 0x61 && ch <= 0x7A) || // a..z
6272        (ch >= 0x30 && ch <= 0x39) || // 0..9
6273        (ch === 0x5C) || // \ (backslash)
6274        ((ch >= 0x80) && RegexNonAsciiIdentifierPart.test(String.fromCharCode(ch)));
6275    }
6276
6277    // 7.6.1.1 Keywords
6278
6279    var keywords = {
6280      'if':1, 'in':1, 'do':1,
6281      'var':1, 'for':1, 'new':1, 'try':1, 'let':1,
6282      'this':1, 'else':1, 'case':1, 'void':1, 'with':1, 'enum':1,
6283      'while':1, 'break':1, 'catch':1, 'throw':1, 'const':1, 'yield':1, 'class':1, 'super':1,
6284      'return':1, 'typeof':1, 'delete':1, 'switch':1, 'export':1, 'import':1, 'public':1, 'static':1,
6285      'default':1, 'finally':1, 'extends':1, 'package':1, 'private':1,
6286      'function':1, 'continue':1, 'debugger':1,
6287      'interface':1, 'protected':1,
6288      'instanceof':1, 'implements':1
6289    };
6290
6291    function skipComment() {
6292      var ch;
6293
6294      while (index$2 < length) {
6295        ch = source.charCodeAt(index$2);
6296
6297        if (isWhiteSpace(ch) || isLineTerminator(ch)) {
6298          ++index$2;
6299        } else {
6300          break;
6301        }
6302      }
6303    }
6304
6305    function scanHexEscape(prefix) {
6306      var i, len, ch, code = 0;
6307
6308      len = (prefix === 'u') ? 4 : 2;
6309      for (i = 0; i < len; ++i) {
6310        if (index$2 < length && isHexDigit(source[index$2])) {
6311          ch = source[index$2++];
6312          code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase());
6313        } else {
6314          throwError({}, MessageUnexpectedToken, ILLEGAL);
6315        }
6316      }
6317      return String.fromCharCode(code);
6318    }
6319
6320    function scanUnicodeCodePointEscape() {
6321      var ch, code, cu1, cu2;
6322
6323      ch = source[index$2];
6324      code = 0;
6325
6326      // At least, one hex digit is required.
6327      if (ch === '}') {
6328        throwError({}, MessageUnexpectedToken, ILLEGAL);
6329      }
6330
6331      while (index$2 < length) {
6332        ch = source[index$2++];
6333        if (!isHexDigit(ch)) {
6334          break;
6335        }
6336        code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase());
6337      }
6338
6339      if (code > 0x10FFFF || ch !== '}') {
6340        throwError({}, MessageUnexpectedToken, ILLEGAL);
6341      }
6342
6343      // UTF-16 Encoding
6344      if (code <= 0xFFFF) {
6345        return String.fromCharCode(code);
6346      }
6347      cu1 = ((code - 0x10000) >> 10) + 0xD800;
6348      cu2 = ((code - 0x10000) & 1023) + 0xDC00;
6349      return String.fromCharCode(cu1, cu2);
6350    }
6351
6352    function getEscapedIdentifier() {
6353      var ch, id;
6354
6355      ch = source.charCodeAt(index$2++);
6356      id = String.fromCharCode(ch);
6357
6358      // '\u' (U+005C, U+0075) denotes an escaped character.
6359      if (ch === 0x5C) {
6360        if (source.charCodeAt(index$2) !== 0x75) {
6361          throwError({}, MessageUnexpectedToken, ILLEGAL);
6362        }
6363        ++index$2;
6364        ch = scanHexEscape('u');
6365        if (!ch || ch === '\\' || !isIdentifierStart(ch.charCodeAt(0))) {
6366          throwError({}, MessageUnexpectedToken, ILLEGAL);
6367        }
6368        id = ch;
6369      }
6370
6371      while (index$2 < length) {
6372        ch = source.charCodeAt(index$2);
6373        if (!isIdentifierPart(ch)) {
6374          break;
6375        }
6376        ++index$2;
6377        id += String.fromCharCode(ch);
6378
6379        // '\u' (U+005C, U+0075) denotes an escaped character.
6380        if (ch === 0x5C) {
6381          id = id.substr(0, id.length - 1);
6382          if (source.charCodeAt(index$2) !== 0x75) {
6383            throwError({}, MessageUnexpectedToken, ILLEGAL);
6384          }
6385          ++index$2;
6386          ch = scanHexEscape('u');
6387          if (!ch || ch === '\\' || !isIdentifierPart(ch.charCodeAt(0))) {
6388            throwError({}, MessageUnexpectedToken, ILLEGAL);
6389          }
6390          id += ch;
6391        }
6392      }
6393
6394      return id;
6395    }
6396
6397    function getIdentifier() {
6398      var start, ch;
6399
6400      start = index$2++;
6401      while (index$2 < length) {
6402        ch = source.charCodeAt(index$2);
6403        if (ch === 0x5C) {
6404          // Blackslash (U+005C) marks Unicode escape sequence.
6405          index$2 = start;
6406          return getEscapedIdentifier();
6407        }
6408        if (isIdentifierPart(ch)) {
6409          ++index$2;
6410        } else {
6411          break;
6412        }
6413      }
6414
6415      return source.slice(start, index$2);
6416    }
6417
6418    function scanIdentifier() {
6419      var start, id, type;
6420
6421      start = index$2;
6422
6423      // Backslash (U+005C) starts an escaped character.
6424      id = (source.charCodeAt(index$2) === 0x5C) ? getEscapedIdentifier() : getIdentifier();
6425
6426      // There is no keyword or literal with only one character.
6427      // Thus, it must be an identifier.
6428      if (id.length === 1) {
6429        type = TokenIdentifier;
6430      } else if (keywords.hasOwnProperty(id)) {
6431        type = TokenKeyword;
6432      } else if (id === 'null') {
6433        type = TokenNullLiteral;
6434      } else if (id === 'true' || id === 'false') {
6435        type = TokenBooleanLiteral;
6436      } else {
6437        type = TokenIdentifier;
6438      }
6439
6440      return {
6441        type: type,
6442        value: id,
6443        start: start,
6444        end: index$2
6445      };
6446    }
6447
6448    // 7.7 Punctuators
6449
6450    function scanPunctuator() {
6451      var start = index$2,
6452        code = source.charCodeAt(index$2),
6453        code2,
6454        ch1 = source[index$2],
6455        ch2,
6456        ch3,
6457        ch4;
6458
6459      switch (code) {
6460
6461        // Check for most common single-character punctuators.
6462        case 0x2E: // . dot
6463        case 0x28: // ( open bracket
6464        case 0x29: // ) close bracket
6465        case 0x3B: // ; semicolon
6466        case 0x2C: // , comma
6467        case 0x7B: // { open curly brace
6468        case 0x7D: // } close curly brace
6469        case 0x5B: // [
6470        case 0x5D: // ]
6471        case 0x3A: // :
6472        case 0x3F: // ?
6473        case 0x7E: // ~
6474          ++index$2;
6475          return {
6476            type: TokenPunctuator,
6477            value: String.fromCharCode(code),
6478            start: start,
6479            end: index$2
6480          };
6481
6482        default:
6483          code2 = source.charCodeAt(index$2 + 1);
6484
6485          // '=' (U+003D) marks an assignment or comparison operator.
6486          if (code2 === 0x3D) {
6487            switch (code) {
6488              case 0x2B: // +
6489              case 0x2D: // -
6490              case 0x2F: // /
6491              case 0x3C: // <
6492              case 0x3E: // >
6493              case 0x5E: // ^
6494              case 0x7C: // |
6495              case 0x25: // %
6496              case 0x26: // &
6497              case 0x2A: // *
6498                index$2 += 2;
6499                return {
6500                  type: TokenPunctuator,
6501                  value: String.fromCharCode(code) + String.fromCharCode(code2),
6502                  start: start,
6503                  end: index$2
6504                };
6505
6506              case 0x21: // !
6507              case 0x3D: // =
6508                index$2 += 2;
6509
6510                // !== and ===
6511                if (source.charCodeAt(index$2) === 0x3D) {
6512                  ++index$2;
6513                }
6514                return {
6515                  type: TokenPunctuator,
6516                  value: source.slice(start, index$2),
6517                  start: start,
6518                  end: index$2
6519                };
6520            }
6521          }
6522      }
6523
6524      // 4-character punctuator: >>>=
6525
6526      ch4 = source.substr(index$2, 4);
6527
6528      if (ch4 === '>>>=') {
6529        index$2 += 4;
6530        return {
6531          type: TokenPunctuator,
6532          value: ch4,
6533          start: start,
6534          end: index$2
6535        };
6536      }
6537
6538      // 3-character punctuators: === !== >>> <<= >>=
6539
6540      ch3 = ch4.substr(0, 3);
6541
6542      if (ch3 === '>>>' || ch3 === '<<=' || ch3 === '>>=') {
6543        index$2 += 3;
6544        return {
6545          type: TokenPunctuator,
6546          value: ch3,
6547          start: start,
6548          end: index$2
6549        };
6550      }
6551
6552      // Other 2-character punctuators: ++ -- << >> && ||
6553      ch2 = ch3.substr(0, 2);
6554
6555      if ((ch1 === ch2[1] && ('+-<>&|'.indexOf(ch1) >= 0)) || ch2 === '=>') {
6556        index$2 += 2;
6557        return {
6558          type: TokenPunctuator,
6559          value: ch2,
6560          start: start,
6561          end: index$2
6562        };
6563      }
6564
6565      // 1-character punctuators: < > = ! + - * % & | ^ /
6566
6567      if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) {
6568        ++index$2;
6569        return {
6570          type: TokenPunctuator,
6571          value: ch1,
6572          start: start,
6573          end: index$2
6574        };
6575      }
6576
6577      throwError({}, MessageUnexpectedToken, ILLEGAL);
6578    }
6579
6580    // 7.8.3 Numeric Literals
6581
6582    function scanHexLiteral(start) {
6583      var number = '';
6584
vendor: 4,689 bytes, lines 6585-6761
6585      while (index$2 < length) {
6586        if (!isHexDigit(source[index$2])) {
6587          break;
6588        }
6589        number += source[index$2++];
6590      }
6591
6592      if (number.length === 0) {
6593        throwError({}, MessageUnexpectedToken, ILLEGAL);
6594      }
6595
6596      if (isIdentifierStart(source.charCodeAt(index$2))) {
6597        throwError({}, MessageUnexpectedToken, ILLEGAL);
6598      }
6599
6600      return {
6601        type: TokenNumericLiteral,
6602        value: parseInt('0x' + number, 16),
6603        start: start,
6604        end: index$2
6605      };
6606    }
6607
6608    function scanOctalLiteral(start) {
6609      var number = '0' + source[index$2++];
6610      while (index$2 < length) {
6611        if (!isOctalDigit(source[index$2])) {
6612          break;
6613        }
6614        number += source[index$2++];
6615      }
6616
6617      if (isIdentifierStart(source.charCodeAt(index$2)) || isDecimalDigit(source.charCodeAt(index$2))) {
6618        throwError({}, MessageUnexpectedToken, ILLEGAL);
6619      }
6620
6621      return {
6622        type: TokenNumericLiteral,
6623        value: parseInt(number, 8),
6624        octal: true,
6625        start: start,
6626        end: index$2
6627      };
6628    }
6629
6630    function scanNumericLiteral() {
6631      var number, start, ch;
6632
6633      ch = source[index$2];
6634      assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'),
6635        'Numeric literal must start with a decimal digit or a decimal point');
6636
6637      start = index$2;
6638      number = '';
6639      if (ch !== '.') {
6640        number = source[index$2++];
6641        ch = source[index$2];
6642
6643        // Hex number starts with '0x'.
6644        // Octal number starts with '0'.
6645        if (number === '0') {
6646          if (ch === 'x' || ch === 'X') {
6647            ++index$2;
6648            return scanHexLiteral(start);
6649          }
6650          if (isOctalDigit(ch)) {
6651            return scanOctalLiteral(start);
6652          }
6653
6654          // decimal number starts with '0' such as '09' is illegal.
6655          if (ch && isDecimalDigit(ch.charCodeAt(0))) {
6656            throwError({}, MessageUnexpectedToken, ILLEGAL);
6657          }
6658        }
6659
6660        while (isDecimalDigit(source.charCodeAt(index$2))) {
6661          number += source[index$2++];
6662        }
6663        ch = source[index$2];
6664      }
6665
6666      if (ch === '.') {
6667        number += source[index$2++];
6668        while (isDecimalDigit(source.charCodeAt(index$2))) {
6669          number += source[index$2++];
6670        }
6671        ch = source[index$2];
6672      }
6673
6674      if (ch === 'e' || ch === 'E') {
6675        number += source[index$2++];
6676
6677        ch = source[index$2];
6678        if (ch === '+' || ch === '-') {
6679          number += source[index$2++];
6680        }
6681        if (isDecimalDigit(source.charCodeAt(index$2))) {
6682          while (isDecimalDigit(source.charCodeAt(index$2))) {
6683            number += source[index$2++];
6684          }
6685        } else {
6686          throwError({}, MessageUnexpectedToken, ILLEGAL);
6687        }
6688      }
6689
6690      if (isIdentifierStart(source.charCodeAt(index$2))) {
6691        throwError({}, MessageUnexpectedToken, ILLEGAL);
6692      }
6693
6694      return {
6695        type: TokenNumericLiteral,
6696        value: parseFloat(number),
6697        start: start,
6698        end: index$2
6699      };
6700    }
6701
6702    // 7.8.4 String Literals
6703
6704    function scanStringLiteral() {
6705      var str = '',
6706        quote, start, ch, code, octal = false;
6707
6708      quote = source[index$2];
6709      assert((quote === '\'' || quote === '"'),
6710        'String literal must starts with a quote');
6711
6712      start = index$2;
6713      ++index$2;
6714
6715      while (index$2 < length) {
6716        ch = source[index$2++];
6717
6718        if (ch === quote) {
6719          quote = '';
6720          break;
6721        } else if (ch === '\\') {
6722          ch = source[index$2++];
6723          if (!ch || !isLineTerminator(ch.charCodeAt(0))) {
6724            switch (ch) {
6725              case 'u':
6726              case 'x':
6727                if (source[index$2] === '{') {
6728                  ++index$2;
6729                  str += scanUnicodeCodePointEscape();
6730                } else {
6731                  str += scanHexEscape(ch);
6732                }
6733                break;
6734              case 'n':
6735                str += '\n';
6736                break;
6737              case 'r':
6738                str += '\r';
6739                break;
6740              case 't':
6741                str += '\t';
6742                break;
6743              case 'b':
6744                str += '\b';
6745                break;
6746              case 'f':
6747                str += '\f';
6748                break;
6749              case 'v':
6750                str += '\x0B';
6751                break;
6752
6753              default:
6754                if (isOctalDigit(ch)) {
6755                  code = '01234567'.indexOf(ch);
6756
6757                  // \0 is not octal escape sequence
6758                  if (code !== 0) {
6759                    octal = true;
6760                  }
6761
6762                  if (index$2 < length && isOctalDigit(source[index$2])) {
6763                    octal = true;
6764                    code = code * 8 + '01234567'.indexOf(source[index$2++]);
6765
6766                    // 3 digits are only allowed when string starts
6767                    // with 0, 1, 2, 3
6768                    if ('0123'.indexOf(ch) >= 0 &&
6769                      index$2 < length &&
6770                      isOctalDigit(source[index$2])) {
6771                      code = code * 8 + '01234567'.indexOf(source[index$2++]);
6772                    }
6773                  }
6774                  str += String.fromCharCode(code);
6775                } else {
6776                  str += ch;
6777                }
6778                break;
6779            }
6780          } else {
6781            if (ch === '\r' && source[index$2] === '\n') {
6782              ++index$2;
6783            }
6784          }
6785        } else if (isLineTerminator(ch.charCodeAt(0))) {
6786          break;
6787        } else {
6788          str += ch;
6789        }
6790      }
6791
6792      if (quote !== '') {
6793        throwError({}, MessageUnexpectedToken, ILLEGAL);
6794      }
6795
6796      return {
6797        type: TokenStringLiteral,
6798        value: str,
6799        octal: octal,
6800        start: start,
6801        end: index$2
6802      };
6803    }
6804
6805    function testRegExp(pattern, flags) {
6806      var tmp = pattern;
6807
6808      if (flags.indexOf('u') >= 0) {
6809        // Replace each astral symbol and every Unicode code point
6810        // escape sequence with a single ASCII symbol to avoid throwing on
6811        // regular expressions that are only valid in combination with the
6812        // `/u` flag.
6813        // Note: replacing with the ASCII symbol `x` might cause false
6814        // negatives in unlikely scenarios. For example, `[\u{61}-b]` is a
6815        // perfectly valid pattern that is equivalent to `[a-b]`, but it
6816        // would be replaced by `[x-b]` which throws an error.
6817        tmp = tmp
6818          .replace(/\\u\{([0-9a-fA-F]+)\}/g, function($0, $1) {
6819            if (parseInt($1, 16) <= 0x10FFFF) {
6820              return 'x';
6821            }
6822            throwError({}, MessageInvalidRegExp);
6823          })
6824          .replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 'x');
6825      }
6826
6827      // First, detect invalid regular expressions.
6828      try {
6829      } catch (e) {
6830        throwError({}, MessageInvalidRegExp);
6831      }
6832
6833      // Return a regular expression object for this pattern-flag pair, or
6834      // `null` in case the current environment doesn't support the flags it
6835      // uses.
6836      try {
6837        return new RegExp(pattern, flags);
6838      } catch (exception) {
6839        return null;
6840      }
6841    }
6842
6843    function scanRegExpBody() {
6844      var ch, str, classMarker, terminated, body;
6845
6846      ch = source[index$2];
6847      assert(ch === '/', 'Regular expression literal must start with a slash');
6848      str = source[index$2++];
6849
6850      classMarker = false;
6851      terminated = false;
6852      while (index$2 < length) {
6853        ch = source[index$2++];
6854        str += ch;
6855        if (ch === '\\') {
6856          ch = source[index$2++];
6857          // ECMA-262 7.8.5
6858          if (isLineTerminator(ch.charCodeAt(0))) {
6859            throwError({}, MessageUnterminatedRegExp);
6860          }
6861          str += ch;
6862        } else if (isLineTerminator(ch.charCodeAt(0))) {
6863          throwError({}, MessageUnterminatedRegExp);
6864        } else if (classMarker) {
6865          if (ch === ']') {
6866            classMarker = false;
6867          }
6868        } else {
6869          if (ch === '/') {
6870            terminated = true;
6871            break;
6872          } else if (ch === '[') {
6873            classMarker = true;
6874          }
6875        }
6876      }
6877
6878      if (!terminated) {
6879        throwError({}, MessageUnterminatedRegExp);
6880      }
6881
6882      // Exclude leading and trailing slash.
6883      body = str.substr(1, str.length - 2);
6884      return {
6885        value: body,
6886        literal: str
6887      };
6888    }
6889
6890    function scanRegExpFlags() {
6891      var ch, str, flags;
6892
6893      str = '';
6894      flags = '';
6895      while (index$2 < length) {
6896        ch = source[index$2];
6897        if (!isIdentifierPart(ch.charCodeAt(0))) {
6898          break;
6899        }
6900
6901        ++index$2;
6902        if (ch === '\\' && index$2 < length) {
6903          throwError({}, MessageUnexpectedToken, ILLEGAL);
6904        } else {
6905          flags += ch;
6906          str += ch;
6907        }
6908      }
6909
6910      if (flags.search(/[^gimuy]/g) >= 0) {
6911        throwError({}, MessageInvalidRegExp, flags);
6912      }
6913
6914      return {
6915        value: flags,
6916        literal: str
6917      };
6918    }
6919
6920    function scanRegExp() {
6921      var start, body, flags, value;
6922
6923      lookahead = null;
6924      skipComment();
6925      start = index$2;
6926
6927      body = scanRegExpBody();
6928      flags = scanRegExpFlags();
6929      value = testRegExp(body.value, flags.value);
6930
6931      return {
6932        literal: body.literal + flags.literal,
6933        value: value,
6934        regex: {
6935          pattern: body.value,
6936          flags: flags.value
6937        },
6938        start: start,
6939        end: index$2
6940      };
6941    }
6942
6943    function isIdentifierName(token) {
6944      return token.type === TokenIdentifier ||
6945        token.type === TokenKeyword ||
6946        token.type === TokenBooleanLiteral ||
6947        token.type === TokenNullLiteral;
6948    }
6949
6950    function advance() {
6951      var ch;
6952
6953      skipComment();
6954
6955      if (index$2 >= length) {
6956        return {
6957          type: TokenEOF,
6958          start: index$2,
6959          end: index$2
6960        };
6961      }
6962
6963      ch = source.charCodeAt(index$2);
6964
6965      if (isIdentifierStart(ch)) {
6966        return scanIdentifier();
6967      }
6968
6969      // Very common: ( and ) and ;
6970      if (ch === 0x28 || ch === 0x29 || ch === 0x3B) {
6971        return scanPunctuator();
6972      }
6973
6974      // String literal starts with single quote (U+0027) or double quote (U+0022).
6975      if (ch === 0x27 || ch === 0x22) {
6976        return scanStringLiteral();
6977      }
6978
6979
6980      // Dot (.) U+002E can also start a floating-point number, hence the need
6981      // to check the next character.
6982      if (ch === 0x2E) {
6983        if (isDecimalDigit(source.charCodeAt(index$2 + 1))) {
6984          return scanNumericLiteral();
6985        }
6986        return scanPunctuator();
6987      }
6988
6989      if (isDecimalDigit(ch)) {
6990        return scanNumericLiteral();
6991      }
6992
6993      return scanPunctuator();
6994    }
6995
6996    function lex() {
6997      var token;
6998
6999      token = lookahead;
7000      index$2 = token.end;
7001
7002      lookahead = advance();
7003
7004      index$2 = token.end;
7005
7006      return token;
7007    }
7008
7009    function peek$1() {
7010      var pos;
7011
7012      pos = index$2;
7013
7014      lookahead = advance();
7015      index$2 = pos;
7016    }
7017
7018    function finishArrayExpression(elements) {
7019      var node = new ASTNode(SyntaxArrayExpression);
7020      node.elements = elements;
7021      return node;
7022    }
7023
7024    function finishBinaryExpression(operator, left, right) {
7025      var node = new ASTNode((operator === '||' || operator === '&&') ? SyntaxLogicalExpression : SyntaxBinaryExpression);
7026      node.operator = operator;
7027      node.left = left;
7028      node.right = right;
7029      return node;
7030    }
7031
vendor: 5,759 bytes, lines 7032-7251
7032    function finishCallExpression(callee, args) {
7033      var node = new ASTNode(SyntaxCallExpression);
7034      node.callee = callee;
7035      node.arguments = args;
7036      return node;
7037    }
7038
7039    function finishConditionalExpression(test, consequent, alternate) {
7040      var node = new ASTNode(SyntaxConditionalExpression);
7041      node.test = test;
7042      node.consequent = consequent;
7043      node.alternate = alternate;
7044      return node;
7045    }
7046
7047    function finishIdentifier(name) {
7048      var node = new ASTNode(SyntaxIdentifier);
7049      node.name = name;
7050      return node;
7051    }
7052
7053    function finishLiteral(token) {
7054      var node = new ASTNode(SyntaxLiteral);
7055      node.value = token.value;
7056      node.raw = source.slice(token.start, token.end);
7057      if (token.regex) {
7058        if (node.raw === '//') {
7059          node.raw = '/(?:)/';
7060        }
7061        node.regex = token.regex;
7062      }
7063      return node;
7064    }
7065
7066    function finishMemberExpression(accessor, object, property) {
7067      var node = new ASTNode(SyntaxMemberExpression);
7068      node.computed = accessor === '[';
7069      node.object = object;
7070      node.property = property;
7071      if (!node.computed) property.member = true;
7072      return node;
7073    }
7074
7075    function finishObjectExpression(properties) {
7076      var node = new ASTNode(SyntaxObjectExpression);
7077      node.properties = properties;
7078      return node;
7079    }
7080
7081    function finishProperty(kind, key, value) {
7082      var node = new ASTNode(SyntaxProperty);
7083      node.key = key;
7084      node.value = value;
7085      node.kind = kind;
7086      return node;
7087    }
7088
7089    function finishUnaryExpression(operator, argument) {
7090      var node = new ASTNode(SyntaxUnaryExpression);
7091      node.operator = operator;
7092      node.argument = argument;
7093      node.prefix = true;
7094      return node;
7095    }
7096
7097    // Throw an exception
7098
7099    function throwError(token, messageFormat) {
7100      var error,
7101        args = Array.prototype.slice.call(arguments, 2),
7102        msg = messageFormat.replace(
7103          /%(\d)/g,
7104          function(whole, index) {
7105            assert(index < args.length, 'Message reference must be in range');
7106            return args[index];
7107          }
7108        );
7109
7110
7111      error = new Error(msg);
7112      error.index = index$2;
7113      error.description = msg;
7114      throw error;
7115    }
7116
7117    // Throw an exception because of the token.
7118
7119    function throwUnexpected(token) {
7120      if (token.type === TokenEOF) {
7121        throwError(token, MessageUnexpectedEOS);
7122      }
7123
7124      if (token.type === TokenNumericLiteral) {
7125        throwError(token, MessageUnexpectedNumber);
7126      }
7127
7128      if (token.type === TokenStringLiteral) {
7129        throwError(token, MessageUnexpectedString);
7130      }
7131
7132      if (token.type === TokenIdentifier) {
7133        throwError(token, MessageUnexpectedIdentifier);
7134      }
7135
7136      if (token.type === TokenKeyword) {
7137        throwError(token, MessageUnexpectedReserved);
7138      }
7139
7140      // BooleanLiteral, NullLiteral, or Punctuator.
7141      throwError(token, MessageUnexpectedToken, token.value);
7142    }
7143
7144    // Expect the next token to match the specified punctuator.
7145    // If not, an exception will be thrown.
7146
7147    function expect(value) {
7148      var token = lex();
7149      if (token.type !== TokenPunctuator || token.value !== value) {
7150        throwUnexpected(token);
7151      }
7152    }
7153
7154    // Return true if the next token matches the specified punctuator.
7155
7156    function match(value) {
7157      return lookahead.type === TokenPunctuator && lookahead.value === value;
7158    }
7159
7160    // Return true if the next token matches the specified keyword
7161
7162    function matchKeyword(keyword) {
7163      return lookahead.type === TokenKeyword && lookahead.value === keyword;
7164    }
7165
7166    // 11.1.4 Array Initialiser
7167
7168    function parseArrayInitialiser() {
7169      var elements = [];
7170
7171      index$2 = lookahead.start;
7172      expect('[');
7173
7174      while (!match(']')) {
7175        if (match(',')) {
7176          lex();
7177          elements.push(null);
7178        } else {
7179          elements.push(parseConditionalExpression());
7180
7181          if (!match(']')) {
7182            expect(',');
7183          }
7184        }
7185      }
7186
7187      lex();
7188
7189      return finishArrayExpression(elements);
7190    }
7191
7192    // 11.1.5 Object Initialiser
7193
7194    function parseObjectPropertyKey() {
7195      var token;
7196
7197      index$2 = lookahead.start;
7198      token = lex();
7199
7200      // Note: This function is called only from parseObjectProperty(), where
7201      // EOF and Punctuator tokens are already filtered out.
7202
7203      if (token.type === TokenStringLiteral || token.type === TokenNumericLiteral) {
7204        if (token.octal) {
7205          throwError(token, MessageStrictOctalLiteral);
7206        }
7207        return finishLiteral(token);
7208      }
7209
7210      return finishIdentifier(token.value);
7211    }
7212
7213    function parseObjectProperty() {
7214      var token, key, id, value;
7215
7216      index$2 = lookahead.start;
7217      token = lookahead;
7218
7219      if (token.type === TokenIdentifier) {
7220        id = parseObjectPropertyKey();
7221        expect(':');
7222        value = parseConditionalExpression();
7223        return finishProperty('init', id, value);
7224      }
7225      if (token.type === TokenEOF || token.type === TokenPunctuator) {
7226        throwUnexpected(token);
7227      } else {
7228        key = parseObjectPropertyKey();
7229        expect(':');
7230        value = parseConditionalExpression();
7231        return finishProperty('init', key, value);
7232      }
7233    }
7234
7235    function parseObjectInitialiser() {
7236      var properties = [],
7237        property, name, key, map = {},
7238        toString = String;
7239
7240      index$2 = lookahead.start;
7241      expect('{');
7242
7243      while (!match('}')) {
7244        property = parseObjectProperty();
7245
7246        if (property.key.type === SyntaxIdentifier) {
7247          name = property.key.name;
7248        } else {
7249          name = toString(property.key.value);
7250        }
7251
7252        key = '$' + name;
7253        if (Object.prototype.hasOwnProperty.call(map, key)) {
7254          throwError({}, MessageStrictDuplicateProperty);
7255        } else {
7256          map[key] = true;
7257        }
7258
7259        properties.push(property);
7260
7261        if (!match('}')) {
7262          expect(',');
7263        }
7264      }
7265
7266      expect('}');
7267
7268      return finishObjectExpression(properties);
7269    }
7270
7271    // 11.1.6 The Grouping Operator
7272
7273    function parseGroupExpression() {
7274      var expr;
7275
7276      expect('(');
7277
7278      expr = parseExpression();
7279
7280      expect(')');
7281
7282      return expr;
7283    }
7284
7285
7286    // 11.1 Primary Expressions
7287
7288    var legalKeywords = {
7289      "if": 1,
7290      "this": 1
7291    };
7292
7293    function parsePrimaryExpression() {
7294      var type, token, expr;
7295
7296      if (match('(')) {
7297        return parseGroupExpression();
7298      }
7299
7300      if (match('[')) {
7301        return parseArrayInitialiser();
7302      }
7303
7304      if (match('{')) {
7305        return parseObjectInitialiser();
7306      }
7307
7308      type = lookahead.type;
7309      index$2 = lookahead.start;
7310
7311
7312      if (type === TokenIdentifier || legalKeywords[lookahead.value]) {
7313        expr = finishIdentifier(lex().value);
7314      } else if (type === TokenStringLiteral || type === TokenNumericLiteral) {
7315        if (lookahead.octal) {
7316          throwError(lookahead, MessageStrictOctalLiteral);
7317        }
7318        expr = finishLiteral(lex());
7319      } else if (type === TokenKeyword) {
7320        throw new Error(DISABLED);
7321      } else if (type === TokenBooleanLiteral) {
7322        token = lex();
7323        token.value = (token.value === 'true');
7324        expr = finishLiteral(token);
7325      } else if (type === TokenNullLiteral) {
7326        token = lex();
7327        token.value = null;
7328        expr = finishLiteral(token);
7329      } else if (match('/') || match('/=')) {
7330        expr = finishLiteral(scanRegExp());
7331        peek$1();
7332      } else {
7333        throwUnexpected(lex());
7334      }
7335
7336      return expr;
7337    }
7338
7339    // 11.2 Left-Hand-Side Expressions
7340
7341    function parseArguments() {
7342      var args = [];
7343
7344      expect('(');
7345
7346      if (!match(')')) {
7347        while (index$2 < length) {
7348          args.push(parseConditionalExpression());
7349          if (match(')')) {
7350            break;
7351          }
7352          expect(',');
7353        }
7354      }
7355
7356      expect(')');
7357
7358      return args;
7359    }
7360
7361    function parseNonComputedProperty() {
7362      var token;
7363      index$2 = lookahead.start;
7364      token = lex();
7365
7366      if (!isIdentifierName(token)) {
7367        throwUnexpected(token);
7368      }
7369
7370      return finishIdentifier(token.value);
7371    }
7372
7373    function parseNonComputedMember() {
7374      expect('.');
7375
7376      return parseNonComputedProperty();
7377    }
7378
7379    function parseComputedMember() {
7380      var expr;
7381
7382      expect('[');
7383
7384      expr = parseExpression();
7385
7386      expect(']');
7387
7388      return expr;
7389    }
7390
7391    function parseLeftHandSideExpressionAllowCall() {
7392      var expr, args, property;
7393
7394      expr = parsePrimaryExpression();
7395
7396      for (;;) {
7397        if (match('.')) {
7398          property = parseNonComputedMember();
7399          expr = finishMemberExpression('.', expr, property);
7400        } else if (match('(')) {
7401          args = parseArguments();
7402          expr = finishCallExpression(expr, args);
7403        } else if (match('[')) {
7404          property = parseComputedMember();
7405          expr = finishMemberExpression('[', expr, property);
7406        } else {
7407          break;
7408        }
7409      }
7410
7411      return expr;
7412    }
7413
7414    // 11.3 Postfix Expressions
7415
7416    function parsePostfixExpression() {
7417      var expr = parseLeftHandSideExpressionAllowCall();
7418
7419      if (lookahead.type === TokenPunctuator) {
7420        if ((match('++') || match('--'))) {
7421          throw new Error(DISABLED);
7422        }
7423      }
7424
7425      return expr;
7426    }
7427
7428    // 11.4 Unary Operators
7429
7430    function parseUnaryExpression() {
7431      var token, expr;
7432
7433      if (lookahead.type !== TokenPunctuator && lookahead.type !== TokenKeyword) {
7434        expr = parsePostfixExpression();
7435      } else if (match('++') || match('--')) {
7436        throw new Error(DISABLED);
7437      } else if (match('+') || match('-') || match('~') || match('!')) {
7438        token = lex();
7439        expr = parseUnaryExpression();
7440        expr = finishUnaryExpression(token.value, expr);
7441      } else if (matchKeyword('delete') || matchKeyword('void') || matchKeyword('typeof')) {
7442        throw new Error(DISABLED);
7443      } else {
7444        expr = parsePostfixExpression();
7445      }
7446
7447      return expr;
7448    }
7449
7450    function binaryPrecedence(token) {
7451      var prec = 0;
7452
7453      if (token.type !== TokenPunctuator && token.type !== TokenKeyword) {
7454        return 0;
7455      }
7456
7457      switch (token.value) {
7458        case '||':
7459          prec = 1;
7460          break;
7461
7462        case '&&':
7463          prec = 2;
7464          break;
7465
7466        case '|':
7467          prec = 3;
7468          break;
7469
7470        case '^':
7471          prec = 4;
7472          break;
7473
7474        case '&':
7475          prec = 5;
7476          break;
7477
7478        case '==':
7479        case '!=':
7480        case '===':
7481        case '!==':
7482          prec = 6;
7483          break;
7484
7485        case '<':
7486        case '>':
7487        case '<=':
7488        case '>=':
7489        case 'instanceof':
7490        case 'in':
7491          prec = 7;
7492          break;
7493
7494        case '<<':
7495        case '>>':
7496        case '>>>':
7497          prec = 8;
7498          break;
7499
7500        case '+':
7501        case '-':
7502          prec = 9;
7503          break;
7504
7505        case '*':
7506        case '/':
7507        case '%':
7508          prec = 11;
7509          break;
7510
7511        default:
7512          break;
7513      }
7514
7515      return prec;
7516    }
7517
7518    // 11.5 Multiplicative Operators
7519    // 11.6 Additive Operators
7520    // 11.7 Bitwise Shift Operators
7521    // 11.8 Relational Operators
7522    // 11.9 Equality Operators
7523    // 11.10 Binary Bitwise Operators
7524    // 11.11 Binary Logical Operators
7525
7526    function parseBinaryExpression() {
7527      var marker, markers, expr, token, prec, stack, right, operator, left, i;
7528
7529      marker = lookahead;
7530      left = parseUnaryExpression();
7531
7532      token = lookahead;
7533      prec = binaryPrecedence(token);
7534      if (prec === 0) {
7535        return left;
7536      }
7537      token.prec = prec;
7538      lex();
7539
7540      markers = [marker, lookahead];
7541      right = parseUnaryExpression();
7542
7543      stack = [left, token, right];
7544
7545      while ((prec = binaryPrecedence(lookahead)) > 0) {
7546
7547        // Reduce: make a binary expression from the three topmost entries.
7548        while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) {
7549          right = stack.pop();
7550          operator = stack.pop().value;
7551          left = stack.pop();
7552          markers.pop();
7553          expr = finishBinaryExpression(operator, left, right);
7554          stack.push(expr);
7555        }
7556
7557        // Shift.
7558        token = lex();
7559        token.prec = prec;
7560        stack.push(token);
7561        markers.push(lookahead);
7562        expr = parseUnaryExpression();
7563        stack.push(expr);
7564      }
7565
7566      // Final reduce to clean-up the stack.
7567      i = stack.length - 1;
7568      expr = stack[i];
7569      markers.pop();
7570      while (i > 1) {
7571        markers.pop();
7572        expr = finishBinaryExpression(stack[i - 1].value, stack[i - 2], expr);
7573        i -= 2;
7574      }
7575
7576      return expr;
7577    }
7578
7579    // 11.12 Conditional Operator
7580
7581    function parseConditionalExpression() {
7582      var expr, consequent, alternate;
7583
7584      expr = parseBinaryExpression();
7585
7586      if (match('?')) {
7587        lex();
7588        consequent = parseConditionalExpression();
7589        expect(':');
7590        alternate = parseConditionalExpression();
7591
7592        expr = finishConditionalExpression(expr, consequent, alternate);
7593      }
7594
7595      return expr;
7596    }
7597
7598    // 11.14 Comma Operator
7599
7600    function parseExpression() {
7601      var expr = parseConditionalExpression();
7602
7603      if (match(',')) {
7604        throw new Error(DISABLED); // no sequence expressions
7605      }
7606
7607      return expr;
7608    }
7609
7610    function parse(code) {
7611      source = code;
7612      index$2 = 0;
7613      length = source.length;
7614      lookahead = null;
7615
7616      peek$1();
7617
7618      var expr = parseExpression();
7619
7620      if (lookahead.type !== TokenEOF) {
7621        throw new Error("Unexpect token after expression.");
7622      }
7623      return expr;
7624    }
7625
7626    var Constants = {
7627      NaN:       'NaN',
7628      E:         'Math.E',
7629      LN2:       'Math.LN2',
7630      LN10:      'Math.LN10',
7631      LOG2E:     'Math.LOG2E',
7632      LOG10E:    'Math.LOG10E',
7633      PI:        'Math.PI',
7634      SQRT1_2:   'Math.SQRT1_2',
7635      SQRT2:     'Math.SQRT2',
7636      MIN_VALUE: 'Number.MIN_VALUE',
7637      MAX_VALUE: 'Number.MAX_VALUE'
7638    };
7639
7640    function accessor$1(fn, fields, name) {
7641      fn.fields = fields || [];
7642      fn.fname = name;
7643      return fn;
7644    }
7645
7646    function error$1(message) {
7647      throw Error(message);
7648    }
7649
7650    function splitAccessPath$1(p) {
7651      var path = [],
7652          q = null,
7653          b = 0,
7654          n = p.length,
7655          s = '',
7656          i, j, c;
7657
7658      p = p + '';
7659
7660      function push() {
7661        path.push(s + p.substring(i, j));
7662        s = '';
7663        i = j + 1;
7664      }
7665
7666      for (i=j=0; j<n; ++j) {
7667        c = p[j];
7668        if (c === '\\') {
7669          s += p.substring(i, j);
7670          i = ++j;
7671        } else if (c === q) {
7672          push();
7673          q = null;
7674          b = -1;
7675        } else if (q) {
7676          continue;
7677        } else if (i === b && c === '"') {
7678          i = j + 1;
7679          q = c;
7680        } else if (i === b && c === "'") {
7681          i = j + 1;
7682          q = c;
7683        } else if (c === '.' && !b) {
7684          if (j > i) {
7685            push();
7686          } else {
7687            i = j + 1;
7688          }
7689        } else if (c === '[') {
7690          if (j > i) push();
7691          b = i = j + 1;
7692        } else if (c === ']') {
7693          if (!b) error$1('Access path missing open bracket: ' + p);
7694          if (b >
7694 0) push();
7695          b = 0;
7696          i = j + 1;
7697        }
7698      }
7699
7700      if (b) error$1('Access path missing closing bracket: ' + p);
7701      if (q) error$1('Access path missing closing quote: ' + p);
7702
7703      if (j > i) {
7704        j++;
7705        push();
7706      }
7707
7708      return path;
7709    }
7710
7711    var isArray$2 = Array.isArray;
7712
7713    function isObject$1(_) {
7714      return _ === Object(_);
7715    }
7716
7717    function isString$1(_) {
7718      return typeof _ === 'string';
7719    }
7720
7721    function $$1(x) {
7722      return isArray$2(x) ? '[' + x.map($$1) + ']'
7723        : isObject$1(x) || isString$1(x) ?
7724          // Output valid JSON and JS source strings.
7725          // See http://timelessrepo.com/json-isnt-a-javascript-subset
7726          JSON.stringify(x).replace('\u2028','\\u2028').replace('\u2029', '\\u2029')
7727        : x;
7728    }
7729
7730    function field$1(field, name) {
7731      var path = splitAccessPath$1(field),
7732          code = 'return _[' + path.map($$1).join('][') + '];';
7733
7734      return accessor$1(
7735        Function('_', code),
7736        [(field = path.length===1 ? path[0] : field)],
7737        name || field
7738      );
7739    }
7740
7741    var empty$1 = [];
7742
7743    var id$1 = field$1('id');
7744
7745    var identity$1 = accessor$1(function(_) { return _; }, empty$1, 'identity');
7746
7747    var zero$1 = accessor$1(function() { return 0; }, empty$1, 'zero');
7748
7749    var one$1 = accessor$1(function() { return 1; }, empty$1, 'one');
7750
7751    var truthy$1 = accessor$1(function() { return true; }, empty$1, 'true');
7752
7753    var falsy$1 = accessor$1(function() { return false; }, empty$1, 'false');
7754
7755    function isFunction$1(_) {
7756      return typeof _ === 'function';
7757    }
7758
7759    function toSet$1(_) {
7760      for (var s={}, i=0, n=_.length; i<n; ++i) s[_[i]] = true;
7761      return s;
7762    }
7763
7764    function Functions(codegen) {
7765
7766      function fncall(name, args, cast, type) {
7767        var obj = codegen(args[0]);
7768        if (cast) {
7769          obj = cast + '(' + obj + ')';
7770          if (cast.lastIndexOf('new ', 0) === 0) obj = '(' + obj + ')';
7771        }
7772        return obj + '.' + name + (type < 0 ? '' : type === 0 ?
7773          '()' :
7774          '(' + args.slice(1).map(codegen).join(',') + ')');
7775      }
7776
7777      function fn(name, cast, type) {
7778        return function(args) {
7779          return fncall(name, args, cast, type);
7780        };
7781      }
7782
7783      var DATE = 'new Date',
7784          STRING = 'String',
7785          REGEXP = 'RegExp';
7786
7787      return {
7788        // MATH functions
7789        isNaN:    'isNaN',
7790        isFinite: 'isFinite',
7791        abs:      'Math.abs',
7792        acos:     'Math.acos',
7793        asin:     'Math.asin',
7794        atan:     'Math.atan',
7795        atan2:    'Math.atan2',
7796        ceil:     'Math.ceil',
7797        cos:      'Math.cos',
7798        exp:      'Math.exp',
7799        floor:    'Math.floor',
7800        log:      'Math.log',
7801        max:      'Math.max',
7802        min:      'Math.min',
7803        pow:      'Math.pow',
7804        random:   'Math.random',
7805        round:    'Math.round',
7806        sin:      'Math.sin',
7807        sqrt:     'Math.sqrt',
7808        tan:      'Math.tan',
7809
7810        clamp: function(args) {
7811          if (args.length < 3) error$1('Missing arguments to clamp function.');
7812          if (args.length > 3) error$1('Too many arguments to clamp function.');
7813          var a = args.map(codegen);
7814          return 'Math.max('+a[1]+', Math.min('+a[2]+','+a[0]+'))';
7815        },
7816
7817        // DATE functions
7818        now:             'Date.now',
7819        utc:             'Date.UTC',
7820        datetime:        DATE,
7821        date:            fn('getDate', DATE, 0),
7822        day:             fn('getDay', DATE, 0),
7823        year:            fn('getFullYear', DATE, 0),
7824        month:           fn('getMonth', DATE, 0),
7825        hours:           fn('getHours', DATE, 0),
7826        minutes:         fn('getMinutes', DATE, 0),
7827        seconds:         fn('getSeconds', DATE, 0),
7828        milliseconds:    fn('getMilliseconds', DATE, 0),
7829        time:            fn('getTime', DATE, 0),
7830        timezoneoffset:  fn('getTimezoneOffset', DATE, 0),
7831        utcdate:         fn('getUTCDate', DATE, 0),
7832        utcday:          fn('getUTCDay', DATE, 0),
7833        utcyear:         fn('getUTCFullYear', DATE, 0),
7834        utcmonth:        fn('getUTCMonth', DATE, 0),
7835        utchours:        fn('getUTCHours', DATE, 0),
7836        utcminutes:      fn('getUTCMinutes', DATE, 0),
7837        utcseconds:      fn('getUTCSeconds', DATE, 0),
7838        utcmilliseconds: fn('getUTCMilliseconds', DATE, 0),
7839
7840        // shared sequence functions
7841        length:      fn('length', null, -1),
7842        indexof:     fn('indexOf', null),
7843        lastindexof: fn('lastIndexOf', null),
7844        slice:       fn('slice', null),
7845
7846        // STRING functions
7847        parseFloat:  'parseFloat',
7848        parseInt:    'parseInt',
7849        upper:       fn('toUpperCase', STRING, 0),
7850        lower:       fn('toLowerCase', STRING, 0),
7851        substring:   fn('substring', STRING),
7852        split:       fn('split', STRING),
7853        replace:     fn('replace', STRING),
7854
7855        // REGEXP functions
7856        regexp:  REGEXP,
7857        test:    fn('test', REGEXP),
7858
7859        // Control Flow functions
7860        if: function(args) {
7861            if (args.length < 3) error$1('Missing arguments to if function.');
7862            if (args.length > 3) error$1('Too many arguments to if function.');
7863            var a = args.map(codegen);
7864            return '('+a[0]+'?'+a[1]+':'+a[2]+')';
7865          }
7866      };
7867    }
7868
7869    function codegen(opt) {
7870      opt = opt || {};
7871
7872      var whitelist = opt.whitelist ? toSet$1(opt.whitelist) : {},
7873          blacklist = opt.blacklist ? toSet$1(opt.blacklist) : {},
7874          constants = opt.constants || Constants,
7875          functions = (opt.functions || Functions)(visit),
7876          globalvar = opt.globalvar,
7877          fieldvar = opt.fieldvar,
7878          globals = {},
7879          fields = {},
7880          memberDepth = 0;
7881
7882      var outputGlobal = isFunction$1(globalvar)
7883        ? globalvar
7884        : function (id) { return globalvar + '["' + id + '"]'; };
7885
7886      function visit(ast) {
7887        if (isString$1(ast)) return ast;
7888        var generator = Generators[ast.type];
7889        if (generator == null) error$1('Unsupported type: ' + ast.type);
7890        return generator(ast);
7891      }
7892
7893      var Generators = {
7894        Literal: function(n) {
7895            return n.raw;
7896          },
7897
7898        Identifier: function(n) {
7899          var id = n.name;
7900          if (memberDepth > 0) {
7901            return id;
7902          } else if (blacklist.hasOwnProperty(id)) {
7903            return error$1('Illegal identifier: ' + id);
7904          } else if (constants.hasOwnProperty(id)) {
7905            return constants[id];
7906          } else if (whitelist.hasOwnProperty(id)) {
7907            return id;
7908          } else {
7909            globals[id] = 1;
7910            return outputGlobal(id);
7911          }
7912        },
7913
7914        MemberExpression: function(n) {
7915            var d = !n.computed;
7916            var o = visit(n.object);
7917            if (d) memberDepth += 1;
7918            var p = visit(n.property);
7919            if (o === fieldvar) { fields[p] = 1; } // HACKish...
7920            if (d) memberDepth -= 1;
7921            return o + (d ? '.'+p : '['+p+']');
7922          },
7923
7924        CallExpression: function(n) {
7925            if (n.callee.type !== 'Identifier') {
7926              error$1('Illegal callee type: ' + n.callee.type);
7927            }
7928            var callee = n.callee.name;
7929            var args = n.arguments;
7930            var fn = functions.hasOwnProperty(callee) && functions[callee];
7931            if (!fn) error$1('Unrecognized function: ' + callee);
7932            return isFunction$1(fn)
7933              ? fn(args)
7934              : fn + '(' + args.map(visit).join(',') + ')';
7935          },
7936
7937        ArrayExpression: function(n) {
7938            return '[' + n.elements.map(visit).join(',') + ']';
7939          },
7940
7941        BinaryExpression: function(n) {
7942            return '(' + visit(n.left) + n.operator + visit(n.right) + ')';
7943          },
7944
7945        UnaryExpression: function(n) {
7946            return '(' + n.operator + visit(n.argument) + ')';
7947          },
7948
7949        ConditionalExpression: function(n) {
7950            return '(' + visit(n.test) +
7951              '?' + visit(n.consequent) +
7952              ':' + visit(n.alternate) +
7953              ')';
7954          },
7955
7956        LogicalExpression: function(n) {
7957            return '(' + visit(n.left) + n.operator + visit(n.right) + ')';
7958          },
7959
7960        ObjectExpression: function(n) {
7961            return '{' + n.properties.map(visit).join(',') + '}';
7962          },
7963
7964        Property: function(n) {
7965            memberDepth += 1;
7966            var k = visit(n.key);
7967            memberDepth -= 1;
7968            return k + ':' + visit(n.value);
7969          }
7970      };
7971
7972      function codegen(ast) {
7973        var result = {
7974          code:    visit(ast),
7975          globals: Object.keys(globals),
7976          fields:  Object.keys(fields)
7977        };
7978        globals = {};
7979        fields = {};
7980        return result;
7981      }
7982
7983      codegen.functions = functions;
7984      codegen.constants = constants;
7985
7986      return codegen;
7987    }
7988
7989    function getName(node) {
7990        let name = [];
7991        if (node.type === 'Identifier') {
7992            return [node.name];
7993        }
7994        if (node.type === 'Literal') {
7995            return [node.value];
7996        }
7997        if (node.type === 'MemberExpression') {
7998            name = name.concat(getName(node.object));
7999            name = name.concat(getName(node.property));
8000        }
8001        return name;
8002    }
8003    function startsWithDatum(node) {
8004        if (node.object.type === 'MemberExpression') {
8005            return startsWithDatum(node.object);
8006        }
8007        return node.object.name === 'datum';
8008    }
8009    function getDependentFields(expression) {
8010        const ast = parse(expression);
8011        const dependents = new Set();
8012        ast.visit((node) => {
8013            if (node.type === 'MemberExpression' && startsWithDatum(node)) {
8014                dependents.add(getName(node)
8015                    .slice(1)
8016                    .join('.'));
8017            }
8018        });
8019        return dependents;
8020    }
8021
8022    /**
8023     * We don't know what a calculate node depends on so we should never move it beyond anything that produces fields.
8024     */
8025    class CalculateNode extends DataFlowNode {
8026        constructor(parent, transform) {
8027            super(parent);
8028            this.transform = transform;
8029            this._dependentFields = getDependentFields(this.transform.calculate);
8030        }
8031        clone() {
8032            return new CalculateNode(null, duplicate(this.transform));
8033        }
8034        static parseAllForSortIndex(parent, model) {
8035            // get all the encoding with sort fields from model
8036            model.forEachFieldDef((fieldDef, channel) => {
8037                if (!isScaleFieldDef(fieldDef)) {
8038                    return;
8039                }
8040                if (isSortArray(fieldDef.sort)) {
8041                    const { field, timeUnit } = fieldDef;
8042                    const sort = fieldDef.sort;
8043                    // generate `datum["a"] === val0 ? 0 : datum["a"] === val1 ? 1 : ... : n` via FieldEqualPredicate
8044                    const calculate = sort
8045                        .map((sortValue, i) => {
8046                        return `${fieldFilterExpression({ field, timeUnit, equal: sortValue })} ? ${i} : `;
8047                    })
8048                        .join('') + sort.length;
8049                    parent = new CalculateNode(parent, {
8050                        calculate,
8051                        as: sortArrayIndexField(fieldDef, channel, { forAs: true })
8052                    });
8053                }
8054            });
8055            return parent;
8056        }
8057        producedFields() {
8058            return new Set([this.transform.as]);
8059        }
8060        dependentFields() {
8061            return this._dependentFields;
8062        }
8063        assemble() {
8064            return {
8065                type: 'formula',
8066                expr: this.transform.calculate,
8067                as: this.transform.as
8068            };
8069        }
8070        hash() {
8071            return `Calculate ${hash(this.transform)}`;
8072        }
8073    }
8074    function sortArrayIndexField(fieldDef, channel, opt) {
8075        return vgField(fieldDef, Object.assign({ prefix: channel, suffix: 'sort_index' }, (opt || {})));
8076    }
8077
8078    const HEADER_CHANNELS = ['row', 'column'];
8079    const HEADER_TYPES = ['header', 'footer'];
8080    function getHeaderType(orient) {
8081        if (orient === 'top' || orient === 'left') {
8082            return 'header';
8083        }
8084        return 'footer';
8085    }
8086    function getTitleGroup(model, channel) {
8087        const title$$1 = model.component.layoutHeaders[channel].title;
8088        const config = model.config ? model.config : undefined;
8089        const facetFieldDef = model.component.layoutHeaders[channel].facetFieldDef
8090            ? model.component.layoutHeaders[channel].facetFieldDef
8091            : undefined;
8092        return {
8093            name: `${channel}-title`,
8094            type: 'group',
8095            role: `${channel}-title`,
8096            title: Object.assign({ text: title$$1, offset: 10 }, (channel === 'row' ? { orient: 'left' } : {}), { style: 'guide-title' }
8096, getHeaderProperties(config, facetFieldDef, HEADER_TITLE_PROPERTIES, HEADER_TITLE_PROPERTIES_MAP))
8097        };
8098    }
8099    function getHeaderGroups(model, channel) {
8100        const layoutHeader = model.component.layoutHeaders[channel];
8101        const groups = [];
8102        for (const headerType of HEADER_TYPES) {
8103            if (layoutHeader[headerType]) {
8104                for (const headerCmpt of layoutHeader[headerType]) {
8105                    groups.push(getHeaderGroup(model, channel, headerType, layoutHeader, headerCmpt));
8106                }
8107            }
8108        }
8109        return groups;
8110    }
8111    // 0, (0,90), 90, (90, 180), 180, (180, 270), 270, (270, 0)
8112    function labelAlign(angle) {
8113        // to keep angle in [0, 360)
8114        angle = ((angle % 360) + 360) % 360;
8115        if ((angle + 90) % 180 === 0) {
8116            // for 90 and 270
8117            return {}; // default center
8118        }
8119        else if (angle < 90 || 270 < angle) {
8120            return { align: { value: 'right' } };
8121        }
8122        else if (135 <= angle && angle < 225) {
8123            return { align: { value: 'left' } };
8124        }
8125        return {};
8126    }
8127    function labelBaseline(angle) {
8128        // to keep angle in [0, 360)
8129        angle = ((angle % 360) + 360) % 360;
8130        if (45 <= angle && angle <= 135) {
8131            return { baseline: 'top' };
8132        }
8133        return { baseline: 'middle' };
8134    }
8135    function getSort(facetFieldDef, channel) {
8136        const { sort } = facetFieldDef;
8137        if (isSortField(sort)) {
8138            return {
8139                field: vgField(sort, { expr: 'datum' }),
8140                order: sort.order || 'ascending'
8141            };
8142        }
8143        else if (isArray(sort)) {
8144            return {
8145                field: sortArrayIndexField(facetFieldDef, channel, { expr: 'datum' }),
8146                order: 'ascending'
8147            };
8148        }
8149        else {
8150            return {
8151                field: vgField(facetFieldDef, { expr: 'datum' }),
8152                order: sort || 'ascending'
8153            };
8154        }
8155    }
8156    function getHeaderGroup(model, channel, headerType, layoutHeader, headerCmpt) {
8157        if (headerCmpt) {
8158            let title$$1 = null;
8159            const { facetFieldDef } = layoutHeader;
8160            if (facetFieldDef && headerCmpt.labels) {
8161                const { header = {} } = facetFieldDef;
8162                const { format: format$$1, labelAngle } = header;
8163                const config = model.config ? model.config : undefined;
8164                const update = Object.assign({}, labelAlign(labelAngle));
8165                title$$1 = Object.assign({ text: formatSignalRef(facetFieldDef, format$$1, 'parent', model.config), offset: 10 }, (channel === 'row' ? { orient: 'left' } : {}), { style: 'guide-label' }, (labelAngle !== undefined ? { angle: labelAngle } : {}), labelBaseline(labelAngle), getHeaderProperties(config, facetFieldDef, HEADER_LABEL_PROPERTIES, HEADER_LABEL_PROPERTIES_MAP), (keys(update).length > 0 ? { encode: { update } } : {}));
8166            }
8167            const axes = headerCmpt.axes;
8168            const hasAxes = axes && axes.length > 0;
8169            if (title$$1 || hasAxes) {
8170                const sizeChannel = channel === 'row' ? 'height' : 'width';
8171                return Object.assign({ name: model.getName(`${channel}_${headerType}`), type: 'group', role: `${channel}-${headerType}` }, (layoutHeader.facetFieldDef
8172                    ? {
8173                        from: { data: model.getName(channel + '_domain') },
8174                        sort: getSort(facetFieldDef, channel)
8175                    }
8176                    : {}), (title$$1 ? { title: title$$1 } : {}), (headerCmpt.sizeSignal
8177                    ? {
8178                        encode: {
8179                            update: {
8180                                [sizeChannel]: headerCmpt.sizeSignal
8181                            }
8182                        }
8183                    }
8184                    : {}), (hasAxes ? { axes } : {}));
8185            }
8186        }
8187        return null;
8188    }
8189    function getHeaderProperties(config, facetFieldDef, properties, propertiesMap) {
8190        const props = {};
8191        for (const prop of properties) {
8192            if (config && config.header) {
8193                if (config.header[prop]) {
8194                    props[propertiesMap[prop]] = config.header[prop];
8195                }
8196            }
8197            if (facetFieldDef && facetFieldDef.header) {
8198                if (facetFieldDef.header[prop]) {
8199                    props[propertiesMap[prop]] = facetFieldDef.header[prop];
8200                }
8201            }
8202        }
8203        return props;
8204    }
8205
8206    function assembleLayoutSignals(model) {
8207        return [].concat(sizeSignals(model, 'width'), sizeSignals(model, 'height'));
8208    }
8209    function sizeSignals(model, sizeType) {
8210        const channel = sizeType === 'width' ? 'x' : 'y';
8211        const size = model.component.layoutSize.get(sizeType);
8212        if (!size || size === 'merged') {
8213            return [];
8214        }
8215        // Read size signal name from name map, just in case it is the top-level size signal that got renamed.
8216        const name = model.getSizeSignalRef(sizeType).signal;
8217        if (size === 'range-step') {
8218            const scaleComponent = model.getScaleComponent(channel);
8219            if (scaleComponent) {
8220                const type = scaleComponent.get('type');
8221                const range = scaleComponent.get('range');
8222                if (hasDiscreteDomain(type) && isVgRangeStep(range)) {
8223                    const scaleName = model.scaleName(channel);
8224                    if (isFacetModel(model.parent)) {
8225                        // If parent is facet and this is an independent scale, return only signal signal
8226                        // as the width/height will be calculated using the cardinality from
8227                        // facet's aggregate rather than reading from scale domain
8228                        const parentResolve = model.parent.component.resolve;
8229                        if (parentResolve.scale[channel] === 'independent') {
8230                            return [stepSignal(scaleName, range)];
8231                        }
8232                    }
8233                    return [
8234                        stepSignal(scaleName, range),
8235                        {
8236                            name,
8237                            update: sizeExpr(scaleName, scaleComponent, `domain('${scaleName}').length`)
8238                        }
8239                    ];
8240                }
8241            }
8242            /* istanbul ignore next: Condition should not happen -- only for warning in development. */
8243            throw new Error('layout size is range step although there is no rangeStep.');
8244        }
8245        else {
8246            return [
8247                {
8248                    name,
8249                    value: size
8250                }
8251            ];
8252        }
8253    }
8254    function stepSignal(scaleName, range) {
8255        return {
8256            name: scaleName + '_step',
8257            value: range.step
8258        };
8259    }
8260    function sizeExpr(scaleName, scaleComponent, cardinality) {
8261        const type = scaleComponent.get('type');
8262        const padding = scaleComponent.get('padding');
8263        const paddingOuter = getFirstDefined(scaleComponent.get('paddingOuter'), padding);
8264        let paddingInner = scaleComponent.get('paddingInner');
8265        paddingInner =
8266            type === 'band'
8267                ? // only band has real paddingInner
8268                    paddingInner !== undefined
8269                        ? paddingInner
8270                        : padding
8271                : // For point, as calculated in https://github.com/vega/vega-scale/blob/master/src/band.js#L128,
8272                    // it's equivalent to have paddingInner = 1 since there is only n-1 steps between n points.
8273                    1;
8274        return `bandspace(${cardinality}, ${paddingInner}, ${paddingOuter}) * ${scaleName}_step`;
8275    }
8276
8277    /**
8278     * Parse an event selector string.
8279     * Returns an array of event stream definitions.
8280     */
8281    function parseSelector(selector, source, marks) {
8282      DEFAULT_SOURCE = source || VIEW;
8283      MARKS = marks || DEFAULT_MARKS;
8284      return parseMerge(selector.trim()).map(parseSelector$1);
8285    }
8286
8287    var VIEW    = 'view',
8288        LBRACK  = '[',
8289        RBRACK  = ']',
8290        LBRACE  = '{',
8291        RBRACE  = '}',
8292        COLON   = ':',
8293        COMMA   = ',',
8294        NAME    = '@',
8295        GT      = '>',
8296        ILLEGAL$1 = /[[\]{}]/,
8297        DEFAULT_SOURCE,
8298        MARKS,
8299        DEFAULT_MARKS = {
8300          '*': 1,
8301          arc: 1,
8302          area: 1,
8303          group: 1,
8304          image: 1,
8305          line: 1,
8306          path: 1,
8307          rect: 1,
8308          rule: 1,
8309          shape: 1,
8310          symbol: 1,
8311          text: 1,
8312          trail: 1
8313        };
8314
8315    function isMarkType(type) {
8316      return MARKS.hasOwnProperty(type);
8317    }
8318
8319    function find(s, i, endChar, pushChar, popChar) {
8320      var count = 0,
8321          n = s.length,
8322          c;
8323      for (; i<n; ++i) {
8324        c = s[i];
8325        if (!count && c === endChar) return i;
8326        else if (popChar && popChar.indexOf(c) >= 0) --count;
8327        else if (pushChar && pushChar.indexOf(c) >= 0) ++count;
8328      }
8329      return i;
8330    }
8331
8332    function parseMerge(s) {
8333      var output = [],
8334          start = 0,
8335          n = s.length,
8336          i = 0;
8337
8338      while (i < n) {
8339        i = find(s, i, COMMA, LBRACK + LBRACE, RBRACK + RBRACE);
8340        output.push(s.substring(start, i).trim());
8341        start = ++i;
8342      }
8343
8344      if (output.length === 0) {
8345        throw 'Empty event selector: ' + s;
8346      }
8347      return output;
8348    }
8349
8350    function parseSelector$1(s) {
8351      return s[0] === '['
8352        ? parseBetween(s)
8353        : parseStream(s);
8354    }
8355
8356    function parseBetween(s) {
8357      var n = s.length,
8358          i = 1,
8359          b, stream;
8360
8361      i = find(s, i, RBRACK, LBRACK, RBRACK);
8362      if (i === n) {
8363        throw 'Empty between selector: ' + s;
8364      }
8365
8366      b = parseMerge(s.substring(1, i));
8367      if (b.length !== 2) {
8368        throw 'Between selector must have two elements: ' + s;
8369      }
8370
8371      s = s.slice(i + 1).trim();
8372      if (s[0] !== GT) {
8373        throw 'Expected \'>\' after between selector: ' + s;
8374      }
8375
8376      b = b.map(parseSelector$1);
8377
8378      stream = parseSelector$1(s.slice(1).trim());
8379      if (stream.between) {
8380        return {
8381          between: b,
8382          stream: stream
8383        };
8384      } else {
8385        stream.between = b;
8386      }
8387
8388      return stream;
8389    }
8390
8391    function parseStream(s) {
8392      var stream = {source: DEFAULT_SOURCE},
8393          source = [],
8394          throttle = [0, 0],
8395          markname = 0,
8396          start = 0,
8397          n = s.length,
8398          i = 0, j,
8399          filter;
8400
8401      // extract throttle from end
8402      if (s[n-1] === RBRACE) {
8403        i = s.lastIndexOf(LBRACE);
8404        if (i >= 0) {
8405          try {
8406            throttle = parseThrottle(s.substring(i+1, n-1));
8407          } catch (e) {
8408            throw 'Invalid throttle specification: ' + s;
8409          }
8410          s = s.slice(0, i).trim();
8411          n = s.length;
8412        } else throw 'Unmatched right brace: ' + s;
8413        i = 0;
8414      }
8415
8416      if (!n) throw s;
8417
8418      // set name flag based on first char
8419      if (s[0] === NAME) markname = ++i;
8420
8421      // extract first part of multi-part stream selector
8422      j = find(s, i, COLON);
8423      if (j < n) {
8424        source.push(s.substring(start, j).trim());
8425        start = i = ++j;
8426      }
8427
8428      // extract remaining part of stream selector
8429      i = find(s, i, LBRACK);
8430      if (i === n) {
8431        source.push(s.substring(start, n).trim());
8432      } else {
8433        source.push(s.substring(start, i).trim());
8434        filter = [];
8435        start = ++i;
8436        if (start === n) throw 'Unmatched left bracket: ' + s;
8437      }
8438
8439      // extract filters
8440      while (i < n) {
8441        i = find(s, i, RBRACK);
8442        if (i === n) throw 'Unmatched left bracket: ' + s;
8443        filter.push(s.substring(start, i).trim());
8444        if (i < n-1 && s[++i] !== LBRACK) throw 'Expected left bracket: ' + s;
8445        start = ++i;
8446      }
8447
8448      // marshall event stream specification
8449      if (!(n = source.length) || ILLEGAL$1.test(source[n-1])) {
8450        throw 'Invalid event selector: ' + s;
8451      }
8452
8453      if (n > 1) {
8454        stream.type = source[1];
8455        if (markname) {
8456          stream.markname = source[0].slice(1);
8457        } else if (isMarkType(source[0])) {
8458          stream.marktype = source[0];
8459        } else {
8460          stream.source = source[0];
8461        }
8462      } else {
8463        stream.type = source[0];
8464      }
8465      if (stream.type.slice(-1) === '!') {
8466        stream.consume = true;
8467        stream.type = stream.type.slice(0, -1);
8468      }
8469      if (filter != null) stream.filter = filter;
8470      if (throttle[0]) stream.throttle = throttle[0];
8471      if (throttle[1]) stream.debounce = throttle[1];
8472
8473      return stream;
8474    }
8475
8476    function parseThrottle(s) {
8477      var a = s.split(COMMA);
8478      if (!s.length || a.length > 2) throw s;
8479      return a.map(function(_) {
8480        var x = +_;
8481        if (x !== x) throw s;
8482        return x;
8483      });
8484    }
8485
8486    class TimeUnitNode extends DataFlowNode {
8487        constructor(parent, formula) {
8488            super(parent);
8489            this.formula = formula;
8490        }
8491        clone() {
8492            return new TimeUnitNode(null, duplicate(this.formula));
8493        }
8494        static makeFromEncoding(parent, model) {
8495            const formula = model.reduceFieldDef((timeUnitComponent, fieldDef) => {
8496                if (fieldDef.timeUnit) {
8497                    const f = vgField(fieldDef, { forAs: true });
8498                    timeUnitComponent[f] = {
8499                        as: f,
8500                        timeUnit: fieldDef.timeUnit,
8501                        field: fieldDef.field
8502                    };
8503                }
8504                return timeUnitComponent;
8505            }, {});
8506            if (keys(formula).length === 0) {
8507                return null;
8508            }
8509            return new TimeUnitNode(parent, formula);
8510        }
8511        static makeFromTransform(parent, t) {
8512            return new TimeUnitNode(parent, {
8513                [t.field]: {
8514                    as: t.as,
8515                    timeUnit: t.timeUnit,
8516                    field: t.field
8517                }
8518            });
8519        }
8520        merge(other) {
8521            this.formula = Object.assign({}, this.formula, other.formula);
8522            other.remove();
8523        }
8524        producedFields() {
8525            return new Set(vals(this.formula).map(f => f.as));
8526        }
8527        dependentFields() {
8528            return new Set(vals(this.formula).map(f => f.field));
8529        }
8530        hash() {
8531            return `TimeUnit ${hash(this.formula)}`;
8532        }
8533        assemble() {
8534            return vals(this.formula).map(c => {
8535                return {
8536                    type: 'formula',
8537                    as: c.as,
8538                    expr: fieldExpr(c.timeUnit, c.field)
8539                };
8540            });
8541        }
8542    }
8543
8544    const TUPLE_FIELDS = '_fields';
8545    const project = {
8546        has: (selDef) => {
8547            const def = selDef;
8548            return def.fields !== undefined || def.encodings !== undefined;
8549        },
8550        parse: (model, selDef, selCmpt) => {
8551            const timeUnits = {};
8552            const f = {};
8553            const p = selCmpt.project || (selCmpt.project = []);
8554            selCmpt.fields = {};
8555            // TODO: find a possible channel mapping for these fields.
8556            if (selDef.fields) {
8557                p.push(...selDef.fields.map(field => ({ field, type: 'E' })));
8558            }
8559            for (const channel of selDef.encodings || []) {
8560                const fieldDef = model.fieldDef(channel);
8561                if (fieldDef) {
8562                    let field = fieldDef.field;
8563                    if (fieldDef.timeUnit) {
8564                        field = model.vgField(channel);
8565                        // Construct TimeUnitComponents which will be combined into a
8566                        // TimeUnitNode. This node may need to be inserted into the
8567                        // dataflow if the selection is used across views that do not
8568                        // have these time units defined.
8569                        timeUnits[field] = {
8570                            as: field,
8571                            field: fieldDef.field,
8572                            timeUnit: fieldDef.timeUnit
8573                        };
8574                    }
8575                    // Prevent duplicate projections on the same field.
8576                    // TODO: what if the same field is bound to multiple channels (e.g., SPLOM diag).
8577                    if (!f[field]) {
8578                        // Determine whether the tuple will store enumerated or ranged values.
8579                        // Interval selections store ranges for continuous scales, and enumerations otherwise.
8580                        // Single/multi selections store ranges for binned fields, and enumerations otherwise.
8581                        let type = 'E';
8582                        if (selCmpt.type === 'interval') {
8583                            const scaleType = model.getScaleComponent(channel).get('type');
8584                            if (hasContinuousDomain(scaleType) && !isBinScale(scaleType)) {
8585                                type = 'R';
8586                            }
8587                        }
8588                        else if (fieldDef.bin) {
8589                            type = 'R-RE';
8590                        }
8591                        p.push((f[field] = { field, channel, type }));
8592                    }
8593                    selCmpt.fields[channel] = field;
8594                }
8595                else {
8596                    warn(message.cannotProjectOnChannelWithoutField(channel));
8597                }
8598            }
8599            if (keys(timeUnits).length) {
8600                selCmpt.timeUnit = new TimeUnitNode(null, timeUnits);
8601            }
8602        },
8603        signals: (model, selCmpt, signals) => {
8604            const name = selCmpt.name + TUPLE + TUPLE_FIELDS;
8605            const hasSignal = signals.filter(s => s.name === name);
8606            return hasSignal.length
8607                ? signals
8608                : signals.concat({
8609                    name,
8610                    update: `${JSON.stringify(selCmpt.project)}`
8611                });
8612        }
8613    };
8614
8615    const scaleBindings = {
8616        has: selCmpt => {
8617            return selCmpt.type === 'interval' && selCmpt.resolve === 'global' && selCmpt.bind && selCmpt.bind === 'scales';
8618        },
8619        parse: (model, selDef, selCmpt) => {
8620            const name = varName(selCmpt.name);
8621            const bound = (selCmpt.scales = []);
8622            for (const p of selCmpt.project) {
8623                const channel = p.channel;
8624                if (!isScaleChannel(channel)) {
8625                    continue;
8626                }
8627                const scale = model.getScaleComponent(channel);
8628                const scaleType = scale ? scale.get('type') : undefined;
8629                if (!scale || !hasContinuousDomain(scaleType) || isBinScale(scaleType)) {
8630                    warn(message.SCALE_BINDINGS_CONTINUOUS);
8631                    continue;
8632                }
8633                scale.set('domainRaw', { signal: accessPathWithDatum(p.field, name) }, true);
8634                bound.push(channel);
8635                // Bind both x/y for diag plot of repeated views.
8636                if (model.repeater && model.repeater.row === model.repeater.column) {
8637                    const scale2 = model.getScaleComponent(channel === X ? Y : X);
8638                    scale2.set('domainRaw', { signal: accessPathWithDatum(p.field, name) }, true);
8639                }
8640            }
8641        },
8642        topLevelSignals: (model, selCmpt, signals) => {
8643            const channelSignals = selCmpt.scales
8644                .filter(channel => {
8645                return !signals.filter(s => s.name === channelSignalName(selCmpt, channel, 'data')).length;
8646            })
8647                .map(channel => {
8648                return { channel, signal: channelSignalName(selCmpt, channel, 'data') };
8649            });
8650            // Top-level signals are only needed for multiview displays and if this
8651            // view's top-level signals haven't already been generated.
8652            if (!model.parent || !channelSignals.length) {
8653                return signals;
8654            }
8655            // vlSelectionResolve does not account for the behavior of bound scales in
8656            // multiview displays. Each unit view adds a tuple to the store, but the
8657            // state of the selection is the unit selection most recently updated. This
8658            // state is captured by the top-level signals that we insert and "push
8659            // outer" to from within the units. We need to reassemble this state into
8660            // the top-level named signal, except no single selCmpt has a global view.
8661            const namedSg = signals.filter(s => s.name === selCmpt.name)[0];
8662            const update = namedSg.update;
8663            if (update.indexOf(VL_SELECTION_RESOLVE) >= 0) {
8664                namedSg.update =
8665                    '{' + channelSignals.map(cs => `${$(selCmpt.fields[cs.channel])}: ${cs.signal}`).join(', ') + '}';
8666            }
8667            else {
8668                for (const cs of channelSignals) {
8669                    const mapping = `, ${$(selCmpt.fields[cs.channel])}: ${cs.signal}`;
8670                    if (update.indexOf(mapping) < 0) {
8671                        namedSg.update = update.substring(0, update.length - 1) + mapping + '}';
8672                    }
8673                }
8674            }
8675            return signals.concat(channelSignals.map(cs => ({ name: cs.signal })));
8676        },
8677        signals: (model, selCmpt, signals) => {
8678            // Nested signals need only push to top-level signals with multiview displays.
8679            if (model.parent) {
8680                for (const channel of selCmpt.scales) {
8681                    const signal = signals.filter(s => s.name === channelSignalName(selCmpt, channel, 'data'))[0];
8682                    // convert to PushSignal
8683                    signal.push = 'outer';
8684                    delete signal.value;
8685                    delete signal.update;
8686                }
8687            }
8688            return signals;
8689        }
8690    };
8691    function domain$1(model, channel) {
8692        const scale = $(model.scaleName(channel));
8693        return `domain(${scale})`;
8694    }
8695
8696    const BRUSH = '_brush';
8697    const SCALE_TRIGGER = '_scale_trigger';
8698    const interval = {
8699        signals: (model, selCmpt) => {
8700            const name = selCmpt.name;
8701            const fieldsSg = name + TUPLE + TUPLE_FIELDS;
8702            const hasScales = scaleBindings.has(selCmpt);
8703            const signals = [];
8704            const dataSignals = [];
8705            const scaleTriggers = [];
8706            if (selCmpt.translate && !hasScales) {
8707                const filterExpr = `!event.item || event.item.mark.name !== ${$(name + BRUSH)}`;
8708                events(selCmpt, (_, evt) => {
8709                    const filters = evt.between[0].filter || (evt.between[0].filter = []);
8710                    if (filters.indexOf(filterExpr) < 0) {
8711                        filters.push(filterExpr);
8712                    }
8713                });
8714            }
8715            for (const p of selCmpt.project) {
8716                const channel = p.channel;
8717                if (channel !== X && channel !== Y) {
8718                    warn('Interval selections only support x and y encoding channels.');
8719                    continue;
8720                }
8721                const cs = channelSignals(model, selCmpt, channel);
8722                const dname = channelSignalName(selCmpt, channel, 'data');
8723                const vname = channelSignalName(selCmpt, channel, 'visual');
8724                const scaleStr = $(model.scaleName(channel));
8725                const scaleType = model.getScaleComponent(channel).get('type');
8726                const toNum = hasContinuousDomain(scaleType) ? '+' : '';
8727                signals.push(...cs);
8728                dataSignals.push(dname);
8729                scaleTriggers.push({
8730                    scaleName: model.scaleName(channel),
8731                    expr: `(!isArray(${dname}) || ` +
8732                        `(${toNum}invert(${scaleStr}, ${vname})[0] === ${toNum}${dname}[0] && ` +
8733                        `${toNum}invert(${scaleStr}, ${vname})[1] === ${toNum}${dname}[1]))`
8734                });
8735            }
8736            // Proxy scale reactions to ensure that an infinite loop doesn't occur
8737            // when an interval selection filter touches the scale.
8738            if (!hasScales) {
8739                signals.push({
8740                    name: name + SCALE_TRIGGER,
8741                    update: scaleTriggers.map(t => t.expr).join(' && ') + ` ? ${name + SCALE_TRIGGER} : {}`
8742                });
8743            }
8744            // Only add an interval to the store if it has valid data extents. Data extents
8745            // are set to null if pixel extents are equal to account for intervals over
8746            // ordinal/nominal domains which, when inverted, will still produce a valid datum.
8747            return signals.concat({
8748                name: name + TUPLE,
8749                on: [
8750                    {
8751                        events: dataSignals.map(t => ({ signal: t })),
8752                        update: dataSignals.join(' && ') +
8753                            ` ? {unit: ${unitName(model)}, fields: ${fieldsSg}, ` +
8754                            `values: [${dataSignals.join(', ')}]} : null`
8755                    }
8756                ]
8757            });
8758        },
8759        modifyExpr: (model, selCmpt) => {
8760            const tpl = selCmpt.name + TUPLE;
8761            return tpl + ', ' + (selCmpt.resolve === 'global' ? 'true' : `{unit: ${unitName(model)}}`);
8762        },
8763        marks: (model, selCmpt, marks) => {
8764            const name = selCmpt.name;
8765            const { xi, yi } = positionalProjections(selCmpt);
8766            const store = `data(${$(selCmpt.name + STORE)})`;
8767            // Do not add a brush if we're binding to scales.
8768            if (scaleBindings.has(selCmpt)) {
8769                return marks;
8770            }
8771            const update = {
8772                x: xi !== null ? { signal: `${name}_x[0]` } : { value: 0 },
8773                y: yi !== null ? { signal: `${name}_y[0]` } : { value: 0 },
8774                x2: xi !== null ? { signal: `${name}_x[1]` } : { field: { group: 'width' } },
8775                y2: yi !== null ? { signal: `${name}_y[1]` } : { field: { group: 'height' } }
8776            };
8777            // If the selection is resolved to global, only a single interval is in
8778            // the store. Wrap brush mark's encodings with a production rule to test
8779            // this based on the `unit` property. Hide the brush mark if it corresponds
8780            // to a unit different from the one in the store.
8781            if (selCmpt.resolve === 'global') {
8782                for (const key$$1 of keys(update)) {
8783                    update[key$$1] = [
8784                        Object.assign({ test: `${store}.length && ${store}[0].unit === ${unitName(model)}` }, update[key$$1]),
8785                        { value: 0 }
8786                    ];
8787                }
8788            }
8789            // Two brush marks ensure that fill colors and other aesthetic choices do
8790            // not interefere with the core marks, but that the brushed region can still
8791            // be interacted with (e.g., dragging it around).
8792            const _a = selCmpt.mark, { fill: fill$$1, fillOpacity } = _a, stroke = __rest(_a, ["fill", "fillOpacity"]);
8793            const vgStroke = keys(stroke).reduce((def, k) => {
8794                def[k] = [
8795                    {
8796                        test: [xi !== null && `${name}_x[0] !== ${name}_x[1]`, yi != null && `${name}_y[0] !== ${name}_y[1]`]
8797                            .filter(x => x)
8798                            .join(' && '),
8799                        value: stroke[k]
8800                    },
8801                    { value: null }
8802                ];
8803                return def;
8804            }, {});
8805            return [
8806                {
8807                    name: name + BRUSH + '_bg',
8808                    type: 'rect',
8809                    clip: true,
8810                    encode: {
8811                        enter: {
8812                            fill: { value: fill$$1 },
8813                            fillOpacity: { value: fillOpacity }
8814                        },
8815                        update: update
8816                    }
8817                }
8818            ].concat(marks, {
8819                name: name + BRUSH,
8820                type: 'rect',
8821                clip: true,
8822                encode: {
8823                    enter: {
8824                        fill: { value: 'transparent' }
8825                    },
8826                    update: Object.assign({}, update, vgStroke)
8827                }
8828            });
8829        }
8830    };
8831    /**
8832     * Returns the visual and data signals for an interval selection.
8833     */
8834    function channelSignals(model, selCmpt, channel) {
8835        const vname = channelSignalName(selCmpt, channel, 'visual');
8836        const dname = channelSignalName(selCmpt, channel, 'data');
8837        const hasScales = scaleBindings.has(selCmpt);
8838        const scaleName = model.scaleName(channel);
8839        const scaleStr = $(scaleName);
8840        const scale = model.getScaleComponent(channel);
8841        const scaleType = scale ? scale.get('type') : undefined;
8842        const size = model.getSizeSignalRef(channel === X ? 'width' : 'height').signal;
8843        const coord = `${channel}(unit)`;
8844        const on = events(selCmpt, (def, evt) => {
8845            return def.concat({ events: evt.between[0], update: `[${coord}, ${coord}]` }, // Brush Start
8846            { events: evt, update: `[${vname}[0], clamp(${coord}, 0, ${size})]` } // Brush End
8847            );
8848        });
8849        // React to pan/zooms of continuous scales. Non-continuous scales
8850        // (bin-linear, band, point) cannot be pan/zoomed and any other changes
8851        // to their domains (e.g., filtering) should clear the brushes.
8852        on.push({
8853            events: { signal: selCmpt.name + SCALE_TRIGGER },
8854            update: hasContinuousDomain(scaleType) && !isBinScale(scaleType)
8855                ? `[scale(${scaleStr}, ${dname}[0]), scale(${scaleStr}, ${dname}[1])]`
8856                : `[0, 0]`
8857        });
8858        return hasScales
8859            ? [{ name: dname, on: [] }]
8860            : [
8861                {
8862                    name: vname,
8863                    value: [],
8864                    on: on
8865                },
8866                {
8867                    name: dname,
8868                    on: [{ events: { signal: vname }, update: `${vname}[0] === ${vname}[1] ? null : invert(${scaleStr}, ${vname})` }]
8869                }
8870            ];
8871    }
8872    function events(selCmpt, cb) {
8873        return selCmpt.events.reduce((on, evt) => {
8874            if (!evt.between) {
8875                warn(`${evt} is not an ordered event stream for interval selections`);
8876                return on;
8877            }
8878            return cb(on, evt);
8879        }, []);
8880    }
8881
8882    function signals(model, selCmpt) {
8883        const name = selCmpt.name;
8884        const fieldsSg = name + TUPLE + TUPLE_FIELDS;
8885        const proj = selCmpt.project;
8886        const datum = '(item().isVoronoi ? datum.datum : datum)';
8887        const values = proj
8888            .map(p => {
8889            const fieldDef = model.fieldDef(p.channel);
8890            // Binned fields should capture extents, for a range test against the raw field.
8891            return fieldDef && fieldDef.bin
8892                ? `[${accessPathWithDatum(model.vgField(p.channel, {}), datum)}, ` +
8893                    `${accessPathWithDatum(model.vgField(p.channel, { binSuffix: 'end' }), datum)}]`
8894                : `${accessPathWithDatum(p.field, datum)}`;
8895        })
8896            .join(', ');
8897        // Only add a discrete selection to the store if a datum is present _and_
8898        // the interaction isn't occurring on a group mark. This guards against
8899        // polluting interactive state with invalid values in faceted displays
8900        // as the group marks are also data-driven. We force the update to account
8901        // for constant null states but varying toggles (e.g., shift-click in
8902        // whitespace followed by a click in whitespace; the store should only
8903        // be cleared on the second click).
8904        return [
8905            {
8906                name: name + TUPLE,
8907                value: {},
8908                on: [
8909                    {
8910                        events: selCmpt.events,
8911                        update: `datum && item().mark.marktype !== 'group' ? ` +
8912                            `{unit: ${unitName(model)}, fields: ${fieldsSg}, values: [${values}]} : null`,
8913                        force: true
8914                    }
8915                ]
8916            }
8917        ];
8918    }
8919    const multi = {
8920        signals: signals,
8921        modifyExpr: (model, selCmpt) => {
8922            const tpl = selCmpt.name + TUPLE;
8923            return tpl + ', ' + (selCmpt.resolve === 'global' ? 'null' : `{unit: ${unitName(model)}}`);
8924        }
8925    };
8926
8927    const single = {
8928        signals: signals,
8929        modifyExpr: (model, selCmpt) => {
8930            const tpl = selCmpt.name + TUPLE;
8931            return tpl + ', ' + (selCmpt.resolve === 'global' ? 'true' : `{unit: ${unitName(model)}}`);
8932        }
8933    };
8934
8935    const VORONOI = 'voronoi';
8936    const nearest = {
8937        has: selCmpt => {
8938            return selCmpt.type !== 'interval' && selCmpt.nearest;
8939        },
8940        marks: (model, selCmpt, marks) => {
8941            const { x, y } = positionalProjections(selCmpt);
8942            const markType = model.mark;
8943            if (isPathMark(markType)) {
8944                warn(message.nearestNotSupportForContinuous(markType));
8945                return marks;
8946            }
8947            const cellDef = {
8948                name: model.getName(VORONOI),
8949                type: 'path',
8950                from: { data: model.getName('marks') },
8951                encode: {
8952                    enter: {
8953                        fill: { value: 'transparent' },
8954                        strokeWidth: { value: 0.35 },
8955                        stroke: { value: 'transparent' },
8956                        isVoronoi: { value: true }
8957                    }
8958                },
8959                transform: [
8960                    {
8961                        type: 'voronoi',
8962                        x: { expr: x || (!x && !y) ? 'datum.datum.x || 0' : '0' },
8963                        y: { expr: y || (!x && !y) ? 'datum.datum.y || 0' : '0' },
8964                        size: [model.getSizeSignalRef('width'), model.getSizeSignalRef('height')]
8965                    }
8966                ]
8967            };
8968            let index = 0;
8969            let exists = false;
8970            marks.forEach((mark, i) => {
8971                const name = mark.name || '';
8972                if (name === model.component.mark[0].name) {
8973                    index = i;
8974                }
8975                else if (name.indexOf(VORONOI) >= 0) {
8976                    exists = true;
8977                }
8978            });
8979            if (!exists) {
8980                marks.splice(index + 1, 0, cellDef);
8981            }
8982            return marks;
8983        }
8984    };
8985
8986    const inputBindings = {
8987        has: selCmpt => {
8988            return selCmpt.type === 'single' && selCmpt.resolve === 'global' && selCmpt.bind && selCmpt.bind !== 'scales';
8989        },
8990        topLevelSignals: (model, selCmpt, signals) => {
8991            const name = selCmpt.name;
8992            const proj = selCmpt.project;
8993            const bind = selCmpt.bind;
8994            const datum = nearest.has(selCmpt) ? '(item().isVoronoi ? datum.datum : datum)' : 'datum';
8995            for (const p of proj) {
8996                const sgname = varName(`${name}_${p.field}`);
8997                const hasSignal = signals.filter(s => s.name === sgname);
8998                if (!hasSignal.length) {
8999                    signals.unshift({
9000                        name: sgname,
9001                        value: '',
9002                        on: [
9003                            {
9004                                events: selCmpt.events,
9005                                update: `datum && item().mark.marktype !== 'group' ? ${accessPathWithDatum(p.field, datum)} : null`
9006                            }
9007                        ],
9008                        bind: bind[p.field] || bind[p.channel] || bind
9009                    });
9010                }
9011            }
9012            return signals;
9013        },
9014        signals: (model, selCmpt, signals) => {
9015            const name = selCmpt.name;
9016            const proj = selCmpt.project;
9017            const signal = signals.filter(s => s.name === name + TUPLE)[0];
9018            const fields = name + TUPLE + TUPLE_FIELDS;
9019            const values = proj.map(p => varName(`${name}_${p.field}`));
9020            const valid = values.map(v => `${v} !== null`).join(' && ');
9021            if (values.length) {
9022                signal.update = `${valid} ? {fields: ${fields}, values: [${values.join(', ')}]} : null`;
9023            }
9024            delete signal.value;
9025            delete signal.on;
9026            return signals;
9027        }
9028    };
9029
9030    const TOGGLE = '_toggle';
9031    const toggle = {
9032        has: selCmpt => {
9033            return selCmpt.type === 'multi' && selCmpt.toggle;
9034        },
9035        signals: (model, selCmpt, signals) => {
9036            return signals.concat({
9037                name: selCmpt.name + TOGGLE,
9038                value: false,
9039                on: [{ events: selCmpt.events, update: selCmpt.toggle }]
9040            });
9041        },
9042        modifyExpr: (model, selCmpt, expr) => {
9043            const tpl = selCmpt.name + TUPLE;
9044            const signal = selCmpt.name + TOGGLE;
9045            return (`${signal} ? null : ${tpl}, ` +
9046                (selCmpt.resolve === 'global' ? `${signal} ? null : true, ` : `${signal} ? null : {unit: ${unitName(model)}}, `) +
9047                `${signal} ? ${tpl} : null`);
9048        }
9049    };
9050
9051    const ANCHOR = '_translate_anchor';
9052    const DELTA = '_translate_delta';
9053    const translate = {
9054        has: selCmpt => {
9055            return selCmpt.type === 'interval' && selCmpt.translate;
9056        },
9057        signals: (model, selCmpt, signals) => {
9058            const name = selCmpt.name;
9059            const hasScales = scaleBindings.has(selCmpt);
9060            const anchor = name + ANCHOR;
9061            const { x, y } = positionalProjections(selCmpt);
9062            let events = parseSelector(selCmpt.translate, 'scope');
9063            if (!hasScales) {
9064                events = events.map(e => ((e.between[0].markname = name + BRUSH), e));
9065            }
9066            signals.push({
9067                name: anchor,
9068                value: {},
9069                on: [
9070                    {
9071                        events: events.map(e => e.between[0]),
9072                        update: '{x: x(unit), y: y(unit)' +
9073                            (x !== null
9074                                ? ', extent_x: ' +
9075                                    (hasScales ? domain$1(model, X) : `slice(${channelSignalName(selCmpt, 'x', 'visual')})`)
9076                                : '') +
9077                            (y !== null
9078                                ? ', extent_y: ' +
9079                                    (hasScales ? domain$1(model, Y) : `slice(${channelSignalName(selCmpt, 'y', 'visual')})`)
9080                                : '') +
9081                            '}'
9082                    }
9083                ]
9084            }, {
9085                name: name + DELTA,
9086                value: {},
9087                on: [
9088                    {
9089                        events: events,
9090                        update: `{x: ${anchor}.x - x(unit), y: ${anchor}.y - y(unit)}`
9091                    }
9092                ]
9093            });
9094            if (x !== null) {
9095                onDelta(model, selCmpt, X, 'width', signals);
9096            }
9097            if (y !== null) {
9098                onDelta(model, selCmpt, Y, 'height', signals);
9099            }
9100            return signals;
9101        }
9102    };
9103    function onDelta(model, selCmpt, channel, size, signals) {
9104        const name = selCmpt.name;
9105        const hasScales = scaleBindings.has(selCmpt);
9106        const signal = signals.filter(s => {
9107            return s.name === channelSignalName(selCmpt, channel, hasScales ? 'data' : 'visual');
9108        })[0];
9109        const anchor = name + ANCHOR;
9110        const delta = name + DELTA;
9111        const sizeSg = model.getSizeSignalRef(size).signal;
9112        const scaleCmpt = model.getScaleComponent(channel);
9113        const scaleType = scaleCmpt.get('type');
9114        const sign = hasScales && channel === X ? '-' : ''; // Invert delta when panning x-scales.
9115        const extent = `${anchor}.extent_${channel}`;
9116        const offset = `${sign}${delta}.${channel} / ` + (hasScales ? `${sizeSg}` : `span(${extent})`);
9117        const panFn = !hasScales
9118            ? 'panLinear'
9119            : scaleType === 'log'
9120                ? 'panLog'
9121                : scaleType === 'pow'
9122                    ? 'panPow'
9123                    : 'panLinear';
9124        const update = `${panFn}(${extent}, ${offset}` +
9125            (hasScales && scaleType === 'pow' ? `, ${scaleCmpt.get('exponent') || 1}` : '') +
9126            ')';
9127        signal.on.push({
9128            events: { signal: delta },
9129            update: hasScales ? update : `clampRange(${update}, 0, ${sizeSg})`
9130        });
9131    }
9132
9133    const ANCHOR$1 = '_zoom_anchor';
9134    const DELTA$1 = '_zoom_delta';
9135    const zoom$2 = {
9136        has: selCmpt => {
9137            return selCmpt.type === 'interval' && selCmpt.zoom;
9138        },
9139        signals: (model, selCmpt, signals) => {
9140            const name = selCmpt.name;
9141            const hasScales = scaleBindings.has(selCmpt);
9142            const delta = name + DELTA$1;
9143            const { x, y } = positionalProjections(selCmpt);
9144            const sx = $(model.scaleName(X));
9145            const sy = $(model.scaleName(Y));
9146            let events = parseSelector(selCmpt.zoom, 'scope');
9147            if (!hasScales) {
9148                events = events.map(e => ((e.markname = name + BRUSH), e));
9149            }
9150            signals.push({
9151                name: name + ANCHOR$1,
9152                on: [
9153                    {
9154                        events: events,
9155                        update: !hasScales
9156                            ? `{x: x(unit), y: y(unit)}`
9157                            : '{' +
9158                                [sx ? `x: invert(${sx}, x(unit))` : '', sy ? `y: invert(${sy}, y(unit))` : '']
9159                                    .filter(expr => !!expr)
9160                                    .join(', ') +
9161                                '}'
9162                    }
9163                ]
9164            }, {
9165                name: delta,
9166                on: [
9167                    {
9168                        events: events,
9169                        force: true,
9170                        update: 'pow(1.001, event.deltaY * pow(16, event.deltaMode))'
9171                    }
9172                ]
9173            });
9174            if (x !== null) {
9175                onDelta$1(model, selCmpt, 'x', 'width', signals);
9176            }
9177            if (y !== null) {
9178                onDelta$1(model, selCmpt, 'y', 'height', signals);
9179            }
9180            return signals;
9181        }
9182    };
9183    function onDelta$1(model, selCmpt, channel, size, signals) {
9184        const name = selCmpt.name;
9185        const hasScales = scaleBindings.has(selCmpt);
9186        const signal = signals.filter(s => {
9187            return s.name === channelSignalName(selCmpt, channel, hasScales ? 'data' : 'visual');
9188        })[0];
9189        const sizeSg = model.getSizeSignalRef(size).signal;
9190        const scaleCmpt = model.getScaleComponent(channel);
9191        const scaleType = scaleCmpt.get('type');
9192        const base = hasScales ? domain$1(model, channel) : signal.name;
9193        const delta = name + DELTA$1;
9194        const anchor = `${name}${ANCHOR$1}.${channel}`;
9195        const zoomFn = !hasScales
9196            ? 'zoomLinear'
9197            : scaleType === 'log'
9198                ? 'zoomLog'
9199                : scaleType === 'pow'
9200                    ? 'zoomPow'
9201                    : 'zoomLinear';
9202        const update = `${zoomFn}(${base}, ${anchor}, ${delta}` +
9203            (hasScales && scaleType === 'pow' ? `, ${scaleCmpt.get('exponent') || 1}` : '') +
9204            ')';
9205        signal.on.push({
9206            events: { signal: delta },
9207            update: hasScales ? update : `clampRange(${update}, 0, ${sizeSg})`
9208        });
9209    }
9210
9211    const compilers = {
9212        project,
9213        toggle,
9214        scales: scaleBindings,
9215        translate,
9216        zoom: zoom$2,
9217        inputs: inputBindings,
9218        nearest
9219    };
9220    function forEachTransform(selCmpt, cb) {
9221        for (const t in compilers) {
9222            if (compilers[t].has(selCmpt)) {
9223                cb(compilers[t]);
9224            }
9225        }
9226    }
9227
9228    const STORE = '_store';
9229    const TUPLE = '_tuple';
9230    const MODIFY = '_modify';
9231    const SELECTION_DOMAIN = '_selection_domain_';
9232    const VL_SELECTION_RESOLVE = 'vlSelectionResolve';
9233    function parseUnitSelection(model, selDefs) {
9234        const selCmpts = {};
9235        const selectionConfig = model.config.selection;
9236        if (selDefs) {
9237            selDefs = duplicate(selDefs); // duplicate to avoid side effects to original spec
9238        }
9239        for (let name in selDefs) {
9240            if (!selDefs.hasOwnProperty(name)) {
9241                continue;
9242            }
9243            const selDef = selDefs[name];
9244            const cfg = selectionConfig[selDef.type];
9245            // Set default values from config if a property hasn't been specified,
9246            // or if it is true. E.g., "translate": true should use the default
9247            // event handlers for translate. However, true may be a valid value for
9248            // a property (e.g., "nearest": true).
9249            for (const key$$1 in cfg) {
9250                // A selection should contain either `encodings` or `fields`, only use
9251                // default values for these two values if neither of them is specified.
9252                if ((key$$1 === 'encodings' && selDef.fields) || (key$$1 === 'fields' && selDef.encodings)) {
9253                    continue;
9254                }
9255                if (key$$1 === 'mark') {
9256                    selDef[key$$1] = Object.assign({}, cfg[key$$1], selDef[key$$1]);
9257                }
9258                if (selDef[key$$1] === undefined || selDef[key$$1] === true) {
9259                    selDef[key$$1] = cfg[key$$1] || selDef[key$$1];
9260                }
9261            }
9262            name = varName(name);
9263            const selCmpt = (selCmpts[name] = Object.assign({}
9263, selDef, { name: name, events: isString(selDef.on) ? parseSelector(selDef.on, 'scope') : selDef.on }));
9264            forEachTransform(selCmpt, txCompiler => {
9265                if (txCompiler.parse) {
9266                    txCompiler.parse(model, selDef, selCmpt);
9267                }
9268            });
9269        }
9270        return selCmpts;
9271    }
9272    function assembleUnitSelectionSignals(model, signals$$1) {
9273        forEachSelection(model, (selCmpt, selCompiler) => {
9274            const name = selCmpt.name;
9275            let modifyExpr = selCompiler.modifyExpr(model, selCmpt);
9276            signals$$1.push(...selCompiler.signals(model, selCmpt));
9277            forEachTransform(selCmpt, txCompiler => {
9278                if (txCompiler.signals) {
9279                    signals$$1 = txCompiler.signals(model, selCmpt, signals$$1);
9280                }
9281                if (txCompiler.modifyExpr) {
9282                    modifyExpr = txCompiler.modifyExpr(model, selCmpt, modifyExpr);
9283                }
9284            });
9285            signals$$1.push({
9286                name: name + MODIFY,
9287                on: [
9288                    {
9289                        events: { signal: name + TUPLE },
9290                        update: `modify(${$(selCmpt.name + STORE)}, ${modifyExpr})`
9291                    }
9292                ]
9293            });
9294        });
9295        return signals$$1;
9296    }
9297    function assembleFacetSignals(model, signals$$1) {
9298        if (model.component.selection && keys(model.component.selection).length) {
9299            const name = $(model.getName('cell'));
9300            signals$$1.unshift({
9301                name: 'facet',
9302                value: {},
9303                on: [
9304                    {
9305                        events: parseSelector('mousemove', 'scope'),
9306                        update: `isTuple(facet) ? facet : group(${name}).datum`
9307                    }
9308                ]
9309            });
9310        }
9311        return signals$$1;
9312    }
9313    function assembleTopLevelSignals(model, signals$$1) {
9314        let hasSelections = false;
9315        forEachSelection(model, (selCmpt, selCompiler) => {
9316            const name = selCmpt.name;
9317            const store = $(name + STORE);
9318            const hasSg = signals$$1.filter(s => s.name === name);
9319            if (!hasSg.length) {
9320                signals$$1.push({
9321                    name: selCmpt.name,
9322                    update: `${VL_SELECTION_RESOLVE}(${store}` +
9323                        (selCmpt.resolve === 'global' ? ')' : `, ${$(selCmpt.resolve)})`)
9324                });
9325            }
9326            hasSelections = true;
9327            if (selCompiler.topLevelSignals) {
9328                signals$$1 = selCompiler.topLevelSignals(model, selCmpt, signals$$1);
9329            }
9330            forEachTransform(selCmpt, txCompiler => {
9331                if (txCompiler.topLevelSignals) {
9332                    signals$$1 = txCompiler.topLevelSignals(model, selCmpt, signals$$1);
9333                }
9334            });
9335        });
9336        if (hasSelections) {
9337            const hasUnit = signals$$1.filter(s => s.name === 'unit');
9338            if (!hasUnit.length) {
9339                signals$$1.unshift({
9340                    name: 'unit',
9341                    value: {},
9342                    on: [{ events: 'mousemove', update: 'isTuple(group()) ? group() : unit' }]
9343                });
9344            }
9345        }
9346        return signals$$1;
9347    }
9348    function assembleUnitSelectionData(model, data) {
9349        forEachSelection(model, selCmpt => {
9350            const contains$$1 = data.filter(d => d.name === selCmpt.name + STORE);
9351            if (!contains$$1.length) {
9352                data.push({ name: selCmpt.name + STORE });
9353            }
9354        });
9355        return data;
9356    }
9357    function assembleUnitSelectionMarks(model, marks) {
9358        forEachSelection(model, (selCmpt, selCompiler) => {
9359            marks = selCompiler.marks ? selCompiler.marks(model, selCmpt, marks) : marks;
9360            forEachTransform(selCmpt, txCompiler => {
9361                if (txCompiler.marks) {
9362                    marks = txCompiler.marks(model, selCmpt, marks);
9363                }
9364            });
9365        });
9366        return marks;
9367    }
9368    function assembleLayerSelectionMarks(model, marks) {
9369        for (const child of model.children) {
9370            if (isUnitModel(child)) {
9371                marks = assembleUnitSelectionMarks(child, marks);
9372            }
9373        }
9374        return marks;
9375    }
9376    function selectionPredicate(model, selections, dfnode) {
9377        const stores = [];
9378        function expr(name) {
9379            const vname = varName(name);
9380            const selCmpt = model.getSelectionComponent(vname, name);
9381            const store = $(vname + STORE);
9382            if (selCmpt.timeUnit) {
9383                const child = dfnode || model.component.data.raw;
9384                const tunode = selCmpt.timeUnit.clone();
9385                if (child.parent) {
9386                    tunode.insertAsParentOf(child);
9387                }
9388                else {
9389                    child.parent = tunode;
9390                }
9391            }
9392            if (selCmpt.empty !== 'none') {
9393                stores.push(store);
9394            }
9395            return (`vlSelectionTest(${store}, datum` + (selCmpt.resolve === 'global' ? ')' : `, ${$(selCmpt.resolve)})`));
9396        }
9397        const predicateStr = logicalExpr(selections, expr);
9398        return ((stores.length ? '!(' + stores.map(s => `length(data(${s}))`).join(' || ') + ') || ' : '') + `(${predicateStr})`);
9399    }
9400    // Selections are parsed _after_ scales. If a scale domain is set to
9401    // use a selection, the SELECTION_DOMAIN constant is used as the
9402    // domainRaw.signal during scale.parse and then replaced with the necessary
9403    // selection expression function during scale.assemble. To not pollute the
9404    // type signatures to account for this setup, the selection domain definition
9405    // is coerced to a string and appended to SELECTION_DOMAIN.
9406    function isRawSelectionDomain(domainRaw) {
9407        return domainRaw.signal.indexOf(SELECTION_DOMAIN) >= 0;
9408    }
9409    function selectionScaleDomain(model, domainRaw) {
9410        const selDomain = JSON.parse(domainRaw.signal.replace(SELECTION_DOMAIN, ''));
9411        const name = varName(selDomain.selection);
9412        const encoding = selDomain.encoding;
9413        let field$$1 = selDomain.field;
9414        let selCmpt = model.component.selection && model.component.selection[name];
9415        if (selCmpt) {
9416            warn('Use "bind": "scales" to setup a binding for scales and selections within the same view.');
9417        }
9418        else {
9419            selCmpt = model.getSelectionComponent(name, selDomain.selection);
9420            if (!encoding && !field$$1) {
9421                field$$1 = selCmpt.project[0].field;
9422                if (selCmpt.project.length > 1) {
9423                    warn('A "field" or "encoding" must be specified when using a selection as a scale domain. ' +
9424                        `Using "field": ${$(field$$1)}.`);
9425                }
9426            }
9427            else if (encoding && !field$$1) {
9428                const encodings = selCmpt.project.filter(p => p.channel === encoding);
9429                if (!encodings.length || encodings.length > 1) {
9430                    field$$1 = selCmpt.project[0].field;
9431                    warn((!encodings.length ? 'No ' : 'Multiple ') +
9432                        `matching ${$(encoding)} encoding found for selection ${$(selDomain.selection)}. ` +
9433                        `Using "field": ${$(field$$1)}.`);
9434                }
9435                else {
9436                    field$$1 = encodings[0].field;
9437                }
9438            }
9439            return { signal: accessPathWithDatum(field$$1, name) };
9440        }
9441        return { signal: 'null' };
9442    }
9443    // Utility functions
9444    function forEachSelection(model, cb) {
9445        const selections = model.component.selection;
9446        for (const name in selections) {
9447            if (selections.hasOwnProperty(name)) {
9448                const sel = selections[name];
9449                cb(sel, compiler(sel.type));
9450            }
9451        }
9452    }
9453    function compiler(type) {
9454        switch (type) {
9455            case 'single':
9456                return single;
9457            case 'multi':
9458                return multi;
9459            case 'interval':
9460                return interval;
9461        }
9462        return null;
9463    }
9464    function getFacetModel(model) {
9465        let parent = model.parent;
9466        while (parent) {
9467            if (isFacetModel(parent)) {
9468                break;
9469            }
9470            parent = parent.parent;
9471        }
9472        return parent;
9473    }
9474    function unitName(model) {
9475        let name = $(model.name);
9476        const facet = getFacetModel(model);
9477        if (facet) {
9478            name +=
9479                (facet.facet.row ? ` + '_' + (${accessPathWithDatum(facet.vgField('row'), 'facet')})` : '') +
9480                    (facet.facet.column ? ` + '_' + (${accessPathWithDatum(facet.vgField('column'), 'facet')})` : '');
9481        }
9482        return name;
9483    }
9484    function requiresSelectionId(model) {
9485        let identifier = false;
9486        forEachSelection(model, selCmpt => {
9487            identifier = identifier || selCmpt.project.some(proj => proj.field === SELECTION_ID);
9488        });
9489        return identifier;
9490    }
9491    function channelSignalName(selCmpt, channel, range) {
9492        const sgNames = selCmpt._signalNames || (selCmpt._signalNames = {});
9493        if (sgNames[channel] && sgNames[channel][range]) {
9494            return sgNames[channel][range];
9495        }
9496        sgNames[channel] = sgNames[channel] || {};
9497        const basename = varName(selCmpt.name + '_' + (range === 'visual' ? channel : selCmpt.fields[channel]));
9498        let name = basename;
9499        let counter = 1;
9500        while (sgNames[name]) {
9501            name = `${basename}_${counter++}`;
9502        }
9503        return (sgNames[name] = sgNames[channel][range] = name);
9504    }
9505    function positionalProjections(selCmpt) {
9506        let x = null;
9507        let xi = null;
9508        let y = null;
9509        let yi = null;
9510        selCmpt.project.forEach((p, i) => {
9511            if (p.channel === X) {
9512                x = p;
9513                xi = i;
9514            }
9515            else if (p.channel === Y) {
9516                y = p;
9517                yi = i;
9518            }
9519        });
9520        return { x, xi, y, yi };
9521    }
9522
9523    /**
9524     * Converts a predicate into an expression.
9525     */
9526    // model is only used for selection filters.
9527    function expression(model, filterOp, node) {
9528        return logicalExpr(filterOp, (predicate) => {
9529            if (isString(predicate)) {
9530                return predicate;
9531            }
9532            else if (isSelectionPredicate(predicate)) {
9533                return selectionPredicate(model, predicate.selection, node);
9534            }
9535            else {
9536                // Filter Object
9537                return fieldFilterExpression(predicate);
9538            }
9539        });
9540    }
9541
9542    // TODO: we need to find a way to refactor these so that scaleName is a part of scale
9543    // but that's complicated.  For now, this is a huge step moving forward.
9544    /**
9545     * @return Vega ValueRef for normal x- or y-position without projection
9546     */
9547    function position(channel, channelDef, channel2Def, scaleName, scale, stack, defaultRef) {
9548        if (isFieldDef(channelDef) && stack && channel === stack.fieldChannel) {
9549            // x or y use stack_end so that stacked line's point mark use stack_end too.
9550            return fieldRef(channelDef, scaleName, { suffix: 'end' });
9551        }
9552        return midPoint(channel, channelDef, channel2Def, scaleName, scale, stack, defaultRef);
9553    }
9554    /**
9555     * @return Vega ValueRef for normal x2- or y2-position without projection
9556     */
9557    function position2(channel, aFieldDef, a2fieldDef, scaleName, scale, stack, defaultRef) {
9558        if (isFieldDef(aFieldDef) &&
9559            stack &&
9560            // If fieldChannel is X and channel is X2 (or Y and Y2)
9561            channel.charAt(0) === stack.fieldChannel.charAt(0)) {
9562            return fieldRef(aFieldDef, scaleName, { suffix: 'start' });
9563        }
9564        return midPoint(channel, a2fieldDef, undefined, scaleName, scale, stack, defaultRef);
9565    }
9566    function getOffset(channel, markDef) {
9567        const offsetChannel = channel + 'Offset';
9568        // TODO: in the future read from encoding channel too
9569        const markDefOffsetValue = markDef[offsetChannel];
9570        if (markDefOffsetValue) {
9571            return markDefOffsetValue;
9572        }
9573        return undefined;
9574    }
9575    /**
9576     * Value Ref for binned fields
9577     */
9578    function bin$2(fieldDef, scaleName, side, offset) {
9579        const binSuffix = side === 'start' ? undefined : 'end';
9580        return fieldRef(fieldDef, scaleName, { binSuffix }, offset ? { offset } : {});
9581    }
9582    function fieldRef(fieldDef, scaleName, opt, mixins) {
9583        const ref = Object.assign({}, (scaleName ? { scale: scaleName } : {}), { field: vgField(fieldDef, opt) });
9584        if (mixins) {
9585            return Object.assign({}, ref, mixins);
9586        }
9587        return ref;
9588    }
9589    function bandRef(scaleName, band = true) {
9590        return {
9591            scale: scaleName,
9592            band: band
9593        };
9594    }
9595    /**
9596     * Signal that returns the middle of a bin from start and end field. Should only be used with x and y.
9597     */
9598    function binMidSignal(scaleName, fieldDef, fieldDef2) {
9599        const start = vgField(fieldDef, { expr: 'datum' });
9600        const end = fieldDef2 !== undefined
9601            ? vgField(fieldDef2, { expr: 'datum' })
9602            : vgField(fieldDef, { binSuffix: 'end', expr: 'datum' });
9603        return {
9604            signal: `scale("${scaleName}", (${start} + ${end}) / 2)`
9605        };
9606    }
9607    /**
9608     * @returns {VgValueRef} Value Ref for xc / yc or mid point for other channels.
9609     */
9610    function midPoint(channel, channelDef, channel2Def, scaleName, scale, stack, defaultRef) {
9611        // TODO: datum support
9612        if (channelDef) {
9613            /* istanbul ignore else */
9614            if (isFieldDef(channelDef)) {
9615                if (isTypedFieldDef(channelDef)) {
9616                    if (isBinning(channelDef.bin)) {
9617                        // Use middle only for x an y to place marks in the center between start and end of the bin range.
9618                        // We do not use the mid point for other channels (e.g. size) so that properties of legends and marks match.
9619                        if (contains([X, Y], channel) && channelDef.type === QUANTITATIVE) {
9620                            if (stack && stack.impute) {
9621                                // For stack, we computed bin_mid so we can impute.
9622                                return fieldRef(channelDef, scaleName, { binSuffix: 'mid' });
9623                            }
9624                            // For non-stack, we can just calculate bin mid on the fly using signal.
9625                            return binMidSignal(scaleName, channelDef);
9626                        }
9627                        return fieldRef(channelDef, scaleName, binRequiresRange(c
9627hannelDef, channel) ? { binSuffix: 'range' } : {});
9628                    }
9629                    else if (isBinned(channelDef.bin)) {
9630                        if (isFieldDef(channel2Def)) {
9631                            return binMidSignal(scaleName, channelDef, channel2Def);
9632                        }
9633                        else {
9634                            const channel2 = channel === X ? X2 : Y2;
9635                            warn(message.channelRequiredForBinned(channel2));
9636                        }
9637                    }
9638                }
9639                if (scale) {
9640                    const scaleType = scale.get('type');
9641                    if (hasDiscreteDomain(scaleType)) {
9642                        if (scaleType === 'band') {
9643                            // For band, to get mid point, need to offset by half of the band
9644                            return fieldRef(channelDef, scaleName, { binSuffix: 'range' }, { band: 0.5 });
9645                        }
9646                        return fieldRef(channelDef, scaleName, { binSuffix: 'range' });
9647                    }
9648                }
9649                return fieldRef(channelDef, scaleName, {}); // no need for bin suffix
9650            }
9651            else if (isValueDef(channelDef)) {
9652                const value = channelDef.value;
9653                if (contains(['x', 'x2'], channel) && value === 'width') {
9654                    return { field: { group: 'width' } };
9655                }
9656                else if (contains(['y', 'y2'], channel) && value === 'height') {
9657                    return { field: { group: 'height' } };
9658                }
9659                return { value };
9660            }
9661            // If channelDef is neither field def or value def, it's a condition-only def.
9662            // In such case, we will use default ref.
9663        }
9664        return isFunction(defaultRef) ? defaultRef() : defaultRef;
9665    }
9666    function tooltipForEncoding(encoding, config) {
9667        const keyValues = [];
9668        const usedKey = {};
9669        function add(fieldDef, channel) {
9670            const mainChannel = getMainRangeChannel(channel);
9671            if (channel !== mainChannel) {
9672                fieldDef = Object.assign({}, fieldDef, { type: encoding[mainChannel].type });
9673            }
9674            const key$$1 = title(fieldDef, config, { allowDisabling: false });
9675            const value = text(fieldDef, config).signal;
9676            if (!usedKey[key$$1]) {
9677                keyValues.push(`${$(key$$1)}: ${value}`);
9678            }
9679            usedKey[key$$1] = true;
9680        }
9681        forEach(encoding, (channelDef, channel) => {
9682            if (isFieldDef(channelDef)) {
9683                add(channelDef, channel);
9684            }
9685            else if (hasConditionalFieldDef(channelDef)) {
9686                add(channelDef.condition, channel);
9687            }
9688        });
9689        return keyValues.length ? { signal: `{${keyValues.join(', ')}}` } : undefined;
9690    }
9691    function text(channelDef, config) {
9692        // text
9693        if (channelDef) {
9694            if (isValueDef(channelDef)) {
9695                return { value: channelDef.value };
9696            }
9697            if (isTypedFieldDef(channelDef)) {
9698                return formatSignalRef(channelDef, format(channelDef), 'datum', config);
9699            }
9700        }
9701        return undefined;
9702    }
9703    function mid(sizeRef) {
9704        return Object.assign({}, sizeRef, { mult: 0.5 });
9705    }
9706    /**
9707     * Whether the scale definitely includes zero in the domain
9708     */
9709    function domainDefinitelyIncludeZero(scale) {
9710        if (scale.get('zero') !== false) {
9711            return true;
9712        }
9713        const domains = scale.domains;
9714        if (isArray(domains)) {
9715            return some(domains, d => isArray(d) && d.length === 2 && d[0] <= 0 && d[1] >= 0);
9716        }
9717        return false;
9718    }
9719    function getDefaultRef(defaultRef, channel, scaleName, scale, mark) {
9720        return () => {
9721            if (isString(defaultRef)) {
9722                if (scaleName) {
9723                    const scaleType = scale.get('type');
9724                    if (contains([ScaleType.LOG, ScaleType.TIME, ScaleType.UTC], scaleType)) {
9725                        // Log scales cannot have zero.
9726                        // Zero in time scale is arbitrary, and does not affect ratio.
9727                        // (Time is an interval level of measurement, not ratio).
9728                        // See https://en.wikipedia.org/wiki/Level_of_measurement for more info.
9729                        if (mark === 'bar' || mark === 'area') {
9730                            warn(message.nonZeroScaleUsedWithLengthMark(mark, channel, { scaleType }));
9731                        }
9732                    }
9733                    else {
9734                        if (domainDefinitelyIncludeZero(scale)) {
9735                            return {
9736                                scale: scaleName,
9737                                value: 0
9738                            };
9739                        }
9740                        if (mark === 'bar' || mark === 'area') {
9741                            warn(message.nonZeroScaleUsedWithLengthMark(mark, channel, { zeroFalse: scale.explicit.zero === false }));
9742                        }
9743                    }
9744                }
9745                if (defaultRef === 'zeroOrMin') {
9746                    return channel === 'x' ? { value: 0 } : { field: { group: 'height' } };
9747                }
9748                else {
9749                    // zeroOrMax
9750                    return channel === 'x' ? { field: { group: 'width' } } : { value: 0 };
9751                }
9752            }
9753            return defaultRef;
9754        };
9755    }
9756
9757    function isVisible(c) {
9758        return c !== 'transparent' && c !== null && c !== undefined;
9759    }
9760    function color(model) {
9761        const { markDef, encoding, config } = model;
9762        const { filled, type: markType }
9762 = markDef;
9763        const configValue = {
9764            fill: getMarkConfig('fill', markDef, config),
9765            stroke: getMarkConfig('stroke', markDef, config),
9766            color: getMarkConfig('color', markDef, config)
9767        };
9768        const transparentIfNeeded = contains(['bar', 'point', 'circle', 'square', 'geoshape'], markType)
9769            ? 'transparent'
9770            : undefined;
9771        const defaultFill = getFirstDefined(markDef.fill, configValue.fill, 
9772        // If there is no fill, always fill symbols, bar, geoshape
9773        // with transparent fills https://github.com/vega/vega-lite/issues/1316
9774        transparentIfNeeded);
9775        const defaultStroke = getFirstDefined(markDef.stroke, configValue.stroke);
9776        const colorVgChannel = filled ? 'fill' : 'stroke';
9777        const fillStrokeMarkDefAndConfig = Object.assign({}, (defaultFill ? { fill: { value: defaultFill } } : {}), (defaultStroke ? { stroke: { value: defaultStroke } } : {}));
9778        if (encoding.fill || encoding.stroke) {
9779            // ignore encoding.color, markDef.color, config.color
9780            if (markDef.color) {
9781                // warn for markDef.color  (no need to warn encoding.color as it will be dropped in normalized already)
9782                warn(message.droppingColor('property', { fill: 'fill' in encoding, stroke: 'stroke' in encoding }));
9783            }
9784            return Object.assign({}, nonPosition('fill', model, { defaultValue: getFirstDefined(defaultFill, transparentIfNeeded) }), nonPosition('stroke', model, { defaultValue: defaultStroke }));
9785        }
9786        else if (encoding.color) {
9787            return Object.assign({}, fillStrokeMarkDefAndConfig, nonPosition('color', model, {
9788                vgChannel: colorVgChannel,
9789                // apply default fill/stroke first, then color config, then transparent if needed.
9790                defaultValue: getFirstDefined(markDef[colorVgChannel], markDef.color, configValue[colorVgChannel], configValue.color, filled ? transparentIfNeeded : undefined)
9791            }));
9792        }
9793        else if (isVisible(markDef.fill) || isVisible(markDef.stroke)) {
9794            // Ignore markDef.color
9795            if (markDef.color) {
9796                warn(message.droppingColor('property', { fill: 'fill' in markDef, stroke: 'stroke' in markDef }));
9797            }
9798            return fillStrokeMarkDefAndConfig;
9799        }
9800        else if (markDef.color) {
9801            return Object.assign({}, fillStrokeMarkDefAndConfig, { 
9802                // override config with markDef.color
9803                [colorVgChannel]: { value: markDef.color } });
9804        }
9805        else if (isVisible(configValue.fill) || isVisible(configValue.stroke)) {
9806            // ignore config.color
9807            return fillStrokeMarkDefAndConfig;
9808        }
9809        else if (configValue.color) {
9810            return Object.assign({}, (transparentIfNeeded ? { fill: { value: 'transparent' } } : {}), { [colorVgChannel]: { value: configValue.color } });
9811        }
9812        return {};
9813    }
9814    function baseEncodeEntry(model, ignore) {
9815        const { fill: fill$$1, stroke } = color(model);
9816        return Object.assign({}, markDefProperties(model.markDef, ignore), wrapInvalid(model, 'fill', fill$$1), wrapInvalid(model, 'stroke', stroke), nonPosition('opacity', model), nonPosition('fillOpacity', model), nonPosition('strokeOpacity', model), nonPosition('strokeWidth', model), tooltip(model), text$1(model, 'href'));
9817    }
9818    function wrapInvalid(model, channel, valueRef) {
9819        const { config, mark } = model;
9820        if (config.invalidValues && valueRef && !isPathMark(mark)) {
9821            // For non-path marks, we have to exclude invalid values (null and NaN) for scales with continuous domains.
9822            // For path marks, we will use "defined" property and skip these values instead.
9823            const test = validPredicate(model, { invalid: true, channels: SCALE_CHANNELS });
9824            if (test) {
9825                return {
9826                    [channel]: [
9827                        // prepend the invalid case
9828                        // TODO: support custom value
9829                        { test, value: null },
9830                        ...array(valueRef)
9831                    ]
9832                };
9833            }
9834        }
9835        return valueRef ? { [channel]: valueRef } : {};
9836    }
9837    function markDefProperties(mark, ignore) {
9838        return VG_MARK_CONFIGS.reduce((m, prop) => {
9839            if (mark[prop] !== undefined && ignore[prop] !== 'ignore') {
9840                m[prop] = { value: mark[prop] };
9841            }
9842            return m;
9843        }, {});
9844    }
9845    function valueIfDefined(prop, value) {
9846        if (value !== undefined) {
9847            return { [prop]: { value: value } };
9848        }
9849        return undefined;
9850    }
9851    function validPredicate(model, { invalid = false, channels }) {
9852        const filterIndex = channels.reduce((aggregator, channel) => {
9853            const scaleComponent = model.getScaleComponent(channel);
9854            if (scaleComponent) {
9855                const scaleType = scaleComponent.get('type');
9856                const field$$1 = model.vgField(channel, { expr: 'datum' });
9857                // While discrete domain scales can handle invalid values, continuous scales can't.
9858                if (field$$1 && hasContinuousDomain(scaleType)) {
9859                    aggregator[field$$1] = true;
9860                }
9861            }
9862            return aggregator;
9863        }, {});
9864        const fields = keys(filterIndex);
9865        if (fields.length > 0) {
9866            const op = invalid ? '||' : '&&';
9867            return fields
9868                .map(field$$1 => {
9869                const eq = invalid ? '===' : '!==';
9870                return `${field$$1} ${eq} null ${op} ${invalid ? '' : '!'}isNaN(${field$$1})`;
9871            })
9872                .join(` ${op} `);
9873        }
9874        return undefined;
9875    }
9876    function defined(model) {
9877        if (model.config.invalidValues === 'filter') {
9878            const signal = validPredicate(model, { channels: ['x', 'y'] });
9879            if (signal) {
9880                return { defined: { signal } };
9881            }
9882        }
9883        return {};
9884    }
9885    /**
9886     * Return mixins for non-positional channels with scales.  (Text doesn't have scale.)
9887     */
9888    function nonPosition(channel, model, opt = {}) {
9889        const { markDef, encoding } = model;
9890        const { vgChannel = channel } = opt;
9891        const { defaultValue = markDef[vgChannel] } = opt;
9892        const defaultRef = opt.defaultRef || (defaultValue !== undefined ? { value: defaultValue } : undefined);
9893        const channelDef = encoding[channel];
9894        return wrapCondition(model, channelDef, vgChannel, cDef => {
9895            return midPoint(channel, cDef, undefined, model.scaleName(channel), model.getScaleComponent(channel), null, // No need to provide stack for non-position as it does not affect mid point
9896            defaultRef);
9897        });
9898    }
9899    /**
9900     * Return a mixin that include a Vega production rule for a Vega-Lite conditional channel definition.
9901     * or a simple mixin if channel def has no condition.
9902     */
9903    function wrapCondition(model, channelDef, vgChannel, refFn) {
9904        const condition = channelDef && channelDef.condition;
9905        const valueRef = refFn(channelDef);
9906        if (condition) {
9907            const conditions = isArray(condition) ? condition : [condition];
9908            const vgConditions = conditions.map(c => {
9909                const conditionValueRef = refFn(c);
9910                const test = isConditionalSelection(c) ? selectionPredicate(model, c.selection) : expression(model, c.test);
9911                return Object.assign({ test }, conditionValueRef);
9912            });
9913            return {
9914                [vgChannel]: [...vgConditions, ...(valueRef !== undefined ? [valueRef] : [])]
9915            };
9916        }
9917        else {
9918            return valueRef !== undefined ? { [vgChannel]: valueRef } : {};
9919        }
9920    }
9921    function tooltip(model) {
9922        const { encoding, markDef, config } = model;
9923        const channelDef = encoding.tooltip;
9924        if (isArray(channelDef)) {
9925            return { tooltip: tooltipForEncoding({ tooltip: channelDef }, config) };
9926        }
9927        else {
9928            return wrapCondition(model, channelDef, 'tooltip', cDef => {
9929                // use valueRef based on channelDef first
9930                const tooltipRefFromChannelDef = text(cDef, model.config);
9931                if (tooltipRefFromChannelDef) {
9932                    return tooltipRefFromChannelDef;
9933                }
9934                if (cDef === null) {
9935                    // Allow using encoding.tooltip = null to disable tooltip
9936                    return undefined;
9937                }
9938                // If tooltipDef does not exist, then use value from markDef or config
9939                const markTooltip = getFirstDefined(markDef.tooltip, getMarkConfig('tooltip', markDef, config));
9940                if (isString(markTooltip)) {
9941                    return { value: markTooltip };
9942                }
9943                else if (isObject(markTooltip)) {
9944                    // `tooltip` is `{fields: 'encodings' | 'fields'}`
9945                    if (markTooltip.content === 'encoding') {
9946                        return tooltipForEncoding(encoding, config);
9947                    }
9948                    else {
9949                        return { signal: 'datum' };
9950                    }
9951                }
9952                return undefined;
9953            });
9954        }
9955    }
9956    function text$1(model, channel = 'text') {
9957        const channelDef = model.encoding[channel];
9958        return wrapCondition(model, channelDef, channel, cDef => text(cDef, model.config));
9959    }
9960    function bandPosition(fieldDef, channel, model) {
9961        const scaleName = model.scaleName(channel);
9962        const sizeChannel = channel === 'x' ? 'width' : 'height';
9963        if (model.encoding.size || model.markDef.size !== undefined) {
9964            const orient = model.markDef.orient;
9965            if (orient) {
9966                const centeredBandPositionMixins = {
9967                    // Use xc/yc and place the mark at the middle of the band
9968                    // This way we never have to deal with size's condition for x/y position.
9969                    [channel + 'c']: fieldRef(fieldDef, scaleName, {}, { band: 0.5 })
9970                };
9971                if (getTypedFieldDef(model.encoding.size)) {
9972                    return Object.assign({}, centeredBandPositionMixins, nonPosition('size', model, { vgChannel: sizeChannel }));
9973                }
9974                else if (isValueDef(model.encoding.size)) {
9975                    return Object.assign({}, centeredBandPositionMixins, nonPosition('size', model, { vgChannel: sizeChannel }));
9976                }
9977                else if (model.markDef.size !== undefined) {
9978                    return Object.assign({}, centeredBandPositionMixins, { [sizeChannel]: { value: model.markDef.size } });
9979                }
9980            }
9981            else {
9982                warn(message.cannotApplySizeToNonOrientedMark(model.markDef.type));
9983            }
9984        }
9985        return {
9986            [channel]: fieldRef(fieldDef, scaleName, { binSuffix: 'range' }),
9987            [sizeChannel]: bandRef(scaleName)
9988        };
9989    }
9990    function centeredBandPosition(channel, model, defaultPosRef, defaultSizeRef) {
9991        const centerChannel = channel === 'x' ? 'xc' : 'yc';
9992        const sizeChannel = channel === 'x' ? 'width' : 'height';
9993        return Object.assign({}, pointPosition(channel, model, defaultPosRef, centerChannel), nonPosition('size', model, { defaultRef: defaultSizeRef, vgChannel: sizeChannel }));
9994    }
9995    function binPosition(fieldDef, fieldDef2, channel, scaleName, spacing, reverse) {
9996        const binSpacing = {
9997            x: reverse ? spacing : 0,
9998            x2: reverse ? 0 : spacing,
9999            y: reverse ? 0 : spacing,
10000            y2: reverse ? spacing : 0
10001        };
10002        const channel2 = channel === X ? X2 : Y2;
10003        if (isBinning(fieldDef.bin)) {
10004            return {
10005                [channel2]: bin$2(fieldDef, scaleName, 'start', binSpacing[`${channel}2`]),
10006                [channel]: bin$2(fieldDef, scaleName, 'end', binSpacing[channel])
10007            };
10008        }
10009        else if (isBinned(fieldDef.bin) && isFieldDef(fieldDef2)) {
10010            return {
10011                [channel2]: fieldRef(fieldDef, scaleName, {}, { offset: binSpacing[`${channel}2`] }),
10012                [channel]: fieldRef(fieldDef2, scaleName, {}, { offset: binSpacing[channel] })
10013            };
10014        }
10015        else {
10016            warn(message.channelRequiredForBinned(channel2));
10017            return undefined;
10018        }
10019    }
10020    /**
10021     * Return mixins for point (non-band) position channels.
10022     */
10023    function pointPosition(channel, model, defaultRef, vgChannel) {
10024        // TODO: refactor how refer to scale as discussed in https://github.com/vega/vega-lite/pull/1613
10025        const { encoding, mark, stack } = model;
10026        const channelDef = encoding[channel];
10027        const channel2Def = encoding[channel === X ? X2 : Y2];
10028        const scaleName = model.scaleName(channel);
10029        const scale = model.getScaleComponent(channel);
10030        const offset = getOffset(channel, model.markDef);
10031        const valueRef = !channelDef && (encoding.latitude || encoding.longitude)
10032            ? // use geopoint output if there are lat/long and there is no point position overriding lat/long.
10033                { field: model.getName(channel) }
10034            : Object.assign({}, position(channel, channelDef, channel2Def, scaleName, scale, stack, getDefaultRef(defaultRef, channel, scaleName, scale, mark)), (offset ? { offset } : {}));
10035        return {
10036            [vgChannel || channel]: valueRef
10037        };
10038    }
10039    /**
10040     * Return mixins for x2, y2.
10041     * If channel is not specified, return one channel based on orientation.
10042     */
10043    function pointPosition2(model, defaultRef, channel) {
10044        const { encoding, mark, stack } = model;
10045        const baseChannel = channel === 'x2' ? 'x' : 'y';
10046        const channelDef = encoding[baseChannel];
10047        const scaleName = model.scaleName(baseChannel);
10048        const scale = model.getScaleComponent(baseChannel);
10049        const offset = getOffset(channel, model.markDef);
10050        const valueRef = !channelDef && (encoding.latitude || encoding.longitude)
10051            ? // use geopoint output if there are lat2/long2 and there is no point position2 overriding lat2/long2.
10052                { field: model.getName(channel) }
10053            : Object.assign({}, position2(channel, channelDef, encoding[channel], scaleName, scale, stack, getDefaultRef(defaultRef, baseChannel, scaleName, scale, mark)), (offset ? { offset } : {}));
10054        return { [channel]: valueRef };
10055    }
10056
10057    function guideEncodeEntry(encoding, model) {
10058        return keys(encoding).reduce((encode, channel) => {
10059            const valueDef = encoding[channel];
10060            return Object.assign({}, encode, wrapCondition(model, valueDef, channel, (x) => ({ value: x.value })));
10061        }, {});
10062    }
10063
10064    function defaultScaleResolve(channel, model) {
10065        if (isLayerModel(model) || isFacetModel(model)) {
10066            return 'shared';
10067        }
10068        else if (isConcatModel(model) || isRepeatModel(model)) {
10069            return contains(POSITION_SCALE_CHANNELS, channel) ? 'independent' : 'shared';
10070        }
10071        /* istanbul ignore next: should never reach here. */
10072        throw new Error('invalid model type for resolve');
10073    }
10074    function parseGuideResolve(resolve, channel) {
10075        const channelScaleResolve = resolve.scale[channel];
10076        const guide = contains(POSITION_SCALE_CHANNELS, channel) ? 'axis' : 'legend';
10077        if (channelScaleResolve === 'independent') {
10078            if (resolve[guide][channel] === 'shared') {
10079                warn(message.independentScaleMeansIndependentGuide(channel));
10080            }
10081            return 'independent';
10082        }
10083        return resolve[guide][channel] || 'shared';
10084    }
10085
10086    /**
10087     * Generic class for storing properties that are explicitly specified
10088     * and implicitly determined by the compiler.
10089     * This is important for scale/axis/legend merging as
10090     * we want to prioritize properties that users explicitly specified.
10091     */
10092    class Split {
10093        constructor(explicit = {}, implicit = {}) {
10094            this.explicit = explicit;
10095            this.implicit = implicit;
10096        }
10097        clone() {
10098            return new Split(duplicate(this.explicit), duplicate(this.implicit));
10099        }
10100        combine() {
10101            // FIXME remove "as any".
10102            // Add "as any" to avoid an error "Spread types may only be created from object types".
10103            return Object.assign({}, this.explicit, this.implicit);
10104        }
10105        get(key) {
10106            // Explicit has higher precedence
10107            return getFirstDefined(this.explicit[key], this.implicit[key]);
10108        }
10109        getWithExplicit(key) {
10110            // Explicit has higher precedence
10111            if (this.explicit[key] !== undefined) {
10112                return { explicit: true, value: this.explicit[key] };
10113            }
10114            else if (this.implicit[key] !== undefined) {
10115                return { explicit: false, value: this.implicit[key] };
10116            }
10117            return { explicit: false, value: undefined };
10118        }
10119        setWithExplicit(key, value) {
10120            if (value.value !== undefined) {
10121                this.set(key, value.value, value.explicit);
10122            }
10123        }
10124        set(key, value, explicit) {
10125            delete this[explicit ? 'implicit' : 'explicit'][key];
10126            this[explicit ? 'explicit' : 'implicit'][key] = value;
10127            return this;
10128        }
10129        copyKeyFromSplit(key, s) {
10130            // Explicit has higher precedence
10131            if (s.explicit[key] !== undefined) {
10132                this.set(key, s.explicit[key], true);
10133            }
10134            else if (s.implicit[key] !== undefined) {
10135                this.set(key, s.implicit[key], false);
10136            }
10137        }
10138        copyKeyFromObject(key, s) {
10139            // Explicit has higher precedence
10140            if (s[key] !== undefined) {
10141                this.set(key, s[key], true);
10142            }
10143        }
10144        /**
10145         * Merge split object into this split object. Properties from the other split
10146         * overwrite properties from this split.
10147         */
10148        copyAll(other) {
10149            for (const key of keys(other.combine())) {
10150                const val = other.getWithExplicit(key);
10151                this.setWithExplicit(key, val);
10152            }
10153        }
10154    }
10155    function makeExplicit(value) {
10156        return {
10157            explicit: true,
10158            value
10159        };
10160    }
10161    function makeImplicit(value) {
10162        return {
10163            explicit: false,
10164            value
10165        };
10166    }
10167    function tieBreakByComparing(compare) {
10168        return (v1, v2, property, propertyOf) => {
10169            const diff = compare(v1.value, v2.value);
10170            if (diff > 0) {
10171                return v1;
10172            }
10173            else if (diff < 0) {
10174                return v2;
10175            }
10176            return defaultTieBreaker(v1, v2, property, propertyOf);
10177        };
10178    }
10179    function defaultTieBreaker(v1, v2, property, propertyOf) {
10180        if (v1.explicit && v2.explicit) {
10181            warn(message.mergeConflictingProperty(property, propertyOf, v1.value, v2.value));
10182        }
10183        // If equal score, prefer v1.
10184        return v1;
10185    }
10186    function mergeValuesWithExplicit(v1, v2, property, propertyOf, tieBreaker = defaultTieBreaker) {
10187        if (v1 === undefined || v1.value === undefined) {
10188            // For first run
10189            return v2;
10190        }
10191        if (v1.explicit && !v2.explicit) {
10192            return v1;
10193        }
10194        else if (v2.explicit && !v1.explicit) {
10195            return v2;
10196        }
10197        else if (stringify(v1.value) === stringify(v2.value)) {
10198            return v1;
10199        }
10200        else {
10201            return tieBreaker(v1, v2, property, propertyOf);
10202        }
10203    }
10204
10205    class LegendComponent extends Split {
10206    }
10207
10208    function values(legend, fieldDef) {
10209        const vals$$1 = legend.values;
10210        if (vals$$1) {
10211            return valueArray(fieldDef, vals$$1);
10212        }
10213        return undefined;
10214    }
10215    function type$2(params) {
10216        const { legend } = params;
10217        return getFirstDefined(legend.type, defaultType$1(params));
10218    }
10219    function defaultType$1({ channel, timeUnit, scaleType, alwaysReturn }) {
10220        // Following the logic in https://github.com/vega/vega-parser/blob/master/src/parsers/legend.js
10221        if (isColorChannel(channel)) {
10222            if (contains(['quarter', 'month', 'day'], timeUnit)) {
10223                return 'symbol';
10224            }
10225            if (isContinuousToContinuous(scaleType)) {
10226                return alwaysReturn ? 'gradient' : undefined;
10227            }
10228        }
10229        return alwaysReturn ? 'symbol' : undefined;
10230    }
10231    function direction({ legend, legendConfig, timeUnit, channel, scaleType }) {
10232        const orient = getFirstDefined(legend.orient, legendConfig.orient, 'right');
10233        const legendType = type$2({ legend, channel, timeUnit, scaleType, alwaysReturn: true });
10234        return getFirstDefined(legend.direction, legendConfig[legendType ? 'gradientDirection' : 'symbolDirection'], defaultDirection(orient, legendType));
10235    }
10236    function defaultDirection(orient, legendType) {
10237        switch (orient) {
10238            case 'top':
10239            case 'bottom':
10240                return 'horizontal';
10241            case 'left':
10242            case 'right':
10243            case 'none':
10244            case undefined: // undefined = "right" in Vega
10245                return undefined; // vertical is Vega's default
10246            default:
10247                // top-left / ...
10248                // For inner legend, uses compact layout like Tableau
10249                return legendType === 'gradient' ? 'horizontal' : undefined;
10250        }
10251    }
10252    function defaultGradientLength({ legend, legendConfig, model, channel, scaleType }) {
10253        const { gradientHorizontalMaxLength, gradientHorizontalMinLength, gradientVerticalMaxLength, gradientVerticalMinLength } = legendConfig;
10254        const dir = direction({ legend, legendConfig, channel, scaleType });
10255        if (dir === 'horizontal') {
10256            const orient = getFirstDefined(legend.orient, legendConfig.orient);
10257            if (orient === 'top' || orient === 'bottom') {
10258                return gradientLengthSignal(model, 'width', gradientHorizontalMinLength, gradientHorizontalMaxLength);
10259            }
10260            else {
10261                return gradientHorizontalMinLength;
10262            }
10263        }
10264        else {
10265            // vertical / undefined (Vega uses vertical by default)
10266            return gradientLengthSignal(model, 'height', gradientVerticalMinLength, gradientVerticalMaxLength);
10267        }
10268    }
10269    function gradientLengthSignal(model, sizeType, min, max) {
10270        const sizeSignal = model.getSizeSignalRef(sizeType).signal;
10271        return { signal: `clamp(${sizeSignal}, ${min}, ${max})` };
10272    }
10273    function defaultLabelOverlap(scaleType) {
10274        if (contains(['quantile', 'threshold', 'log'], scaleType)) {
10275            return 'greedy';
10276        }
10277        return undefined;
10278    }
10279
10280    function type$3(legendCmp, model, channel) {
10281        const scaleType = model.getScaleComponent(channel).get('type');
10282        return getFirstDefined(legendCmp.get('type'), defaultType$1({ channel, scaleType, alwaysReturn: true }));
10283    }
10284    function symbols(fieldDef, symbolsSpec, model, channel, legendCmp) {
10285        if (type$3(legendCmp, model, channel) !== 'symbol') {
10286            return undefined;
10287        }
10288        let out = Object.assign({}, applyMarkConfig({}, model, FILL_STROKE_CONFIG), color(model)); // FIXME: remove this when VgEncodeEntry is compatible with SymbolEncodeEntry
10289        switch (model.mark) {
10290            case BAR:
10291            case TICK:
10292            case TEXT$1:
10293                out.shape = { value: 'square' };
10294                break;
10295            case CIRCLE:
10296            case SQUARE:
10297                out.shape = { value: model.mark };
10298                break;
10299            case POINT:
10300            case LINE:
10301            case GEOSHAPE:
10302            case AREA:
10303                // use default circle
10304                break;
10305        }
10306        const { markDef, encoding } = model;
10307        const filled = markDef.filled;
10308        const opacity = getMaxValue(encoding.opacity) || markDef.opacity;
10309        if (out.fill) {
10310            // for fill legend, we don't want any fill in symbol
10311            if (channel === 'fill' || (filled && channel === COLOR)) {
10312                delete out.fill;
10313            }
10314            else {
10315                if (out.fill['field']) {
10316                    // For others, set fill to some opaque value (or nothing if a color is already set)
10317                    if (legendCmp.get('symbolFillColor')) {
10318                        delete out.fill;
10319                    }
10320                    else {
10321                        out.fill = { value: 'black' };
10322                        out.fillOpacity = { value: opacity || 1 };
10323                    }
10324                }
10325                else if (isArray(out.fill)) {
10326                    const fill$$1 = getFirstConditionValue(encoding.fill || encoding.color) ||
10327                        markDef.fill ||
10328                        (filled && markDef.color);
10329                    if (fill$$1) {
10330                        out.fill = { value: fill$$1 };
10331                    }
10332                }
10333            }
10334        }
10335        if (out.stroke) {
10336            if (channel === 'stroke' || (!filled && channel === COLOR)) {
10337                delete out.stroke;
10338            }
10339            else {
10340                if (out.stroke['field']) {
10341                    // For others, remove stroke field
10342                    delete out.stroke;
10343                }
10344                else if (isArray(out.stroke)) {
10345                    const stroke = getFirstDefined(getFirstConditionValue(encoding.stroke || encoding.color), markDef.stroke, filled ? markDef.color : undefined);
10346                    if (stroke) {
10347                        out.stroke = { value: stroke };
10348                    }
10349                }
10350            }
10351        }
10352        if (channel !== SHAPE) {
10353            const shape = getFirstConditionValue(encoding.shape) || markDef.shape;
10354            if (shape) {
10355                out.shape = { value: shape };
10356            }
10357        }
10358        if (channel !== OPACITY) {
10359            if (opacity) {
10360                // only apply opacity if it is neither zero or undefined
10361                out.opacity = { value: opacity };
10362            }
10363        }
10364        out = Object.assign({}, out, symbolsSpec);
10365        return keys(out).length > 0 ? out : undefined;
10366    }
10367    function gradient(fieldDef, gradientSpec, model, channel, legendCmp) {
10368        if (type$3(legendCmp, model, channel) !== 'gradient') {
10369            return undefined;
10370        }
10371        let out = {};
10372        const opacity = getMaxValue(model.encoding.opacity) || model.markDef.opacity;
10373        if (opacity) {
10374            // only apply opacity if it is neither zero or undefined
10375            out.opacity = { value: opacity };
10376        }
10377        out = Object.assign({}, out, gradientSpec);
10378        return keys(out).length > 0 ? out : undefined;
10379    }
10380    function labels(fieldDef, labelsSpec, model, channel, legendCmp) {
10381        const legend = model.legend(channel);
10382        const config = model.config;
10383        let out = {};
10384        if (isTimeFieldDef(fieldDef)) {
10385            const isUTCScale = model.getScaleComponent(channel).get('type') === ScaleType.UTC;
10386            const expr = timeFormatExpression('datum.value', fieldDef.timeUnit, legend.format, config.legend.shortTimeLabels, config.timeFormat, isUTCScale);
10387            labelsSpec = Object.assign({}, (expr ? { text: { signal: expr } } : {}), labelsSpec);
10388        }
10389        out = Object.assign({}, out, labelsSpec);
10390        return keys(out).length > 0 ? out : undefined;
10391    }
10392    function getMaxValue(channelDef) {
10393        return getConditionValue(channelDef, (v, conditionalDef) => Math.max(v, conditionalDef.value));
10394    }
10395    function getFirstConditionValue(channelDef) {
10396        return getConditionValue(channelDef, (v, conditionalDef) => {
10397            return getFirstDefined(v, conditionalDef.value);
10398        });
10399    }
10400    function getConditionValue(channelDef, reducer) {
10401        if (hasConditionalValueDef(channelDef)) {
10402            return (isArray(channelDef.condition) ? channelDef.condition : [channelDef.condition]).reduce(reducer, channelDef.value);
10403        }
10404        else if (isValueDef(channelDef)) {
10405            return channelDef.value;
10406        }
10407        return undefined;
10408    }
10409
10410    var encode = /*#__PURE__*/Object.freeze({
10411        symbols: symbols,
10412        gradient: gradient,
10413        labels: labels
10414    });
10415
10416    function parseLegend(model) {
10417        if (isUnitModel(model)) {
10418            model.component.legends = parseUnitLegend(model);
10419        }
10420        else {
10421            model.component.legends = parseNonUnitLegend(model);
10422        }
10423    }
10424    function parseUnitLegend(model) {
10425        const { encoding } = model;
10426        return [COLOR, FILL, STROKE, STROKEWIDTH, SIZE, SHAPE, OPACITY, FILLOPACITY, STROKEOPACITY].reduce((legendComponent, channel) => {
10427            const def = encoding[channel];
10428            if (model.legend(channel) &&
10429                model.getScaleComponent(channel) &&
10430                !(isFieldDef(def) && (channel === SHAPE && def.type === GEOJSON))) {
10431                legendComponent[channel] = parseLegendForChannel(model, channel);
10432            }
10433            return legendComponent;
10434        }, {});
10435    }
10436    function getLegendDefWithScale(model, channel) {
10437        // For binned field with continuous scale, use a special scale so we can override the mark props and labels
10438        switch (channel) {
10439            case COLOR:
10440                const scale = model.scaleName(COLOR);
10441                return model.markDef.filled ? { fill: scale } : { stroke: scale };
10442            case FILL:
10443            case STROKE:
10444            case STROKEWIDTH:
10445            case SIZE:
10446            case SHAPE:
10447            case OPACITY:
10448            case FILLOPACITY:
10449            case STROKEOPACITY:
10450                return { [channel]: model.scaleName(channel) };
10451        }
10452    }
10453    function isExplicit(value, property, legend, fieldDef) {
10454        switch (property) {
10455            case 'values':
10456                // specified legend.values is already respected, but may get transformed.
10457                return !!legend.values;
10458            case 'title':
10459                // title can be explicit if fieldDef.title is set
10460                if (property === 'title' && value === fieldDef.title) {
10461                    return true;
10462                }
10463        }
10464        // Otherwise, things are explicit if the returned value matches the specified property
10465        return value === legend[property];
10466    }
10467    function parseLegendForChannel(model, channel) {
10468        const fieldDef = model.fieldDef(channel);
10469        const legend = model.legend(channel);
10470        const legendCmpt = new LegendComponent({}, getLegendDefWithScale(model, channel));
10471        for (const property of LEGEND_PROPERTIES) {
10472            const value = getProperty(property, legend, channel, model);
10473            if (value !== undefined) {
10474                const explicit = isExplicit(value, property, legend, fieldDef);
10475                if (explicit || model.config.legend[property] === undefined) {
10476                    legendCmpt.set(property, value, explicit);
10477                }
10478            }
10479        }
10480        const legendEncoding = legend.encoding || {};
10481        const legendEncode = ['labels', 'legend', 'title', 'symbols', 'gradient'].reduce((e, part) => {
10482            const legendEncodingPart = guideEncodeEntry(legendEncoding[part] || {}, model);
10483            const value = encode[part]
10484                ? encode[part](fieldDef, legendEncodingPart, model, channel, legendCmpt) // apply rule
10485                : legendEncodingPart; // no rule -- just default values
10486            if (value !== undefined && keys(value).length > 0) {
10487                e[part] = { update: value };
10488            }
10489            return e;
10490        }, {});
10491        if (keys(legendEncode).length > 0) {
10492            legendCmpt.set('encode', legendEncode, !!legend.encoding);
10493        }
10494        return legendCmpt;
10495    }
10496    function getProperty(property, legend, channel, model) {
10497        const { encoding } = model;
10498        const fieldDef = getTypedFieldDef(encoding[channel]);
10499        const legendConfig = model.config.legend;
10500        const { timeUnit } = fieldDef;
10501        const scaleType = model.getScaleComponent(channel).get('type');
10502        switch (property) {
10503            case 'format':
10504                // We don't include temporal field here as we apply format in encode block
10505                return numberFormat(fieldDef, legend.format, model.config);
10506            case 'title':
10507                return title(fieldDef, model.config, { allowDisabling: true }) || undefined;
10508            case 'type':
10509                return type$2({ legend, channel, timeUnit, scaleType, alwaysReturn: false });
10510            case 'direction':
10511                return direction({ legend, legendConfig, timeUnit, channel, scaleType });
10512            // TODO: enable when https://github.com/vega/vega/issues/1351 is fixed
10513            // case 'clipHeight':
10514            //   return getFirstDefined(specifiedLegend.clipHeight, properties.clipHeight(properties.type(...)));
10515            case 'labelOverlap':
10516                return getFirstDefined(legend.labelOverlap, defaultLabelOverlap(scaleType));
10517            case 'gradientLength':
10518                return getFirstDefined(
10519                // do specified gradientLength first
10520                legend.gradientLength, legendConfig.gradientLength, 
10521                // Otherwise, use smart default based on plot height
10522                defaultGradientLength({
10523                    model,
10524                    legend,
10525                    legendConfig,
10526                    channel,
10527                    scaleType
10528                }));
10529            case 'values':
10530                return values(legend, fieldDef);
10531        }
10532        // Otherwise, return specified property.
10533        return legend[property];
10534    }
10535    function parseNonUnitLegend(model) {
10536        const { legends, resolve } = model.component;
10537        for (const child of model.children) {
10538            parseLegend(child);
10539            keys(child.component.legends).forEach((channel) => {
10540                resolve.legend[channel] = parseGuideResolve(model.component.resolve, channel);
10541                if (resolve.legend[channel] === 'shared') {
10542                    // If the resolve says shared (and has not been overridden)
10543                    // We will try to merge and see if there is a conflict
10544                    legends[channel] = mergeLegendComponent(legends[channel], child.component.legends[channel]);
10545                    if (!legends[channel]) {
10546                        // If merge returns nothing, there is a conflict so we cannot make the legend shared.
10547                        // Thus, mark legend as independent and remove the legend component.
10548                        resolve.legend[channel] = 'independent';
10549                        delete legends[channel];
10550                    }
10551                }
10552            });
10553        }
10554        keys(legends).forEach((channel) => {
10555            for (const child of model.children) {
10556                if (!child.component.legends[channel]) {
10557                    // skip if the child does not have a particular legend
10558                    continue;
10559                }
10560                if (resolve.legend[channel] === 'shared') {
10561                    // After merging shared legend, make sure to remove legend from child
10562                    delete child.component.legends[channel];
10563                }
10564            }
10565        });
10566        return legends;
10567    }
10568    function mergeLegendComponent(mergedLegend, childLegend) {
10569        if (!mergedLegend) {
10570            return childLegend.clone();
10571        }
10572        const mergedOrient = mergedLegend.getWithExplicit('orient');
10573        const childOrient = childLegend.getWithExplicit('orient');
10574        if (mergedOrient.explicit && childOrient.explicit && mergedOrient.value !== childOrient.value) {
10575            // TODO: throw warning if resolve is explicit (We don't have info about explicit/implicit resolve yet.)
10576            // Cannot merge due to inconsistent orient
10577            return undefined;
10578        }
10579        let typeMerged = false;
10580        // Otherwise, let's merge
10581        for (const prop of VG_LEGEND_PROPERTIES) {
10582            const mergedValueWithExplicit = mergeValuesWithExplicit(mergedLegend.getWithExplicit(prop), childLegend.getWithExplicit(prop), prop, 'legend', 
10583            // Tie breaker function
10584            (v1, v2) => {
10585                switch (prop) {
10586                    case 'title':
10587                        return mergeTitleComponent(v1, v2);
10588                    case 'type':
10589                        // There are only two types. If we have different types, then prefer symbol over gradient.
10590                        typeMerged = true;
10591                        return makeImplicit('symbol');
10592                }
10593                return defaultTieBreaker(v1, v2, prop, 'legend');
10594            });
10595            mergedLegend.setWithExplicit(prop, mergedValueWithExplicit);
10596        }
10597        if (typeMerged) {
10598            if (((mergedLegend.implicit || {}).encode || {}).gradient) {
10599                deleteNestedProperty(mergedLegend.implicit, ['encode', 'gradient']);
10600            }
10601            if (((mergedLegend.explicit || {}).encode || {}).gradient) {
10602                deleteNestedProperty(mergedLegend.explicit, ['encode', 'gradient']);
10603            }
10604        }
10605        return mergedLegend;
10606    }
10607
10608    function assembleLegends(model) {
10609        const legendComponentIndex = model.component.legends;
10610        const legendByDomain = {};
10611        for (const channel of keys(legendComponentIndex)) {
10612            const scaleComponent = model.getScaleComponent(channel);
10613            const domainHash = stringify(scaleComponent.domains);
10614            if (legendByDomain[domainHash]) {
10615                for (const mergedLegendComponent of legendByDomain[domainHash]) {
10616                    const merged = mergeLegendComponent(mergedLegendComponent, legendComponentIndex[channel]);
10617                    if (!merged) {
10618                        // If cannot merge, need to add this legend separately
10619                        legendByDomain[domainHash].push(legendComponentIndex[channel]);
10620                    }
10621                }
10622            }
10623            else {
10624                legendByDomain[domainHash] = [legendComponentIndex[channel].clone()];
10625            }
10626        }
10627        return flatten(vals(legendByDomain)).map((legendCmpt) => {
10628            const legend = legendCmpt.combine();
10629            // For non color channel's legend, we need to override symbol stroke config from Vega config
10630            if (legend.encode && legend.encode.symbols) {
10631                const out = legend.encode.symbols.update;
10632                if (out.fill && out.fill['value'] !== 'transparent' && !out.stroke && !legend.stroke) {
10633                    // For non color channel's legend, we need to override symbol stroke config from Vega config if stroke channel is not used.
10634                    out.stroke = { value: 'transparent' };
10635                }
10636            }
10637            return legend;
10638        });
10639    }
10640
10641    function assembleProjections(model) {
10642        if (isLayerModel(model) || isConcatModel(model) || isRepeatModel(model)) {
10643            return assembleProjectionsForModelAndChildren(model);
10644        }
10645        else {
10646            return assembleProjectionForModel(model);
10647        }
10648    }
10649    function assembleProjectionsForModelAndChildren(model) {
10650        return model.children.reduce((projections, child) => {
10651            return projections.concat(child.assembleProjections());
10652        }, assembleProjectionForModel(model));
10653    }
10654    function assembleProjectionForModel(model) {
10655        const component = model.component.projection;
10656        if (!component || component.merged) {
10657            return [];
10658        }
10659        const projection = component.combine();
10660        const { name } = projection, rest = __rest(projection, ["name"]); // we need to extract name so that it is always present in the output and pass TS type validation
10661        const size = {
10662            signal: `[${component.size.map(ref => ref.signal).join(', ')}]`
10663        };
10664        const fit = component.data.reduce((sources, data) => {
10665            const source = isSignalRef(data) ? data.signal : `data('${model.lookupDataSource(data)}')`;
10666            if (!contains(sources, source)) {
10667                // build a unique list of sources
10668                sources.push(source);
10669            }
10670            return sources;
10671        }, []);
10672        if (fit.length <= 0) {
10673            throw new Error("Projection's fit didn't find any data sources");
10674        }
10675        return [
10676            Object.assign({ name,
10677                size, fit: {
10678                    signal: fit.length > 1 ? `[${fit.join(', ')}]` : fit[0]
10679                } }, rest)
10680        ];
10681    }
10682
10683    const PROJECTION_PROPERTIES = [
10684        'type',
10685        'clipAngle',
10686        'clipExtent',
10687        'center',
10688        'rotate',
10689        'precision',
10690        'coefficient',
10691        'distance',
10692        'fraction',
10693        'lobes',
10694        'parallel',
10695        'radius',
10696        'ratio',
10697        'spacing',
10698        'tilt'
10699    ];
10700
10701    class ProjectionComponent extends Split {
10702        constructor(name, specifiedProjection, size, data) {
10703            super(Object.assign({}, specifiedProjection), // all explicit properties of projection
10704            { name } // name as initial implicit property
10705            );
10706            this.specifiedProjection = specifiedProjection;
10707            this.size = size;
10708            this.data = data;
10709            this.merged = false;
10710        }
10711    }
10712
10713    function parseProjection(model) {
10714        if (isUnitModel(model)) {
10715            model.component.projection = parseUnitProjection(model);
10716        }
10717        else {
10718            // because parse happens from leaves up (unit specs before layer spec),
10719            // we can be sure that the above if statement has already occurred
10720            // and therefore we have access to child.component.projection
10721            // for each of model's children
10722            model.component.projection = parseNonUnitProjections(model);
10723        }
10724    }
10725    function parseUnitProjection(model) {
10726        const { specifiedProjection, config, hasProjection } = model;
10727        if (hasProjection) {
10728            const data = [];
10729            [[LONGITUDE, LATITUDE], [LONGITUDE2, LATITUDE2]].forEach(posssiblePair => {
10730                if (model.channelHasField(posssiblePair[0]) || model.channelHasField(posssiblePair[1])) {
10731                    data.push({
10732                        signal: model.getName(`geojson_${data.length}`)
10733                    });
10734                }
10735            });
10736            if (model.channelHasField(SHAPE) && model.fieldDef(SHAPE).type === GEOJSON) {
10737                data.push({
10738                    signal: model.getName(`geojson_${data.length}`)
10739                });
10740            }
10741            if (data.length === 0) {
10742                // main source is geojson, so we can just use that
10743                data.push(model.requestDataName(MAIN));
10744            }
10745            return new ProjectionComponent(model.projectionName(true), Object.assign({}, (config.projection || {}), (specifiedProjection || {})), [model.getSizeSignalRef('width'), model.getSizeSignalRef('height')], data);
10746        }
10747        return undefined;
10748    }
10749    function mergeIfNoConflict(first, second) {
10750        const allPropertiesShared = every(PROJECTION_PROPERTIES, prop => {
10751            // neither has the property
10752            if (!first.explicit.hasOwnProperty(prop) && !second.explicit.hasOwnProperty(prop)) {
10753                return true;
10754            }
10755            // both have property and an equal value for property
10756            if (first.explicit.hasOwnProperty(prop) &&
10757                second.explicit.hasOwnProperty(prop) &&
10758                // some properties might be signals or objects and require hashing for comparison
10759                stringify(first.get(prop)) === stringify(second.get(prop))) {
10760                return true;
10761            }
10762            return false;
10763        });
10764        const size = stringify(first.size) === stringify(second.size);
10765        if (size) {
10766            if (allPropertiesShared) {
10767                return first;
10768            }
10769            else if (stringify(first.explicit) === stringify({})) {
10770                return second;
10771            }
10772            else if (stringify(second.explicit) === stringify({})) {
10773                return first;
10774            }
10775        }
10776        // if all properties don't match, let each unit spec have its own projection
10777        return null;
10778    }
10779    function parseNonUnitProjections(model) {
10780        if (model.children.length === 0) {
10781            return undefined;
10782        }
10783        let nonUnitProjection;
10784        const mergable = every(model.children, child => {
10785            parseProjection(child);
10786            const projection = child.component.projection;
10787            if (!projection) {
10788                // child layer does not use a projection
10789                return true;
10790            }
10791            else if (!nonUnitProjection) {
10792                // cached 'projection' is null, cache this one
10793                nonUnitProjection = projection;
10794                return true;
10795            }
10796            else {
10797                const merge = mergeIfNoConflict(nonUnitProjection, projection);
10798                if (merge) {
10799                    nonUnitProjection = merge;
10800                }
10801                return !!merge;
10802            }
10803        });
10804        // it cached one and all other children share the same projection,
10805        if (nonUnitProjection && mergable) {
10806            // so we can elevate it to the layer level
10807            const name = model.projectionName(true);
10808            const modelProjection = new ProjectionComponent(name, nonUnitProjection.specifiedProjection, nonUnitProjection.size, duplicate(nonUnitProjection.data));
10809            // rename and assign all others as merged
10810            model.children.forEach(child => {
10811                if (child.component.projection) {
10812                    modelProjection.data = modelProjection.data.concat(child.component.projection.data);
10813                    child.renameProjection(child.component.projection.get('name'), name);
10814                    child.component.projection.merged = true;
10815                }
10816            });
10817            return modelProjection;
10818        }
10819        return undefined;
10820    }
10821
10822    /**
10823     * A class that wraps an expression with a list signal names in the expression. If they are renamed during parsing multi-views,   we can rename then signals in the assemble phase.
10824     */
10825    class SignalRefComponent {
10826        constructor(expr, signalNames) {
10827            this.expr = expr;
10828            this.signalNames = signalNames;
10829        }
10830        static fromName(signalName) {
10831            return new SignalRefComponent(signalName, [signalName]);
10832        }
10833        /**
10834         * Generate new signal based on this signal
10835         */
10836        map(f) {
10837            return new SignalRefComponent(f(this.expr), this.signalNames);
10838        }
10839    }
10840    const generate = codegen({ globalvar: 'global' });
10841    function evalOrMakeSignalRefComponent(expr, params) {
10842        const varNames = keys(params);
10843        const signalNames = [];
10844        for (const varName$$1 of varNames) {
10845            const param = params[varName$$1];
10846            if (param instanceof SignalRefComponent) {
10847                for (const name of param.signalNames) {
10848                    signalNames.push(name);
10849                }
10850                expr = expr.replace(globalWholeWordRegExp(varName$$1), param.expr);
10851            }
10852            else {
10853                // primitive value
10854                expr = expr.replace(globalWholeWordRegExp(varName$$1), $(param));
10855            }
10856        }
10857        if (signalNames.length > 0) {
10858            return new SignalRefComponent(expr, signalNames);
10859        }
10860        else {
10861            try {
10862                // Try to evaluate the expression and return the value if we succeed.
10863                const ast = parse(expr);
10864                const { code } = generate(ast);
10865                const f = new Function('global', `return ${code};`); // tslint:disable-line:function-constructor
10866                return f(params);
10867            }
10868            catch (error$$1) {
10869                if (error$$1.message.indexOf('Unrecognized function') === 0) {
10870                    // This expression contains a function that needs to be evaluated at runtime, so let's keep it as a SignalRefComponent.
10871                    return new SignalRefComponent(expr, []);
10872                }
10873                throw error$$1;
10874            }
10875        }
10876    }
10877
10878    /**
10879     * Iterates over a dataflow graph and checks whether all links are consistent.
10880     */
10881    function checkLinks(nodes) {
10882        for (const node of nodes) {
10883            for (const child of node.children) {
10884                if (child.parent !== node) {
10885                    console.error('Dataflow graph is inconsistent.', parent, child);
10886                    return false;
10887                }
10888            }
10889            if (!checkLinks(node.children)) {
10890                return false;
10891            }
10892        }
10893        return true;
10894    }
10895
10896    class SourceNode extends DataFlowNode {
10897        constructor(data) {
10898            super(null); // source cannot have parent
10899            data = data || { name: 'source' };
10900            const format = data.format ? Object.assign({}, omit(data.format, ['parse'])) : {};
10901            if (isInlineData(data)) {
10902                this._data = { values: data.values };
10903            }
10904            else if (isUrlData(data)) {
10905                this._data = { url: data.url };
10906                if (!format.type) {
10907                    // Extract extension from URL using snippet from
10908                    // http://stackoverflow.com/questions/680929/how-to-extract-extension-from-filename-string-in-javascript
10909                    let defaultExtension = /(?:\.([^.]+))?$/.exec(data.url)[1];
10910                    if (!contains(['json', 'csv', 'tsv', 'dsv', 'topojson'], defaultExtension)) {
10911                        defaultExtension = 'json';
10912                    }
10913                    // defaultExtension has type string but we ensure that it is DataFormatType above
10914                    format.type = defaultExtension;
10915                }
10916            }
10917            else if (isNamedData(data)) {
10918                this._data = {};
10919            }
10920            // any dataset can be named
10921            if (data.name) {
10922                this._name = data.name;
10923            }
10924            if (format && keys(format).length > 0) {
10925                this._data.format = format;
10926            }
10927        }
10928        get data() {
10929            return this._data;
10930        }
10931        hasName() {
10932            return !!this._name;
10933        }
10934        get dataName() {
10935            return this._name;
10936        }
10937        set dataName(name) {
10938            this._name = name;
10939        }
10940        set parent(parent) {
10941            throw new Error('Source nodes have to be roots.');
10942        }
10943        remove() {
10944            throw new Error('Source nodes are roots and cannot be removed.');
10945        }
10946        hash() {
10947            throw new Error('Cannot hash sources');
10948        }
10949        assemble() {
10950            return Object.assign({ name: this._name }, this._data, { transform: [] });
10951        }
10952    }
10953
10954    /**
10955     * Abstract base class for BottomUpOptimizer and TopDownOptimizer.
10956     * Contains only mutation handling logic. Subclasses need to implement iteration logic.
10957     */
10958    class OptimizerBase {
10959        constructor() {
10960            this._mutated = false;
10961        }
10962        // Once true, _mutated is never set to false
10963        setMutated() {
10964            this._mutated = true;
10965        }
10966        get mutatedFlag() {
10967            return this._mutated;
10968        }
10969    }
10970    /**
10971     * Starts from a node and runs the optimization function(the "run" method) upwards to the root,
10972     * depending on the continueFlag and mutatedFlag values returned by the optimization function.
10973     */
10974    class BottomUpOptimizer extends OptimizerBase {
10975        constructor() {
10976            super();
10977            this._continue = false;
10978        }
10979        setContinue() {
10980            this._continue = true;
10981        }
10982        get continueFlag() {
10983            return this._continue;
10984        }
10985        get flags() {
10986            return { continueFlag: this.continueFlag, mutatedFlag: this.mutatedFlag };
10987        }
10988        set flags({ continueFlag, mutatedFlag }) {
10989            if (continueFlag) {
10990                this.setContinue();
10991            }
10992            if (mutatedFlag) {
10993                this.setMutated();
10994            }
10995        }
10996        optimizeNextFromLeaves(node) {
10997            if (node instanceof SourceNode) {
10998                return false;
10999            }
11000            const next = node.parent;
11001            const { continueFlag } = this.run(node);
11002            if (continueFlag) {
11003                this.optimizeNextFromLeaves(next);
11004            }
11005            return this.mutatedFlag;
11006        }
11007    }
11008    /**
11009     * The optimizer function( the "run" method), is invoked on the given node and then continues recursively.
11010     */
11011    class TopDownOptimizer extends OptimizerBase {
11012    }
11013
11014    function addDimension(dims, channel, fieldDef) {
11015        if (isTypedFieldDef(fieldDef) && isBinning(fieldDef.bin)) {
11016            dims.add(vgField(fieldDef, {}));
11017            dims.add(vgField(fieldDef, { binSuffix: 'end' }));
11018            if (binRequiresRange(fieldDef, channel)) {
11019                dims.add(vgField(fieldDef, { binSuffix: 'range' }));
11020            }
11021        }
11022        else {
11023            dims.add(vgField(fieldDef));
11024        }
11025        return dims;
11026    }
11027    function mergeMeasures(parentMeasures, childMeasures) {
11028        for (const field of keys(childMeasures)) {
11029            // when we merge a measure, we either have to add an aggregation operator or even a new field
11030            const ops = childMeasures[field];
11031            for (const op of keys(ops)) {
11032                if (field in parentMeasures) {
11033                    // add operator to existing measure field
11034                    parentMeasures[field][op] = new Set([...(parentMeasures[field][op] || []), ...ops[op]]);
11035                }
11036                else {
11037                    parentMeasures[field] = { [op]: ops[op] };
11038                }
11039            }
11040        }
11041    }
11042    class AggregateNode extends DataFlowNode {
11043        /**
11044         * @param dimensions string set for dimensions
11045         * @param measures dictionary mapping field name => dict of aggregation functions and names to use
11046         */
11047        constructor(parent, dimensions, measures) {
11048            super(parent);
11049            this.dimensions = dimensions;
11050            this.measures = measures;
11051        }
11052        clone() {
11053            return new AggregateNode(null, new Set(this.dimensions), duplicate(this.measures));
11054        }
11055        get groupBy() {
11056            return this.dimensions;
11057        }
11058        static makeFromEncoding(parent, model) {
11059            let isAggregate = false;
11060            model.forEachFieldDef(fd => {
11061                if (fd.aggregate) {
11062                    isAggregate = true;
11063                }
11064            });
11065            const meas = {};
11066            const dims = new Set();
11067            if (!isAggregate) {
11068                // no need to create this node if the model has no aggregation
11069                return null;
11070            }
11071            model.forEachFieldDef((fieldDef, channel) => {
11072                const { aggregate, field } = fieldDef;
11073                if (aggregate) {
11074                    if (aggregate === 'count') {
11075                        meas['*'] = meas['*'] || {};
11076                        meas['*']['count'] = new Set([vgField(fieldDef, { forAs: true })]);
11077                    }
11078                    else {
11079                        meas[field] = meas[field] || {};
11080                        meas[field][aggregate] = new Set([vgField(fieldDef, { forAs: true })]);
11081                        // For scale channel with domain === 'unaggregated', add min/max so we can use their union as unaggregated domain
11082                        if (isScaleChannel(channel) && model.scaleDomain(channel) === 'unaggregated') {
11083                            meas[field]['min'] = new Set([vgField({ field, aggregate: 'min' }, { forAs: true })]);
11084                            meas[field]['max'] = new Set([vgField({ field, aggregate: 'max' }, { forAs: true })]);
11085                        }
11086                    }
11087                }
11088                else {
11089                    addDimension(dims, channel, fieldDef);
11090                }
11091            });
11092            if (dims.size + keys(meas).length === 0) {
11093                return null;
11094            }
11095            return new AggregateNode(parent, dims, meas);
11096        }
11097        static makeFromTransform(parent, t) {
11098            const dims = new Set();
11099            const meas = {};
11100            for (const s of t.aggregate) {
11101                const { op, field, as } = s;
11102                if (op) {
11103                    if (op === 'count') {
11104                        meas['*'] = meas['*'] || {};
11105                        meas['*']['count'] = new Set([as ? as : vgField(s, { forAs: true })]);
11106                    }
11107                    else {
11108                        meas[field] = meas[field] || {};
11109                        meas[field][op] = new Set([as ? as : vgField(s, { forAs: true })]);
11110                    }
11111                }
11112            }
11113            for (const s of t.groupby || []) {
11114                dims.add(s);
11115            }
11116            if (dims.size + keys(meas).length === 0) {
11117                return null;
11118            }
11119            return new AggregateNode(parent, dims, meas);
11120        }
11121        merge(other) {
11122            if (setEqual(this.dimensions, other.dimensions)) {
11123                mergeMeasures(this.measures, other.measures);
11124                return true;
11125            }
11126            else {
11127                debug('different dimensions, cannot merge');
11128                return false;
11129            }
11130        }
11131        addDimensions(fields) {
11132            fields.forEach(this.dimensions.add, this.dimensions);
11133        }
11134        dependentFields() {
11135            return new Set([...this.dimensions, ...keys(this.measures)]);
11136        }
11137        producedFields() {
11138            const out = new Set();
11139            for (const field of keys(this.measures)) {
11140                for (const op of keys(this.measures[field])) {
11141                    const m = this.measures[field][op];
11142                    if (m.size === 0) {
11143                        out.add(`${op}_${field}`);
11144                    }
11145                    else {
11146                        m.forEach(out.add, out);
11147                    }
11148                }
11149            }
11150            return out;
11151        }
11152        hash() {
11153            return `Aggregate ${hash({ dimensions: this.dimensions, measures: this.measures })}`;
11154        }
11155        assemble() {
11156            const ops = [];
11157            const fields = [];
11158            const as = [];
11159            for (const field of keys(this.measures)) {
11160                for (const op of keys(this.measures[field])) {
11161                    for (const alias of this.measures[field][op]) {
11162                        as.push(alias);
11163                        ops.push(op);
11164                        fields.push(replacePathInField(field));
11165                    }
11166                }
11167            }
11168            const result = {
11169                type: 'aggregate',
11170                groupby: [...this.dimensions],
11171                ops,
11172                fields,
11173                as
11174            };
11175            return result;
11176        }
11177    }
11178
11179    /**
11180     * A node that helps us track what fields we are faceting by.
11181     */
11182    class FacetNode extends DataFlowNode {
11183        /**
11184         * @param model The facet model.
11185         * @param name The name that this facet source will have.
11186         * @param data The source data for this facet data.
11187         */
11188        constructor(parent, model, name, data) {
11189            super(parent);
11190            this.model = model;
11191            this.name = name;
11192            this.data = data;
11193            for (const channel of [COLUMN, ROW]) {
11194                const fieldDef = model.facet[channel];
11195                if (fieldDef) {
11196                    const { bin, sort } = fieldDef;
11197                    this[channel] = Object.assign({ name: model.getName(`${channel}_domain`), fields: [vgField(fieldDef), ...(isBinning(bin) ? [vgField(fieldDef, { binSuffix: 'end' })] : [])] }, (isSortField(sort)
11198                        ? { sortField: sort }
11199                        : isArray(sort)
11200                            ? { sortIndexField: sortArrayIndexField(fieldDef, channel) }
11201                            : {}));
11202                }
11203            }
11204            this.childModel = model.child;
11205        }
11206        hash() {
11207            let out = `Facet`;
11208            if (this.column) {
11209                out += ` c:${hash(this.column)}`;
11210            }
11211            if (this.row) {
11212                out += ` r:${hash(this.row)}`;
11213            }
11214            return out;
11215        }
11216        get fields() {
11217            return [...((this.column && this.column.fields) || []), ...((this.row && this.row.fields) || [])];
11218        }
11219        /**
11220         * The name to reference this source is its name.
11221         */
11222        getSource() {
11223            return this.name;
11224        }
11225        getChildIndependentFieldsWithStep() {
11226            const childIndependentFieldsWithStep = {};
11227            for (const channel of ['x', 'y']) {
11228                const childScaleComponent = this.childModel.component.scales[channel];
11229                if (childScaleComponent && !childScaleComponent.merged) {
11230                    const type = childScaleComponent.get('type');
11231                    const range = childScaleComponent.get('range');
11232                    if (hasDiscreteDomain(type) && isVgRangeStep(range)) {
11233                        const domain = assembleDomain(this.childModel, channel);
11234                        const field$$1 = getFieldFromDomain(domain);
11235                        if (field$$1) {
11236                            childIndependentFieldsWithStep[channel] = field$$1;
11237                        }
11238                        else {
11239                            warn('Unknown field for ${channel}.  Cannot calculate view size.');
11240                        }
11241                    }
11242                }
11243            }
11244            return childIndependentFieldsWithStep;
11245        }
11246        assembleRowColumnData(channel, crossedDataName, childIndependentFieldsWithStep) {
11247            const childChannel = channel === 'row' ? 'y' : 'x';
11248            const fields = [];
11249            const ops = [];
11250            const as = [];
11251            if (childIndependentFieldsWithStep[childChannel]) {
11252                if (crossedDataName) {
11253                    // If there is a crossed data, calculate max
11254                    fields.push(`distinct_${childIndependentFieldsWithStep[childChannel]}`);
11255                    ops.push('max');
11256                }
11257                else {
11258                    // If there is no crossed data, just calculate distinct
11259                    fields.push(childIndependentFieldsWithStep[childChannel]);
11260                    ops.push('distinct');
11261                }
11262                // Although it is technically a max, just name it distinct so it's easier to refer to it
11263                as.push(`distinct_${childIndependentFieldsWithStep[childChannel]}`);
11264            }
11265            const { sortField, sortIndexField } = this[channel];
11266            if (sortField) {
11267                const { op, field: field$$1 } = sortField;
11268                fields.push(field$$1);
11269                ops.push(op);
11270                as.push(vgField(sortField, { forAs: true }));
11271            }
11272            else if (sortIndexField) {
11273                fields.push(sortIndexField);
11274                ops.push('max');
11275                as.push(sortIndexField);
11276            }
11277            return {
11278                name: this[channel].name,
11279                // Use data from the crossed one if it exist
11280                source: crossedDataName || this.data,
11281                transform: [
11282                    Object.assign({ type: 'aggregate', groupby: this[channel].fields }, (fields.length
11283                        ? {
11284                            fields,
11285                            ops,
11286                            as
11287                        }
11288                        : {}))
11289                ]
11290            };
11291        }
11292        assemble() {
11293            const data = [];
11294            let crossedDataName = null;
11295            const childIndependentFieldsWithStep = this.getChildIndependentFieldsWithStep();
11296            if (this.column && this.row && (childIndependentFieldsWithStep.x || childIndependentFieldsWithStep.y)) {
11297                // Need to create a cross dataset to correctly calculate cardinality
11298                crossedDataName = `cross_${this.column.name}_${this.row.name}`;
11299                const fields = [].concat(childIndependentFieldsWithStep.x ? [childIndependentFieldsWithStep.x] : [], childIndependentFieldsWithStep.y ? [childIndependentFieldsWithStep.y] : []);
11300                const ops = fields.map(() => 'distinct');
11301                data.push({
11302                    name: crossedDataName,
11303                    source: this.data,
11304                    transform: [
11305                        {
11306                            type: 'aggregate',
11307                            groupby: [...this.column.fields, ...this.row.fields],
11308                            fields,
11309                            ops
11310                        }
11311                    ]
11312                });
11313            }
11314            for (const channel of [COLUMN, ROW]) {
11315                if (this[channel]) {
11316                    data.push(this.assembleRowColumnData(channel, crossedDataName, childIndependentFieldsWithStep));
11317                }
11318            }
11319            return data;
11320        }
11321    }
11322
11323    /**
11324     * @param field The field.
11325     * @param parse What to parse the field as.
11326     */
11327    function parseExpression$1(field$$1, parse) {
11328        const f = accessPathWithDatum(field$$1);
11329        if (parse === 'number') {
11330            return `toNumber(${f})`;
11331        }
11332        else if (parse === 'boolean') {
11333            return `toBoolean(${f})`;
11334        }
11335        else if (parse === 'string') {
11336            return `toString(${f})`;
11337        }
11338        else if (parse === 'date') {
11339            return `toDate(${f})`;
11340        }
11341        else if (parse === 'flatten') {
11342            return f;
11343        }
11344        else if (parse.indexOf('date:') === 0) {
11345            const specifier = parse.slice(5, parse.length);
11346            return `timeParse(${f},${specifier})`;
11347        }
11348        else if (parse.indexOf('utc:') === 0) {
11349            const specifier = parse.slice(4, parse.length);
11350            return `utcParse(${f},${specifier})`;
11351        }
11352        else {
11353            warn(message.unrecognizedParse(parse));
11354            return null;
11355        }
11356    }
11357    class ParseNode extends DataFlowNode {
11358        clone() {
11359            return new ParseNode(null, duplicate(this._parse));
11360        }
11361        constructor(parent, parse) {
11362            super(parent);
11363            this._parse = parse;
11364        }
11365        hash() {
11366            return `Parse ${hash(this._parse)}`;
11367        }
11368        /**
11369         * Creates a parse node from a data.format.parse and updates ancestorParse.
11370         */
11371        static makeExplicit(parent, model, ancestorParse) {
11372            // Custom parse
11373            let explicit = {};
11374            const data = model.data;
11375            if (data && data.format && data.format.parse) {
11376                explicit = data.format.parse;
11377            }
11378            return this.makeWithAncestors(parent, explicit, {}, ancestorParse);
11379        }
11380        static makeImplicitFromFilterTransform(parent, transform, ancestorParse) {
11381            const parse = {};
11382            forEachLeaf(transform.filter, filter => {
11383                if (isFieldPredicate(filter)) {
11384                    // Automatically add a parse node for filters with filter objects
11385                    let val = null;
11386                    // For EqualFilter, just use the equal property.
11387                    // For RangeFilter and OneOfFilter, all array members should have
11388                    // the same type, so we only use the first one.
11389                    if (isFieldEqualPredicate(filter)) {
11390                        val = filter.equal;
11391                    }
11392                    else if (isFieldRangePredicate(filter)) {
11393                        val = filter.range[0];
11394                    }
11395                    else if (isFieldOneOfPredicate(filter)) {
11396                        val = (filter.oneOf || filter['in'])[0];
11397                    } // else -- for filter expression, we can't infer anything
11398                    if (val) {
11399                        if (isDateTime(val)) {
11400                            parse[filter.field] = 'date';
11401                        }
11402                        else if (isNumber(val)) {
11403                            parse[filter.field] = 'number';
11404                        }
11405                        else if (isString(val)) {
11406                            parse[filter.field] = 'string';
11407                        }
11408                    }
11409                    if (filter.timeUnit) {
11410                        parse[filter.field] = 'date';
11411                    }
11412                }
11413            });
11414            if (keys(parse).length === 0) {
11415                return null;
11416            }
11417            return this.makeWithAncestors(parent, {}, parse, ancestorParse);
11418        }
11419        /**
11420         * Creates a parse node for implicit parsing from a model and updates ancestorParse.
11421         */
11422        static makeImplicitFromEncoding(parent, model, ancestorParse) {
11423            const implicit = {};
11424            function add(fieldDef) {
11425                if (isTimeFieldDef(fieldDef)) {
11426                    implicit[fieldDef.field] = 'date';
11427                }
11428                else if (isNumberFieldDef(fieldDef) && isMinMaxOp(fieldDef.aggregate)) {
11429                    implicit[fieldDef.field] = 'number';
11430                }
11431                else if (accessPathDepth(fieldDef.field) > 1) {
11432                    // For non-date/non-number (strings and booleans), derive a flattened field for a referenced nested field.
11433                    // (Parsing numbers / dates already flattens numeric and temporal fields.)
11434                    if (!(fieldDef.field in implicit)) {
11435                        implicit[fieldDef.field] = 'flatten';
11436                    }
11437                }
11438                else if (isScaleFieldDef(fieldDef) && isSortField(fieldDef.sort) && accessPathDepth(fieldDef.sort.field) > 1) {
11439                    // Flatten fields that we sort by but that are not otherwise flattened.
11440                    if (!(fieldDef.sort.field in implicit)) {
11441                        implicit[fieldDef.sort.field] = 'flatten';
11442                    }
11443                }
11444            }
11445            if (isUnitModel(model) || isFacetModel(model)) {
11446                // Parse encoded fields
11447                model.forEachFieldDef((fieldDef, channel) => {
11448                    if (isTypedFieldDef(fieldDef)) {
11449                        add(fieldDef);
11450                    }
11451                    else {
11452                        const mainChannel = getMainRangeChannel(channel);
11453                        if (mainChannel !== channel) {
11454                            const mainFieldDef = model.fieldDef(mainChannel);
11455                            add(Object.assign({}, fieldDef, { type: mainFieldDef.type }));
11456                        }
11457                        else {
11458                            throw new Error(`Non-secondary channel ${channel} must have type in its field definition ${JSON.stringify(fieldDef)}`);
11459                        }
11460                    }
11461                });
11462            }
11463            return this.makeWithAncestors(parent, {}, implicit, ancestorParse);
11464        }
11465        /**
11466         * Creates a parse node from "explicit" parse and "implicit" parse and updates ancestorParse.
11467         */
11468        static makeWithAncestors(parent, explicit, implicit, ancestorParse) {
11469            // We should not parse what has already been parsed in a parent (explicitly or implicitly) or what has been derived (maked as "derived"). We also don't need to flatten a field that has already been parsed.
11470            for (const field$$1 of keys(implicit)) {
11471                const parsedAs = ancestorParse.getWithExplicit(field$$1);
11472                if (parsedAs.value !== undefined) {
11473                    // We always ignore derived fields even if they are implicitly defined because we expect users to create the right types.
11474                    if (parsedAs.explicit ||
11475                        parsedAs.value === implicit[field$$1] ||
11476                        parsedAs.value === 'derived' ||
11477                        implicit[field$$1] === 'flatten') {
11478                        delete implicit[field$$1];
11479                    }
11480                    else {
11481                        warn(message.differentParse(field$$1, implicit[field$$1], parsedAs.value));
11482                    }
11483                }
11484            }
11485            for (const field$$1 of keys(explicit)) {
11486                const parsedAs = ancestorParse.get(field$$1);
11487                if (parsedAs !== undefined) {
11488                    // Don't parse a field again if it has been parsed with the same type already.
11489                    if (parsedAs === explicit[field$$1]) {
11490                        delete explicit[field$$1];
11491                    }
11492                    else {
11493                        warn(message.differentParse(field$$1, explicit[field$$1], parsedAs));
11494                    }
11495                }
11496            }
11497            const parse = new Split(explicit, implicit);
11498            // add the format parse from this model so that children don't parse the same field again
11499            ancestorParse.copyAll(parse);
11500            // copy only non-null parses
11501            const p = {};
11502            for (const key$$1 of keys(parse.combine())) {
11503                const val = parse.get(key$$1);
11504                if (val !== null) {
11505                    p[key$$1] = val;
11506                }
11507            }
11508            if (keys(p).length === 0 || ancestorParse.parseNothing) {
11509                return null;
11510            }
11511            return new ParseNode(parent, p);
11512        }
11513        get parse() {
11514            return this._parse;
11515        }
11516        merge(other) {
11517            this._parse = Object.assign({}, this._parse, other.parse);
11518            other.remove();
11519        }
11520        /**
11521         * Assemble an object for Vega's format.parse property.
11522         */
11523        assembleFormatParse() {
11524            const formatParse = {};
11525            for (const field$$1 of keys(this._parse)) {
11526                const p = this._parse[field$$1];
11527                if (accessPathDepth(field$$1) === 1) {
11528                    formatParse[field$$1] = p;
11529                }
11530            }
11531            return formatParse;
11532        }
11533        // format parse depends and produces all fields in its parse
11534        producedFields() {
11535            return new Set(keys(this._parse));
11536        }
11537        dependentFields() {
11538            return new Set(keys(this._parse));
11539        }
11540        assembleTransforms(onlyNested = false) {
11541            return keys(this._parse)
11542                .filter(field$$1 => (onlyNested ? accessPathDepth(field$$1) > 1 : true))
11543                .map(field$$1 => {
11544                const expr = parseExpression$1(field$$1, this._parse[field$$1]);
11545                if (!expr) {
11546                    return null;
11547                }
11548                const formula = {
11549                    type: 'formula',
11550                    expr,
11551                    as: removePathFromField(field$$1) // Vega output is always flattened
11552                };
11553                return formula;
11554            })
11555                .filter(t => t !== null);
11556        }
11557    }
11558
11559    function getStackByFields(model) {
11560        return model.stack.stackBy.reduce((fields, by) => {
11561            const fieldDef = by.fieldDef;
11562            const _field = vgField(fieldDef);
11563            if (_field) {
11564                fields.push(_field);
11565            }
11566            return fields;
11567        }, []);
11568    }
11569    function isValidAsArray(as) {
11570        return isArray(as) && as.every(s => isString(s)) && as.length > 1;
11571    }
11572    class StackNode extends DataFlowNode {
11573        clone() {
11574            return new StackNode(null, duplicate(this._stack));
11575        }
11576        constructor(parent, stack) {
11577            super(parent);
11578            this._stack = stack;
11579        }
11580        static makeFromTransform(parent, stackTransform) {
11581            const { stack, groupby, as, offset = 'zero' } = stackTransform;
11582            const sortFields = [];
11583            const sortOrder = [];
11584            if (stackTransform.sort !== undefined) {
11585                for (const sortField of stackTransform.sort) {
11586                    sortFields.push(sortField.field);
11587                    sortOrder.push(getFirstDefined(sortField.order, 'ascending'));
11588                }
11589            }
11590            const sort = {
11591                field: sortFields,
11592                order: sortOrder
11593            };
11594            let normalizedAs;
11595            if (isValidAsArray(as)) {
11596                normalizedAs = as;
11597            }
11598            else if (isString(as)) {
11599                normalizedAs = [as, as + '_end'];
11600            }
11601            else {
11602                normalizedAs = [stackTransform.stack + '_start', stackTransform.stack + '_end'];
11603            }
11604            return new StackNode(parent, {
11605                stackField: stack,
11606                groupby,
11607                offset,
11608                sort,
11609                facetby: [],
11610                as: normalizedAs
11611            });
11612        }
11613        static makeFromEncoding(parent, model) {
11614            const stackProperties = model.stack;
11615            const { encoding } = model;
11616            if (!stackProperties) {
11617                return null;
11618            }
11619            let dimensionFieldDef;
11620            if (stackProperties.groupbyChannel) {
11621                const cDef = encoding[stackProperties.groupbyChannel];
11622                dimensionFieldDef = getTypedFieldDef(cDef);
11623            }
11624            const stackby = getStackByFields(model);
11625            const orderDef = model.encoding.order;
11626            let sort;
11627            if (isArray(orderDef) || isFieldDef(orderDef)) {
11628                sort = sortParams(orderDef);
11629            }
11630            else {
11631                // default = descending by stackFields
11632                // FIXME is the default here correct for binned fields?
11633                sort = stackby.reduce((s, field$$1) => {
11634                    s.field.push(field$$1);
11635                    s.order.push('descending');
11636                    return s;
11637                }, { field: [], order: [] });
11638            }
11639            return new StackNode(parent, {
11640                dimensionFieldDef,
11641                stackField: model.vgField(stackProperties.fieldChannel),
11642                facetby: [],
11643                stackby,
11644                sort,
11645                offset: stackProperties.offset,
11646                impute: stackProperties.impute,
11647                as: [
11648                    model.vgField(stackProperties.fieldChannel, { suffix: 'start', forAs: true }),
11649                    model.vgField(stackProperties.fieldChannel, { suffix: 'end', forAs: true })
11650                ]
11651            });
11652        }
11653        get stack() {
11654            return this._stack;
11655        }
11656        addDimensions(fields) {
11657            this._stack.facetby = this._stack.facetby.concat(fields);
11658        }
11659        dependentFields() {
11660            const out = new Set();
11661            out.add(this._stack.stackField);
11662            this.getGroupbyFields().forEach(f => out.add(f));
11663            this._stack.facetby.forEach(f => out.add(f));
11664            const field$$1 = this._stack.sort.field;
11665            isArray(field$$1) ? field$$1.forEach(f => out.add(f)) : out.add(field$$1);
11666            return out;
11667        }
11668        producedFields() {
11669            return new Set(this._stack.as);
11670        }
11671        hash() {
11672            return `Stack ${hash(this._stack)}`;
11673        }
11674        getGroupbyFields() {
11675            const { dimensionFieldDef, impute, groupby } = this._stack;
11676            if (dimensionFieldDef) {
11677                if (dimensionFieldDef.bin) {
11678                    if (impute) {
11679                        // For binned group by field with impute, we calculate bin_mid
11680                        // as we cannot impute two fields simultaneously
11681                        return [vgField(dimensionFieldDef, { binSuffix: 'mid' })];
11682                    }
11683                    return [
11684                        // For binned group by field without impute, we need both bin (start) and bin_end
11685                        vgField(dimensionFieldDef, {}),
11686                        vgField(dimensionFieldDef, { binSuffix: 'end' })
11687                    ];
11688                }
11689                return [vgField(dimensionFieldDef)];
11690            }
11691            return groupby || [];
11692        }
11693        assemble() {
11694            const transform = [];
11695            const { facetby, dimensionFieldDef, stackField: field$$1, stackby, sort, offset, impute, as } = this._stack;
11696            // Impute
11697            if (impute && dimensionFieldDef) {
11698                if (dimensionFieldDef.bin) {
11699                    // As we can only impute one field at a time, we need to calculate
11700                    // mid point for a binned field
11701                    transform.push({
11702                        type: 'formula',
11703                        expr: '(' +
11704                            vgField(dimensionFieldDef, { expr: 'datum' }) +
11705                            '+' +
11706                            vgField(dimensionFieldDef, { expr: 'datum', binSuffix: 'end' }) +
11707                            ')/2',
11708                        as: vgField(dimensionFieldDef, { binSuffix: 'mid', forAs: true })
11709                    });
11710                }
11711                transform.push({
11712                    type: 'impute',
11713                    field: field$$1,
11714                    groupby: [...stackby, ...facetby],
11715                    key: vgField(dimensionFieldDef, { binSuffix: 'mid' }),
11716                    method: 'value',
11717                    value: 0
11718                });
11719            }
11720            // Stack
11721            transform.push({
11722                type: 'stack',
11723                groupby: this.getGroupbyFields().concat(facetby),
11724                field: field$$1,
11725                sort,
11726                as,
11727                offset
11728            });
11729            return transform;
11730        }
11731    }
11732
11733    /**
11734     * A class for the window transform nodes
11735     */
11736    class WindowTransformNode extends DataFlowNode {
11737        constructor(parent, transform) {
11738            super(parent);
11739            this.transform = transform;
11740        }
11741        clone() {
11742            return new WindowTransformNode(null, duplicate(this.transform));
11743        }
11744        addDimensions(fields) {
11745            this.transform.groupby = unique(this.transform.groupby.concat(fields), d => d);
11746        }
11747        dependentFields() {
11748            const out = new Set();
11749            this.transform.groupby.forEach(f => out.add(f));
11750            this.transform.sort.forEach(m => out.add(m.field));
11751            this.transform.window
11752                .map(w => w.field)
11753                .filter(f => f !== undefined)
11754                .forEach(f => out.add(f));
11755            return out;
11756        }
11757        producedFields() {
11758            return new Set(this.transform.window.map(this.getDefaultName));
11759        }
11760        getDefaultName(windowFieldDef) {
11761            return windowFieldDef.as || vgField(windowFieldDef);
11762        }
11763        hash() {
11764            return `WindowTransform ${hash(this.transform)}`;
11765        }
11766        assemble() {
11767            const fields = [];
11768            const ops = [];
11769            const as = [];
11770            const params = [];
11771            for (const window of this.transform.window) {
11772                ops.push(window.op);
11773                as.push(this.getDefaultName(window));
11774                params.push(window.param === undefined ? null : window.param);
11775                fields.push(window.field === undefined ? null : window.field);
11776            }
11777            const frame = this.transform.frame;
11778            const groupby = this.transform.groupby;
11779            const sortFields = [];
11780            const sortOrder = [];
11781            if (this.transform.sort !== undefined) {
11782                for (const sortField of this.transform.sort) {
11783                    sortFields.push(sortField.field);
11784                    sortOrder.push(sortField.order || 'ascending');
11785                }
11786            }
11787            const sort = {
11788                field: sortFields,
11789                order: sortOrder
11790            };
11791            const ignorePeers = this.transform.ignorePeers;
11792            const result = {
11793                type: 'window',
11794                params,
11795                as,
11796                ops,
11797                fields,
11798                sort
11799            };
11800            if (ignorePeers !== undefined) {
11801                result.ignorePeers = ignorePeers;
11802            }
11803            if (groupby !== undefined) {
11804                result.groupby = groupby;
11805            }
11806            if (frame !== undefined) {
11807                result.frame = frame;
11808            }
11809            return result;
11810        }
11811    }
11812
11813    /**
11814     * Move parse nodes up to forks.
11815     */
11816    class MoveParseUp extends BottomUpOptimizer {
11817        run(node) {
11818            const parent = node.parent;
11819            // move parse up by merging or swapping
11820            if (node instanceof ParseNode) {
11821                if (parent instanceof SourceNode) {
11822                    return this.flags;
11823                }
11824                if (parent.numChildren() > 1) {
11825                    // don't move parse further up but continue with parent.
11826                    this.setContinue();
11827                    return this.flags;
11828                }
11829                if (parent instanceof ParseNode) {
11830                    this.setMutated();
11831                    parent.merge(node);
11832                }
11833                else {
11834                    // don't swap with nodes that produce something that the parse node depends on (e.g. lookup)
11835                    if (fieldIntersection(parent.producedFields(), node.dependentFields())) {
11836                        this.setContinue();
11837                        return this.flags;
11838                    }
11839                    this.setMutated();
11840                    node.swapWithParent();
11841                }
11842            }
11843            this.setContinue();
11844            return this.flags;
11845        }
11846    }
11847    /**
11848     * Merge identical nodes at forks by comparing hashes.
11849     *
11850     * Does not need to iterate from leaves so we implement this with recursion as it's a bit simpler.
11851     */
11852    class MergeIdenticalNodes extends TopDownOptimizer {
11853        mergeNodes(parent, nodes) {
11854            const mergedNode = nodes.shift();
11855            for (const node of nodes) {
11856                parent.removeChild(node);
11857                node.parent = mergedNode;
11858                node.remove();
11859            }
11860        }
11861        run(node) {
11862            const hashes = node.children.map(x => x.hash());
11863            const buckets = {};
11864            for (let i = 0; i < hashes.length; i++) {
11865                if (buckets[hashes[i]] === undefined) {
11866                    buckets[hashes[i]] = [node.children[i]];
11867                }
11868                else {
11869                    buckets[hashes[i]].push(node.children[i]);
11870                }
11871            }
11872            for (const k of keys(buckets)) {
11873                if (buckets[k].length > 1) {
11874                    this.setMutated();
11875                    this.mergeNodes(node, buckets[k]);
11876                }
11877            }
11878            for (const child of node.children) {
11879                this.run(child);
11880            }
11881            return this.mutatedFlag;
11882        }
11883    }
11884    /**
11885     * Repeatedly remove leaf nodes that are not output or facet nodes.
11886     * The reason is that we don't need subtrees that don't have any output nodes.
11887     * Facet nodes are needed for the row or column domains.
11888     */
11889    class RemoveUnusedSubtrees extends BottomUpOptimizer {
11890        run(node) {
11891            if (node instanceof OutputNode || node.numChildren() > 0 || node instanceof FacetNode) {
11892                // no need to continue with parent because it is output node or will have children (there was a fork)
11893                return this.flags;
11894            }
11895            else {
11896                this.setMutated();
11897                node.remove();
11898            }
11899            return this.flags;
11900        }
11901    }
11902    /**
11903     * Removes duplicate time unit nodes (as determined by the name of the
11904     * output field) that may be generated due to selections projected over
11905     * time units.
11906     */
11907    class RemoveDuplicateTimeUnits extends BottomUpOptimizer {
11908        constructor() {
11909            super(...arguments);
11910            this.fields = new Set();
11911        }
11912        run(node) {
11913            this.setContinue();
11914            if (node instanceof TimeUnitNode) {
11915                const pfields = node.producedFields();
11916                if (hasIntersection(pfields, this.fields)) {
11917                    this.setMutated();
11918                    node.remove();
11919                }
11920                else {
11921                    this.fields = new Set([...this.fields, ...pfields]);
11922                }
11923            }
11924            return this.flags;
11925        }
11926    }
11927    /**
11928     * Clones the subtree and ignores output nodes except for the leaves, which are renamed.
11929     */
11930    function cloneSubtree(facet) {
11931        function clone(node) {
11932            if (!(node instanceof FacetNode)) {
11933                const copy = node.clone();
11934                if (copy instanceof OutputNode) {
11935                    const newName = FACET_SCALE_PREFIX + copy.getSource();
11936                    copy.setSource(newName);
11937                    facet.model.component.data.outputNodes[newName] = copy;
11938                }
11939                else if (copy instanceof AggregateNode || copy instanceof StackNode || copy instanceof WindowTransformNode) {
11940                    copy.addDimensions(facet.fields);
11941                }
11942                flatten(node.children.map(clone)).forEach((n) => (n.parent = copy));
11943                return [copy];
11944            }
11945            return flatten(node.children.map(clone));
11946        }
11947        return clone;
11948    }
11949    /**
11950     * Move facet nodes down to the next fork or output node. Also pull the main output with the facet node.
11951     * After moving down the facet node, make a copy of the subtree and make it a child of the main output.
11952     */
11953    function moveFacetDown(node) {
11954        if (node instanceof FacetNode) {
11955            if (node.numChildren() === 1 && !(node.children[0] instanceof OutputNode)) {
11956                // move down until we hit a fork or output node
11957                const child = node.children[0];
11958                if (child instanceof AggregateNode || child instanceof StackNode || child instanceof WindowTransformNode) {
11959                    child.addDimensions(node.fields);
11960                }
11961                child.swapWithParent();
11962                moveFacetDown(node);
11963            }
11964            else {
11965                // move main to facet
11966                const facetMain = node.model.component.data.main;
11967                moveMainDownToFacet(facetMain);
11968                // replicate the subtree and place it before the facet's main node
11969                const cloner = cloneSubtree(node);
11970                const copy = flatten(node.children.map(cloner));
11971                for (const c of copy) {
11972                    c.parent = facetMain;
11973                }
11974            }
11975        }
11976        else {
11977            node.children.map(moveFacetDown);
11978        }
11979    }
11980    function moveMainDownToFacet(node) {
11981        if (node instanceof OutputNode && node.type === MAIN) {
11982            if (node.numChildren() === 1) {
11983                const child = node.children[0];
11984                if (!(child instanceof FacetNode)) {
11985                    child.swapWithParent();
11986                    moveMainDownToFacet(node);
11987                }
11988            }
11989        }
11990    }
11991    /**
11992     * Remove nodes that are not required starting from a root.
11993     */
11994    class RemoveUnnecessaryNodes extends TopDownOptimizer {
11995        run(node) {
11996            // remove output nodes that are not required
11997            if (node instanceof OutputNode && !node.isRequired()) {
11998                this.setMutated();
11999                node.remove();
12000            }
12001            for (const child of node.children) {
12002                this.run(child);
12003            }
12004            return this.mutatedFlag;
12005        }
12006    }
12007    /**
12008     * Inserts an Intermediate ParseNode containing all non-conflicting Parse fields and removes the empty ParseNodes
12009     */
12010    class MergeParse extends BottomUpOptimizer {
12011        run(node) {
12012            const parent = node.parent;
12013            const parseChildren = parent.children.filter((x) => x instanceof ParseNode);
12014            if (parseChildren.length > 1) {
12015                const commonParse = {};
12016                for (const parseNode of parseChildren) {
12017                    const parse = parseNode.parse;
12018                    for (const k of keys(parse)) {
12019                        if (commonParse[k] === undefined) {
12020                            commonParse[k] = parse[k];
12021                        }
12022                        else if (commonParse[k] !== parse[k]) {
12023                            delete commonParse[k];
12024                        }
12025                    }
12026                }
12027                if (keys(commonParse).length !== 0) {
12028                    this.setMutated();
12029                    const mergedParseNode = new ParseNode(parent, commonParse);
12030                    for (const parseNode of parseChildren) {
12031                        for (const key of keys(commonParse)) {
12032                            delete parseNode.parse[key];
12033                        }
12034                        parent.removeChild(parseNode);
12035                        parseNode.parent = mergedParseNode;
12036                        if (keys(parseNode.parse).length === 0) {
12037                            parseNode.remove();
12038                        }
12039                    }
12040                }
12041            }
12042            this.setContinue();
12043            return this.flags;
12044        }
12045    }
12046    class MergeAggregateNodes extends BottomUpOptimizer {
12047        run(node) {
12048            const parent = node.parent;
12049            const aggChildren = parent.children.filter((x) => x instanceof AggregateNode);
12050            // Object which we'll use to map the fields which an aggregate is grouped by to
12051            // the set of aggregates with that grouping. This is useful as only aggregates
12052            // with the same group by can be merged
12053            const groupedAggregates = {};
12054            // Build groupedAggregates
12055            for (const agg of aggChildren) {
12056                const groupBys = hash(keys(agg.groupBy).sort());
12057                if (!(groupBys in groupedAggregates)) {
12058                    groupedAggregates[groupBys] = [];
12059                }
12060                groupedAggregates[groupBys].push(agg);
12061            }
12062            // Merge aggregateNodes with same key in groupedAggregates
12063            for (const group of keys(groupedAggregates)) {
12064                const mergeableAggs = groupedAggregates[group];
12065                if (mergeableAggs.length > 1) {
12066                    const mergedAggs = mergeableAggs.pop();
12067                    for (const agg of mergeableAggs) {
12068                        if (mergedAggs.merge(agg)) {
12069                            parent.removeChild(agg);
12070                            agg.parent = mergedAggs;
12071                            agg.remove();
12072                            this.setMutated();
12073                        }
12074                    }
12075                }
12076            }
12077            this.setContinue();
12078            return this.flags;
12079        }
12080    }
12081
12082    const FACET_SCALE_PREFIX = 'scale_';
12083    const MAX_OPTIMIZATION_RUNS = 5;
12084    /**
12085     * Return all leaf nodes.
12086     */
12087    function getLeaves(roots) {
12088        const leaves = [];
12089        function append(node) {
12090            if (node.numChildren() === 0) {
12091                leaves.push(node);
12092            }
12093            else {
12094                node.children.forEach(append);
12095            }
12096        }
12097        roots.forEach(append);
12098        return leaves;
12099    }
12100    function isTrue(x) {
12101        return x;
12102    }
12103    /**
12104     * Run the specified optimizer on the provided nodes.
12105     *
12106     * @param optimizer The optimizer to run.
12107     * @param nodes A set of nodes to optimize.
12108     * @param flag Flag that will be or'ed with return valued from optimization calls to the nodes.
12109     */
12110    function runOptimizer(optimizer, nodes, flag) {
12111        const flags = nodes.map(node => {
12112            const optimizerInstance = new optimizer();
12113            if (optimizerInstance instanceof BottomUpOptimizer) {
12114                return optimizerInstance.optimizeNextFromLeaves(node);
12115            }
12116            else {
12117                return optimizerInstance.run(node);
12118            }
12119        });
12120        return flags.some(isTrue) || flag;
12121    }
12122    function optimizationDataflowHelper(dataComponent) {
12123        let roots = dataComponent.sources;
12124        let mutatedFlag = false;
12125        // mutatedFlag should always be on the right side otherwise short circuit logic might cause the mutating method to not execute
12126        mutatedFlag = runOptimizer(RemoveUnnecessaryNodes, roots, mutatedFlag);
12127        // remove source nodes that don't have any children because they also don't have output nodes
12128        roots = roots.filter(r => r.numChildren() > 0);
12129        mutatedFlag = runOptimizer(RemoveUnusedSubtrees, getLeaves(roots), mutatedFlag);
12130        roots = roots.filter(r => r.numChildren() > 0);
12131        mutatedFlag = runOptimizer(MoveParseUp, getLeaves(roots), mutatedFlag);
12132        mutatedFlag = runOptimizer(RemoveDuplicateTimeUnits, getLeaves(roots), mutatedFlag);
12133        mutatedFlag = runOptimizer(MergeParse, getLeaves(roots), mutatedFlag);
12134        mutatedFlag = runOptimizer(MergeAggregateNodes, getLeaves(roots), mutatedFlag);
12135        mutatedFlag = runOptimizer(MergeIdenticalNodes, roots, mutatedFlag);
12136        dataComponent.sources = roots;
12137        return mutatedFlag;
12138    }
12139    /**
12140     * Optimizes the dataflow of the passed in data component.
12141     */
12142    function optimizeDataflow(data) {
12143        // check before optimizations
12144        checkLinks(data.sources);
12145        let firstPassCounter = 0;
12146        let secondPassCounter = 0;
12147        for (let i = 0; i < MAX_OPTIMIZATION_RUNS; i++) {
12148            if (!optimizationDataflowHelper(data)) {
12149                break;
12150            }
12151            firstPassCounter++;
12152        }
12153        // move facets down and make a copy of the subtree so that we can have scales at the top level
12154        data.sources.map(moveFacetDown);
12155        for (let i = 0; i < MAX_OPTIMIZATION_RUNS; i++) {
12156            if (!optimizationDataflowHelper(data)) {
12157                break;
12158            }
12159            secondPassCounter++;
12160        }
12161        // check after optimizations
12162        checkLinks(data.sources);
12163        if (Math.max(firstPassCounter, secondPassCounter) === MAX_OPTIMIZATION_RUNS) {
12164            warn(`Maximum optimization runs(${MAX_OPTIMIZATION_RUNS}) reached.`);
12165        }
12166    }
12167
12168    function parseScaleDomain(model) {
12169        if (isUnitModel(model)) {
12170            parseUnitScaleDomain(model);
12171        }
12172        else {
12173            parseNonUnitScaleDomain(model);
12174        }
12175    }
12176    function parseUnitScaleDomain(model) {
12177        const scales = model.specifiedScales;
12178        const localScaleComponents = model.component.scales;
12179        keys(localScaleComponents).forEach((channel) => {
12180            const specifiedScale = scales[channel];
12181            const specifiedDomain = specifiedScale ? specifiedScale.domain : undefined;
12182            const domains = parseDomainForChannel(model, channel);
12183            const localScaleCmpt = localScaleComponents[channel];
12184            localScaleCmpt.domains = domains;
12185            if (isSelectionDomain(specifiedDomain)) {
12186                // As scale parsing occurs before selection parsing, we use a temporary
12187                // signal here and append the scale.domain definition. This is replaced
12188                // with the correct domainRaw signal during scale assembly.
12189                // For more information, see isRawSelectionDomain in selection.ts.
12190                // FIXME: replace this with a special property in the scaleComponent
12191                localScaleCmpt.set('domainRaw', {
12192                    signal: SELECTION_DOMAIN + hash(specifiedDomain)
12193                }, true);
12194            }
12195            if (model.component.data.isFaceted) {
12196                // get resolve from closest facet parent as this decides whether we need to refer to cloned subtree or not
12197                let facetParent = model;
12198                while (!isFacetModel(facetParent) && facetParent.parent) {
12199                    facetParent = facetParent.parent;
12200                }
12201                const resolve = facetParent.component.resolve.scale[channel];
12202                if (resolve === 'shared') {
12203                    for (const domain of domains) {
12204                        // Replace the scale domain with data output from a cloned subtree after the facet.
12205                        if (isDataRefDomain(domain)) {
12206                            // use data from cloned subtree (which is the same as data but with a prefix added once)
12207                            domain.data = FACET_SCALE_PREFIX + domain.data.replace(FACET_SCALE_PREFIX, '');
12208                        }
12209                    }
12210                }
12211            }
12212        });
12213    }
12214    function parseNonUnitScaleDomain(model) {
12215        for (const child of model.children) {
12216            parseScaleDomain(child);
12217        }
12218        const localScaleComponents = model.component.scales;
12219        keys(localScaleComponents).forEach((channel) => {
12220            let domains;
12221            let domainRaw = null;
12222            for (const child of model.children) {
12223                const childComponent = child.component.scales[channel];
12224                if (childComponent) {
12225                    if (domains === undefined) {
12226                        domains = childComponent.domains;
12227                    }
12228                    else {
12229                        domains = domains.concat(childComponent.domains);
12230                    }
12231                    const dr = childComponent.get('domainRaw');
12232                    if (domainRaw && dr && domainRaw.signal !== dr.signal) {
12233                        warn('The same selection must be used to override scale domains in a layered view.');
12234                    }
12235                    domainRaw = dr;
12236                }
12237            }
12238            localScaleComponents[channel].domains = domains;
12239            if (domainRaw) {
12240                localScaleComponents[channel].set('domainRaw', domainRaw, true);
12241            }
12242        });
12243    }
12244    /**
12245     * Remove unaggregated domain if it is not applicable
12246     * Add unaggregated domain if domain is not specified and config.scale.useUnaggregatedDomain is true.
12247     */
12248    function normalizeUnaggregatedDomain(domain, fieldDef, scaleType, scaleConfig) {
12249        if (domain === 'unaggregated') {
12250            const { valid, reason } = canUseUnaggregatedDomain(fieldDef, scaleType);
12251            if (!valid) {
12252                warn(reason);
12253                return undefined;
12254            }
12255        }
12256        else if (domain === undefined && scaleConfig.useUnaggregatedDomain) {
12257            // Apply config if domain is not specified.
12258            const { valid } = canUseUnaggregatedDomain(fieldDef, scaleType);
12259            if (valid) {
12260                return 'unaggregated';
12261            }
12262        }
12263        return domain;
12264    }
12265    function parseDomainForChannel(model, channel) {
12266        const scaleType = model.getScaleComponent(channel).get('type');
12267        const domain = normalizeUnaggregatedDomain(model.scaleDomain(channel), model.fieldDef(channel), scaleType, model.config.scale);
12268        if (domain !== model.scaleDomain(channel)) {
12269            model.specifiedScales[channel] = Object.assign({}, model.specifiedScales[channel], { domain });
12270        }
12271        // If channel is either X or Y then union them with X2 & Y2 if they exist
12272        if (channel === 'x' && model.channelHasField('x2')) {
12273            if (model.channelHasField('x')) {
12274                return parseSingleChannelDomain(scaleType, domain, model, 'x').concat(parseSingleChannelDomain(scaleType, domain, model, 'x2'));
12275            }
12276            else {
12277                return parseSingleChannelDomain(scaleType, domain, model, 'x2');
12278            }
12279        }
12280        else if (channel === 'y' && model.channelHasField('y2')) {
12281            if (model.channelHasField('y')) {
12282                return parseSingleChannelDomain(scaleType, domain, model, 'y').concat(parseSingleChannelDomain(scaleType, domain, model, 'y2'));
12283            }
12284            else {
12285                return parseSingleChannelDomain(scaleType, domain, model, 'y2');
12286            }
12287        }
12288        return parseSingleChannelDomain(scaleType, domain, model, channel);
12289    }
12290    function mapDomainToDataSignal(domain, type, timeUnit) {
12291        return domain.map(v => {
12292            const data = valueExpr(v, { timeUnit, type });
12293            return { signal: `{data: ${data}}` };
12294        });
12295    }
12296    function parseSingleChannelDomain(scaleType, domain, model, channel) {
12297        const fieldDef = model.fieldDef(channel);
12298        if (domain && domain !== 'unaggregated' && !isSelectionDomain(domain)) {
12299            // explicit value
12300            const { type, timeUnit } = fieldDef;
12301            if (type === 'temporal' || timeUnit) {
12302                return mapDomainToDataSignal(domain, type, timeUnit);
12303            }
12304            return [domain];
12305        }
12306        const stack = model.stack;
12307        if (stack && channel === stack.fieldChannel) {
12308            if (stack.offset === 'normalize') {
12309                return [[0, 1]];
12310            }
12311            const data = model.requestDataName(MAIN);
12312            return [
12313                {
12314                    data,
12315                    field: model.vgField(channel, { suffix: 'start' })
12316                },
12317                {
12318                    data,
12319                    field: model.vgField(channel, { suffix: 'end' })
12320                }
12321            ];
12322        }
12323        const sort = isScaleChannel(channel) ? domainSort(model, channel, scaleType) : undefined;
12324        if (domain === 'unaggregated') {
12325            const data = model.requestDataName(MAIN);
12326            const { field: field$$1 } = fieldDef;
12327            return [
12328                {
12329                    data,
12330                    field: vgField({ field: field$$1, aggregate: 'min' })
12331                },
12332                {
12333                    data,
12334                    field: vgField({ field: field$$1, aggregate: 'max' })
12335                }
12336            ];
12337        }
12338        else if (isBinning(fieldDef.bin)) {
12339            // bin
12340            if (isBinScale(scaleType)) {
12341                const signal = model.getName(`${binToString(fieldDef.bin)}_${fieldDef.field}_bins`);
12342                return [{ signal: `sequence(${signal}.start, ${signal}.stop + ${signal}.step, ${signal}.step)` }];
12343            }
12344            if (hasDiscreteDomain(scaleType)) {
12345                // ordinal bin scale takes domain from bin_range, ordered by bin start
12346                // This is useful for both axis-based scale (x/y) and legend-based scale (other channels).
12347                return [
12348                    {
12349                        // If sort by aggregation of a specified sort field, we need to use RAW table,
12350                        // so we can aggregate values for the scale independently from the main aggregation.
12351                        data: isBoolean$1(sort) ? model.requestDataName(MAIN) : model.requestDataName(RAW),
12352                        // Use range if we added it and the scale does not support computing a range as a signal.
12353                        field: model.vgField(channel, binRequiresRange(fieldDef, channel) ? { binSuffix: 'range' } : {}),
12354                        // we have to use a sort object if sort = true to make the sort correct by bin start
12355                        sort: sort === true || !isSortField(sort)
12356                            ? {
12357                                field: model.vgField(channel, {}),
12358                                op: 'min' // min or max doesn't matter since we sort by the start of the bin range
12359                            }
12360                            : sort
12361                    }
12362                ];
12363            }
12364            else {
12365                // continuous scales
12366                if (channel === 'x' || channel === 'y') {
12367                    if (isBinParams(fieldDef.bin) && fieldDef.bin.extent) {
12368                        return [fieldDef.bin.extent];
12369                    }
12370                    // X/Y position have to include start and end for non-ordinal scale
12371                    const data = model.requestDataName(MAIN);
12372                    return [
12373                        {
12374                            data,
12375                            field: model.vgField(channel, {})
12376                        },
12377                        {
12378                            data,
12379                            field: model.vgField(channel, { binSuffix: 'end' })
12380                        }
12381                    ];
12382                }
12383                else {
12384                    // TODO: use bin_mid
12385                    return [
12386                        {
12387                            data: model.requestDataName(MAIN),
12388                            field: model.vgField(channel, {})
12389                        }
12390                    ];
12391                }
12392            }
12393        }
12394        else if (sort) {
12395            return [
12396                {
12397                    // If sort by aggregation of a specified sort field, we need to use RAW table,
12398                    // so we can aggregate values for the scale independently from the main aggregation.
12399                    data: isBoolean$1(sort) ? model.requestDataName(MAIN) : model.requestDataName(RAW),
12400                    field: model.vgField(channel),
12401                    sort: sort
12402                }
12403            ];
12404        }
12405        else {
12406            return [
12407                {
12408                    data: model.requestDataName(MAIN),
12409                    field: model.vgField(channel)
12410                }
12411            ];
12412        }
12413    }
12414    function domainSort(model, channel, scaleType) {
12415        if (!hasDiscreteDomain(scaleType)) {
12416            return undefined;
12417        }
12418        // save to cast as the only exception is the geojson type for shape, which would not generate a scale
12419        const fieldDef = model.fieldDef(channel);
12420        const sort = fieldDef.sort;
12421        // if the sort is specified with array, use the derived sort index field
12422        if (isSortArray(sort)) {
12423            return {
12424                op: 'min',
12425                field: sortArrayIndexField(fieldDef, channel),
12426                order: 'ascending'
12427            };
12428        }
12429        // Sorted based on an aggregate calculation over a specified sort field (only for ordinal scale)
12430        if (isSortField(sort)) {
12431            // flatten nested fields
12432            return Object.assign({}, sort, (sort.field ? { field: replacePathInField(sort.field) } : {}));
12433        }
12434        if (sort === 'descending') {
12435            return {
12436                op: 'min',
12437                field: model.vgField(channel),
12438                order: 'descending'
12439            };
12440        }
12441        if (contains(['ascending', undefined /* default =ascending*/], sort)) {
12442            return true;
12443        }
12444        // sort == null
12445        return undefined;
12446    }
12447    /**
12448     * Determine if a scale can use unaggregated domain.
12449     * @return {Boolean} Returns true if all of the following conditions apply:
12450     * 1. `scale.domain` is `unaggregated`
12451     * 2. Aggregation function is not `count` or `sum`
12452     * 3. The scale is quantitative or time scale.
12453     */
12454    function canUseUnaggregatedDomain(fieldDef, scaleType) {
12455        if (!fieldDef.aggregate) {
12456            return {
12457                valid: false,
12458                reason: message.unaggregateDomainHasNoEffectForRawField(fieldDef)
12459            };
12460        }
12461        if (!SHARED_DOMAIN_OP_INDEX[fieldDef.aggregate]) {
12462            return {
12463                valid: false,
12464                reason: message.unaggregateDomainWithNonSharedDomainOp(fieldDef.aggregate)
12465            };
12466        }
12467        if (fieldDef.type === 'quantitative') {
12468            if (scaleType === 'log') {
12469                return {
12470                    valid: false,
12471                    reason: message.unaggregatedDomainWithLogScale(fieldDef)
12472                };
12473            }
12474        }
12475        return { valid: true };
12476    }
12477    /**
12478     * Converts an array of domains to a single Vega scale domain.
12479     */
12480    function mergeDomains(domains) {
12481        const uniqueDomains = unique(domains.map(domain => {
12482            // ignore sort property when computing the unique domains
12483            if (isDataRefDomain(domain)) {
12484                const domainWithoutSort = __rest(domain, ["sort"]);
12485                return domainWithoutSort;
12486            }
12487            return domain;
12488        }), hash);
12489        const sorts = unique(domains
12490            .map(d => {
12491            if (isDataRefDomain(d)) {
12492                const s = d.sort;
12493                if (s !== undefined && !isBoolean$1(s)) {
12494                    if (s.op === 'count') {
12495                        // let's make sure that if op is count, we don't use a field
12496                        delete s.field;
12497                    }
12498                    if (s.order === 'ascending') {
12499                        // drop order: ascending as it is the default
12500                        delete s.order;
12501                    }
12502                }
12503                return s;
12504            }
12505            return undefined;
12506        })
12507            .filter(s => s !== undefined), hash);
12508        if (uniqueDomains.length === 1) {
12509            const domain = domains[0];
12510            if (isDataRefDomain(domain) && sorts.length > 0) {
12511                let sort = sorts[0];
12512                if (sorts.length > 1) {
12513                    warn(message.MORE_THAN_ONE_SORT);
12514                    sort = true;
12515                }
12516                return Object.assign({}, domain, { sort });
12517            }
12518            return domain;
12519        }
12520        // only keep simple sort properties that work with unioned domains
12521        const simpleSorts = unique(sorts.map(s => {
12522            if (isBoolean$1(s)) {
12523                return s;
12524            }
12525            if (s.op === 'count') {
12526                return s;
12527            }
12528            warn(message.domainSortDropped(s));
12529            return true;
12530        }), hash);
12531        let sort;
12532        if (simpleSorts.length === 1) {
12533            sort = simpleSorts[0];
12534        }
12535        else if (simpleSorts.length > 1) {
12536            warn(message.MORE_THAN_ONE_SORT);
12537            sort = true;
12538        }
12539        const allData = unique(domains.map(d => {
12540            if (isDataRefDomain(d)) {
12541                return d.data;
12542            }
12543            return null;
12544        }), x => x);
12545        if (allData.length === 1 && allData[0] !== null) {
12546            // create a union domain of different fields with a single data source
12547            const domain = Object.assign({ data: allData[0], fields: uniqueDomains.map(d => d.field) }, (sort ? { sort } : {}));
12548            return domain;
12549        }
12550        return Object.assign({ fields: uniqueDomains }, (sort ? { sort } : {}));
12551    }
12552    /**
12553     * Return a field if a scale single field.
12554     * Return `undefined` otherwise.
12555     *
12556     */
12557    function getFieldFromDomain(domain) {
12558        if (isDataRefDomain(domain) && isString(domain.field)) {
12559            return domain.field;
12560        }
12561        else if (isDataRefUnionedDomain(domain)) {
12562            let field$$1;
12563            for (const nonUnionDomain of domain.fields) {
12564                if (isDataRefDomain(nonUnionDomain) && isString(nonUnionDomain.field)) {
12565                    if (!field$$1) {
12566                        field$$1 = nonUnionDomain.field;
12567                    }
12568                    else if (field$$1 !== nonUnionDomain.field) {
12569                        warn('Detected faceted independent scales that union domain of multiple fields from different data sources.  We will use the first field.  The result view size may be incorrect.');
12570                        return field$$1;
12571                    }
12572                }
12573            }
12574            warn('Detected faceted independent scales that union domain of identical fields from different source detected.  We will assume that this is the same field from a different fork of the same data source.  However, if this is not case, the result view size maybe incorrect.');
12575            return field$$1;
12576        }
12577        else if (isFieldRefUnionDomain(domain)) {
12578            warn('Detected faceted independent scales that union domain of multiple fields from the same data source.  We will use the first field.  The result view size may be incorrect.');
12579            const field$$1 = domain.fields[0];
12580            return isString(field$$1) ? field$$1 : undefined;
12581        }
12582        return undefined;
12583    }
12584    function assembleDomain(model, channel) {
12585        const scaleComponent = model.component.scales[channel];
12586        const domains = scaleComponent.domains.map(domain => {
12587            // Correct references to data as the original domain's data was determined
12588            // in parseScale, which happens before parseData. Thus the original data
12589            // reference can be incorrect.
12590            if (isDataRefDomain(domain)) {
12591                domain.data = model.lookupDataSource(domain.data);
12592            }
12593            return domain;
12594        });
12595        // domains is an array that has to be merged into a single vega domain
12596        return mergeDomains(domains);
12597    }
12598
12599    function assembleScales(model) {
12600        if (isLayerModel(model) || isConcatModel(model) || isRepeatModel(model)) {
12601            // For concat / layer / repeat, include scales of children too
12602            return model.children.reduce((scales, child) => {
12603                return scales.concat(assembleScales(child));
12604            }, assembleScalesForModel(model));
12605        }
12606        else {
12607            // For facet, child scales would not be included in the parent's scope.
12608            // For unit, there is no child.
12609            return assembleScalesForModel(model);
12610        }
12611    }
12612    function assembleScalesForModel(model) {
12613        return keys(model.component.scales).reduce((scales, channel) => {
12614            const scaleComponent = model.component.scales[channel];
12615            if (scaleComponent.merged) {
12616                // Skipped merged scales
12617                return scales;
12618            }
12619            const scale = scaleComponent.combine();
12620            // need to separate const and non const object destruction
12621            let { domainRaw } = scale;
12622            const { name, type, domainRaw: _d, range: _r } = scale, otherScaleProps = __rest(scale, ["name", "type", "domainRaw", "range"]);
12623            const range = assembleScaleRange(scale.range, name, model, channel);
12624            // As scale parsing occurs before selection parsing, a temporary signal
12625            // is used for domainRaw. Here, we detect if this temporary signal
12626            // is set, and replace it with the correct domainRaw signal.
12627            // For more information, see isRawSelectionDomain in selection.ts.
12628            if (domainRaw && isRawSelectionDomain(domainRaw)) {
12629                domainRaw = selectionScaleDomain(model, domainRaw);
12630            }
12631            scales.push(Object.assign({ name,
12632                type, domain: assembleDomain(model, channel) }, (domainRaw ? { domainRaw } : {}), { range: range }, otherScaleProps));
12633            return scales;
12634        }, []);
12635    }
12636    function assembleExprSignal(scaleRange, model) {
12637        let signal = scaleRange.expr;
12638        for (const signalName of scaleRange.signalNames) {
12639            const newName = model.getSignalName(signalName);
12640            if (newName !== signalName) {
12641                // replace the signal name
12642                const regEx = globalWholeWordRegExp(signalName);
12643                signal = signal.replace(regEx, newName);
12644            }
12645        }
12646        return { signal };
12647    }
12648    function assembleScaleRange(scaleRange, scaleName, model, channel) {
12649        if (scaleRange instanceof SignalRefComponent) {
12650            return assembleExprSignal(scaleRange, model);
12651        }
12652        else if (isArray(scaleRange)) {
12653            return scaleRange.map(val => {
12654                if (val instanceof SignalRefComponent) {
12655                    return assembleExprSignal(val, model);
12656                }
12657                return val;
12658            });
12659        }
12660        else {
12661            // add signals to x/y range
12662            if (channel === 'x' || channel === 'y') {
12663                if (isVgRangeStep(scaleRange)) {
12664                    // For x/y range step, use a signal created in layout assemble instead of a constant range step.
12665                    return {
12666                        step: { signal: scaleName + '_step' }
12667                    };
12668                }
12669            }
12670            return scaleRange;
12671        }
12672    }
12673
12674    class ScaleComponent extends Split {
12675        constructor(name, typeWithExplicit) {
12676            super({}, // no initial explicit property
12677            { name } // name as initial implicit property
12678            );
12679            this.merged = false;
12680            this.domains = [];
12681            this.setWithExplicit('type', typeWithExplicit);
12682        }
12683    }
12684
12685    const RANGE_PROPERTIES = ['range', 'rangeStep', 'scheme'];
12686    function getSizeType(channel) {
12687        return channel === 'x' ? 'width' : channel === 'y' ? 'height' : undefined;
12688    }
12689    function parseUnitScaleRange(model) {
12690        const localScaleComponents = model.component.scales;
12691        // use SCALE_CHANNELS instead of scales[channel] to ensure that x, y come first!
12692        SCALE_CHANNELS.forEach((channel) => {
12693            const localScaleCmpt = localScaleComponents[channel];
12694            if (!localScaleCmpt) {
12695                return;
12696            }
12697            const mergedScaleCmpt = model.getScaleComponent(channel);
12698            const specifiedScale = model.specifiedScales[channel];
12699            const fieldDef = model.fieldDef(channel);
12700            // Read if there is a specified width/height
12701            const sizeType = getSizeType(channel);
12702            let sizeSpecified = sizeType ? !!model.component.layoutSize.get(sizeType) : undefined;
12703            const scaleType = mergedScaleCmpt.get('type');
12704            // if autosize is fit, size cannot be data driven
12705            const rangeStep = contains(['point', 'band'], scaleType) || !!specifiedScale.rangeStep;
12706            if (sizeType && model.fit && !sizeSpecified && rangeStep) {
12707                warn(message.CANNOT_FIX_RANGE_STEP_WITH_FIT);
12708                sizeSpecified = true;
12709            }
12710            const xyRangeSteps = getXYRangeStep(model);
12711            const rangeWithExplicit = parseRangeForChannel(channel, scaleType, fieldDef.type, specifiedScale, model.config, localScaleCmpt.get('zero'), model.mark, sizeSpecified, model.getName(sizeType), xyRangeSteps);
12712            localScaleCmpt.setWithExplicit('range', rangeWithExplicit);
12713        });
12714    }
12715    function getRangeStep(model, channel) {
12716        const scaleCmpt = model.getScaleComponent(channel);
12717        if (!scaleCmpt) {
12718            return undefined;
12719        }
12720        const scaleType = scaleCmpt.get('type');
12721        const fieldDef = model.fieldDef(channel);
12722        if (hasDiscreteDomain(scaleType)) {
12723            const range = scaleCmpt && scaleCmpt.get('range');
12724            if (range && isVgRangeStep(range) && isNumber(range.step)) {
12725                return range.step;
12726            }
12727            // TODO: support the case without range step
12728        }
12729        else if (fieldDef && fieldDef.bin) {
12730            if (isBinning(fieldDef.bin)) {
12731                const binSignal = model.getName(vgField(fieldDef, { suffix: 'bins' }));
12732                // TODO: extract this to be range step signal
12733                const binCount = `(${binSignal}.stop - ${binSignal}.start) / ${binSignal}.step`;
12734                const sizeType = getSizeType(channel);
12735                const sizeSignal = model.getName(sizeType);
12736                return new SignalRefComponent(`${sizeSignal} / (${binCount})`, [sizeSignal, binSignal]);
12737            }
12738            // TODO: handle binned case
12739        }
12740        return undefined;
12741    }
12742    function getXYRangeStep(model) {
12743        const steps = [];
12744        for (const channel of POSITION_SCALE_CHANNELS) {
12745            const step = getRangeStep(model, channel);
12746            if (step !== undefined) {
12747                steps.push(step);
12748            }
12749        }
12750        return steps;
12751    }
12752    /**
12753     * Return mixins that includes one of the range properties (range, rangeStep, scheme).
12754     */
12755    function parseRangeForChannel(channel, scaleType, type, specifiedScale, config, zero$$1, mark, sizeSpecified, sizeSignal, xyRangeSteps) {
12756        const noRangeStep = sizeSpecified || specifiedScale.rangeStep === null;
12757        // Check if any of the range properties is specified.
12758        // If so, check if it is compatible and make sure that we only output one of the properties
12759        for (const property of RANGE_PROPERTIES) {
12760            if (specifiedScale[property] !== undefined) {
12761                const supportedByScaleType = scaleTypeSupportProperty(scaleType, property);
12762                const channelIncompatability = channelScalePropertyIncompatability(channel, property);
12763                if (!supportedByScaleType) {
12764                    warn(message.scalePropertyNotWorkWithScaleType(scaleType, property, channel));
12765                }
12766                else if (channelIncompatability) {
12767                    // channel
12768                    warn(channelIncompatability);
12769                }
12770                else {
12771                    switch (property) {
12772                        case 'range':
12773                            return makeExplicit(specifiedScale[property]);
12774                        case 'scheme':
12775                            return makeExplicit(parseScheme(specifiedScale[property]));
12776                        case 'rangeStep':
12777                            const rangeStep = specifiedScale[property];
12778                            if (rangeStep !== null) {
12779                                if (!sizeSpecified) {
12780                                    return makeExplicit({ step: rangeStep });
12781                                }
12782                                else {
12783                                    // If top-level size is specified, we ignore specified rangeStep.
12784                                    warn(message.rangeStepDropped(channel));
12785                                }
12786                            }
12787                    }
12788                }
12789            }
12790        }
12791        return makeImplicit(defaultRange(channel, scaleType, type, config, zero$$1, mark, sizeSignal, xyRangeSteps, noRangeStep, specifiedScale.domain));
12792    }
12793    function parseScheme(scheme) {
12794        if (isExtendedScheme(scheme)) {
12795            const r = { scheme: scheme.name };
12796            if (scheme.count) {
12797                r.count = scheme.count;
12798            }
12799            if (scheme.extent) {
12800                r.extent = scheme.extent;
12801            }
12802            return r;
12803        }
12804        return { scheme: scheme };
12805    }
12806    function defaultRange(channel, scaleType, type, config, zero$$1, mark, sizeSignal, xyRangeSteps, noRangeStep, domain) {
12807        switch (channel) {
12808            case X:
12809            case Y:
12810                if (contains(['point', 'band'], scaleType) && !noRangeStep) {
12811                    if (channel === X && mark === 'text') {
12812                        if (config.scale.textXRangeStep) {
12813                            return { step: config.scale.textXRangeStep };
12814                        }
12815                    }
12816                    else {
12817                        if (config.scale.rangeStep) {
12818                            return { step: config.scale.rangeStep };
12819                        }
12820                    }
12821                }
12822                // If range step is null, use zero to width or height.
12823                // Note that these range signals are temporary
12824                // as they can be merged and renamed.
12825                // (We do not have the right size signal here since parseLayoutSize() happens after parseScale().)
12826                // We will later replace these temporary names with
12827                // the final name in assembleScaleRange()
12828                if (channel === Y && hasContinuousDomain(scaleType)) {
12829                    // For y continuous scale, we have to start from the height as the bottom part has the max value.
12830                    return [SignalRefComponent.fromName(sizeSignal), 0];
12831                }
12832                else {
12833                    return [0, SignalRefComponent.fromName(sizeSignal)];
12834                }
12835            case SIZE:
12836                // TODO: support custom rangeMin, rangeMax
12837                const rangeMin = sizeRangeMin(mark, zero$$1, config);
12838                const rangeMax = sizeRangeMax(mark, xyRangeSteps, config);
12839                if (isContinuousToDiscrete(scaleType)) {
12840                    return interpolateRange(rangeMin, rangeMax, defaultContinuousToDiscreteCount(scaleType, config, domain, channel));
12841                }
12842                else {
12843                    return [rangeMin, rangeMax];
12844                }
12845            case STROKEWIDTH:
12846                // TODO: support custom rangeMin, rangeMax
12847                return [config.scale.minStrokeWidth, config.scale.maxStrokeWidth];
12848            case SHAPE:
12849                return 'symbol';
12850            case COLOR:
12851            case FILL:
12852            case STROKE:
12853                if (scaleType === 'ordinal') {
12854                    // Only nominal data uses ordinal scale by default
12855                    return type === 'nominal' ? 'category' : 'ordinal';
12856                }
12857                else if (isContinuousToDiscrete(scaleType)) {
12858                    const count = defaultContinuousToDiscreteCount(scaleType, config, domain, channel);
12859                    if (config.range && isVgScheme(config.range.ordinal)) {
12860                        return Object.assign({}
12860, config.range.ordinal, { count });
12861                    }
12862                    else {
12863                        return { scheme: 'blues', count };
12864                    }
12865                }
12866                else if (isContinuousToContinuous(scaleType)) {
12867                    // Manually set colors for now. We will revise this after https://github.com/vega/vega/issues/1369
12868                    return ['#f7fbff', '#0e427f'];
12869                }
12870                else {
12871                    return mark === 'rect' || mark === 'geoshape' ? 'heatmap' : 'ramp';
12872                }
12873            case OPACITY:
12874            case FILLOPACITY:
12875            case STROKEOPACITY:
12876                // TODO: support custom rangeMin, rangeMax
12877                return [config.scale.minOpacity, config.scale.maxOpacity];
12878        }
12879        /* istanbul ignore next: should never reach here */
12880        throw new Error(`Scale range undefined for channel ${channel}`);
12881    }
12882    function defaultContinuousToDiscreteCount(scaleType, config, domain, channel) {
12883        switch (scaleType) {
12884            case 'quantile':
12885                return config.scale.quantileCount;
12886            case 'quantize':
12887                return config.scale.quantizeCount;
12888            case 'threshold':
12889                if (domain !== undefined && isArray(domain)) {
12890                    return domain.length + 1;
12891                }
12892                else {
12893                    warn(message.domainRequiredForThresholdScale(channel));
12894                    // default threshold boundaries for threshold scale since domain has cardinality of 2
12895                    return 3;
12896                }
12897        }
12898    }
12899    /**
12900     * Returns the linear interpolation of the range according to the cardinality
12901     *
12902     * @param rangeMin start of the range
12903     * @param rangeMax end of the range
12904     * @param cardinality number of values in the output range
12905     */
12906    function interpolateRange(rangeMin, rangeMax, cardinality) {
12907        const step = '(rangeMax - rangeMin) / (cardinality - 1)';
12908        return evalOrMakeSignalRefComponent(`sequence(rangeMin, rangeMax + ${step}, ${step})`, {
12909            rangeMin,
12910            rangeMax,
12911            cardinality
12912        });
12913    }
12914    function sizeRangeMin(mark, zero$$1, config) {
12915        if (zero$$1) {
12916            return 0;
12917        }
12918        switch (mark) {
12919            case 'bar':
12920            case 'tick':
12921                return config.scale.minBandSize;
12922            case 'line':
12923            case 'trail':
12924            case 'rule':
12925                return config.scale.minStrokeWidth;
12926            case 'text':
12927                return config.scale.minFontSize;
12928            case 'point':
12929            case 'square':
12930            case 'circle':
12931                return config.scale.minSize;
12932        }
12933        /* istanbul ignore next: should never reach here */
12934        // sizeRangeMin not implemented for the mark
12935        throw new Error(message.incompatibleChannel('size', mark));
12936    }
12937    const MAX_SIZE_RANGE_STEP_RATIO = 0.95;
12938    function sizeRangeMax(mark, xyRangeSteps, config) {
12939        const scaleConfig = config.scale;
12940        switch (mark) {
12941            case 'bar':
12942            case 'tick':
12943                if (config.scale.maxBandSize !== undefined) {
12944                    return config.scale.maxBandSize;
12945                }
12946                const min = minXYRangeStep(xyRangeSteps, config.scale);
12947                return evalOrMakeSignalRefComponent(`min - 1`, { min });
12948            case 'line':
12949            case 'trail':
12950            case 'rule':
12951                return config.scale.maxStrokeWidth;
12952            case 'text':
12953                return config.scale.maxFontSize;
12954            case 'point':
12955            case 'square':
12956            case 'circle':
12957                if (config.scale.maxSize) {
12958                    return config.scale.maxSize;
12959                }
12960                const pointStep = minXYRangeStep(xyRangeSteps, scaleConfig);
12961                return evalOrMakeSignalRefComponent(`pow(${MAX_SIZE_RANGE_STEP_RATIO} * pointStep, 2)`, {
12962                    pointStep
12963                });
12964        }
12965        /* istanbul ignore next: should never reach here */
12966        // sizeRangeMax not implemented for the mark
12967        throw new Error(message.incompatibleChannel('size', mark));
12968    }
12969    /**
12970     * @returns {number} Range step of x or y or minimum between the two if both are ordinal scale.
12971     */
12972    function minXYRangeStep(xyRangeSteps, scaleConfig) {
12973        if (xyRangeSteps.length > 0) {
12974            const exprs = [];
12975            const signalNames = [];
12976            let min = Infinity;
12977            for (const step of xyRangeSteps) {
12978                if (step instanceof SignalRefComponent) {
12979                    exprs.push(step.expr);
12980                    for (const signalName of step.signalNames) {
12981                        signalNames.push(signalName);
12982                    }
12983                    min = undefined;
12984                }
12985                else {
12986                    exprs.push(step);
12987                    if (min !== undefined && step < min) {
12988                        min = step;
12989                    }
12990                }
12991            }
12992            return min !== undefined ? min : new SignalRefComponent(`min(${exprs.join(', ')})`, signalNames);
12993        }
12994        if (scaleConfig.rangeStep) {
12995            return scaleConfig.rangeStep;
12996        }
12997        return 21; // FIXME: re-evaluate the default value here.
12998    }
12999
13000    function parseScaleProperty(model, property) {
13001        if (isUnitModel(model)) {
13002            parseUnitScaleProperty(model, property);
13003        }
13004        else {
13005            parseNonUnitScaleProperty(model, property);
13006        }
13007    }
13008    function parseUnitScaleProperty(model, property) {
13009        const localScaleComponents = model.component.scales;
13010        keys(localScaleComponents).forEach((channel) => {
13011            const specifiedScale = model.specifiedScales[channel];
13012            const localScaleCmpt = localScaleComponents[channel];
13013            const mergedScaleCmpt = model.getScaleComponent(channel);
13014            const fieldDef = model.fieldDef(channel);
13015            const config = model.config;
13016            const specifiedValue = specifiedScale[property];
13017            const sType = mergedScaleCmpt.get('type');
13018            const supportedByScaleType = scaleTypeSupportProperty(sType, property);
13019            const channelIncompatability = channelScalePropertyIncompatability(channel, property);
13020            if (specifiedValue !== undefined) {
13021                // If there is a specified value, check if it is compatible with scale type and channel
13022                if (!supportedByScaleType) {
13023                    warn(message.scalePropertyNotWorkWithScaleType(sType, property, channel));
13024                }
13025                else if (channelIncompatability) {
13026                    // channel
13027                    warn(channelIncompatability);
13028                }
13029            }
13030            if (supportedByScaleType && channelIncompatability === undefined) {
13031                if (specifiedValue !== undefined) {
13032                    // copyKeyFromObject ensure type safety
13033                    localScaleCmpt.copyKeyFromObject(property, specifiedScale);
13034                }
13035                else {
13036                    const value = getDefaultValue(property, channel, fieldDef, mergedScaleCmpt.get('type'), mergedScaleCmpt.get('padding'), mergedScaleCmpt.get('paddingInner'), specifiedScale.domain, model.markDef, config);
13037                    if (value !== undefined) {
13038                        localScaleCmpt.set(property, value, false);
13039                    }
13040                }
13041            }
13042        });
13043    }
13044    // Note: This method is used in Voyager.
13045    function getDefaultValue(property, channel, fieldDef, scaleType, scalePadding, scalePaddingInner, specifiedDomain, markDef, config) {
13046        const scaleConfig = config.scale;
13047        // If we have default rule-base, determine default value first
13048        switch (property) {
13049            case 'interpolate':
13050                return interpolate(channel, scaleType);
13051            case 'nice':
13052                return nice(scaleType, channel, fieldDef);
13053            case 'padding':
13054                return padding(channel, scaleType, scaleConfig, fieldDef, markDef, config.bar);
13055            case 'paddingInner':
13056                return paddingInner(scalePadding, channel, markDef.type, scaleConfig);
13057            case 'paddingOuter':
13058                return paddingOuter(scalePadding, channel, scaleType, markDef.type, scalePaddingInner, scaleConfig);
13059            case 'reverse':
13060                return reverse(scaleType, fieldDef.sort);
13061            case 'zero':
13062                return zero$2(channel, fieldDef, specifiedDomain, markDef, scaleType);
13063        }
13064        // Otherwise, use scale config
13065        return scaleConfig[property];
13066    }
13067    // This method is here rather than in range.ts to avoid circular dependency.
13068    function parseScaleRange(model) {
13069        if (isUnitModel(model)) {
13070            parseUnitScaleRange(model);
13071        }
13072        else {
13073            parseNonUnitScaleProperty(model, 'range');
13074        }
13075    }
13076    function parseNonUnitScaleProperty(model, property) {
13077        const localScaleComponents = model.component.scales;
13078        for (const child of model.children) {
13079            if (property === 'range') {
13080                parseScaleRange(child);
13081            }
13082            else {
13083                parseScaleProperty(child, property);
13084            }
13085        }
13086        keys(localScaleComponents).forEach((channel) => {
13087            let valueWithExplicit;
13088            for (const child of model.children) {
13089                const childComponent = child.component.scales[channel];
13090                if (childComponent) {
13091                    const childValueWithExplicit = childComponent.getWithExplicit(property);
13092                    valueWithExplicit = mergeValuesWithExplicit(valueWithExplicit, childValueWithExplicit, property, 'scale', tieBreakByComparing((v1, v2) => {
13093                        switch (property) {
13094                            case 'range':
13095                                // For range step, prefer larger step
13096                                if (v1.step && v2.step) {
13097                                    return v1.step - v2.step;
13098                                }
13099                                return 0;
13100                            // TODO: precedence rule for other properties
13101                        }
13102                        return 0;
13103                    }));
13104                }
13105            }
13106            localScaleComponents[channel].setWithExplicit(property, valueWithExplicit);
13107        });
13108    }
13109    function interpolate(channel, scaleType) {
13110        if (contains([COLOR, FILL, STROKE], channel) && isContinuousToContinuous(scaleType)) {
13111            return 'hcl';
13112        }
13113        return undefined;
13114    }
13115    function nice(scaleType, channel, fieldDef) {
13116        if (fieldDef.bin || contains([ScaleType.TIME, ScaleType.UTC], scaleType)) {
13117            return undefined;
13118        }
13119        return contains([X, Y], channel) ? true : undefined;
13120    }
13121    function padding(channel, scaleType, scaleConfig, fieldDef, markDef, barConfig) {
13122        if (contains([X, Y], channel)) {
13123            if (isContinuousToContinuous(scaleType)) {
13124                if (scaleConfig.continuousPadding !== undefined) {
13125                    return scaleConfig.continuousPadding;
13126                }
13127                const { type, orient } = markDef;
13128                if (type === 'bar' && !fieldDef.bin) {
13129                    if ((orient === 'vertical' && channel === 'x') || (orient === 'horizontal' && channel === 'y')) {
13130                        return barConfig.continuousBandSize;
13131                    }
13132                }
13133            }
13134            if (scaleType === ScaleType.POINT) {
13135                return scaleConfig.pointPadding;
13136            }
13137        }
13138        return undefined;
13139    }
13140    function paddingInner(paddingValue, channel, mark, scaleConfig) {
13141        if (paddingValue !== undefined) {
13142            // If user has already manually specified "padding", no need to add default paddingInner.
13143            return undefined;
13144        }
13145        if (contains([X, Y], channel)) {
13146            // Padding is only set for X and Y by default.
13147            // Basically it doesn't make sense to add padding for color and size.
13148            // paddingOuter would only be called if it's a band scale, just return the default for bandScale.
13149            const { bandPaddingInner, barBandPaddingInner, rectBandPaddingInner } = scaleConfig;
13150            return getFirstDefined(bandPaddingInner, mark === 'bar' ? barBandPaddingInner : rectBandPaddingInner);
13151        }
13152        return undefined;
13153    }
13154    function paddingOuter(paddingValue, channel, scaleType, mark, paddingInnerValue, scaleConfig) {
13155        if (paddingValue !== undefined) {
13156            // If user has already manually specified "padding", no need to add default paddingOuter.
13157            return undefined;
13158        }
13159        if (contains([X, Y], channel)) {
13160            // Padding is only set for X and Y by default.
13161            // Basically it doesn't make sense to add padding for color and size.
13162            if (scaleType === ScaleType.BAND) {
13163                const { bandPaddingOuter, barBandPaddingOuter, rectBandPaddingOuter } = scaleConfig;
13164                return getFirstDefined(bandPaddingOuter, mark === 'bar' ? barBandPaddingOuter : rectBandPaddingOuter, 
13165                /* By default, paddingOuter is paddingInner / 2. The reason is that
13166                  size (width/height) = step * (cardinality - paddingInner + 2 * paddingOuter).
13167                  and we want the width/height to be integer by default.
13168                  Note that step (by default) and cardinality are integers.) */
13169                paddingInnerValue / 2);
13170            }
13171        }
13172        return undefined;
13173    }
13174    function reverse(scaleType, sort) {
13175        if (hasContinuousDomain(scaleType) && sort === 'descending') {
13176            // For continuous domain scales, Vega does not support domain sort.
13177            // Thus, we reverse range instead if sort is descending
13178            return true;
13179        }
13180        return undefined;
13181    }
13182    function zero$2(channel, fieldDef, specifiedScale, markDef, scaleType) {
13183        // If users explicitly provide a domain range, we should not augment zero as that will be unexpected.
13184        const hasCustomDomain = !!specifiedScale && specifiedScale !== 'unaggregated';
13185        if (hasCustomDomain) {
13186            return false;
13187        }
13188        // If there is no custom domain, return true only for the following cases:
13189        // 1) using quantitative field with size
13190        // While this can be either ratio or interval fields, our assumption is that
13191        // ratio are more common. However, if the scaleType is discretizing scale, we want to return
13192        // false so that range doesn't start at zero
13193        if (channel === 'size' && fieldDef.type === 'quantitative' && !isContinuousToDiscrete(scaleType)) {
13194            return true;
13195        }
13196        // 2) non-binned, quantitative x-scale or y-scale
13197        // (For binning, we should not include zero by default because binning are calculated without zero.)
13198        if (!fieldDef.bin && contains([X, Y], channel)) {
13199            const { orient, type }
13199 = markDef;
13200            if (contains(['bar', 'area', 'line', 'trail'], type)) {
13201                if ((orient === 'horizontal' && channel === 'y') || (orient === 'vertical' && channel === 'x')) {
13202                    return false;
13203                }
13204            }
13205            return true;
13206        }
13207        return false;
13208    }
13209
13210    /**
13211     * Determine if there is a specified scale type and if it is appropriate,
13212     * or determine default type if type is unspecified or inappropriate.
13213     */
13214    // NOTE: CompassQL uses this method.
13215    function scaleType(specifiedScale, channel, fieldDef, mark, scaleConfig) {
13216        const defaultScaleType = defaultType$2(channel, fieldDef, mark, specifiedScale, scaleConfig);
13217        const { type } = specifiedScale;
13218        if (!isScaleChannel(channel)) {
13219            // There is no scale for these channels
13220            return null;
13221        }
13222        if (type !== undefined) {
13223            // Check if explicitly specified scale type is supported by the channel
13224            if (!channelSupportScaleType(channel, type)) {
13225                warn(message.scaleTypeNotWorkWithChannel(channel, type, defaultScaleType));
13226                return defaultScaleType;
13227            }
13228            // Check if explicitly specified scale type is supported by the data type
13229            if (!scaleTypeSupportDataType(type, fieldDef.type, fieldDef.bin)) {
13230                warn(message.scaleTypeNotWorkWithFieldDef(type, defaultScaleType));
13231                return defaultScaleType;
13232            }
13233            return type;
13234        }
13235        return defaultScaleType;
13236    }
13237    /**
13238     * Determine appropriate default scale type.
13239     */
13240    // NOTE: Voyager uses this method.
13241    function defaultType$2(channel, fieldDef, mark, specifiedScale, scaleConfig) {
13242        switch (fieldDef.type) {
13243            case 'nominal':
13244            case 'ordinal':
13245                if (isColorChannel(channel) || rangeType(channel) === 'discrete') {
13246                    if (channel === 'shape' && fieldDef.type === 'ordinal') {
13247                        warn(message.discreteChannelCannotEncode(channel, 'ordinal'));
13248                    }
13249                    return 'ordinal';
13250                }
13251                if (contains(['x', 'y'], channel)) {
13252                    if (contains(['rect', 'bar', 'rule'], mark)) {
13253                        // The rect/bar mark should fit into a band.
13254                        // For rule, using band scale to make rule align with axis ticks better https://github.com/vega/vega-lite/issues/3429
13255                        return 'band';
13256                    }
13257                    if (mark === 'bar') {
13258                        return 'band';
13259                    }
13260                }
13261                // Otherwise, use ordinal point scale so we can easily get center positions of the marks.
13262                return 'point';
13263            case 'temporal':
13264                if (isColorChannel(channel)) {
13265                    return 'time';
13266                }
13267                else if (rangeType(channel) === 'discrete') {
13268                    warn(message.discreteChannelCannotEncode(channel, 'temporal'));
13269                    // TODO: consider using quantize (equivalent to binning) once we have it
13270                    return 'ordinal';
13271                }
13272                return 'time';
13273            case 'quantitative':
13274                if (isColorChannel(channel)) {
13275                    if (isBinning(fieldDef.bin)) {
13276                        return 'bin-ordinal';
13277                    }
13278                    const { domain = undefined, range = undefined } = specifiedScale || {};
13279                    if (domain && isArray(domain) && domain.length > 2 && (range && isArray(range) && range.length > 2)) {
13280                        // If there are piecewise domain and range specified, use linear as default color scale as sequential does not support piecewise domain
13281                        return 'linear';
13282                    }
13283                    // Use `sequential` as the default color scale for continuous data
13284                    // since it supports both array range and scheme range.
13285                    return 'sequential';
13286                }
13287                else if (rangeType(channel) === 'discrete') {
13288                    warn(message.discreteChannelCannotEncode(channel, 'quantitative'));
13289                    // TODO: consider using quantize (equivalent to binning) once we have it
13290                    return 'ordinal';
13291                }
13292                // x and y use a linear scale because selections don't work with bin scales.
13293                // Binned scales apply discretization but pan/zoom apply transformations to a [min, max] extent domain.
13294                if (isBinning(fieldDef.bin) && channel !== 'x' && channel !== 'y') {
13295                    return 'bin-linear';
13296                }
13297                return 'linear';
13298            case 'geojson':
13299                return undefined;
13300        }
13301        /* istanbul ignore next: should never reach this */
13302        throw new Error(message.invalidFieldType(fieldDef.type));
13303    }
13304
13305    function parseScale(model) {
13306        parseScaleCore(model);
13307        parseScaleDomain(model);
13308        for (const prop of NON_TYPE_DOMAIN_RANGE_VEGA_SCALE_PROPERTIES) {
13309            parseScaleProperty(model, prop);
13310        }
13311        // range depends on zero
13312        parseScaleRange(model);
13313    }
13314    function parseScaleCore(model) {
13315        if (isUnitModel(model)) {
13316            model.component.scales = parseUnitScaleCore(model);
13317        }
13318        else {
13319            model.component.scales = parseNonUnitScaleCore(model);
13320        }
13321    }
13322    /**
13323     * Parse scales for all channels of a model.
13324     */
13325    function parseUnitScaleCore(model) {
13326        const { encoding, config, mark } = model;
13327        return SCALE_CHANNELS.reduce((scaleComponents, channel) => {
13328            let fieldDef;
13329            let specifiedScale;
13330            const channelDef = encoding[channel];
13331            // Don't generate scale for shape of geoshape
13332            if (isFieldDef(channelDef) && mark === GEOSHAPE && channel === SHAPE && channelDef.type === GEOJSON) {
13333                return scaleComponents;
13334            }
13335            if (isFieldDef(channelDef)) {
13336                fieldDef = channelDef;
13337                specifiedScale = channelDef.scale;
13338            }
13339            else if (hasConditionalFieldDef(channelDef)) {
13340                fieldDef = channelDef.condition;
13341                specifiedScale = channelDef.condition['scale']; // We use ['scale'] since we know that channel here has scale for sure
13342            }
13343            if (fieldDef && specifiedScale !== null && specifiedScale !== false) {
13344                specifiedScale = specifiedScale || {};
13345                const sType = scaleType(specifiedScale, channel, fieldDef, mark, config.scale);
13346                scaleComponents[channel] = new ScaleComponent(model.scaleName(channel + '', true), {
13347                    value: sType,
13348                    explicit: specifiedScale.type === sType
13349                });
13350            }
13351            return scaleComponents;
13352        }, {});
13353    }
13354    const scaleTypeTieBreaker = tieBreakByComparing((st1, st2) => scaleTypePrecedence(st1) - scaleTypePrecedence(st2));
13355    function parseNonUnitScaleCore(model) {
13356        const scaleComponents = (model.component.scales = {});
13357        const scaleTypeWithExplicitIndex = {};
13358        const resolve = model.component.resolve;
13359        // Parse each child scale and determine if a particular channel can be merged.
13360        for (const child of model.children) {
13361            parseScaleCore(child);
13362            // Instead of always merging right away -- check if it is compatible to merge first!
13363            keys(child.component.scales).forEach((channel) => {
13364                // if resolve is undefined, set default first
13365                resolve.scale[channel] = resolve.scale[channel] || defaultScaleResolve(channel, model);
13366                if (resolve.scale[channel] === 'shared') {
13367                    const explicitScaleType = scaleTypeWithExplicitIndex[channel];
13368                    const childScaleType = child.component.scales[channel].getWithExplicit('type');
13369                    if (explicitScaleType) {
13370                        if (scaleCompatible(explicitScaleType.value, childScaleType.value)) {
13371                            // merge scale component if type are compatible
13372                            scaleTypeWithExplicitIndex[channel] = mergeValuesWithExplicit(explicitScaleType, childScaleType, 'type', 'scale', scaleTypeTieBreaker);
13373                        }
13374                        else {
13375                            // Otherwise, update conflicting channel to be independent
13376                            resolve.scale[channel] = 'independent';
13377                            // Remove from the index so they don't get merged
13378                            delete scaleTypeWithExplicitIndex[channel];
13379                        }
13380                    }
13381                    else {
13382                        scaleTypeWithExplicitIndex[channel] = childScaleType;
13383                    }
13384                }
13385            });
13386        }
13387        // Merge each channel listed in the index
13388        keys(scaleTypeWithExplicitIndex).forEach((channel) => {
13389            // Create new merged scale component
13390            const name = model.scaleName(channel, true);
13391            const typeWithExplicit = scaleTypeWithExplicitIndex[channel];
13392            scaleComponents[channel] = new ScaleComponent(name, typeWithExplicit);
13393            // rename each child and mark them as merged
13394            for (const child of model.children) {
13395                const childScale = child.component.scales[channel];
13396                if (childScale) {
13397                    child.renameScale(childScale.get('name'), name);
13398                    childScale.merged = true;
13399                }
13400            }
13401        });
13402        return scaleComponents;
13403    }
13404
13405    class NameMap {
13406        constructor() {
13407            this.nameMap = {};
13408        }
13409        rename(oldName, newName) {
13410            this.nameMap[oldName] = newName;
13411        }
13412        has(name) {
13413            return this.nameMap[name] !== undefined;
13414        }
13415        get(name) {
13416            // If the name appears in the _nameMap, we need to read its new name.
13417            // We have to loop over the dict just in case the new name also gets renamed.
13418            while (this.nameMap[name] && name !== this.nameMap[name]) {
13419                name = this.nameMap[name];
13420            }
13421            return name;
13422        }
13423    }
13424    /*
13425      We use type guards instead of `instanceof` as `instanceof` makes
13426      different parts of the compiler depend on the actual implementation of
13427      the model classes, which in turn depend on different parts of the compiler.
13428      Thus, `instanceof` leads to circular dependency problems.
13429
13430      On the other hand, type guards only make different parts of the compiler
13431      depend on the type of the model classes, but not the actual implementation.
13432    */
13433    function isUnitModel(model) {
13434        return model && model.type === 'unit';
13435    }
13436    function isFacetModel(model) {
13437        return model && model.type === 'facet';
13438    }
13439    function isRepeatModel(model) {
13440        return model && model.type === 'repeat';
13441    }
13442    function isConcatModel(model) {
13443        return model && model.type === 'concat';
13444    }
13445    function isLayerModel(model) {
13446        return model && model.type === 'layer';
13447    }
13448    class Model {
13449        constructor(spec, parent, parentGivenName, config, repeater, resolve) {
13450            this.children = [];
13451            /**
13452             * Corrects the data references in marks after assemble.
13453             */
13454            this.correctDataNames = (mark) => {
13455                // TODO: make this correct
13456                // for normal data references
13457                if (mark.from && mark.from.data) {
13458                    mark.from.data = this.lookupDataSource(mark.from.data);
13459                }
13460                // for access to facet data
13461                if (mark.from && mark.from.facet && mark.from.facet.data) {
13462                    mark.from.facet.data = this.lookupDataSource(mark.from.facet.data);
13463                }
13464                return mark;
13465            };
13466            this.parent = parent;
13467            this.config = config;
13468            this.repeater = repeater;
13469            // If name is not provided, always use parent's givenName to avoid name conflicts.
13470            this.name = spec.name || parentGivenName;
13471            this.title = isString(spec.title) ? { text: spec.title } : spec.title;
13472            // Shared name maps
13473            this.scaleNameMap = parent ? parent.scaleNameMap : new NameMap();
13474            this.projectionNameMap = parent ? parent.projectionNameMap : new NameMap();
13475            this.signalNameMap = parent ? parent.signalNameMap : new NameMap();
13476            this.data = spec.data;
13477            this.description = spec.description;
13478            this.transforms = normalizeTransform(spec.transform || []);
13479            this.layout =
13480                isUnitSpec(spec) || isLayerSpec(spec) ? undefined : extractCompositionLayout(spec);
13481            this.component = {
13482                data: {
13483                    sources: parent ? parent.component.data.sources : [],
13484                    outputNodes: parent ? parent.component.data.outputNodes : {},
13485                    outputNodeRefCounts: parent ? parent.component.data.outputNodeRefCounts : {},
13486                    // data is faceted if the spec is a facet spec or the parent has faceted data and no data is defined
13487                    isFaceted: isFacetSpec(spec) || (parent && parent.component.data.isFaceted && !spec.data)
13488                },
13489                layoutSize: new Split(),
13490                layoutHeaders: { row: {}, column: {} },
13491                mark: null,
13492                resolve: Object.assign({ scale: {}, axis: {}, legend: {} }, (resolve ? duplicate(resolve) : {})),
13493                selection: null,
13494                scales: null,
13495                projection: null,
13496                axes: {},
13497                legends: {}
13498            };
13499        }
13500        get width() {
13501            return this.getSizeSignalRef('width');
13502        }
13503        get height() {
13504            return this.getSizeSignalRef('height');
13505        }
13506        initSize(size) {
13507            const { width, height } = size;
13508            if (width) {
13509                this.component.layoutSize.set('width', width, true);
13510            }
13511            if (height) {
13512                this.component.layoutSize.set('height', height, true);
13513            }
13514        }
13515        parse() {
13516            this.parseScale();
13517            this.parseLayoutSize(); // depends on scale
13518            this.renameTopLevelLayoutSizeSignal();
13519            this.parseSelection();
13520            this.parseProjection();
13521            this.parseData(); // (pathorder) depends on markDef; selection filters depend on parsed selections; depends on projection because some transforms require the finalized projection name.
13522            this.parseAxisAndHeader(); // depends on scale and layout size
13523            this.parseLegend(); // depends on scale, markDef
13524            this.parseMarkGroup(); // depends on data name, scale, layout size, axisGroup, and children's scale, axis, legend and mark.
13525        }
13526        parseScale() {
13527            parseScale(this);
13528        }
13529        parseProjection() {
13530            parseProjection(this);
13531        }
13532        /**
13533         * Rename top-level spec's size to be just width / height, ignoring model name.
13534         * This essentially merges the top-level spec's width/height signals with the width/height signals
13535         * to help us reduce redundant signals declaration.
13536         */
13537        renameTopLevelLayoutSizeSignal() {
13538            if (this.getName('width') !== 'width') {
13539                this.renameSignal(this.getName('width'), 'width');
13540            }
13541            if (this.getName('height') !== 'height') {
13542                this.renameSignal(this.getName('height'), 'height');
13543            }
13544        }
13545        parseLegend() {
13546            parseLegend(this);
13547        }
13548        assembleGroupStyle() {
13549            if (this.type === 'unit' || this.type === 'layer') {
13550                return 'cell';
13551            }
13552            return undefined;
13553        }
13554        assembleLayoutSize() {
13555            if (this.type === 'unit' || this.type === 'layer') {
13556                return {
13557                    width: this.getSizeSignalRef('width'),
13558                    height: this.getSizeSignalRef('height')
13559                };
13560            }
13561            return undefined;
13562        }
13563        assembleLayout() {
13564            if (!this.layout) {
13565                return undefined;
13566            }
13567            const { align, bounds, center, spacing = {} } = this.layout;
13568            return Object.assign({ padding: isNumber(spacing)
13569                    ? spacing
13570                    : {
13571                        row: spacing.row || 10,
13572                        column: spacing.column || 10
13573                    } }, this.assembleDefaultLayout(), (align ? { align } : {}), (bounds ? { bounds } : {}), (center ? { center } : {}));
13574        }
13575        assembleDefaultLayout() {
13576            return {};
13577        }
13578        assembleHeaderMarks() {
13579            const { layoutHeaders } = this.component;
13580            let headerMarks = [];
13581            for (const channel of HEADER_CHANNELS) {
13582                if (layoutHeaders[channel].title) {
13583                    headerMarks.push(getTitleGroup(this, channel));
13584                }
13585            }
13586            for (const channel of HEADER_CHANNELS) {
13587                headerMarks = headerMarks.concat(getHeaderGroups(this, channel));
13588            }
13589            return headerMarks;
13590        }
13591        assembleAxes() {
13592            return assembleAxes(this.component.axes, this.config);
13593        }
13594        assembleLegends() {
13595            return assembleLegends(this);
13596        }
13597        assembleProjections() {
13598            return assembleProjections(this);
13599        }
13600        assembleTitle() {
13601            const _a = this.title || {}, { encoding } = _a, titleNoEncoding = __rest(_a, ["encoding"]);
13602            const title$$1 = Object.assign({}, extractTitleConfig(this.config.title).nonMark, titleNoEncoding, (encoding ? { encode: { update: encoding } } : {}));
13603            if (title$$1.text) {
13604                if (!contains(['unit', 'layer'], this.type)) {
13605                    // As described in https://github.com/vega/vega-lite/issues/2875:
13606                    // Due to vega/vega#960 (comment), we only support title's anchor for unit and layered spec for now.
13607                    if (title$$1.anchor && title$$1.anchor !== 'start') {
13608                        warn(message.cannotSetTitleAnchor(this.type));
13609                    }
13610                    title$$1.anchor = 'start';
13611                }
13612                if (contains(['middle', undefined], title$$1.anchor) && title$$1.frame === undefined) {
13613                    title$$1.frame = 'group';
13614                }
13615                return keys(title$$1).length > 0 ? title$$1 : undefined;
13616            }
13617            return undefined;
13618        }
13619        /**
13620         * Assemble the mark group for this model.  We accept optional `signals` so that we can include concat top-level signals with the top-level model's local signals.
13621         */
13622        assembleGroup(signals = []) {
13623            const group = {};
13624            signals = signals.concat(this.assembleSelectionSignals());
13625            if (signals.length > 0) {
13626                group.signals = signals;
13627            }
13628            const layout = this.assembleLayout();
13629            if (layout) {
13630                group.layout = layout;
13631            }
13632            group.marks = [].concat(this.assembleHeaderMarks(), this.assembleMarks());
13633            // Only include scales if this spec is top-level or if parent is facet.
13634            // (Otherwise, it will be merged with upper-level's scope.)
13635            const scales = !this.parent || isFacetModel(this.parent) ? assembleScales(this) : [];
13636            if (scales.length > 0) {
13637                group.scales = scales;
13638            }
13639            const axes = this.assembleAxes();
13640            if (axes.length > 0) {
13641                group.axes = axes;
13642            }
13643            const legends = this.assembleLegends();
13644            if (legends.length > 0) {
13645                group.legends = legends;
13646            }
13647            return group;
13648        }
13649        hasDescendantWithFieldOnChannel(channel) {
13650            for (const child of this.children) {
13651                if (isUnitModel(child)) {
13652                    if (child.channelHasField(channel)) {
13653                        return true;
13654                    }
13655                }
13656                else {
13657                    if (child.hasDescendantWithFieldOnChannel(channel)) {
13658                        return true;
13659                    }
13660                }
13661            }
13662            return false;
13663        }
13664        getName(text) {
13665            return varName((this.name ? this.name + '_' : '') + text);
13666        }
13667        /**
13668         * Request a data source name for the given data source type and mark that data source as required. This method should be called in parse, so that all used data source can be correctly instantiated in assembleData().
13669         */
13670        requestDataName(name) {
13671            const fullName = this.getName(name);
13672            // Increase ref count. This is critical because otherwise we won't create a data source.
13673            // We also increase the ref counts on OutputNode.getSource() calls.
13674            const refCounts = this.component.data.outputNodeRefCounts;
13675            refCounts[fullName] = (refCounts[fullName] || 0) + 1;
13676            return fullName;
13677        }
13678        getSizeSignalRef(sizeType) {
13679            if (isFacetModel(this.parent)) {
13680                const channel = sizeType === 'width' ? 'x' : 'y';
13681                const scaleComponent = this.component.scales[channel];
13682                if (scaleComponent && !scaleComponent.merged) {
13683                    // independent scale
13684                    const type = scaleComponent.get('type');
13685                    const range = scaleComponent.get('range');
13686                    if (hasDiscreteDomain(type) && isVgRangeStep(range)) {
13687                        const scaleName = scaleComponent.get('name');
13688                        const domain = assembleDomain(this, channel);
13689                        const field$$1 = getFieldFromDomain(domain);
13690                        if (field$$1) {
13691                            const fieldRef = vgField({ aggregate: 'distinct', field: field$$1 }, { expr: 'datum' });
13692                            return {
13693                                signal: sizeExpr(scaleName, scaleComponent, fieldRef)
13694                            };
13695                        }
13696                        else {
13697                            warn('Unknown field for ${channel}.  Cannot calculate view size.');
13698                            return null;
13699                        }
13700                    }
13701                }
13702            }
13703            return {
13704                signal: this.signalNameMap.get(this.getName(sizeType))
13705            };
13706        }
13707        /**
13708         * Lookup the name of the datasource for an output node. You probably want to call this in assemble.
13709         */
13710        lookupDataSource(name) {
13711            const node = this.component.data.outputNodes[name];
13712            if (!node) {
13713                // Name not found in map so let's just return what we got.
13714                // This can happen if we already have the correct name.
13715                return name;
13716            }
13717            return node.getSource();
13718        }
13719        getSignalName(oldSignalName) {
13720            return this.signalNameMap.get(oldSignalName);
13721        }
13722        renameSignal(oldName, newName) {
13723            this.signalNameMap.rename(oldName, newName);
13724        }
13725        renameScale(oldName, newName) {
13726            this.scaleNameMap.rename(oldName, newName);
13727        }
13728        renameProjection(oldName, newName) {
13729            this.projectionNameMap.rename(oldName, newName);
13730        }
13731        /**
13732         * @return scale name for a given channel after the scale has been parsed and named.
13733         */
13734        scaleName(originalScaleName, parse) {
13735            if (parse) {
13736                // During the parse phase always return a value
13737                // No need to refer to rename map because a scale can't be renamed
13738                // before it has the original name.
13739                return this.getName(originalScaleName);
13740            }
13741            // If there is a scale for the channel, it should either
13742            // be in the scale component or exist in the name map
13743            if (
13744            // If there is a scale for the channel, there should be a local scale component for it
13745            (isChannel(originalScaleName) && isScaleChannel(originalScaleName) && this.component.scales[originalScaleName]) ||
13746                // in the scale name map (the scale get merged by its parent)
13747                this.scaleNameMap.has(this.getName(originalScaleName))) {
13748                return this.scaleNameMap.get(this.getName(originalScaleName));
13749            }
13750            return undefined;
13751        }
13752        /**
13753         * @return projection name after the projection has been parsed and named.
13754         */
13755        projectionName(parse) {
13756            if (parse) {
13757                // During the parse phase always return a value
13758                // No need to refer to rename map because a projection can't be renamed
13759                // before it has the original name.
13760                return this.getName('projection');
13761            }
13762            if ((this.component.projection && !this.component.projection.merged) ||
13763                this.projectionNameMap.has(this.getName('projection'))) {
13764                return this.projectionNameMap.get(this.getName('projection'));
13765            }
13766            return undefined;
13767        }
13768        /**
13769         * Traverse a model's hierarchy to get the scale component for a particular channel.
13770         */
13771        getScaleComponent(channel) {
13772            /* istanbul ignore next: This is warning for debugging test */
13773            if (!this.component.scales) {
13774                throw new Error('getScaleComponent cannot be called before parseScale().  Make sure you have called parseScale or use parseUnitModelWithScale().');
13775            }
13776            const localScaleComponent = this.component.scales[channel];
13777            if (localScaleComponent && !localScaleComponent.merged) {
13778                return localScaleComponent;
13779            }
13780            return this.parent ? this.parent.getScaleComponent(channel) : undefined;
13781        }
13782        /**
13783         * Traverse a model's hierarchy to get a particular selection component.
13784         */
13785        getSelectionComponent(variableName, origName) {
13786            let sel = this.component.selection[variableName];
13787            if (!sel && this.parent) {
13788                sel = this.parent.getSelectionComponent(variableName, origName);
13789            }
13790            if (!sel) {
13791                throw new Error(message.selectionNotFound(origName));
13792            }
13793            return sel;
13794        }
13795    }
13796    /** Abstract class for UnitModel and FacetModel.  Both of which can contain fieldDefs as a part of its own specification. */
13797    class ModelWithField extends Model {
13798        /** Get "field" reference for Vega */
13799        vgField(channel, opt = {}) {
13800            const fieldDef = this.fieldDef(channel);
13801            if (!fieldDef) {
13802                return undefined;
13803            }
13804            return vgField(fieldDef, opt);
13805        }
13806        reduceFieldDef(f, init, t) {
13807            return reduce(this.getMapping(), (acc, cd, c) => {
13808                const fieldDef = getFieldDef(cd);
13809                if (fieldDef) {
13810                    return f(acc, fieldDef, c);
13811                }
13812                return acc;
13813            }, init, t);
13814        }
13815        forEachFieldDef(f, t) {
13816            forEach(this.getMapping(), (cd, c) => {
13817                const fieldDef = getFieldDef(cd);
13818                if (fieldDef) {
13819                    f(fieldDef, c);
13820                }
13821            }, t);
13822        }
13823    }
13824
13825    function rangeFormula(model, fieldDef, channel, config) {
13826        if (binRequiresRange(fieldDef, channel)) {
13827            // read format from axis or legend, if there is no format then use config.numberFormat
13828            const guide = isUnitModel(model) ? model.axis(channel) || model.legend(channel) || {} : {};
13829            const startField = vgField(fieldDef, { expr: 'datum' });
13830            const endField = vgField(fieldDef, { expr: 'datum', binSuffix: 'end' });
13831            return {
13832                formulaAs: vgField(fieldDef, { binSuffix: 'range', forAs: true }),
13833                formula: binFormatExpression(startField, endField, guide.format, config)
13834            };
13835        }
13836        return {};
13837    }
13838    function binKey(bin, field$$1) {
13839        return `${binToString(bin)}_${field$$1}`;
13840    }
13841    function getSignalsFromModel(model, key$$1) {
13842        return {
13843            signal: model.getName(`${key$$1}
13843_bins`),
13844            extentSignal: model.getName(`${key$$1}_extent`)
13845        };
13846    }
13847    function isBinTransform(t) {
13848        return 'as' in t;
13849    }
13850    function createBinComponent(t, bin, model) {
13851        let as;
13852        if (isBinTransform(t)) {
13853            as = isString(t.as) ? [t.as, `${t.as}_end`] : [t.as[0], t.as[1]];
13854        }
13855        else {
13856            as = [vgField(t, { forAs: true }), vgField(t, { binSuffix: 'end', forAs: true })];
13857        }
13858        const normalizedBin = normalizeBin(bin, undefined) || {};
13859        const key$$1 = binKey(normalizedBin, t.field);
13860        const { signal, extentSignal } = getSignalsFromModel(model, key$$1);
13861        const binComponent = Object.assign({ bin: normalizedBin, field: t.field, as: as }, (signal ? { signal } : {}), (extentSignal ? { extentSignal } : {}));
13862        return { key: key$$1, binComponent };
13863    }
13864    class BinNode extends DataFlowNode {
13865        constructor(parent, bins) {
13866            super(parent);
13867            this.bins = bins;
13868        }
13869        clone() {
13870            return new BinNode(null, duplicate(this.bins));
13871        }
13872        static makeFromEncoding(parent, model) {
13873            const bins = model.reduceFieldDef((binComponentIndex, fieldDef, channel) => {
13874                if (isTypedFieldDef(fieldDef) && isBinning(fieldDef.bin)) {
13875                    const { key: key$$1, binComponent } = createBinComponent(fieldDef, fieldDef.bin, model);
13876                    binComponentIndex[key$$1] = Object.assign({}, binComponent, binComponentIndex[key$$1], rangeFormula(model, fieldDef, channel, model.config));
13877                }
13878                return binComponentIndex;
13879            }, {});
13880            if (keys(bins).length === 0) {
13881                return null;
13882            }
13883            return new BinNode(parent, bins);
13884        }
13885        /**
13886         * Creates a bin node from BinTransform.
13887         * The optional parameter should provide
13888         */
13889        static makeFromTransform(parent, t, model) {
13890            const { key: key$$1, binComponent } = createBinComponent(t, t.bin, model);
13891            return new BinNode(parent, {
13892                [key$$1]: binComponent
13893            });
13894        }
13895        merge(other) {
13896            this.bins = Object.assign({}, this.bins, other.bins);
13897            other.remove();
13898        }
13899        producedFields() {
13900            return new Set(flatten(vals(this.bins).map(c => c.as)));
13901        }
13902        dependentFields() {
13903            return new Set(vals(this.bins).map(c => c.field));
13904        }
13905        hash() {
13906            return `Bin ${hash(this.bins)}`;
13907        }
13908        assemble() {
13909            return flatten(vals(this.bins).map(bin => {
13910                const transform = [];
13911                const binTrans = Object.assign({ type: 'bin', field: bin.field, as: bin.as, signal: bin.signal }, bin.bin);
13912                if (!bin.bin.extent && bin.extentSignal) {
13913                    transform.push({
13914                        type: 'extent',
13915                        field: bin.field,
13916                        signal: bin.extentSignal
13917                    });
13918                    binTrans.extent = { signal: bin.extentSignal };
13919                }
13920                transform.push(binTrans);
13921                if (bin.formula) {
13922                    transform.push({
13923                        type: 'formula',
13924                        expr: bin.formula,
13925                        as: bin.formulaAs
13926                    });
13927                }
13928                return transform;
13929            }));
13930        }
13931    }
13932
13933    class FilterNode extends DataFlowNode {
13934        constructor(parent, model, filter) {
13935            super(parent);
13936            this.model = model;
13937            this.filter = filter;
13938            // TODO: refactor this to not take a node and
13939            // then add a static function makeFromOperand and make the constructor take only an expression
13940            this.expr = expression(this.model, this.filter, this);
13941            this._dependentFields = getDependentFields(this.expr);
13942        }
13943        clone() {
13944            return new FilterNode(null, this.model, duplicate(this.filter));
13945        }
13946        dependentFields() {
13947            return this._dependentFields;
13948        }
13949        assemble() {
13950            return {
13951                type: 'filter',
13952                expr: this.expr
13953            };
13954        }
13955        hash() {
13956            return `Filter ${this.expr}`;
13957        }
13958    }
13959
13960    /**
13961     * A class for flatten transform nodes
13962     */
13963    class FlattenTransformNode extends DataFlowNode {
13964        constructor(parent, transform) {
13965            super(parent);
13966            this.transform = transform;
13967            this.transform = duplicate(transform); // duplicate to prevent side effects
13968            const { flatten: flatten$$1, as = [] } = this.transform;
13969            this.transform.as = flatten$$1.map((f, i) => as[i] || f);
13970        }
13971        clone() {
13972            return new FlattenTransformNode(this.parent, duplicate(this.transform));
13973        }
13974        producedFields() {
13975            return new Set(this.transform.as);
13976        }
13977        hash() {
13978            return `FlattenTransform ${hash(this.transform)}`;
13979        }
13980        assemble() {
13981            const { flatten: fields, as } = this.transform;
13982            const result = {
13983                type: 'flatten',
13984                fields,
13985                as
13986            };
13987            return result;
13988        }
13989    }
13990
13991    /**
13992     * A class for flatten transform nodes
13993     */
13994    class FoldTransformNode extends DataFlowNode {
13995        constructor(parent, transform) {
13996            super(parent);
13997            this.transform = transform;
13998            this.transform = duplicate(transform); // duplicate to prevent side effects
13999            const specifiedAs = this.transform.as || [undefined, undefined];
14000            this.transform.as = [specifiedAs[0] || 'key', specifiedAs[1] || 'value'];
14001        }
14002        clone() {
14003            return new FoldTransformNode(null, duplicate(this.transform));
14004        }
14005        producedFields() {
14006            return new Set(this.transform.as);
14007        }
14008        hash() {
14009            return `FoldTransform ${hash(this.transform)}`;
14010        }
14011        assemble() {
14012            const { fold, as } = this.transform;
14013            const result = {
14014                type: 'fold',
14015                fields: fold,
14016                as
14017            };
14018            return result;
14019        }
14020    }
14021
14022    class GeoJSONNode extends DataFlowNode {
14023        constructor(parent, fields, geojson, signal) {
14024            super(parent);
14025            this.fields = fields;
14026            this.geojson = geojson;
14027            this.signal = signal;
14028        }
14029        clone() {
14030            return new GeoJSONNode(null, duplicate(this.fields), this.geojson, this.signal);
14031        }
14032        static parseAll(parent, model) {
14033            let geoJsonCounter = 0;
14034            [[LONGITUDE, LATITUDE], [LONGITUDE2, LATITUDE2]].forEach((coordinates) => {
14035                const pair = coordinates.map(channel => model.channelHasField(channel) ? model.fieldDef(channel).field : undefined);
14036                if (pair[0] || pair[1]) {
14037                    parent = new GeoJSONNode(parent, pair, null, model.getName(`geojson_${geoJsonCounter++}`));
14038                }
14039            });
14040            if (model.channelHasField(SHAPE)) {
14041                const fieldDef = model.fieldDef(SHAPE);
14042                if (fieldDef.type === GEOJSON) {
14043                    parent = new GeoJSONNode(parent, null, fieldDef.field, model.getName(`geojson_${geoJsonCounter++}`));
14044                }
14045            }
14046            return parent;
14047        }
14048        assemble() {
14049            return Object.assign({ type: 'geojson' }, (this.fields ? { fields: this.fields } : {}), (this.geojson ? { geojson: this.geojson } : {}), { signal: this.signal });
14050        }
14051    }
14052
14053    class GeoPointNode extends DataFlowNode {
14054        constructor(parent, projection, fields, as) {
14055            super(parent);
14056            this.projection = projection;
14057            this.fields = fields;
14058            this.as = as;
14059        }
14060        clone() {
14061            return new GeoPointNode(null, this.projection, duplicate(this.fields), duplicate(this.as));
14062        }
14063        static parseAll(parent, model) {
14064            if (!model.projectionName()) {
14065                return parent;
14066            }
14067            [[LONGITUDE, LATITUDE], [LONGITUDE2, LATITUDE2]].forEach((coordinates) => {
14068                const pair = coordinates.map(channel => model.channelHasField(channel) ? model.fieldDef(channel).field : undefined);
14069                const suffix = coordinates[0] === LONGITUDE2 ? '2' : '';
14070                if (pair[0] || pair[1]) {
14071                    parent = new GeoPointNode(parent, model.projectionName(), pair, [
14072                        model.getName('x' + suffix),
14073                        model.getName('y' + suffix)
14074                    ]);
14075                }
14076            });
14077            return parent;
14078        }
14079        assemble() {
14080            return {
14081                type: 'geopoint',
14082                projection: this.projection,
14083                fields: this.fields,
14084                as: this.as
14085            };
14086        }
14087    }
14088
14089    class IdentifierNode extends DataFlowNode {
14090        clone() {
14091            return new IdentifierNode(null);
14092        }
14093        constructor(parent) {
14094            super(parent);
14095        }
14096        producedFields() {
14097            return new Set([SELECTION_ID]);
14098        }
14099        assemble() {
14100            return { type: 'identifier', as: SELECTION_ID };
14101        }
14102    }
14103
14104    const area = {
14105        vgMark: 'area',
14106        encodeEntry: (model) => {
14107            return Object.assign({}, baseEncodeEntry(model, { size: 'ignore', orient: 'include' }), pointPosition('x', model, 'zeroOrMin'), pointPosition('y', model, 'zeroOrMin'), pointPosition2(model, 'zeroOrMin', model.markDef.orient === 'horizontal' ? 'x2' : 'y2'), defined(model));
14108        }
14109    };
14110
14111    const bar = {
14112        vgMark: 'rect',
14113        encodeEntry: (model) => {
14114            return Object.assign({}, baseEncodeEntry(model, { size: 'ignore', orient: 'ignore' }), x(model), y(model));
14115        }
14116    };
14117    function x(model) {
14118        const { config, encoding, markDef, width } = model;
14119        const orient = markDef.orient;
14120        const sizeDef = encoding.size;
14121        const xDef = encoding.x;
14122        const x2Def = encoding.x2;
14123        const xScaleName = model.scaleName(X);
14124        const xScale = model.getScaleComponent(X);
14125        // x, x2, and width -- we must specify two of these in all conditions
14126        if (isFieldDef(xDef) && isBinned(xDef.bin)) {
14127            return binPosition(xDef, x2Def, X, xScaleName, getFirstDefined(markDef.binSpacing, config.bar.binSpacing), xScale.get('reverse'));
14128        }
14129        else if (orient === 'horizontal' || x2Def) {
14130            return Object.assign({}, pointPosition('x', model, 'zeroOrMin'), pointPosition2(model, 'zeroOrMin', 'x2'));
14131        }
14132        else {
14133            // vertical
14134            if (isFieldDef(xDef)) {
14135                const xScaleType = xScale.get('type');
14136                if (isBinning(xDef.bin) && !sizeDef && !hasDiscreteDomain(xScaleType)) {
14137                    return binPosition(xDef, undefined, X, model.scaleName('x'), getFirstDefined(markDef.binSpacing, config.bar.binSpacing), xScale.get('reverse'));
14138                }
14139                else {
14140                    if (xScaleType === ScaleType.BAND) {
14141                        return bandPosition(xDef, 'x', model);
14142                    }
14143                }
14144            }
14145            // sized bin, normal point-ordinal axis, quantitative x-axis, or no x
14146            return centeredBandPosition('x', model, Object.assign({}, mid(width)), defaultSizeRef(markDef, xScaleName, xScale, config));
14147        }
14148    }
14149    function y(model) {
14150        const { config, encoding, height, markDef } = model;
14151        const orient = markDef.orient;
14152        const sizeDef = encoding.size;
14153        const yDef = encoding.y;
14154        const y2Def = encoding.y2;
14155        const yScaleName = model.scaleName(Y);
14156        const yScale = model.getScaleComponent(Y);
14157        // y, y2 & height -- we must specify two of these in all conditions
14158        if (isFieldDef(yDef) && isBinned(yDef.bin)) {
14159            return binPosition(yDef, y2Def, Y, yScaleName, getFirstDefined(markDef.binSpacing, config.bar.binSpacing), yScale.get('reverse'));
14160        }
14161        else if (orient === 'vertical' || y2Def) {
14162            return Object.assign({}, pointPosition('y', model, 'zeroOrMin'), pointPosition2(model, 'zeroOrMin', 'y2'));
14163        }
14164        else {
14165            if (isFieldDef(yDef)) {
14166                const yScaleType = yScale.get('type');
14167                if (isBinning(yDef.bin) && !sizeDef && !hasDiscreteDomain(yScaleType)) {
14168                    return binPosition(yDef, undefined, Y, model.scaleName('y'), getFirstDefined(markDef.binSpacing, config.bar.binSpacing), yScale.get('reverse'));
14169                }
14170                else if (yScaleType === ScaleType.BAND) {
14171                    return bandPosition(yDef, 'y', model);
14172                }
14173            }
14174            return centeredBandPosition('y', model, mid(height), defaultSizeRef(markDef, yScaleName, yScale, config));
14175        }
14176    }
14177    function defaultSizeRef(markDef, scaleName, scale, config) {
14178        if (markDef.size !== undefined) {
14179            return { value: markDef.size };
14180        }
14181        const sizeConfig = getMarkConfig('size', markDef, config, {
14182            // config.mark.size shouldn't affect bar size
14183            skipGeneralMarkConfig: true
14184        });
14185        if (sizeConfig !== undefined) {
14186            return { value: sizeConfig };
14187        }
14188        if (scale) {
14189            const scaleType = scale.get('type');
14190            if (scaleType === 'point' || scaleType === 'band') {
14191                if (config.bar.discreteBandSize !== undefined) {
14192                    return { value: config.bar.discreteBandSize };
14193                }
14194                if (scaleType === ScaleType.POINT) {
14195                    const scaleRange = scale.get('range');
14196                    if (isVgRangeStep(scaleRange) && isNumber(scaleRange.step)) {
14197                        return { value: scaleRange.step - 1 };
14198                    }
14199                    warn(message.BAR_WITH_POINT_SCALE_AND_RANGESTEP_NULL);
14200                }
14201                else {
14202                    // BAND
14203                    return bandRef(scaleName);
14204                }
14205            }
14206            else {
14207                // continuous scale
14208                return { value: config.bar.continuousBandSize };
14209            }
14210        }
14211        // No Scale
14212        const value = getFirstDefined(
14213        // No scale is like discrete bar (with one item)
14214        config.bar.discreteBandSize, config.scale.rangeStep ? config.scale.rangeStep - 1 : undefined, 
14215        // If somehow default rangeStep is set to null or undefined, use 20 as back up
14216        20);
14217        return { value };
14218    }
14219
14220    const geoshape = {
14221        vgMark: 'shape',
14222        encodeEntry: (model) => {
14223            return Object.assign({}, baseEncodeEntry(model, { size: 'ignore', orient: 'ignore' }));
14224        },
14225        postEncodingTransform: (model) => {
14226            const { encoding } = model;
14227            const shapeDef = encoding.shape;
14228            const transform = Object.assign({ type: 'geoshape', projection: model.projectionName() }, (shapeDef && isFieldDef(shapeDef) && shapeDef.type === GEOJSON
14229                ? { field: vgField(shapeDef, { expr: 'datum' }) }
14230                : {}));
14231            return [transform];
14232        }
14233    };
14234
14235    const line = {
14236        vgMark: 'line',
14237        encodeEntry: (model) => {
14238            const { width, height } = model;
14239            return Object.assign({}, baseEncodeEntry(model, { size: 'ignore', orient: 'ignore' }), pointPosition('x', model, mid(width)), pointPosition('y', model, mid(height)), nonPosition('size', model, {
14240                vgChannel: 'strokeWidth' // VL's line size is strokeWidth
14241            }), defined(model));
14242        }
14243    };
14244    const trail = {
14245        vgMark: 'trail',
14246        encodeEntry: (model) => {
14247            const { width, height } = model;
14248            return Object.assign({}, baseEncodeEntry(model, { size: 'include', orient: 'ignore' }), pointPosition('x', model, mid(width)), pointPosition('y', model, mid(height)), nonPosition('size', model), defined(model));
14249        }
14250    };
14251
14252    function encodeEntry(model, fixedShape) {
14253        const { config, markDef, width, height } = model;
14254        return Object.assign({}, baseEncodeEntry(model, { size: 'include', orient: 'ignore' }), pointPosition('x', model, mid(width)), pointPosition('y', model, mid(height)), nonPosition('size', model, { defaultValue: getMarkConfig('size', markDef, config) }), shapeMixins(model, config, fixedShape));
14255    }
14256    function shapeMixins(model, config, fixedShape) {
14257        if (fixedShape) {
14258            return { shape: { value: fixedShape } };
14259        }
14260        return nonPosition('shape', model, { defaultValue: getMarkConfig('shape', model.markDef, config) });
14261    }
14262    const point = {
14263        vgMark: 'symbol',
14264        encodeEntry: (model) => {
14265            return encodeEntry(model);
14266        }
14267    };
14268    const circle = {
14269        vgMark: 'symbol',
14270        encodeEntry: (model) => {
14271            return encodeEntry(model, 'circle');
14272        }
14273    };
14274    const square = {
14275        vgMark: 'symbol',
14276        encodeEntry: (model) => {
14277            return encodeEntry(model, 'square');
14278        }
14279    };
14280
14281    const rect = {
14282        vgMark: 'rect',
14283        encodeEntry: (model) => {
14284            return Object.assign({}, baseEncodeEntry(model, { size: 'ignore', orient: 'ignore' }), x$1(model), y$1(model));
14285        }
14286    };
14287    function x$1(model) {
14288        const xDef = model.encoding.x;
14289        const x2Def = model.encoding.x2;
14290        const xScale = model.getScaleComponent(X);
14291        const xScaleType = xScale ? xScale.get('type') : undefined;
14292        const xScaleName = model.scaleName(X);
14293        if (isFieldDef(xDef) && (isBinning(xDef.bin) || isBinned(xDef.bin))) {
14294            return binPosition(xDef, x2Def, X, xScaleName, 0, xScale.get('reverse'));
14295        }
14296        else if (isFieldDef(xDef) && xScale && hasDiscreteDomain(xScaleType)) {
14297            /* istanbul ignore else */
14298            if (xScaleType === ScaleType.BAND) {
14299                return bandPosition(xDef, 'x', model);
14300            }
14301            else {
14302                // We don't support rect mark with point/ordinal scale
14303                throw new Error(message.scaleTypeNotWorkWithMark(RECT, xScaleType));
14304            }
14305        }
14306        else {
14307            // continuous scale or no scale
14308            return Object.assign({}, pointPosition('x', model, 'zeroOrMax'), pointPosition2(model, 'zeroOrMin', 'x2'));
14309        }
14310    }
14311    function y$1(model) {
14312        const yDef = model.encoding.y;
14313        const y2Def = model.encoding.y2;
14314        const yScale = model.getScaleComponent(Y);
14315        const yScaleType = yScale ? yScale.get('type') : undefined;
14316        const yScaleName = model.scaleName(Y);
14317        if (isFieldDef(yDef) && (isBinning(yDef.bin) || isBinned(yDef.bin))) {
14318            return binPosition(yDef, y2Def, Y, yScaleName, 0, yScale.get('reverse'));
14319        }
14320        else if (isFieldDef(yDef) && yScale && hasDiscreteDomain(yScaleType)) {
14321            /* istanbul ignore else */
14322            if (yScaleType === ScaleType.BAND) {
14323                return bandPosition(yDef, 'y', model);
14324            }
14325            else {
14326                // We don't support rect mark with point/ordinal scale
14327                throw new Error(message.scaleTypeNotWorkWithMark(RECT, yScaleType));
14328            }
14329        }
14330        else {
14331            // continuous scale or no scale
14332            return Object.assign({}, pointPosition('y', model, 'zeroOrMax'), pointPosition2(model, 'zeroOrMin', 'y2'));
14333        }
14334    }
14335
14336    const rule = {
14337        vgMark: 'rule',
14338        encodeEntry: (model) => {
14339            const { markDef, width, height } = model;
14340            const orient = markDef.orient;
14341            if (!model.encoding.x && !model.encoding.y && !model.encoding.latitude && !model.encoding.longitude) {
14342                // Show nothing if we have none of x, y, lat, and long.
14343                return {};
14344            }
14345            return Object.assign({}, baseEncodeEntry(model, { size: 'ignore', orient: 'ignore' }), pointPosition('x', model, orient === 'horizontal' ? 'zeroOrMin' : mid(width)), pointPosition('y', model, orient === 'vertical' ? 'zeroOrMin' : mid(height)), (orient !== 'vertical' ? pointPosition2(model, 'zeroOrMax', 'x2') : {}), (orient !== 'horizontal' ? pointPosition2(model, 'zeroOrMax', 'y2') : {}), nonPosition('size', model, {
14346                vgChannel: 'strokeWidth',
14347                defaultValue: markDef.size
14348            }));
14349        }
14350    };
14351
14352    const text$2 = {
14353        vgMark: 'text',
14354        encodeEntry: (model) => {
14355            const { config, encoding, width, height, markDef } = model;
14356            // We have to support mark property and config for both size and fontSize for text
14357            // - size is from original Vega-Lite, which allows users to easily transition from size channel of other marks to text.
14358            // - fontSize is from Vega and we need support it to make sure that all Vega configs all work correctly in Vega-Lite.
14359            // Precedence: markDef > style config > mark-specific config
14360            // For each of them, fontSize is more specific than size, thus has higher precedence
14361            const defaultValue = getFirstDefined(markDef.fontSize, markDef.size, getStyleConfig('fontSize', markDef, config.style), getStyleConfig('size', markDef, config.style), config[markDef.type].fontSize, config[markDef.type].size
14362            // general mark config shouldn't be used as they are only for point/circle/square
14363            );
14364            return Object.assign({}, baseEncodeEntry(model, { size: 'ignore', orient: 'ignore' }), pointPosition('x', model, mid(width)), pointPosition('y', model, mid(height)), text$1(model), nonPosition('size', model, {
14365                defaultValue,
14366                vgChannel: 'fontSize' // VL's text size is fontSize
14367            }), valueIfDefined('align', align(model.markDef, encoding, config)));
14368        }
14369    };
14370    function align(markDef, encoding, config) {
14371        const a = markDef.align || getMarkConfig('align', markDef, config);
14372        if (a === undefined) {
14373            return 'center';
14374        }
14375        // If there is a config, Vega-parser will process this already.
14376        return undefined;
14377    }
14378
14379    const tick = {
14380        vgMark: 'rect',
14381        encodeEntry: (model) => {
14382            const { config, markDef, width, height } = model;
14383            const orient = markDef.orient;
14384            const vgSizeChannel = orient === 'horizontal' ? 'width' : 'height';
14385            const vgThicknessChannel = orient === 'horizontal' ? 'height' : 'width';
14386            return Object.assign({}, baseEncodeEntry(model, { size: 'ignore', orient: 'ignore' }), pointPosition('x', model, mid(width), 'xc'), pointPosition('y', model, mid(height), 'yc'), nonPosition('size', model, {
14387                defaultValue: defaultSize(model),
14388                vgChannel: vgSizeChannel
14389            }), { [vgThicknessChannel]: { value: getFirstDefined(markDef.thickness, config.tick.thickness) } });
14390        }
14391    };
14392    function defaultSize(model) {
14393        const { config, markDef } = model;
14394        const orient = markDef.orient;
14395        const scale = model.getScaleComponent(orient === 'horizontal' ? 'x' : 'y');
14396        if (markDef.size !== undefined) {
14397            return markDef.size;
14398        }
14399        else if (config.tick.bandSize !== undefined) {
14400            return config.tick.bandSize;
14401        }
14402        else {
14403            const scaleRange = scale ? scale.get('range') : undefined;
14404            const rangeStep = scaleRange && isVgRangeStep(scaleRange) ? scaleRange.step : config.scale.rangeStep;
14405            if (typeof rangeStep !== 'number') {
14406                // FIXME consolidate this log
14407                throw new Error('Function does not handle non-numeric rangeStep');
14408            }
14409            return (rangeStep * 3) / 4;
14410        }
14411    }
14412
14413    const markCompiler = {
14414        area,
14415        bar,
14416        circle,
14417        geoshape,
14418        line,
14419        point,
14420        rect,
14421        rule,
14422        square,
14423        text: text$2,
14424        tick,
14425        trail
14426    };
14427    function parseMarkGroup(model) {
14428        if (contains([LINE, AREA, TRAIL], model.mark)) {
14429            return parsePathMark(model);
14430        }
14431        else {
14432            return getMarkGroups(model);
14433        }
14434    }
14435    const FACETED_PATH_PREFIX = 'faceted_path_';
14436    function parsePathMark(model) {
14437        const details = pathGroupingFields(model.mark, model.encoding);
14438        const pathMarks = getMarkGroups(model, {
14439            // If has subfacet for line/area group, need to use faceted data from below.
14440            fromPrefix: details.length > 0 ? FACETED_PATH_PREFIX : ''
14441        });
14442        if (details.length > 0) {
14443            // have level of details - need to facet line into subgroups
14444            // TODO: for non-stacked plot, map order to zindex. (Maybe rename order for layer to zindex?)
14445            return [
14446                {
14447                    name: model.getName('pathgroup'),
14448                    type: 'group',
14449                    from: {
14450                        facet: {
14451                            name: FACETED_PATH_PREFIX + model.requestDataName(MAIN),
14452                            data: model.requestDataName(MAIN),
14453                            groupby: details
14454                        }
14455                    },
14456                    encode: {
14457                        update: {
14458                            width: { field: { group: 'width' } },
14459                            height: { field: { group: 'height' } }
14460                        }
14461                    },
14462                    marks: pathMarks
14463                }
14464            ];
14465        }
14466        else {
14467            return pathMarks;
14468        }
14469    }
14470    function getSort$1(model) {
14471        const { encoding, stack, mark, markDef } = model;
14472        const order = encoding.order;
14473        if (!isArray(order) && isValueDef(order)) {
14474            return undefined;
14475        }
14476        else if ((isArray(order) || isFieldDef(order)) && !stack) {
14477            // Sort by the order field if it is specified and the field is not stacked. (For stacked field, order specify stack order.)
14478            return sortParams(order, { expr: 'datum' });
14479        }
14480        else if (isPathMark(mark)) {
14481            // For both line and area, we sort values based on dimension by default
14482            const dimensionChannelDef = encoding[markDef.orient === 'horizontal' ? 'y' : 'x'];
14483            if (isFieldDef(dimensionChannelDef)) {
14484                const s = dimensionChannelDef.sort;
14485                const sortField = isSortField(s)
14486                    ? vgField({
14487                        // FIXME: this op might not already exist?
14488                        // FIXME: what if dimensionChannel (x or y) contains custom domain?
14489                        aggregate: isAggregate(model.encoding) ? s.op : undefined,
14490                        field: s.field
14491                    }, { expr: 'datum' })
14492                    : vgField(dimensionChannelDef, {
14493                        // For stack with imputation, we only have bin_mid
14494                        binSuffix: model.stack && model.stack.impute ? 'mid' : undefined,
14495                        expr: 'datum'
14496                    });
14497                return {
14498                    field: sortField,
14499                    order: 'descending'
14500                };
14501            }
14502            return undefined;
14503        }
14504        return undefined;
14505    }
14506    function getMarkGroups(model, opt = { fromPrefix: '' }) {
14507        const mark = model.mark;
14508        const clip = getFirstDefined(model.markDef.clip, scaleClip(model));
14509        const style = getStyles(model.markDef);
14510        const key$$1 = model.encoding.key;
14511        const sort = getSort$1(model);
14512        const postEncodingTransform = markCompiler[mark].postEncodingTransform
14513            ? markCompiler[mark].postEncodingTransform(model)
14514            : null;
14515        return [
14516            Object.assign({ name: model.getName('marks'), type: markCompiler[mark].vgMark }, (clip ? { clip: true } : {}), (style ? { style } : {}), (key$$1 ? { key: { field: key$$1.field } } : {}), (sort ? { sort } : {}), { from: { data: opt.fromPrefix + model.requestDataName(MAIN) }, encode: {
14517                    update: markCompiler[mark].encodeEntry(model)
14518                } }, (postEncodingTransform
14519                ? {
14520                    transform: postEncodingTransform
14521                }
14522                : {}))
14523        ];
14524    }
14525    /**
14526     * Returns list of path grouping fields
14527     * that the model's spec contains.
14528     */
14529    function pathGroupingFields(mark, encoding) {
14530        return keys(encoding).reduce((details, channel) => {
14531            switch (channel) {
14532                // x, y, x2, y2, lat, long, lat1, long2, order, tooltip, href, cursor should not cause lines to group
14533                case 'x':
14534                case 'y':
14535                case 'order':
14536                case 'href':
14537                case 'x2':
14538                case 'y2':
14539                case 'xError':
14540                case 'yError':
14541                case 'xError2':
14542                case 'yError2':
14543                case 'latitude':
14544                case 'longitude':
14545                case 'latitude2':
14546                case 'longitude2':
14547                // TODO: case 'cursor':
14548                // text, shape, shouldn't be a part of line/trail/area
14549                case 'text':
14550                case 'shape':
14551                // tooltip fields should not be added to group by
14552                case 'tooltip':
14553                    return details;
14554                case 'detail':
14555                case 'key':
14556                    const channelDef = encoding[channel];
14557                    if (isArray(channelDef) || isFieldDef(channelDef)) {
14558                        (isArray(channelDef) ? channelDef : [channelDef]).forEach(fieldDef => {
14559                            if (!fieldDef.aggregate) {
14560                                details.push(vgField(fieldDef, {}));
14561                            }
14562                        });
14563                    }
14564                    return details;
14565                case 'size':
14566                    if (mark === 'trail') {
14567                        // For trail, size should not group trail lines.
14568                        return details;
14569                    }
14570                // For line, it should group lines.
14571                /* tslint:disable */
14572                // intentional fall through
14573                case 'color':
14574                case 'fill':
14575                case 'stroke':
14576                case 'opacity':
14577                case 'fillOpacity':
14578                case 'strokeOpacity':
14579                case 'strokeWidth':
14580                    // TODO strokeDashOffset:
14581                    /* tslint:enable */
14582                    const fieldDef = getTypedFieldDef(encoding[channel]);
14583                    if (fieldDef && !fieldDef.aggregate) {
14584                        details.push(vgField(fieldDef, {}));
14585                    }
14586                    return details;
14587                default:
14588                    throw new Error(`Bug: Channel ${channel} unimplemented for line mark`);
14589            }
14590        }, []);
14591    }
14592    /**
14593     * If scales are bound to interval selections, we want to automatically clip
14594     * marks to account for panning/zooming interactions. We identify bound scales
14595     * by the domainRaw property, which gets added during scale parsing.
14596     */
14597    function scaleClip(model) {
14598        const xScale = model.getScaleComponent('x');
14599        const yScale = model.getScaleComponent('y');
14600        return (xScale && xScale.get('domainRaw')) || (yScale && yScale.get('domainRaw')) ? true : false;
14601    }
14602
14603    class ImputeNode extends DataFlowNode {
14604        constructor(parent, transform) {
14605            super(parent);
14606            this.transform = transform;
14607        }
14608        clone() {
14609            return new ImputeNode(null, duplicate(this.transform));
14610        }
14611        producedFields() {
14612            return new Set([this.transform.impute]);
14613        }
14614        processSequence(keyvals) {
14615            const { start = 0, stop, step } = keyvals;
14616            const result = [start, stop, ...(step ? [step] : [])].join(',');
14617            return { signal: `sequence(${result})` };
14618        }
14619        static makeFromTransform(parent, imputeTransform) {
14620            return new ImputeNode(parent, imputeTransform);
14621        }
14622        static makeFromEncoding(parent, model) {
14623            const encoding = model.encoding;
14624            const xDef = encoding.x;
14625            const yDef = encoding.y;
14626            if (isFieldDef(xDef) && isFieldDef(yDef)) {
14627                const imputedChannel = xDef.impute ? xDef : yDef.impute ? yDef : undefined;
14628                if (imputedChannel === undefined) {
14629                    return undefined;
14630                }
14631                const keyChannel = xDef.impute ? yDef : yDef.impute ? xDef : undefined;
14632                const { method, value, frame, keyvals } = imputedChannel.impute;
14633                const groupbyFields = pathGroupingFields(model.mark, encoding);
14634                return new ImputeNode(parent, Object.assign({ impute: imputedChannel.field, key: keyChannel.field }, (method ? { method } : {}), (value !== undefined ? { value } : {}), (frame ? { frame } : {}), (keyvals !== undefined ? { keyvals } : {}), (groupbyFields.length ? { groupby: groupbyFields } : {})));
14635            }
14636            return null;
14637        }
14638        hash() {
14639            return `Impute ${hash(this.transform)}`;
14640        }
14641        assemble() {
14642            const { impute, key, keyvals, method, groupby, value, frame = [null, null] } = this.transform;
14643            const initialImpute = Object.assign({ type: 'impute', field: impute, key }, (keyvals ? { keyvals: isImputeSequence(keyvals) ? this.processSequence(keyvals) : keyvals } : {}), { method: 'value' }, (groupby ? { groupby } : {}), { value: null });
14644            let setImputedField;
14645            if (method && method !== 'value') {
14646                const deriveNewField = Object.assign({ type: 'window', as: [`imputed_${impute}_value`], ops: [method], fields: [impute], frame, ignorePeers: false }, (groupby ? { groupby } : {}));
14647                const replaceOriginal = {
14648                    type: 'formula',
14649                    expr: `datum.${impute} === null ? datum.imputed_${impute}_value : datum.${impute}`,
14650                    as: impute
14651                };
14652                setImputedField = [deriveNewField, replaceOriginal];
14653            }
14654            else {
14655                const replaceWithValue = {
14656                    type: 'formula',
14657                    expr: `datum.${impute} === null ? ${value} : datum.${impute}`,
14658                    as: impute
14659                };
14660                setImputedField = [replaceWithValue];
14661            }
14662            return [initialImpute, ...setImputedField];
14663        }
14664    }
14665
14666    /**
14667     * Class to track interesting properties (see https://15721.courses.cs.cmu.edu/spring2016/papers/graefe-ieee1995.pdf)
14668     * about how fields have been parsed or whether they have been derived in a transform. We use this to not parse the
14669     * same field again (or differently).
14670     */
14671    class AncestorParse extends Split {
14672        constructor(explicit = {}, implicit = {}, parseNothing = false) {
14673            super(explicit, implicit);
14674            this.explicit = explicit;
14675            this.implicit = implicit;
14676            this.parseNothing = parseNothing;
14677        }
14678        clone() {
14679            const clone = super.clone();
14680            clone.parseNothing = this.parseNothing;
14681            return clone;
14682        }
14683    }
14684
14685    class LookupNode extends DataFlowNode {
14686        constructor(parent, transform, secondary) {
14687            super(parent);
14688            this.transform = transform;
14689            this.secondary = secondary;
14690        }
14691        clone() {
14692            return new LookupNode(null, duplicate(this.transform), this.secondary);
14693        }
14694        static make(parent, model, transform, counter) {
14695            const sources = model.component.data.sources;
14696            let fromSource = findSource(transform.from.data, sources);
14697            if (!fromSource) {
14698                fromSource = new SourceNode(transform.from.data);
14699                sources.push(fromSource);
14700            }
14701            const fromOutputName = model.getName(`lookup_${counter}`);
14702            const fromOutputNode = new OutputNode(fromSource, fromOutputName, 'lookup', model.component.data.outputNodeRefCounts);
14703            model.component.data.outputNodes[fromOutputName] = fromOutputNode;
14704            return new LookupNode(parent, transform, fromOutputNode.getSource());
14705        }
14706        producedFields() {
14707            return new Set(this.transform.from.fields || (this.transform.as instanceof Array ? this.transform.as : [this.transform.as]));
14708        }
14709        hash() {
14710            return `Lookup ${hash({ transform: this.transform, secondary: this.secondary })}`;
14711        }
14712        assemble() {
14713            let foreign;
14714            if (this.transform.from.fields) {
14715                // lookup a few fields and add create a flat output
14716                foreign = Object.assign({ values: this.transform.from.fields }, (this.transform.as ? { as: this.transform.as instanceof Array ? this.transform.as : [this.transform.as] } : {}));
14717            }
14718            else {
14719                // lookup full record and nest it
14720                let asName = this.transform.as;
14721                if (!isString(asName)) {
14722                    warn(message.NO_FIELDS_NEEDS_AS);
14723                    asName = '_lookup';
14724                }
14725                foreign = {
14726                    as: [asName]
14727                };
14728            }
14729            return Object.assign({ type: 'lookup', from: this.secondary, key: this.transform.from.key, fields: [this.transform.lookup] }, foreign, (this.transform.default ? { default: this.transform.default } : {}));
14730        }
14731    }
14732
14733    /**
14734     * A class for the sample transform nodes
14735     */
14736    class SampleTransformNode extends DataFlowNode {
14737        constructor(parent, transform) {
14738            super(parent);
14739            this.transform = transform;
14740        }
14741        clone() {
14742            return new SampleTransformNode(null, duplicate(this.transform));
14743        }
14744        hash() {
14745            return `SampleTransform ${hash(this.transform)}`;
14746        }
14747        assemble() {
14748            return {
14749                type: 'sample',
14750                size: this.transform.sample
14751            };
14752        }
14753    }
14754
14755    function makeWalkTree(data) {
14756        // to name datasources
14757        let datasetIndex = 0;
14758        /**
14759         * Recursively walk down the tree.
14760         */
14761        function walkTree(node, dataSource) {
14762            if (node instanceof SourceNode) {
14763                // If the source is a named data source or a data source with values, we need
14764                // to put it in a different data source. Otherwise, Vega may override the data.
14765                if (!isUrlData(node.data)) {
14766                    data.push(dataSource);
14767                    const newData = {
14768                        name: null,
14769                        source: dataSource.name,
14770                        transform: []
14771                    };
14772                    dataSource = newData;
14773                }
14774            }
14775            if (node instanceof ParseNode) {
14776                if (node.parent instanceof SourceNode && !dataSource.source) {
14777                    // If node's parent is a root source and the data source does not refer to another data source, use normal format parse
14778                    dataSource.format = Object.assign({}, (dataSource.format || {}), { parse: node.assembleFormatParse() });
14779                    // add calculates for all nested fields
14780                    dataSource.transform = dataSource.transform.concat(node.assembleTransforms(true));
14781                }
14782                else {
14783                    // Otherwise use Vega expression to parse
14784                    dataSource.transform = dataSource.transform.concat(node.assembleTransforms());
14785                }
14786            }
14787            if (node instanceof FacetNode) {
14788                if (!dataSource.name) {
14789                    dataSource.name = `data_${datasetIndex++}`;
14790                }
14791                if (!dataSource.source || dataSource.transform.length > 0) {
14792                    data.push(dataSource);
14793                    node.data = dataSource.name;
14794                }
14795                else {
14796                    node.data = dataSource.source;
14797                }
14798                node.assemble().forEach(d => data.push(d));
14799                // break here because the rest of the tree has to be taken care of by the facet.
14800                return;
14801            }
14802            if (node instanceof FilterNode ||
14803                node instanceof CalculateNode ||
14804                node instanceof GeoPointNode ||
14805                node instanceof GeoJSONNode ||
14806                node instanceof AggregateNode ||
14807                node instanceof LookupNode ||
14808                node instanceof WindowTransformNode ||
14809                node instanceof FoldTransformNode ||
14810                node instanceof FlattenTransformNode ||
14811                node instanceof IdentifierNode ||
14812                node instanceof SampleTransformNode) {
14813                dataSource.transform.push(node.assemble());
14814            }
14815            if (node instanceof BinNode ||
14816                node instanceof TimeUnitNode ||
14817                node instanceof ImputeNode ||
14818                node instanceof StackNode) {
14819                dataSource.transform = dataSource.transform.concat(node.assemble());
14820            }
14821            if (node instanceof OutputNode) {
14822                if (dataSource.source && dataSource.transform.length === 0) {
14823                    node.setSource(dataSource.source);
14824                }
14825                else if (node.parent instanceof OutputNode) {
14826                    // Note that an output node may be required but we still do not assemble a
14827                    // separate data source for it.
14828                    node.setSource(dataSource.name);
14829                }
14830                else {
14831                    if (!dataSource.name) {
14832                        dataSource.name = `data_${datasetIndex++}`;
14833                    }
14834                    // Here we set the name of the datasource we generated. From now on
14835                    // other assemblers can use it.
14836                    node.setSource(dataSource.name);
14837                    // if this node has more than one child, we will add a datasource automatically
14838                    if (node.numChildren() === 1) {
14839                        data.push(dataSource);
14840                        const newData = {
14841                            name: null,
14842                            source: dataSource.name,
14843                            transform: []
14844                        };
14845                        dataSource = newData;
14846                    }
14847                }
14848            }
14849            switch (node.numChildren()) {
14850                case 0:
14851                    // done
14852                    if (node instanceof OutputNode && (!dataSource.source || dataSource.transform.length > 0)) {
14853                        // do not push empty datasources that are simply references
14854                        data.push(dataSource);
14855                    }
14856                    break;
14857                case 1:
14858                    walkTree(node.children[0], dataSource);
14859                    break;
14860                default:
14861                    if (!dataSource.name) {
14862                        dataSource.name = `data_${datasetIndex++}`;
14863                    }
14864                    let source = dataSource.name;
14865                    if (!dataSource.source || dataSource.transform.length > 0) {
14866                        data.push(dataSource);
14867                    }
14868                    else {
14869                        source = dataSource.source;
14870                    }
14871                    node.children.forEach(child => {
14872                        const newData = {
14873                            name: null,
14874                            source: source,
14875                            transform: []
14876                        };
14877                        walkTree(child, newData);
14878                    });
14879                    break;
14880            }
14881        }
14882        return walkTree;
14883    }
14884    /**
14885     * Assemble data sources that are derived from faceted data.
14886     */
14887    function assembleFacetData(root) {
14888        const data = [];
14889        const walkTree = makeWalkTree(data);
14890        root.children.forEach(child => walkTree(child, {
14891            source: root.name,
14892            name: null,
14893            transform: []
14894        }));
14895        return data;
14896    }
14897    /**
14898     * Create Vega Data array from a given compiled model and append all of them to the given array
14899     *
14900     * @param  model
14901     * @param  data array
14902     * @return modified data array
14903     */
14904    function assembleRootData(dataComponent, datasets) {
14905        const data = [];
14906        // roots.forEach(debug);
14907        // draw(roots);
14908        const walkTree = makeWalkTree(data);
14909        let sourceIndex = 0;
14910        dataComponent.sources.forEach(root => {
14911            // assign a name if the source does not have a name yet
14912            if (!root.hasName()) {
14913                root.dataName = `source_${sourceIndex++}`;
14914            }
14915            const newData = root.assemble();
14916            walkTree(root, newData);
14917        });
14918        // remove empty transform arrays for cleaner output
14919        data.forEach(d => {
14920            if (d.transform.length === 0) {
14921                delete d.transform;
14922            }
14923        });
14924        // move sources without transforms (the ones that are potentially used in lookups) to the beginning
14925        let whereTo = 0;
14926        for (let i = 0; i < data.length; i++) {
14927            const d = data[i];
14928            if ((d.transform || []).length === 0 && !d.source) {
14929                data.splice(whereTo++, 0, data.splice(i, 1)[0]);
14930            }
14931        }
14932        // now fix the from references in lookup transforms
14933        for (const d of data) {
14934            for (const t of d.transform || []) {
14935                if (t.type === 'lookup') {
14936                    t.from = dataComponent.outputNodes[t.from].getSource();
14937                }
14938            }
14939        }
14940        // inline values for datasets that are in the datastore
14941        for (const d of data) {
14942            if (d.name in datasets) {
14943                d.values = datasets[d.name];
14944            }
14945        }
14946        return data;
14947    }
14948
14949    function parseLayerLayoutSize(model) {
14950        parseChildrenLayoutSize(model);
14951        const layoutSizeCmpt = model.component.layoutSize;
14952        layoutSizeCmpt.setWithExplicit('width', parseNonUnitLayoutSizeForChannel(model, 'width'));
14953        layoutSizeCmpt.setWithExplicit('height', parseNonUnitLayoutSizeForChannel(model, 'height'));
14954    }
14955    const parseRepeatLayoutSize = parseLayerLayoutSize;
14956    function parseConcatLayoutSize(model) {
14957        parseChildrenLayoutSize(model);
14958        const layoutSizeCmpt = model.component.layoutSize;
14959        const sizeTypeToMerge = model.isVConcat ? 'width' : 'height';
14960        layoutSizeCmpt.setWithExplicit(sizeTypeToMerge, parseNonUnitLayoutSizeForChannel(model, sizeTypeToMerge));
14961    }
14962    function parseChildrenLayoutSize(model) {
14963        for (const child of model.children) {
14964            child.parseLayoutSize();
14965        }
14966    }
14967    function parseNonUnitLayoutSizeForChannel(model, sizeType) {
14968        const channel = sizeType === 'width' ? 'x' : 'y';
14969        const resolve = model.component.resolve;
14970        let mergedSize;
14971        // Try to merge layout size
14972        for (const child of model.children) {
14973            const childSize = child.component.layoutSize.getWithExplicit(sizeType);
14974            const scaleResolve = resolve.scale[channel];
14975            if (scaleResolve === 'independent' && childSize.value === 'range-step') {
14976                // Do not merge independent scales with range-step as their size depends
14977                // on the scale domains, which can be different between scales.
14978                mergedSize = undefined;
14979                break;
14980            }
14981            if (mergedSize) {
14982                if (scaleResolve === 'independent' && mergedSize.value !== childSize.value) {
14983                    // For independent scale, only merge if all the sizes are the same.
14984                    // If the values are different, abandon the merge!
14985                    mergedSize = undefined;
14986                    break;
14987                }
14988                mergedSize = mergeValuesWithExplicit(mergedSize, childSize, sizeType, '');
14989            }
14990            else {
14991                mergedSize = childSize;
14992            }
14993        }
14994        if (mergedSize) {
14995            // If merged, rename size and set size of all children.
14996            for (const child of model.children) {
14997                model.renameSignal(child.getName(sizeType), model.getName(sizeType));
14998                child.component.layoutSize.set(sizeType, 'merged', false);
14999            }
15000            return mergedSize;
15001        }
15002        else {
15003            // Otherwise, there is no merged size.
15004            return {
15005                explicit: false,
15006                value: undefined
15007            };
15008        }
15009    }
15010    function parseUnitLayoutSize(model) {
15011        const layoutSizeComponent = model.component.layoutSize;
15012        if (!layoutSizeComponent.explicit.width) {
15013            const width = defaultUnitSize(model, 'width');
15014            layoutSizeComponent.set('width', width, false);
15015        }
15016        if (!layoutSizeComponent.explicit.height) {
15017            const height = defaultUnitSize(model, 'height');
15018            layoutSizeComponent.set('height', height, false);
15019        }
15020    }
15021    function defaultUnitSize(model, sizeType) {
15022        const channel = sizeType === 'width' ? 'x' : 'y';
15023        const config = model.config;
15024        const scaleComponent = model.getScaleComponent(channel);
15025        if (scaleComponent) {
15026            const scaleType = scaleComponent.get('type');
15027            const range = scaleComponent.get('range');
15028            if (hasDiscreteDomain(scaleType) && isVgRangeStep(range)) {
15029                // For discrete domain with range.step, use dynamic width/height
15030                return 'range-step';
15031            }
15032            else {
15033                return config.view[sizeType];
15034            }
15035        }
15036        else if (model.hasProjection) {
15037            return config.view[sizeType];
15038        }
15039        else {
15040            // No scale - set default size
15041            if (sizeType === 'width' && model.mark === 'text') {
15042                // width for text mark without x-field is a bit wider than typical range step
15043                return config.scale.textXRangeStep;
15044            }
15045            // Set width/height equal to rangeStep config or if rangeStep is null, use value from default scale config.
15046            return config.scale.rangeStep || defaultScaleConfig.rangeStep;
15047        }
15048    }
15049
15050    function replaceRepeaterInFacet(facet, repeater) {
15051        return replaceRepeater(facet, repeater);
15052    }
15053    function replaceRepeaterInEncoding(encoding, repeater) {
15054        return replaceRepeater(encoding, repeater);
15055    }
15056    /**
15057     * Replaces repeated value and returns if the repeated value is valid.
15058     */
15059    function replaceRepeat(o, repeater) {
15060        if (isRepeatRef(o.field)) {
15061            if (o.field.repeat in repeater) {
15062                // any needed to calm down ts compiler
15063                return Object.assign({}, o, { field: repeater[o.field.repeat] });
15064            }
15065            else {
15066                warn(message.noSuchRepeatedValue(o.field.repeat));
15067                return undefined;
15068            }
15069        }
15070        return o;
15071    }
15072    /**
15073     * Replace repeater values in a field def with the concrete field name.
15074     */
15075    function replaceRepeaterInFieldDef(fieldDef, repeater) {
15076        fieldDef = replaceRepeat(fieldDef, repeater);
15077        if (fieldDef === undefined) {
15078            // the field def should be ignored
15079            return undefined;
15080        }
15081        else if (fieldDef === null) {
15082            return null;
15083        }
15084        if (isSortableFieldDef(fieldDef) && isSortField(fieldDef.sort)) {
15085            const sort = replaceRepeat(fieldDef.sort, repeater);
15086            fieldDef = Object.assign({}, fieldDef, (sort ? { sort } : {}));
15087        }
15088        return fieldDef;
15089    }
15090    function replaceRepeaterInChannelDef(channelDef, repeater) {
15091        if (isFieldDef(channelDef)) {
15092            const fd = replaceRepeaterInFieldDef(channelDef, repeater);
15093            if (fd) {
15094                return fd;
15095            }
15096            else if (isConditionalDef(channelDef)) {
15097                return { condition: channelDef.condition };
15098            }
15099        }
15100        else {
15101            if (hasConditionalFieldDef(channelDef)) {
15102                const fd = replaceRepeaterInFieldDef(channelDef.condition, repeater);
15103                if (fd) {
15104                    return Object.assign({}, channelDef, { condition: fd });
15105                }
15106                else {
15107                    const channelDefWithoutCondition = __rest(channelDef, ["condition"]);
15108                    return channelDefWithoutCondition;
15109                }
15110            }
15111            return channelDef;
15112        }
15113        return undefined;
15114    }
15115    function replaceRepeater(mapping, repeater) {
15116        const out = {};
15117        for (const channel in mapping) {
15118            if (mapping.hasOwnProperty(channel)) {
15119                const channelDef = mapping[channel];
15120                if (isArray(channelDef)) {
15121                    // array cannot have condition
15122                    out[channel] = channelDef.map(cd => replaceRepeaterInChannelDef(cd, repeater)).filter(cd =>
15122 cd);
15123                }
15124                else {
15125                    const cd = replaceRepeaterInChannelDef(channelDef, repeater);
15126                    if (cd !== undefined) {
15127                        out[channel] = cd;
15128                    }
15129                }
15130            }
15131        }
15132        return out;
15133    }
15134
15135    function facetSortFieldName(fieldDef, sort, opt) {
15136        return vgField(sort, Object.assign({ suffix: `by_${vgField(fieldDef)}` }, (opt || {})));
15137    }
15138    class FacetModel extends ModelWithField {
15139        constructor(spec, parent, parentGivenName, repeater, config) {
15140            super(spec, parent, parentGivenName, config, repeater, spec.resolve);
15141            this.type = 'facet';
15142            this.child = buildModel(spec.spec, this, this.getName('child'), undefined, repeater, config, false);
15143            this.children = [this.child];
15144            const facet = replaceRepeaterInFacet(spec.facet, repeater);
15145            this.facet = this.initFacet(facet);
15146        }
15147        initFacet(facet) {
15148            // clone to prevent side effect to the original spec
15149            return reduce(facet, (normalizedFacet, fieldDef, channel) => {
15150                if (!contains([ROW, COLUMN], channel)) {
15151                    // Drop unsupported channel
15152                    warn(message.incompatibleChannel(channel, 'facet'));
15153                    return normalizedFacet;
15154                }
15155                if (fieldDef.field === undefined) {
15156                    warn(message.emptyFieldDef(fieldDef, channel));
15157                    return normalizedFacet;
15158                }
15159                // Convert type to full, lowercase type, or augment the fieldDef with a default type if missing.
15160                normalizedFacet[channel] = normalize(fieldDef, channel);
15161                return normalizedFacet;
15162            }, {});
15163        }
15164        channelHasField(channel) {
15165            return !!this.facet[channel];
15166        }
15167        fieldDef(channel) {
15168            return this.facet[channel];
15169        }
15170        parseData() {
15171            this.component.data = parseData(this);
15172            this.child.parseData();
15173        }
15174        parseLayoutSize() {
15175            parseChildrenLayoutSize(this);
15176        }
15177        parseSelection() {
15178            // As a facet has a single child, the selection components are the same.
15179            // The child maintains its selections to assemble signals, which remain
15180            // within its unit.
15181            this.child.parseSelection();
15182            this.component.selection = this.child.component.selection;
15183        }
15184        parseMarkGroup() {
15185            this.child.parseMarkGroup();
15186        }
15187        parseAxisAndHeader() {
15188            this.child.parseAxisAndHeader();
15189            this.parseHeader('column');
15190            this.parseHeader('row');
15191            this.mergeChildAxis('x');
15192            this.mergeChildAxis('y');
15193        }
15194        parseHeader(channel) {
15195            if (this.channelHasField(channel)) {
15196                const fieldDef = this.facet[channel];
15197                let title$$1 = title(fieldDef, this.config, { allowDisabling: true });
15198                if (this.child.component.layoutHeaders[channel].title) {
15199                    // merge title with child to produce "Title / Subtitle / Sub-subtitle"
15200                    title$$1 += ' / ' + this.child.component.layoutHeaders[channel].title;
15201                    this.child.component.layoutHeaders[channel].title = null;
15202                }
15203                this.component.layoutHeaders[channel] = {
15204                    title: title$$1,
15205                    facetFieldDef: fieldDef,
15206                    // TODO: support adding label to footer as well
15207                    header: [this.makeHeaderComponent(channel, true)]
15208                };
15209            }
15210        }
15211        makeHeaderComponent(channel, labels) {
15212            const sizeType = channel === 'row' ? 'height' : 'width';
15213            return {
15214                labels,
15215                sizeSignal: this.child.component.layoutSize.get(sizeType) ? this.child.getSizeSignalRef(sizeType) : undefined,
15216                axes: []
15217            };
15218        }
15219        mergeChildAxis(channel) {
15220            const { child } = this;
15221            if (child.component.axes[channel]) {
15222                const { layoutHeaders, resolve } = this.component;
15223                resolve.axis[channel] = parseGuideResolve(resolve, channel);
15224                if (resolve.axis[channel] === 'shared') {
15225                    // For shared axis, move the axes to facet's header or footer
15226                    const headerChannel = channel === 'x' ? 'column' : 'row';
15227                    const layoutHeader = layoutHeaders[headerChannel];
15228                    for (const axisComponent of child.component.axes[channel]) {
15229                        const headerType = getHeaderType(axisComponent.get('orient'));
15230                        layoutHeader[headerType] = layoutHeader[headerType] || [this.makeHeaderComponent(headerChannel, false)];
15231                        const mainAxis = assembleAxis(axisComponent, 'main', this.config, { header: true });
15232                        // LayoutHeader no longer keep track of property precedence, thus let's combine.
15233                        layoutHeader[headerType][0].axes.push(mainAxis);
15234                        axisComponent.mainExtracted = true;
15235                    }
15236                }
15237            }
15238        }
15239        assembleSelectionTopLevelSignals(signals) {
15240            return this.child.assembleSelectionTopLevelSignals(signals);
15241        }
15242        assembleSelectionSignals() {
15243            this.child.assembleSelectionSignals();
15244            return [];
15245        }
15246        assembleSelectionData(data) {
15247            return this.child.assembleSelectionData(data);
15248        }
15249        getHeaderLayoutMixins() {
15250            const layoutMixins = {};
15251            ['row', 'column'].forEach((channel) => {
15252                ['header', 'footer'].forEach((headerType) => {
15253                    const layoutHeaderComponent = this.component.layoutHeaders[channel];
15254                    const headerComponent = layoutHeaderComponent[headerType];
15255                    if (headerComponent && headerComponent[0]) {
15256                        // set header/footerBand
15257                        const sizeType = channel === 'row' ? 'height' : 'width';
15258                        const bandType = headerType === 'header' ? 'headerBand' : 'footerBand';
15259                        if (!this.child.component.layoutSize.get(sizeType)) {
15260                            // If facet child does not have size signal, then apply headerBand
15261                            layoutMixins[bandType] = layoutMixins[bandType] || {};
15262                            layoutMixins[bandType][channel] = 0.5;
15263                        }
15264                        if (layoutHeaderComponent.title) {
15265                            layoutMixins.offset = layoutMixins.offset || {};
15266                            layoutMixins.offset[channel === 'row' ? 'rowTitle' : 'columnTitle'] = 10;
15267                        }
15268                    }
15269                });
15270            });
15271            return layoutMixins;
15272        }
15273        assembleDefaultLayout() {
15274            const columns = this.channelHasField('column') ? this.columnDistinctSignal() : 1;
15275            // TODO: determine default align based on shared / independent scales
15276            return Object.assign({}, this.getHeaderLayoutMixins(), { columns, bounds: 'full', align: 'all' });
15277        }
15278        assembleLayoutSignals() {
15279            // FIXME(https://github.com/vega/vega-lite/issues/1193): this can be incorrect if we have independent scales.
15280            return this.child.assembleLayoutSignals();
15281        }
15282        columnDistinctSignal() {
15283            if (this.parent && this.parent instanceof FacetModel) {
15284                // For nested facet, we will add columns to group mark instead
15285                // See discussion in https://github.com/vega/vega/issues/952
15286                // and https://github.com/vega/vega-view/releases/tag/v1.2.6
15287                return undefined;
15288            }
15289            else {
15290                // In facetNode.assemble(), the name is always this.getName('column') + '_layout'.
15291                const facetLayoutDataName = this.getName('column_domain');
15292                return { signal: `length(data('${facetLayoutDataName}'))` };
15293            }
15294        }
15295        assembleGroup(signals) {
15296            if (this.parent && this.parent instanceof FacetModel) {
15297                // Provide number of columns for layout.
15298                // See discussion in https://github.com/vega/vega/issues/952
15299                // and https://github.com/vega/vega-view/releases/tag/v1.2.6
15300                return Object.assign({}, (this.channelHasField('column')
15301                    ? {
15302                        encode: {
15303                            update: {
15304                                // TODO(https://github.com/vega/vega-lite/issues/2759):
15305                                // Correct the signal for facet of concat of facet_column
15306                                columns: { field: vgField(this.facet.column, { prefix: 'distinct' }) }
15307                            }
15308                        }
15309                    }
15310                    : {}), super.assembleGroup(signals));
15311            }
15312            return super.assembleGroup(signals);
15313        }
15314        /**
15315         * Aggregate cardinality for calculating size
15316         */
15317        getCardinalityAggregateForChild() {
15318            const fields = [];
15319            const ops = [];
15320            const as = [];
15321            if (this.child instanceof FacetModel) {
15322                if (this.child.channelHasField('column')) {
15323                    const field$$1 = vgField(this.child.facet.column);
15324                    fields.push(field$$1);
15325                    ops.push('distinct');
15326                    as.push(`distinct_${field$$1}`);
15327                }
15328            }
15329            else {
15330                for (const channel of ['x', 'y']) {
15331                    const childScaleComponent = this.child.component.scales[channel];
15332                    if (childScaleComponent && !childScaleComponent.merged) {
15333                        const type = childScaleComponent.get('type');
15334                        const range = childScaleComponent.get('range');
15335                        if (hasDiscreteDomain(type) && isVgRangeStep(range)) {
15336                            const domain = assembleDomain(this.child, channel);
15337                            const field$$1 = getFieldFromDomain(domain);
15338                            if (field$$1) {
15339                                fields.push(field$$1);
15340                                ops.push('distinct');
15341                                as.push(`distinct_${field$$1}`);
15342                            }
15343                            else {
15344                                warn('Unknown field for ${channel}.  Cannot calculate view size.');
15345                            }
15346                        }
15347                    }
15348                }
15349            }
15350            return { fields, ops, as };
15351        }
15352        assembleFacet() {
15353            const { name, data } = this.component.data.facetRoot;
15354            const { row, column } = this.facet;
15355            const { fields, ops, as } = this.getCardinalityAggregateForChild();
15356            const groupby = [];
15357            ['row', 'column'].forEach((channel) => {
15358                const fieldDef = this.facet[channel];
15359                if (fieldDef) {
15360                    groupby.push(vgField(fieldDef));
15361                    const { sort } = fieldDef;
15362                    if (isSortField(sort)) {
15363                        const { field: field$$1, op } = sort;
15364                        const outputName = facetSortFieldName(fieldDef, sort);
15365                        if (row && column) {
15366                            // For crossed facet, use pre-calculate field as it requires a different groupby
15367                            // For each calculated field, apply max and assign them to the same name as
15368                            // all values of the same group should be the same anyway.
15369                            fields.push(outputName);
15370                            ops.push('max');
15371                            as.push(outputName);
15372                        }
15373                        else {
15374                            fields.push(field$$1);
15375                            ops.push(op);
15376                            as.push(outputName);
15377                        }
15378                    }
15379                    else if (isArray(sort)) {
15380                        const outputName = sortArrayIndexField(fieldDef, channel);
15381                        fields.push(outputName);
15382                        ops.push('max');
15383                        as.push(outputName);
15384                    }
15385                }
15386            });
15387            const cross = !!row && !!column;
15388            return Object.assign({ name,
15389                data,
15390                groupby }, (cross || fields.length
15391                ? {
15392                    aggregate: Object.assign({}, (cross ? { cross } : {}), (fields.length ? { fields, ops, as } : {}))
15393                }
15394                : {}));
15395        }
15396        headerSortFields(channel) {
15397            const { facet } = this;
15398            const fieldDef = facet[channel];
15399            if (fieldDef) {
15400                if (isSortField(fieldDef.sort)) {
15401                    return [facetSortFieldName(fieldDef, fieldDef.sort, { expr: 'datum' })];
15402                }
15403                else if (isArray(fieldDef.sort)) {
15404                    return [sortArrayIndexField(fieldDef, channel, { expr: 'datum' })];
15405                }
15406                return [vgField(fieldDef, { expr: 'datum' })];
15407            }
15408            return [];
15409        }
15410        headerSortOrder(channel) {
15411            const { facet } = this;
15412            const fieldDef = facet[channel];
15413            if (fieldDef) {
15414                const { sort } = fieldDef;
15415                const order = (isSortField(sort) ? sort.order : !isArray(sort) && sort) || 'ascending';
15416                return [order];
15417            }
15418            return [];
15419        }
15420        assembleMarks() {
15421            const { child } = this;
15422            const facetRoot = this.component.data.facetRoot;
15423            const data = assembleFacetData(facetRoot);
15424            // If we facet by two dimensions, we need to add a cross operator to the aggregation
15425            // so that we create all groups
15426            const layoutSizeEncodeEntry = child.assembleLayoutSize();
15427            const title$$1 = child.assembleTitle();
15428            const style = child.assembleGroupStyle();
15429            const markGroup = Object.assign({ name: this.getName('cell'), type: 'group' }, (title$$1 ? { title: title$$1 } : {}), (style ? { style } : {}), { from: {
15430                    facet: this.assembleFacet()
15431                }, 
15432                // TODO: move this to after data
15433                sort: {
15434                    field: [...this.headerSortFields('row'), ...this.headerSortFields('column')],
15435                    order: [...this.headerSortOrder('row'), ...this.headerSortOrder('column')]
15436                } }, (data.length > 0 ? { data: data } : {}), (layoutSizeEncodeEntry ? { encode: { update: layoutSizeEncodeEntry } } : {}), child.assembleGroup(assembleFacetSignals(this, [])));
15437            return [markGroup];
15438        }
15439        getMapping() {
15440            return this.facet;
15441        }
15442    }
15443
15444    function makeWindowFromFacet(parent, facet) {
15445        const { row, column } = facet;
15446        if (row && column) {
15447            let newParent = null;
15448            // only need to make one for crossed facet
15449            for (const fieldDef of [row, column]) {
15450                if (isSortField(fieldDef.sort)) {
15451                    const { field, op } = fieldDef.sort;
15452                    parent = newParent = new WindowTransformNode(parent, {
15453                        window: [
15454                            {
15455                                op,
15456                                field,
15457                                as: facetSortFieldName(fieldDef, fieldDef.sort, { forAs: true })
15458                            }
15459                        ],
15460                        groupby: [vgField(fieldDef)],
15461                        frame: [null, null]
15462                    });
15463                }
15464            }
15465            return newParent;
15466        }
15467        return null;
15468    }
15469
15470    function findSource(data, sources) {
15471        for (const other of sources) {
15472            const otherData = other.data;
15473            if (isInlineData(data) && isInlineData(otherData)) {
15474                const srcVals = data.values;
15475                const otherVals = otherData.values;
15476                if (deepEqual(srcVals, otherVals)) {
15477                    return other;
15478                }
15479            }
15480            else if (isUrlData(data) && isUrlData(otherData)) {
15481                if (data.url === otherData.url) {
15482                    return other;
15483                }
15484            }
15485            else if (isNamedData(data)) {
15486                if (data.name === other.dataName) {
15487                    return other;
15488                }
15489            }
15490        }
15491        return null;
15492    }
15493    function parseRoot(model, sources) {
15494        if (model.data || !model.parent) {
15495            // if the model defines a data source or is the root, create a source node
15496            const existingSource = findSource(model.data, sources);
15497            if (existingSource) {
15498                existingSource.data.format = mergeDeep({}, model.data.format, existingSource.data.format);
15499                return existingSource;
15500            }
15501            else {
15502                const source = new SourceNode(model.data);
15503                sources.push(source);
15504                return source;
15505            }
15506        }
15507        else {
15508            // If we don't have a source defined (overriding parent's data), use the parent's facet root or main.
15509            return model.parent.component.data.facetRoot
15510                ? model.parent.component.data.facetRoot
15511                : model.parent.component.data.main;
15512        }
15513    }
15514    /**
15515     * Parses a transforms array into a chain of connected dataflow nodes.
15516     */
15517    function parseTransformArray(head, model, ancestorParse) {
15518        let lookupCounter = 0;
15519        for (const t of model.transforms) {
15520            let derivedType = undefined;
15521            let transformNode;
15522            if (isCalculate(t)) {
15523                transformNode = head = new CalculateNode(head, t);
15524                derivedType = 'derived';
15525            }
15526            else if (isFilter(t)) {
15527                transformNode = head = ParseNode.makeImplicitFromFilterTransform(head, t, ancestorParse) || head;
15528                head = new FilterNode(head, model, t.filter);
15529            }
15530            else if (isBin(t)) {
15531                transformNode = head = BinNode.makeFromTransform(head, t, model);
15532                derivedType = 'number';
15533            }
15534            else if (isTimeUnit$1(t)) {
15535                transformNode = head = TimeUnitNode.makeFromTransform(head, t);
15536                derivedType = 'date';
15537                // Create parse node because the input to time unit is always date.
15538                const parsedAs = ancestorParse.getWithExplicit(t.field);
15539                if (parsedAs.value === undefined) {
15540                    head = new ParseNode(head, { [t.field]: derivedType });
15541                    ancestorParse.set(t.field, derivedType, false);
15542                }
15543            }
15544            else if (isAggregate$1(t)) {
15545                transformNode = head = AggregateNode.makeFromTransform(head, t);
15546                derivedType = 'number';
15547                if (requiresSelectionId(model)) {
15548                    head = new IdentifierNode(head);
15549                }
15550            }
15551            else if (isLookup(t)) {
15552                transformNode = head = LookupNode.make(head, model, t, lookupCounter++);
15553                derivedType = 'derived';
15554            }
15555            else if (isWindow(t)) {
15556                transformNode = head = new WindowTransformNode(head, t);
15557                derivedType = 'number';
15558            }
15559            else if (isStack(t)) {
15560                transformNode = head = StackNode.makeFromTransform(head, t);
15561                derivedType = 'derived';
15562            }
15563            else if (isFold(t)) {
15564                transformNode = head = new FoldTransformNode(head, t);
15565                derivedType = 'derived';
15566            }
15567            else if (isFlatten(t)) {
15568                transformNode = head = new FlattenTransformNode(head, t);
15569                derivedType = 'derived';
15570            }
15571            else if (isSample(t)) {
15572                head = new SampleTransformNode(head, t);
15573            }
15574            else if (isImpute(t)) {
15575                transformNode = head = ImputeNode.makeFromTransform(head, t);
15576                derivedType = 'derived';
15577            }
15578            else {
15579                warn(message.invalidTransformIgnored(t));
15580                continue;
15581            }
15582            if (transformNode && derivedType !== undefined) {
15583                for (const field of transformNode.producedFields()) {
15584                    ancestorParse.set(field, derivedType, false);
15585                }
15586            }
15587        }
15588        return head;
15589    }
15590    /*
15591    Description of the dataflow (http://asciiflow.com/):
15592         +--------+
15593         | Source |
15594         +---+----+
15595             |
15596             v
15597         FormatParse
15598         (explicit)
15599             |
15600             v
15601         Transforms
15602    (Filter, Calculate, Binning, TimeUnit, Aggregate, Window, ...)
15603             |
15604             v
15605         FormatParse
15606         (implicit)
15607             |
15608             v
15609     Binning (in `encoding`)
15610             |
15611             v
15612     Timeunit (in `encoding`)
15613             |
15614             v
15615    Formula From Sort Array
15616             |
15617             v
15618          +--+--+
15619          | Raw |
15620          +-----+
15621             |
15622             v
15623      Aggregate (in `encoding`)
15624             |
15625             v
15626      Stack (in `encoding`)
15627             |
15628             v
15629      Invalid Filter
15630             |
15631             v
15632       +----------+
15633       |   Main   |
15634       +----------+
15635             |
15636             v
15637         +-------+
15638         | Facet |----> "column", "column-layout", and "row"
15639         +-------+
15640             |
15641             v
15642      ...Child data...
15643    */
15644    function parseData(model) {
15645        let head = parseRoot(model, model.component.data.sources);
15646        const { outputNodes, outputNodeRefCounts } = model.component.data;
15647        const ancestorParse = model.parent ? model.parent.component.data.ancestorParse.clone() : new AncestorParse();
15648        // format.parse: null means disable parsing
15649        if (model.data && model.data.format && model.data.format.parse === null) {
15650            ancestorParse.parseNothing = true;
15651        }
15652        head = ParseNode.makeExplicit(head, model, ancestorParse) || head;
15653        // Default discrete selections require an identifier transform to
15654        // uniquely identify data points as the _id field is volatile. Add
15655        // this transform at the head of our pipeline such that the identifier
15656        // field is available for all subsequent datasets. Additional identifier
15657        // transforms will be necessary when new tuples are constructed
15658        // (e.g., post-aggregation).
15659        if (requiresSelectionId(model) && (isUnitModel(model) || isLayerModel(model))) {
15660            head = new IdentifierNode(head);
15661        }
15662        // HACK: This is equivalent for merging bin extent for union scale.
15663        // FIXME(https://github.com/vega/vega-lite/issues/2270): Correctly merge extent / bin node for shared bin scale
15664        const parentIsLayer = model.parent && isLayerModel(model.parent);
15665        if (isUnitModel(model) || isFacetModel(model)) {
15666            if (parentIsLayer) {
15667                head = BinNode.makeFromEncoding(head, model) || head;
15668            }
15669        }
15670        if (model.transforms.length > 0) {
15671            head = parseTransformArray(head, model, ancestorParse);
15672        }
15673        head = ParseNode.makeImplicitFromEncoding(head, model, ancestorParse) || head;
15674        if (isUnitModel(model)) {
15675            head = GeoJSONNode.parseAll(head, model);
15676            head = GeoPointNode.parseAll(head, model);
15677        }
15678        if (isUnitModel(model) || isFacetModel(model)) {
15679            if (!parentIsLayer) {
15680                head = BinNode.makeFromEncoding(head, model) || head;
15681            }
15682            head = TimeUnitNode.makeFromEncoding(head, model) || head;
15683            head = CalculateNode.parseAllForSortIndex(head, model);
15684        }
15685        // add an output node pre aggregation
15686        const rawName = model.getName(RAW);
15687        const raw = new OutputNode(head, rawName, RAW, outputNodeRefCounts);
15688        outputNodes[rawName] = raw;
15689        head = raw;
15690        if (isUnitModel(model)) {
15691            const agg = AggregateNode.makeFromEncoding(head, model);
15692            if (agg) {
15693                head = agg;
15694                if (requiresSelectionId(model)) {
15695                    head = new IdentifierNode(head);
15696                }
15697            }
15698            head = ImputeNode.makeFromEncoding(head, model) || head;
15699            head = StackNode.makeFromEncoding(head, model) || head;
15700        }
15701        // output node for marks
15702        const mainName = model.getName(MAIN);
15703        const main = new OutputNode(head, mainName, MAIN, outputNodeRefCounts);
15704        outputNodes[mainName] = main;
15705        head = main;
15706        // add facet marker
15707        let facetRoot = null;
15708        if (isFacetModel(model)) {
15709            const facetName = model.getName('facet');
15710            // Derive new sort index field for facet's sort array
15711            head = CalculateNode.parseAllForSortIndex(head, model);
15712            // Derive new aggregate (via window) for facet's sort field
15713            // TODO: use JoinAggregate once we have it
15714            // augment data source with new fields for crossed facet
15715            head = makeWindowFromFacet(head, model.facet) || head;
15716            facetRoot = new FacetNode(head, model, facetName, main.get
15716Source());
15717            outputNodes[facetName] = facetRoot;
15718            head = facetRoot;
15719        }
15720        return Object.assign({}, model.component.data, { outputNodes,
15721            outputNodeRefCounts,
15722            raw,
15723            main,
15724            facetRoot,
15725            ancestorParse });
15726    }
15727
15728    class BaseConcatModel extends Model {
15729        constructor(spec, parent, parentGivenName, config, repeater, resolve) {
15730            super(spec, parent, parentGivenName, config, repeater, resolve);
15731        }
15732        parseData() {
15733            this.component.data = parseData(this);
15734            this.children.forEach(child => {
15735                child.parseData();
15736            });
15737        }
15738        parseSelection() {
15739            // Merge selections up the hierarchy so that they may be referenced
15740            // across unit specs. Persist their definitions within each child
15741            // to assemble signals which remain within output Vega unit groups.
15742            this.component.selection = {};
15743            for (const child of this.children) {
15744                child.parseSelection();
15745                keys(child.component.selection).forEach(key => {
15746                    this.component.selection[key] = child.component.selection[key];
15747                });
15748            }
15749        }
15750        parseMarkGroup() {
15751            for (const child of this.children) {
15752                child.parseMarkGroup();
15753            }
15754        }
15755        parseAxisAndHeader() {
15756            for (const child of this.children) {
15757                child.parseAxisAndHeader();
15758            }
15759            // TODO(#2415): support shared axes
15760        }
15761        assembleSelectionTopLevelSignals(signals) {
15762            return this.children.reduce((sg, child) => child.assembleSelectionTopLevelSignals(sg), signals);
15763        }
15764        assembleSelectionSignals() {
15765            this.children.forEach(child => child.assembleSelectionSignals());
15766            return [];
15767        }
15768        assembleLayoutSignals() {
15769            return this.children.reduce((signals, child) => {
15770                return signals.concat(child.assembleLayoutSignals());
15771            }, assembleLayoutSignals(this));
15772        }
15773        assembleSelectionData(data) {
15774            return this.children.reduce((db, child) => child.assembleSelectionData(db), data);
15775        }
15776        assembleMarks() {
15777            // only children have marks
15778            return this.children.map(child => {
15779                const title = child.assembleTitle();
15780                const style = child.assembleGroupStyle();
15781                const layoutSizeEncodeEntry = child.assembleLayoutSize();
15782                return Object.assign({ type: 'group', name: child.getName('group') }, (title ? { title } : {}), (style ? { style } : {}), (layoutSizeEncodeEntry
15783                    ? {
15784                        encode: {
15785                            update: layoutSizeEncodeEntry
15786                        }
15787                    }
15788                    : {}), child.assembleGroup());
15789            });
15790        }
15791    }
15792
15793    class ConcatModel extends BaseConcatModel {
15794        constructor(spec, parent, parentGivenName, repeater, config) {
15795            super(spec, parent, parentGivenName, config, repeater, spec.resolve);
15796            this.type = 'concat';
15797            if (spec.resolve && spec.resolve.axis && (spec.resolve.axis.x === 'shared' || spec.resolve.axis.y === 'shared')) {
15798                warn(message.CONCAT_CANNOT_SHARE_AXIS);
15799            }
15800            this.isVConcat = isVConcatSpec(spec);
15801            this.children = (isVConcatSpec(spec) ? spec.vconcat : spec.hconcat).map((child, i) => {
15802                return buildModel(child, this, this.getName('concat_' + i), undefined, repeater, config, false);
15803            });
15804        }
15805        parseLayoutSize() {
15806            parseConcatLayoutSize(this);
15807        }
15808        parseAxisGroup() {
15809            return null;
15810        }
15811        assembleDefaultLayout() {
15812            return Object.assign({}, (this.isVConcat ? { columns: 1 } : {}), { bounds: 'full', 
15813                // Use align each so it can work with multiple plots with different size
15814                align: 'each' });
15815        }
15816    }
15817
15818    function isFalseOrNull(v) {
15819        return v === false || v === null;
15820    }
15821    class AxisComponent extends Split {
15822        constructor(explicit = {}, implicit = {}, mainExtracted = false) {
15823            super();
15824            this.explicit = explicit;
15825            this.implicit = implicit;
15826            this.mainExtracted = mainExtracted;
15827        }
15828        clone() {
15829            return new AxisComponent(duplicate(this.explicit), duplicate(this.implicit), this.mainExtracted);
15830        }
15831        hasAxisPart(part) {
15832            // FIXME(https://github.com/vega/vega-lite/issues/2552) this method can be wrong if users use a Vega theme.
15833            if (part === 'axis') {
15834                // always has the axis container part
15835                return true;
15836            }
15837            if (part === 'grid' || part === 'title') {
15838                return !!this.get(part);
15839            }
15840            // Other parts are enabled by default, so they should not be false or null.
15841            return !isFalseOrNull(this.get(part));
15842        }
15843    }
15844
15845    function getAxisConfig(property, config, channel, orient = '', scaleType) {
15846        // configTypes to loop, starting from higher precedence
15847        const configTypes = (scaleType === 'band' ? ['axisBand'] : []).concat([
15848            channel === 'x' ? 'axisX' : 'axisY',
15849            'axis' + orient.substr(0, 1).toUpperCase() + orient.substr(1),
15850            'axis'
15851        ]);
15852        for (const configType of configTypes) {
15853            if (config[configType] && config[configType][property] !== undefined) {
15854                return config[configType][property];
15855            }
15856        }
15857        return undefined;
15858    }
15859
15860    function labels$1(model, channel, specifiedLabelsSpec, orient) {
15861        const fieldDef = model.fieldDef(channel) ||
15862            (channel === 'x' ? model.fieldDef('x2') : channel === 'y' ? model.fieldDef('y2') : undefined);
15863        const axis = model.axis(channel);
15864        const config = model.config;
15865        let labelsSpec = {};
15866        // Text
15867        if (isTimeFieldDef(fieldDef)) {
15868            const isUTCScale = model.getScaleComponent(channel).get('type') === ScaleType.UTC;
15869            const expr = timeFormatExpression('datum.value', fieldDef.timeUnit, axis.format, config.axis.shortTimeLabels, null, isUTCScale);
15870            if (expr) {
15871                labelsSpec.text = { signal: expr };
15872            }
15873        }
15874        labelsSpec = Object.assign({}, labelsSpec, specifiedLabelsSpec);
15875        return keys(labelsSpec).length === 0 ? undefined : labelsSpec;
15876    }
15877
15878    // TODO: we need to refactor this method after we take care of config refactoring
15879    /**
15880     * Default rules for whether to show a grid should be shown for a channel.
15881     * If `grid` is unspecified, the default value is `true` for ordinal scales that are not binned
15882     */
15883    function defaultGrid(scaleType, fieldDef) {
15884        return !hasDiscreteDomain(scaleType) && !isBinning(fieldDef.bin);
15885    }
15886    function gridScale(model, channel) {
15887        const gridChannel = channel === 'x' ? 'y' : 'x';
15888        if (model.getScaleComponent(gridChannel)) {
15889            return model.scaleName(gridChannel);
15890        }
15891        return undefined;
15892    }
15893    function labelAngle(model, specifiedAxis, channel, fieldDef) {
15894        // try axis value
15895        if (specifiedAxis.labelAngle !== undefined) {
15896            // Make angle within [0,360)
15897            return ((specifiedAxis.labelAngle % 360) + 360) % 360;
15898        }
15899        else {
15900            // try axis config value
15901            const angle = getAxisConfig('labelAngle', model.config, channel, orient(channel), model.getScaleComponent(channel).get('type'));
15902            if (angle !== undefined) {
15903                return ((angle % 360) + 360) % 360;
15904            }
15905            else {
15906                // get default value
15907                if (channel === X && contains([NOMINAL, ORDINAL], fieldDef.type)) {
15908                    return 270;
15909                }
15910                // no default
15911                return undefined;
15912            }
15913        }
15914    }
15915    function defaultLabelBaseline(angle, axisOrient) {
15916        if (angle !== undefined) {
15917            if (axisOrient === 'top' || axisOrient === 'bottom') {
15918                if (angle <= 45 || 315 <= angle) {
15919                    return axisOrient === 'top' ? 'bottom' : 'top';
15920                }
15921                else if (135 <= angle && angle <= 225) {
15922                    return axisOrient === 'top' ? 'top' : 'bottom';
15923                }
15924                else {
15925                    return 'middle';
15926                }
15927            }
15928            else {
15929                if (angle <= 45 || 315 <= angle || (135 <= angle && angle <= 225)) {
15930                    return 'middle';
15931                }
15932                else if (45 <= angle && angle <= 135) {
15933                    return axisOrient === 'left' ? 'top' : 'bottom';
15934                }
15935                else {
15936                    return axisOrient === 'left' ? 'bottom' : 'top';
15937                }
15938            }
15939        }
15940        return undefined;
15941    }
15942    function defaultLabelAlign(angle, axisOrient) {
15943        if (angle !== undefined) {
15944            angle = ((angle % 360) + 360) % 360;
15945            if (axisOrient === 'top' || axisOrient === 'bottom') {
15946                if (angle % 180 === 0) {
15947                    return 'center';
15948                }
15949                else if (0 < angle && angle < 180) {
15950                    return axisOrient === 'top' ? 'right' : 'left';
15951                }
15952                else {
15953                    return axisOrient === 'top' ? 'left' : 'right';
15954                }
15955            }
15956            else {
15957                if ((angle + 90) % 180 === 0) {
15958                    return 'center';
15959                }
15960                else if (90 <= angle && angle < 270) {
15961                    return axisOrient === 'left' ? 'left' : 'right';
15962                }
15963                else {
15964                    return axisOrient === 'left' ? 'right' : 'left';
15965                }
15966            }
15967        }
15968        return undefined;
15969    }
15970    function defaultLabelFlush(fieldDef, channel) {
15971        if (channel === 'x' && contains(['quantitative', 'temporal'], fieldDef.type)) {
15972            return true;
15973        }
15974        return undefined;
15975    }
15976    function defaultLabelOverlap$1(fieldDef, scaleType) {
15977        // do not prevent overlap for nominal data because there is no way to infer what the missing labels are
15978        if (fieldDef.type !== 'nominal') {
15979            if (scaleType === 'log') {
15980                return 'greedy';
15981            }
15982            return true;
15983        }
15984        return undefined;
15985    }
15986    function orient(channel) {
15987        switch (channel) {
15988            case X:
15989                return 'bottom';
15990            case Y:
15991                return 'left';
15992        }
15993        /* istanbul ignore next: This should never happen. */
15994        throw new Error(message.INVALID_CHANNEL_FOR_AXIS);
15995    }
15996    function defaultTickCount({ fieldDef, scaleType, size, scaleName, specifiedAxis = {} }) {
15997        if (!hasDiscreteDomain(scaleType) &&
15998            scaleType !== 'log' &&
15999            !contains(['month', 'hours', 'day', 'quarter'], fieldDef.timeUnit)) {
16000            if (specifiedAxis.tickStep) {
16001                return { signal: `(domain('${scaleName}')[1] - domain('${scaleName}')[0]) / ${specifiedAxis.tickStep} + 1` };
16002            }
16003            else if (isBinning(fieldDef.bin)) {
16004                // for binned data, we don't want more ticks than maxbins
16005                return { signal: `ceil(${size.signal}/10)` };
16006            }
16007            return { signal: `ceil(${size.signal}/40)` };
16008        }
16009        return undefined;
16010    }
16011    function values$1(specifiedAxis, model, fieldDef, channel) {
16012        const vals$$1 = specifiedAxis.values;
16013        if (vals$$1) {
16014            return valueArray(fieldDef, vals$$1);
16015        }
16016        if (fieldDef.type === QUANTITATIVE) {
16017            if (isBinning(fieldDef.bin)) {
16018                const domain = model.scaleDomain(channel);
16019                if (domain && domain !== 'unaggregated' && !isSelectionDomain(domain)) {
16020                    // explicit value
16021                    return vals$$1;
16022                }
16023                const binSignal = model.getName(vgField(fieldDef, { suffix: 'bins' }));
16024                return { signal: `sequence(${binSignal}.start, ${binSignal}.stop + ${binSignal}.step, ${binSignal}.step)` };
16025            }
16026            else if (specifiedAxis.tickStep) {
16027                const scaleName = model.scaleName(channel);
16028                const step = specifiedAxis.tickStep;
16029                return { signal: `sequence(domain('${scaleName}')[0], domain('${scaleName}')[1] + ${step}, ${step})` };
16030            }
16031        }
16032        return undefined;
16033    }
16034
16035    function parseUnitAxis(model) {
16036        return POSITION_SCALE_CHANNELS.reduce((axis, channel) => {
16037            if (model.component.scales[channel] && model.axis(channel)) {
16038                axis[channel] = [parseAxis(channel, model)];
16039            }
16040            return axis;
16041        }, {});
16042    }
16043    const OPPOSITE_ORIENT = {
16044        bottom: 'top',
16045        top: 'bottom',
16046        left: 'right',
16047        right: 'left'
16048    };
16049    function parseLayerAxis(model) {
16050        const { axes, resolve } = model.component;
16051        const axisCount = { top: 0, bottom: 0, right: 0, left: 0 };
16052        for (const child of model.children) {
16053            child.parseAxisAndHeader();
16054            for (const channel of keys(child.component.axes)) {
16055                resolve.axis[channel] = parseGuideResolve(model.component.resolve, channel);
16056                if (resolve.axis[channel] === 'shared') {
16057                    // If the resolve says shared (and has not been overridden)
16058                    // We will try to merge and see if there is a conflict
16059                    axes[channel] = mergeAxisComponents(axes[channel], child.component.axes[channel]);
16060                    if (!axes[channel]) {
16061                        // If merge returns nothing, there is a conflict so we cannot make the axis shared.
16062                        // Thus, mark axis as independent and remove the axis component.
16063                        resolve.axis[channel] = 'independent';
16064                        delete axes[channel];
16065                    }
16066                }
16067            }
16068        }
16069        // Move axes to layer's axis component and merge shared axes
16070        for (const channel of [X, Y]) {
16071            for (const child of model.children) {
16072                if (!child.component.axes[channel]) {
16073                    // skip if the child does not have a particular axis
16074                    continue;
16075                }
16076                if (resolve.axis[channel] === 'independent') {
16077                    // If axes are independent, concat the axisComponent array.
16078                    axes[channel] = (axes[channel] || []).concat(child.component.axes[channel]);
16079                    // Automatically adjust orient
16080                    for (const axisComponent of child.component.axes[channel]) {
16081                        const { value: orient$$1, explicit } = axisComponent.getWithExplicit('orient');
16082                        if (axisCount[orient$$1] > 0 && !explicit) {
16083                            // Change axis orient if the number do not match
16084                            const oppositeOrient = OPPOSITE_ORIENT[orient$$1];
16085                            if (axisCount[orient$$1] > axisCount[oppositeOrient]) {
16086                                axisComponent.set('orient', oppositeOrient, false);
16087                            }
16088                        }
16089                        axisCount[orient$$1]++;
16090                        // TODO(https://github.com/vega/vega-lite/issues/2634): automatically add extra offset?
16091                    }
16092                }
16093                // After merging, make sure to remove axes from child
16094                delete child.component.axes[channel];
16095            }
16096        }
16097    }
16098    function mergeAxisComponents(mergedAxisCmpts, childAxisCmpts) {
16099        if (mergedAxisCmpts) {
16100            // FIXME: this is a bit wrong once we support multiple axes
16101            if (mergedAxisCmpts.length !== childAxisCmpts.length) {
16102                return undefined; // Cannot merge axis component with different number of axes.
16103            }
16104            const length = mergedAxisCmpts.length;
16105            for (let i = 0; i < length; i++) {
16106                const merged = mergedAxisCmpts[i];
16107                const child = childAxisCmpts[i];
16108                if (!!merged !== !!child) {
16109                    return undefined;
16110                }
16111                else if (merged && child) {
16112                    const mergedOrient = merged.getWithExplicit('orient');
16113                    const childOrient = child.getWithExplicit('orient');
16114                    if (mergedOrient.explicit && childOrient.explicit && mergedOrient.value !== childOrient.value) {
16115                        // TODO: throw warning if resolve is explicit (We don't have info about explicit/implicit resolve yet.)
16116                        // Cannot merge due to inconsistent orient
16117                        return undefined;
16118                    }
16119                    else {
16120                        mergedAxisCmpts[i] = mergeAxisComponent(merged, child);
16121                    }
16122                }
16123            }
16124        }
16125        else {
16126            // For first one, return a copy of the child
16127            return childAxisCmpts.map(axisComponent => axisComponent.clone());
16128        }
16129        return mergedAxisCmpts;
16130    }
16131    function mergeAxisComponent(merged, child) {
16132        for (const prop of VG_AXIS_PROPERTIES) {
16133            const mergedValueWithExplicit = mergeValuesWithExplicit(merged.getWithExplicit(prop), child.getWithExplicit(prop), prop, 'axis', 
16134            // Tie breaker function
16135            (v1, v2) => {
16136                switch (prop) {
16137                    case 'title':
16138                        return mergeTitleComponent(v1, v2);
16139                    case 'gridScale':
16140                        return {
16141                            explicit: v1.explicit,
16142                            value: getFirstDefined(v1.value, v2.value)
16143                        };
16144                }
16145                return defaultTieBreaker(v1, v2, prop, 'axis');
16146            });
16147            merged.setWithExplicit(prop, mergedValueWithExplicit);
16148        }
16149        return merged;
16150    }
16151    function getFieldDefTitle(model, channel) {
16152        const channel2 = channel === 'x' ? 'x2' : 'y2';
16153        const fieldDef = model.fieldDef(channel);
16154        const fieldDef2 = model.fieldDef(channel2);
16155        const title1 = fieldDef ? fieldDef.title : undefined;
16156        const title2 = fieldDef2 ? fieldDef2.title : undefined;
16157        if (title1 && title2) {
16158            return mergeTitle(title1, title2);
16159        }
16160        else if (title1) {
16161            return title1;
16162        }
16163        else if (title2) {
16164            return title2;
16165        }
16166        else if (title1 !== undefined) {
16167            // falsy value to disable config
16168            return title1;
16169        }
16170        else if (title2 !== undefined) {
16171            // falsy value to disable config
16172            return title2;
16173        }
16174        return undefined;
16175    }
16176    function isExplicit$1(value, property, axis, model, channel) {
16177        switch (property) {
16178            case 'values':
16179                return !!axis.values;
16180            // specified axis.values is already respected, but may get transformed.
16181            case 'encode':
16182                // both VL axis.encoding and axis.labelAngle affect VG axis.encode
16183                return !!axis.encoding || !!axis.labelAngle;
16184            case 'title':
16185                // title can be explicit if fieldDef.title is set
16186                if (value === getFieldDefTitle(model, channel)) {
16187                    return true;
16188                }
16189        }
16190        // Otherwise, things are explicit if the returned value matches the specified property
16191        return value === axis[property];
16192    }
16193    function parseAxis(channel, model) {
16194        const axis = model.axis(channel);
16195        const axisComponent = new AxisComponent();
16196        // 1.2. Add properties
16197        VG_AXIS_PROPERTIES.forEach(property => {
16198            const value = getProperty$1(property, axis, channel, model);
16199            if (value !== undefined) {
16200                const explicit = isExplicit$1(value, property, axis, model, channel);
16201                const configValue = getAxisConfig(property, model.config, channel, axisComponent.get('orient'), model.getScaleComponent(channel).get('type'));
16202                // only set property if it is explicitly set or has no config value (otherwise we will accidentally override config)
16203                if (explicit || configValue === undefined) {
16204                    // Do not apply implicit rule if there is a config value
16205                    axisComponent.set(property, value, explicit);
16206                }
16207                else if (property === 'grid' && configValue) {
16208                    // Grid is an exception because we need to set grid = true to generate another grid axis
16209                    axisComponent.set(property, configValue, false);
16210                }
16211            }
16212        });
16213        // 2) Add guide encode definition groups
16214        const axisEncoding = axis.encoding || {};
16215        const axisEncode = AXIS_PARTS.reduce((e, part) => {
16216            if (!axisComponent.hasAxisPart(part)) {
16217                // No need to create encode for a disabled part.
16218                return e;
16219            }
16220            const axisEncodingPart = guideEncodeEntry(axisEncoding[part] || {}, model);
16221            const value = part === 'labels'
16222                ? labels$1(model, channel, axisEncodingPart, axisComponent.get('orient'))
16223                : axisEncodingPart;
16224            if (value !== undefined && keys(value).length > 0) {
16225                e[part] = { update: value };
16226            }
16227            return e;
16228        }, {});
16229        // FIXME: By having encode as one property, we won't have fine grained encode merging.
16230        if (keys(axisEncode).length > 0) {
16231            axisComponent.set('encode', axisEncode, !!axis.encoding || axis.labelAngle !== undefined);
16232        }
16233        return axisComponent;
16234    }
16235    function getProperty$1(property, specifiedAxis, channel, model) {
16236        const fieldDef = model.fieldDef(channel);
16237        // Some properties depend on labelAngle so we have to declare it here.
16238        // Also, we don't use `getFirstDefined` for labelAngle
16239        // as we want to normalize specified value to be within [0,360)
16240        const labelAngle$$1 = labelAngle(model, specifiedAxis, channel, fieldDef);
16241        switch (property) {
16242            case 'scale':
16243                return model.scaleName(channel);
16244            case 'gridScale':
16245                return gridScale(model, channel);
16246            case 'format':
16247                // We don't include temporal field here as we apply format in encode block
16248                return numberFormat(fieldDef, specifiedAxis.format, model.config);
16249            case 'grid': {
16250                if (isBinned(model.fieldDef(channel).bin)) {
16251                    return false;
16252                }
16253                else {
16254                    const scaleType = model.getScaleComponent(channel).get('type');
16255                    return getFirstDefined(specifiedAxis.grid, defaultGrid(scaleType, fieldDef));
16256                }
16257            }
16258            case 'labelAlign':
16259                return getFirstDefined(specifiedAxis.labelAlign, defaultLabelAlign(labelAngle$$1, orient(channel)));
16260            case 'labelAngle':
16261                return labelAngle$$1;
16262            case 'labelBaseline':
16263                return getFirstDefined(specifiedAxis.labelBaseline, defaultLabelBaseline(labelAngle$$1, orient(channel)));
16264            case 'labelFlush':
16265                return getFirstDefined(specifiedAxis.labelFlush, defaultLabelFlush(fieldDef, channel));
16266            case 'labelOverlap': {
16267                const scaleType = model.getScaleComponent(channel).get('type');
16268                return getFirstDefined(specifiedAxis.labelOverlap, defaultLabelOverlap$1(fieldDef, scaleType));
16269            }
16270            case 'orient':
16271                return getFirstDefined(specifiedAxis.orient, orient(channel));
16272            case 'tickCount': {
16273                const scaleType = model.getScaleComponent(channel).get('type');
16274                const scaleName = model.scaleName(channel);
16275                const sizeType = channel === 'x' ? 'width' : channel === 'y' ? 'height' : undefined;
16276                const size = sizeType ? model.getSizeSignalRef(sizeType) : undefined;
16277                return getFirstDefined(specifiedAxis.tickCount, defaultTickCount({ fieldDef, scaleType, size, scaleName, specifiedAxis }));
16278            }
16279            case 'title':
16280                const channel2 = channel === 'x' ? 'x2' : 'y2';
16281                const fieldDef2 = model.fieldDef(channel2);
16282                // Keep undefined so we use default if title is unspecified.
16283                // For other falsy value, keep them so we will hide the title.
16284                return getFirstDefined(specifiedAxis.title, getFieldDefTitle(model, channel), // If title not specified, store base parts of fieldDef (and f
16284ieldDef2 if exists)
16285                mergeTitleFieldDefs([toFieldDefBase(fieldDef)], fieldDef2 ? [toFieldDefBase(fieldDef2)] : []));
16286            case 'values':
16287                return values$1(specifiedAxis, model, fieldDef, channel);
16288        }
16289        // Otherwise, return specified property.
16290        return isAxisProperty(property) ? specifiedAxis[property] : undefined;
16291    }
16292
16293    function normalizeMarkDef(mark, encoding, config) {
16294        const markDef = isMarkDef(mark) ? Object.assign({}, mark) : { type: mark };
16295        // set orient, which can be overridden by rules as sometimes the specified orient is invalid.
16296        const specifiedOrient = markDef.orient || getMarkConfig('orient', markDef, config);
16297        markDef.orient = orient$1(markDef.type, encoding, specifiedOrient);
16298        if (specifiedOrient !== undefined && specifiedOrient !== markDef.orient) {
16299            warn(message.orientOverridden(markDef.orient, specifiedOrient));
16300        }
16301        // set opacity and filled if not specified in mark config
16302        const specifiedOpacity = getFirstDefined(markDef.opacity, getMarkConfig('opacity', markDef, config));
16303        if (specifiedOpacity === undefined) {
16304            markDef.opacity = opacity(markDef.type, encoding);
16305        }
16306        const specifiedFilled = markDef.filled;
16307        if (specifiedFilled === undefined) {
16308            markDef.filled = filled(markDef, config);
16309        }
16310        // set cursor, which should be pointer if href channel is present unless otherwise specified
16311        const specifiedCursor = markDef.cursor || getMarkConfig('cursor', markDef, config);
16312        if (specifiedCursor === undefined) {
16313            markDef.cursor = cursor(markDef, encoding, config);
16314        }
16315        return markDef;
16316    }
16317    function cursor(markDef, encoding, config) {
16318        if (encoding.href || markDef.href || getMarkConfig('href', markDef, config)) {
16319            return 'pointer';
16320        }
16321        return markDef.cursor;
16322    }
16323    function opacity(mark, encoding) {
16324        if (contains([POINT, TICK, CIRCLE, SQUARE], mark)) {
16325            // point-based marks
16326            if (!isAggregate(encoding)) {
16327                return 0.7;
16328            }
16329        }
16330        return undefined;
16331    }
16332    function filled(markDef, config) {
16333        const filledConfig = getMarkConfig('filled', markDef, config);
16334        const mark = markDef.type;
16335        return getFirstDefined(filledConfig, mark !== POINT && mark !== LINE && mark !== RULE);
16336    }
16337    function orient$1(mark, encoding, specifiedOrient) {
16338        switch (mark) {
16339            case POINT:
16340            case CIRCLE:
16341            case SQUARE:
16342            case TEXT$1:
16343            case RECT:
16344                // orient is meaningless for these marks.
16345                return undefined;
16346        }
16347        const { x, y, x2, y2 } = encoding;
16348        switch (mark) {
16349            case BAR:
16350                if (isFieldDef(x) && isBinned(x.bin)) {
16351                    return 'vertical';
16352                }
16353                if (isFieldDef(y) && isBinned(y.bin)) {
16354                    return 'horizontal';
16355                }
16356                if (y2 || x2) {
16357                    // Ranged bar does not always have clear orientation, so we allow overriding
16358                    if (specifiedOrient) {
16359                        return specifiedOrient;
16360                    }
16361                    // If y is range and x is non-range, non-bin Q, y is likely a prebinned field
16362                    if (!x2 && isFieldDef(x) && x.type === QUANTITATIVE && !isBinning(x.bin)) {
16363                        return 'horizontal';
16364                    }
16365                    // If x is range and y is non-range, non-bin Q, x is likely a prebinned field
16366                    if (!y2 && isFieldDef(y) && y.type === QUANTITATIVE && !isBinning(y.bin)) {
16367                        return 'vertical';
16368                    }
16369                }
16370            /* tslint:disable */
16371            case RULE: // intentionally fall through
16372                // return undefined for line segment rule and bar with both axis ranged
16373                if (x2 && y2) {
16374                    return undefined;
16375                }
16376            case AREA: // intentionally fall through
16377                // If there are range for both x and y, y (vertical) has higher precedence.
16378                if (y2) {
16379                    if (isFieldDef(y) && isBinned(y.bin)) {
16380                        return 'horizontal';
16381                    }
16382                    else {
16383                        return 'vertical';
16384                    }
16385                }
16386                else if (x2) {
16387                    if (isFieldDef(x) && isBinned(x.bin)) {
16388                        return 'vertical';
16389                    }
16390                    else {
16391                        return 'horizontal';
16392                    }
16393                }
16394                else if (mark === RULE) {
16395                    if (encoding.x && !encoding.y) {
16396                        return 'vertical';
16397                    }
16398                    else if (encoding.y && !encoding.x) {
16399                        return 'horizontal';
16400                    }
16401                }
16402            case LINE: // intentional fall through
16403            case TICK: // Tick is opposite to bar, line, area and never have ranged mark.
16404                /* tslint:enable */
16405                const xIsContinuous = isFieldDef(encoding.x) && isContinuous(encoding.x);
16406                const yIsContinuous = isFieldDef(encoding.y) && isContinuous(encoding.y);
16407                if (xIsContinuous && !yIsContinuous) {
16408                    return mark !== 'tick' ? 'horizontal' : 'vertical';
16409                }
16410                else if (!xIsContinuous && yIsContinuous) {
16411                    return mark !== 'tick' ? 'vertical' : 'horizontal';
16412                }
16413                else if (xIsContinuous && yIsContinuous) {
16414                    const xDef = encoding.x; // we can cast here since they are surely fieldDef
16415                    const yDef = encoding.y;
16416                    const xIsTemporal = xDef.type === TEMPORAL;
16417                    const yIsTemporal = yDef.type === TEMPORAL;
16418                    // temporal without timeUnit is considered continuous, but better serves as dimension
16419                    if (xIsTemporal && !yIsTemporal) {
16420                        return mark !== 'tick' ? 'vertical' : 'horizontal';
16421                    }
16422                    else if (!xIsTemporal && yIsTemporal) {
16423                        return mark !== 'tick' ? 'horizontal' : 'vertical';
16424                    }
16425                    if (!xDef.aggregate && yDef.aggregate) {
16426                        return mark !== 'tick' ? 'vertical' : 'horizontal';
16427                    }
16428                    else if (xDef.aggregate && !yDef.aggregate) {
16429                        return mark !== 'tick' ? 'horizontal' : 'vertical';
16430                    }
16431                    if (specifiedOrient) {
16432                        // When ambiguous, use user specified one.
16433                        return specifiedOrient;
16434                    }
16435                    return 'vertical';
16436                }
16437                else {
16438                    // Discrete x Discrete case
16439                    if (specifiedOrient) {
16440                        // When ambiguous, use user specified one.
16441                        return specifiedOrient;
16442                    }
16443                    return undefined;
16444                }
16445        }
16446        return 'vertical';
16447    }
16448
16449    /**
16450     * Internal model of Vega-Lite specification for the compiler.
16451     */
16452    class UnitModel extends ModelWithField {
16453        constructor(spec, parent, parentGivenName, parentGivenSize = {}, repeater, config, fit) {
16454            super(spec, parent, parentGivenName, config, repeater, undefined);
16455            this.fit = fit;
16456            this.type = 'unit';
16457            this.specifiedScales = {};
16458            this.specifiedAxes = {};
16459            this.specifiedLegends = {};
16460            this.specifiedProjection = {};
16461            this.selection = {};
16462            this.children = [];
16463            this.initSize(Object.assign({}, parentGivenSize, (spec.width ? { width: spec.width } : {}), (spec.height ? { height: spec.height } : {})));
16464            const mark = isMarkDef(spec.mark) ? spec.mark.type : spec.mark;
16465            const encoding = (this.encoding = normalizeEncoding(replaceRepeaterInEncoding(spec.encoding || {}, repeater), mark));
16466            this.markDef = normalizeMarkDef(spec.mark, encoding, config);
16467            // calculate stack properties
16468            this.stack = stack(mark, encoding, this.config.stack);
16469            this.specifiedScales = this.initScales(mark, encoding);
16470            this.specifiedAxes = this.initAxes(encoding);
16471            this.specifiedLegends = this.initLegend(encoding);
16472            this.specifiedProjection = spec.projection;
16473            // Selections will be initialized upon parse.
16474            this.selection = spec.selection;
16475        }
16476        get hasProjection() {
16477            const { encoding } = this;
16478            const isGeoShapeMark = this.mark === GEOSHAPE;
16479            const hasGeoPosition = encoding && GEOPOSITION_CHANNELS.some(channel => isFieldDef(encoding[channel]));
16480            return isGeoShapeMark || hasGeoPosition;
16481        }
16482        /**
16483         * Return specified Vega-lite scale domain for a particular channel
16484         * @param channel
16485         */
16486        scaleDomain(channel) {
16487            const scale = this.specifiedScales[channel];
16488            return scale ? scale.domain : undefined;
16489        }
16490        axis(channel) {
16491            return this.specifiedAxes[channel];
16492        }
16493        legend(channel) {
16494            return this.specifiedLegends[channel];
16495        }
16496        initScales(mark, encoding) {
16497            return SCALE_CHANNELS.reduce((scales, channel) => {
16498                let fieldDef;
16499                let specifiedScale;
16500                const channelDef = encoding[channel];
16501                if (isFieldDef(channelDef)) {
16502                    fieldDef = channelDef;
16503                    specifiedScale = channelDef.scale;
16504                }
16505                else if (hasConditionalFieldDef(channelDef)) {
16506                    fieldDef = channelDef.condition;
16507                    specifiedScale = channelDef.condition['scale'];
16508                }
16509                if (fieldDef) {
16510                    scales[channel] = specifiedScale || {};
16511                }
16512                return scales;
16513            }, {});
16514        }
16515        initAxes(encoding) {
16516            return [X, Y].reduce((_axis, channel) => {
16517                // Position Axis
16518                // TODO: handle ConditionFieldDef
16519                const channelDef = encoding[channel];
16520                if (isFieldDef(channelDef) ||
16521                    (channel === X && isFieldDef(encoding.x2)) ||
16522                    (channel === Y && isFieldDef(encoding.y2))) {
16523                    const axisSpec = isFieldDef(channelDef) ? channelDef.axis : null;
16524                    if (axisSpec !== null) {
16525                        _axis[channel] = Object.assign({}, axisSpec);
16526                    }
16527                }
16528                return _axis;
16529            }, {});
16530        }
16531        initLegend(encoding) {
16532            return NONPOSITION_SCALE_CHANNELS.reduce((_legend, channel) => {
16533                const channelDef = encoding[channel];
16534                if (channelDef) {
16535                    const legend = isFieldDef(channelDef)
16536                        ? channelDef.legend
16537                        : hasConditionalFieldDef(channelDef)
16538                            ? channelDef.condition['legend']
16539                            : null;
16540                    if (legend !== null && legend !== false) {
16541                        _legend[channel] = Object.assign({}, legend);
16542                    }
16543                }
16544                return _legend;
16545            }, {});
16546        }
16547        parseData() {
16548            this.component.data = parseData(this);
16549        }
16550        parseLayoutSize() {
16551            parseUnitLayoutSize(this);
16552        }
16553        parseSelection() {
16554            this.component.selection = parseUnitSelection(this, this.selection);
16555        }
16556        parseMarkGroup() {
16557            this.component.mark = parseMarkGroup(this);
16558        }
16559        parseAxisAndHeader() {
16560            this.component.axes = parseUnitAxis(this);
16561        }
16562        assembleSelectionTopLevelSignals(signals) {
16563            return assembleTopLevelSignals(this, signals);
16564        }
16565        assembleSelectionSignals() {
16566            return assembleUnitSelectionSignals(this, []);
16567        }
16568        assembleSelectionData(data) {
16569            return assembleUnitSelectionData(this, data);
16570        }
16571        assembleLayout() {
16572            return null;
16573        }
16574        assembleLayoutSignals() {
16575            return assembleLayoutSignals(this);
16576        }
16577        assembleMarks() {
16578            let marks = this.component.mark || [];
16579            // If this unit is part of a layer, selections should augment
16580            // all in concert rather than each unit individually. This
16581            // ensures correct interleaving of clipping and brushed marks.
16582            if (!this.parent || !isLayerModel(this.parent)) {
16583                marks = assembleUnitSelectionMarks(this, marks);
16584            }
16585            return marks.map(this.correctDataNames);
16586        }
16587        assembleLayoutSize() {
16588            return {
16589                width: this.getSizeSignalRef('width'),
16590                height: this.getSizeSignalRef('height')
16591            };
16592        }
16593        getMapping() {
16594            return this.encoding;
16595        }
16596        toSpec(excludeConfig, excludeData) {
16597            const encoding = duplicate(this.encoding);
16598            let spec;
16599            spec = {
16600                mark: this.markDef,
16601                encoding: encoding
16602            };
16603            if (!excludeConfig) {
16604                spec.config = duplicate(this.config);
16605            }
16606            if (!excludeData) {
16607                spec.data = duplicate(this.data);
16608            }
16609            // remove defaults
16610            return spec;
16611        }
16612        get mark() {
16613            return this.markDef.type;
16614        }
16615        channelHasField(channel) {
16616            return channelHasField(this.encoding, channel);
16617        }
16618        fieldDef(channel) {
16619            const channelDef = this.encoding[channel];
16620            return getTypedFieldDef(channelDef);
16621        }
16622    }
16623
16624    class LayerModel extends Model {
16625        constructor(spec, parent, parentGivenName, parentGivenSize, repeater, config, fit) {
16626            super(spec, parent, parentGivenName, config, repeater, spec.resolve);
16627            this.type = 'layer';
16628            const layoutSize = Object.assign({}, parentGivenSize, (spec.width ? { width: spec.width } : {}), (spec.height ? { height: spec.height } : {}));
16629            this.initSize(layoutSize);
16630            this.children = spec.layer.map((layer, i) => {
16631                if (isLayerSpec(layer)) {
16632                    return new LayerModel(layer, this, this.getName('layer_' + i), layoutSize, repeater, config, fit);
16633                }
16634                if (isUnitSpec(layer)) {
16635                    return new UnitModel(layer, this, this.getName('layer_' + i), layoutSize, repeater, config, fit);
16636                }
16637                throw new Error(message.INVALID_SPEC);
16638            });
16639        }
16640        parseData() {
16641            this.component.data = parseData(this);
16642            for (const child of this.children) {
16643                child.parseData();
16644            }
16645        }
16646        parseLayoutSize() {
16647            parseLayerLayoutSize(this);
16648        }
16649        parseSelection() {
16650            // Merge selections up the hierarchy so that they may be referenced
16651            // across unit specs. Persist their definitions within each child
16652            // to assemble signals which remain within output Vega unit groups.
16653            this.component.selection = {};
16654            for (const child of this.children) {
16655                child.parseSelection();
16656                keys(child.component.selection).forEach(key => {
16657                    this.component.selection[key] = child.component.selection[key];
16658                });
16659            }
16660        }
16661        parseMarkGroup() {
16662            for (const child of this.children) {
16663                child.parseMarkGroup();
16664            }
16665        }
16666        parseAxisAndHeader() {
16667            parseLayerAxis(this);
16668        }
16669        assembleSelectionTopLevelSignals(signals) {
16670            return this.children.reduce((sg, child) => child.assembleSelectionTopLevelSignals(sg), signals);
16671        }
16672        // TODO: Support same named selections across children.
16673        assembleSelectionSignals() {
16674            return this.children.reduce((signals, child) => {
16675                return signals.concat(child.assembleSelectionSignals());
16676            }, []);
16677        }
16678        assembleLayoutSignals() {
16679            return this.children.reduce((signals, child) => {
16680                return signals.concat(child.assembleLayoutSignals());
16681            }, assembleLayoutSignals(this));
16682        }
16683        assembleSelectionData(data) {
16684            return this.children.reduce((db, child) => child.assembleSelectionData(db), data);
16685        }
16686        assembleTitle() {
16687            let title = super.assembleTitle();
16688            if (title) {
16689                return title;
16690            }
16691            // If title does not provide layer, look into children
16692            for (const child of this.children) {
16693                title = child.assembleTitle();
16694                if (title) {
16695                    return title;
16696                }
16697            }
16698            return undefined;
16699        }
16700        assembleLayout() {
16701            return null;
16702        }
16703        assembleMarks() {
16704            return assembleLayerSelectionMarks(this, flatten(this.children.map(child => {
16705                return child.assembleMarks();
16706            })));
16707        }
16708        assembleLegends() {
16709            return this.children.reduce((legends, child) => {
16710                return legends.concat(child.assembleLegends());
16711            }, assembleLegends(this));
16712        }
16713    }
16714
16715    class RepeatModel extends BaseConcatModel {
16716        constructor(spec, parent, parentGivenName, repeatValues, config) {
16717            super(spec, parent, parentGivenName, config, repeatValues, spec.resolve);
16718            this.type = 'repeat';
16719            if (spec.resolve && spec.resolve.axis && (spec.resolve.axis.x === 'shared' || spec.resolve.axis.y === 'shared')) {
16720                warn(message.REPEAT_CANNOT_SHARE_AXIS);
16721            }
16722            this.repeat = spec.repeat;
16723            this.children = this._initChildren(spec, this.repeat, repeatValues, config);
16724        }
16725        _initChildren(spec, repeat, repeater, config) {
16726            const children = [];
16727            const row = repeat.row || [repeater ? repeater.row : null];
16728            const column = repeat.column || [repeater ? repeater.column : null];
16729            // cross product
16730            for (const rowField of row) {
16731                for (const columnField of column) {
16732                    const name = (rowField ? '_' + rowField : '') + (columnField ? '_' + columnField : '');
16733                    const childRepeat = {
16734                        row: rowField,
16735                        column: columnField
16736                    };
16737                    children.push(buildModel(spec.spec, this, this.getName('child' + name), undefined, childRepeat, config, false));
16738                }
16739            }
16740            return children;
16741        }
16742        parseLayoutSize() {
16743            parseRepeatLayoutSize(this);
16744        }
16745        assembleDefaultLayout() {
16746            return {
16747                columns: this.repeat && this.repeat.column ? this.repeat.column.length : 1,
16748                bounds: 'full',
16749                align: 'all'
16750            };
16751        }
16752    }
16753
16754    function buildModel(spec, parent, parentGivenName, unitSize, repeater, config, fit) {
16755        if (isFacetSpec(spec)) {
16756            return new FacetModel(spec, parent, parentGivenName, repeater, config);
16757        }
16758        if (isLayerSpec(spec)) {
16759            return new LayerModel(spec, parent, parentGivenName, unitSize, repeater, config, fit);
16760        }
16761        if (isUnitSpec(spec)) {
16762            return new UnitModel(spec, parent, parentGivenName, unitSize, repeater, config, fit);
16763        }
16764        if (isRepeatSpec(spec)) {
16765            return new RepeatModel(spec, parent, parentGivenName, repeater, config);
16766        }
16767        if (isConcatSpec(spec)) {
16768            return new ConcatModel(spec, parent, parentGivenName, repeater, config);
16769        }
16770        throw new Error(message.INVALID_SPEC);
16771    }
16772
16773    /**
16774     * Vega-Lite's main function, for compiling Vega-lite spec into Vega spec.
16775     *
16776     * At a high-level, we make the following transformations in different phases:
16777     *
16778     * Input spec
16779     *     |
16780     *     |  (Normalization)
16781     *     v
16782     * Normalized Spec (Row/Column channels in single-view specs becomes faceted specs, composite marks becomes layered specs.)
16783     *     |
16784     *     |  (Build Model)
16785     *     v
16786     * A model tree of the spec
16787     *     |
16788     *     |  (Parse)
16789     *     v
16790     * A model tree with parsed components (intermediate structure of visualization primitives in a format that can be easily merged)
16791     *     |
16792     *     | (Optimize)
16793     *     v
16794     * A model tree with parsed components with the data component optimized
16795     *     |
16796     *     | (Assemble)
16797     *     v
16798     * Vega spec
16799     */
16800    function compile(inputSpec, opt = {}) {
16801        // 0. Augment opt with default opts
16802        if (opt.logger) {
16803            // set the singleton logger to the provided logger
16804            set(opt.logger);
16805        }
16806        if (opt.fieldTitle) {
16807            // set the singleton field title formatter
16808            setTitleFormatter(opt.fieldTitle);
16809        }
16810        try {
16811            // 1. Initialize config by deep merging default config with the config provided via option and the input spec.
16812            const config = initConfig(mergeDeep({}, opt.config, inputSpec.config));
16813            // 2. Normalize: Convert input spec -> normalized spec
16814            // - Decompose all extended unit specs into composition of unit spec.  For example, a box plot get expanded into multiple layers of bars, ticks, and rules. The shorthand row/column channel is also expanded to a facet spec.
16815            const spec = normalizeTopLevelSpec$$1(inputSpec, config);
16816            // - Normalize autosize to be a autosize properties object.
16817            const autosize = normalizeAutoSize(inputSpec.autosize, config.autosize, isLayerSpec(spec) || isUnitSpec(spec));
16818            // 3. Build Model: normalized spec -> Model (a tree structure)
16819            // This phases instantiates the models with default config by doing a top-down traversal. This allows us to pass properties that child models derive from their parents via their constructors.
16820            // See the abstract `Model` class and its children (UnitModel, LayerModel, FacetModel, RepeatModel, ConcatModel) for different types of models.
16821            const model = buildModel(spec, null, '', undefined, undefined, c
16821onfig, autosize.type === 'fit');
16822            // 4 Parse: Model --> Model with components
16823            // Note that components = intermediate representations that are equivalent to Vega specs.
16824            // We need these intermediate representation because we need to merge many visualization "components" like projections, scales, axes, and legends.
16825            // We will later convert these components into actual Vega specs in the assemble phase.
16826            // In this phase, we do a bottom-up traversal over the whole tree to
16827            // parse for each type of components once (e.g., data, layout, mark, scale).
16828            // By doing bottom-up traversal, we start parsing components of unit specs and
16829            // then merge child components of parent composite specs.
16830            //
16831            // Please see inside model.parse() for order of different components parsed.
16832            model.parse();
16833            // 5. Optimize the dataflow.  This will modify the data component of the model.
16834            optimizeDataflow(model.component.data);
16835            // 6. Assemble: convert model components --> Vega Spec.
16836            return assembleTopLevelModel(model, getTopLevelProperties(inputSpec, config, autosize), inputSpec.datasets, inputSpec.usermeta);
16837        }
16838        finally {
16839            // Reset the singleton logger if a logger is provided
16840            if (opt.logger) {
16841                reset();
16842            }
16843            // Reset the singleton field title formatter if provided
16844            if (opt.fieldTitle) {
16845                resetTitleFormatter();
16846            }
16847        }
16848    }
16849    function getTopLevelProperties(topLevelSpec, config, autosize) {
16850        return Object.assign({ autosize: keys(autosize).length === 1 && autosize.type ? autosize.type : autosize }, extractTopLevelProperties(config), extractTopLevelProperties(topLevelSpec));
16851    }
16852    /*
16853     * Assemble the top-level model.
16854     *
16855     * Note: this couldn't be `model.assemble()` since the top-level model
16856     * needs some special treatment to generate top-level properties.
16857     */
16858    function assembleTopLevelModel(model, topLevelProperties, datasets = {}, usermeta) {
16859        // TODO: change type to become VgSpec
16860        // Config with Vega-Lite only config removed.
16861        const vgConfig = model.config ? stripAndRedirectConfig(model.config) : undefined;
16862        const data = [].concat(model.assembleSelectionData([]), 
16863        // only assemble data in the root
16864        assembleRootData(model.component.data, datasets));
16865        const projections = model.assembleProjections();
16866        const title$$1 = model.assembleTitle();
16867        const style = model.assembleGroupStyle();
16868        let layoutSignals = model.assembleLayoutSignals();
16869        // move width and height signals with values to top level
16870        layoutSignals = layoutSignals.filter(signal => {
16871            if ((signal.name === 'width' || signal.name === 'height') && signal.value !== undefined) {
16872                topLevelProperties[signal.name] = +signal.value;
16873                return false;
16874            }
16875            return true;
16876        });
16877        const output = Object.assign({ $schema: 'https://vega.github.io/schema/vega/v4.json' }, (model.description ? { description: model.description } : {}), topLevelProperties, (title$$1 ? { title: title$$1 } : {}), (style ? { style } : {}), { data: data }, (projections.length > 0 ? { projections: projections } : {}), model.assembleGroup([...layoutSignals, ...model.assembleSelectionTopLevelSignals([])]), (vgConfig ? { config: vgConfig } : {}), (usermeta ? { usermeta } : {}));
16878        return {
16879            spec: output
16880            // TODO: add warning / errors here
16881        };
16882    }
16883
16884    /**
16885     * Modifies spec extracting transformations from encoding and moving them to the transforms array
16886     */
16887    function extractTransforms(spec, config) {
16888        if (isFacetSpec(spec) || isRepeatSpec(spec)) {
16889            return extractTransformsSingle(spec, config);
16890        }
16891        if (isLayerSpec(spec)) {
16892            return extractTransformsLayered(spec, config);
16893        }
16894        if (isUnitSpec(spec)) {
16895            return extractTransformsUnit(spec, config);
16896        }
16897        if (isVConcatSpec(spec)) {
16898            return extractTransformsVConcat(spec, config);
16899        }
16900        if (isHConcatSpec(spec)) {
16901            return extractTransformsHConcat(spec, config);
16902        }
16903        throw new Error(message.INVALID_SPEC);
16904    }
16905    function extractTransformsUnit(spec, config) {
16906        if (spec.encoding) {
16907            const { encoding: oldEncoding, transform: oldTransforms } = spec, rest = __rest(spec, ["encoding", "transform"]);
16908            const { bins, timeUnits, aggregate, groupby, encoding: newEncoding } = extractTransformsFromEncoding(oldEncoding, config);
16909            return Object.assign({ transform: [
16910                    ...(oldTransforms ? oldTransforms : []),
16911                    ...bins,
16912                    ...timeUnits,
16913                    ...(!aggregate.length ? [] : [{ aggregate, groupby }])
16914                ] }, rest, { encoding: newEncoding });
16915        }
16916        else {
16917            return spec;
16918        }
16919    }
16920    function extractTransformsSingle(spec, config) {
16921        const { spec: subspec } = spec, rest = __rest(spec, ["spec"]);
16922        return Object.assign({}, rest, { spec: extractTransforms(subspec, config) });
16923    }
16924    function extractTransformsLayered(spec, config) {
16925        const { layer } = spec, rest = __rest(spec, ["layer"]);
16926        return Object.assign({}, rest, { layer: layer.map(subspec => {
16927                return extractTransforms(subspec, config);
16928            }) });
16929    }
16930    function extractTransformsVConcat(spec, config) {
16931        const { vconcat } = spec, rest = __rest(spec, ["vconcat"]);
16932        return Object.assign({}, rest, { vconcat: vconcat.map(subspec => {
16933                return extractTransforms(subspec, config);
16934            }) });
16935    }
16936    function extractTransformsHConcat(spec, config) {
16937        const { hconcat } = spec, rest = __rest(spec, ["hconcat"]);
16938        return Object.assign({}, rest, { hconcat: hconcat.map(subspec => {
16939                return extractTransforms(subspec, config);
16940            }) });
16941    }
16942
16943    const version$1 = pkg.version;
16944
16945    exports.aggregate = aggregate;
16946    exports.axis = axis;
16947    exports.bin = bin$1;
16948    exports.channel = channel;
16949    exports.compositeMark = index;
16950    exports.config = config;
16951    exports.data = data;
16952    exports.datetime = datetime;
16953    exports.encoding = encoding;
16954    exports.fieldDef = fielddef;
16955    exports.header = header;
16956    exports.legend = legend;
16957    exports.mark = mark;
16958    exports.scale = scale;
16959    exports.sort = sort;
16960    exports.spec = index$1;
16961    exports.stack = stack$1;
16962    exports.timeUnit = timeunit;
16963    exports.transform = transform;
16964    exports.type = type;
16965    exports.util = util;
16966    exports.validate = validate;
16967    exports.version = version$1;
16968    exports.compile = compile;
16969    exports.extractTransforms = extractTransforms;
16970
16971    Object.defineProperty(exports, '__esModule', { value: true });
16972
16973}));
16974//# sourceMappingURL=vega-lite.js.map

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.