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.