From 034069bf6ff1e64aa38e61679da37187927f05ed Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Sun, 28 Jul 2024 14:05:28 -0400 Subject: [PATCH 01/24] chore: update deps --- package.json | 36 +- src/compile/data/bin.ts | 2 +- src/compile/data/debug.ts | 2 +- src/compile/legend/encode.ts | 2 +- src/compile/mark/encode/position-rect.ts | 2 +- src/compile/mark/encode/valueref.ts | 2 +- src/normalize/index.ts | 2 +- yarn.lock | 3069 ++++++++-------------- 8 files changed, 1185 insertions(+), 1932 deletions(-) diff --git a/package.json b/package.json index 9d859a036f..baf558a481 100644 --- a/package.json +++ b/package.json @@ -79,8 +79,8 @@ "url": "https://github.com/vega/vega-lite/issues" }, "devDependencies": { - "@babel/core": "^7.24.7", - "@babel/preset-env": "^7.24.7", + "@babel/core": "^7.24.9", + "@babel/preset-env": "^7.25.0", "@babel/preset-typescript": "^7.24.7", "@release-it/conventional-changelog": "^8.0.1", "@rollup/plugin-alias": "^5.1.0", @@ -92,37 +92,37 @@ "@types/d3": "^7.4.3", "@types/jest": "^29.5.12", "@types/pako": "^2.0.3", - "@typescript-eslint/eslint-plugin": "^7.13.0", - "@typescript-eslint/parser": "^7.13.0", - "ajv": "^8.16.0", + "@typescript-eslint/eslint-plugin": "^7.17.0", + "@typescript-eslint/parser": "^7.17.0", + "ajv": "^8.17.1", "ajv-formats": "^2.1.1", "cheerio": "^1.0.0-rc.12", - "conventional-changelog-cli": "^4.1.0", + "conventional-changelog-cli": "^5.0.0", "d3": "^7.9.0", "del-cli": "^5.1.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-jest": "^27.9.0", - "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-prettier": "^5.2.1", "fast-json-stable-stringify": "~2.1.0", - "highlight.js": "^11.9.0", + "highlight.js": "^11.10.0", "jest": "^29.7.0", "jest-dev-server": "^10.0.0", "mkdirp": "^3.0.1", "pako": "^2.1.0", - "prettier": "^3.3.2", + "prettier": "^3.3.3", "puppeteer": "^15.0.0", - "release-it": "17.2.1", - "rollup": "^4.18.0", + "release-it": "17.6.0", + "rollup": "^4.19.1", "rollup-plugin-bundle-size": "^1.0.3", "serve": "^14.2.3", - "terser": "^5.31.1", - "ts-jest": "^29.1.4", - "ts-json-schema-generator": "^1.5.0", - "typescript": "~5.4.5", - "vega-cli": "^5.28.0", + "terser": "^5.31.3", + "ts-jest": "^29.2.3", + "ts-json-schema-generator": "^2.3.0", + "typescript": "~5.5.4", + "vega-cli": "^5.30.0", "vega-datasets": "^2.8.1", - "vega-embed": "^6.25.0", + "vega-embed": "^6.26.0", "vega-tooltip": "^0.34.0", "yaml-front-matter": "^4.1.1" }, @@ -130,7 +130,7 @@ "json-stringify-pretty-compact": "~3.0.0", "tslib": "~2.6.3", "vega-event-selector": "~3.0.1", - "vega-expression": "~5.1.0", + "vega-expression": "~5.1.1", "vega-util": "~1.17.2", "yargs": "~17.7.2" }, diff --git a/src/compile/data/bin.ts b/src/compile/data/bin.ts index 1c43602e77..49cb293d8f 100644 --- a/src/compile/data/bin.ts +++ b/src/compile/data/bin.ts @@ -17,7 +17,7 @@ function rangeFormula(model: ModelWithField, fieldDef: TypedFieldDef, ch // read format from axis or legend, if there is no format then use config.numberFormat const guide = isUnitModel(model) - ? model.axis(channel as PositionChannel) ?? model.legend(channel as NonPositionScaleChannel) ?? {} + ? (model.axis(channel as PositionChannel) ?? model.legend(channel as NonPositionScaleChannel) ?? {}) : {}; const startField = vgField(fieldDef, {expr: 'datum'}); diff --git a/src/compile/data/debug.ts b/src/compile/data/debug.ts index fb2e16112c..fbba4f0902 100644 --- a/src/compile/data/debug.ts +++ b/src/compile/data/debug.ts @@ -83,7 +83,7 @@ export function dotString(roots: readonly DataFlowNode[]) { label: getLabel(node), hash: node instanceof SourceNode - ? node.data.url ?? node.data.name ?? node.debugName + ? (node.data.url ?? node.data.name ?? node.debugName) : String(node.hash()).replace(/"/g, '') }; diff --git a/src/compile/legend/encode.ts b/src/compile/legend/encode.ts index 334e0b0381..405c1851cb 100644 --- a/src/compile/legend/encode.ts +++ b/src/compile/legend/encode.ts @@ -58,7 +58,7 @@ export function symbols( const symbolFillColor = legendCmpt.get('symbolFillColor') ?? config.legend.symbolFillColor; const symbolStrokeColor = legendCmpt.get('symbolStrokeColor') ?? config.legend.symbolStrokeColor; - const opacity = symbolOpacity === undefined ? getMaxValue(encoding.opacity) ?? markDef.opacity : undefined; + const opacity = symbolOpacity === undefined ? (getMaxValue(encoding.opacity) ?? markDef.opacity) : undefined; if (out.fill) { // for fill legend, we don't want any fill in symbol diff --git a/src/compile/mark/encode/position-rect.ts b/src/compile/mark/encode/position-rect.ts index 505bb12e08..7b037cbff9 100644 --- a/src/compile/mark/encode/position-rect.ts +++ b/src/compile/mark/encode/position-rect.ts @@ -313,7 +313,7 @@ function rectBinPosition({ const axis = model.component.axes[channel]?.[0]; const axisTranslate = axis?.get('translate') ?? 0.5; // vega default is 0.5 - const spacing = isXorY(channel) ? getMarkPropOrConfig('binSpacing', markDef, config) ?? 0 : 0; + const spacing = isXorY(channel) ? (getMarkPropOrConfig('binSpacing', markDef, config) ?? 0) : 0; const channel2 = getSecondaryRangeChannel(channel); const vgChannel = getVgPositionChannel(channel); diff --git a/src/compile/mark/encode/valueref.ts b/src/compile/mark/encode/valueref.ts index 52854dac48..39f9995401 100644 --- a/src/compile/mark/encode/valueref.ts +++ b/src/compile/mark/encode/valueref.ts @@ -249,7 +249,7 @@ export function midPoint({ { offset, // For band, to get mid point, need to offset by half of the band - band: scaleType === 'band' ? bandPosition ?? channelDef.bandPosition ?? 0.5 : undefined + band: scaleType === 'band' ? (bandPosition ?? channelDef.bandPosition ?? 0.5) : undefined } ); } else if (isValueDef(channelDef)) { diff --git a/src/normalize/index.ts b/src/normalize/index.ts index 439c59a6e8..43c35238c9 100644 --- a/src/normalize/index.ts +++ b/src/normalize/index.ts @@ -59,7 +59,7 @@ function normalizeGenericSpec( } function _normalizeAutoSize(autosize: AutosizeType | AutoSizeParams) { - return isString(autosize) ? {type: autosize} : autosize ?? {}; + return isString(autosize) ? {type: autosize} : (autosize ?? {}); } /** diff --git a/yarn.lock b/yarn.lock index 9232ee8b3d..b39a6fcdec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,11 +2,6 @@ # yarn lockfile v1 -"@aashutoshrathi/word-wrap@^1.2.3": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" - integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== - "@ampproject/remapping@^2.2.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" @@ -15,15 +10,7 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.21.4", "@babel/code-frame@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.6.tgz#ab88da19344445c3d8889af2216606d3329f3ef2" - integrity sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA== - dependencies: - "@babel/highlight" "^7.24.6" - picocolors "^1.0.0" - -"@babel/code-frame@^7.24.7": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.21.4", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== @@ -31,85 +18,42 @@ "@babel/highlight" "^7.24.7" picocolors "^1.0.0" -"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.6.tgz#b3600217688cabb26e25f8e467019e66d71b7ae2" - integrity sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ== - -"@babel/compat-data@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.7.tgz#d23bbea508c3883ba8251fb4164982c36ea577ed" - integrity sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw== - -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.6.tgz#8650e0e4b03589ebe886c4e4a60398db0a7ec787" - integrity sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.24.6" - "@babel/generator" "^7.24.6" - "@babel/helper-compilation-targets" "^7.24.6" - "@babel/helper-module-transforms" "^7.24.6" - "@babel/helpers" "^7.24.6" - "@babel/parser" "^7.24.6" - "@babel/template" "^7.24.6" - "@babel/traverse" "^7.24.6" - "@babel/types" "^7.24.6" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.24.8", "@babel/compat-data@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.0.tgz#6b226a5da3a686db3c30519750e071dce292ad95" + integrity sha512-P4fwKI2mjEb3ZU5cnMJzvRsRKGBUcs8jvxIoRmr6ufAY9Xk2Bz7JubRTTivkw55c7WQJfTECeqYVa+HZ0FzREg== -"@babel/core@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.7.tgz#b676450141e0b52a3d43bc91da86aa608f950ac4" - integrity sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g== +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.24.9": + version "7.24.9" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.9.tgz#dc07c9d307162c97fa9484ea997ade65841c7c82" + integrity sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.24.7" - "@babel/helper-compilation-targets" "^7.24.7" - "@babel/helper-module-transforms" "^7.24.7" - "@babel/helpers" "^7.24.7" - "@babel/parser" "^7.24.7" + "@babel/generator" "^7.24.9" + "@babel/helper-compilation-targets" "^7.24.8" + "@babel/helper-module-transforms" "^7.24.9" + "@babel/helpers" "^7.24.8" + "@babel/parser" "^7.24.8" "@babel/template" "^7.24.7" - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" + "@babel/traverse" "^7.24.8" + "@babel/types" "^7.24.9" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.24.6", "@babel/generator@^7.7.2": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.6.tgz#dfac82a228582a9d30c959fe50ad28951d4737a7" - integrity sha512-S7m4eNa6YAPJRHmKsLHIDJhNAGNKoWNiWefz1MBbpnt8g9lvMDl1hir4P9bo/57bQEmuwEhnRU/AMWsD0G/Fbg== +"@babel/generator@^7.24.9", "@babel/generator@^7.25.0", "@babel/generator@^7.7.2": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.0.tgz#f858ddfa984350bc3d3b7f125073c9af6988f18e" + integrity sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw== dependencies: - "@babel/types" "^7.24.6" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^2.5.1" - -"@babel/generator@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.7.tgz#1654d01de20ad66b4b4d99c135471bc654c55e6d" - integrity sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA== - dependencies: - "@babel/types" "^7.24.7" + "@babel/types" "^7.25.0" "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" -"@babel/helper-annotate-as-pure@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" - integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== - dependencies: - "@babel/types" "^7.22.5" - "@babel/helper-annotate-as-pure@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz#5373c7bc8366b12a033b4be1ac13a206c6656aab" @@ -125,65 +69,43 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" -"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.6.tgz#4a51d681f7680043d38e212715e2a7b1ad29cb51" - integrity sha512-VZQ57UsDGlX/5fFA7GkVPplZhHsVc+vuErWgdOiysI9Ksnw0Pbbd6pnPiR/mmJyKHgyIW0c7KT32gmhiF+cirg== +"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.24.7", "@babel/helper-compilation-targets@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz#b607c3161cd9d1744977d4f97139572fe778c271" + integrity sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw== dependencies: - "@babel/compat-data" "^7.24.6" - "@babel/helper-validator-option" "^7.24.6" - browserslist "^4.22.2" + "@babel/compat-data" "^7.24.8" + "@babel/helper-validator-option" "^7.24.8" + browserslist "^4.23.1" lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-compilation-targets@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz#4eb6c4a80d6ffeac25ab8cd9a21b5dfa48d503a9" - integrity sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg== - dependencies: - "@babel/compat-data" "^7.24.7" - "@babel/helper-validator-option" "^7.24.7" - browserslist "^4.22.2" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-create-class-features-plugin@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.7.tgz#2eaed36b3a1c11c53bdf80d53838b293c52f5b3b" - integrity sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg== +"@babel/helper-create-class-features-plugin@^7.24.7", "@babel/helper-create-class-features-plugin@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.0.tgz#a109bf9c3d58dfed83aaf42e85633c89f43a6253" + integrity sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ== dependencies: "@babel/helper-annotate-as-pure" "^7.24.7" - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-function-name" "^7.24.7" - "@babel/helper-member-expression-to-functions" "^7.24.7" + "@babel/helper-member-expression-to-functions" "^7.24.8" "@babel/helper-optimise-call-expression" "^7.24.7" - "@babel/helper-replace-supers" "^7.24.7" + "@babel/helper-replace-supers" "^7.25.0" "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" - "@babel/helper-split-export-declaration" "^7.24.7" + "@babel/traverse" "^7.25.0" semver "^6.3.1" -"@babel/helper-create-regexp-features-plugin@^7.18.6": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1" - integrity sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - regexpu-core "^5.3.1" - semver "^6.3.1" - -"@babel/helper-create-regexp-features-plugin@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz#be4f435a80dc2b053c76eeb4b7d16dd22cfc89da" - integrity sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA== +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.24.7", "@babel/helper-create-regexp-features-plugin@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.0.tgz#17afe5d23b3a833a90f0fab9c2ae69fea192de5c" + integrity sha512-q0T+dknZS+L5LDazIP+02gEZITG5unzvb6yIjcmj5i0eFrs5ToBV2m2JGH4EsE/gtP8ygEGLGApBgRIZkTm7zg== dependencies: "@babel/helper-annotate-as-pure" "^7.24.7" regexpu-core "^5.3.1" semver "^6.3.1" -"@babel/helper-define-polyfill-provider@^0.6.1": - version "0.6.1" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz#fadc63f0c2ff3c8d02ed905dcea747c5b0fb74fd" - integrity sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA== +"@babel/helper-define-polyfill-provider@^0.6.1", "@babel/helper-define-polyfill-provider@^0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz#18594f789c3594acb24cfdb4a7f7b7d2e8bd912d" + integrity sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ== dependencies: "@babel/helper-compilation-targets" "^7.22.6" "@babel/helper-plugin-utils" "^7.22.5" @@ -191,71 +113,15 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" -"@babel/helper-environment-visitor@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz#ac7ad5517821641550f6698dd5468f8cef78620d" - integrity sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g== - -"@babel/helper-environment-visitor@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz#4b31ba9551d1f90781ba83491dd59cf9b269f7d9" - integrity sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ== - dependencies: - "@babel/types" "^7.24.7" - -"@babel/helper-function-name@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.24.6.tgz#cebdd063386fdb95d511d84b117e51fc68fec0c8" - integrity sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w== +"@babel/helper-member-expression-to-functions@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz#6155e079c913357d24a4c20480db7c712a5c3fb6" + integrity sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA== dependencies: - "@babel/template" "^7.24.6" - "@babel/types" "^7.24.6" + "@babel/traverse" "^7.24.8" + "@babel/types" "^7.24.8" -"@babel/helper-function-name@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz#75f1e1725742f39ac6584ee0b16d94513da38dd2" - integrity sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA== - dependencies: - "@babel/template" "^7.24.7" - "@babel/types" "^7.24.7" - -"@babel/helper-hoist-variables@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.6.tgz#8a7ece8c26756826b6ffcdd0e3cf65de275af7f9" - integrity sha512-SF/EMrC3OD7dSta1bLJIlrsVxwtd0UpjRJqLno6125epQMJ/kyFmpTT4pbvPbdQHzCHg+biQ7Syo8lnDtbR+uA== - dependencies: - "@babel/types" "^7.24.6" - -"@babel/helper-hoist-variables@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz#b4ede1cde2fd89436397f30dc9376ee06b0f25ee" - integrity sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ== - dependencies: - "@babel/types" "^7.24.7" - -"@babel/helper-member-expression-to-functions@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.7.tgz#67613d068615a70e4ed5101099affc7a41c5225f" - integrity sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w== - dependencies: - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" - -"@babel/helper-module-imports@^7.18.6": - version "7.24.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz#6ac476e6d168c7c23ff3ba3cf4f7841d46ac8128" - integrity sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg== - dependencies: - "@babel/types" "^7.24.0" - -"@babel/helper-module-imports@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz#65e54ffceed6a268dc4ce11f0433b82cfff57852" - integrity sha512-a26dmxFJBF62rRO9mmpgrfTLsAuyHk4e1hKTUkD/fcMfynt8gvEKwQPQDVxWhca8dHoDck+55DFt42zV0QMw5g== - dependencies: - "@babel/types" "^7.24.6" - -"@babel/helper-module-imports@^7.24.7": +"@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz#f2f980392de5b84c3328fc71d38bd81bbb83042b" integrity sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA== @@ -263,27 +129,15 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" -"@babel/helper-module-transforms@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.6.tgz#22346ed9df44ce84dee850d7433c5b73fab1fe4e" - integrity sha512-Y/YMPm83mV2HJTbX1Qh2sjgjqcacvOlhbzdCCsSlblOKjSYmQqEbO6rUniWQyRo9ncyfjT8hnUjlG06RXDEmcA== - dependencies: - "@babel/helper-environment-visitor" "^7.24.6" - "@babel/helper-module-imports" "^7.24.6" - "@babel/helper-simple-access" "^7.24.6" - "@babel/helper-split-export-declaration" "^7.24.6" - "@babel/helper-validator-identifier" "^7.24.6" - -"@babel/helper-module-transforms@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz#31b6c9a2930679498db65b685b1698bfd6c7daf8" - integrity sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ== +"@babel/helper-module-transforms@^7.24.7", "@babel/helper-module-transforms@^7.24.8", "@babel/helper-module-transforms@^7.24.9", "@babel/helper-module-transforms@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.0.tgz#3ffc23c473a2769a7e40d3274495bd559fdd2ecc" + integrity sha512-bIkOa2ZJYn7FHnepzr5iX9Kmz8FjIz4UKzJ9zhX3dnYuVW0xul9RuR3skBfoLu+FPTQw90EHW9rJsSZhyLQ3fQ== dependencies: - "@babel/helper-environment-visitor" "^7.24.7" "@babel/helper-module-imports" "^7.24.7" "@babel/helper-simple-access" "^7.24.7" - "@babel/helper-split-export-declaration" "^7.24.7" "@babel/helper-validator-identifier" "^7.24.7" + "@babel/traverse" "^7.25.0" "@babel/helper-optimise-call-expression@^7.24.7": version "7.24.7" @@ -292,40 +146,28 @@ dependencies: "@babel/types" "^7.24.7" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.6.tgz#fa02a32410a15a6e8f8185bcbf608f10528d2a24" - integrity sha512-MZG/JcWfxybKwsA9N9PmtF2lOSFSEMVCpIRrbxccZFLJPrJciJdG/UhSh5W96GEteJI2ARqm5UAHxISwRDLSNg== - -"@babel/helper-plugin-utils@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz#98c84fe6fe3d0d3ae7bfc3a5e166a46844feb2a0" - integrity sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.24.8", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878" + integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg== -"@babel/helper-remap-async-to-generator@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.7.tgz#b3f0f203628522713849d49403f1a414468be4c7" - integrity sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA== +"@babel/helper-remap-async-to-generator@^7.24.7", "@babel/helper-remap-async-to-generator@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz#d2f0fbba059a42d68e5e378feaf181ef6055365e" + integrity sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw== dependencies: "@babel/helper-annotate-as-pure" "^7.24.7" - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-wrap-function" "^7.24.7" + "@babel/helper-wrap-function" "^7.25.0" + "@babel/traverse" "^7.25.0" -"@babel/helper-replace-supers@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz#f933b7eed81a1c0265740edc91491ce51250f765" - integrity sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg== +"@babel/helper-replace-supers@^7.24.7", "@babel/helper-replace-supers@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz#ff44deac1c9f619523fe2ca1fd650773792000a9" + integrity sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg== dependencies: - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-member-expression-to-functions" "^7.24.7" + "@babel/helper-member-expression-to-functions" "^7.24.8" "@babel/helper-optimise-call-expression" "^7.24.7" - -"@babel/helper-simple-access@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.6.tgz#1d6e04d468bba4fc963b4906f6dac6286cfedff1" - integrity sha512-nZzcMMD4ZhmB35MOOzQuiGO5RzL6tJbsT37Zx8M5L/i9KSrukGXWTjLe1knIbb/RmxoJE9GON9soq0c0VEMM5g== - dependencies: - "@babel/types" "^7.24.6" + "@babel/traverse" "^7.25.0" "@babel/helper-simple-access@^7.24.7": version "7.24.7" @@ -343,85 +185,37 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" -"@babel/helper-split-export-declaration@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz#e830068f7ba8861c53b7421c284da30ae656d7a3" - integrity sha512-CvLSkwXGWnYlF9+J3iZUvwgAxKiYzK3BWuo+mLzD/MDGOZDj7Gq8+hqaOkMxmJwmlv0iu86uH5fdADd9Hxkymw== - dependencies: - "@babel/types" "^7.24.6" - -"@babel/helper-split-export-declaration@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz#83949436890e07fa3d6873c61a96e3bbf692d856" - integrity sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA== - dependencies: - "@babel/types" "^7.24.7" - -"@babel/helper-string-parser@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.6.tgz#28583c28b15f2a3339cfafafeaad42f9a0e828df" - integrity sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q== - -"@babel/helper-string-parser@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz#4d2d0f14820ede3b9807ea5fc36dfc8cd7da07f2" - integrity sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg== - -"@babel/helper-validator-identifier@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz#08bb6612b11bdec78f3feed3db196da682454a5e" - integrity sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw== +"@babel/helper-string-parser@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" + integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== "@babel/helper-validator-identifier@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== -"@babel/helper-validator-option@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.6.tgz#59d8e81c40b7d9109ab7e74457393442177f460a" - integrity sha512-Jktc8KkF3zIkePb48QO+IapbXlSapOW9S+ogZZkcO6bABgYAxtZcjZ/O005111YLf+j4M84uEgwYoidDkXbCkQ== +"@babel/helper-validator-option@^7.24.7", "@babel/helper-validator-option@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d" + integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q== -"@babel/helper-validator-option@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz#24c3bb77c7a425d1742eec8fb433b5a1b38e62f6" - integrity sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw== - -"@babel/helper-wrap-function@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.24.7.tgz#52d893af7e42edca7c6d2c6764549826336aae1f" - integrity sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw== +"@babel/helper-wrap-function@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz#dab12f0f593d6ca48c0062c28bcfb14ebe812f81" + integrity sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ== dependencies: - "@babel/helper-function-name" "^7.24.7" - "@babel/template" "^7.24.7" - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" + "@babel/template" "^7.25.0" + "@babel/traverse" "^7.25.0" + "@babel/types" "^7.25.0" -"@babel/helpers@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.6.tgz#cd124245299e494bd4e00edda0e4ea3545c2c176" - integrity sha512-V2PI+NqnyFu1i0GyTd/O/cTpxzQCYioSkUIRmgo7gFEHKKCg5w46+r/A6WeUR1+P3TeQ49dspGPNd/E3n9AnnA== - dependencies: - "@babel/template" "^7.24.6" - "@babel/types" "^7.24.6" - -"@babel/helpers@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.7.tgz#aa2ccda29f62185acb5d42fb4a3a1b1082107416" - integrity sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg== +"@babel/helpers@^7.24.8": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.0.tgz#e69beb7841cb93a6505531ede34f34e6a073650a" + integrity sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw== dependencies: - "@babel/template" "^7.24.7" - "@babel/types" "^7.24.7" - -"@babel/highlight@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.6.tgz#6d610c1ebd2c6e061cade0153bf69b0590b7b3df" - integrity sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ== - dependencies: - "@babel/helper-validator-identifier" "^7.24.6" - chalk "^2.4.2" - js-tokens "^4.0.0" - picocolors "^1.0.0" + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.0" "@babel/highlight@^7.24.7": version "7.24.7" @@ -433,30 +227,32 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.6.tgz#5e030f440c3c6c78d195528c3b688b101a365328" - integrity sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.24.8", "@babel/parser@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.0.tgz#9fdc9237504d797b6e7b8f66e78ea7f570d256ad" + integrity sha512-CzdIU9jdP0dg7HdyB+bHvDJGagUv+qtzZt5rYCWwW6tITNqV9odjp6Qu41gkG0ca5UfdDUWrKkiAnHHdGRnOrA== -"@babel/parser@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.7.tgz#9a5226f92f0c5c8ead550b750f5608e766c8ce85" - integrity sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw== +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.0.tgz#328275f22d809b962978d998c6eba22a233ac8aa" + integrity sha512-dG0aApncVQwAUJa8tP1VHTnmU67BeIQvKafd3raEx315H54FfkZSz3B/TT+33ZQAjatGJA79gZqTtqL5QZUKXw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/traverse" "^7.25.0" -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.7.tgz#fd059fd27b184ea2b4c7e646868a9a381bbc3055" - integrity sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ== +"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz#cd0c583e01369ef51676bdb3d7b603e17d2b3f73" + integrity sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA== dependencies: - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.7.tgz#468096ca44bbcbe8fcc570574e12eb1950e18107" - integrity sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg== +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.0.tgz#749bde80356b295390954643de7635e0dffabe73" + integrity sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.24.7": version "7.24.7" @@ -467,13 +263,13 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" "@babel/plugin-transform-optional-chaining" "^7.24.7" -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.7.tgz#71b21bb0286d5810e63a1538aa901c58e87375ec" - integrity sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg== +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz#3a82a70e7cb7294ad2559465ebcb871dfbf078fb" + integrity sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw== dependencies: - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/traverse" "^7.25.0" "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": version "7.21.0-placeholder-for-preset-env.2" @@ -550,20 +346,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.24.7": +"@babel/plugin-syntax-jsx@^7.24.7", "@babel/plugin-syntax-jsx@^7.7.2": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz#39a1fa4a7e3d3d7f34e2acc6be585b718d30e02d" integrity sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ== dependencies: "@babel/helper-plugin-utils" "^7.24.7" -"@babel/plugin-syntax-jsx@^7.7.2": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz#3f6ca04b8c841811dbc3c5c5f837934e0d626c10" - integrity sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" @@ -620,20 +409,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.24.7": +"@babel/plugin-syntax-typescript@^7.24.7", "@babel/plugin-syntax-typescript@^7.7.2": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz#58d458271b4d3b6bb27ee6ac9525acbb259bad1c" integrity sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA== dependencies: "@babel/helper-plugin-utils" "^7.24.7" -"@babel/plugin-syntax-typescript@^7.7.2": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz#b3bcc51f396d15f3591683f90239de143c076844" - integrity sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/plugin-syntax-unicode-sets-regex@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" @@ -649,15 +431,15 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" -"@babel/plugin-transform-async-generator-functions@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.7.tgz#7330a5c50e05181ca52351b8fd01642000c96cfd" - integrity sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g== +"@babel/plugin-transform-async-generator-functions@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.0.tgz#b785cf35d73437f6276b1e30439a57a50747bddf" + integrity sha512-uaIi2FdqzjpAMvVqvB51S42oC2JEVgh0LDsGfZVDysWE8LrJtQC2jvKmOqEYThKyB7bDEb7BP1GYWDm7tABA0Q== dependencies: - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-remap-async-to-generator" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-remap-async-to-generator" "^7.25.0" "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/traverse" "^7.25.0" "@babel/plugin-transform-async-to-generator@^7.24.7": version "7.24.7" @@ -675,12 +457,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" -"@babel/plugin-transform-block-scoping@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.7.tgz#42063e4deb850c7bd7c55e626bf4e7ab48e6ce02" - integrity sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ== +"@babel/plugin-transform-block-scoping@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz#23a6ed92e6b006d26b1869b1c91d1b917c2ea2ac" + integrity sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" "@babel/plugin-transform-class-properties@^7.24.7": version "7.24.7" @@ -699,18 +481,16 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-class-static-block" "^7.14.5" -"@babel/plugin-transform-classes@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.7.tgz#4ae6ef43a12492134138c1e45913f7c46c41b4bf" - integrity sha512-CFbbBigp8ln4FU6Bpy6g7sE8B/WmCmzvivzUC6xDAdWVsjYTXijpuuGJmYkAaoWAzcItGKT3IOAbxRItZ5HTjw== +"@babel/plugin-transform-classes@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.0.tgz#63122366527d88e0ef61b612554fe3f8c793991e" + integrity sha512-xyi6qjr/fYU304fiRwFbekzkqVJZ6A7hOjWZd+89FVcBqPV3S9Wuozz82xdpLspckeaafntbzglaW4pqpzvtSw== dependencies: "@babel/helper-annotate-as-pure" "^7.24.7" - "@babel/helper-compilation-targets" "^7.24.7" - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-function-name" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-replace-supers" "^7.24.7" - "@babel/helper-split-export-declaration" "^7.24.7" + "@babel/helper-compilation-targets" "^7.24.8" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-replace-supers" "^7.25.0" + "@babel/traverse" "^7.25.0" globals "^11.1.0" "@babel/plugin-transform-computed-properties@^7.24.7": @@ -721,12 +501,12 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/template" "^7.24.7" -"@babel/plugin-transform-destructuring@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.7.tgz#a097f25292defb6e6cc16d6333a4cfc1e3c72d9e" - integrity sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw== +"@babel/plugin-transform-destructuring@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz#c828e814dbe42a2718a838c2a2e16a408e055550" + integrity sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" "@babel/plugin-transform-dotall-regex@^7.24.7": version "7.24.7" @@ -743,6 +523,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz#809af7e3339466b49c034c683964ee8afb3e2604" + integrity sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.0" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/plugin-transform-dynamic-import@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz#4d8b95e3bae2b037673091aa09cd33fecd6419f4" @@ -775,14 +563,14 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" -"@babel/plugin-transform-function-name@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.7.tgz#6d8601fbffe665c894440ab4470bc721dd9131d6" - integrity sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w== +"@babel/plugin-transform-function-name@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.0.tgz#d17890029ceefb45189ea203b404a496263a8412" + integrity sha512-CQmfSnK14eYu82fu6GlCwRciHB7mp7oLN+DeyGDDwUr9cMwuSVviJKPXw/YcRYZdB1TdlLJWHHwXwnwD1WnCmQ== dependencies: - "@babel/helper-compilation-targets" "^7.24.7" - "@babel/helper-function-name" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-compilation-targets" "^7.24.8" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/traverse" "^7.25.0" "@babel/plugin-transform-json-strings@^7.24.7": version "7.24.7" @@ -822,24 +610,24 @@ "@babel/helper-module-transforms" "^7.24.7" "@babel/helper-plugin-utils" "^7.24.7" -"@babel/plugin-transform-modules-commonjs@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.7.tgz#9fd5f7fdadee9085886b183f1ad13d1ab260f4ab" - integrity sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ== +"@babel/plugin-transform-modules-commonjs@^7.24.7", "@babel/plugin-transform-modules-commonjs@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz#ab6421e564b717cb475d6fff70ae7f103536ea3c" + integrity sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA== dependencies: - "@babel/helper-module-transforms" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-module-transforms" "^7.24.8" + "@babel/helper-plugin-utils" "^7.24.8" "@babel/helper-simple-access" "^7.24.7" -"@babel/plugin-transform-modules-systemjs@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.7.tgz#f8012316c5098f6e8dee6ecd58e2bc6f003d0ce7" - integrity sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw== +"@babel/plugin-transform-modules-systemjs@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz#8f46cdc5f9e5af74f3bd019485a6cbe59685ea33" + integrity sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw== dependencies: - "@babel/helper-hoist-variables" "^7.24.7" - "@babel/helper-module-transforms" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-module-transforms" "^7.25.0" + "@babel/helper-plugin-utils" "^7.24.8" "@babel/helper-validator-identifier" "^7.24.7" + "@babel/traverse" "^7.25.0" "@babel/plugin-transform-modules-umd@^7.24.7": version "7.24.7" @@ -906,12 +694,12 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-transform-optional-chaining@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.7.tgz#b8f6848a80cf2da98a8a204429bec04756c6d454" - integrity sha512-tK+0N9yd4j+x/4hxF3F0e0fu/VdcxU18y5SevtyM/PCFlQvXbR0Zmlo2eBrKtVipGNFzpq56o8WsIIKcJFUCRQ== +"@babel/plugin-transform-optional-chaining@^7.24.7", "@babel/plugin-transform-optional-chaining@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz#bb02a67b60ff0406085c13d104c99a835cdf365d" + integrity sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" "@babel/plugin-syntax-optional-chaining" "^7.8.3" @@ -991,21 +779,22 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" -"@babel/plugin-transform-typeof-symbol@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.7.tgz#f074be466580d47d6e6b27473a840c9f9ca08fb0" - integrity sha512-VtR8hDy7YLB7+Pet9IarXjg/zgCMSF+1mNS/EQEiEaUPoFXCVsHG64SIxcaaI2zJgRiv+YmgaQESUfWAdbjzgg== +"@babel/plugin-transform-typeof-symbol@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz#383dab37fb073f5bfe6e60c654caac309f92ba1c" + integrity sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" "@babel/plugin-transform-typescript@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.7.tgz#b006b3e0094bf0813d505e0c5485679eeaf4a881" - integrity sha512-iLD3UNkgx2n/HrjBesVbYX6j0yqn/sJktvbtKKgcaLIQ4bTTQ8obAypc1VpyHPD2y4Phh9zHOaAt8e/L14wCpw== + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.0.tgz#56f47fb87b86a97caa9c7770920a1967d40ac86e" + integrity sha512-LZicxFzHIw+Sa3pzgMgSz6gdpsdkfiMObHUzhSIrwKF0+/rP/nuR49u79pSS+zIFJ1FeGeqQD2Dq4QGFbOVvSw== dependencies: "@babel/helper-annotate-as-pure" "^7.24.7" - "@babel/helper-create-class-features-plugin" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-create-class-features-plugin" "^7.25.0" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" "@babel/plugin-syntax-typescript" "^7.24.7" "@babel/plugin-transform-unicode-escapes@^7.24.7": @@ -1039,19 +828,20 @@ "@babel/helper-create-regexp-features-plugin" "^7.24.7" "@babel/helper-plugin-utils" "^7.24.7" -"@babel/preset-env@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.7.tgz#ff067b4e30ba4a72f225f12f123173e77b987f37" - integrity sha512-1YZNsc+y6cTvWlDHidMBsQZrZfEFjRIo/BZCT906PMdzOyXtSLTgqGdrpcuTDCXyd11Am5uQULtDIcCfnTc8fQ== - dependencies: - "@babel/compat-data" "^7.24.7" - "@babel/helper-compilation-targets" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-validator-option" "^7.24.7" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.24.7" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.24.7" +"@babel/preset-env@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.25.0.tgz#3fe92e470311e91478129efda101816c680f0479" + integrity sha512-vYAA8PrCOeZfG4D87hmw1KJ1BPubghXP1e2MacRFwECGNKL76dkA38JEwYllbvQCpf/kLxsTtir0b8MtxKoVCw== + dependencies: + "@babel/compat-data" "^7.25.0" + "@babel/helper-compilation-targets" "^7.24.8" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-validator-option" "^7.24.8" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.25.0" + "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.25.0" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.25.0" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.24.7" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.24.7" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.25.0" "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-class-properties" "^7.12.13" @@ -1072,29 +862,30 @@ "@babel/plugin-syntax-top-level-await" "^7.14.5" "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" "@babel/plugin-transform-arrow-functions" "^7.24.7" - "@babel/plugin-transform-async-generator-functions" "^7.24.7" + "@babel/plugin-transform-async-generator-functions" "^7.25.0" "@babel/plugin-transform-async-to-generator" "^7.24.7" "@babel/plugin-transform-block-scoped-functions" "^7.24.7" - "@babel/plugin-transform-block-scoping" "^7.24.7" + "@babel/plugin-transform-block-scoping" "^7.25.0" "@babel/plugin-transform-class-properties" "^7.24.7" "@babel/plugin-transform-class-static-block" "^7.24.7" - "@babel/plugin-transform-classes" "^7.24.7" + "@babel/plugin-transform-classes" "^7.25.0" "@babel/plugin-transform-computed-properties" "^7.24.7" - "@babel/plugin-transform-destructuring" "^7.24.7" + "@babel/plugin-transform-destructuring" "^7.24.8" "@babel/plugin-transform-dotall-regex" "^7.24.7" "@babel/plugin-transform-duplicate-keys" "^7.24.7" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.25.0" "@babel/plugin-transform-dynamic-import" "^7.24.7" "@babel/plugin-transform-exponentiation-operator" "^7.24.7" "@babel/plugin-transform-export-namespace-from" "^7.24.7" "@babel/plugin-transform-for-of" "^7.24.7" - "@babel/plugin-transform-function-name" "^7.24.7" + "@babel/plugin-transform-function-name" "^7.25.0" "@babel/plugin-transform-json-strings" "^7.24.7" "@babel/plugin-transform-literals" "^7.24.7" "@babel/plugin-transform-logical-assignment-operators" "^7.24.7" "@babel/plugin-transform-member-expression-literals" "^7.24.7" "@babel/plugin-transform-modules-amd" "^7.24.7" - "@babel/plugin-transform-modules-commonjs" "^7.24.7" - "@babel/plugin-transform-modules-systemjs" "^7.24.7" + "@babel/plugin-transform-modules-commonjs" "^7.24.8" + "@babel/plugin-transform-modules-systemjs" "^7.25.0" "@babel/plugin-transform-modules-umd" "^7.24.7" "@babel/plugin-transform-named-capturing-groups-regex" "^7.24.7" "@babel/plugin-transform-new-target" "^7.24.7" @@ -1103,7 +894,7 @@ "@babel/plugin-transform-object-rest-spread" "^7.24.7" "@babel/plugin-transform-object-super" "^7.24.7" "@babel/plugin-transform-optional-catch-binding" "^7.24.7" - "@babel/plugin-transform-optional-chaining" "^7.24.7" + "@babel/plugin-transform-optional-chaining" "^7.24.8" "@babel/plugin-transform-parameters" "^7.24.7" "@babel/plugin-transform-private-methods" "^7.24.7" "@babel/plugin-transform-private-property-in-object" "^7.24.7" @@ -1114,7 +905,7 @@ "@babel/plugin-transform-spread" "^7.24.7" "@babel/plugin-transform-sticky-regex" "^7.24.7" "@babel/plugin-transform-template-literals" "^7.24.7" - "@babel/plugin-transform-typeof-symbol" "^7.24.7" + "@babel/plugin-transform-typeof-symbol" "^7.24.8" "@babel/plugin-transform-unicode-escapes" "^7.24.7" "@babel/plugin-transform-unicode-property-regex" "^7.24.7" "@babel/plugin-transform-unicode-regex" "^7.24.7" @@ -1123,7 +914,7 @@ babel-plugin-polyfill-corejs2 "^0.4.10" babel-plugin-polyfill-corejs3 "^0.10.4" babel-plugin-polyfill-regenerator "^0.6.1" - core-js-compat "^3.31.0" + core-js-compat "^3.37.1" semver "^6.3.1" "@babel/preset-modules@0.1.6-no-external-plugins": @@ -1152,77 +943,40 @@ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime@^7.8.4": - version "7.24.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd" - integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA== + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb" + integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw== dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.24.6", "@babel/template@^7.3.3": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.6.tgz#048c347b2787a6072b24c723664c8d02b67a44f9" - integrity sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw== - dependencies: - "@babel/code-frame" "^7.24.6" - "@babel/parser" "^7.24.6" - "@babel/types" "^7.24.6" - -"@babel/template@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.7.tgz#02efcee317d0609d2c07117cb70ef8fb17ab7315" - integrity sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig== +"@babel/template@^7.24.7", "@babel/template@^7.25.0", "@babel/template@^7.3.3": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a" + integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q== dependencies: "@babel/code-frame" "^7.24.7" - "@babel/parser" "^7.24.7" - "@babel/types" "^7.24.7" - -"@babel/traverse@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.6.tgz#0941ec50cdeaeacad0911eb67ae227a4f8424edc" - integrity sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw== - dependencies: - "@babel/code-frame" "^7.24.6" - "@babel/generator" "^7.24.6" - "@babel/helper-environment-visitor" "^7.24.6" - "@babel/helper-function-name" "^7.24.6" - "@babel/helper-hoist-variables" "^7.24.6" - "@babel/helper-split-export-declaration" "^7.24.6" - "@babel/parser" "^7.24.6" - "@babel/types" "^7.24.6" - debug "^4.3.1" - globals "^11.1.0" + "@babel/parser" "^7.25.0" + "@babel/types" "^7.25.0" -"@babel/traverse@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.7.tgz#de2b900163fa741721ba382163fe46a936c40cf5" - integrity sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA== +"@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8", "@babel/traverse@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.0.tgz#e8533c0a57ed97921d1e5b20dd3db63d3efaebf5" + integrity sha512-ubALThHQy4GCf6mbb+5ZRNmLLCI7bJ3f8Q6LHBSRlSKSWj5a7dSUzJBLv3VuIhFrFPgjF4IzPF567YG/HSCdZA== dependencies: "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.24.7" - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-function-name" "^7.24.7" - "@babel/helper-hoist-variables" "^7.24.7" - "@babel/helper-split-export-declaration" "^7.24.7" - "@babel/parser" "^7.24.7" - "@babel/types" "^7.24.7" + "@babel/generator" "^7.25.0" + "@babel/parser" "^7.25.0" + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.0" debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.24.0", "@babel/types@^7.24.6", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.6.tgz#ba4e1f59870c10dc2fa95a274ac4feec23b21912" - integrity sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ== - dependencies: - "@babel/helper-string-parser" "^7.24.6" - "@babel/helper-validator-identifier" "^7.24.6" - to-fast-properties "^2.0.0" - -"@babel/types@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.7.tgz#6027fe12bc1aa724cd32ab113fb7f1988f1f66f2" - integrity sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.24.9", "@babel/types@^7.25.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.0.tgz#e6e3656c581f28da8452ed4f69e38008ec0ba41b" + integrity sha512-LcnxQSsd9aXOIgmmSpvZ/1yo46ra2ESYyqLcryaBZOghxy5qqOBjvCWP5JfkI8yl9rlxRgdLTTMCQQRcN2hdCg== dependencies: - "@babel/helper-string-parser" "^7.24.7" + "@babel/helper-string-parser" "^7.24.8" "@babel/helper-validator-identifier" "^7.24.7" to-fast-properties "^2.0.0" @@ -1231,6 +985,14 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@conventional-changelog/git-client@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@conventional-changelog/git-client/-/git-client-1.0.1.tgz#143be2777ba389c3c14f83fa19b7cab6a49a503b" + integrity sha512-PJEqBwAleffCMETaVm/fUgHldzBE35JFk3/9LL6NUA5EXa3qednu+UT6M7E5iBu3zIQZCULYIiZ90fBYHt6xUw== + dependencies: + "@types/semver" "^7.5.5" + semver "^7.5.2" + "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -1239,9 +1001,9 @@ eslint-visitor-keys "^3.3.0" "@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": - version "4.10.0" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" - integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== + version "4.11.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" + integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== "@eslint/eslintrc@^2.1.4": version "2.1.4" @@ -1304,10 +1066,10 @@ resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c" integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg== -"@inquirer/figures@^1.0.1": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.3.tgz#1227cc980f88e6d6ab85abadbf164f5038041edd" - integrity sha512-ErXXzENMH5pJt5/ssXV0DfWUZqly8nGzf0UcBV9xTnP+KyffE2mqyxIMBrZ8ijQck2nU0TQm40EQB53YreyWHw== +"@inquirer/figures@^1.0.3": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.5.tgz#57f9a996d64d3e3345d2a3ca04d36912e94f8790" + integrity sha512-79hP/VWdZ2UVc9bFGJnoQ/lQMpL74mGgzSYX1xUqCVk7/v73vJCMw1VuyWN1jGkZ9B3z7THAbySqGbCNefcjfA== "@isaacs/cliui@^8.0.2": version "8.0.2" @@ -1557,9 +1319,9 @@ "@jridgewell/trace-mapping" "^0.3.25" "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" @@ -1569,13 +1331,6 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@ljharb/through@^2.3.13": - version "2.3.13" - resolved "https://registry.yarnpkg.com/@ljharb/through/-/through-2.3.13.tgz#b7e4766e0b65aa82e529be945ab078de79874edc" - integrity sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ== - dependencies: - call-bind "^1.0.7" - "@mapbox/node-pre-gyp@^1.0.0": version "1.0.11" resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa" @@ -1647,34 +1402,29 @@ "@octokit/types" "^13.0.0" universal-user-agent "^6.0.0" -"@octokit/openapi-types@^20.0.0": - version "20.0.0" - resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-20.0.0.tgz#9ec2daa0090eeb865ee147636e0c00f73790c6e5" - integrity sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA== +"@octokit/openapi-types@^22.2.0": + version "22.2.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-22.2.0.tgz#75aa7dcd440821d99def6a60b5f014207ae4968e" + integrity sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg== -"@octokit/openapi-types@^22.0.1": - version "22.0.1" - resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-22.0.1.tgz#41f5b1c4dad3e547906ea9258837fcbea7cc72b4" - integrity sha512-1yN5m1IMNXthoBDUXFF97N1gHop04B3H8ws7wtOr8GgRyDO1gKALjwMHARNBoMBiB/2vEe/vxstrApcJZzQbnQ== - -"@octokit/plugin-paginate-rest@^9.1.5": - version "9.2.1" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.1.tgz#2e2a2f0f52c9a4b1da1a3aa17dabe3c459b9e401" - integrity sha512-wfGhE/TAkXZRLjksFXuDZdmGnJQHvtU/joFQdweXUgzo1XwvBCD4o4+75NtFfjfLK5IwLf9vHTfSiU3sLRYpRw== +"@octokit/plugin-paginate-rest@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.1.tgz#fe92d04b49f134165d6fbb716e765c2f313ad364" + integrity sha512-ryqobs26cLtM1kQxqeZui4v8FeznirUsksiA+RYemMPJ7Micju0WSkv50dBksTuZks9O5cg4wp+t8fZ/cLY56g== dependencies: - "@octokit/types" "^12.6.0" + "@octokit/types" "^13.5.0" "@octokit/plugin-request-log@^4.0.0": version "4.0.1" resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-4.0.1.tgz#98a3ca96e0b107380664708111864cb96551f958" integrity sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA== -"@octokit/plugin-rest-endpoint-methods@^10.2.0": - version "10.4.1" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz#41ba478a558b9f554793075b2e20cd2ef973be17" - integrity sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg== +"@octokit/plugin-rest-endpoint-methods@13.2.2": + version "13.2.2" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.2.tgz#af8e5dd2cddfea576f92ffaf9cb84659f302a638" + integrity sha512-EI7kXWidkt3Xlok5uN43suK99VWqc8OaIMktY9d9+RNKl69juoTyxmLoWPIZgJYzi41qj/9zU7G/ljnNOJ5AFA== dependencies: - "@octokit/types" "^12.6.0" + "@octokit/types" "^13.5.0" "@octokit/request-error@^5.1.0": version "5.1.0" @@ -1695,29 +1445,22 @@ "@octokit/types" "^13.1.0" universal-user-agent "^6.0.0" -"@octokit/rest@20.1.0": - version "20.1.0" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-20.1.0.tgz#78310528f4849a69b44b15ccd27f99c7e737bb7d" - integrity sha512-STVO3itHQLrp80lvcYB2UIKoeil5Ctsgd2s1AM+du3HqZIR35ZH7WE9HLwUOLXH0myA0y3AGNPo8gZtcgIbw0g== +"@octokit/rest@20.1.1": + version "20.1.1" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-20.1.1.tgz#ec775864f53fb42037a954b9a40d4f5275b3dc95" + integrity sha512-MB4AYDsM5jhIHro/dq4ix1iWTLGToIGk6cWF5L6vanFaMble5jTX/UBQyiv05HsWnwUtY8JrfHy2LWfKwihqMw== dependencies: "@octokit/core" "^5.0.2" - "@octokit/plugin-paginate-rest" "^9.1.5" + "@octokit/plugin-paginate-rest" "11.3.1" "@octokit/plugin-request-log" "^4.0.0" - "@octokit/plugin-rest-endpoint-methods" "^10.2.0" - -"@octokit/types@^12.6.0": - version "12.6.0" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-12.6.0.tgz#8100fb9eeedfe083aae66473bd97b15b62aedcb2" - integrity sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw== - dependencies: - "@octokit/openapi-types" "^20.0.0" + "@octokit/plugin-rest-endpoint-methods" "13.2.2" -"@octokit/types@^13.0.0", "@octokit/types@^13.1.0": - version "13.4.0" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.4.0.tgz#b9f6865a6fc491387352d7f327e1f030fa7be1cd" - integrity sha512-WlMegy3lPXYWASe3k9Jslc5a0anrYAYMWtsFrxBTdQjS70hvLH6C+PGvHbOsgy3RA3LouGJoU/vAt4KarecQLQ== +"@octokit/types@^13.0.0", "@octokit/types@^13.1.0", "@octokit/types@^13.5.0": + version "13.5.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.5.0.tgz#4796e56b7b267ebc7c921dcec262b3d5bfb18883" + integrity sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ== dependencies: - "@octokit/openapi-types" "^22.0.1" + "@octokit/openapi-types" "^22.2.0" "@pkgjs/parseargs@^0.11.0": version "0.11.0" @@ -1824,85 +1567,85 @@ estree-walker "^2.0.2" picomatch "^2.3.1" -"@rollup/rollup-android-arm-eabi@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz#bbd0e616b2078cd2d68afc9824d1fadb2f2ffd27" - integrity sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ== - -"@rollup/rollup-android-arm64@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz#97255ef6384c5f73f4800c0de91f5f6518e21203" - integrity sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA== - -"@rollup/rollup-darwin-arm64@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz#b6dd74e117510dfe94541646067b0545b42ff096" - integrity sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w== - -"@rollup/rollup-darwin-x64@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz#e07d76de1cec987673e7f3d48ccb8e106d42c05c" - integrity sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA== - -"@rollup/rollup-linux-arm-gnueabihf@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz#9f1a6d218b560c9d75185af4b8bb42f9f24736b8" - integrity sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA== - -"@rollup/rollup-linux-arm-musleabihf@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz#53618b92e6ffb642c7b620e6e528446511330549" - integrity sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A== - -"@rollup/rollup-linux-arm64-gnu@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz#99a7ba5e719d4f053761a698f7b52291cefba577" - integrity sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw== - -"@rollup/rollup-linux-arm64-musl@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz#f53db99a45d9bc00ce94db8a35efa7c3c144a58c" - integrity sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ== - -"@rollup/rollup-linux-powerpc64le-gnu@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz#cbb0837408fe081ce3435cf3730e090febafc9bf" - integrity sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA== - -"@rollup/rollup-linux-riscv64-gnu@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz#8ed09c1d1262ada4c38d791a28ae0fea28b80cc9" - integrity sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg== - -"@rollup/rollup-linux-s390x-gnu@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz#938138d3c8e0c96f022252a28441dcfb17afd7ec" - integrity sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg== - -"@rollup/rollup-linux-x64-gnu@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz#1a7481137a54740bee1ded4ae5752450f155d942" - integrity sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w== - -"@rollup/rollup-linux-x64-musl@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz#f1186afc601ac4f4fc25fac4ca15ecbee3a1874d" - integrity sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg== - -"@rollup/rollup-win32-arm64-msvc@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz#ed6603e93636a96203c6915be4117245c1bd2daf" - integrity sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA== - -"@rollup/rollup-win32-ia32-msvc@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz#14e0b404b1c25ebe6157a15edb9c46959ba74c54" - integrity sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg== - -"@rollup/rollup-win32-x64-msvc@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz#5d694d345ce36b6ecf657349e03eb87297e68da4" - integrity sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g== +"@rollup/rollup-android-arm-eabi@4.19.1": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.1.tgz#7746deb85e4a8fb54fbfda8ac5c102692f102476" + integrity sha512-XzqSg714++M+FXhHfXpS1tDnNZNpgxxuGZWlRG/jSj+VEPmZ0yg6jV4E0AL3uyBKxO8mO3xtOsP5mQ+XLfrlww== + +"@rollup/rollup-android-arm64@4.19.1": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.1.tgz#93de4d867709d3313794723b5afd91e1e174f906" + integrity sha512-thFUbkHteM20BGShD6P08aungq4irbIZKUNbG70LN8RkO7YztcGPiKTTGZS7Kw+x5h8hOXs0i4OaHwFxlpQN6A== + +"@rollup/rollup-darwin-arm64@4.19.1": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.1.tgz#e41e6a81673260ab196e0f59462b9940a6ac03cd" + integrity sha512-8o6eqeFZzVLia2hKPUZk4jdE3zW7LCcZr+MD18tXkgBBid3lssGVAYuox8x6YHoEPDdDa9ixTaStcmx88lio5Q== + +"@rollup/rollup-darwin-x64@4.19.1": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.1.tgz#2b0a0aef6e8c5317d494cfc9076d7a16b099bdcb" + integrity sha512-4T42heKsnbjkn7ovYiAdDVRRWZLU9Kmhdt6HafZxFcUdpjlBlxj4wDrt1yFWLk7G4+E+8p2C9tcmSu0KA6auGA== + +"@rollup/rollup-linux-arm-gnueabihf@4.19.1": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.1.tgz#e22319deb5367384ef315e66bc6de80d2bf2b3ae" + integrity sha512-MXg1xp+e5GhZ3Vit1gGEyoC+dyQUBy2JgVQ+3hUrD9wZMkUw/ywgkpK7oZgnB6kPpGrxJ41clkPPnsknuD6M2Q== + +"@rollup/rollup-linux-arm-musleabihf@4.19.1": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.1.tgz#d5dd68f5d7ae21b345a5c87208c94e5c813f54b8" + integrity sha512-DZNLwIY4ftPSRVkJEaxYkq7u2zel7aah57HESuNkUnz+3bZHxwkCUkrfS2IWC1sxK6F2QNIR0Qr/YXw7nkF3Pw== + +"@rollup/rollup-linux-arm64-gnu@4.19.1": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.1.tgz#1703d3a418d33f8f025acaf93f39ca1efcd5b645" + integrity sha512-C7evongnjyxdngSDRRSQv5GvyfISizgtk9RM+z2biV5kY6S/NF/wta7K+DanmktC5DkuaJQgoKGf7KUDmA7RUw== + +"@rollup/rollup-linux-arm64-musl@4.19.1": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.1.tgz#3f59c2c6e60f75ce8b1090bd841c555e3bb01f0e" + integrity sha512-89tFWqxfxLLHkAthAcrTs9etAoBFRduNfWdl2xUs/yLV+7XDrJ5yuXMHptNqf1Zw0UCA3cAutkAiAokYCkaPtw== + +"@rollup/rollup-linux-powerpc64le-gnu@4.19.1": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.1.tgz#3f99a0921596a6f539121a312df29af52a205f15" + integrity sha512-PromGeV50sq+YfaisG8W3fd+Cl6mnOOiNv2qKKqKCpiiEke2KiKVyDqG/Mb9GWKbYMHj5a01fq/qlUR28PFhCQ== + +"@rollup/rollup-linux-riscv64-gnu@4.19.1": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.1.tgz#c08fb3e629d50d2eac31329347cfc559a1cf81d1" + integrity sha512-/1BmHYh+iz0cNCP0oHCuF8CSiNj0JOGf0jRlSo3L/FAyZyG2rGBuKpkZVH9YF+x58r1jgWxvm1aRg3DHrLDt6A== + +"@rollup/rollup-linux-s390x-gnu@4.19.1": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.1.tgz#173722cd745779d730d4b24d21386185e0e12de8" + integrity sha512-0cYP5rGkQWRZKy9/HtsWVStLXzCF3cCBTRI+qRL8Z+wkYlqN7zrSYm6FuY5Kd5ysS5aH0q5lVgb/WbG4jqXN1Q== + +"@rollup/rollup-linux-x64-gnu@4.19.1": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.1.tgz#0af2b6541ab0f4954d2c4f96bcdc7947420dd28c" + integrity sha512-XUXeI9eM8rMP8aGvii/aOOiMvTs7xlCosq9xCjcqI9+5hBxtjDpD+7Abm1ZhVIFE1J2h2VIg0t2DX/gjespC2Q== + +"@rollup/rollup-linux-x64-musl@4.19.1": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.1.tgz#f973f9552744764b221128f7c3629222216ace69" + integrity sha512-V7cBw/cKXMfEVhpSvVZhC+iGifD6U1zJ4tbibjjN+Xi3blSXaj/rJynAkCFFQfoG6VZrAiP7uGVzL440Q6Me2Q== + +"@rollup/rollup-win32-arm64-msvc@4.19.1": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.1.tgz#21ac5ed84d914bc31821fec3dd909f7257cfb17b" + integrity sha512-88brja2vldW/76jWATlBqHEoGjJLRnP0WOEKAUbMcXaAZnemNhlAHSyj4jIwMoP2T750LE9lblvD4e2jXleZsA== + +"@rollup/rollup-win32-ia32-msvc@4.19.1": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.1.tgz#0cfe740063b35dcd5a62c4e243226631a846ce11" + integrity sha512-LdxxcqRVSXi6k6JUrTah1rHuaupoeuiv38du8Mt4r4IPer3kwlTo+RuvfE8KzZ/tL6BhaPlzJ3835i6CxrFIRQ== + +"@rollup/rollup-win32-x64-msvc@4.19.1": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.1.tgz#5f2c40d3f1b53ede80fb4e6964f840c0f8936832" + integrity sha512-2bIrL28PcK3YCqD9anGxDxamxdiJAxA+l7fWIwM5o8UqNy1t3d1NdAweO2XhA0KTDJ5aH1FsuiT5+7VhtHliXg== "@sideway/address@^4.1.5": version "4.1.5" @@ -1989,9 +1732,9 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.5.tgz#7b7502be0aa80cc4ef22978846b983edaafcd4dd" - integrity sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ== + version "7.20.6" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7" + integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg== dependencies: "@babel/types" "^7.20.7" @@ -2067,9 +1810,9 @@ "@types/d3-dsv" "*" "@types/d3-force@*": - version "3.0.9" - resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-3.0.9.tgz#dd96ccefba4386fe4ff36b8e4ee4e120c21fcf29" - integrity sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA== + version "3.0.10" + resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-3.0.10.tgz#6dc8fc6e1f35704f3b057090beeeb7ac674bff1a" + integrity sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw== "@types/d3-format@*": version "3.0.4" @@ -2270,13 +2013,13 @@ integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag== "@types/node@*": - version "20.12.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.7.tgz#04080362fa3dd6c5822061aa3124f5c152cff384" - integrity sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg== + version "22.0.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.0.0.tgz#04862a2a71e62264426083abe1e27e87cac05a30" + integrity sha512-VT7KSYudcPOzP5Q0wfbowyNLaVR8QWUdw+088uFWwfvpY6uCWaXpqV6ieLAu9WBcnTa7H4Z5RLK8I5t2FuOcqw== dependencies: - undici-types "~5.26.4" + undici-types "~6.11.1" -"@types/normalize-package-data@^2.4.0", "@types/normalize-package-data@^2.4.1": +"@types/normalize-package-data@^2.4.0", "@types/normalize-package-data@^2.4.1", "@types/normalize-package-data@^2.4.3": version "2.4.4" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA== @@ -2291,7 +2034,7 @@ resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975" integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q== -"@types/semver@^7.3.12": +"@types/semver@^7.3.12", "@types/semver@^7.5.5": version "7.5.8" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== @@ -2320,30 +2063,30 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@^7.13.0": - version "7.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.15.0.tgz#8eaf396ac2992d2b8f874b68eb3fcd6b179cb7f3" - integrity sha512-uiNHpyjZtFrLwLDpHnzaDlP3Tt6sGMqTCiqmxaN4n4RP0EfYZDODJyddiFDF44Hjwxr5xAcaYxVKm9QKQFJFLA== +"@typescript-eslint/eslint-plugin@^7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.17.0.tgz#c8ed1af1ad2928ede5cdd207f7e3090499e1f77b" + integrity sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A== dependencies: "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "7.15.0" - "@typescript-eslint/type-utils" "7.15.0" - "@typescript-eslint/utils" "7.15.0" - "@typescript-eslint/visitor-keys" "7.15.0" + "@typescript-eslint/scope-manager" "7.17.0" + "@typescript-eslint/type-utils" "7.17.0" + "@typescript-eslint/utils" "7.17.0" + "@typescript-eslint/visitor-keys" "7.17.0" graphemer "^1.4.0" ignore "^5.3.1" natural-compare "^1.4.0" ts-api-utils "^1.3.0" -"@typescript-eslint/parser@^7.13.0": - version "7.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.15.0.tgz#f4a536e5fc6a1c05c82c4d263a2bfad2da235c80" - integrity sha512-k9fYuQNnypLFcqORNClRykkGOMOj+pV6V91R4GO/l1FDGwpqmSwoOQrOHo3cGaH63e+D3ZiCAOsuS/D2c99j/A== +"@typescript-eslint/parser@^7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.17.0.tgz#be8e32c159190cd40a305a2121220eadea5a88e7" + integrity sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A== dependencies: - "@typescript-eslint/scope-manager" "7.15.0" - "@typescript-eslint/types" "7.15.0" - "@typescript-eslint/typescript-estree" "7.15.0" - "@typescript-eslint/visitor-keys" "7.15.0" + "@typescript-eslint/scope-manager" "7.17.0" + "@typescript-eslint/types" "7.17.0" + "@typescript-eslint/typescript-estree" "7.17.0" + "@typescript-eslint/visitor-keys" "7.17.0" debug "^4.3.4" "@typescript-eslint/scope-manager@5.62.0": @@ -2354,21 +2097,21 @@ "@typescript-eslint/types" "5.62.0" "@typescript-eslint/visitor-keys" "5.62.0" -"@typescript-eslint/scope-manager@7.15.0": - version "7.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.15.0.tgz#201b34b0720be8b1447df17b963941bf044999b2" - integrity sha512-Q/1yrF/XbxOTvttNVPihxh1b9fxamjEoz2Os/Pe38OHwxC24CyCqXxGTOdpb4lt6HYtqw9HetA/Rf6gDGaMPlw== +"@typescript-eslint/scope-manager@7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.17.0.tgz#e072d0f914662a7bfd6c058165e3c2b35ea26b9d" + integrity sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA== dependencies: - "@typescript-eslint/types" "7.15.0" - "@typescript-eslint/visitor-keys" "7.15.0" + "@typescript-eslint/types" "7.17.0" + "@typescript-eslint/visitor-keys" "7.17.0" -"@typescript-eslint/type-utils@7.15.0": - version "7.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.15.0.tgz#5b83c904c6de91802fb399305a50a56d10472c39" - integrity sha512-SkgriaeV6PDvpA6253PDVep0qCqgbO1IOBiycjnXsszNTVQe5flN5wR5jiczoEoDEnAqYFSFFc9al9BSGVltkg== +"@typescript-eslint/type-utils@7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.17.0.tgz#c5da78feb134c9c9978cbe89e2b1a589ed22091a" + integrity sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA== dependencies: - "@typescript-eslint/typescript-estree" "7.15.0" - "@typescript-eslint/utils" "7.15.0" + "@typescript-eslint/typescript-estree" "7.17.0" + "@typescript-eslint/utils" "7.17.0" debug "^4.3.4" ts-api-utils "^1.3.0" @@ -2377,10 +2120,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== -"@typescript-eslint/types@7.15.0": - version "7.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.15.0.tgz#fb894373a6e3882cbb37671ffddce44f934f62fc" - integrity sha512-aV1+B1+ySXbQH0pLK0rx66I3IkiZNidYobyfn0WFsdGhSXw+P3YOqeTq5GED458SfB24tg+ux3S+9g118hjlTw== +"@typescript-eslint/types@7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.17.0.tgz#7ce8185bdf06bc3494e73d143dbf3293111b9cff" + integrity sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A== "@typescript-eslint/typescript-estree@5.62.0": version "5.62.0" @@ -2395,13 +2138,13 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@7.15.0": - version "7.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.15.0.tgz#e323bfa3966e1485b638ce751f219fc1f31eba37" - integrity sha512-gjyB/rHAopL/XxfmYThQbXbzRMGhZzGw6KpcMbfe8Q3nNQKStpxnUKeXb0KiN/fFDR42Z43szs6rY7eHk0zdGQ== +"@typescript-eslint/typescript-estree@7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.17.0.tgz#dcab3fea4c07482329dd6107d3c6480e228e4130" + integrity sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw== dependencies: - "@typescript-eslint/types" "7.15.0" - "@typescript-eslint/visitor-keys" "7.15.0" + "@typescript-eslint/types" "7.17.0" + "@typescript-eslint/visitor-keys" "7.17.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" @@ -2409,15 +2152,15 @@ semver "^7.6.0" ts-api-utils "^1.3.0" -"@typescript-eslint/utils@7.15.0": - version "7.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.15.0.tgz#9e6253c4599b6e7da2fb64ba3f549c73eb8c1960" - integrity sha512-hfDMDqaqOqsUVGiEPSMLR/AjTSCsmJwjpKkYQRo1FNbmW4tBwBspYDwO9eh7sKSTwMQgBw9/T4DHudPaqshRWA== +"@typescript-eslint/utils@7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.17.0.tgz#815cd85b9001845d41b699b0ce4f92d6dfb84902" + integrity sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw== dependencies: "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "7.15.0" - "@typescript-eslint/types" "7.15.0" - "@typescript-eslint/typescript-estree" "7.15.0" + "@typescript-eslint/scope-manager" "7.17.0" + "@typescript-eslint/types" "7.17.0" + "@typescript-eslint/typescript-estree" "7.17.0" "@typescript-eslint/utils@^5.10.0": version "5.62.0" @@ -2441,12 +2184,12 @@ "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" -"@typescript-eslint/visitor-keys@7.15.0": - version "7.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.15.0.tgz#1da0726201a859343fe6a05742a7c1792fff5b66" - integrity sha512-Hqgy/ETgpt2L5xueA/zHHIl4fJI2O4XUE9l4+OIfbJIRSnTJb/QscncdqqZzofQegIJugRIF57OJea1khw2SDw== +"@typescript-eslint/visitor-keys@7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.17.0.tgz#680465c734be30969e564b4647f38d6cdf49bfb0" + integrity sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A== dependencies: - "@typescript-eslint/types" "7.15.0" + "@typescript-eslint/types" "7.17.0" eslint-visitor-keys "^3.4.3" "@ungap/structured-clone@^1.2.0": @@ -2486,9 +2229,9 @@ acorn-jsx@^5.3.2: integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn@^8.8.2, acorn@^8.9.0: - version "8.11.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" - integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== add-stream@^1.0.0: version "1.0.0" @@ -2544,25 +2287,15 @@ ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0: - version "8.13.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.13.0.tgz#a3939eaec9fb80d217ddf0c3376948c023f28c91" - integrity sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA== +ajv@^8.0.0, ajv@^8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== dependencies: fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" - uri-js "^4.4.1" - -ajv@^8.16.0: - version "8.16.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.16.0.tgz#22e2a92b94f005f7e0f9c9d39652ef0b8f6f0cb4" - integrity sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw== - dependencies: - fast-deep-equal "^3.1.3" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.4.1" ansi-align@^3.0.1: version "3.0.1" @@ -2665,14 +2398,6 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -array-buffer-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" - integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== - dependencies: - call-bind "^1.0.5" - is-array-buffer "^3.0.4" - array-ify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" @@ -2683,32 +2408,6 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array.prototype.map@^1.0.5: - version "1.0.7" - resolved "https://registry.yarnpkg.com/array.prototype.map/-/array.prototype.map-1.0.7.tgz#82fa4d6027272d1fca28a63bbda424d0185d78a7" - integrity sha512-XpcFfLoBEAhezrrNw1V+yLXkE7M6uR7xJEsxbG6c/V9v043qurwVJB9r9UTnoSioFDoz1i1VOydpWGmJpfVZbg== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-array-method-boxes-properly "^1.0.0" - es-object-atoms "^1.0.0" - is-string "^1.0.7" - -arraybuffer.prototype.slice@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" - integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== - dependencies: - array-buffer-byte-length "^1.0.1" - call-bind "^1.0.5" - define-properties "^1.2.1" - es-abstract "^1.22.3" - es-errors "^1.2.1" - get-intrinsic "^1.2.3" - is-array-buffer "^3.0.4" - is-shared-array-buffer "^1.0.2" - arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -2728,22 +2427,20 @@ async-retry@1.3.3: dependencies: retry "0.13.1" +async@^3.2.3: + version "3.2.5" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" + integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -available-typed-arrays@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" - integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== - dependencies: - possible-typed-array-names "^1.0.0" - axios@^1.6.1: - version "1.6.8" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66" - integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ== + version "1.7.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" + integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== dependencies: follow-redirects "^1.15.6" form-data "^4.0.0" @@ -2784,12 +2481,12 @@ babel-plugin-jest-hoist@^29.6.3: "@types/babel__traverse" "^7.0.6" babel-plugin-polyfill-corejs2@^0.4.10: - version "0.4.10" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz#276f41710b03a64f6467433cab72cbc2653c38b1" - integrity sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ== + version "0.4.11" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz#30320dfe3ffe1a336c15afdcdafd6fd615b25e33" + integrity sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q== dependencies: "@babel/compat-data" "^7.22.6" - "@babel/helper-define-polyfill-provider" "^0.6.1" + "@babel/helper-define-polyfill-provider" "^0.6.2" semver "^6.3.1" babel-plugin-polyfill-corejs3@^0.10.4: @@ -2801,11 +2498,11 @@ babel-plugin-polyfill-corejs3@^0.10.4: core-js-compat "^3.36.1" babel-plugin-polyfill-regenerator@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz#4f08ef4c62c7a7f66a35ed4c0d75e30506acc6be" - integrity sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g== + version "0.6.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz#addc47e240edd1da1058ebda03021f382bba785e" + integrity sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg== dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.1" + "@babel/helper-define-polyfill-provider" "^0.6.2" babel-preset-current-node-syntax@^1.0.0: version "1.0.1" @@ -2910,22 +2607,22 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.2: +braces@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: fill-range "^7.1.1" -browserslist@^4.22.2, browserslist@^4.23.0: - version "4.23.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" - integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== +browserslist@^4.23.0, browserslist@^4.23.1: + version "4.23.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.2.tgz#244fe803641f1c19c28c48c4b6ec9736eb3d32ed" + integrity sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA== dependencies: - caniuse-lite "^1.0.30001587" - electron-to-chromium "^1.4.668" + caniuse-lite "^1.0.30001640" + electron-to-chromium "^1.4.820" node-releases "^2.0.14" - update-browserslist-db "^1.0.13" + update-browserslist-db "^1.1.0" bs-logger@0.x: version "0.2.6" @@ -2994,17 +2691,6 @@ cacheable-request@^10.2.8: normalize-url "^8.0.0" responselike "^3.0.0" -call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" - integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - set-function-length "^1.2.1" - callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -3035,10 +2721,10 @@ camelcase@^7.0.0, camelcase@^7.0.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-7.0.1.tgz#f02e50af9fd7782bc8b88a3558c32fd3a388f048" integrity sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw== -caniuse-lite@^1.0.30001587: - version "1.0.30001608" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001608.tgz#7ae6e92ffb300e4b4ec2f795e0abab456ec06cc0" - integrity sha512-cjUJTQkk9fQlJR2s4HMuPMvTiRggl0rAVMtthQuyOlDWuqHXqN8azLq+pi8B2TjwKJ32diHjUqRIKeFX4z1FoA== +caniuse-lite@^1.0.30001640: + version "1.0.30001643" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz#9c004caef315de9452ab970c3da71085f8241dbd" + integrity sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg== canvas@^2.11.2: version "2.11.2" @@ -3086,7 +2772,7 @@ chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: +chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -3145,9 +2831,9 @@ ci-info@^3.2.0: integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== cjs-module-lexer@^1.0.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" - integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== + version "1.3.1" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz#c485341ae8fd999ca4ee5af2d7a1c9ae01e0099c" + integrity sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q== clean-stack@^4.0.0: version "4.2.0" @@ -3265,9 +2951,9 @@ commander@7: integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== commander@^12.0.0: - version "12.0.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-12.0.0.tgz#b929db6df8546080adfd004ab215ed48cf6f2592" - integrity sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA== + version "12.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== commander@^5.1.0: version "5.1.0" @@ -3363,19 +3049,31 @@ conventional-changelog-angular@^7.0.0: dependencies: compare-func "^2.0.0" +conventional-changelog-angular@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-8.0.0.tgz#5701386850f0e0c2e630b43ee7821d322d87e7a6" + integrity sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA== + dependencies: + compare-func "^2.0.0" + conventional-changelog-atom@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/conventional-changelog-atom/-/conventional-changelog-atom-4.0.0.tgz#291fd1583517d4e7131dba779ad9fa238359daa1" integrity sha512-q2YtiN7rnT1TGwPTwjjBSIPIzDJCRE+XAUahWxnh+buKK99Kks4WLMHoexw38GXx9OUxAsrp44f9qXe5VEMYhw== -conventional-changelog-cli@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/conventional-changelog-cli/-/conventional-changelog-cli-4.1.0.tgz#e3abc622f82d8923a8603eec19411fae0048e72b" - integrity sha512-MscvILWZ6nWOoC+p/3Nn3D2cVLkjeQjyZPUr0bQ+vUORE/SPrkClJh8BOoMNpS4yk+zFJ5LlgXACxH6XGQoRXA== +conventional-changelog-atom@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/conventional-changelog-atom/-/conventional-changelog-atom-5.0.0.tgz#f3e06e06244bd0aef2e5f09ed590933d948e809c" + integrity sha512-WfzCaAvSCFPkznnLgLnfacRAzjgqjLUjvf3MftfsJzQdDICqkOOpcMtdJF3wTerxSpv2IAAjX8doM3Vozqle3g== + +conventional-changelog-cli@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/conventional-changelog-cli/-/conventional-changelog-cli-5.0.0.tgz#feda8f20873347f73042a810db1c03377c39068d" + integrity sha512-9Y8fucJe18/6ef6ZlyIlT2YQUbczvoQZZuYmDLaGvcSBP+M6h+LAvf7ON7waRxKJemcCII8Yqu5/8HEfskTxJQ== dependencies: add-stream "^1.0.0" - conventional-changelog "^5.1.0" - meow "^12.0.1" + conventional-changelog "^6.0.0" + meow "^13.0.0" tempfile "^5.0.0" conventional-changelog-codemirror@^4.0.0: @@ -3383,6 +3081,11 @@ conventional-changelog-codemirror@^4.0.0: resolved "https://registry.yarnpkg.com/conventional-changelog-codemirror/-/conventional-changelog-codemirror-4.0.0.tgz#3421aced2377552229cef454447aa06e2a319516" integrity sha512-hQSojc/5imn1GJK3A75m9hEZZhc3urojA5gMpnar4JHmgLnuM3CUIARPpEk86glEKr3c54Po3WV/vCaO/U8g3Q== +conventional-changelog-codemirror@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/conventional-changelog-codemirror/-/conventional-changelog-codemirror-5.0.0.tgz#994ced326cf358c5e549f5ac59bf3f8cdc09f783" + integrity sha512-8gsBDI5Y3vrKUCxN6Ue8xr6occZ5nsDEc4C7jO/EovFGozx8uttCAyfhRrvoUAWi2WMm3OmYs+0mPJU7kQdYWQ== + conventional-changelog-conventionalcommits@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz#aa5da0f1b2543094889e8cf7616ebe1a8f5c70d5" @@ -3390,6 +3093,13 @@ conventional-changelog-conventionalcommits@^7.0.2: dependencies: compare-func "^2.0.0" +conventional-changelog-conventionalcommits@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-8.0.0.tgz#3fa2857c878701e7f0329db5a1257cb218f166fe" + integrity sha512-eOvlTO6OcySPyyyk8pKz2dP4jjElYunj9hn9/s0OB+gapTO8zwS9UQWrZ1pmF2hFs3vw1xhonOLGcGjy/zgsuA== + dependencies: + compare-func "^2.0.0" + conventional-changelog-core@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/conventional-changelog-core/-/conventional-changelog-core-7.0.0.tgz#d8879ebb8692cd1fa8126c209e1b3af34d94e113" @@ -3406,26 +3116,62 @@ conventional-changelog-core@^7.0.0: read-pkg "^8.0.0" read-pkg-up "^10.0.0" +conventional-changelog-core@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/conventional-changelog-core/-/conventional-changelog-core-8.0.0.tgz#5166eea9ef58a659fc97b065525f4499a0d3f311" + integrity sha512-EATUx5y9xewpEe10UEGNpbSHRC6cVZgO+hXQjofMqpy+gFIrcGvH3Fl6yk2VFKh7m+ffenup2N7SZJYpyD9evw== + dependencies: + "@hutson/parse-repository-url" "^5.0.0" + add-stream "^1.0.0" + conventional-changelog-writer "^8.0.0" + conventional-commits-parser "^6.0.0" + git-raw-commits "^5.0.0" + git-semver-tags "^8.0.0" + hosted-git-info "^7.0.0" + normalize-package-data "^6.0.0" + read-package-up "^11.0.0" + read-pkg "^9.0.0" + conventional-changelog-ember@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/conventional-changelog-ember/-/conventional-changelog-ember-4.0.0.tgz#d90409083a840cd8955bf8257b17498fc539db6a" integrity sha512-D0IMhwcJUg1Y8FSry6XAplEJcljkHVlvAZddhhsdbL1rbsqRsMfGx/PIkPYq0ru5aDgn+OxhQ5N5yR7P9mfsvA== +conventional-changelog-ember@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/conventional-changelog-ember/-/conventional-changelog-ember-5.0.0.tgz#cca926a68aa9bc2a6370b211906b1dea82564567" + integrity sha512-RPflVfm5s4cSO33GH/Ey26oxhiC67akcxSKL8CLRT3kQX2W3dbE19sSOM56iFqUJYEwv9mD9r6k79weWe1urfg== + conventional-changelog-eslint@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/conventional-changelog-eslint/-/conventional-changelog-eslint-5.0.0.tgz#d7f428f787f079b3ce08ccc76ed46d4b1852f41b" integrity sha512-6JtLWqAQIeJLn/OzUlYmzd9fKeNSWmQVim9kql+v4GrZwLx807kAJl3IJVc3jTYfVKWLxhC3BGUxYiuVEcVjgA== +conventional-changelog-eslint@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/conventional-changelog-eslint/-/conventional-changelog-eslint-6.0.0.tgz#9d37abcf6ade84031ce01093be7447f2cd73098b" + integrity sha512-eiUyULWjzq+ybPjXwU6NNRflApDWlPEQEHvI8UAItYW/h22RKkMnOAtfCZxMmrcMO1OKUWtcf2MxKYMWe9zJuw== + conventional-changelog-express@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/conventional-changelog-express/-/conventional-changelog-express-4.0.0.tgz#5f50086bae1cd9887959af1fa3d5244fd1f55974" integrity sha512-yWyy5c7raP9v7aTvPAWzqrztACNO9+FEI1FSYh7UP7YT1AkWgv5UspUeB5v3Ibv4/o60zj2o9GF2tqKQ99lIsw== +conventional-changelog-express@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/conventional-changelog-express/-/conventional-changelog-express-5.0.0.tgz#e08fb0f2c27bc5319ce7d8e78c9e9fb99ae1feb5" + integrity sha512-D8Q6WctPkQpvr2HNCCmwU5GkX22BVHM0r4EW8vN0230TSyS/d6VQJDAxGb84lbg0dFjpO22MwmsikKL++Oo/oQ== + conventional-changelog-jquery@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/conventional-changelog-jquery/-/conventional-changelog-jquery-5.0.0.tgz#d56e5cc9158b5035669ac6e0f773c3e593621887" integrity sha512-slLjlXLRNa/icMI3+uGLQbtrgEny3RgITeCxevJB+p05ExiTgHACP5p3XiMKzjBn80n+Rzr83XMYfRInEtCPPw== +conventional-changelog-jquery@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/conventional-changelog-jquery/-/conventional-changelog-jquery-6.0.0.tgz#5b6bd8b4a720363dc6c2162a3f751961c55256b0" + integrity sha512-2kxmVakyehgyrho2ZHBi90v4AHswkGzHuTaoH40bmeNqUt20yEkDOSpw8HlPBfvEQBwGtbE+5HpRwzj6ac2UfA== + conventional-changelog-jshint@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/conventional-changelog-jshint/-/conventional-changelog-jshint-4.0.0.tgz#95aec357f9122b214671381ef94124287208ece9" @@ -3433,11 +3179,23 @@ conventional-changelog-jshint@^4.0.0: dependencies: compare-func "^2.0.0" +conventional-changelog-jshint@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/conventional-changelog-jshint/-/conventional-changelog-jshint-5.0.0.tgz#42bcc629b9c75bb118364754d120ae49fd742b85" + integrity sha512-gGNphSb/opc76n2eWaO6ma4/Wqu3tpa2w7i9WYqI6Cs2fncDSI2/ihOfMvXveeTTeld0oFvwMVNV+IYQIk3F3g== + dependencies: + compare-func "^2.0.0" + conventional-changelog-preset-loader@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-4.1.0.tgz#996bc40d516471c5bf8248fdc30222563b9bcfe6" integrity sha512-HozQjJicZTuRhCRTq4rZbefaiCzRM2pr6u2NL3XhrmQm4RMnDXfESU6JKu/pnKwx5xtdkYfNCsbhN5exhiKGJA== +conventional-changelog-preset-loader@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-5.0.0.tgz#922ad617c13ad3243bef967cfc0f8373893c216d" + integrity sha512-SetDSntXLk8Jh1NOAl1Gu5uLiCNSYenB5tm0YVeZKePRIgDW9lQImromTwLa3c/Gae298tsgOM+/CYT9XAl0NA== + conventional-changelog-writer@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-7.0.1.tgz#e64ef74fa8e773cab4124af217f3f02b29eb0a9c" @@ -3450,6 +3208,17 @@ conventional-changelog-writer@^7.0.0: semver "^7.5.2" split2 "^4.0.0" +conventional-changelog-writer@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-8.0.0.tgz#81522ed40400a4ca8ab78a42794aae9667c745ae" + integrity sha512-TQcoYGRatlAnT2qEWDON/XSfnVG38JzA7E0wcGScu7RElQBkg9WWgZd1peCWFcWDh1xfb2CfsrcvOn1bbSzztA== + dependencies: + "@types/semver" "^7.5.5" + conventional-commits-filter "^5.0.0" + handlebars "^4.7.7" + meow "^13.0.0" + semver "^7.5.2" + conventional-changelog@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/conventional-changelog/-/conventional-changelog-5.1.0.tgz#04b36a5ad0518e0323e9d629e3b86e34f7abb7eb" @@ -3467,11 +3236,33 @@ conventional-changelog@^5.1.0: conventional-changelog-jshint "^4.0.0" conventional-changelog-preset-loader "^4.1.0" +conventional-changelog@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/conventional-changelog/-/conventional-changelog-6.0.0.tgz#ef941d2fde727be20e0f3a342e4e3b235d6e8663" + integrity sha512-tuUH8H/19VjtD9Ig7l6TQRh+Z0Yt0NZ6w/cCkkyzUbGQTnUEmKfGtkC9gGfVgCfOL1Rzno5NgNF4KY8vR+Jo3w== + dependencies: + conventional-changelog-angular "^8.0.0" + conventional-changelog-atom "^5.0.0" + conventional-changelog-codemirror "^5.0.0" + conventional-changelog-conventionalcommits "^8.0.0" + conventional-changelog-core "^8.0.0" + conventional-changelog-ember "^5.0.0" + conventional-changelog-eslint "^6.0.0" + conventional-changelog-express "^5.0.0" + conventional-changelog-jquery "^6.0.0" + conventional-changelog-jshint "^5.0.0" + conventional-changelog-preset-loader "^5.0.0" + conventional-commits-filter@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-4.0.0.tgz#845d713e48dc7d1520b84ec182e2773c10c7bf7f" integrity sha512-rnpnibcSOdFcdclpFwWa+pPlZJhXE7l+XK04zxhbWrhgpR96h33QLz8hITTXbcYICxVr3HZFtbtUAQ+4LdBo9A== +conventional-commits-filter@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz#72811f95d379e79d2d39d5c0c53c9351ef284e86" + integrity sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q== + conventional-commits-parser@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz#57f3594b81ad54d40c1b4280f04554df28627d9a" @@ -3482,6 +3273,13 @@ conventional-commits-parser@^5.0.0: meow "^12.0.1" split2 "^4.0.0" +conventional-commits-parser@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-6.0.0.tgz#74e3be5344d8cd99f7c3353da2efa1d1dd618061" + integrity sha512-TbsINLp48XeMXR8EvGjTnKGsZqBemisPoyWESlpRyR8lif0lcwzqz+NMtYSj1ooF/WYjSuu7wX0CtdeeMEQAmA== + dependencies: + meow "^13.0.0" + conventional-recommended-bump@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/conventional-recommended-bump/-/conventional-recommended-bump-9.0.0.tgz#2910b08b10e6c705301335ab916e7438eba5907f" @@ -3499,10 +3297,10 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -core-js-compat@^3.31.0, core-js-compat@^3.36.1: - version "3.36.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.36.1.tgz#1818695d72c99c25d621dca94e6883e190cea3c8" - integrity sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA== +core-js-compat@^3.36.1, core-js-compat@^3.37.1: + version "3.37.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.37.1.tgz#c844310c7852f4bdf49b8d339730b97e17ff09ee" + integrity sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg== dependencies: browserslist "^4.23.0" @@ -3844,33 +3642,6 @@ data-uri-to-buffer@^6.0.2: resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz#8a58bb67384b261a38ef18bea1810cb01badd28b" integrity sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw== -data-view-buffer@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" - integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -data-view-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" - integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -data-view-byte-offset@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" - integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-data-view "^1.0.1" - debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -3878,7 +3649,14 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" + integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== + dependencies: + ms "2.1.2" + +debug@4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -3918,9 +3696,9 @@ decompress-response@^6.0.0: mimic-response "^3.1.0" dedent@^1.0.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" - integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== + version "1.5.3" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" + integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== deep-extend@^0.6.0: version "0.6.0" @@ -3962,29 +3740,11 @@ defer-to-connect@^2.0.1: resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== -define-data-property@^1.0.1, define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" - define-lazy-prop@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== -define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" - integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== - dependencies: - define-data-property "^1.0.1" - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - degenerator@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-5.0.1.tgz#9403bf297c6dad9a1ece409b37db27954f91f2f5" @@ -4126,10 +3886,17 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== -electron-to-chromium@^1.4.668: - version "1.4.731" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.731.tgz#d3dc19f359045b750a1fb0bc42315a502d950187" - integrity sha512-+TqVfZjpRz2V/5SPpmJxq9qK620SC5SqCnxQIOi7i/U08ZDcTpKbT7Xjj9FU5CbXTMUb4fywbIr8C7cGv4hcjw== +ejs@^3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + +electron-to-chromium@^1.4.820: + version "1.5.2" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.2.tgz#6126ad229ce45e781ec54ca40db0504787f23d19" + integrity sha512-kc4r3U3V3WLaaZqThjYz/Y6z8tJe+7K0bbjUVo3i+LWIypVdMx5nXCkwRe6SWbY6ILqLdc1rKcKmr3HoH7wjSQ== emittery@^0.13.1: version "0.13.1" @@ -4175,116 +3942,7 @@ error-ex@^1.3.1, error-ex@^1.3.2: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2: - version "1.23.3" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" - integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== - dependencies: - array-buffer-byte-length "^1.0.1" - arraybuffer.prototype.slice "^1.0.3" - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - data-view-buffer "^1.0.1" - data-view-byte-length "^1.0.1" - data-view-byte-offset "^1.0.0" - es-define-property "^1.0.0" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - es-set-tostringtag "^2.0.3" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.6" - get-intrinsic "^1.2.4" - get-symbol-description "^1.0.2" - globalthis "^1.0.3" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - has-proto "^1.0.3" - has-symbols "^1.0.3" - hasown "^2.0.2" - internal-slot "^1.0.7" - is-array-buffer "^3.0.4" - is-callable "^1.2.7" - is-data-view "^1.0.1" - is-negative-zero "^2.0.3" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.3" - is-string "^1.0.7" - is-typed-array "^1.1.13" - is-weakref "^1.0.2" - object-inspect "^1.13.1" - object-keys "^1.1.1" - object.assign "^4.1.5" - regexp.prototype.flags "^1.5.2" - safe-array-concat "^1.1.2" - safe-regex-test "^1.0.3" - string.prototype.trim "^1.2.9" - string.prototype.trimend "^1.0.8" - string.prototype.trimstart "^1.0.8" - typed-array-buffer "^1.0.2" - typed-array-byte-length "^1.0.1" - typed-array-byte-offset "^1.0.2" - typed-array-length "^1.0.6" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.15" - -es-array-method-boxes-properly@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" - integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== - -es-define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" - integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== - dependencies: - get-intrinsic "^1.2.4" - -es-errors@^1.2.1, es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-get-iterator@^1.0.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" - integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.3" - has-symbols "^1.0.3" - is-arguments "^1.1.1" - is-map "^2.0.2" - is-set "^2.0.2" - is-string "^1.0.7" - isarray "^2.0.5" - stop-iteration-iterator "^1.0.0" - -es-object-atoms@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" - integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== - dependencies: - es-errors "^1.3.0" - -es-set-tostringtag@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" - integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== - dependencies: - get-intrinsic "^1.2.4" - has-tostringtag "^1.0.2" - hasown "^2.0.1" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -escalade@^3.1.1: +escalade@^3.1.1, escalade@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== @@ -4337,13 +3995,13 @@ eslint-plugin-jest@^27.9.0: dependencies: "@typescript-eslint/utils" "^5.10.0" -eslint-plugin-prettier@^5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz#17cfade9e732cef32b5f5be53bd4e07afd8e67e1" - integrity sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw== +eslint-plugin-prettier@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz#d1c8f972d8f60e414c25465c163d16f209411f95" + integrity sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw== dependencies: prettier-linter-helpers "^1.0.0" - synckit "^0.8.6" + synckit "^0.9.1" eslint-scope@^5.1.1: version "5.1.1" @@ -4425,9 +4083,9 @@ esprima@^4.0.0, esprima@^4.0.1: integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.4.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" - integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== dependencies: estraverse "^5.1.0" @@ -4567,6 +4225,11 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-uri@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.1.tgz#cddd2eecfc83a71c1be2cc2ef2061331be8a7134" + integrity sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw== + fast-url-parser@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" @@ -4618,6 +4281,13 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + fill-range@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" @@ -4649,6 +4319,11 @@ find-process@^1.4.7: commander "^5.1.0" debug "^4.1.1" +find-up-simple@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/find-up-simple/-/find-up-simple-1.0.0.tgz#21d035fde9fdbd56c8f4d2f63f32fd93a1cfc368" + integrity sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw== + find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -4692,13 +4367,6 @@ follow-redirects@^1.15.6: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== -for-each@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" - integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== - dependencies: - is-callable "^1.1.3" - foreground-child@^3.1.0: version "3.2.1" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.2.1.tgz#767004ccf3a5b30df39bed90718bab43fe0a59f7" @@ -4769,21 +4437,6 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -function.prototype.name@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" - integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - functions-have-names "^1.2.3" - -functions-have-names@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" - integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== - gauge@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" @@ -4814,17 +4467,6 @@ get-east-asian-width@^1.0.0: resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz#5e6ebd9baee6fb8b7b6bd505221065f0cd91f64e" integrity sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA== -get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" - integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" - get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -4847,15 +4489,6 @@ get-stream@^8.0.1: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== -get-symbol-description@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" - integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== - dependencies: - call-bind "^1.0.5" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - get-uri@^6.0.1: version "6.0.3" resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-6.0.3.tgz#0d26697bc13cf91092e519aa63aa60ee5b6f385a" @@ -4875,6 +4508,14 @@ git-raw-commits@^4.0.0: meow "^12.0.1" split2 "^4.0.0" +git-raw-commits@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-5.0.0.tgz#38af4301e70c17be03fec01a37a6cd90ce0db04e" + integrity sha512-I2ZXrXeOc0KrCvC7swqtIFXFN+rbjnC7b2T943tvemIOVNl+XP8YnA9UVwqFhzzLClnSA60KR/qEjLpXzs73Qg== + dependencies: + "@conventional-changelog/git-client" "^1.0.0" + meow "^13.0.0" + git-semver-tags@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/git-semver-tags/-/git-semver-tags-7.0.1.tgz#74426e7d7710e5a263655e78b4c651eed804d63e" @@ -4883,6 +4524,14 @@ git-semver-tags@^7.0.0: meow "^12.0.1" semver "^7.5.2" +git-semver-tags@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/git-semver-tags/-/git-semver-tags-8.0.0.tgz#745ee2d934f74c70014d0ed617e18f4712950e32" + integrity sha512-N7YRIklvPH3wYWAR2vysaqGLPRcpwQ0GKdlqTiVN5w1UmCdaeY3K8s6DMKRCh54DDdzyt/OAB6C8jgVtb7Y2Fg== + dependencies: + "@conventional-changelog/git-client" "^1.0.0" + meow "^13.0.0" + git-up@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/git-up/-/git-up-7.0.0.tgz#bace30786e36f56ea341b6f69adfd83286337467" @@ -4912,10 +4561,10 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob@^10.4.1: - version "10.4.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.2.tgz#bed6b95dade5c1f80b4434daced233aee76160e5" - integrity sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w== +glob@^10.3.12, glob@^10.4.1: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== dependencies: foreground-child "^3.1.0" jackspeak "^3.1.2" @@ -4936,23 +4585,12 @@ glob@^7.0.0, glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^8.0.3: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - -global-dirs@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.1.tgz#0c488971f066baceda21447aecb1a8b911d22485" - integrity sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA== +global-directory@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/global-directory/-/global-directory-4.0.1.tgz#4d7ac7cfd2cb73f304c53b8810891748df5e361e" + integrity sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q== dependencies: - ini "2.0.0" + ini "4.1.1" global-modules@^0.2.3: version "0.2.3" @@ -4984,17 +4622,10 @@ globals@^13.19.0: dependencies: type-fest "^0.20.2" -globalthis@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" - integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== - dependencies: - define-properties "^1.1.3" - -globby@14.0.1: - version "14.0.1" - resolved "https://registry.yarnpkg.com/globby/-/globby-14.0.1.tgz#a1b44841aa7f4c6d8af2bc39951109d77301959b" - integrity sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ== +globby@14.0.2: + version "14.0.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-14.0.2.tgz#06554a54ccfe9264e5a9ff8eded46aa1e306482f" + integrity sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw== dependencies: "@sindresorhus/merge-streams" "^2.1.0" fast-glob "^3.3.2" @@ -5026,13 +4657,6 @@ globby@^13.1.2: merge2 "^1.4.1" slash "^4.0.0" -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" - got@13.0.0: version "13.0.0" resolved "https://registry.yarnpkg.com/got/-/got-13.0.0.tgz#a2402862cef27a5d0d1b07c0fb25d12b58175422" @@ -5050,23 +4674,6 @@ got@13.0.0: p-cancelable "^3.0.0" responselike "^3.0.0" -got@^12.1.0: - version "12.6.1" - resolved "https://registry.yarnpkg.com/got/-/got-12.6.1.tgz#8869560d1383353204b5a9435f782df9c091f549" - integrity sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ== - dependencies: - "@sindresorhus/is" "^5.2.0" - "@szmarczak/http-timer" "^5.0.1" - cacheable-lookup "^7.0.0" - cacheable-request "^10.2.8" - decompress-response "^6.0.0" - form-data-encoder "^2.1.2" - get-stream "^6.0.1" - http2-wrapper "^2.1.10" - lowercase-keys "^3.0.0" - p-cancelable "^3.0.0" - responselike "^3.0.0" - graceful-fs@4.2.10: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" @@ -5113,11 +4720,6 @@ has-ansi@^2.0.0: dependencies: ansi-regex "^2.0.0" -has-bigints@^1.0.1, has-bigints@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" - integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== - has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -5128,46 +4730,22 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" - -has-proto@^1.0.1, has-proto@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" - integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== - -has-symbols@^1.0.2, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" - has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== -hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: +hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== dependencies: function-bind "^1.1.2" -highlight.js@^11.9.0: - version "11.9.0" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0" - integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw== +highlight.js@^11.10.0: + version "11.10.0" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.10.0.tgz#6e3600dc4b33d6dc23d5bd94fbf72405f5892b92" + integrity sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ== homedir-polyfill@^1.0.0: version "1.0.3" @@ -5184,9 +4762,9 @@ hosted-git-info@^4.0.1: lru-cache "^6.0.0" hosted-git-info@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-7.0.1.tgz#9985fcb2700467fecf7f33a4d4874e30680b5322" - integrity sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA== + version "7.0.2" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-7.0.2.tgz#9b751acac097757667f30114607ef7b661ff4f17" + integrity sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w== dependencies: lru-cache "^10.0.1" @@ -5234,10 +4812,10 @@ https-proxy-agent@5.0.1, https-proxy-agent@^5.0.0: agent-base "6" debug "4" -https-proxy-agent@^7.0.2, https-proxy-agent@^7.0.3: - version "7.0.4" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz#8e97b841a029ad8ddc8731f26595bad868cb4168" - integrity sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg== +https-proxy-agent@^7.0.3, https-proxy-agent@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz#9e8b5013873299e11fab6fd548405da2d6c602b2" + integrity sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw== dependencies: agent-base "^7.0.2" debug "4" @@ -5290,9 +4868,9 @@ import-lazy@^4.0.0: integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== import-local@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" - integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== dependencies: pkg-dir "^4.2.0" resolve-cwd "^3.0.0" @@ -5307,6 +4885,11 @@ indent-string@^5.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-5.0.0.tgz#4fd2980fccaf8622d14c64d694f4cf33c81951a5" integrity sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg== +index-to-position@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/index-to-position/-/index-to-position-0.1.2.tgz#e11bfe995ca4d8eddb1ec43274488f3c201a7f09" + integrity sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -5320,29 +4903,25 @@ inherits@2, inherits@^2.0.3, inherits@^2.0.4: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" - integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== +ini@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ini/-/ini-4.1.1.tgz#d95b3d843b1e906e56d6747d5447904ff50ce7a1" + integrity sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g== ini@^1.3.4, ini@~1.3.0: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -inquirer@9.2.19: - version "9.2.19" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-9.2.19.tgz#e142ebc111b6328a21eb84d8e7dd226ff824239e" - integrity sha512-WpxOT71HGsFya6/mj5PUue0sWwbpbiPfAR+332zLj/siB0QA1PZM8v3GepegFV1Op189UxHUCF6y8AySdtOMVA== +inquirer@9.3.2: + version "9.3.2" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-9.3.2.tgz#9bc5ced19f572e848044baa05094a498f1e448c6" + integrity sha512-+ynEbhWKhyomnaX0n2aLIMSkgSlGB5RrWbNXnEqj6mdaIydu6y40MdBjL38SAB0JcdmOaIaMua1azdjLEr3sdw== dependencies: - "@inquirer/figures" "^1.0.1" - "@ljharb/through" "^2.3.13" + "@inquirer/figures" "^1.0.3" ansi-escapes "^4.3.2" - chalk "^5.3.0" - cli-cursor "^3.1.0" cli-width "^4.1.0" external-editor "^3.1.0" - lodash "^4.17.21" mute-stream "1.0.0" ora "^5.4.1" run-async "^3.0.0" @@ -5350,15 +4929,7 @@ inquirer@9.2.19: string-width "^4.2.3" strip-ansi "^6.0.1" wrap-ansi "^6.2.0" - -internal-slot@^1.0.4, internal-slot@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" - integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== - dependencies: - es-errors "^1.3.0" - hasown "^2.0.0" - side-channel "^1.0.4" + yoctocolors-cjs "^2.1.1" "internmap@1 - 2": version "2.0.3" @@ -5378,42 +4949,11 @@ ip-address@^9.0.5: jsbn "1.1.0" sprintf-js "^1.1.3" -is-arguments@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" - integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-array-buffer@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" - integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-bigint@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== - dependencies: - has-bigints "^1.0.1" - -is-boolean-object@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - is-builtin-module@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" @@ -5421,11 +4961,6 @@ is-builtin-module@^3.2.1: dependencies: builtin-modules "^3.3.0" -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - is-ci@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867" @@ -5433,26 +4968,12 @@ is-ci@3.0.1: dependencies: ci-info "^3.2.0" -is-core-module@^2.13.0, is-core-module@^2.5.0, is-core-module@^2.8.1: - version "2.13.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" - integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== - dependencies: - hasown "^2.0.0" - -is-data-view@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" - integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== - dependencies: - is-typed-array "^1.1.13" - -is-date-object@^1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== +is-core-module@^2.13.0, is-core-module@^2.5.0: + version "2.15.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea" + integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA== dependencies: - has-tostringtag "^1.0.0" + hasown "^2.0.2" is-docker@^2.0.0: version "2.2.1" @@ -5498,13 +5019,13 @@ is-inside-container@^1.0.0: dependencies: is-docker "^3.0.0" -is-installed-globally@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" - integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== +is-installed-globally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-1.0.0.tgz#08952c43758c33d815692392f7f8437b9e436d5a" + integrity sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ== dependencies: - global-dirs "^3.0.0" - is-path-inside "^3.0.2" + global-directory "^4.0.1" + is-path-inside "^4.0.0" is-interactive@^1.0.0: version "1.0.0" @@ -5516,33 +5037,16 @@ is-interactive@^2.0.0: resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-2.0.0.tgz#40c57614593826da1100ade6059778d597f16e90" integrity sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ== -is-map@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" - integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== - is-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g== -is-negative-zero@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" - integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== - is-npm@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-6.0.0.tgz#b59e75e8915543ca5d881ecff864077cba095261" integrity sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ== -is-number-object@^1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" - integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== - dependencies: - has-tostringtag "^1.0.0" - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -5558,7 +5062,7 @@ is-path-cwd@^3.0.0: resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-3.0.0.tgz#889b41e55c8588b1eb2a96a61d05740a674521c7" integrity sha512-kyiNFFLU0Ampr6SDZitD/DwUo4Zs1nSdnygUBqsu3LooL00Qvb5j+UnvApUn/TTj1J3OuE6BTdQ5rudKmU2ZaA== -is-path-inside@^3.0.2, is-path-inside@^3.0.3: +is-path-inside@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== @@ -5585,26 +5089,6 @@ is-reference@1.2.1: dependencies: "@types/estree" "*" -is-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-set@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" - integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== - -is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" - integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== - dependencies: - call-bind "^1.0.7" - is-ssh@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.4.0.tgz#4f8220601d2839d8fa624b3106f8e8884f01b8b2" @@ -5622,20 +5106,6 @@ is-stream@^3.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== - dependencies: - has-tostringtag "^1.0.0" - -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - is-text-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-2.0.0.tgz#b2484e2b720a633feb2e85b67dc193ff72c75636" @@ -5643,13 +5113,6 @@ is-text-path@^2.0.0: dependencies: text-extensions "^2.0.0" -is-typed-array@^1.1.13: - version "1.1.13" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" - integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== - dependencies: - which-typed-array "^1.1.14" - is-typedarray@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -5670,13 +5133,6 @@ is-unicode-supported@^2.0.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz#fdf32df9ae98ff6ab2cedc155a5a6e895701c451" integrity sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q== -is-weakref@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" - integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== - dependencies: - call-bind "^1.0.2" - is-windows@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c" @@ -5696,20 +5152,15 @@ is-wsl@^3.1.0: dependencies: is-inside-container "^1.0.0" -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -issue-parser@7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/issue-parser/-/issue-parser-7.0.0.tgz#27b832c5f5967da897e08ca1949d188e98873b1a" - integrity sha512-jgAw78HO3gs9UrKqJNQvfDj9Ouy8Mhu40fbEJ8yXff4MW8+/Fcn9iFjyWUQ6SKbX8ipPk3X5A3AyfYHRu6uVLw== +issue-parser@7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/issue-parser/-/issue-parser-7.0.1.tgz#8a053e5a4952c75bb216204e454b4fc7d4cc9637" + integrity sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg== dependencies: lodash.capitalize "^4.2.1" lodash.escaperegexp "^4.1.2" @@ -5734,9 +5185,9 @@ istanbul-lib-instrument@^5.0.4: semver "^6.3.0" istanbul-lib-instrument@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz#91655936cf7380e4e473383081e38478b69993b1" - integrity sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw== + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== dependencies: "@babel/core" "^7.23.9" "@babel/parser" "^7.23.9" @@ -5770,28 +5221,25 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -iterate-iterator@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/iterate-iterator/-/iterate-iterator-1.0.2.tgz#551b804c9eaa15b847ea6a7cdc2f5bf1ec150f91" - integrity sha512-t91HubM4ZDQ70M9wqp+pcNpu8OyJ9UAtXntT/Bcsvp5tZMnz9vRa+IunKXeI8AnfZMTv0jNuVEmGeLSMjVvfPw== - -iterate-value@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/iterate-value/-/iterate-value-1.0.2.tgz#935115bd37d006a52046535ebc8d07e9c9337f57" - integrity sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ== - dependencies: - es-get-iterator "^1.0.2" - iterate-iterator "^1.0.1" - jackspeak@^3.1.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.0.tgz#a75763ff36ad778ede6a156d8ee8b124de445b4a" - integrity sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw== + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== dependencies: "@isaacs/cliui" "^8.0.2" optionalDependencies: "@pkgjs/parseargs" "^0.11.0" +jake@^10.8.5: + version "10.9.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" + integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + jest-changed-files@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" @@ -6164,9 +5612,9 @@ jest@^29.7.0: jest-cli "^29.7.0" joi@^17.11.0: - version "17.12.3" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.12.3.tgz#944646979cd3b460178547b12ba37aca8482f63d" - integrity sha512-2RRziagf555owrm9IRVtdKynOBeITiDpuZqIpgwqXShPncPKNiRQoiGsl/T8SQdq+8ugRzH2LqY67irr2y/d+g== + version "17.13.3" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.13.3.tgz#0f5cc1169c999b30d344366d384b12d92558bcec" + integrity sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA== dependencies: "@hapi/hoek" "^9.3.0" "@hapi/topo" "^5.1.0" @@ -6220,9 +5668,9 @@ json-parse-even-better-errors@^2.3.0: integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json-parse-even-better-errors@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz#02bb29fb5da90b5444581749c22cedd3597c6cb0" - integrity sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg== + version "3.0.2" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz#b43d35e89c0f3be6b5fbbe9dc6c82467b30c28da" + integrity sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ== json-schema-traverse@^0.4.1: version "0.4.1" @@ -6285,12 +5733,17 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -latest-version@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-7.0.0.tgz#843201591ea81a4d404932eeb61240fe04e9e5da" - integrity sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg== +ky@^1.2.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/ky/-/ky-1.5.0.tgz#fa2c9c11c175a6d0072e572216207a4edc895d10" + integrity sha512-bkQo+UqryW6Zmo/DsixYZE4Z9t2mzvNMhceyIhuMuInb3knm5Q+GNGMKveydJAj+Z6piN1SwI6eR/V0G+Z0BtA== + +latest-version@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-9.0.0.tgz#e91ed216e7a4badc6f73b66c65adb46c58ec6ba1" + integrity sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA== dependencies: - package-json "^8.1.0" + package-json "^10.0.0" leven@^3.1.0: version "3.1.0" @@ -6402,15 +5855,10 @@ lowercase-keys@^3.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2" integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ== -lru-cache@^10.0.1: - version "10.2.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3" - integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q== - -lru-cache@^10.2.0: - version "10.3.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.3.0.tgz#4a4aaf10c84658ab70f79a85a9a3f1e1fb11196b" - integrity sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ== +lru-cache@^10.0.1, lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== lru-cache@^5.1.1: version "5.1.1" @@ -6437,9 +5885,9 @@ macos-release@^3.1.0: integrity sha512-fSErXALFNsnowREYZ49XCdOHF8wOPWuFOGQrAhP7x5J/BqQv+B02cNsTykGpDgRVx43EKg++6ANmTaGTtW+hUA== magic-string@^0.30.3: - version "0.30.9" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.9.tgz#8927ae21bfdd856310e07a1bc8dd5e73cb6c251d" - integrity sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw== + version "0.30.10" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.10.tgz#123d9c41a0cb5640c892b041d4cfb3bd0aa4b39e" + integrity sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ== dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" @@ -6512,6 +5960,11 @@ meow@^12.0.1: resolved "https://registry.yarnpkg.com/meow/-/meow-12.1.1.tgz#e558dddbab12477b69b2e9a2728c327f191bace6" integrity sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw== +meow@^13.0.0: + version "13.2.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-13.2.0.tgz#6b7d63f913f984063b3cc261b6e8800c4cd3474f" + integrity sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA== + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -6523,18 +5976,23 @@ merge2@^1.3.0, merge2@^1.4.1: integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + version "4.0.7" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" + integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" -mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": +mime-db@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== +"mime-db@>= 1.43.0 < 2": + version "1.53.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.53.0.tgz#3cb63cd820fc29896d9d4e8c32ab4fcd74ccb447" + integrity sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg== + mime-db@~1.33.0: version "1.33.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" @@ -6599,9 +6057,9 @@ minimatch@^5.0.1: brace-expansion "^2.0.1" minimatch@^9.0.4: - version "9.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" - integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== dependencies: brace-expansion "^2.0.1" @@ -6675,9 +6133,9 @@ mute-stream@1.0.0: integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== nan@^2.17.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.19.0.tgz#bb58122ad55a6c5bc973303908d5b16cfdd5a8c0" - integrity sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw== + version "2.20.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3" + integrity sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw== natural-compare@^1.4.0: version "1.4.0" @@ -6740,9 +6198,9 @@ node-int64@^0.4.0: integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== node-releases@^2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" - integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== nopt@^5.0.0: version "5.0.0" @@ -6762,12 +6220,11 @@ normalize-package-data@^3.0.2: validate-npm-package-license "^3.0.1" normalize-package-data@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-6.0.0.tgz#68a96b3c11edd462af7189c837b6b1064a484196" - integrity sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg== + version "6.0.2" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-6.0.2.tgz#a7bc22167fe24025412bcff0a9651eb768b03506" + integrity sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g== dependencies: hosted-git-info "^7.0.0" - is-core-module "^2.8.1" semver "^7.3.5" validate-npm-package-license "^3.0.4" @@ -6822,26 +6279,6 @@ object-assign@^4.1.0, object-assign@^4.1.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" - integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== - -object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" - integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== - dependencies: - call-bind "^1.0.5" - define-properties "^1.2.1" - has-symbols "^1.0.3" - object-keys "^1.1.1" - on-headers@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" @@ -6879,16 +6316,16 @@ open@10.1.0: is-wsl "^3.1.0" optionator@^0.9.3: - version "0.9.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" - integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== dependencies: - "@aashutoshrathi/word-wrap" "^1.2.3" deep-is "^0.1.3" fast-levenshtein "^2.0.6" levn "^0.4.1" prelude-ls "^1.2.1" type-check "^0.4.0" + word-wrap "^1.2.5" ora@8.0.1: version "8.0.1" @@ -6998,20 +6435,20 @@ p-try@^2.0.0: integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== pac-proxy-agent@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz#6b9ddc002ec3ff0ba5fdf4a8a21d363bcc612d75" - integrity sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A== + version "7.0.2" + resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-7.0.2.tgz#0fb02496bd9fb8ae7eb11cfd98386daaac442f58" + integrity sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg== dependencies: "@tootallnate/quickjs-emscripten" "^0.23.0" agent-base "^7.0.2" debug "^4.3.4" get-uri "^6.0.1" http-proxy-agent "^7.0.0" - https-proxy-agent "^7.0.2" - pac-resolver "^7.0.0" - socks-proxy-agent "^8.0.2" + https-proxy-agent "^7.0.5" + pac-resolver "^7.0.1" + socks-proxy-agent "^8.0.4" -pac-resolver@^7.0.0: +pac-resolver@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-7.0.1.tgz#54675558ea368b64d210fd9c92a640b5f3b8abb6" integrity sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg== @@ -7024,15 +6461,15 @@ package-json-from-dist@^1.0.0: resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00" integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw== -package-json@^8.1.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-8.1.1.tgz#3e9948e43df40d1e8e78a85485f1070bf8f03dc8" - integrity sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA== +package-json@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-10.0.1.tgz#e49ee07b8de63b638e7f1b5bb353733e428fe7d7" + integrity sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg== dependencies: - got "^12.1.0" - registry-auth-token "^5.0.1" - registry-url "^6.0.0" - semver "^7.3.7" + ky "^1.2.0" + registry-auth-token "^5.0.2" + registry-url "^6.0.1" + semver "^7.6.0" pako@^2.1.0: version "2.1.0" @@ -7067,6 +6504,15 @@ parse-json@^7.0.0: lines-and-columns "^2.0.3" type-fest "^3.8.0" +parse-json@^8.0.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-8.1.0.tgz#91cdc7728004e955af9cb734de5684733b24a717" + integrity sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA== + dependencies: + "@babel/code-frame" "^7.22.13" + index-to-position "^0.1.2" + type-fest "^4.7.1" + parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" @@ -7164,10 +6610,10 @@ pend@~1.2.0: resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.0.0, picocolors@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" + integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" @@ -7186,11 +6632,6 @@ pkg-dir@4.2.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -possible-typed-array-names@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" - integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== - prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -7203,10 +6644,10 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.2.tgz#03ff86dc7c835f2d2559ee76876a3914cec4a90a" - integrity sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA== +prettier@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" + integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== pretty-bytes@^3.0.0: version "3.0.1" @@ -7229,18 +6670,6 @@ progress@2.0.3: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== -promise.allsettled@1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/promise.allsettled/-/promise.allsettled-1.0.7.tgz#b9dd51e9cffe496243f5271515652c468865f2d8" - integrity sha512-hezvKvQQmsFkOdrZfYxUxkyxl8mgFQeT259Ajj9PXdbg9VzBCWrItOev72JyWxkCD5VSSqAeHmlN3tWx4DlmsA== - dependencies: - array.prototype.map "^1.0.5" - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - get-intrinsic "^1.2.1" - iterate-value "^1.0.2" - prompts@^2.0.1, prompts@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -7359,9 +6788,18 @@ rc@1.2.8, rc@^1.0.1, rc@^1.1.6: strip-json-comments "~2.0.1" react-is@^18.0.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" - integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +read-package-up@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/read-package-up/-/read-package-up-11.0.0.tgz#71fb879fdaac0e16891e6e666df22de24a48d5ba" + integrity sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ== + dependencies: + find-up-simple "^1.0.0" + read-pkg "^9.0.0" + type-fest "^4.6.0" read-pkg-up@^10.0.0: version "10.1.0" @@ -7401,6 +6839,17 @@ read-pkg@^8.0.0, read-pkg@^8.1.0: parse-json "^7.0.0" type-fest "^4.2.0" +read-pkg@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-9.0.1.tgz#b1b81fb15104f5dbb121b6bbdee9bbc9739f569b" + integrity sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA== + dependencies: + "@types/normalize-package-data" "^2.4.3" + normalize-package-data "^6.0.0" + parse-json "^8.0.0" + type-fest "^4.6.0" + unicorn-magic "^0.1.0" + readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" @@ -7449,16 +6898,6 @@ regenerator-transform@^0.15.2: dependencies: "@babel/runtime" "^7.8.4" -regexp.prototype.flags@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" - integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== - dependencies: - call-bind "^1.0.6" - define-properties "^1.2.1" - es-errors "^1.3.0" - set-function-name "^2.0.1" - regexpu-core@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" @@ -7479,7 +6918,7 @@ registry-auth-token@3.3.2: rc "^1.1.6" safe-buffer "^5.0.1" -registry-auth-token@^5.0.1: +registry-auth-token@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-5.0.2.tgz#8b026cc507c8552ebbe06724136267e63302f756" integrity sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ== @@ -7493,7 +6932,7 @@ registry-url@3.1.0: dependencies: rc "^1.0.1" -registry-url@^6.0.0: +registry-url@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-6.0.1.tgz#056d9343680f2f64400032b1e199faa692286c58" integrity sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q== @@ -7507,23 +6946,23 @@ regjsparser@^0.9.1: dependencies: jsesc "~0.5.0" -release-it@17.2.1: - version "17.2.1" - resolved "https://registry.yarnpkg.com/release-it/-/release-it-17.2.1.tgz#1d5bbc32023a79a819cf66807485126f19f127fd" - integrity sha512-zBOpaHyjrXC3g/9rHyQlvuDw9yCn9AGphrlL+t3gWNEhbZKEQ62WNY45JxllcJMNx9orQUxBZ3o7pVCqkeuTbg== +release-it@17.6.0: + version "17.6.0" + resolved "https://registry.yarnpkg.com/release-it/-/release-it-17.6.0.tgz#de91db313d76849f727a7434f7b8bdb52b6a4ac8" + integrity sha512-EE34dtRPL7BHpYQC7E+zAU8kjkyxFHxLk5Iqnmn/5nGcjgOQu34Au29M2V9YvxiP3tZbIlEn4gItEzu7vAPRbw== dependencies: "@iarna/toml" "2.2.5" - "@octokit/rest" "20.1.0" + "@octokit/rest" "20.1.1" async-retry "1.3.3" chalk "5.3.0" cosmiconfig "9.0.0" execa "8.0.1" git-url-parse "14.0.0" - globby "14.0.1" + globby "14.0.2" got "13.0.0" - inquirer "9.2.19" + inquirer "9.3.2" is-ci "3.0.1" - issue-parser "7.0.0" + issue-parser "7.0.1" lodash "4.17.21" mime-types "2.1.35" new-github-release-url "2.0.0" @@ -7531,11 +6970,10 @@ release-it@17.2.1: open "10.1.0" ora "8.0.1" os-name "5.1.0" - promise.allsettled "1.0.7" proxy-agent "6.4.0" - semver "7.6.0" + semver "7.6.2" shelljs "0.8.5" - update-notifier "7.0.0" + update-notifier "7.1.0" url-join "5.0.0" wildcard-match "5.1.3" yargs-parser "21.1.1" @@ -7647,29 +7085,29 @@ rollup-plugin-bundle-size@^1.0.3: chalk "^1.1.3" maxmin "^2.1.0" -rollup@^4.18.0: - version "4.18.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.18.0.tgz#497f60f0c5308e4602cf41136339fbf87d5f5dda" - integrity sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg== +rollup@^4.19.1: + version "4.19.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.19.1.tgz#21d865cd60d4a325172ce8b082e60caccd97b309" + integrity sha512-K5vziVlg7hTpYfFBI+91zHBEMo6jafYXpkMlqZjg7/zhIG9iHqazBf4xz9AVdjS9BruRn280ROqLI7G3OFRIlw== dependencies: "@types/estree" "1.0.5" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.18.0" - "@rollup/rollup-android-arm64" "4.18.0" - "@rollup/rollup-darwin-arm64" "4.18.0" - "@rollup/rollup-darwin-x64" "4.18.0" - "@rollup/rollup-linux-arm-gnueabihf" "4.18.0" - "@rollup/rollup-linux-arm-musleabihf" "4.18.0" - "@rollup/rollup-linux-arm64-gnu" "4.18.0" - "@rollup/rollup-linux-arm64-musl" "4.18.0" - "@rollup/rollup-linux-powerpc64le-gnu" "4.18.0" - "@rollup/rollup-linux-riscv64-gnu" "4.18.0" - "@rollup/rollup-linux-s390x-gnu" "4.18.0" - "@rollup/rollup-linux-x64-gnu" "4.18.0" - "@rollup/rollup-linux-x64-musl" "4.18.0" - "@rollup/rollup-win32-arm64-msvc" "4.18.0" - "@rollup/rollup-win32-ia32-msvc" "4.18.0" - "@rollup/rollup-win32-x64-msvc" "4.18.0" + "@rollup/rollup-android-arm-eabi" "4.19.1" + "@rollup/rollup-android-arm64" "4.19.1" + "@rollup/rollup-darwin-arm64" "4.19.1" + "@rollup/rollup-darwin-x64" "4.19.1" + "@rollup/rollup-linux-arm-gnueabihf" "4.19.1" + "@rollup/rollup-linux-arm-musleabihf" "4.19.1" + "@rollup/rollup-linux-arm64-gnu" "4.19.1" + "@rollup/rollup-linux-arm64-musl" "4.19.1" + "@rollup/rollup-linux-powerpc64le-gnu" "4.19.1" + "@rollup/rollup-linux-riscv64-gnu" "4.19.1" + "@rollup/rollup-linux-s390x-gnu" "4.19.1" + "@rollup/rollup-linux-x64-gnu" "4.19.1" + "@rollup/rollup-linux-x64-musl" "4.19.1" + "@rollup/rollup-win32-arm64-msvc" "4.19.1" + "@rollup/rollup-win32-ia32-msvc" "4.19.1" + "@rollup/rollup-win32-x64-msvc" "4.19.1" fsevents "~2.3.2" run-applescript@^7.0.0: @@ -7701,16 +7139,6 @@ rxjs@^7.8.1: dependencies: tslib "^2.1.0" -safe-array-concat@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" - integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== - dependencies: - call-bind "^1.0.7" - get-intrinsic "^1.2.4" - has-symbols "^1.0.3" - isarray "^2.0.5" - safe-buffer@5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -7721,15 +7149,6 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-regex-test@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" - integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-regex "^1.1.4" - safe-stable-stringify@^2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" @@ -7747,18 +7166,21 @@ semver-diff@^4.0.0: dependencies: semver "^7.3.5" -semver@7.6.0, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0: - version "7.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" - integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== - dependencies: - lru-cache "^6.0.0" +semver@7.6.2: + version "7.6.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" + integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== +semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.2: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + serialize-javascript@^6.0.1: version "6.0.2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" @@ -7802,28 +7224,6 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== -set-function-length@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - -set-function-name@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" - integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - functions-have-names "^1.2.3" - has-property-descriptors "^1.0.2" - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -7845,16 +7245,6 @@ shelljs@0.8.5: interpret "^1.0.0" rechoir "^0.6.2" -side-channel@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" - signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -7909,16 +7299,16 @@ smob@^1.0.0: resolved "https://registry.yarnpkg.com/smob/-/smob-1.5.0.tgz#85d79a1403abf128d24d3ebc1cdc5e1a9548d3ab" integrity sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig== -socks-proxy-agent@^8.0.2: - version "8.0.3" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz#6b2da3d77364fde6292e810b496cb70440b9b89d" - integrity sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A== +socks-proxy-agent@^8.0.2, socks-proxy-agent@^8.0.4: + version "8.0.4" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz#9071dca17af95f483300316f4b063578fa0db08c" + integrity sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw== dependencies: agent-base "^7.1.1" debug "^4.3.4" - socks "^2.7.1" + socks "^2.8.3" -socks@^2.7.1: +socks@^2.8.3: version "2.8.3" resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.3.tgz#1ebd0f09c52ba95a09750afe3f3f9f724a800cb5" integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw== @@ -7977,9 +7367,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.17" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz#887da8aa73218e51a1d917502d79863161a93f9c" - integrity sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg== + version "3.0.18" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz#22aa922dcf2f2885a6494a261f2d8b75345d0326" + integrity sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ== split2@^4.0.0: version "4.2.0" @@ -8008,13 +7398,6 @@ stdin-discarder@^0.2.1: resolved "https://registry.yarnpkg.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz#390037f44c4ae1a1ae535c5fe38dc3aba8d997be" integrity sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ== -stop-iteration-iterator@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" - integrity sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ== - dependencies: - internal-slot "^1.0.4" - string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -8051,42 +7434,14 @@ string-width@^5.0.1, string-width@^5.1.2: strip-ansi "^7.0.1" string-width@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.1.0.tgz#d994252935224729ea3719c49f7206dc9c46550a" - integrity sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw== + version "7.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.2.0.tgz#b5bb8e2165ce275d4d43476dd2700ad9091db6dc" + integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== dependencies: emoji-regex "^10.3.0" get-east-asian-width "^1.0.0" strip-ansi "^7.1.0" -string.prototype.trim@^1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" - integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.0" - es-object-atoms "^1.0.0" - -string.prototype.trimend@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" - integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -string.prototype.trimstart@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" - integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -8185,10 +7540,10 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -synckit@^0.8.6: - version "0.8.8" - resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.8.tgz#fe7fe446518e3d3d49f5e429f443cf08b6edfcd7" - integrity sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ== +synckit@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.9.1.tgz#febbfbb6649979450131f64735aa3f6c14575c88" + integrity sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A== dependencies: "@pkgr/core" "^0.1.0" tslib "^2.6.2" @@ -8238,20 +7593,10 @@ tempfile@^5.0.0: dependencies: temp-dir "^3.0.0" -terser@^5.17.4: - version "5.31.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.0.tgz#06eef86f17007dbad4593f11a574c7f5eb02c6a1" - integrity sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg== - dependencies: - "@jridgewell/source-map" "^0.3.3" - acorn "^8.8.2" - commander "^2.20.0" - source-map-support "~0.5.20" - -terser@^5.31.1: - version "5.31.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.1.tgz#735de3c987dd671e95190e6b98cfe2f07f3cf0d4" - integrity sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg== +terser@^5.17.4, terser@^5.31.3: + version "5.31.3" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.3.tgz#b24b7beb46062f4653f049eea4f0cd165d0f0c38" + integrity sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" @@ -8333,12 +7678,13 @@ ts-api-utils@^1.3.0: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== -ts-jest@^29.1.4: - version "29.1.4" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.4.tgz#26f8a55ce31e4d2ef7a1fd47dc7fa127e92793ef" - integrity sha512-YiHwDhSvCiItoAgsKtoLFCuakDzDsJ1DLDnSouTaTmdOcOwIkSzbLXduaQ6M5DRVhuZC/NYaaZ/mtHbWMv/S6Q== +ts-jest@^29.2.3: + version "29.2.3" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.2.3.tgz#3d226ac36b8b820151a38f164414f9f6b412131f" + integrity sha512-yCcfVdiBFngVz9/keHin9EnsrQtQtEu3nRykNy9RVp+FiPFFbPJ3Sg6Qg4+TkmH0vMP5qsTKgXSsk80HRwvdgQ== dependencies: bs-logger "0.x" + ejs "^3.1.10" fast-json-stable-stringify "2.x" jest-util "^29.0.0" json5 "^2.2.3" @@ -8347,30 +7693,26 @@ ts-jest@^29.1.4: semver "^7.5.3" yargs-parser "^21.0.1" -ts-json-schema-generator@^1.5.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/ts-json-schema-generator/-/ts-json-schema-generator-1.5.1.tgz#7759c421240be86d393a884ad186f926b22332db" - integrity sha512-apX5qG2+NA66j7b4AJm8q/DpdTeOsjfh7A3LpKsUiil0FepkNwtN28zYgjrsiiya2/OPhsr/PSjX5FUYg79rCg== +ts-json-schema-generator@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/ts-json-schema-generator/-/ts-json-schema-generator-2.3.0.tgz#d533027cdb13b625acba0a3e931a4ba88f0e44ad" + integrity sha512-t4lBQAwZc0sOJq9LJt3NgbznIcslVnm0JeEMFq8qIRklpMRY8jlYD0YmnRWbqBKANxkby91P1XanSSlSOFpUmg== dependencies: "@types/json-schema" "^7.0.15" commander "^12.0.0" - glob "^8.0.3" + glob "^10.3.12" json5 "^2.2.3" normalize-path "^3.0.0" safe-stable-stringify "^2.4.3" - typescript "~5.4.2" + tslib "^2.6.2" + typescript "^5.4.5" tslib@^1.8.1: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.1, tslib@^2.1.0, tslib@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" - integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== - -tslib@~2.6.3: +tslib@^2.0.1, tslib@^2.1.0, tslib@^2.6.2, tslib@^2.6.3, tslib@~2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== @@ -8419,54 +7761,10 @@ type-fest@^3.8.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.13.1.tgz#bb744c1f0678bea7543a2d1ec24e83e68e8c8706" integrity sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g== -type-fest@^4.2.0: - version "4.15.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.15.0.tgz#21da206b89c15774cc718c4f2d693e13a1a14a43" - integrity sha512-tB9lu0pQpX5KJq54g+oHOLumOx+pMep4RaM6liXh2PKmVRFF+/vAtUP0ZaJ0kOySfVNjF6doBWPHhBhISKdlIA== - -typed-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" - integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - is-typed-array "^1.1.13" - -typed-array-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" - integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== - dependencies: - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - -typed-array-byte-offset@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" - integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - -typed-array-length@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" - integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== - dependencies: - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - possible-typed-array-names "^1.0.0" +type-fest@^4.2.0, type-fest@^4.6.0, type-fest@^4.7.1: + version "4.23.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.23.0.tgz#8196561a6b835175473be744f3e41e2dece1496b" + integrity sha512-ZiBujro2ohr5+Z/hZWHESLz3g08BBdrdLMieYFULJO+tWc437sn8kQsWLJoZErY8alNhxre9K4p3GURAG11n+w== typedarray-to-buffer@^3.1.5: version "3.1.5" @@ -8480,25 +7778,15 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typescript@~5.4.2, typescript@~5.4.5: - version "5.4.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" - integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== +typescript@^5.4.5, typescript@~5.5.4: + version "5.5.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" + integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== uglify-js@^3.1.4: - version "3.17.4" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" - integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== - -unbox-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" - integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== - dependencies: - call-bind "^1.0.2" - has-bigints "^1.0.2" - has-symbols "^1.0.3" - which-boxed-primitive "^1.0.2" + version "3.19.1" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.1.tgz#2d5df6a0872c43da43187968308d7741d44b8056" + integrity sha512-y/2wiW+ceTYR2TSSptAhfnEtpLaQ4Ups5zrjB2d3kuVxHj16j/QJwPl5PvuGy9uARb39J0+iKxcRPvtpsx4A4A== unbzip2-stream@1.4.3: version "1.4.3" @@ -8508,10 +7796,10 @@ unbzip2-stream@1.4.3: buffer "^5.2.1" through "^2.3.8" -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici-types@~6.11.1: + version "6.11.1" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.11.1.tgz#432ea6e8efd54a48569705a699e62d8f4981b197" + integrity sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ== unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" @@ -8558,13 +7846,13 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== -update-browserslist-db@^1.0.13: - version "1.0.13" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" - integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== +update-browserslist-db@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" + integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" + escalade "^3.1.2" + picocolors "^1.0.1" update-check@1.5.4: version "1.5.4" @@ -8574,25 +7862,25 @@ update-check@1.5.4: registry-auth-token "3.3.2" registry-url "3.1.0" -update-notifier@7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-7.0.0.tgz#295aa782dadab784ed4073f7ffaea1fb2123031c" - integrity sha512-Hv25Bh+eAbOLlsjJreVPOs4vd51rrtCrmhyOJtbpAojro34jS4KQaEp4/EvlHJX7jSO42VvEFpkastVyXyIsdQ== +update-notifier@7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-7.1.0.tgz#b8f43cc2dc094c221f179bfab9eba9f4b1469965" + integrity sha512-8SV3rIqVY6EFC1WxH6L0j55s0MO79MFBS1pivmInRJg3pCEDgWHBj1Q6XByTtCLOZIFA0f6zoG9ZWf2Ks9lvTA== dependencies: boxen "^7.1.1" chalk "^5.3.0" configstore "^6.0.0" import-lazy "^4.0.0" is-in-ci "^0.1.0" - is-installed-globally "^0.4.0" + is-installed-globally "^1.0.0" is-npm "^6.0.0" - latest-version "^7.0.0" + latest-version "^9.0.0" pupa "^3.1.0" - semver "^7.5.4" + semver "^7.6.2" semver-diff "^4.0.0" xdg-basedir "^5.1.0" -uri-js@^4.2.2, uri-js@^4.4.1: +uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== @@ -8610,9 +7898,9 @@ util-deprecate@^1.0.1: integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== v8-to-istanbul@^9.0.1: - version "9.2.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz#2ed7644a245cddd83d4e087b9b33b3e62dfd10ad" - integrity sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA== + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== dependencies: "@jridgewell/trace-mapping" "^0.3.12" "@types/istanbul-lib-coverage" "^2.0.1" @@ -8631,251 +7919,228 @@ vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== -vega-canvas@^1.2.6, vega-canvas@^1.2.7: +vega-canvas@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/vega-canvas/-/vega-canvas-1.2.7.tgz#cf62169518f5dcd91d24ad352998c2248f8974fb" integrity sha512-OkJ9CACVcN9R5Pi9uF6MZBF06pO6qFpDYHWSKBJsdHP5o724KrsgR6UvbnXFH82FdsiTOff/HqjuaG8C7FL+9Q== -vega-cli@^5.28.0: - version "5.29.0" - resolved "https://registry.yarnpkg.com/vega-cli/-/vega-cli-5.29.0.tgz#d6b2e6ecf84ce84caad689acba3b60799353187d" - integrity sha512-ndiQEjHrV0DkT7nWEroQerAuZwNZC3c9SZlmVh8a19vY9s/GsPfNdOq2apAN44mtruMtD3tzgajLLxEii7/wEA== +vega-cli@^5.30.0: + version "5.30.0" + resolved "https://registry.yarnpkg.com/vega-cli/-/vega-cli-5.30.0.tgz#0293791e3451d45798e52b7583146fdb3b297605" + integrity sha512-qHlVNh6SU/sV96Zys30t7jtVlDKAn+2Ex2EuiU8xK+DLDB8h2t0IK5/FwR8CxE9rLWHYYXDOuCxkzRqFRzSMQQ== dependencies: canvas "^2.11.2" - vega "5.29.0" + vega "5.30.0" yargs "17" -vega-crossfilter@~4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/vega-crossfilter/-/vega-crossfilter-4.1.1.tgz#3ff3ca0574883706f7a399dc6d60f4a0f065ece4" - integrity sha512-yesvlMcwRwxrtAd9IYjuxWJJuAMI0sl7JvAFfYtuDkkGDtqfLXUcCzHIATqW6igVIE7tWwGxnbfvQLhLNgK44Q== +vega-crossfilter@~4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/vega-crossfilter/-/vega-crossfilter-4.1.2.tgz#810281c279b3592310f12814bc61206dd42ca61d" + integrity sha512-J7KVEXkpfRJBfRvwLxn5vNCzQCNkrnzmDvkvwhuiwT4gPm5sk7MK5TuUP8GCl/iKYw+kWeVXEtrVHwWtug+bcQ== dependencies: d3-array "^3.2.2" - vega-dataflow "^5.7.5" - vega-util "^1.17.1" + vega-dataflow "^5.7.6" + vega-util "^1.17.2" -vega-dataflow@^5.7.3, vega-dataflow@^5.7.5, vega-dataflow@~5.7.5: - version "5.7.5" - resolved "https://registry.yarnpkg.com/vega-dataflow/-/vega-dataflow-5.7.5.tgz#0d559f3c3a968831f2995e099a2e270993ddfed9" - integrity sha512-EdsIl6gouH67+8B0f22Owr2tKDiMPNNR8lEvJDcxmFw02nXd8juimclpLvjPQriqn6ta+3Dn5txqfD117H04YA== +vega-dataflow@^5.7.6, vega-dataflow@~5.7.6: + version "5.7.6" + resolved "https://registry.yarnpkg.com/vega-dataflow/-/vega-dataflow-5.7.6.tgz#21dfad9120cb18d9aeaed578658670839d1adc95" + integrity sha512-9Md8+5iUC1MVKPKDyZ7pCEHk6I9am+DgaMzZqo/27O/KI4f23/WQXPyuI8jbNmc/mkm340P0TKREmzL5M7+2Dg== dependencies: - vega-format "^1.1.1" - vega-loader "^4.5.1" - vega-util "^1.17.1" + vega-format "^1.1.2" + vega-loader "^4.5.2" + vega-util "^1.17.2" vega-datasets@^2.8.1: version "2.8.1" resolved "https://registry.yarnpkg.com/vega-datasets/-/vega-datasets-2.8.1.tgz#a38202efff1bc763c3120954f55a7d1c9758af3f" integrity sha512-RxFyrlIH3xWzHtBDxp2vsFNCxuzNNfAtEvyufFX25UczIORyNi1T336NbM62g67k4KbDkeXzWEZHwr71qQKF8w== -vega-embed@^6.25.0: - version "6.25.0" - resolved "https://registry.yarnpkg.com/vega-embed/-/vega-embed-6.25.0.tgz#a58ed5593cde3e0653184140d88306e18fee02d5" - integrity sha512-pK99jEhZPNYgx4daiYDyNZ7f1h2ep5PyzMYN/qKzJNxzcaNf8wgmUjHrWeJSeMh8RNyw89VRphIleeg7LNLhDA== +vega-embed@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/vega-embed/-/vega-embed-6.26.0.tgz#25ca51783b2819adf6e6330ae6dd5771e8da8653" + integrity sha512-AZCTdKHDAuhp6TFZRQOOs332tStCwZr/5e4uZMNEuJL69A57cT66NNZJdNiCP6u66REzIToYtMJhMTL9wl5B3A== dependencies: fast-json-patch "^3.1.1" json-stringify-pretty-compact "^3.0.0" - semver "^7.6.0" - tslib "^2.6.2" + semver "^7.6.2" + tslib "^2.6.3" vega-interpreter "^1.0.5" vega-schema-url-parser "^2.2.0" - vega-themes "^2.14.0" + vega-themes "^2.15.0" vega-tooltip "^0.34.0" -vega-encode@~4.10.0: - version "4.10.0" - resolved "https://registry.yarnpkg.com/vega-encode/-/vega-encode-4.10.0.tgz#def64d29a0ed897abebcc9f421dbb8953adeea00" - integrity sha512-TTWIXVWHLGMkPEUC1bLkQKZdKnHUTGcjO2JST3jxHFgnGtN/HOovjaeOm2mkOoxrHJgQERyKorpGprOttuY6Kg== +vega-encode@~4.10.1: + version "4.10.1" + resolved "https://registry.yarnpkg.com/vega-encode/-/vega-encode-4.10.1.tgz#1656e20396db99c414f495704ef3d9cff99631df" + integrity sha512-d25nVKZDrg109rC65M8uxE+7iUrTxktaqgK4fU3XZBgpWlh1K4UbU5nDag7kiHVVN4tKqwgd+synEotra9TiVQ== dependencies: d3-array "^3.2.2" d3-interpolate "^3.0.1" - vega-dataflow "^5.7.5" - vega-scale "^7.3.0" - vega-util "^1.17.1" + vega-dataflow "^5.7.6" + vega-scale "^7.4.1" + vega-util "^1.17.2" vega-event-selector@^3.0.1, vega-event-selector@~3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/vega-event-selector/-/vega-event-selector-3.0.1.tgz#b99e92147b338158f8079d81b28b2e7199c2e259" integrity sha512-K5zd7s5tjr1LiOOkjGpcVls8GsH/f2CWCrWcpKy74gTCp+llCdwz0Enqo013ZlGaRNjfgD/o1caJRt3GSaec4A== -vega-expression@^5.0.1, vega-expression@^5.1.0, vega-expression@~5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/vega-expression/-/vega-expression-5.1.0.tgz#4ec0e66b56a2faba88361eb717011303bbb1ff61" - integrity sha512-u8Rzja/cn2PEUkhQN3zUj3REwNewTA92ExrcASNKUJPCciMkHJEjESwFYuI6DWMCq4hQElQ92iosOAtwzsSTqA== +vega-expression@^5.0.1, vega-expression@^5.1.1, vega-expression@~5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/vega-expression/-/vega-expression-5.1.1.tgz#9b2d287a1f34d990577c9798ae68ec88453815ef" + integrity sha512-zv9L1Hm0KHE9M7mldHyz8sXbGu3KmC0Cdk7qfHkcTNS75Jpsem6jkbu6ZAwx5cNUeW91AxUQOu77r4mygq2wUQ== dependencies: "@types/estree" "^1.0.0" - vega-util "^1.17.1" + vega-util "^1.17.2" -vega-force@~4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/vega-force/-/vega-force-4.2.0.tgz#5374d0dbac674c92620a9801e12b650b0966336a" - integrity sha512-aE2TlP264HXM1r3fl58AvZdKUWBNOGkIvn4EWyqeJdgO2vz46zSU7x7TzPG4ZLuo44cDRU5Ng3I1eQk23Asz6A== +vega-force@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/vega-force/-/vega-force-4.2.1.tgz#bdce6ec8572867b4ff2fb7e09d2894798c5358ec" + integrity sha512-2BcuuqFr77vcCyKfcpedNFeYMxi+XEFCrlgLWNx7YV0PI8pdP5y/yPkzyuE9Tb894+KkRAvfQHZRAshcnFNcMw== dependencies: d3-force "^3.0.0" - vega-dataflow "^5.7.5" - vega-util "^1.17.1" + vega-dataflow "^5.7.6" + vega-util "^1.17.2" -vega-format@^1.1.1, vega-format@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/vega-format/-/vega-format-1.1.1.tgz#92e4876e18064e7ad54f39045f7b24dede0030b8" - integrity sha512-Rll7YgpYbsgaAa54AmtEWrxaJqgOh5fXlvM2wewO4trb9vwM53KBv4Q/uBWCLK3LLGeBXIF6gjDt2LFuJAUtkQ== +vega-format@^1.1.2, vega-format@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vega-format/-/vega-format-1.1.2.tgz#d344ba8a2680144e92127459c149a4181e9e7f84" + integrity sha512-0kUfAj0dg0U6GcEY0Kp6LiSTCZ8l8jl1qVdQyToMyKmtZg/q56qsiJQZy3WWRr1MtWkTIZL71xSJXgjwjeUaAw== dependencies: d3-array "^3.2.2" d3-format "^3.1.0" d3-time-format "^4.1.0" - vega-time "^2.1.1" - vega-util "^1.17.1" + vega-time "^2.1.2" + vega-util "^1.17.2" -vega-functions@^5.13.1, vega-functions@^5.14.0, vega-functions@~5.14.0: - version "5.14.0" - resolved "https://registry.yarnpkg.com/vega-functions/-/vega-functions-5.14.0.tgz#8235157ae35c0e12f9122e3b783d693967de3c40" - integrity sha512-Q0rocHmJDfQ0tS91kdN8WcEosq1e3HPK1Yf5z36SPYPmTzKw3uxUGE52tLxC832acAYqPmi8R41wAoI/yFQTPg== +vega-functions@^5.15.0, vega-functions@~5.15.0: + version "5.15.0" + resolved "https://registry.yarnpkg.com/vega-functions/-/vega-functions-5.15.0.tgz#a7905e1dd6457efe265dbf954cbc0a5721c484b0" + integrity sha512-pCqmm5efd+3M65jrJGxEy3UGuRksmK6DnWijoSNocnxdCBxez+yqUUVX9o2pN8VxMe3648vZnR9/Vk5CXqRvIQ== dependencies: d3-array "^3.2.2" d3-color "^3.1.0" d3-geo "^3.1.0" - vega-dataflow "^5.7.5" - vega-expression "^5.1.0" - vega-scale "^7.3.0" - vega-scenegraph "^4.10.2" + vega-dataflow "^5.7.6" + vega-expression "^5.1.1" + vega-scale "^7.4.1" + vega-scenegraph "^4.13.0" vega-selections "^5.4.2" - vega-statistics "^1.8.1" - vega-time "^2.1.1" - vega-util "^1.17.1" + vega-statistics "^1.9.0" + vega-time "^2.1.2" + vega-util "^1.17.2" -vega-geo@~4.4.1: - version "4.4.1" - resolved "https://registry.yarnpkg.com/vega-geo/-/vega-geo-4.4.1.tgz#3850232bf28c98fab5e26c5fb401acb6fb37b5e5" - integrity sha512-s4WeZAL5M3ZUV27/eqSD3v0FyJz3PlP31XNSLFy4AJXHxHUeXT3qLiDHoVQnW5Om+uBCPDtTT1ROx1smGIf2aA== +vega-geo@~4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/vega-geo/-/vega-geo-4.4.2.tgz#da4a08ee39c9488bfc4fe6493779f584dd8bb412" + integrity sha512-unuV/UxUHf6UJu6GYxMZonC3SZlMfFXYLOkgEsRSvmsMPt3+CVv8FmG88dXNRUJUrdROrJepgecqx0jOwMSnGA== dependencies: d3-array "^3.2.2" d3-color "^3.1.0" d3-geo "^3.1.0" vega-canvas "^1.2.7" - vega-dataflow "^5.7.5" - vega-projection "^1.6.0" - vega-statistics "^1.8.1" - vega-util "^1.17.1" + vega-dataflow "^5.7.6" + vega-projection "^1.6.1" + vega-statistics "^1.9.0" + vega-util "^1.17.2" -vega-hierarchy@~4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/vega-hierarchy/-/vega-hierarchy-4.1.1.tgz#897974a477dfa70cc0d4efab9465b6cc79a9071f" - integrity sha512-h5mbrDtPKHBBQ9TYbvEb/bCqmGTlUX97+4CENkyH21tJs7naza319B15KRK0NWOHuhbGhFmF8T0696tg+2c8XQ== +vega-hierarchy@~4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/vega-hierarchy/-/vega-hierarchy-4.1.2.tgz#e42938c42527b392b110b1e3bf89eaa456dba1b8" + integrity sha512-m+xDtT5092YPSnV0rdTLW+AWmoCb+A54JQ66MUJwiDBpKxvfKnTiQeuiWDU2YudjUoXZN9EBOcI6QHF8H2Lu2A== dependencies: d3-hierarchy "^3.1.2" - vega-dataflow "^5.7.5" - vega-util "^1.17.1" + vega-dataflow "^5.7.6" + vega-util "^1.17.2" vega-interpreter@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/vega-interpreter/-/vega-interpreter-1.0.5.tgz#19e1d1b5f84a4ea9cb25c4e90a05ce16cd058484" integrity sha512-po6oTOmeQqr1tzTCdD15tYxAQLeUnOVirAysgVEemzl+vfmvcEP7jQmlc51jz0jMA+WsbmE6oJywisQPu/H0Bg== -vega-label@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/vega-label/-/vega-label-1.2.1.tgz#ea45fa5a407991c44edfea9c4ca40874d544a3db" - integrity sha512-n/ackJ5lc0Xs9PInCaGumYn2awomPjJ87EMVT47xNgk2bHmJoZV1Ve/1PUM6Eh/KauY211wPMrNp/9Im+7Ripg== +vega-label@~1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/vega-label/-/vega-label-1.3.0.tgz#21b3e5ef40e63f51ac987a449d183068c4961503" + integrity sha512-EfSFSCWAwVPsklM5g0gUEuohALgryuGC/SKMmsOH7dYT/bywmLBZhLVbrE+IHJAUauoGrMhYw1mqnXL/0giJBg== dependencies: - vega-canvas "^1.2.6" - vega-dataflow "^5.7.3" - vega-scenegraph "^4.9.2" - vega-util "^1.15.2" + vega-canvas "^1.2.7" + vega-dataflow "^5.7.6" + vega-scenegraph "^4.13.0" + vega-util "^1.17.2" -vega-loader@^4.5.1, vega-loader@~4.5.1: - version "4.5.1" - resolved "https://registry.yarnpkg.com/vega-loader/-/vega-loader-4.5.1.tgz#b85262b3cb8376487db0c014a8a13c3a5e6d52ad" - integrity sha512-qy5x32SaT0YkEujQM2yKqvLGV9XWQ2aEDSugBFTdYzu/1u4bxdUSRDREOlrJ9Km3RWIOgFiCkobPmFxo47SKuA== +vega-loader@^4.5.2, vega-loader@~4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/vega-loader/-/vega-loader-4.5.2.tgz#7212f093c397b153f69f7e6cfef47817c17c5c01" + integrity sha512-ktIdGz3DRIS3XfTP9lJ6oMT5cKwC86nQkjUbXZbOtwXQFVNE2xVWBuH13GP6FKUZxg5hJCMtb5v/e/fwTvhKsQ== dependencies: d3-dsv "^3.0.1" node-fetch "^2.6.7" topojson-client "^3.1.0" - vega-format "^1.1.1" - vega-util "^1.17.1" + vega-format "^1.1.2" + vega-util "^1.17.2" -vega-parser@~6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/vega-parser/-/vega-parser-6.3.0.tgz#64233674dba48b68494e7d95dd15b4c27ef15993" - integrity sha512-swS5RuP2imRarMpGWaAZusoKkXc4Z5WxWx349pkqxIAf4F7H8Ya9nThEkSWsFozd75O9nWh0QLifds8Xb7KjUg== +vega-parser@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/vega-parser/-/vega-parser-6.4.0.tgz#6a12f07f0f9178492a17842efe7e1f51a2d36bed" + integrity sha512-/hFIJs0yITxfvLIfhhcpUrcbKvu4UZYoMGmly5PSsbgo60oAsVQW8ZbX2Ji3iNFqZJh1ifoX/P0j+9wep1OISw== dependencies: - vega-dataflow "^5.7.5" + vega-dataflow "^5.7.6" vega-event-selector "^3.0.1" - vega-functions "^5.14.0" - vega-scale "^7.3.1" + vega-functions "^5.15.0" + vega-scale "^7.4.1" vega-util "^1.17.2" -vega-projection@^1.6.0, vega-projection@~1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/vega-projection/-/vega-projection-1.6.0.tgz#921acd3220e7d9d04ccd5ce0109433afb3236966" - integrity sha512-LGUaO/kpOEYuTlul+x+lBzyuL9qmMwP1yShdUWYLW+zXoeyGbs5OZW+NbPPwLYqJr5lpXDr/vGztFuA/6g2xvQ== +vega-projection@^1.6.1, vega-projection@~1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/vega-projection/-/vega-projection-1.6.1.tgz#da687abc60f4a93bb888385beb23e0a1000f8b57" + integrity sha512-sqfnAAHumU7MWU1tQN3b6HNgKGF3legek0uLHhjLKcDJQxEc7kwcD18txFz2ffQks6d5j+AUhBiq4GARWf0DEQ== dependencies: d3-geo "^3.1.0" d3-geo-projection "^4.0.0" - vega-scale "^7.3.0" + vega-scale "^7.4.1" -vega-regression@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/vega-regression/-/vega-regression-1.2.0.tgz#12e9df88cf49994ac1a1799f64fb9c118a77a5e0" - integrity sha512-6TZoPlhV/280VbxACjRKqlE0Nv48z5g4CSNf1FmGGTWS1rQtElPTranSoVW4d7ET5eVQ6f9QLxNAiALptvEq+g== +vega-regression@~1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/vega-regression/-/vega-regression-1.3.0.tgz#3e68e234fa9460041fac082c6a3469c896d436a8" + integrity sha512-gxOQfmV7Ft/MYKpXDEo09WZyBuKOBqxqDRWay9KtfGq/E0Y4vbTPsWLv2cB1ToPJdKE6XSN6Re9tCIw5M/yMUg== dependencies: d3-array "^3.2.2" - vega-dataflow "^5.7.3" + vega-dataflow "^5.7.6" vega-statistics "^1.9.0" - vega-util "^1.15.2" - -vega-runtime@^6.1.4, vega-runtime@~6.1.4: - version "6.1.4" - resolved "https://registry.yarnpkg.com/vega-runtime/-/vega-runtime-6.1.4.tgz#98b67160cea9554e690bfd44719f9d17f90c4220" - integrity sha512-0dDYXyFLQcxPQ2OQU0WuBVYLRZnm+/CwVu6i6N4idS7R9VXIX5581EkCh3pZ20pQ/+oaA7oJ0pR9rJgJ6rukRQ== - dependencies: - vega-dataflow "^5.7.5" - vega-util "^1.17.1" + vega-util "^1.17.2" -vega-scale@^7.3.0, vega-scale@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/vega-scale/-/vega-scale-7.3.1.tgz#5cb23d1edcf5d759e25fe40b7608a6132a62da46" - integrity sha512-tyTlaaCpHN2Ik/PPKl/j9ThadBDjPtypqW1D7IsUSkzfoZ7RPlI2jwAaoj2C/YW5jFRbEOx3njmjogp48I5CvA== +vega-runtime@^6.2.0, vega-runtime@~6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/vega-runtime/-/vega-runtime-6.2.0.tgz#10f435089fff11d8e1b49cb0cbab8041731e6f06" + integrity sha512-30UXbujWjKNd5aeP+oeHuwFmzuyVYlBj4aDy9+AjfWLECu8wJt4K01vwegcaGPdCWcPLVIv4Oa9Lob4mcXn5KQ== dependencies: - d3-array "^3.2.2" - d3-interpolate "^3.0.1" - d3-scale "^4.0.2" - vega-time "^2.1.1" - vega-util "^1.17.1" + vega-dataflow "^5.7.6" + vega-util "^1.17.2" -vega-scale@~7.4.0: - version "7.4.0" - resolved "https://registry.yarnpkg.com/vega-scale/-/vega-scale-7.4.0.tgz#05f12ac9cfa40d219b17adecb77f1ecdf7e3e1e8" - integrity sha512-+GxjtToQiR2OqnlvRsnVTaX/HGLG9EPiFWkIwSG5ZCLSAxm0CRiqAQvvRmj0HEeIw8F92aGRX4rSoM8qyGAK5A== +vega-scale@^7.4.1, vega-scale@~7.4.1: + version "7.4.1" + resolved "https://registry.yarnpkg.com/vega-scale/-/vega-scale-7.4.1.tgz#2dcd3e39ebb00269b03a8be86e44c7b48c67442a" + integrity sha512-dArA28DbV/M92O2QvswnzCmQ4bq9WwLKUoyhqFYWCltmDwkmvX7yhqiFLFMWPItIm7mi4Qyoygby6r4DKd1X2A== dependencies: d3-array "^3.2.2" d3-interpolate "^3.0.1" d3-scale "^4.0.2" d3-scale-chromatic "^3.1.0" - vega-time "^2.1.1" - vega-util "^1.17.1" - -vega-scenegraph@^4.10.2, vega-scenegraph@^4.9.2: - version "4.11.2" - resolved "https://registry.yarnpkg.com/vega-scenegraph/-/vega-scenegraph-4.11.2.tgz#7e9cad503c95fb5af22691bbd394faa8a0b97ce9" - integrity sha512-PXSvv/L7Ek+9mwOTPLpzgkXdfGCR+AcWV5aquPGrqCWoiIF49VJkKFNT1HWxj3RZJX0XKo2r7SuXvRBb9EJ1aA== - dependencies: - d3-path "^3.1.0" - d3-shape "^3.2.0" - vega-canvas "^1.2.7" - vega-loader "^4.5.1" - vega-scale "^7.3.0" - vega-util "^1.17.1" + vega-time "^2.1.2" + vega-util "^1.17.2" -vega-scenegraph@~4.12.0: - version "4.12.0" - resolved "https://registry.yarnpkg.com/vega-scenegraph/-/vega-scenegraph-4.12.0.tgz#ac4b08a84f6980b90c4ab2f80a186887ccf444e5" - integrity sha512-l0Us6TLRV7AAd1CxB6mvxXt9/psknqgrr0+6d1zNWtHL8tGszPE4FqllZC5m4ZtUouvE4PWKGybd5uJR0dpchw== +vega-scenegraph@^4.13.0, vega-scenegraph@~4.13.0: + version "4.13.0" + resolved "https://registry.yarnpkg.com/vega-scenegraph/-/vega-scenegraph-4.13.0.tgz#c4fa5c82773f6244a9ca8b01a44e380adf03fabd" + integrity sha512-nfl45XtuqB5CxyIZJ+bbJ+dofzosPCRlmF+eUQo+0J23NkNXsTzur+1krJDSdhcw0SOYs4sbYRoMz1cpuOM4+Q== dependencies: d3-path "^3.1.0" d3-shape "^3.2.0" vega-canvas "^1.2.7" - vega-loader "^4.5.1" - vega-scale "^7.3.0" - vega-util "^1.17.1" + vega-loader "^4.5.2" + vega-scale "^7.4.1" + vega-util "^1.17.2" vega-schema-url-parser@^2.2.0: version "2.2.0" @@ -8891,26 +8156,26 @@ vega-selections@^5.4.2: vega-expression "^5.0.1" vega-util "^1.17.1" -vega-statistics@^1.8.1, vega-statistics@^1.9.0, vega-statistics@~1.9.0: +vega-statistics@^1.9.0, vega-statistics@~1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/vega-statistics/-/vega-statistics-1.9.0.tgz#7d6139cea496b22d60decfa6abd73346f70206f9" integrity sha512-GAqS7mkatpXcMCQKWtFu1eMUKLUymjInU0O8kXshWaQrVWjPIO2lllZ1VNhdgE0qGj4oOIRRS11kzuijLshGXQ== dependencies: d3-array "^3.2.2" -vega-themes@^2.14.0: - version "2.14.0" - resolved "https://registry.yarnpkg.com/vega-themes/-/vega-themes-2.14.0.tgz#0df269396e057123ecf3942e3b704bf125d1eed7" - integrity sha512-9dLmsUER7gJrDp8SEYKxBFmXmpyzLlToKIjxq3HCvYjz8cnNrRGyAhvIlKWOB3ZnGvfYV+vnv3ZRElSNL31nkA== +vega-themes@^2.15.0: + version "2.15.0" + resolved "https://registry.yarnpkg.com/vega-themes/-/vega-themes-2.15.0.tgz#cf7592efb45406957e9beb67d7033ee5f7b7a511" + integrity sha512-DicRAKG9z+23A+rH/3w3QjJvKnlGhSbbUXGjBvYGseZ1lvj9KQ0BXZ2NS/+MKns59LNpFNHGi9us/wMlci4TOA== -vega-time@^2.1.1, vega-time@~2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/vega-time/-/vega-time-2.1.1.tgz#0f1fb4e220dd5ed57401b58fb2293241f049ada0" - integrity sha512-z1qbgyX0Af2kQSGFbApwBbX2meenGvsoX8Nga8uyWN8VIbiySo/xqizz1KrP6NbB6R+x5egKmkjdnyNThPeEWA== +vega-time@^2.1.2, vega-time@~2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/vega-time/-/vega-time-2.1.2.tgz#0c414e74780613d6d3234fb97f19b50c0ebd9f49" + integrity sha512-6rXc6JdDt8MnCRy6UzUCsa6EeFycPDmvioMddLfKw38OYCV8pRQC5nw44gyddOwXgUTJLiCtn/sp53P0iA542A== dependencies: d3-array "^3.2.2" d3-time "^3.1.0" - vega-util "^1.17.1" + vega-util "^1.17.2" vega-tooltip@^0.34.0: version "0.34.0" @@ -8919,107 +8184,107 @@ vega-tooltip@^0.34.0: dependencies: vega-util "^1.17.2" -vega-transforms@~4.11.1: - version "4.11.1" - resolved "https://registry.yarnpkg.com/vega-transforms/-/vega-transforms-4.11.1.tgz#bc1291c49337eb465c3ead1ac0297cd8dd98d74a" - integrity sha512-DDbqEQnvy9/qEvv0bAKPqAuzgaNb7Lh2xKJFom2Yzx4tZHCl8dnKxC1lH9JnJlAMdtZuiNLPARUkf3pCNQ/olw== +vega-transforms@~4.12.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/vega-transforms/-/vega-transforms-4.12.0.tgz#6a69e0b67934b0c0a40a6f607fdb543bf749955e" + integrity sha512-bh/2Qbj85O70mjfLRgPKAsABArgSUP0k+GjmaY54zukIRxoGxKju+85nigeX/aR/INpEqNWif+5lL+NvmyWA5w== dependencies: d3-array "^3.2.2" - vega-dataflow "^5.7.5" - vega-statistics "^1.8.1" - vega-time "^2.1.1" - vega-util "^1.17.1" + vega-dataflow "^5.7.6" + vega-statistics "^1.9.0" + vega-time "^2.1.2" + vega-util "^1.17.2" -vega-typings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/vega-typings/-/vega-typings-1.1.0.tgz#95bee43fff8a3c9cb921dd5aee2ea87c7f4ca58b" - integrity sha512-uI6RWlMiGRhsgmw/LzJtjCc0kwhw2f0JpyNMTAnOy90kE4e4CiaZN5nJp8S9CcfcBoPEZHc166AOn2SSNrKn3A== +vega-typings@~1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/vega-typings/-/vega-typings-1.3.1.tgz#025a6031505794b44d9b6e2c49d4551b8918d4ae" + integrity sha512-j9Sdgmvowz09jkMgTFGVfiv7ycuRP/TQkdHRPXIYwt3RDgPQn7inyFcJ8C8ABFt4MiMWdjOwbneF6KWW8TRXIw== dependencies: "@types/geojson" "7946.0.4" vega-event-selector "^3.0.1" - vega-expression "^5.1.0" + vega-expression "^5.1.1" vega-util "^1.17.2" -vega-util@^1.15.2, vega-util@^1.17.1, vega-util@^1.17.2, vega-util@~1.17.2: +vega-util@^1.17.1, vega-util@^1.17.2, vega-util@~1.17.2: version "1.17.2" resolved "https://registry.yarnpkg.com/vega-util/-/vega-util-1.17.2.tgz#f69aa09fd5d6110c19c4a0f0af9e35945b99987d" integrity sha512-omNmGiZBdjm/jnHjZlywyYqafscDdHaELHx1q96n5UOz/FlO9JO99P4B3jZg391EFG8dqhWjQilSf2JH6F1mIw== -vega-view-transforms@~4.5.9: - version "4.5.9" - resolved "https://registry.yarnpkg.com/vega-view-transforms/-/vega-view-transforms-4.5.9.tgz#5f109555c08ee9ac23ff9183d578eb9cbac6fe61" - integrity sha512-NxEq4ZD4QwWGRrl2yDLnBRXM9FgCI+vvYb3ZC2+nVDtkUxOlEIKZsMMw31op5GZpfClWLbjCT3mVvzO2xaTF+g== +vega-view-transforms@~4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/vega-view-transforms/-/vega-view-transforms-4.6.0.tgz#829d56ca3c8116b0dded4ec0502f4ac70253de9a" + integrity sha512-z3z66aJTA3ZRo4oBY4iBXnn+A4KqBGZT/UrlKDbm+7Ec+Ip+hK2tF8Kmhp/WNcMsDZoUWFqLJgR2VgOgvJk9RA== dependencies: - vega-dataflow "^5.7.5" - vega-scenegraph "^4.10.2" - vega-util "^1.17.1" + vega-dataflow "^5.7.6" + vega-scenegraph "^4.13.0" + vega-util "^1.17.2" -vega-view@~5.12.1: - version "5.12.1" - resolved "https://registry.yarnpkg.com/vega-view/-/vega-view-5.12.1.tgz#923f81eace6344b6157d64b7eea1b2a4324f1537" - integrity sha512-9TdF35FTZNzfvfj+YM38vHOgfeGxMy2xMY+2B46ZHoustt3J/mxtfueu3RGFsGIitUGhFrmLeEHxlVHP/tY+sQ== +vega-view@~5.13.0: + version "5.13.0" + resolved "https://registry.yarnpkg.com/vega-view/-/vega-view-5.13.0.tgz#8ea96da9fcdf42fe7c0e95fe6258933477524745" + integrity sha512-ZPAAQ3iYz6YrQjJoDT+0bcxJkXt9PKF5v4OO7Omw8PFhkIv++jFXeKlQTW1bBtyQ92dkdGGHv5lYY67Djqjf3A== dependencies: d3-array "^3.2.2" d3-timer "^3.0.1" - vega-dataflow "^5.7.5" - vega-format "^1.1.1" - vega-functions "^5.13.1" - vega-runtime "^6.1.4" - vega-scenegraph "^4.10.2" - vega-util "^1.17.1" + vega-dataflow "^5.7.6" + vega-format "^1.1.2" + vega-functions "^5.15.0" + vega-runtime "^6.2.0" + vega-scenegraph "^4.13.0" + vega-util "^1.17.2" -vega-voronoi@~4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/vega-voronoi/-/vega-voronoi-4.2.2.tgz#f2068ddd01d184047c4f18bceb14dbf5edab2854" - integrity sha512-Bq2YOp2MGphhQnUuLwl3dsyBs6MuEU86muTjDbBJg33+HkZtE1kIoQZr+EUHa46NBsY1NzSKddOTu8wcaFrWiQ== +vega-voronoi@~4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/vega-voronoi/-/vega-voronoi-4.2.3.tgz#54c4bb96b9b94c3fa0160bee24695dcb9d583fe1" + integrity sha512-aYYYM+3UGqwsOx+TkVtF1IZfguy0H7AN79dR8H0nONRIc+vhk/lbnlkgwY2nSzEu0EZ4b5wZxeGoDBEVmdDEcg== dependencies: d3-delaunay "^6.0.2" - vega-dataflow "^5.7.5" - vega-util "^1.17.1" + vega-dataflow "^5.7.6" + vega-util "^1.17.2" -vega-wordcloud@~4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/vega-wordcloud/-/vega-wordcloud-4.1.4.tgz#38584cf47ef52325d6a8dc38908b5d2378cc6e62" - integrity sha512-oeZLlnjiusLAU5vhk0IIdT5QEiJE0x6cYoGNq1th+EbwgQp153t4r026fcib9oq15glHFOzf81a8hHXHSJm1Jw== +vega-wordcloud@~4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/vega-wordcloud/-/vega-wordcloud-4.1.5.tgz#789c9e67225c77f3f35a6fc052beec1c2bdc8b5e" + integrity sha512-p+qXU3cb9VeWzJ/HEdax0TX2mqDJcSbrCIfo2d/EalOXGkvfSLKobsmMQ8DxPbtVp0uhnpvfCGDyMJw+AzcI2A== dependencies: vega-canvas "^1.2.7" - vega-dataflow "^5.7.5" - vega-scale "^7.3.0" - vega-statistics "^1.8.1" - vega-util "^1.17.1" + vega-dataflow "^5.7.6" + vega-scale "^7.4.1" + vega-statistics "^1.9.0" + vega-util "^1.17.2" -vega@5.29.0: - version "5.29.0" - resolved "https://registry.yarnpkg.com/vega/-/vega-5.29.0.tgz#84b989cb258b74ee18e3a13ad82149bd9573b139" - integrity sha512-4+pX8UIxV1rtHpIKvzHXof5CeyMTGKMDFtuN8UmSjvJ+l5FtSen++qmSxbAc/EnkLqo5i9B2iCYTr2og77EBrA== +vega@5.30.0: + version "5.30.0" + resolved "https://registry.yarnpkg.com/vega/-/vega-5.30.0.tgz#d12350c829878b481453ab28ce10855a954df06d" + integrity sha512-ZGoC8LdfEUV0LlXIuz7hup9jxuQYhSaWek2M7r9dEHAPbPrzSQvKXZ0BbsJbrarM100TGRpTVN/l1AFxCwDkWw== dependencies: - vega-crossfilter "~4.1.1" - vega-dataflow "~5.7.5" - vega-encode "~4.10.0" + vega-crossfilter "~4.1.2" + vega-dataflow "~5.7.6" + vega-encode "~4.10.1" vega-event-selector "~3.0.1" - vega-expression "~5.1.0" - vega-force "~4.2.0" - vega-format "~1.1.1" - vega-functions "~5.14.0" - vega-geo "~4.4.1" - vega-hierarchy "~4.1.1" - vega-label "~1.2.1" - vega-loader "~4.5.1" - vega-parser "~6.3.0" - vega-projection "~1.6.0" - vega-regression "~1.2.0" - vega-runtime "~6.1.4" - vega-scale "~7.4.0" - vega-scenegraph "~4.12.0" + vega-expression "~5.1.1" + vega-force "~4.2.1" + vega-format "~1.1.2" + vega-functions "~5.15.0" + vega-geo "~4.4.2" + vega-hierarchy "~4.1.2" + vega-label "~1.3.0" + vega-loader "~4.5.2" + vega-parser "~6.4.0" + vega-projection "~1.6.1" + vega-regression "~1.3.0" + vega-runtime "~6.2.0" + vega-scale "~7.4.1" + vega-scenegraph "~4.13.0" vega-statistics "~1.9.0" - vega-time "~2.1.1" - vega-transforms "~4.11.1" - vega-typings "~1.1.0" + vega-time "~2.1.2" + vega-transforms "~4.12.0" + vega-typings "~1.3.1" vega-util "~1.17.2" - vega-view "~5.12.1" - vega-view-transforms "~4.5.9" - vega-voronoi "~4.2.2" - vega-wordcloud "~4.1.4" + vega-view "~5.13.0" + vega-view-transforms "~4.6.0" + vega-voronoi "~4.2.3" + vega-wordcloud "~4.1.5" wait-on@^7.2.0: version "7.2.0" @@ -9064,28 +8329,6 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" - -which-typed-array@^1.1.14, which-typed-array@^1.1.15: - version "1.1.15" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" - integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.2" - which@^1.2.12: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" @@ -9126,6 +8369,11 @@ windows-release@^5.0.1: dependencies: execa "^5.1.1" +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + wordwrap@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" @@ -9260,6 +8508,11 @@ yocto-queue@^0.1.0: integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== yocto-queue@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" - integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== + version "1.1.1" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.1.1.tgz#fef65ce3ac9f8a32ceac5a634f74e17e5b232110" + integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g== + +yoctocolors-cjs@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz#f4b905a840a37506813a7acaa28febe97767a242" + integrity sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA== From c5e6e664484352ac63c839c07e6000a723a21da9 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Sun, 28 Jul 2024 14:11:25 -0400 Subject: [PATCH 02/24] ci: use node 22 --- .github/workflows/check-toc.yml | 2 +- .github/workflows/check.yml | 2 +- .github/workflows/release-docs-and-schema.yml | 4 +- .github/workflows/test-docs.yml | 2 +- .github/workflows/test.yml | 6 +- build/vega-lite-schema.json | 104 +++++++----------- 6 files changed, 46 insertions(+), 74 deletions(-) diff --git a/.github/workflows/check-toc.yml b/.github/workflows/check-toc.yml index f0a86f775b..99e1b32408 100644 --- a/.github/workflows/check-toc.yml +++ b/.github/workflows/check-toc.yml @@ -24,7 +24,7 @@ jobs: uses: actions/setup-node@v4 with: cache: 'yarn' - node-version: 21 + node-version: 22 - name: Install Node dependencies run: yarn --frozen-lockfile diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 911fa813c1..8ebf25b6b9 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -19,7 +19,7 @@ jobs: uses: actions/setup-node@v4 with: cache: 'yarn' - node-version: 21 + node-version: 22 - name: Install Node dependencies run: yarn --frozen-lockfile diff --git a/.github/workflows/release-docs-and-schema.yml b/.github/workflows/release-docs-and-schema.yml index 183c00e0c1..c34f3b7975 100644 --- a/.github/workflows/release-docs-and-schema.yml +++ b/.github/workflows/release-docs-and-schema.yml @@ -26,7 +26,7 @@ jobs: uses: actions/setup-node@v4 with: cache: 'yarn' - node-version: 21 + node-version: 22 - name: Install Node dependencies run: yarn --frozen-lockfile @@ -62,7 +62,7 @@ jobs: uses: actions/setup-node@v4 with: cache: 'yarn' - node-version: 21 + node-version: 22 - name: Setup Pages uses: actions/configure-pages@v5 diff --git a/.github/workflows/test-docs.yml b/.github/workflows/test-docs.yml index bdb107b208..287eaeaebd 100644 --- a/.github/workflows/test-docs.yml +++ b/.github/workflows/test-docs.yml @@ -20,7 +20,7 @@ jobs: uses: actions/setup-node@v4 with: cache: 'yarn' - node-version: 21 + node-version: 22 - name: Install Node dependencies run: yarn --frozen-lockfile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fa56b7ed3b..2b9bdc282d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: uses: actions/setup-node@v4 with: cache: 'yarn' - node-version: 21 + node-version: 22 - name: Install Node dependencies run: yarn --frozen-lockfile @@ -44,7 +44,7 @@ jobs: uses: actions/setup-node@v4 with: cache: 'yarn' - node-version: 21 + node-version: 22 - name: Install Node dependencies run: yarn --frozen-lockfile @@ -88,7 +88,7 @@ jobs: uses: actions/setup-node@v4 with: cache: 'yarn' - node-version: 21 + node-version: 22 - name: Install Node dependencies run: yarn --frozen-lockfile diff --git a/build/vega-lite-schema.json b/build/vega-lite-schema.json index ba6489df88..61ecd11300 100644 --- a/build/vega-lite-schema.json +++ b/build/vega-lite-schema.json @@ -4428,46 +4428,37 @@ ] }, "BinnedTimeUnit": { - "anyOf": [ - { - "enum": [ - "binnedyear", - "binnedyearquarter", - "binnedyearquartermonth", - "binnedyearmonth", - "binnedyearmonthdate", - "binnedyearmonthdatehours", - "binnedyearmonthdatehoursminutes", - "binnedyearmonthdatehoursminutesseconds", - "binnedyearweek", - "binnedyearweekday", - "binnedyearweekdayhours", - "binnedyearweekdayhoursminutes", - "binnedyearweekdayhoursminutesseconds", - "binnedyeardayofyear" - ], - "type": "string" - }, - { - "enum": [ - "binnedutcyear", - "binnedutcyearquarter", - "binnedutcyearquartermonth", - "binnedutcyearmonth", - "binnedutcyearmonthdate", - "binnedutcyearmonthdatehours", - "binnedutcyearmonthdatehoursminutes", - "binnedutcyearmonthdatehoursminutesseconds", - "binnedutcyearweek", - "binnedutcyearweekday", - "binnedutcyearweekdayhours", - "binnedutcyearweekdayhoursminutes", - "binnedutcyearweekdayhoursminutesseconds", - "binnedutcyeardayofyear" - ], - "type": "string" - } - ] + "enum": [ + "binnedyear", + "binnedyearquarter", + "binnedyearquartermonth", + "binnedyearmonth", + "binnedyearmonthdate", + "binnedyearmonthdatehours", + "binnedyearmonthdatehoursminutes", + "binnedyearmonthdatehoursminutesseconds", + "binnedyearweek", + "binnedyearweekday", + "binnedyearweekdayhours", + "binnedyearweekdayhoursminutes", + "binnedyearweekdayhoursminutesseconds", + "binnedyeardayofyear", + "binnedutcyear", + "binnedutcyearquarter", + "binnedutcyearquartermonth", + "binnedutcyearmonth", + "binnedutcyearmonthdate", + "binnedutcyearmonthdatehours", + "binnedutcyearmonthdatehoursminutes", + "binnedutcyearmonthdatehoursminutesseconds", + "binnedutcyearweek", + "binnedutcyearweekday", + "binnedutcyearweekdayhours", + "binnedutcyearweekdayhoursminutes", + "binnedutcyearweekdayhoursminutesseconds", + "binnedutcyeardayofyear" + ], + "type": "string" }, "Blend": { "enum": [ @@ -4761,7 +4752,8 @@ "set2", "set3", "tableau10", - "tableau20" + "tableau20", + "observable10" ], "type": "string" }, @@ -19238,29 +19230,9 @@ "type": "object" }, "ParseValue": { - "anyOf": [ - { - "type": "null" - }, - { - "type": "string" - }, - { - "const": "string", - "type": "string" - }, - { - "const": "boolean", - "type": "string" - }, - { - "const": "date", - "type": "string" - }, - { - "const": "number", - "type": "string" - } + "type": [ + "string", + "null" ] }, "PivotTransform": { @@ -27672,6 +27644,8 @@ }, "SingleDefUnitChannel": { "enum": [ + "text", + "shape", "x", "y", "xOffset", @@ -27696,9 +27670,7 @@ "strokeDash", "size", "angle", - "shape", "key", - "text", "href", "url", "description" From ca5f706469b3030de7aa5c5472dc15b5b3f1541f Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Sun, 28 Jul 2024 14:16:47 -0400 Subject: [PATCH 03/24] ci: node 20 --- .github/workflows/check-toc.yml | 2 +- .github/workflows/check.yml | 2 +- .github/workflows/release-docs-and-schema.yml | 4 ++-- .github/workflows/test-docs.yml | 2 +- .github/workflows/test.yml | 6 +++--- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/check-toc.yml b/.github/workflows/check-toc.yml index 99e1b32408..4a7a158a9f 100644 --- a/.github/workflows/check-toc.yml +++ b/.github/workflows/check-toc.yml @@ -24,7 +24,7 @@ jobs: uses: actions/setup-node@v4 with: cache: 'yarn' - node-version: 22 + node-version: 20 - name: Install Node dependencies run: yarn --frozen-lockfile diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 8ebf25b6b9..17ebf3547f 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -19,7 +19,7 @@ jobs: uses: actions/setup-node@v4 with: cache: 'yarn' - node-version: 22 + node-version: 20 - name: Install Node dependencies run: yarn --frozen-lockfile diff --git a/.github/workflows/release-docs-and-schema.yml b/.github/workflows/release-docs-and-schema.yml index c34f3b7975..b0651b17e7 100644 --- a/.github/workflows/release-docs-and-schema.yml +++ b/.github/workflows/release-docs-and-schema.yml @@ -26,7 +26,7 @@ jobs: uses: actions/setup-node@v4 with: cache: 'yarn' - node-version: 22 + node-version: 20 - name: Install Node dependencies run: yarn --frozen-lockfile @@ -62,7 +62,7 @@ jobs: uses: actions/setup-node@v4 with: cache: 'yarn' - node-version: 22 + node-version: 20 - name: Setup Pages uses: actions/configure-pages@v5 diff --git a/.github/workflows/test-docs.yml b/.github/workflows/test-docs.yml index 287eaeaebd..ea24b5fe76 100644 --- a/.github/workflows/test-docs.yml +++ b/.github/workflows/test-docs.yml @@ -20,7 +20,7 @@ jobs: uses: actions/setup-node@v4 with: cache: 'yarn' - node-version: 22 + node-version: 20 - name: Install Node dependencies run: yarn --frozen-lockfile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2b9bdc282d..deeb021c76 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: uses: actions/setup-node@v4 with: cache: 'yarn' - node-version: 22 + node-version: 20 - name: Install Node dependencies run: yarn --frozen-lockfile @@ -44,7 +44,7 @@ jobs: uses: actions/setup-node@v4 with: cache: 'yarn' - node-version: 22 + node-version: 20 - name: Install Node dependencies run: yarn --frozen-lockfile @@ -88,7 +88,7 @@ jobs: uses: actions/setup-node@v4 with: cache: 'yarn' - node-version: 22 + node-version: 20 - name: Install Node dependencies run: yarn --frozen-lockfile From eb26d70e79bd84ebd8739b7d5a1d9bd5c5f85d31 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Sun, 28 Jul 2024 15:37:05 -0400 Subject: [PATCH 04/24] work towards not needing suppressImplicitAnyIndexErrors --- src/aggregate.ts | 10 +++++----- src/axis.ts | 3 ++- src/bin.ts | 4 ++-- src/channel.ts | 9 +++++---- src/channeldef.ts | 5 +++-- src/config.ts | 39 ++++++++++++++++++++------------------- src/encoding.ts | 22 +++++++++++----------- src/mark.ts | 6 +++--- src/predicate.ts | 8 ++++---- src/scale.ts | 8 ++++---- src/spec/base.ts | 6 +++--- src/spec/repeat.ts | 3 ++- src/spec/toplevel.ts | 2 +- src/transform.ts | 9 +++++---- src/util.ts | 25 ++++++++++++++++++------- tsconfig.json | 1 - 16 files changed, 88 insertions(+), 72 deletions(-) diff --git a/src/aggregate.ts b/src/aggregate.ts index 910db866f0..66f381332d 100644 --- a/src/aggregate.ts +++ b/src/aggregate.ts @@ -1,7 +1,7 @@ import {AggregateOp} from 'vega'; -import {isString} from 'vega-util'; +import {hasOwnProperty, isString} from 'vega-util'; import {FieldName} from './channeldef'; -import {contains, Flag} from './util'; +import {contains, Flag, hasKey} from './util'; const AGGREGATE_OP_INDEX: Flag = { argmax: 1, @@ -50,15 +50,15 @@ export type NonArgAggregateOp = Exclude; export type Aggregate = NonArgAggregateOp | ArgmaxDef | ArgminDef; export function isArgminDef(a: Aggregate | string): a is ArgminDef { - return !!a && !!a['argmin']; + return !!a && hasKey(a, 'argmin'); } export function isArgmaxDef(a: Aggregate | string): a is ArgmaxDef { - return !!a && !!a['argmax']; + return !!a && hasKey(a, 'argmax'); } export function isAggregateOp(a: string | ArgminDef | ArgmaxDef): a is AggregateOp { - return isString(a) && !!AGGREGATE_OP_INDEX[a]; + return isString(a) && hasOwnProperty(AGGREGATE_OP_INDEX, a); } export const COUNTING_OPS = new Set([ diff --git a/src/axis.ts b/src/axis.ts index c5da6bea5c..078ba2e482 100644 --- a/src/axis.ts +++ b/src/axis.ts @@ -19,6 +19,7 @@ import {ExprRef} from './expr'; import {Guide, GuideEncodingEntry, TitleMixins, VlOnlyGuideConfig} from './guide'; import {Flag, keys} from './util'; import {MapExcludeValueRefAndReplaceSignalWith, VgEncodeChannel} from './vega.schema'; +import {hasOwnProperty} from 'vega-util'; export type BaseAxisNoValueRefs = AxisOverrideMixins & VLOnlyAxisMixins & @@ -569,7 +570,7 @@ const AXIS_PROPERTIES_INDEX: Flag> = { }; export function isAxisProperty(prop: string): prop is keyof Axis { - return !!AXIS_PROPERTIES_INDEX[prop]; + return hasOwnProperty(AXIS_PROPERTIES_INDEX, prop); } // Export for dependent projects diff --git a/src/bin.ts b/src/bin.ts index 33148722b0..e94230b31b 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -16,7 +16,7 @@ import { } from './channel'; import {normalizeBin} from './channeldef'; import {ParameterExtent} from './selection'; -import {entries, keys, varName} from './util'; +import {entries, hasKey, keys, varName} from './util'; export interface BaseBin { /** @@ -124,7 +124,7 @@ export function isBinParams(bin: BinParams | boolean | 'binned'): bin is BinPara } export function isParameterExtent(extent: unknown): extent is ParameterExtent { - return extent?.['param']; + return hasKey(extent, 'param'); } export function autoMaxBins(channel?: ExtendedChannel): number { diff --git a/src/channel.ts b/src/channel.ts index a66d21143e..a37ebdc4e5 100644 --- a/src/channel.ts +++ b/src/channel.ts @@ -3,6 +3,7 @@ * such as 'x', 'y', 'color'. */ +import {hasOwnProperty} from 'vega-util'; import {RangeType} from './compile/scale/type'; import {Encoding} from './encoding'; import {Mark} from './mark'; @@ -200,11 +201,11 @@ export const SINGLE_DEF_UNIT_CHANNELS = keys(SINGLE_DEF_UNIT_CHANNEL_INDEX); export type SingleDefUnitChannel = (typeof SINGLE_DEF_UNIT_CHANNELS)[number]; export function isSingleDefUnitChannel(str: string): str is SingleDefUnitChannel { - return !!SINGLE_DEF_UNIT_CHANNEL_INDEX[str]; + return hasOwnProperty(SINGLE_DEF_UNIT_CHANNEL_INDEX, str); } export function isChannel(str: string): str is Channel { - return !!CHANNEL_INDEX[str]; + return hasOwnProperty(CHANNEL_INDEX, str); } export type SecondaryRangeChannel = 'x2' | 'y2' | 'latitude2' | 'longitude2' | 'theta2' | 'radius2'; @@ -444,7 +445,7 @@ export const NONPOSITION_SCALE_CHANNELS = keys(NONPOSITION_SCALE_CHANNEL_INDEX); export type NonPositionScaleChannel = (typeof NONPOSITION_SCALE_CHANNELS)[number]; export function isNonPositionScaleChannel(channel: Channel): channel is NonPositionScaleChannel { - return !!NONPOSITION_CHANNEL_INDEX[channel]; + return hasOwnProperty(NONPOSITION_CHANNEL_INDEX, channel); } /** @@ -481,7 +482,7 @@ export const SCALE_CHANNELS = keys(SCALE_CHANNEL_INDEX); export type ScaleChannel = (typeof SCALE_CHANNELS)[number]; export function isScaleChannel(channel: ExtendedChannel): channel is ScaleChannel { - return !!SCALE_CHANNEL_INDEX[channel]; + return hasOwnProperty(SCALE_CHANNEL_INDEX, channel); } export type SupportedMark = Partial>; diff --git a/src/channeldef.ts b/src/channeldef.ts index 0b8b96f0eb..6cdb09fb82 100644 --- a/src/channeldef.ts +++ b/src/channeldef.ts @@ -1,4 +1,4 @@ -import {Gradient, ScaleType, SignalRef, Text} from 'vega'; +import {Gradient, hasOwnProperty, ScaleType, SignalRef, Text} from 'vega'; import {isArray, isBoolean, isNumber, isString} from 'vega-util'; import {Aggregate, isAggregateOp, isArgmaxDef, isArgminDef, isCountingAggregateOp} from './aggregate'; import {Axis} from './axis'; @@ -83,6 +83,7 @@ import { Dict, flatAccessWithDatum, getFirstDefined, + hasKey, internalField, omit, removePathFromField, @@ -157,7 +158,7 @@ export type ConditionalPredicate = { export type ConditionalParameter = ParameterPredicate & CD; export function isConditionalParameter(c: Conditional): c is ConditionalParameter { - return c['param']; + return hasKey(c, 'param'); } export interface ConditionValueDefMixins { diff --git a/src/config.ts b/src/config.ts index 4956261e2c..7a1cbf0fba 100644 --- a/src/config.ts +++ b/src/config.ts @@ -25,7 +25,7 @@ import {defaultConfig as defaultSelectionConfig, SelectionConfig} from './select import {BaseViewBackground, CompositionConfigMixins, DEFAULT_SPACING, isStep} from './spec/base'; import {TopLevelProperties} from './spec/toplevel'; import {extractTitleConfig, TitleConfig} from './title'; -import {duplicate, getFirstDefined, isEmpty, keys, omit} from './util'; +import {duplicate, getFirstDefined, hasKey, isEmpty, keys, omit} from './util'; export interface ViewConfig extends BaseViewBackground { /** @@ -72,7 +72,7 @@ export function getViewConfigContinuousSize( viewConfig: ViewConfig, channel: 'width' | 'height' ) { - return viewConfig[channel] ?? viewConfig[channel === 'width' ? 'continuousWidth' : 'continuousHeight']; // get width/height for backwards compatibility + return (viewConfig as any)[channel] ?? viewConfig[channel === 'width' ? 'continuousWidth' : 'continuousHeight']; // get width/height for backwards compatibility } export function getViewConfigDiscreteStep( @@ -87,7 +87,7 @@ export function getViewConfigDiscreteSize( viewConfig: ViewConfig, channel: 'width' | 'height' ) { - const size = viewConfig[channel] ?? viewConfig[channel === 'width' ? 'discreteWidth' : 'discreteHeight']; // get width/height for backwards compatibility + const size = (viewConfig as any)[channel] ?? viewConfig[channel === 'width' ? 'discreteWidth' : 'discreteHeight']; // get width/height for backwards compatibility return getFirstDefined(size, {step: viewConfig.step}); } @@ -100,7 +100,7 @@ export const defaultViewConfig: ViewConfig = { }; export function isVgScheme(rangeScheme: string[] | RangeScheme): rangeScheme is RangeScheme { - return rangeScheme && !!rangeScheme['scheme']; + return rangeScheme && hasKey(rangeScheme, 'scheme'); } export type ColorConfig = Record; @@ -517,7 +517,7 @@ function getAxisConfigInternal(axisConfig: AxisConfig) { const axisConfigInternal: AxisConfig = {}; for (const prop of props) { const val = axisConfig[prop]; - axisConfigInternal[prop as any] = isConditionalAxisValue(val) + (axisConfigInternal as any)[prop] = isConditionalAxisValue(val) ? signalOrValueRefWithCondition(val) : signalRefOrValue(val); } @@ -571,11 +571,9 @@ export function initConfig(specifiedConfig: Config = {}): Config { const outputConfig: Config = omit(mergedConfig, configPropsWithExpr); - for (const prop of ['background', 'lineBreak', 'padding']) { - if (mergedConfig[prop]) { - outputConfig[prop] = signalRefOrValue(mergedConfig[prop]); - } - } + outputConfig['background'] = signalRefOrValue(mergedConfig['background']); + outputConfig['lineBreak'] = signalRefOrValue(mergedConfig['lineBreak']); + outputConfig['padding'] = signalRefOrValue(mergedConfig['padding']); for (const markConfigType of mark.MARK_CONFIGS) { if (mergedConfig[markConfigType]) { @@ -678,8 +676,8 @@ export function stripAndRedirectConfig(config: Config) { if (config.axis) { // delete condition axis config for (const prop in config.axis) { - if (isConditionalAxisValue(config.axis[prop])) { - delete config.axis[prop]; + if (isConditionalAxisValue(config.axis[prop as keyof AxisConfig])) { + delete config.axis[prop as keyof AxisConfig]; } } } @@ -709,14 +707,14 @@ export function stripAndRedirectConfig(config: Config) { for (const markType of MARK_STYLES) { // Remove Vega-Lite-only mark config for (const prop of VL_ONLY_MARK_CONFIG_PROPERTIES) { - delete config[markType][prop]; + delete (config as any)[markType][prop]; } // Remove Vega-Lite only mark-specific config const vlOnlyMarkSpecificConfigs = VL_ONLY_ALL_MARK_SPECIFIC_CONFIG_PROPERTY_INDEX[markType]; if (vlOnlyMarkSpecificConfigs) { for (const prop of vlOnlyMarkSpecificConfigs) { - delete config[markType][prop]; + delete (config as any)[markType][prop]; } } @@ -728,15 +726,16 @@ export function stripAndRedirectConfig(config: Config) { for (const m of getAllCompositeMarks()) { // Clean up the composite mark config as we don't need them in the output specs anymore - delete config[m]; + delete (config as any)[m]; } redirectTitleConfig(config); // Remove empty config objects. for (const prop in config) { - if (isObject(config[prop]) && isEmpty(config[prop])) { - delete config[prop]; + const p = prop as keyof typeof config; + if (isObject(config[p]) && isEmpty(config[p])) { + delete config[p]; } } @@ -781,7 +780,9 @@ function redirectConfigToStyleConfig( toProp?: string, compositeMarkPart?: string ) { - const propConfig: MarkConfig = compositeMarkPart ? config[prop][compositeMarkPart] : config[prop]; + const propConfig: MarkConfig = compositeMarkPart + ? (config as any)[prop][compositeMarkPart] + : config[prop as keyof Config]; if (prop === 'view') { toProp = 'cell'; // View's default style is "cell" @@ -799,6 +800,6 @@ function redirectConfigToStyleConfig( if (!compositeMarkPart) { // For composite mark, so don't delete the whole config yet as we have to do multiple redirections. - delete config[prop]; + delete config[prop as keyof Config]; } } diff --git a/src/encoding.ts b/src/encoding.ts index 0dafbc975e..d37bab1d78 100644 --- a/src/encoding.ts +++ b/src/encoding.ts @@ -457,7 +457,7 @@ export function extractTransformsFromEncoding(oldEncoding: Encoding, config } newFieldDef.bin = 'binned'; if (!isSecondaryRangeChannel(channel)) { - newFieldDef['type'] = QUANTITATIVE; + (newFieldDef as any)['type'] = QUANTITATIVE; } } else if (timeUnit && !isBinnedTimeUnit(timeUnit)) { timeUnits.push({ @@ -470,16 +470,16 @@ export function extractTransformsFromEncoding(oldEncoding: Encoding, config const formatType = isTypedFieldDef(channelDef) && channelDef.type !== TEMPORAL && 'time'; if (formatType) { if (channel === TEXT || channel === TOOLTIP) { - newFieldDef['formatType'] = formatType; + (newFieldDef as any)['formatType'] = formatType; } else if (isNonPositionScaleChannel(channel)) { - newFieldDef['legend'] = { + (newFieldDef as any)['legend'] = { formatType, - ...newFieldDef['legend'] + ...(newFieldDef as any)['legend'] }; } else if (isXorY(channel)) { - newFieldDef['axis'] = { + (newFieldDef as any)['axis'] = { formatType, - ...newFieldDef['axis'] + ...(newFieldDef as any)['axis'] }; } } @@ -487,14 +487,14 @@ export function extractTransformsFromEncoding(oldEncoding: Encoding, config } // now the field should refer to post-transformed field instead - encoding[channel as any] = newFieldDef; + (encoding as any)[channel as any] = newFieldDef; } else { groupby.push(field); - encoding[channel as any] = oldEncoding[channel]; + (encoding as any)[channel as any] = oldEncoding[channel]; } } else { // For value def / signal ref / datum def, just copy - encoding[channel as any] = oldEncoding[channel]; + (encoding as any)[channel as any] = oldEncoding[channel]; } }); @@ -628,7 +628,7 @@ export function initEncoding( continue; } - normalizedEncoding[channel as any] = initChannelDef(channelDef as ChannelDef, channel, config); + (normalizedEncoding as any)[channel as any] = initChannelDef(channelDef as ChannelDef, channel, config); } } return normalizedEncoding; @@ -642,7 +642,7 @@ export function normalizeEncoding(encoding: Encoding, config: Config): E for (const channel of keys(encoding)) { const newChannelDef = initChannelDef(encoding[channel], channel, config, {compositeMark: true}); - normalizedEncoding[channel as any] = newChannelDef; + (normalizedEncoding as any)[channel as any] = newChannelDef; } return normalizedEncoding; diff --git a/src/mark.ts b/src/mark.ts index 16d3d385c1..fd0eed3fdc 100644 --- a/src/mark.ts +++ b/src/mark.ts @@ -1,7 +1,7 @@ import {Align, Color, Gradient, MarkConfig as VgMarkConfig, Orientation, SignalRef, TextBaseline} from 'vega'; import {CompositeMark, CompositeMarkDef} from './compositemark'; import {ExprRef} from './expr'; -import {Flag, keys} from './util'; +import {Flag, hasKey, keys} from './util'; import {MapExcludeValueRefAndReplaceSignalWith} from './vega.schema'; import {MarkInvalidMixins} from './invalid'; @@ -291,7 +291,7 @@ export interface RectBinSpacingMixins { export type AnyMark = CompositeMark | CompositeMarkDef | Mark | MarkDef; export function isMarkDef(mark: string | GenericMarkDef): mark is GenericMarkDef { - return mark['type']; + return hasKey(mark, 'type'); } export function isPrimitiveMark(mark: AnyMark): mark is Mark { @@ -453,7 +453,7 @@ export interface RelativeBandSize { } export function isRelativeBandSize(o: number | RelativeBandSize | ExprRef | SignalRef): o is RelativeBandSize { - return o && o['band'] != undefined; + return o && hasKey(o, 'band'); } export const BAR_CORNER_RADIUS_INDEX: Partial< diff --git a/src/predicate.ts b/src/predicate.ts index b981099e60..89f45ff15b 100644 --- a/src/predicate.ts +++ b/src/predicate.ts @@ -2,11 +2,11 @@ import type {SignalRef} from 'vega'; import {isArray} from 'vega-util'; import {FieldName, valueExpr, vgField} from './channeldef'; import {DateTime} from './datetime'; -import {ExprRef} from './expr'; +import {ExprRef, isExprRef, replaceExprRef} from './expr'; import {LogicalComposition} from './logical'; import {ParameterName} from './parameter'; import {fieldExpr as timeUnitFieldExpr, normalizeTimeUnit, TimeUnit, TimeUnitParams, BinnedTimeUnit} from './timeunit'; -import {stringify} from './util'; +import {hasKey, stringify} from './util'; import {isSignalRef} from './vega.schema'; export type Predicate = @@ -48,7 +48,7 @@ export interface ParameterPredicate { } export function isSelectionPredicate(predicate: LogicalComposition): predicate is ParameterPredicate { - return predicate?.['param']; + return hasKey(predicate, 'param'); } export interface FieldPredicateBase { @@ -227,7 +227,7 @@ export function fieldFilterExpression(predicate: FieldPredicate, useInRange = tr } else if (isFieldValidPredicate(predicate)) { return fieldValidPredicate(fieldExpr, predicate.valid); } else if (isFieldRangePredicate(predicate)) { - const {range} = predicate; + const {range} = replaceExprRef(predicate); const lower = isSignalRef(range) ? {signal: `${range.signal}[0]`} : range[0]; const upper = isSignalRef(range) ? {signal: `${range.signal}[1]`} : range[1]; diff --git a/src/scale.ts b/src/scale.ts index 38033dcc0a..f8cc6080cc 100644 --- a/src/scale.ts +++ b/src/scale.ts @@ -18,7 +18,7 @@ import {ScaleInvalidDataConfigMixins} from './invalid'; import * as log from './log'; import {ParameterExtent} from './selection'; import {NOMINAL, ORDINAL, QUANTITATIVE, TEMPORAL, Type} from './type'; -import {contains, Flag, keys} from './util'; +import {contains, Flag, hasKey, keys} from './util'; export const ScaleType = { // Continuous - Quantitative @@ -483,11 +483,11 @@ export type Domain = export type Scheme = string | SchemeParams; export function isExtendedScheme(scheme: Scheme | SignalRef): scheme is SchemeParams { - return !isString(scheme) && !!scheme['name']; + return !isString(scheme) && hasKey(scheme, 'name'); } export function isParameterDomain(domain: Domain): domain is ParameterExtent { - return domain?.['param']; + return hasKey(domain, 'param'); } export interface DomainUnionWith { @@ -499,7 +499,7 @@ export interface DomainUnionWith { } export function isDomainUnionWith(domain: Domain): domain is DomainUnionWith { - return domain?.['unionWith']; + return hasKey(domain, 'unionWith'); } export interface FieldRange { diff --git a/src/spec/base.ts b/src/spec/base.ts index 578542719a..9867011877 100644 --- a/src/spec/base.ts +++ b/src/spec/base.ts @@ -310,9 +310,9 @@ export function extractCompositionLayout( // Then copy properties from the spec for (const prop of COMPOSITION_LAYOUT_PROPERTIES) { - if (spec[prop] !== undefined) { + if ((spec as any)[prop] !== undefined) { if (prop === 'spacing') { - const spacing: number | RowCol = spec[prop]; + const spacing: number | RowCol = (spec as any)[prop]; layout[prop] = isNumber(spacing) ? spacing @@ -321,7 +321,7 @@ export function extractCompositionLayout( column: spacing.column ?? spacingConfig }; } else { - (layout[prop] as any) = spec[prop]; + (layout[prop] as any) = (spec as any)[prop]; } } } diff --git a/src/spec/repeat.ts b/src/spec/repeat.ts index be4808e41e..fd816c6cd5 100644 --- a/src/spec/repeat.ts +++ b/src/spec/repeat.ts @@ -3,6 +3,7 @@ import {LayerSpec, NonNormalizedSpec} from '.'; import {Field} from '../channeldef'; import {BaseSpec, GenericCompositionLayoutWithColumns, ResolveMixins} from './base'; import {UnitSpecWithFrame} from './unit'; +import {hasKey} from '../util'; export interface RepeatMapping { /** @@ -61,5 +62,5 @@ export function isRepeatSpec(spec: BaseSpec): spec is RepeatSpec { } export function isLayerRepeatSpec(spec: RepeatSpec): spec is LayerRepeatSpec { - return !isArray(spec.repeat) && spec.repeat['layer']; + return !isArray(spec.repeat) && hasKey(spec.repeat, 'layer'); } diff --git a/src/spec/toplevel.ts b/src/spec/toplevel.ts index 6a488e2b9b..dd245bfced 100644 --- a/src/spec/toplevel.ts +++ b/src/spec/toplevel.ts @@ -122,7 +122,7 @@ export function extractTopLevelProperties(t: TopLevelProperties, includeParams: const o: TopLevelProperties = {}; for (const p of TOP_LEVEL_PROPERTIES) { if (t && t[p] !== undefined) { - o[p as any] = signalRefOrValue(t[p]); + (o as any)[p] = signalRefOrValue(t[p]); } } if (includeParams) { diff --git a/src/transform.ts b/src/transform.ts index 4db847b71c..a37f759ba5 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -8,6 +8,7 @@ import {ParameterName} from './parameter'; import {normalizePredicate, Predicate} from './predicate'; import {SortField} from './sort'; import {TimeUnit, TimeUnitTransformParams} from './timeunit'; +import {hasKey} from './util'; export interface FilterTransform { /** @@ -259,7 +260,7 @@ export interface ImputeSequence { } export function isImputeSequence(t: ImputeSequence | any[] | undefined): t is ImputeSequence { - return t?.['stop'] !== undefined; + return hasKey(t, 'stop'); } export interface ImputeTransform extends ImputeParams { @@ -366,15 +367,15 @@ export interface LookupTransform { } export function isLookup(t: Transform): t is LookupTransform { - return 'lookup' in t; + return hasKey(t, 'lookup'); } export function isLookupData(from: LookupData | LookupSelection): from is LookupData { - return 'data' in from; + return hasKey(from, 'data'); } export function isLookupSelection(from: LookupData | LookupSelection): from is LookupSelection { - return 'param' in from; + return hasKey(from, 'param'); } export interface FoldTransform { diff --git a/src/util.ts b/src/util.ts index 832043a667..fc37076324 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,5 +1,6 @@ import {hasOwnProperty, isNumber, isString, splitAccessPath, stringValue, writeConfig} from 'vega-util'; import {isLogicalAnd, isLogicalNot, isLogicalOr, LogicalComposition} from './logical'; +import {isObject} from 'vega'; export const duplicate = structuredClone; @@ -41,7 +42,7 @@ export function omit(obj: T, props: readonl /** * Monkey patch Set so that `stringify` produces a string representation of sets. */ -Set.prototype['toJSON'] = function () { +(Set.prototype as any)['toJSON'] = function () { return `Set(${[...this].map(x => stringify(x)).join(',')})`; }; @@ -134,7 +135,7 @@ export function unique(values: readonly T[], f: (item: T) => string | number) if (v in u) { continue; } - u[v] = 1; + (u as any)[v] = 1; results.push(val); } return results; @@ -407,7 +408,7 @@ export function deepEqual(a: any, b: any) { if (a.constructor.name !== b.constructor.name) return false; let length; - let i; + let i: number; if (Array.isArray(a)) { length = a.length; @@ -418,21 +419,21 @@ export function deepEqual(a: any, b: any) { if (a instanceof Map && b instanceof Map) { if (a.size !== b.size) return false; - for (i of a.entries()) if (!b.has(i[0])) return false; - for (i of a.entries()) if (!deepEqual(i[1], b.get(i[0]))) return false; + for (const e of a.entries()) if (!b.has(e[0])) return false; + for (const e of a.entries()) if (!deepEqual(e[1], b.get(e[0]))) return false; return true; } if (a instanceof Set && b instanceof Set) { if (a.size !== b.size) return false; - for (i of a.entries()) if (!b.has(i[0])) return false; + for (const e of a.entries()) if (!b.has(e[0])) return false; return true; } if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) { length = (a as any).length; if (length != (b as any).length) return false; - for (i = length; i-- !== 0; ) if (a[i] !== b[i]) return false; + for (i = length; i-- !== 0; ) if ((a as any)[i] !== (b as any)[i]) return false; return true; } @@ -509,3 +510,13 @@ export function stringify(data: any) { return `{${out}}`; })(data); } + +/** + * Check if the input object has the key and it's not undefined. + * @param object the object + * @param key the key to search + * @returns if the object has the key and it's not undefined. + */ +export function hasKey(object: unknown, key: string) { + return isObject(object) && hasOwnProperty(object, key) && (object as any)[key] !== undefined; +} diff --git a/tsconfig.json b/tsconfig.json index d6d2f78743..6dd949a405 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,6 @@ "strictNullChecks": false, "preserveConstEnums": true, "resolveJsonModule": true, - "suppressImplicitAnyIndexErrors": true, "isolatedModules": true, "lib": ["ESNext.Array", "DOM", "DOM.Iterable", "ES2021.String"], "ignoreDeprecations": "5.0", From 2ecb1e3a3539cbe7a15ca72723bec76caf86d187 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Tue, 30 Jul 2024 19:50:52 -0400 Subject: [PATCH 05/24] compiles again --- site/static/index.ts | 10 ++-- src/channel.ts | 6 +- src/channeldef.ts | 58 +++++++++---------- src/compile/axis/assemble.ts | 22 ++++--- src/compile/axis/config.ts | 10 ++-- src/compile/axis/parse.ts | 2 +- src/compile/common.ts | 23 ++++---- src/compile/data/aggregate.ts | 4 +- src/compile/data/debug.ts | 4 +- src/compile/data/facet.ts | 2 +- src/compile/data/formatparse.ts | 5 +- src/compile/data/optimizers.ts | 2 +- src/compile/data/parse.ts | 4 +- src/compile/data/timeunit.ts | 4 +- src/compile/facet.ts | 14 ++--- src/compile/header/assemble.ts | 8 +-- src/compile/header/common.ts | 2 +- src/compile/legend/assemble.ts | 2 +- src/compile/legend/encode.ts | 53 ++++++++--------- src/compile/legend/parse.ts | 13 +++-- src/compile/mark/encode/base.ts | 17 ++++-- src/compile/mark/encode/color.ts | 4 +- src/compile/mark/encode/offset.ts | 3 +- src/compile/mark/encode/position-align.ts | 5 +- src/compile/mark/encode/position-rect.ts | 8 ++- src/compile/mark/encode/tooltip.ts | 10 ++-- src/compile/mark/init.ts | 2 +- src/compile/model.ts | 2 +- src/compile/resolve.ts | 4 +- src/compile/scale/domain.ts | 2 +- src/compile/scale/parse.ts | 2 +- src/compile/scale/properties.ts | 39 ++++++------- src/compile/selection/inputs.ts | 2 +- src/compile/selection/interval.ts | 30 ++++++---- src/compile/selection/parse.ts | 10 ++-- src/compile/unit.ts | 12 ++-- src/compositemark/common.ts | 6 +- src/compositemark/errorbar.ts | 2 +- src/config.ts | 2 +- src/data.ts | 13 +++-- src/datetime.ts | 4 +- src/expr.ts | 4 +- src/logical.ts | 8 ++- src/mark.ts | 15 ++++- src/normalize/core.ts | 6 +- src/normalize/repeater.ts | 9 +-- src/normalize/selectioncompat.ts | 9 +-- src/normalize/toplevelselection.ts | 2 +- src/predicate.ts | 2 +- src/sort.ts | 5 +- src/spec/base.ts | 8 +-- src/spec/concat.ts | 7 ++- src/spec/facet.ts | 7 ++- src/spec/layer.ts | 3 +- src/spec/repeat.ts | 2 +- src/spec/toplevel.ts | 2 +- src/spec/unit.ts | 3 +- src/timeunit.ts | 9 +-- src/transform.ts | 36 ++++++------ src/type.ts | 3 +- src/util.ts | 14 +++-- src/vega.schema.ts | 12 ++-- test-runtime/discrete.test.ts | 10 ++-- test-runtime/interval.test.ts | 20 +++---- test-runtime/resolve.test.ts | 8 +-- test-runtime/toggle.test.ts | 2 +- test-runtime/translate.test.ts | 32 +++++----- test-runtime/util.ts | 11 ++-- test-runtime/zoom.test.ts | 23 ++++---- test/compile/axis/properties.test.ts | 8 +-- test/compile/compile.test.ts | 2 +- test/compile/legend/encode.test.ts | 6 +- test/compile/mark/area.test.ts | 3 +- test/compile/mark/encode/color.test.ts | 4 +- .../mark/encode/position-point.test.ts | 2 +- .../mark/encode/position-range.test.ts | 6 +- .../compile/mark/encode/position-rect.test.ts | 28 ++++----- test/compile/mark/line.test.ts | 2 +- test/compile/mark/point.test.ts | 24 ++++---- test/compile/model.test.ts | 2 +- test/compile/scale/parse.test.ts | 4 +- test/compile/scale/range.test.ts | 5 +- test/compositemark/boxplot.test.ts | 38 ++++++------ test/compositemark/errorbar.test.ts | 42 +++++++------- test/config.test.ts | 6 +- test/normalize/core.test.ts | 6 +- 86 files changed, 470 insertions(+), 417 deletions(-) diff --git a/site/static/index.ts b/site/static/index.ts index eded94b542..9a342574f0 100644 --- a/site/static/index.ts +++ b/site/static/index.ts @@ -20,8 +20,8 @@ import {compile, TopLevelSpec} from '../../src'; import {post} from './post'; import {runStreamingExample} from './streaming'; -window['runStreamingExample'] = runStreamingExample; -window['embedExample'] = embedExample; +(window as any)['runStreamingExample'] = runStreamingExample; +(window as any)['embedExample'] = embedExample; hljs.registerLanguage('javascript', javascript); hljs.registerLanguage('typescript', typescript); @@ -131,13 +131,13 @@ async function getSpec(el: d3.BaseType) { } } -window['changeSpec'] = (elId: string, newSpec: string) => { +(window as any)['changeSpec'] = (elId: string, newSpec: string) => { const el = document.getElementById(elId); select(el).attr('data-name', newSpec); getSpec(el); }; -window['buildSpecOpts'] = (id: string, baseName: string) => { +(window as any)['buildSpecOpts'] = (id: string, baseName: string) => { const oldName = select(`#${id}`).attr('data-name'); const prefixSel = select(`select[name=${id}]`); const inputsSel = selectAll(`input[name=${id}]:checked`); @@ -149,7 +149,7 @@ window['buildSpecOpts'] = (id: string, baseName: string) => { .join('_'); const newName = baseName + prefix + (values ? `_${values}` : ''); if (oldName !== newName) { - window['changeSpec'](id, newName); + (window as any)['changeSpec'](id, newName); } }; diff --git a/src/channel.ts b/src/channel.ts index a37ebdc4e5..16eff8c7c6 100644 --- a/src/channel.ts +++ b/src/channel.ts @@ -92,7 +92,7 @@ const POLAR_POSITION_CHANNEL_INDEX = { export type PolarPositionChannel = keyof typeof POLAR_POSITION_CHANNEL_INDEX; export function isPolarPositionChannel(c: Channel): c is PolarPositionChannel { - return c in POLAR_POSITION_CHANNEL_INDEX; + return hasOwnProperty(POLAR_POSITION_CHANNEL_INDEX, c); } const GEO_POSIITON_CHANNEL_INDEX = { @@ -118,7 +118,7 @@ export function getPositionChannelFromLatLong(channel: GeoPositionChannel): Posi } export function isGeoPositionChannel(c: Channel): c is GeoPositionChannel { - return c in GEO_POSIITON_CHANNEL_INDEX; + return hasOwnProperty(GEO_POSIITON_CHANNEL_INDEX, c); } export const GEOPOSITION_CHANNELS = keys(GEO_POSIITON_CHANNEL_INDEX); @@ -422,7 +422,7 @@ export const OFFSET_SCALE_CHANNELS = keys(OFFSET_SCALE_CHANNEL_INDEX); export type OffsetScaleChannel = (typeof OFFSET_SCALE_CHANNELS)[0]; export function isXorYOffset(channel: Channel): channel is OffsetScaleChannel { - return channel in OFFSET_SCALE_CHANNEL_INDEX; + return hasOwnProperty(OFFSET_SCALE_CHANNEL_INDEX, channel); } // NON_POSITION_SCALE_CHANNEL = SCALE_CHANNELS without position / offset diff --git a/src/channeldef.ts b/src/channeldef.ts index 6cdb09fb82..bb894b134b 100644 --- a/src/channeldef.ts +++ b/src/channeldef.ts @@ -1,4 +1,4 @@ -import {Gradient, hasOwnProperty, ScaleType, SignalRef, Text} from 'vega'; +import {Gradient, ScaleType, SignalRef, Text} from 'vega'; import {isArray, isBoolean, isNumber, isString} from 'vega-util'; import {Aggregate, isAggregateOp, isArgmaxDef, isArgminDef, isCountingAggregateOp} from './aggregate'; import {Axis} from './axis'; @@ -220,7 +220,7 @@ export type FieldName = string; export type Field = FieldName | RepeatRef; export function isRepeatRef(field: Field | any): field is RepeatRef { - return field && !isString(field) && 'repeat' in field; + return field && !isString(field) && hasKey(field, 'repeat'); } /** @@hidden */ @@ -357,7 +357,7 @@ export interface SortableFieldDef< } export function isSortableFieldDef(fieldDef: FieldDef): fieldDef is SortableFieldDef { - return 'sort' in fieldDef; + return hasKey(fieldDef, 'sort'); } export type ScaleFieldDef< @@ -648,7 +648,7 @@ export interface OrderOnlyDef { export function isOrderOnlyDef( orderDef: OrderFieldDef | OrderFieldDef[] | OrderValueDef | OrderOnlyDef ): orderDef is OrderOnlyDef { - return orderDef && !!(orderDef as OrderOnlyDef).sort && !orderDef['field']; + return hasKey(orderDef, 'sort') && !hasKey(orderDef, 'field'); } export type OrderValueDef = ConditionValueDefMixins & NumericValueDef; @@ -661,7 +661,7 @@ export type ChannelDef = Encoding[keyof Encoding export function isConditionalDef | GuideEncodingConditionalValueDef | ExprRef | SignalRef>( channelDef: CD ): channelDef is CD & {condition: Conditional} { - return channelDef && 'condition' in channelDef; + return hasKey(channelDef, 'condition'); } /** @@ -670,21 +670,21 @@ export function isConditionalDef | GuideEncodingCondi export function hasConditionalFieldDef( channelDef: Partial> ): channelDef is {condition: Conditional>} { - const condition = channelDef?.['condition']; + const condition = (channelDef as any)?.['condition']; return !!condition && !isArray(condition) && isFieldDef(condition); } export function hasConditionalFieldOrDatumDef( channelDef: ChannelDef ): channelDef is {condition: Conditional>} { - const condition = channelDef?.['condition']; + const condition = (channelDef as any)?.['condition']; return !!condition && !isArray(condition) && isFieldOrDatumDef(condition); } export function hasConditionalValueDef( channelDef: ChannelDef ): channelDef is ValueDef & {condition: Conditional> | Conditional>[]} { - const condition = channelDef?.['condition']; + const condition = (channelDef as any)?.['condition']; return !!condition && (isArray(condition) || isValueDef(condition)); } @@ -692,17 +692,17 @@ export function isFieldDef( channelDef: Partial> | FieldDefBase | DatumDef ): channelDef is FieldDefBase | TypedFieldDef | SecondaryFieldDef { // TODO: we can't use field in channelDef here as it's somehow failing runtime test - return channelDef && (!!channelDef['field'] || channelDef['aggregate'] === 'count'); + return hasKey(channelDef, 'field') || (channelDef as any)?.aggregate === 'count'; } export function channelDefType(channelDef: ChannelDef): Type | undefined { - return channelDef?.['type']; + return (channelDef as any)?.['type']; } export function isDatumDef( channelDef: Partial> | FieldDefBase | DatumDef ): channelDef is DatumDef { - return channelDef && 'datum' in channelDef; + return hasKey(channelDef, 'datum'); } export function isContinuousFieldOrDatumDef( @@ -728,33 +728,37 @@ export function isFieldOrDatumDef( } export function isTypedFieldDef(channelDef: ChannelDef): channelDef is TypedFieldDef { - return channelDef && ('field' in channelDef || channelDef['aggregate'] === 'count') && 'type' in channelDef; + return ( + channelDef && + (hasKey(channelDef, 'field') || (channelDef as any)['aggregate'] === 'count') && + hasKey(channelDef, 'type') + ); } export function isValueDef(channelDef: Partial>): channelDef is ValueDef { - return channelDef && 'value' in channelDef && 'value' in channelDef; + return hasKey(channelDef, 'value'); } export function isScaleFieldDef(channelDef: ChannelDef): channelDef is ScaleFieldDef { - return channelDef && ('scale' in channelDef || 'sort' in channelDef); + return hasKey(channelDef, 'scale') || hasKey(channelDef, 'sort'); } export function isPositionFieldOrDatumDef( channelDef: ChannelDef ): channelDef is PositionFieldDef | PositionDatumDef { - return channelDef && ('axis' in channelDef || 'stack' in channelDef || 'impute' in channelDef); + return hasKey(channelDef, 'axis') || hasKey(channelDef, 'stack') || hasKey(channelDef, 'impute'); } export function isMarkPropFieldOrDatumDef( channelDef: ChannelDef ): channelDef is MarkPropFieldDef | MarkPropDatumDef { - return channelDef && 'legend' in channelDef; + return hasKey(channelDef, 'legend'); } export function isStringFieldOrDatumDef( channelDef: ChannelDef ): channelDef is StringFieldDef | StringDatumDef { - return channelDef && ('format' in channelDef || 'formatType' in channelDef); + return hasKey(channelDef, 'format') || hasKey(channelDef, 'formatType'); } export function toStringFieldDef(fieldDef: FieldDef): StringFieldDef { @@ -783,7 +787,7 @@ export interface FieldRefOption { function isOpFieldDef( fieldDef: FieldDefBase | WindowFieldDef | AggregatedFieldDef ): fieldDef is WindowFieldDef | AggregatedFieldDef { - return 'op' in fieldDef; + return hasKey(fieldDef, 'op'); } /** @@ -911,11 +915,7 @@ export function functionalTitleFormatter(fieldDef: FieldDefBase) { const timeUnitParams = timeUnit && !isBinnedTimeUnit(timeUnit) ? normalizeTimeUnit(timeUnit) : undefined; const fn = aggregate || timeUnitParams?.unit || (timeUnitParams?.maxbins && 'timeunit') || (isBinning(bin) && 'bin'); - if (fn) { - return `${fn.toUpperCase()}(${field})`; - } else { - return field; - } + return fn ? `${fn.toUpperCase()}(${field})` : field; } export const defaultTitleFormatter: FieldTitleFormatter = (fieldDef: FieldDefBase, config: Config) => { @@ -1102,8 +1102,8 @@ export function initFieldOrDatumDef( : isFacetFieldDef(fd) ? 'header' : null; - if (guideType && fd[guideType]) { - const {format, formatType, ...newGuide} = fd[guideType]; + if (guideType && (fd as any)[guideType]) { + const {format, formatType, ...newGuide} = (fd as any)[guideType]; if (isCustomFormatType(formatType) && !config.customFormatTypes) { log.warn(log.message.customFormatTypeNotAllowed(channel)); return initFieldOrDatumDef({...fd, [guideType]: newGuide}, channel, config, opt); @@ -1177,7 +1177,7 @@ export function initFieldDef( } else if (!isSecondaryRangeChannel(channel)) { // If type is empty / invalid, then augment with default type const newType = defaultType(fieldDef as TypedFieldDef, channel); - fieldDef['type'] = newType; + (fieldDef as any)['type'] = newType; } if (isTypedFieldDef(fieldDef)) { @@ -1195,7 +1195,7 @@ export function initFieldDef( sort: {encoding: sort} }; } - const sub = sort.substr(1); + const sub = sort.substring(1); if (sort.charAt(0) === '-' && isSortByChannel(sub)) { return { ...fieldDef, @@ -1304,7 +1304,7 @@ export function channelCompatibility( case RADIUS2: case X2: case Y2: - if (type === 'nominal' && !fieldDef['sort']) { + if (type === 'nominal' && !(fieldDef as any)['sort']) { return { compatible: false, warning: `Channel ${channel} should not be used with an unsorted discrete field.` @@ -1346,7 +1346,7 @@ export function isFieldOrDatumDefForTimeFormat(fieldOrDatumDef: FieldDef * Check if field def has type `temporal`. If you want to also cover field defs that use a time format, use `isTimeFormatFieldDef`. */ export function isTimeFieldDef(def: FieldDef | DatumDef): boolean { - return def && (def['type'] === 'temporal' || (isFieldDef(def) && !!def.timeUnit)); + return def && ((def as any)['type'] === 'temporal' || (isFieldDef(def) && !!def.timeUnit)); } /** diff --git a/src/compile/axis/assemble.ts b/src/compile/axis/assemble.ts index b165a2cfc4..efbc4754b2 100644 --- a/src/compile/axis/assemble.ts +++ b/src/compile/axis/assemble.ts @@ -1,6 +1,12 @@ import {Axis as VgAxis, AxisEncode, NewSignal, SignalRef, Text} from 'vega'; import {array, isArray} from 'vega-util'; -import {AXIS_PARTS, AXIS_PROPERTY_TYPE, CONDITIONAL_AXIS_PROP_INDEX, isConditionalAxisValue} from '../../axis'; +import { + AXIS_PARTS, + AXIS_PROPERTY_TYPE, + CONDITIONAL_AXIS_PROP_INDEX, + ConditionalAxisProp, + isConditionalAxisValue +} from '../../axis'; import {POSITION_SCALE_CHANNELS} from '../../channel'; import {defaultTitle, FieldDefBase} from '../../channeldef'; import {Config} from '../../config'; @@ -49,7 +55,8 @@ export function assembleAxis( return undefined; } - for (const prop in axis) { + for (const p in axis) { + const prop = p as keyof typeof axis; const propType = AXIS_PROPERTY_TYPE[prop]; const propValue = axis[prop]; @@ -59,10 +66,10 @@ export function assembleAxis( } else if (isConditionalAxisValue(propValue)) { // deal with conditional axis value - const {condition, ...valueOrSignalRef} = propValue; + const {condition, ...valueOrSignalRef} = propValue as any; const conditions = array(condition); - const propIndex = CONDITIONAL_AXIS_PROP_INDEX[prop]; + const propIndex = CONDITIONAL_AXIS_PROP_INDEX[prop as ConditionalAxisProp]; if (propIndex) { const {vgProp, part} = propIndex; // If there is a corresponding Vega property for the channel, @@ -91,13 +98,14 @@ export function assembleAxis( }) .join('') + exprFromValueRefOrSignalRef(valueOrSignalRef) }; - axis[prop] = signalRef; + (axis as any)[prop] = signalRef; } } else if (isSignalRef(propValue)) { - const propIndex = CONDITIONAL_AXIS_PROP_INDEX[prop]; + const propIndex = CONDITIONAL_AXIS_PROP_INDEX[prop as ConditionalAxisProp]; if (propIndex) { const {vgProp, part} = propIndex; - setAxisEncode(axis, part, vgProp, propValue); + // FIXME: remove as any + setAxisEncode(axis, part, vgProp, propValue as any); delete axis[prop]; } // else do nothing since the property already supports signal } diff --git a/src/compile/axis/config.ts b/src/compile/axis/config.ts index 2f242bbf36..527810e009 100644 --- a/src/compile/axis/config.ts +++ b/src/compile/axis/config.ts @@ -27,9 +27,9 @@ function getAxisConfigFromConfigTypes( const conditionalOrientAxisConfig = {}; for (const prop of props.values()) { - conditionalOrientAxisConfig[prop] = { + (conditionalOrientAxisConfig as any)[prop] = { // orient is surely signal in this case - signal: `${orient['signal']} === "${orient1}" ? ${signalOrStringValue( + signal: `${(orient as any)['signal']} === "${orient1}" ? ${signalOrStringValue( orientConfig1[prop] )} : ${signalOrStringValue(orientConfig2[prop])}` }; @@ -38,7 +38,7 @@ function getAxisConfigFromConfigTypes( return conditionalOrientAxisConfig; } - return config[configType]; + return (config as any)[configType]; }) ]); } @@ -85,7 +85,7 @@ export function getAxisConfigStyle(axisConfigTypes: string[], config: Config) { const toMerge = [{}]; for (const configType of axisConfigTypes) { // TODO: add special casing to add conditional value based on orient signal - let style = config[configType]?.style; + let style = (config as any)[configType]?.style; if (style) { style = array(style); for (const s of style) { @@ -110,7 +110,7 @@ export function getAxisConfig( }; } - for (const configFrom of ['vlOnlyAxisConfig', 'vgAxisConfig', 'axisConfigStyle']) { + for (const configFrom of ['vlOnlyAxisConfig', 'vgAxisConfig', 'axisConfigStyle'] as const) { if (axisConfigs[configFrom]?.[property] !== undefined) { return {configFrom, configValue: axisConfigs[configFrom][property]}; } diff --git a/src/compile/axis/parse.ts b/src/compile/axis/parse.ts index 26524adfb5..76c7cda0cf 100644 --- a/src/compile/axis/parse.ts +++ b/src/compile/axis/parse.ts @@ -198,7 +198,7 @@ function isExplicit( } } // Otherwise, things are explicit if the returned value matches the specified property - return value === axis[property]; + return value === (axis as any)[property]; } /** diff --git a/src/compile/common.ts b/src/compile/common.ts index 2617aa759a..629d880bb0 100644 --- a/src/compile/common.ts +++ b/src/compile/common.ts @@ -17,7 +17,7 @@ import {isExprRef} from '../expr'; import {Mark, MarkConfig, MarkDef} from '../mark'; import {SortFields} from '../sort'; import {isText} from '../title'; -import {deepEqual, getFirstDefined} from '../util'; +import {deepEqual, getFirstDefined, hasKey} from '../util'; import {isSignalRef, VgEncodeChannel, VgEncodeEntry, VgValueRef} from '../vega.schema'; import {AxisComponentProps} from './axis/component'; import {Explicit} from './split'; @@ -91,7 +91,7 @@ export function applyMarkConfig(e: VgEncodeEntry, model: UnitModel, propsList: ( for (const property of propsList) { const value = getMarkConfig(property, model.markDef, model.config); if (value !== undefined) { - e[property] = signalOrValueRef(value); + (e as any)[property] = signalOrValueRef(value); } } return e; @@ -111,8 +111,8 @@ export function getMarkPropOrConfig

[P] { const {vgChannel, ignoreVgConfig} = opt; - if (vgChannel && mark[vgChannel] !== undefined) { - return mark[vgChannel]; + if (vgChannel && hasKey(mark, vgChannel)) { + return mark[vgChannel] as any; } else if (mark[channel] !== undefined) { return mark[channel]; } else if (ignoreVgConfig && (!vgChannel || vgChannel === channel)) { @@ -132,18 +132,19 @@ export function getMarkConfig

, {vgChannel}: {vgChannel?: VgEncodeChannel} = {} ): MarkDef[P] { + const cfg = getMarkStyleConfig(channel, mark, config.style) as MarkDef[P]; return getFirstDefined[P]>( // style config has highest precedence - vgChannel ? getMarkStyleConfig(channel, mark, config.style) : undefined, - getMarkStyleConfig(channel, mark, config.style), + vgChannel ? cfg : undefined, + cfg, // then mark-specific config - vgChannel ? config[mark.type][vgChannel] : undefined, + vgChannel ? (config[mark.type] as any)[vgChannel] : undefined, - config[mark.type][channel as any], // Need to cast because MarkDef doesn't perfectly match with AnyMarkConfig, but if the type isn't available, we'll get nothing here, which is fine + (config[mark.type] as any)[channel], // Need to cast because MarkDef doesn't perfectly match with AnyMarkConfig, but if the type isn't available, we'll get nothing here, which is fine // If there is vgChannel, skip vl channel. // For example, vl size for text is vg fontSize, but config.mark.size is only for point size. - vgChannel ? config.mark[vgChannel] : config.mark[channel as any] // Need to cast for the same reason as above + vgChannel ? (config.mark as any)[vgChannel] : (config.mark as any)[channel] // Need to cast for the same reason as above ); } @@ -165,8 +166,8 @@ export function getStyleConfig

x.hash()); - const buckets: {hash?: DataFlowNode[]} = {}; + const buckets: Record = {}; for (let i = 0; i < hashes.length; i++) { if (buckets[hashes[i]] === undefined) { diff --git a/src/compile/data/parse.ts b/src/compile/data/parse.ts index 9fc9009402..b480d09665 100644 --- a/src/compile/data/parse.ts +++ b/src/compile/data/parse.ts @@ -84,7 +84,7 @@ export function findSource(data: Data, sources: SourceNode[]) { continue; } - const formatMesh = data['format']?.mesh; + const formatMesh = (data as any).format?.mesh; const otherFeature = otherData.format?.feature; // feature and mesh are mutually exclusive @@ -93,7 +93,7 @@ export function findSource(data: Data, sources: SourceNode[]) { } // we have to extract the same feature or mesh - const formatFeature = data['format']?.feature; + const formatFeature = (data as any).format?.feature; if ((formatFeature || otherFeature) && formatFeature !== otherFeature) { continue; } diff --git a/src/compile/data/timeunit.ts b/src/compile/data/timeunit.ts index d64c8be0df..7e4bda8b49 100644 --- a/src/compile/data/timeunit.ts +++ b/src/compile/data/timeunit.ts @@ -82,7 +82,7 @@ export class TimeUnitNode extends DataFlowNode { } if (component) { - timeUnitComponent[hash(component)] = component; + (timeUnitComponent as any)[hash(component)] = component; } } return timeUnitComponent; @@ -137,7 +137,7 @@ export class TimeUnitNode extends DataFlowNode { * Remove time units coming from the other node. */ public removeFormulas(fields: Set) { - const newFormula = {}; + const newFormula: Dict = {}; for (const [key, timeUnitComponent] of entries(this.timeUnits)) { const fieldAs = isTimeUnitTransformComponent(timeUnitComponent) diff --git a/src/compile/facet.ts b/src/compile/facet.ts index e7f073d72e..7f60737dc8 100644 --- a/src/compile/facet.ts +++ b/src/compile/facet.ts @@ -10,7 +10,7 @@ import {hasDiscreteDomain} from '../scale'; import {DEFAULT_SORT_OP, EncodingSortField, isSortField, SortOrder} from '../sort'; import {NormalizedFacetSpec} from '../spec'; import {EncodingFacetMapping, FacetFieldDef, FacetMapping, isFacetMapping} from '../spec/facet'; -import {keys} from '../util'; +import {hasKey, keys} from '../util'; import {isVgRangeStep, VgData, VgLayout, VgMarkGroup} from '../vega.schema'; import {buildModel} from './buildmodel'; import {assembleFacetData} from './data/assemble'; @@ -58,7 +58,7 @@ export class FacetModel extends ModelWithField { } const channels = keys(facet); - const normalizedFacet = {}; + const normalizedFacet: EncodingFacetMapping = {}; for (const channel of channels) { if (![ROW, COLUMN].includes(channel)) { // Drop unsupported channel @@ -91,11 +91,11 @@ export class FacetModel extends ModelWithField { } public channelHasField(channel: ExtendedChannel): boolean { - return !!this.facet[channel]; + return hasKey(this.facet, channel); } public fieldDef(channel: ExtendedChannel): TypedFieldDef { - return this.facet[channel]; + return (this.facet as any)[channel]; } public parseData() { @@ -153,7 +153,7 @@ export class FacetModel extends ModelWithField { if (['right', 'bottom'].includes(titleOrient)) { const headerChannel = getHeaderChannel(channel, titleOrient); layoutMixins.titleAnchor ??= {}; - layoutMixins.titleAnchor[headerChannel] = 'end'; + (layoutMixins.titleAnchor as any)[headerChannel] = 'end'; } } @@ -164,12 +164,12 @@ export class FacetModel extends ModelWithField { if (channel !== 'facet' && !this.child.component.layoutSize.get(sizeType)) { // If facet child does not have size signal, then apply headerBand layoutMixins[bandType] ??= {}; - layoutMixins[bandType][channel] = 0.5; + (layoutMixins[bandType] as any)[channel] = 0.5; } if (layoutHeaderComponent.title) { layoutMixins.offset ??= {}; - layoutMixins.offset[channel === 'row' ? 'rowTitle' : 'columnTitle'] = 10; + (layoutMixins.offset as any)[channel === 'row' ? 'rowTitle' : 'columnTitle'] = 10; } } } diff --git a/src/compile/header/assemble.ts b/src/compile/header/assemble.ts index 1bc29596fd..8ab085f3ae 100644 --- a/src/compile/header/assemble.ts +++ b/src/compile/header/assemble.ts @@ -233,8 +233,8 @@ const LAYOUT_TITLE_BAND = { } }; -export function getLayoutTitleBand(titleAnchor: TitleAnchor, headerChannel: HeaderChannel) { - return LAYOUT_TITLE_BAND[headerChannel][titleAnchor]; +export function getLayoutTitleBand(titleAnchor: TitleAnchor, headerChannel: HeaderChannel): 0 | 1 { + return (LAYOUT_TITLE_BAND[headerChannel] as any)[titleAnchor]; } export function assembleLayoutTitleBand( @@ -256,7 +256,7 @@ export function assembleLayoutTitleBand( const headerChannel = getHeaderChannel(channel, titleOrient); const band = getLayoutTitleBand(titleAnchor, headerChannel); if (band !== undefined) { - titleBand[headerChannel] = band; + (titleBand as any)[headerChannel] = band; } } } @@ -279,7 +279,7 @@ export function assembleHeaderProperties( const value = getHeaderProperty(prop, facetFieldDef?.header, config, channel); if (value !== undefined) { - props[propertiesMap[prop]] = value; + (props as any)[propertiesMap[prop]] = value; } } return props; diff --git a/src/compile/header/common.ts b/src/compile/header/common.ts index dc252a9901..f78354464f 100644 --- a/src/compile/header/common.ts +++ b/src/compile/header/common.ts @@ -39,7 +39,7 @@ export function getHeaderProperties( for (const prop of properties) { const value = getHeaderProperty(prop, header || {}, config, channel); if (value !== undefined) { - props[prop] = value; + (props as any)[prop] = value; } } return props; diff --git a/src/compile/legend/assemble.ts b/src/compile/legend/assemble.ts index 3732dac599..3d289abe32 100644 --- a/src/compile/legend/assemble.ts +++ b/src/compile/legend/assemble.ts @@ -61,7 +61,7 @@ export function assembleLegend(legendCmpt: LegendComponent, config: Config) { if (legend.encode?.symbols) { const out = legend.encode.symbols.update; - if (out.fill && out.fill['value'] !== 'transparent' && !out.stroke && !legend.stroke) { + if (out.fill && (out.fill as any)['value'] !== 'transparent' && !out.stroke && !legend.stroke) { // For non color channel's legend, we need to override symbol stroke config from Vega config if stroke channel is not used. out.stroke = {value: 'transparent'}; } diff --git a/src/compile/legend/encode.ts b/src/compile/legend/encode.ts index 405c1851cb..b83e5dd336 100644 --- a/src/compile/legend/encode.ts +++ b/src/compile/legend/encode.ts @@ -13,7 +13,7 @@ import { } from '../../channeldef'; import {Encoding} from '../../encoding'; import {FILL_STROKE_CONFIG} from '../../mark'; -import {getFirstDefined, isEmpty, varName} from '../../util'; +import {getFirstDefined, hasKey, isEmpty, varName} from '../../util'; import {applyMarkConfig, signalOrValueRef} from '../common'; import {formatCustomType, isCustomFormatType} from '../format'; import * as mixins from '../mark/encode'; @@ -64,21 +64,18 @@ export function symbols( // for fill legend, we don't want any fill in symbol if (channel === 'fill' || (filled && channel === COLOR)) { delete out.fill; - } else { - if (out.fill['field']) { - // For others, set fill to some opaque value (or nothing if a color is already set) - if (symbolFillColor) { - delete out.fill; - } else { - out.fill = signalOrValueRef(config.legend.symbolBaseFillColor ?? 'black'); - out.fillOpacity = signalOrValueRef(opacity ?? 1); - } - } else if (isArray(out.fill)) { - const fill = - getFirstConditionValue(encoding.fill ?? encoding.color) ?? markDef.fill ?? (filled && markDef.color); - if (fill) { - out.fill = signalOrValueRef(fill) as ColorValueRef; - } + } else if (hasKey(out.fill, 'field')) { + // For others, set fill to some opaque value (or nothing if a color is already set) + if (symbolFillColor) { + delete out.fill; + } else { + out.fill = signalOrValueRef(config.legend.symbolBaseFillColor ?? 'black'); + out.fillOpacity = signalOrValueRef(opacity ?? 1); + } + } else if (isArray(out.fill)) { + const fill = getFirstConditionValue(encoding.fill ?? encoding.color) ?? markDef.fill ?? (filled && markDef.color); + if (fill) { + out.fill = signalOrValueRef(fill) as ColorValueRef; } } } @@ -86,19 +83,17 @@ export function symbols( if (out.stroke) { if (channel === 'stroke' || (!filled && channel === COLOR)) { delete out.stroke; - } else { - if (out.stroke['field'] || symbolStrokeColor) { - // For others, remove stroke field - delete out.stroke; - } else if (isArray(out.stroke)) { - const stroke = getFirstDefined( - getFirstConditionValue(encoding.stroke || encoding.color), - markDef.stroke, - filled ? markDef.color : undefined - ); - if (stroke) { - out.stroke = {value: stroke} as ColorValueRef; - } + } else if (hasKey(out.stroke, 'field') || symbolStrokeColor) { + // For others, remove stroke field + delete out.stroke; + } else if (isArray(out.stroke)) { + const stroke = getFirstDefined( + getFirstConditionValue(encoding.stroke || encoding.color), + markDef.stroke, + filled ? markDef.color : undefined + ); + if (stroke) { + out.stroke = {value: stroke} as ColorValueRef; } } } diff --git a/src/compile/legend/parse.ts b/src/compile/legend/parse.ts index 4e778ba303..f32728192f 100644 --- a/src/compile/legend/parse.ts +++ b/src/compile/legend/parse.ts @@ -4,7 +4,7 @@ import {DatumDef, FieldDef, getFieldOrDatumDef, isFieldDef, MarkPropDatumDef, Ma import {LegendInternal, LEGEND_SCALE_CHANNELS} from '../../legend'; import {normalizeTimeUnit} from '../../timeunit'; import {GEOJSON} from '../../type'; -import {deleteNestedProperty, isEmpty, keys, varName} from '../../util'; +import {deleteNestedProperty, hasKey, isEmpty, keys, varName} from '../../util'; import {mergeTitleComponent} from '../common'; import {guideEncodeEntry} from '../guide'; import {isUnitModel, Model} from '../model'; @@ -81,7 +81,7 @@ function isExplicit( } } // Otherwise, things are explicit if the returned value matches the specified property - return value === (legend || {})[property]; + return value === (legend ?? ({} as any))[property]; } export function parseLegendForChannel(model: UnitModel, channel: NonPositionScaleChannel): LegendComponent { @@ -132,10 +132,10 @@ export function parseLegendForChannel(model: UnitModel, channel: NonPositionScal continue; } - const value = property in legendRules ? legendRules[property](ruleParams) : legend[property]; + const value = property in legendRules ? legendRules[property](ruleParams) : (legend as any)[property]; if (value !== undefined) { const explicit = isExplicit(value, property, legend, model.fieldDef(channel)); - if (explicit || config.legend[property] === undefined) { + if (explicit || hasKey(config.legend, property)) { legendCmpt.set(property, value, explicit); } } @@ -147,8 +147,9 @@ export function parseLegendForChannel(model: UnitModel, channel: NonPositionScal const legendEncodeParams: LegendEncodeParams = {fieldOrDatumDef, model, channel, legendCmpt, legendType}; - for (const part of ['labels', 'legend', 'title', 'symbols', 'gradient', 'entries']) { - const legendEncodingPart = guideEncodeEntry(legendEncoding[part] ?? {}, model); + for (const part of ['labels', 'legend', 'title', 'symbols', 'gradient', 'entries'] as const) { + // FIXME: remove as any (figure out what legendEncoding.entries is) + const legendEncodingPart = guideEncodeEntry((legendEncoding as any)[part] ?? {}, model); const value = part in legendEncodeRules diff --git a/src/compile/mark/encode/base.ts b/src/compile/mark/encode/base.ts index 55cff371e4..cc2c21944c 100644 --- a/src/compile/mark/encode/base.ts +++ b/src/compile/mark/encode/base.ts @@ -1,4 +1,6 @@ +import {MarkConfig} from 'vega'; import {MarkDef} from '../../../mark'; +import {hasKey} from '../../../util'; import {VG_MARK_CONFIGS, VgEncodeEntry, VgValueRef} from '../../../vega.schema'; import {signalOrValueRef} from '../../common'; import {UnitModel} from '../../unit'; @@ -44,10 +46,13 @@ function colorRef(channel: 'fill' | 'stroke', valueRef: VgValueRef | VgValueRef[ } function markDefProperties(mark: MarkDef, ignore: Ignore) { - return VG_MARK_CONFIGS.reduce((m, prop) => { - if (!ALWAYS_IGNORE.has(prop) && mark[prop] !== undefined && ignore[prop] !== 'ignore') { - m[prop] = signalOrValueRef(mark[prop]); - } - return m; - }, {}); + return VG_MARK_CONFIGS.reduce( + (m, prop) => { + if (!ALWAYS_IGNORE.has(prop) && hasKey(mark, prop) && (ignore as any)[prop] !== 'ignore') { + m[prop] = signalOrValueRef(mark[prop]); + } + return m; + }, + {} as Record + ); } diff --git a/src/compile/mark/encode/color.ts b/src/compile/mark/encode/color.ts index c01b388883..1fa6419ba4 100644 --- a/src/compile/mark/encode/color.ts +++ b/src/compile/mark/encode/color.ts @@ -19,7 +19,7 @@ export function color(model: UnitModel, opt: {filled: boolean | undefined} = {fi const defaultFill = getMarkPropOrConfig(filled === true ? 'color' : undefined, markDef, config, {vgChannel: 'fill'}) ?? // need to add this manually as getMarkConfig normally drops config.mark[channel] if vgChannel is specified - config.mark[filled === true && 'color'] ?? + (config.mark as any)[filled === true && 'color'] ?? // If there is no fill, always fill symbols, bar, geoshape // with transparent fills https://github.com/vega/vega-lite/issues/1316 transparentIfNeeded; @@ -27,7 +27,7 @@ export function color(model: UnitModel, opt: {filled: boolean | undefined} = {fi const defaultStroke = getMarkPropOrConfig(filled === false ? 'color' : undefined, markDef, config, {vgChannel: 'stroke'}) ?? // need to add this manually as getMarkConfig normally drops config.mark[channel] if vgChannel is specified - config.mark[filled === false && 'color']; + (config.mark as any)[filled === false && 'color']; const colorVgChannel = filled ? 'fill' : 'stroke'; diff --git a/src/compile/mark/encode/offset.ts b/src/compile/mark/encode/offset.ts index 0ee51be1d2..5ea67729f6 100644 --- a/src/compile/mark/encode/offset.ts +++ b/src/compile/mark/encode/offset.ts @@ -39,7 +39,8 @@ export function positionOffset({ | 'radius2Offset'; // Need to cast as the type can't be inferred automatically const defaultValue = markDef[channel]; - const channelDef = encoding[channel]; + // FIXME: remove as any + const channelDef = (encoding as any)[channel]; if ((channel === 'xOffset' || channel === 'yOffset') && channelDef) { const ref = midPoint({ diff --git a/src/compile/mark/encode/position-align.ts b/src/compile/mark/encode/position-align.ts index 8fbc3c2d54..8a65d7eabf 100644 --- a/src/compile/mark/encode/position-align.ts +++ b/src/compile/mark/encode/position-align.ts @@ -39,9 +39,10 @@ export function vgAlignedPositionChannel( alignExcludingSignal = align; } + // FIXME: remove as any if (channel === 'x') { - return ALIGNED_X_CHANNEL[alignExcludingSignal || (defaultAlign === 'top' ? 'left' : 'center')]; + return (ALIGNED_X_CHANNEL as any)[alignExcludingSignal || (defaultAlign === 'top' ? 'left' : 'center')]; } else { - return BASELINED_Y_CHANNEL[alignExcludingSignal || defaultAlign]; + return (BASELINED_Y_CHANNEL as any)[alignExcludingSignal || defaultAlign]; } } diff --git a/src/compile/mark/encode/position-rect.ts b/src/compile/mark/encode/position-rect.ts index 7b037cbff9..05bd00bdc2 100644 --- a/src/compile/mark/encode/position-rect.ts +++ b/src/compile/mark/encode/position-rect.ts @@ -46,7 +46,9 @@ export function rectPosition(model: UnitModel, channel: 'x' | 'y' | 'theta' | 'r const orient = markDef.orient; const hasSizeDef = - encoding[sizeChannel] ?? encoding.size ?? getMarkPropOrConfig('size', markDef, config, {vgChannel: sizeChannel}); + (encoding as any)[sizeChannel] ?? + encoding.size ?? + getMarkPropOrConfig('size', markDef, config, {vgChannel: sizeChannel}); const offsetScaleChannel = getOffsetChannel(channel); @@ -57,7 +59,7 @@ export function rectPosition(model: UnitModel, channel: 'x' | 'y' | 'theta' | 'r isFieldDef(channelDef) && (isBinning(channelDef.bin) || isBinned(channelDef.bin) || (channelDef.timeUnit && !channelDef2)) && !(hasSizeDef && !isRelativeBandSize(hasSizeDef)) && - !encoding[offsetScaleChannel] && + !(encoding as any)[offsetScaleChannel] && !hasDiscreteDomain(scaleType) ) { return rectBinPosition({ @@ -310,7 +312,7 @@ function rectBinPosition({ const bandSize = getBandSize({channel, fieldDef, markDef, config, scaleType}); - const axis = model.component.axes[channel]?.[0]; + const axis = (model.component.axes as any)[channel]?.[0]; const axisTranslate = axis?.get('translate') ?? 0.5; // vega default is 0.5 const spacing = isXorY(channel) ? (getMarkPropOrConfig('binSpacing', markDef, config) ?? 0) : 0; diff --git a/src/compile/mark/encode/tooltip.ts b/src/compile/mark/encode/tooltip.ts index 48f70ce3af..8eab740b1f 100644 --- a/src/compile/mark/encode/tooltip.ts +++ b/src/compile/mark/encode/tooltip.ts @@ -15,7 +15,7 @@ import { import {Config} from '../../../config'; import {Encoding, forEach} from '../../../encoding'; import {StackProperties} from '../../../stack'; -import {entries} from '../../../util'; +import {Dict, entries} from '../../../util'; import {isSignalRef} from '../../../vega.schema'; import {getMarkPropOrConfig} from '../../common'; import {binFormatExpression, formatSignalRef} from '../../format'; @@ -81,7 +81,7 @@ export function tooltipData( {reactiveGeom}: {reactiveGeom?: boolean} = {} ) { const formatConfig = {...config, ...config.tooltipFormat}; - const toSkip = {}; + const toSkip = new Set(); const expr = reactiveGeom ? 'datum.datum' : 'datum'; const tuples: {channel: Channel; key: string; value: string}[] = []; @@ -109,7 +109,7 @@ export function tooltipData( const endField = vgField(fieldDef2, {expr}); const {format, formatType} = getFormatMixins(fieldDef); value = binFormatExpression(startField, endField, format, formatType, formatConfig); - toSkip[channel2] = true; + toSkip.add(channel2); } } @@ -143,9 +143,9 @@ export function tooltipData( } }); - const out = {}; + const out: Dict = {}; for (const {channel, key, value} of tuples) { - if (!toSkip[channel] && !out[key]) { + if (!toSkip.has(channel) && !out[key]) { out[key] = value; } } diff --git a/src/compile/mark/init.ts b/src/compile/mark/init.ts index 3c730e5d39..bf0b426648 100644 --- a/src/compile/mark/init.ts +++ b/src/compile/mark/init.ts @@ -41,7 +41,7 @@ export function initMarkdef(originalMarkDef: MarkDef, encoding: Encoding if (cornerRadiusEnd !== undefined) { const newProps = (markDef.orient === 'horizontal' && encoding.x2) || (markDef.orient === 'vertical' && encoding.y2) - ? ['cornerRadius'] + ? (['cornerRadius'] as const) : BAR_CORNER_RADIUS_END_INDEX[markDef.orient]; for (const newProp of newProps) { diff --git a/src/compile/model.ts b/src/compile/model.ts index 6dc99627d7..378573a24c 100644 --- a/src/compile/model.ts +++ b/src/compile/model.ts @@ -328,7 +328,7 @@ export abstract class Model { if (!isTopLevel) { // Descriptions are already added to the top-level description so we only need to add them to the inner views. if (this.description) { - encodeEntry['description'] = signalOrValueRef(this.description); + (encodeEntry as any)['description'] = signalOrValueRef(this.description); } // For top-level spec, we can set the global width and height signal to adjust the group size. diff --git a/src/compile/resolve.ts b/src/compile/resolve.ts index a45e8460f6..82a1e898b2 100644 --- a/src/compile/resolve.ts +++ b/src/compile/resolve.ts @@ -20,11 +20,11 @@ export function parseGuideResolve(resolve: Resolve, channel: ScaleChannel): Reso const guide = isXorY(channel) ? 'axis' : 'legend'; if (channelScaleResolve === 'independent') { - if (resolve[guide][channel] === 'shared') { + if ((resolve[guide] as any)[channel] === 'shared') { log.warn(log.message.independentScaleMeansIndependentGuide(channel)); } return 'independent'; } - return resolve[guide][channel] || 'shared'; + return (resolve[guide] as any)[channel] || 'shared'; } diff --git a/src/compile/scale/domain.ts b/src/compile/scale/domain.ts index 4a23487bf1..8da2fdbe77 100644 --- a/src/compile/scale/domain.ts +++ b/src/compile/scale/domain.ts @@ -249,7 +249,7 @@ function parseSingleChannelDomain( const fieldOrDatumDef = getFieldOrDatumDef(encoding[channel]) as ScaleDatumDef | ScaleFieldDef; const {type} = fieldOrDatumDef; - const timeUnit = fieldOrDatumDef['timeUnit']; + const timeUnit = (fieldOrDatumDef as any)['timeUnit']; const dataSourceTypeForScaleDomain = getScaleDataSourceForHandlingInvalidValues({ invalid: getMarkConfig('invalid', markDef, config), diff --git a/src/compile/scale/parse.ts b/src/compile/scale/parse.ts index e6baf799af..b373d88fdf 100644 --- a/src/compile/scale/parse.ts +++ b/src/compile/scale/parse.ts @@ -54,7 +54,7 @@ function parseUnitScaleCore(model: UnitModel): ScaleComponentIndex { continue; } - let specifiedScale = fieldOrDatumDef && fieldOrDatumDef['scale']; + let specifiedScale = (fieldOrDatumDef as any)?.scale; if (fieldOrDatumDef && specifiedScale !== null && specifiedScale !== false) { specifiedScale ??= {}; const hasNestedOffsetScale = channelHasNestedOffsetScale(encoding, channel); diff --git a/src/compile/scale/properties.ts b/src/compile/scale/properties.ts index ca73775106..5218d9650e 100644 --- a/src/compile/scale/properties.ts +++ b/src/compile/scale/properties.ts @@ -87,7 +87,7 @@ function parseUnitScaleProperty(model: UnitModel, property: Exclude = { } ] : [], - bind: bind[p.field] ?? bind[p.channel] ?? bind + bind: (bind as any)[p.field] ?? (bind as any)[p.channel] ?? bind }); } }); diff --git a/src/compile/selection/interval.ts b/src/compile/selection/interval.ts index 12456bfa31..2ca3ee6f1c 100644 --- a/src/compile/selection/interval.ts +++ b/src/compile/selection/interval.ts @@ -235,18 +235,24 @@ const interval: SelectionCompiler<'interval'> = { // not interefere with the core marks, but that the brushed region can still // be interacted with (e.g., dragging it around). const {fill, fillOpacity, cursor, ...stroke} = selCmpt.mark; - const vgStroke = keys(stroke).reduce((def, k) => { - def[k] = [ - { - test: [x !== undefined && `${xvname}[0] !== ${xvname}[1]`, y !== undefined && `${yvname}[0] !== ${yvname}[1]`] - .filter(t => t) - .join(' && '), - value: stroke[k] - }, - {value: null} - ]; - return def; - }, {}); + const vgStroke = keys(stroke).reduce( + (def, k) => { + def[k] = [ + { + test: [ + x !== undefined && `${xvname}[0] !== ${xvname}[1]`, + y !== undefined && `${yvname}[0] !== ${yvname}[1]` + ] + .filter(t => t) + .join(' && '), + value: stroke[k] + }, + {value: null} + ]; + return def; + }, + {} as Record + ); // Set cursor to move unless the brush cannot be translated const vgCursor = cursor ?? (selCmpt.translate ? 'move' : null); diff --git a/src/compile/selection/parse.ts b/src/compile/selection/parse.ts index d816af1505..2512fa5dd9 100644 --- a/src/compile/selection/parse.ts +++ b/src/compile/selection/parse.ts @@ -35,11 +35,11 @@ export function parseUnitSelection(model: UnitModel, selDefs: SelectionParameter } if (key === 'mark') { - defaults[key] = {...cfg[key], ...defaults[key]}; + (defaults as any).mark = {...(cfg as any).mark, ...(defaults as any).mark}; } - if (defaults[key] === undefined || defaults[key] === true) { - defaults[key] = duplicate(cfg[key] ?? defaults[key]); + if ((defaults as any)[key] === undefined || (defaults as any)[key] === true) { + (defaults as any)[key] = duplicate((cfg as any)[key] ?? (defaults as any)[key]); } } @@ -101,8 +101,8 @@ export function parseSelectionPredicate( export function parseSelectionExtent(model: Model, name: string, extent: ParameterExtent) { const vname = varName(name); - const encoding = extent['encoding']; - let field = extent['field']; + const encoding = (extent as any).encoding; + let field = (extent as any).field; let selCmpt; try { diff --git a/src/compile/unit.ts b/src/compile/unit.ts index c56a8d4501..dbaf32e24d 100644 --- a/src/compile/unit.ts +++ b/src/compile/unit.ts @@ -141,7 +141,7 @@ export class UnitModel extends ModelWithField { } public axis(channel: PositionChannel): AxisInternal { - return this.specifiedAxes[channel]; + return (this.specifiedAxes as any)[channel]; } public legend(channel: NonPositionScaleChannel): LegendInternal { @@ -191,15 +191,15 @@ export class UnitModel extends ModelWithField { : axisSpec; } return _axis; - }, {}); + }, {} as any); } private initAxis(axis: Axis): Axis { const props = keys(axis); - const axisInternal = {}; + const axisInternal: any = {}; for (const prop of props) { const val = axis[prop]; - axisInternal[prop as any] = isConditionalAxisValue(val) + axisInternal[prop] = isConditionalAxisValue(val) ? signalOrValueRefWithCondition(val) : signalRefOrValue(val); } @@ -218,7 +218,7 @@ export class UnitModel extends ModelWithField { } return _legend; - }, {}); + }, {} as any); } public parseData() { @@ -298,7 +298,7 @@ export class UnitModel extends ModelWithField { } public fieldDef(channel: SingleDefChannel) { - const channelDef = this.encoding[channel]; + const channelDef = (this.encoding as any)[channel]; return getFieldDef(channelDef); } diff --git a/src/compositemark/common.ts b/src/compositemark/common.ts index b806c259bb..225dd25e93 100644 --- a/src/compositemark/common.ts +++ b/src/compositemark/common.ts @@ -95,7 +95,7 @@ export function filterTooltipWithAggregatedField( (filteredEncoding as Encoding).tooltip = customTooltipWithAggregatedField; } } else { - if (tooltip['aggregate']) { + if ((tooltip as any).aggregate) { (filteredEncoding as Encoding).tooltip = tooltip; } else { customTooltipWithoutAggregatedField = tooltip; @@ -238,8 +238,8 @@ export function compositeMarkContinuousAxis( const continuousAxisChannelDef = encoding[continuousAxis] as PositionFieldDef; // Safe to cast because if x is not continuous fielddef, the orient would not be horizontal. const continuousAxisChannelDef2 = encoding[`${continuousAxis}2`] as SecondaryFieldDef; - const continuousAxisChannelDefError = encoding[`${continuousAxis}Error`] as SecondaryFieldDef; - const continuousAxisChannelDefError2 = encoding[`${continuousAxis}Error2`] as SecondaryFieldDef; + const continuousAxisChannelDefError = (encoding as any)[`${continuousAxis}Error`] as SecondaryFieldDef; + const continuousAxisChannelDefError2 = (encoding as any)[`${continuousAxis}Error2`] as SecondaryFieldDef; return { continuousAxisChannelDef: filterAggregateFromChannelDef(continuousAxisChannelDef, compositeMark), diff --git a/src/compositemark/errorbar.ts b/src/compositemark/errorbar.ts index 527efd4d35..537fa09173 100644 --- a/src/compositemark/errorbar.ts +++ b/src/compositemark/errorbar.ts @@ -143,7 +143,7 @@ export function normalizeErrorBar( outerSpec, tooltipEncoding } = errorBarParams(spec, ERRORBAR, config); - delete encodingWithoutContinuousAxis['size']; + delete (encodingWithoutContinuousAxis as any).size; const makeErrorBarPart = makeCompositeAggregatePartFactory( markDef, diff --git a/src/config.ts b/src/config.ts index 7a1cbf0fba..0a914c0639 100644 --- a/src/config.ts +++ b/src/config.ts @@ -100,7 +100,7 @@ export const defaultViewConfig: ViewConfig = { }; export function isVgScheme(rangeScheme: string[] | RangeScheme): rangeScheme is RangeScheme { - return rangeScheme && hasKey(rangeScheme, 'scheme'); + return hasKey(rangeScheme, 'scheme'); } export type ColorConfig = Record; diff --git a/src/data.ts b/src/data.ts index 0c18396a07..4e38462de6 100644 --- a/src/data.ts +++ b/src/data.ts @@ -4,6 +4,7 @@ import {Vector2} from 'vega'; import {FieldName} from './channeldef'; import {VgData} from './vega.schema'; +import {hasKey} from './util'; export type ParseValue = null | string | 'string' | 'boolean' | 'date' | 'number'; @@ -123,15 +124,15 @@ export interface NamedData extends DataBase { } export function isUrlData(data: Partial | Partial): data is UrlData { - return 'url' in data; + return hasKey(data, 'url'); } export function isInlineData(data: Partial | Partial): data is InlineData { - return 'values' in data; + return hasKey(data, 'values'); } export function isNamedData(data: Partial | Partial): data is NamedData { - return 'name' in data && !isUrlData(data) && !isInlineData(data) && !isGenerator(data); + return hasKey(data, 'name') && !isUrlData(data) && !isInlineData(data) && !isGenerator(data); } export function isGenerator(data: Partial | Partial): data is Generator { @@ -139,15 +140,15 @@ export function isGenerator(data: Partial | Partial): data is Gene } export function isSequenceGenerator(data: Partial | Partial): data is SequenceGenerator { - return 'sequence' in data; + return hasKey(data, 'sequence'); } export function isSphereGenerator(data: Partial | Partial): data is SphereGenerator { - return 'sphere' in data; + return hasKey(data, 'sphere'); } export function isGraticuleGenerator(data: Partial | Partial): data is GraticuleGenerator { - return 'graticule' in data; + return hasKey(data, 'graticule'); } export enum DataSourceType { diff --git a/src/datetime.ts b/src/datetime.ts index 32f97381b3..5d3498eeb2 100644 --- a/src/datetime.ts +++ b/src/datetime.ts @@ -3,7 +3,7 @@ import {isNumber, isObject} from 'vega-util'; import * as log from './log'; import {TIMEUNIT_PARTS} from './timeunit'; -import {duplicate, isNumeric, keys} from './util'; +import {duplicate, hasKey, isNumeric, keys} from './util'; /** * @minimum 1 @@ -125,7 +125,7 @@ export interface DateTimeExpr { export function isDateTime(o: any): o is DateTime { if (o && isObject(o)) { for (const part of TIMEUNIT_PARTS) { - if (part in o) { + if (hasKey(o, part)) { return true; } } diff --git a/src/expr.ts b/src/expr.ts index c7f217a9a5..c28352f89f 100644 --- a/src/expr.ts +++ b/src/expr.ts @@ -1,5 +1,5 @@ import {signalRefOrValue} from './compile/common'; -import {Dict, keys} from './util'; +import {Dict, hasKey, keys} from './util'; import {MappedExclude} from './vega.schema'; export interface ExprRef { @@ -10,7 +10,7 @@ export interface ExprRef { } export function isExprRef(o: any): o is ExprRef { - return !!o?.expr; + return hasKey(o, 'expr'); } export function replaceExprRef>(index: T, {level}: {level: number} = {level: 0}) { diff --git a/src/logical.ts b/src/logical.ts index 80f57c4e89..a51055efbe 100644 --- a/src/logical.ts +++ b/src/logical.ts @@ -1,3 +1,5 @@ +import {hasKey} from './util'; + export type LogicalComposition = LogicalNot | LogicalAnd | LogicalOr | T; export interface LogicalOr { @@ -13,15 +15,15 @@ export interface LogicalNot { } export function isLogicalOr(op: LogicalComposition): op is LogicalOr { - return !!op.or; + return hasKey(op, 'or'); } export function isLogicalAnd(op: LogicalComposition): op is LogicalAnd { - return !!op.and; + return hasKey(op, 'and'); } export function isLogicalNot(op: LogicalComposition): op is LogicalNot { - return !!op.not; + return hasKey(op, 'not'); } export function forEachLeaf(op: LogicalComposition, fn: (op: T) => void) { diff --git a/src/mark.ts b/src/mark.ts index fd0eed3fdc..71fcec9208 100644 --- a/src/mark.ts +++ b/src/mark.ts @@ -1,4 +1,13 @@ -import {Align, Color, Gradient, MarkConfig as VgMarkConfig, Orientation, SignalRef, TextBaseline} from 'vega'; +import { + Align, + Color, + Gradient, + MarkConfig as VgMarkConfig, + Orientation, + SignalRef, + TextBaseline, + hasOwnProperty +} from 'vega'; import {CompositeMark, CompositeMarkDef} from './compositemark'; import {ExprRef} from './expr'; import {Flag, hasKey, keys} from './util'; @@ -43,7 +52,7 @@ export const GEOSHAPE = Mark.geoshape; export type Mark = keyof typeof Mark; export function isMark(m: string): m is Mark { - return m in Mark; + return hasOwnProperty(Mark, m); } export const PATH_MARKS = ['line', 'area', 'trail'] as const; @@ -453,7 +462,7 @@ export interface RelativeBandSize { } export function isRelativeBandSize(o: number | RelativeBandSize | ExprRef | SignalRef): o is RelativeBandSize { - return o && hasKey(o, 'band'); + return hasKey(o, 'band'); } export const BAR_CORNER_RADIUS_INDEX: Partial< diff --git a/src/normalize/core.ts b/src/normalize/core.ts index 545e24ff8a..6299dde210 100644 --- a/src/normalize/core.ts +++ b/src/normalize/core.ts @@ -283,8 +283,8 @@ export class CoreNormalizer extends SpecMapper { const out: EncodingOrFacet = {}; for (const channel in mapping) { - if (hasOwnProperty(mapping, channel)) { + if (hasKey(mapping, channel)) { const channelDef: ChannelDef | ChannelDef[] = mapping[channel]; if (isArray(channelDef)) { // array cannot have condition - out[channel] = (channelDef as ChannelDef[]) // somehow we need to cast it here + (out as any)[channel] = (channelDef as ChannelDef[]) // somehow we need to cast it here .map(cd => replaceRepeaterInChannelDef(cd, repeater)) .filter(cd => cd); } else { const cd = replaceRepeaterInChannelDef(channelDef, repeater); if (cd !== undefined) { - out[channel] = cd; + (out as any)[channel] = cd; } } } diff --git a/src/normalize/selectioncompat.ts b/src/normalize/selectioncompat.ts index f5e64df739..7d3402c525 100644 --- a/src/normalize/selectioncompat.ts +++ b/src/normalize/selectioncompat.ts @@ -7,6 +7,7 @@ import {SpecMapper} from '../spec/map'; import {isBin, isFilter, isLookup} from '../transform'; import {duplicate, entries, vals} from '../util'; import {NormalizerParams} from './base'; +import {Encoding} from '../encoding'; export class SelectionCompatibilityNormalizer extends SpecMapper< NormalizerParams, @@ -28,9 +29,9 @@ export class SelectionCompatibilityNormalizer extends SpecMapper< spec = normalizeTransforms(spec, normParams); if (spec.encoding) { - const encoding = {}; + const encoding: Encoding = {}; for (const [channel, enc] of entries(spec.encoding)) { - encoding[channel] = normalizeChannelDef(enc, normParams); + (encoding as any)[channel] = normalizeChannelDef(enc, normParams); } spec = {...spec, encoding}; @@ -54,8 +55,8 @@ export class SelectionCompatibilityNormalizer extends SpecMapper< } // Propagate emptiness forwards and backwards - normParams.emptySelections[name] = empty !== 'none'; - for (const pred of vals(normParams.selectionPredicates[name] ?? {})) { + (normParams.emptySelections as any)[name] = empty !== 'none'; + for (const pred of vals((normParams.selectionPredicates as any)[name] ?? {})) { pred.empty = empty !== 'none'; } diff --git a/src/normalize/toplevelselection.ts b/src/normalize/toplevelselection.ts index 226d700426..4e1cb263b6 100644 --- a/src/normalize/toplevelselection.ts +++ b/src/normalize/toplevelselection.ts @@ -66,7 +66,7 @@ export class TopLevelSelectionsNormalizer extends SpecMapper = SortArray | AllSortString | EncodingSortField | SortByEncoding | null; export function isSortByEncoding(sort: Sort): sort is SortByEncoding { - return !!sort?.['encoding']; + return hasKey(sort, 'encoding'); } export function isSortField(sort: Sort): sort is EncodingSortField { - return sort && (sort['op'] === 'count' || !!sort['field']); + return sort && ((sort as any).op === 'count' || hasKey(sort, 'field')); } export function isSortArray(sort: Sort): sort is SortArray { diff --git a/src/spec/base.ts b/src/spec/base.ts index 9867011877..35505386e4 100644 --- a/src/spec/base.ts +++ b/src/spec/base.ts @@ -1,5 +1,5 @@ import {Color, Cursor, SignalRef, Text} from 'vega'; -import {isNumber, isObject} from 'vega-util'; +import {isNumber} from 'vega-util'; import {NormalizedSpec} from '.'; import {Data} from '../data'; import {ExprRef} from '../expr'; @@ -7,7 +7,7 @@ import {MarkConfig} from '../mark'; import {Resolve} from '../resolve'; import {TitleParams} from '../title'; import {Transform} from '../transform'; -import {Flag, keys} from '../util'; +import {Flag, hasKey, keys} from '../util'; import {LayoutAlign, RowCol} from '../vega.schema'; import {isConcatSpec, isVConcatSpec} from './concat'; import {isFacetMapping, isFacetSpec} from './facet'; @@ -74,7 +74,7 @@ export function getStepFor({step, offsetIsDiscrete}: {step: Step; offsetIsDiscre } export function isStep(size: number | Step | 'container' | 'merged'): size is Step { - return isObject(size) && size['step'] !== undefined; + return hasKey(size, 'step'); } // TODO(https://github.com/vega/vega-lite/issues/2503): Make this generic so we can support some form of top-down sizing. @@ -115,7 +115,7 @@ export interface LayoutSizeMixins { } export function isFrameMixins(o: any): o is FrameMixins { - return o['view'] || o['width'] || o['height']; + return hasKey(o, 'view') || hasKey(o, 'width') || hasKey(o, 'height'); } export interface FrameMixins extends LayoutSizeMixins { diff --git a/src/spec/concat.ts b/src/spec/concat.ts index 63d113271a..538f33b5e7 100644 --- a/src/spec/concat.ts +++ b/src/spec/concat.ts @@ -1,4 +1,5 @@ import {GenericSpec, NormalizedSpec} from '.'; +import {hasKey} from '../util'; import {BaseSpec, BoundsMixins, GenericCompositionLayoutWithColumns, ResolveMixins} from './base'; /** @@ -68,13 +69,13 @@ export function isAnyConcatSpec(spec: BaseSpec): spec is GenericVConcatSpec } export function isConcatSpec(spec: BaseSpec): spec is GenericConcatSpec { - return 'concat' in spec; + return hasKey(spec, 'concat'); } export function isVConcatSpec(spec: BaseSpec): spec is GenericVConcatSpec { - return 'vconcat' in spec; + return hasKey(spec, 'vconcat'); } export function isHConcatSpec(spec: BaseSpec): spec is GenericHConcatSpec { - return 'hconcat' in spec; + return hasKey(spec, 'hconcat'); } diff --git a/src/spec/facet.ts b/src/spec/facet.ts index 807bf0b541..f8fb8aaf35 100644 --- a/src/spec/facet.ts +++ b/src/spec/facet.ts @@ -8,6 +8,7 @@ import {StandardType} from '../type'; import {BaseSpec, GenericCompositionLayoutWithColumns, ResolveMixins} from './base'; import {GenericLayerSpec, NormalizedLayerSpec} from './layer'; import {GenericUnitSpec, NormalizedUnitSpec} from './unit'; +import {hasKey} from '../util'; export interface FacetFieldDef extends TypedFieldDef { @@ -90,7 +91,7 @@ export interface FacetMapping< export function isFacetMapping( f: FacetFieldDef | FacetMapping ): f is FacetMapping { - return 'row' in f || 'column' in f; + return hasKey(f, 'row') || hasKey(f, 'column'); } /** @@ -107,7 +108,7 @@ export interface EncodingFacetMapping(channelDef: ChannelDef): channelDef is FacetFieldDef { - return !!channelDef && 'header' in channelDef; + return hasKey(channelDef, 'header'); } /** @@ -137,5 +138,5 @@ export interface GenericFacetSpec, L extends export type NormalizedFacetSpec = GenericFacetSpec; export function isFacetSpec(spec: BaseSpec): spec is GenericFacetSpec { - return 'facet' in spec; + return hasKey(spec, 'facet'); } diff --git a/src/spec/layer.ts b/src/spec/layer.ts index aafab34e68..0c2f395563 100644 --- a/src/spec/layer.ts +++ b/src/spec/layer.ts @@ -2,6 +2,7 @@ import {Field} from '../channeldef'; import {SharedCompositeEncoding} from '../compositemark'; import {ExprRef} from '../expr'; import {Projection} from '../projection'; +import {hasKey} from '../util'; import {BaseSpec, FrameMixins, ResolveMixins} from './base'; import {GenericUnitSpec, NormalizedUnitSpec, UnitSpec} from './unit'; @@ -45,5 +46,5 @@ export interface LayerSpec extends BaseSpec, FrameMixins, Resol export type NormalizedLayerSpec = GenericLayerSpec; export function isLayerSpec(spec: BaseSpec): spec is GenericLayerSpec { - return 'layer' in spec; + return hasKey(spec, 'layer'); } diff --git a/src/spec/repeat.ts b/src/spec/repeat.ts index fd816c6cd5..60bc04e62a 100644 --- a/src/spec/repeat.ts +++ b/src/spec/repeat.ts @@ -58,7 +58,7 @@ export interface LayerRepeatSpec extends BaseSpec, GenericCompositionLayoutWithC } export function isRepeatSpec(spec: BaseSpec): spec is RepeatSpec { - return 'repeat' in spec; + return hasKey(spec, 'repeat'); } export function isLayerRepeatSpec(spec: RepeatSpec): spec is LayerRepeatSpec { diff --git a/src/spec/toplevel.ts b/src/spec/toplevel.ts index dd245bfced..0c8e3da39b 100644 --- a/src/spec/toplevel.ts +++ b/src/spec/toplevel.ts @@ -80,7 +80,7 @@ export interface TopLevelProperties = GenericUn export type TopLevelUnitSpec = TopLevel> & DataMixins; export function isUnitSpec(spec: BaseSpec): spec is FacetedUnitSpec | NormalizedUnitSpec { - return 'mark' in spec; + return hasKey(spec, 'mark'); } diff --git a/src/timeunit.ts b/src/timeunit.ts index 456f151ce6..0ef009371f 100644 --- a/src/timeunit.ts +++ b/src/timeunit.ts @@ -1,6 +1,7 @@ import {isObject, isString} from 'vega-util'; import {DateTime, DateTimeExpr, dateTimeExprToExpr, dateTimeToExpr} from './datetime'; import {accessPathWithDatum, keys, stringify, varName} from './util'; +import {hasOwnProperty} from 'vega'; /** Time Unit that only corresponds to only one part of Date objects. */ export const LOCAL_SINGLE_TIMEUNIT_INDEX = { @@ -22,7 +23,7 @@ export type LocalSingleTimeUnit = keyof typeof LOCAL_SINGLE_TIMEUNIT_INDEX; export const TIMEUNIT_PARTS = keys(LOCAL_SINGLE_TIMEUNIT_INDEX); export function isLocalSingleTimeUnit(timeUnit: string): timeUnit is LocalSingleTimeUnit { - return !!LOCAL_SINGLE_TIMEUNIT_INDEX[timeUnit]; + return hasOwnProperty(LOCAL_SINGLE_TIMEUNIT_INDEX, timeUnit); } export const UTC_SINGLE_TIMEUNIT_INDEX = { @@ -325,13 +326,13 @@ export function fieldExpr(fullTimeUnit: TimeUnit, field: string, {end}: {end: bo for (const part of TIMEUNIT_PARTS) { if (containsTimeUnit(fullTimeUnit, part)) { - dateExpr[part] = func(part); + (dateExpr as any)[part] = func(part); lastTimeUnit = part; } } if (end) { - dateExpr[lastTimeUnit] += '+1'; + (dateExpr as any)[lastTimeUnit] += '+1'; } return dateTimeExprToExpr(dateExpr); @@ -459,7 +460,7 @@ const DATE_PARTS = { type DatePart = keyof typeof DATE_PARTS; export function isDatePart(timeUnit: LocalSingleTimeUnit): timeUnit is DatePart { - return !!DATE_PARTS[timeUnit]; + return hasOwnProperty(DATE_PARTS, timeUnit); } export function getDateTimePartAndStep( diff --git a/src/transform.ts b/src/transform.ts index a37f759ba5..17d7ffd010 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -36,7 +36,7 @@ export interface FilterTransform { } export function isFilter(t: Transform): t is FilterTransform { - return 'filter' in t; + return hasKey(t, 'filter'); } export interface CalculateTransform { @@ -434,7 +434,7 @@ export interface PivotTransform { } export function isPivot(t: Transform): t is PivotTransform { - return 'pivot' in t; + return hasKey(t, 'pivot'); } export interface DensityTransform { @@ -508,7 +508,7 @@ export interface DensityTransform { } export function isDensity(t: Transform): t is DensityTransform { - return 'density' in t; + return hasKey(t, 'density'); } export interface QuantileTransform { @@ -541,7 +541,7 @@ export interface QuantileTransform { } export function isQuantile(t: Transform): t is QuantileTransform { - return 'quantile' in t; + return hasKey(t, 'quantile'); } export interface RegressionTransform { @@ -597,7 +597,7 @@ export interface RegressionTransform { } export function isRegression(t: Transform): t is RegressionTransform { - return 'regression' in t; + return hasKey(t, 'regression'); } export interface LoessTransform { @@ -632,54 +632,54 @@ export interface LoessTransform { } export function isLoess(t: Transform): t is LoessTransform { - return 'loess' in t; + return hasKey(t, 'loess'); } export function isSample(t: Transform): t is SampleTransform { - return 'sample' in t; + return hasKey(t, 'sample'); } export function isWindow(t: Transform): t is WindowTransform { - return 'window' in t; + return hasKey(t, 'window'); } export function isJoinAggregate(t: Transform): t is JoinAggregateTransform { - return 'joinaggregate' in t; + return hasKey(t, 'joinaggregate'); } export function isFlatten(t: Transform): t is FlattenTransform { - return 'flatten' in t; + return hasKey(t, 'flatten'); } export function isCalculate(t: Transform): t is CalculateTransform { - return 'calculate' in t; + return hasKey(t, 'calculate'); } export function isBin(t: Transform): t is BinTransform { - return 'bin' in t; + return hasKey(t, 'bin'); } export function isImpute(t: Transform): t is ImputeTransform { - return 'impute' in t; + return hasKey(t, 'impute'); } export function isTimeUnit(t: Transform): t is TimeUnitTransform { - return 'timeUnit' in t; + return hasKey(t, 'timeUnit'); } export function isAggregate(t: Transform): t is AggregateTransform { - return 'aggregate' in t; + return hasKey(t, 'aggregate'); } export function isStack(t: Transform): t is StackTransform { - return 'stack' in t; + return hasKey(t, 'stack'); } export function isFold(t: Transform): t is FoldTransform { - return 'fold' in t; + return hasKey(t, 'fold'); } export function isExtent(t: Transform): t is ExtentTransform { - return 'extent' in t && !('density' in t) && !('regression' in t); + return hasKey(t, 'extent') && !hasKey(t, 'density') && !hasKey(t, 'regression'); } export type Transform = | AggregateTransform diff --git a/src/type.ts b/src/type.ts index 6773ba69d1..08f164d934 100644 --- a/src/type.ts +++ b/src/type.ts @@ -1,3 +1,4 @@ +import {hasOwnProperty} from 'vega-util'; import {keys} from './util'; /** @@ -14,7 +15,7 @@ export const Type = { export type Type = keyof typeof Type; export function isType(t: any): t is Type { - return t in Type; + return hasOwnProperty(Type, t); } export function isContinuous(type: Type): type is 'quantitative' | 'temporal' { diff --git a/src/util.ts b/src/util.ts index fc37076324..2506c21622 100644 --- a/src/util.ts +++ b/src/util.ts @@ -214,9 +214,11 @@ export function isEmpty(obj: object) { // This is a stricter version of Object.keys but with better types. See https://github.com/Microsoft/TypeScript/pull/12253#issuecomment-263132208 export const keys = Object.keys as (o: T) => Extract[]; -export const vals = Object.values; +// Stricter version from https://github.com/microsoft/TypeScript/issues/51572#issuecomment-1319153323 +export const vals = Object.values as (obj: T) => Array; -export const entries = Object.entries; +// Stricter version from https://github.com/microsoft/TypeScript/issues/51572#issuecomment-1319153323 +export const entries = Object.entries as (obj: T) => Array<[keyof T, T[keyof T]]>; // Using mapped type to declare a collect of flags for a string literal type S // https://www.typescriptlang.org/docs/handbook/advanced-types.html#mapped-types @@ -513,10 +515,14 @@ export function stringify(data: any) { /** * Check if the input object has the key and it's not undefined. + * * @param object the object * @param key the key to search * @returns if the object has the key and it's not undefined. */ -export function hasKey(object: unknown, key: string) { - return isObject(object) && hasOwnProperty(object, key) && (object as any)[key] !== undefined; +// export function hasKey(object: unknown, key: string) { +// return isObject(object) && hasOwnProperty(object, key) && (object as any)[key] !== undefined; +// } +export function hasKey(obj: T, key: string | number | symbol): key is keyof T { + return isObject(obj) && hasOwnProperty(obj, key) && (obj as any)[key] !== undefined; } diff --git a/src/vega.schema.ts b/src/vega.schema.ts index 01dd369167..0f767cb19a 100644 --- a/src/vega.schema.ts +++ b/src/vega.schema.ts @@ -45,7 +45,7 @@ import {isArray} from 'vega-util'; import {Value} from './channeldef'; import {ExprRef} from './expr'; import {SortOrder} from './sort'; -import {Dict, Flag, keys} from './util'; +import {Dict, Flag, hasKey, keys} from './util'; export type {VgSortField, VgUnionSortField, VgCompare, VgTitle, LayoutAlign, ProjectionType, VgExprRef}; @@ -87,7 +87,7 @@ export type VgScaleDataRefWithSort = ScaleDataRef & { }; export function isSignalRef(o: any): o is SignalRef { - return !!o?.signal; + return hasKey(o, 'signal'); } // TODO: add type of value (Make it VgValueRef {value?:V ...}) @@ -121,7 +121,7 @@ export type VgMultiFieldsRefWithSort = ScaleMultiFieldsRef & { export type VgRange = RangeScheme | ScaleData | RangeBand | RangeRaw; export function isVgRangeStep(range: VgRange): range is VgRangeStep { - return !!range['step']; + return hasKey(range, 'step'); } export interface VgRangeStep { @@ -193,21 +193,21 @@ export interface VgLayout { export function isDataRefUnionedDomain(domain: VgDomain): domain is VgScaleMultiDataRefWithSort { if (!isArray(domain)) { - return 'fields' in domain && !('data' in domain); + return hasKey(domain, 'fields') && !hasKey(domain, 'data'); } return false; } export function isFieldRefUnionDomain(domain: VgDomain): domain is VgMultiFieldsRefWithSort { if (!isArray(domain)) { - return 'fields' in domain && 'data' in domain; + return hasKey(domain, 'fields') && hasKey(domain, 'data'); } return false; } export function isDataRefDomain(domain: VgDomain | any): domain is VgScaleDataRefWithSort { if (!isArray(domain)) { - return 'field' in domain && 'data' in domain; + return hasKey(domain, 'field') && hasKey(domain, 'data'); } return false; } diff --git a/test-runtime/discrete.test.ts b/test-runtime/discrete.test.ts index bf4a83eb64..f30dc81978 100644 --- a/test-runtime/discrete.test.ts +++ b/test-runtime/discrete.test.ts @@ -25,7 +25,7 @@ describe(`point selections at runtime in unit views`, () => { it('should add values to the store', async () => { for (let i = 0; i < hits.qq.length; i++) { await embed(spec('unit', i, {type})); - const store = await page.evaluate(pt('qq', i)); + const store = (await page.evaluate(pt('qq', i))) as [any]; expect(store).toHaveLength(1); expect(store[0]).toHaveProperty(SELECTION_ID); await testRender(`click_${i}`); @@ -39,7 +39,7 @@ describe(`point selections at runtime in unit views`, () => { const t = async (emb: (i: number) => void) => { for (let i = 0; i < hits.qq.length; i++) { emb(i); - const store = await page.evaluate(pt('qq', i)); + const store = (await page.evaluate(pt('qq', i))) as [any]; expect(store).toHaveLength(1); expect(store[0].fields).toHaveLength(fields.length); expect(store[0].values).toHaveLength(fields.length); @@ -70,10 +70,10 @@ describe(`point selections at runtime in unit views`, () => { it('should clear out the store', async () => { for (let i = 0; i < hits.qq_clear.length; i++) { await embed(spec('unit', i, {type})); - let store = await page.evaluate(pt('qq', i)); + let store = (await page.evaluate(pt('qq', i))) as [any]; expect(store).toHaveLength(1); - store = await page.evaluate(pt('qq_clear', i)); + store = (await page.evaluate(pt('qq_clear', i))) as [any]; expect(store).toHaveLength(0); await testRender(`clear_${i}`); } @@ -90,7 +90,7 @@ describe(`point selections at runtime in unit views`, () => { for (let i = 0; i < hits.bins.length; i++) { await embed(spec('unit', i, {type, encodings}, {x: {bin: true}, y: {bin: true}})); - const store = await page.evaluate(pt('bins', i)); + const store = (await page.evaluate(pt('bins', i))) as [any]; expect(store).toHaveLength(1); expect(store[0].fields).toHaveLength(fields.length); expect(store[0].values).toHaveLength(fields.length); diff --git a/test-runtime/interval.test.ts b/test-runtime/interval.test.ts index 9cf49edb06..2f1f4a0c54 100644 --- a/test-runtime/interval.test.ts +++ b/test-runtime/interval.test.ts @@ -25,7 +25,7 @@ describe('interval selections at runtime in unit views', () => { it('should add extents to the store', async () => { for (let i = 0; i < hits.drag.length; i++) { await embed(spec('unit', i, {type})); - const store = await page.evaluate(brush('drag', i)); + const store = (await page.evaluate(brush('drag', i))) as [any]; expect(store).toHaveLength(1); expect(store[0].fields).toHaveLength(2); expect(store[0].values).toHaveLength(2); @@ -44,7 +44,7 @@ describe('interval selections at runtime in unit views', () => { it('should respect projections', async () => { await embed(spec('unit', 0, {type, encodings: ['x']})); for (let i = 0; i < hits.drag.length; i++) { - const store = await page.evaluate(brush('drag', i)); + const store = (await page.evaluate(brush('drag', i))) as [any]; expect(store).toHaveLength(1); expect(store[0].fields).toHaveLength(1); expect(store[0].values).toHaveLength(1); @@ -57,7 +57,7 @@ describe('interval selections at runtime in unit views', () => { await embed(spec('unit', 1, {type, encodings: ['y']})); for (let i = 0; i < hits.drag.length; i++) { - const store = await page.evaluate(brush('drag', i)); + const store = (await page.evaluate(brush('drag', i))) as [any]; expect(store).toHaveLength(1); expect(store[0].fields).toHaveLength(1); expect(store[0].values).toHaveLength(1); @@ -72,10 +72,10 @@ describe('interval selections at runtime in unit views', () => { it('should clear out stored extents', async () => { for (let i = 0; i < hits.drag_clear.length; i++) { await embed(spec('unit', i, {type})); - let store = await page.evaluate(brush('drag', i)); + let store = (await page.evaluate(brush('drag', i))) as [any]; expect(store).toHaveLength(1); - store = await page.evaluate(brush('drag_clear', i)); + store = (await page.evaluate(brush('drag_clear', i))) as [any]; expect(store).toHaveLength(0); await testRender(`clear_${i}`); } @@ -95,7 +95,7 @@ describe('interval selections at runtime in unit views', () => { ) ); for (let i = 0; i < hits.bins.length; i++) { - const store = await page.evaluate(brush('bins', i)); + const store = (await page.evaluate(brush('bins', i))) as [any]; expect(store).toHaveLength(1); expect(store[0].fields).toHaveLength(1); expect(store[0].values).toHaveLength(1); @@ -122,7 +122,7 @@ describe('interval selections at runtime in unit views', () => { for (let i = 0; i < hits.drag.length; i++) { await embed(spec('unit', i, {type}, {x: {type: 'ordinal'}, y: {type: 'nominal'}})); - const store = await page.evaluate(brush('drag', i)); + const store = (await page.evaluate(brush('drag', i))) as [any]; expect(store).toHaveLength(1); expect(store[0].fields).toHaveLength(2); expect(store[0].values).toHaveLength(2); @@ -151,7 +151,7 @@ describe('interval selections at runtime in unit views', () => { [1496346498000, 1504364922000] ]; for (let i = 0; i < hits.drag.length; i++) { - const store = toNumber(await page.evaluate(brush('drag', i))); + const store = toNumber((await page.evaluate(brush('drag', i))) as [any]); expect(store).toEqual(expect.arrayContaining(extents[i])); await testRender(`temporal_${i}`); } @@ -166,7 +166,7 @@ describe('interval selections at runtime in unit views', () => { [1325752128000, 1325837664000] ]; for (let i = 0; i < hits.drag.length; i++) { - const store = toNumber(await page.evaluate(brush('drag', i))); + const store = toNumber((await page.evaluate(brush('drag', i))) as [any]); expect(store).toEqual(expect.arrayContaining(extents[i])); await testRender(`dayTimeUnit_${i}`); } @@ -188,7 +188,7 @@ describe('interval selections at runtime in unit views', () => { } ) ); - const store = await page.evaluate(brush('drag', i)); + const store = (await page.evaluate(brush('drag', i))) as [any]; expect(store).toHaveLength(1); expect(store[0].fields).toHaveLength(2); expect(store[0].values).toHaveLength(2); diff --git a/test-runtime/resolve.test.ts b/test-runtime/resolve.test.ts index c6c195f46d..d3045c4488 100644 --- a/test-runtime/resolve.test.ts +++ b/test-runtime/resolve.test.ts @@ -16,7 +16,7 @@ import {TopLevelSpec} from '../src'; for (const type of selectionTypes) { const isInterval = type === 'interval'; - const hits = isInterval ? hitsMaster.interval : hitsMaster.discrete; + const hits: any = isInterval ? hitsMaster.interval : hitsMaster.discrete; const fn = isInterval ? brush : pt; describe(`${type} selections at runtime`, () => { @@ -55,7 +55,7 @@ for (const type of selectionTypes) { for (let i = 0; i < hits[specType].length; i++) { await embed(spec(specType, i, selection)); const parent = parentSelector(specType, i); - const store = await page.evaluate(fn(specType, i, parent)); + const store = (await page.evaluate(fn(specType, i, parent))) as [any]; expect(store).toHaveLength(1); expect(store[0].unit).toMatch(unitNameRegex(specType, i)); await testRender(`global_${i}`); @@ -84,7 +84,7 @@ for (const type of selectionTypes) { await embed(spec(specType, 0, selection)); for (let i = 0; i < hits[specType].length; i++) { const parent = parentSelector(specType, i); - const store = await page.evaluate(fn(specType, i, parent)); + const store = (await page.evaluate(fn(specType, i, parent))) as [any]; expect(store).toHaveLength(i + 1); expect(store[i].unit).toMatch(unitNameRegex(specType, i)); await testRender(`${resolve}_${i}`); @@ -98,7 +98,7 @@ for (const type of selectionTypes) { for (let i = hits[`${specType}_clear`].length - 1; i >= 0; i--) { const parent = parentSelector(specType, i); - const store = await page.evaluate(fn(`${specType}_clear`, i, parent)); + const store = (await page.evaluate(fn(`${specType}_clear`, i, parent))) as [any]; expect(store).toHaveLength(i); if (i > 0) { expect(store[i - 1].unit).toMatch(unitNameRegex(specType, i - 1)); diff --git a/test-runtime/toggle.test.ts b/test-runtime/toggle.test.ts index 0621e8787e..938e686844 100644 --- a/test-runtime/toggle.test.ts +++ b/test-runtime/toggle.test.ts @@ -12,7 +12,7 @@ const hits = { composite: [1, 3, 5, 7, 8, 9] }; -function toggle(key: string, idx: number, shiftKey: boolean, parent?: string) { +function toggle(key: keyof typeof hits, idx: number, shiftKey: boolean, parent?: string) { const fn = key.match('_clear') ? 'clear' : 'pt'; return `${fn}(${hits[key][idx]}, ${stringValue(parent)}, ${!!shiftKey})`; } diff --git a/test-runtime/translate.test.ts b/test-runtime/translate.test.ts index 3f3850092e..6590ddd73d 100644 --- a/test-runtime/translate.test.ts +++ b/test-runtime/translate.test.ts @@ -56,9 +56,9 @@ describe('Translate interval selections at runtime', () => { it('should move back-and-forth', async () => { for (let i = 0; i < hits.translate.length; i++) { await embed(spec('unit', i, {type, ...binding})); - const drag = (await page.evaluate(brush('drag', i)))[0]; + const drag = ((await page.evaluate(brush('drag', i))) as [any])[0]; await testRender(`${i}-0`); - const translate = (await page.evaluate(brush('translate', i, null, bind === unbound)))[0]; + const translate = ((await page.evaluate(brush('translate', i, null, bind === unbound))) as [any])[0]; expect(translate.values[0][0])[assertExtent[bind].x[i]](drag.values[0][0]); expect(translate.values[0][1])[assertExtent[bind].x[i]](drag.values[0][1]); expect(translate.values[1][0])[assertExtent[bind].y[i]](drag.values[1][0]); @@ -81,9 +81,9 @@ describe('Translate interval selections at runtime', () => { } ) ); - const drag = (await page.evaluate(brush('bins', i)))[0]; + const drag = ((await page.evaluate(brush('bins', i))) as [any])[0]; await testRender(`bins_${i}-0`); - const translate = (await page.evaluate(brush('bins_translate', i, null, bind === unbound)))[0]; + const translate = ((await page.evaluate(brush('bins_translate', i, null, bind === unbound))) as [any])[0]; expect(translate.values[0][0])[assertExtent[bind].y[i]](drag.values[0][0]); expect(translate.values[0][1])[assertExtent[bind].y[i]](drag.values[0][1]); await testRender(`bins_${i}-1`); @@ -119,9 +119,9 @@ describe('Translate interval selections at runtime', () => { } ) ); - const drag = (await page.evaluate(brush('drag', i)))[0]; + const drag = ((await page.evaluate(brush('drag', i))) as [any])[0]; await testRender(`logpow_${i}-0`); - const translate = (await page.evaluate(brush('translate', i, null, bind === unbound)))[0]; + const translate = ((await page.evaluate(brush('translate', i, null, bind === unbound))) as [any])[0]; expect(translate.values[0][0])[assertExtent[bind].x[i]](drag.values[0][0]); expect(translate.values[0][1])[assertExtent[bind].x[i]](drag.values[0][1]); expect(translate.values[1][0])[assertExtent[bind].y[i]](drag.values[1][0]); @@ -144,9 +144,9 @@ describe('Translate interval selections at runtime', () => { } ) ); - const drag = (await page.evaluate(brush('drag', i)))[0]; + const drag = ((await page.evaluate(brush('drag', i))) as [any])[0]; await testRender(`ord_${i}-0`); - const translate = (await page.evaluate(brush('translate', i, null, true)))[0]; + const translate = ((await page.evaluate(brush('translate', i, null, true))) as [any])[0]; expect(translate.values[0][0])[assertExtent[bind].x[i]](drag.values[0][0]); expect(translate.values[0][1])[assertExtent[bind].x[i]](drag.values[0][1]); expect(translate.values[1][0])[assertExtent[bind].y[i]](drag.values[1][0]); @@ -167,16 +167,16 @@ describe('Translate interval selections at runtime', () => { } }; it(`should work with shared scales in ${specType} views`, async () => { - for (let i = 0; i < hits[specType].length; i++) { + for (let i = 0; i < (hits as any)[specType].length; i++) { await embed(spec(specType, 0, {type, ...binding}, {resolve: {scale: {x: 'shared', y: 'shared'}}})); const parent = parentSelector(specType, i); - const xscale = await page.evaluate('view._runtime.scales.x.value.domain()'); - const yscale = await page.evaluate('view._runtime.scales.y.value.domain()'); - const drag = (await page.evaluate(brush(specType, i, parent)))[0]; - expect(drag.values[0][0])[assertExtents[specType].x[i]](xscale[0]); - expect(drag.values[0][1])[assertExtents[specType].x[i]](xscale[1]); - expect(drag.values[1][0])[assertExtents[specType].y[i]](yscale[0]); - expect(drag.values[1][1])[assertExtents[specType].y[i]](yscale[1]); + const xscale = (await page.evaluate('view._runtime.scales.x.value.domain()')) as any[]; + const yscale = (await page.evaluate('view._runtime.scales.y.value.domain()')) as any[]; + const drag = ((await page.evaluate(brush(specType, i, parent))) as [any])[0]; + ((expect(drag.values[0][0]) as any)[(assertExtents as any)[specType].x[i]] as any)(xscale[0]); + ((expect(drag.values[0][1]) as any)[(assertExtents as any)[specType].x[i]] as any)(xscale[1]); + ((expect(drag.values[1][0]) as any)[(assertExtents as any)[specType].y[i]] as any)(yscale[0]); + ((expect(drag.values[1][1]) as any)[(assertExtents as any)[specType].y[i]] as any)(yscale[1]); await testRender(`${specType}_${i}`); } }); diff --git a/test-runtime/util.ts b/test-runtime/util.ts index 62c30db31a..f7a482fd6c 100644 --- a/test-runtime/util.ts +++ b/test-runtime/util.ts @@ -11,7 +11,7 @@ const output = 'test-runtime/resources'; export type ComposeType = 'unit' | 'repeat' | 'facet'; export const selectionTypes: SelectionType[] = ['point', 'interval']; -export const compositeTypes: ComposeType[] = ['repeat', 'facet']; +export const compositeTypes = ['repeat', 'facet'] as const; export const resolutions: SelectionResolution[] = ['union', 'intersect']; export const bound = 'bound'; @@ -249,7 +249,7 @@ export function geoSpec(selDef?: IntervalSelectionConfigWithoutType): TopLevelSp }; } -export function unitNameRegex(specType: ComposeType, idx: number) { +export function unitNameRegex(specType: 'repeat' | 'facet', idx: number) { const name = UNIT_NAMES[specType][idx].replace('child_', ''); return new RegExp(`child(.*?)_${name}`); } @@ -258,12 +258,13 @@ export function parentSelector(compositeType: ComposeType, index: number) { return compositeType === 'facet' ? `cell > g:nth-child(${index + 1})` : `${UNIT_NAMES.repeat[index]}_group`; } -export function brush(key: string, idx: number, parent?: string, targetBrush?: boolean) { +export type BrushKeys = keyof typeof hits.interval; +export function brush(key: BrushKeys, idx: number, parent?: string, targetBrush?: boolean) { const fn = key.match('_clear') ? 'clear' : 'brush'; return `${fn}(${hits.interval[key][idx].join(', ')}, ${stringValue(parent)}, ${!!targetBrush})`; } -export function pt(key: string, idx: number, parent?: string) { +export function pt(key: keyof typeof hits.discrete, idx: number, parent?: string) { const fn = key.match('_clear') ? 'clear' : 'pt'; return `${fn}(${hits.discrete[key][idx]}, ${stringValue(parent)})`; } @@ -271,7 +272,7 @@ export function pt(key: string, idx: number, parent?: string) { export function embedFn(page: Page) { return async (specification: TopLevelSpec) => { await page.evaluate( - (_: any) => window['embed'](_), + (_: any) => (window as any).embed(_), // specification is serializable even if the types don't agree specification as any ); diff --git a/test-runtime/zoom.test.ts b/test-runtime/zoom.test.ts index db05298f2a..2cb45f022d 100644 --- a/test-runtime/zoom.test.ts +++ b/test-runtime/zoom.test.ts @@ -3,6 +3,7 @@ import { bound, brush, + BrushKeys, compositeTypes, embedFn, geoSpec, @@ -21,7 +22,7 @@ import {TopLevelSpec} from '../src'; type InOut = 'in' | 'out'; -function zoom(key: string, idx: number, direction: InOut, parent?: string, targetBrush?: boolean) { +function zoom(key: keyof typeof hits, idx: number, direction: InOut, parent?: string, targetBrush?: boolean) { const delta = direction === 'out' ? 200 : -200; return `zoom(${hits[key][idx]}, ${delta}, ${parent}, ${targetBrush})`; } @@ -56,13 +57,13 @@ describe('Zoom interval selections at runtime', () => { out: ['toBeLessThanOrEqual', 'toBeGreaterThanOrEqual'] } as const; - async function setup(brushKey: string, idx: number, encodings: string[], parent?: string) { + async function setup(brushKey: BrushKeys, idx: number, encodings: string[], parent?: string) { const inOut: InOut = idx % 2 ? 'out' : 'in'; let xold: number[]; let yold: number[]; if (bind === unbound) { - const drag = (await page.evaluate(brush(brushKey, idx, parent)))[0]; + const drag = ((await page.evaluate(brush(brushKey, idx, parent))) as [any])[0]; xold = drag.values[0].sort(cmp); yold = encodings.includes('y') ? drag.values[encodings.indexOf('x') + 1].sort(cmp) : null; } else { @@ -79,7 +80,7 @@ describe('Zoom interval selections at runtime', () => { const {inOut, xold, yold} = await setup('drag', i, ['x', 'y']); await testRender(`${inOut}-0`); - const zoomed = (await page.evaluate(zoom('zoom', i, inOut, null, bind === unbound)))[0]; + const zoomed = ((await page.evaluate(zoom('zoom', i, inOut, null, bind === unbound))) as [any])[0]; const xnew = zoomed.values[0].sort(cmp); const ynew = zoomed.values[1].sort(cmp); await testRender(`${inOut}-1`); @@ -109,7 +110,7 @@ describe('Zoom interval selections at runtime', () => { const {inOut, yold} = await setup('bins', i, encodings); await testRender(`bins_${inOut}-0`); - const zoomed = (await page.evaluate(zoom('bins', i, inOut, null, bind === unbound)))[0]; + const zoomed = ((await page.evaluate(zoom('bins', i, inOut, null, bind === unbound))) as [any])[0]; const ynew = zoomed.values[0].sort(cmp); expect(ynew[0])[assertExtent[inOut][0]](yold[0]); expect(ynew[1])[assertExtent[inOut][1]](yold[1]); @@ -126,7 +127,7 @@ describe('Zoom interval selections at runtime', () => { const {inOut, xold} = await setup('drag', i, encodings); await testRender(`temporal_${inOut}-0`); - const zoomed = (await page.evaluate(zoom('zoom', i, inOut, null, bind === unbound)))[0]; + const zoomed = ((await page.evaluate(zoom('zoom', i, inOut, null, bind === unbound))) as [any])[0]; const xnew = zoomed.values[0].sort(cmp); expect(+xnew[0])[assertExtent[inOut][0]](+new Date(xold[0])); expect(+xnew[1])[assertExtent[inOut][1]](+new Date(xold[1])); @@ -150,7 +151,7 @@ describe('Zoom interval selections at runtime', () => { const {inOut, xold, yold} = await setup('drag', i, ['x', 'y']); await testRender(`logpow_${inOut}-0`); - const zoomed = (await page.evaluate(zoom('zoom', i, inOut, null, bind === unbound)))[0]; + const zoomed = ((await page.evaluate(zoom('zoom', i, inOut, null, bind === unbound))) as [any])[0]; const xnew = zoomed.values[0].sort(cmp); const ynew = zoomed.values[1].sort(cmp); expect(xnew[0])[assertExtent[inOut][0]](xold[0]); @@ -178,7 +179,7 @@ describe('Zoom interval selections at runtime', () => { const {inOut, xold, yold} = await setup('drag', i, ['x', 'y']); await testRender(`ord_${inOut}-0`); - const zoomed = (await page.evaluate(zoom('zoom', i, inOut, null, bind === unbound)))[0]; + const zoomed = ((await page.evaluate(zoom('zoom', i, inOut, null, bind === unbound))) as [any])[0]; const xnew = zoomed.values[0].sort(cmp); const ynew = zoomed.values[1].sort(cmp); @@ -199,8 +200,10 @@ describe('Zoom interval selections at runtime', () => { for (let i = 0; i < hits.bins.length; i++) { await embed(spec(specType, 0, {type, ...binding}, {resolve: {scale: {x: 'shared', y: 'shared'}}})); const parent = parentSelector(specType, i); - const {inOut, xold, yold} = await setup(specType, i, ['x', 'y'], parent); - const zoomed = (await page.evaluate(zoom('bins', i, inOut, null, false /* bind === unbound */)))[0]; + const {inOut, xold, yold} = await setup(specType as any, i, ['x', 'y'], parent); + const zoomed = ( + (await page.evaluate(zoom('bins', i, inOut, null, false /* bind === unbound */))) as [any] + )[0]; const xnew = zoomed.values[0].sort(cmp); const ynew = zoomed.values[1].sort(cmp); expect(xnew[0])[assertExtent[inOut][0]](xold[0]); diff --git a/test/compile/axis/properties.test.ts b/test/compile/axis/properties.test.ts index 7db2e31d02..d77ff5a1e8 100644 --- a/test/compile/axis/properties.test.ts +++ b/test/compile/axis/properties.test.ts @@ -278,7 +278,7 @@ describe('compile/axis/properties', () => { }); it('correctly align y-axis labels for labelAngle and orient signals', () => { - const ast = parseExpression(defaultLabelAlign({signal: 'a'}, {signal: 'o'}, 'y')['signal']); + const ast = (parseExpression(defaultLabelAlign({signal: 'a'}, {signal: 'o'}, 'y') as any) as any).signal; let a: number; let o: AxisOrient; // test all angles @@ -297,7 +297,7 @@ describe('compile/axis/properties', () => { it('correctly align x-axis labels for labelAngle and orient signals', () => { return new Promise(done => { - const ast = parseExpression(defaultLabelAlign({signal: 'a'}, {signal: 'o'}, 'x')['signal']); + const ast = parseExpression((defaultLabelAlign({signal: 'a'}, {signal: 'o'}, 'x') as any).signal); let a: number; let o: AxisOrient; // test all angles @@ -390,7 +390,7 @@ describe('compile/axis/properties', () => { it('correctly align y-axis labels for labelAngle and orient signals', () => { return new Promise(done => { - const ast = parseExpression(defaultLabelBaseline({signal: 'a'}, {signal: 'o'}, 'y')['signal']); + const ast = parseExpression((defaultLabelBaseline({signal: 'a'}, {signal: 'o'}, 'y') as any).signal); let a: number; let o: AxisOrient; // test all angles @@ -411,7 +411,7 @@ describe('compile/axis/properties', () => { it('correctly align x-axis labels for labelAngle and orient signals', () => { return new Promise(done => { - const ast = parseExpression(defaultLabelBaseline({signal: 'a'}, {signal: 'o'}, 'x')['signal']); + const ast = parseExpression((defaultLabelBaseline({signal: 'a'}, {signal: 'o'}, 'x') as any).signal); let a: number; let o: AxisOrient; // test all angles diff --git a/test/compile/compile.test.ts b/test/compile/compile.test.ts index 3991e35bc9..0f551306fa 100644 --- a/test/compile/compile.test.ts +++ b/test/compile/compile.test.ts @@ -571,7 +571,7 @@ describe('compile/compile', () => { } }); - expect(spec.autosize['resize']).toBeTruthy(); + expect((spec.autosize as any).resize).toBeTruthy(); }); }); diff --git a/test/compile/legend/encode.test.ts b/test/compile/legend/encode.test.ts index d36bbaae7f..31157d5e93 100644 --- a/test/compile/legend/encode.test.ts +++ b/test/compile/legend/encode.test.ts @@ -76,7 +76,7 @@ describe('compile/legend', () => { legendType } ); - expect(symbol.opacity['value']).toBe(0.7); // default opacity is 0.7. + expect((symbol.opacity as any).value).toBe(0.7); // default opacity is 0.7. }); it('should use symbolOpacity when set', () => { @@ -121,7 +121,7 @@ describe('compile/legend', () => { legendType } ); - expect(symbol.opacity['value']).toBe(1); + expect((symbol.opacity as any).value).toBe(1); }); }); @@ -143,7 +143,7 @@ describe('compile/legend', () => { } ); - expect(gradient.opacity['value']).toBe(0.7); // default opacity is 0.7. + expect((gradient.opacity as any).value).toBe(0.7); // default opacity is 0.7. }); }); diff --git a/test/compile/mark/area.test.ts b/test/compile/mark/area.test.ts index d499536732..0154f7c154 100644 --- a/test/compile/mark/area.test.ts +++ b/test/compile/mark/area.test.ts @@ -1,3 +1,4 @@ +import {SignalRef} from 'vega'; import {COLOR, X, Y} from '../../../src/channel'; import {area} from '../../../src/compile/mark/area'; import {Encoding} from '../../../src/encoding'; @@ -55,7 +56,7 @@ describe('Mark: Area', () => { }); it('should use bin_mid for the defined check', () => { - expect(props.defined['signal']).toContain('bin_maxbins_10_IMDB_Rating_mid'); + expect((props.defined as SignalRef).signal).toContain('bin_maxbins_10_IMDB_Rating_mid'); }); }); diff --git a/test/compile/mark/encode/color.test.ts b/test/compile/mark/encode/color.test.ts index 3baa9086f8..8993451737 100644 --- a/test/compile/mark/encode/color.test.ts +++ b/test/compile/mark/encode/color.test.ts @@ -45,7 +45,7 @@ describe('compile/mark/encode/color', () => { const colorMixins = color(model); expect(colorMixins.stroke).toEqual({field: 'gender', scale: 'color'}); - expect(colorMixins.fill['value']).toBe('transparent'); + expect((colorMixins.fill as any).value).toBe('transparent'); }); it('add transparent fill when stroke is encoded', () => { @@ -68,7 +68,7 @@ describe('compile/mark/encode/color', () => { const colorMixins = color(model); expect(colorMixins.stroke).toEqual({field: 'gender', scale: 'stroke'}); - expect(colorMixins.fill['value']).toBe('transparent'); + expect((colorMixins.fill as any).value).toBe('transparent'); }); it('combines color with fill when filled=false', () => { diff --git a/test/compile/mark/encode/position-point.test.ts b/test/compile/mark/encode/position-point.test.ts index 02209fdb8e..ab56de6eb4 100644 --- a/test/compile/mark/encode/position-point.test.ts +++ b/test/compile/mark/encode/position-point.test.ts @@ -26,7 +26,7 @@ describe('compile/mark/encode/position-point', () => { [X, Y].forEach(channel => { const mixins = pointPosition(channel, model, {defaultPos: 'zeroOrMin'}); - expect(mixins[channel]['field']).toEqual(model.getName(channel)); + expect((mixins[channel] as any).field).toEqual(model.getName(channel)); }); }); }); diff --git a/test/compile/mark/encode/position-range.test.ts b/test/compile/mark/encode/position-range.test.ts index d088b81874..c562d57fbc 100644 --- a/test/compile/mark/encode/position-range.test.ts +++ b/test/compile/mark/encode/position-range.test.ts @@ -27,7 +27,7 @@ describe('compile/mark/encode/position-range', () => { [X, Y].forEach(channel => { const mixins = rangePosition(channel, model, {defaultPos: 'zeroOrMin', defaultPos2: 'zeroOrMin'}); - expect(mixins[`${channel}c`]['field']).toEqual(model.getName(channel)); + expect((mixins[`${channel}c`] as any).field).toEqual(model.getName(channel)); const sizeChannel = getSizeChannel(channel); expect(mixins[sizeChannel]).toEqual({value: 42}); @@ -63,8 +63,8 @@ describe('compile/mark/encode/position-range', () => { [X, Y].forEach(channel => { const mixins = rangePosition(channel, model, {defaultPos: 'zeroOrMin', defaultPos2: 'zeroOrMin'}); - expect(mixins[channel]['field']).toEqual(model.getName(channel)); - expect(mixins[`${channel}2`]['field']).toEqual(model.getName(`${channel}2`)); + expect((mixins[channel] as any).field).toEqual(model.getName(channel)); + expect((mixins[`${channel}2`] as any).field).toEqual(model.getName(`${channel}2`)); }); }); }); diff --git a/test/compile/mark/encode/position-rect.test.ts b/test/compile/mark/encode/position-rect.test.ts index f042b1f725..bfa882710e 100644 --- a/test/compile/mark/encode/position-rect.test.ts +++ b/test/compile/mark/encode/position-rect.test.ts @@ -21,10 +21,10 @@ describe('compile/mark/encode/position-rect', () => { }); const props = rectPosition(model, 'x'); - expect(props.x['offset']).toEqual({ + expect((props.x as any).offset).toEqual({ signal: '0.5 + (r ? -1 : 1) * -0.5' }); - expect(props.x2['offset']).toEqual({ + expect((props.x2 as any).offset).toEqual({ signal: '0.5 + (r ? -1 : 1) * 0.5' }); }); @@ -71,8 +71,8 @@ describe('compile/mark/encode/position-rect', () => { }); const props = rectPosition(model, 'x'); - expect(props.x['field']).toBe(`yearmonth_date_${OFFSETTED_RECT_END_SUFFIX}`); - expect(props.x2['field']).toBe(`yearmonth_date_${OFFSETTED_RECT_START_SUFFIX}`); + expect((props.x as any).field).toBe(`yearmonth_date_${OFFSETTED_RECT_END_SUFFIX}`); + expect((props.x2 as any).field).toBe(`yearmonth_date_${OFFSETTED_RECT_START_SUFFIX}`); }); it('produces correct x-mixins for binned data with step and start field, without end field', () => { @@ -113,10 +113,10 @@ describe('compile/mark/encode/position-rect', () => { }); const props = rectPosition(model, 'y'); - expect(props.y2['offset']).toEqual({ + expect((props.y2 as any).offset).toEqual({ signal: '0.5 + (r ? -1 : 1) * -0.5' }); - expect(props.y['offset']).toEqual({ + expect((props.y as any).offset).toEqual({ signal: '0.5 + (r ? -1 : 1) * 0.5' }); }); @@ -138,10 +138,10 @@ describe('compile/mark/encode/position-rect', () => { const props = rectPosition(model, 'x'); - expect(props.x['offset']).toEqual({ + expect((props.x as any).offset).toEqual({ signal: '0.5 + (r ? -1 : 1) * -1' }); - expect(props.x2['offset']).toEqual({ + expect((props.x2 as any).offset).toEqual({ signal: '0.5 + (r ? -1 : 1) * 1' }); }); @@ -162,10 +162,10 @@ describe('compile/mark/encode/position-rect', () => { }); const props = rectPosition(model, 'y'); - expect(props.y2['offset']).toEqual({ + expect((props.y2 as any).offset).toEqual({ signal: '0.5 + (r ? -1 : 1) * -1' }); - expect(props.y['offset']).toEqual({ + expect((props.y as any).offset).toEqual({ signal: '0.5 + (r ? -1 : 1) * 1' }); }); @@ -231,10 +231,10 @@ describe('compile/mark/encode/position-rect', () => { model.parseAxesAndHeaders(); const props = rectPosition(model, 'x'); - expect(props.x['offset']).toEqual({ + expect((props.x as any).offset).toEqual({ signal: 't + -0.5' }); - expect(props.x2['offset']).toEqual({ + expect((props.x2 as any).offset).toEqual({ signal: 't + 0.5' }); }); @@ -257,10 +257,10 @@ describe('compile/mark/encode/position-rect', () => { model.parseAxesAndHeaders(); const props = rectPosition(model, 'x'); - expect(props.x['offset']).toEqual({ + expect((props.x as any).offset).toEqual({ signal: 't + (r ? -1 : 1) * (o + -0.5)' }); - expect(props.x2['offset']).toEqual({ + expect((props.x2 as any).offset).toEqual({ signal: 't + (r ? -1 : 1) * (o + 0.5)' }); }); diff --git a/test/compile/mark/line.test.ts b/test/compile/mark/line.test.ts index d758d2c179..bdb9be7996 100644 --- a/test/compile/mark/line.test.ts +++ b/test/compile/mark/line.test.ts @@ -72,7 +72,7 @@ describe('Mark: Line', () => { const props = line.encodeEntry(model); // If size field is dropped, then strokeWidth only have value - expect(props.strokeWidth && props.strokeWidth['scale']).toBeFalsy(); + expect(props.strokeWidth && (props.strokeWidth as any).scale).toBeFalsy(); expect(localLogger.warns[0]).toEqual(log.message.LINE_WITH_VARYING_SIZE); }) ); diff --git a/test/compile/mark/point.test.ts b/test/compile/mark/point.test.ts index f6065fa271..8750878604 100644 --- a/test/compile/mark/point.test.ts +++ b/test/compile/mark/point.test.ts @@ -323,8 +323,8 @@ describe('Mark: Point', () => { it('should have one condition for color with scale for "yield"', () => { expect(Array.isArray(props.stroke)).toBe(true); expect(props.stroke).toHaveLength(2); - expect(props.stroke[0].scale).toEqual(COLOR); - expect(props.stroke[0].field).toBe('yield'); + expect((props.stroke as any)[0].scale).toEqual(COLOR); + expect((props.stroke as any)[0].field).toBe('yield'); }); }); @@ -340,8 +340,8 @@ describe('Mark: Point', () => { it('should have one condition for color with scale for "yield"', () => { expect(Array.isArray(props.stroke)).toBe(true); expect(props.stroke).toHaveLength(2); - expect(props.stroke[0].test).toBe('true'); - expect(props.stroke[1].value).toBe('#4c78a8'); + expect((props.stroke as any)[0].test).toBe('true'); + expect((props.stroke as any)[1].value).toBe('#4c78a8'); }); }); @@ -415,7 +415,7 @@ describe('Mark: Square', () => { }); const props = square.encodeEntry(model); - expect(props.shape['value']).toBe('square'); + expect((props.shape as any).value).toBe('square'); }); it('should be filled by default', () => { @@ -427,7 +427,7 @@ describe('Mark: Square', () => { }); const props = square.encodeEntry(model); - expect(props.fill['value']).toBe('blue'); + expect((props.fill as any).value).toBe('blue'); }); it('with config.mark.filled:false should have transparent fill', () => { @@ -445,8 +445,8 @@ describe('Mark: Square', () => { const props = square.encodeEntry(model); - expect(props.stroke['value']).toBe('blue'); - expect(props.fill['value']).toBe('transparent'); + expect((props.stroke as any).value).toBe('blue'); + expect((props.fill as any).value).toBe('transparent'); }); }); @@ -460,11 +460,11 @@ describe('Mark: Circle', () => { const props = circle.encodeEntry(model); it('should have correct shape', () => { - expect(props.shape['value']).toBe('circle'); + expect((props.shape as any).value).toBe('circle'); }); it('should be filled by default', () => { - expect(props.fill['value']).toBe('blue'); + expect((props.fill as any).value).toBe('blue'); }); it('with config.mark.filled:false should have transparent fill', () => { @@ -482,8 +482,8 @@ describe('Mark: Circle', () => { const filledCircleProps = circle.encodeEntry(filledCircleModel); - expect(filledCircleProps.stroke['value']).toBe('blue'); - expect(filledCircleProps.fill['value']).toBe('transparent'); + expect((filledCircleProps.stroke as any).value).toBe('blue'); + expect((filledCircleProps.fill as any).value).toBe('transparent'); }); it('converts expression in mark properties to signal', () => { diff --git a/test/compile/model.test.ts b/test/compile/model.test.ts index 7389cb1915..27c414ff9d 100644 --- a/test/compile/model.test.ts +++ b/test/compile/model.test.ts @@ -186,7 +186,7 @@ describe('Model', () => { expect(model.assembleGroupEncodeEntry(true)).toBeUndefined(); - expect(model.assembleGroupEncodeEntry(false)['description']).toEqual({value: 'My awesome view'}); + expect((model.assembleGroupEncodeEntry(false) as any).description).toEqual({value: 'My awesome view'}); }); }); }); diff --git a/test/compile/scale/parse.test.ts b/test/compile/scale/parse.test.ts index 6137b1c2df..14cd522879 100644 --- a/test/compile/scale/parse.test.ts +++ b/test/compile/scale/parse.test.ts @@ -469,11 +469,11 @@ describe('src/compile', () => { it('should add a selection extent', () => { expect('selectionExtent' in xScale.explicit).toBeTruthy(); expect(xScale.explicit.selectionExtent.param).toBe('brush'); - expect(xScale.explicit.selectionExtent['encoding']).toBe('x'); + expect((xScale.explicit.selectionExtent as any).encoding).toBe('x'); expect('selectionExtent' in yScale.explicit).toBeTruthy(); expect(yScale.explicit.selectionExtent.param).toBe('foobar'); - expect(yScale.explicit.selectionExtent['field']).toBe('Miles_per_Gallon'); + expect((yScale.explicit.selectionExtent as any).field).toBe('Miles_per_Gallon'); }); }); }); diff --git a/test/compile/scale/range.test.ts b/test/compile/scale/range.test.ts index 26e014a5e9..a136096362 100644 --- a/test/compile/scale/range.test.ts +++ b/test/compile/scale/range.test.ts @@ -1,3 +1,4 @@ +import {RangeRaw} from 'vega'; import { defaultContinuousToDiscreteCount, interpolateRange, @@ -574,8 +575,8 @@ describe('compile/scale', () => { } }); const r = parseRangeForChannel('radius', model); - expect(r.value[0]).toBe(0); - expect(r.value[1]).toEqual({signal: 'min(width,height)/2'}); + expect((r.value as RangeRaw)[0]).toBe(0); + expect((r.value as RangeRaw)[1]).toEqual({signal: 'min(width,height)/2'}); }); }); diff --git a/test/compositemark/boxplot.test.ts b/test/compositemark/boxplot.test.ts index 931f9aa444..e4a1ecd39e 100644 --- a/test/compositemark/boxplot.test.ts +++ b/test/compositemark/boxplot.test.ts @@ -922,7 +922,7 @@ describe('normalizeBoxIQR', () => { defaultConfig ); - expect(normalizedSpec['layer'][1].layer[0]).toEqual({ + expect((normalizedSpec as any).layer[1].layer[0]).toEqual({ mark: { type: 'bar', style: 'boxplot-box', @@ -1012,7 +1012,7 @@ describe('normalizeBoxIQR', () => { expect(normalizedSpecWithTooltip).not.toEqual(normalizedSpecWithoutTooltip); - const innerLayer = normalizedSpecWithTooltip['layer'][0]['layer'][0]; + const innerLayer = (normalizedSpecWithTooltip as any).layer[0].layer[0]; const {tooltip, ...encodingWithoutTooltip} = innerLayer['encoding']; innerLayer['encoding'] = encodingWithoutTooltip; @@ -1034,8 +1034,8 @@ describe('normalizeBoxIQR', () => { ); // There is correct tooltips in whisker layer - const whiskerLayer = normalizedSpecWithTooltip['layer'][0]['layer'][1]; - for (const whisker of whiskerLayer['layer']) { + const whiskerLayer = (normalizedSpecWithTooltip as any).layer[0].layer[1]; + for (const whisker of whiskerLayer.layer) { const {tooltip} = whisker['encoding']; expect(array(tooltip)).toEqual([ { @@ -1046,16 +1046,16 @@ describe('normalizeBoxIQR', () => { ]); } - const whiskerAggregate = whiskerLayer['transform'][1]['aggregate']; - expect(whiskerLayer['transform'][1]['aggregate'][whiskerAggregate.length - 1]).toEqual({ + const whiskerAggregate = whiskerLayer['transform'][1].aggregate; + expect(whiskerLayer['transform'][1].aggregate[whiskerAggregate.length - 1]).toEqual({ op: 'mean', as: 'mean_people', field: 'people' }); // There is correct tooltips in whisker layer - const boxLayer = normalizedSpecWithTooltip['layer'][1]; - for (const box of boxLayer['layer']) { + const boxLayer = (normalizedSpecWithTooltip as any).layer[1]; + for (const box of boxLayer.layer) { const {tooltip} = box['encoding']; expect(array(tooltip)).toEqual([ { @@ -1066,12 +1066,12 @@ describe('normalizeBoxIQR', () => { ]); } - const boxAggregate = boxLayer['transform'][0]['aggregate']; + const boxAggregate = boxLayer['transform'][0].aggregate; const customBoxAggregate = boxAggregate[0]; expect(customBoxAggregate).toEqual({op: 'mean', as: 'mean_people', field: 'people'}); // There is no tooltip in outlier layer - expect(normalizedSpecWithTooltip['layer'][0]['layer'][0]['encoding']['tooltip']).toBeFalsy(); + expect((normalizedSpecWithTooltip as any).layer[0].layer[0]['encoding'].tooltip).toBeFalsy(); }); it('should include custom tooltip with aggregate into box and whiskers layer and custom tooltip without aggregate into outlier layer', () => { @@ -1099,8 +1099,8 @@ describe('normalizeBoxIQR', () => { ); // There are correct tooltips in whisker layer - const whiskerLayer = normalizedSpecWithTooltip['layer'][0]['layer'][1]; - for (const whisker of whiskerLayer['layer']) { + const whiskerLayer = (normalizedSpecWithTooltip as any).layer[0].layer[1]; + for (const whisker of whiskerLayer.layer) { const {tooltip} = whisker['encoding']; expect(array(tooltip)).toEqual([ { @@ -1111,16 +1111,16 @@ describe('normalizeBoxIQR', () => { ]); } - const whiskerAggregate = whiskerLayer['transform'][1]['aggregate']; - expect(whiskerLayer['transform'][1]['aggregate'][whiskerAggregate.length - 1]).toEqual({ + const whiskerAggregate = whiskerLayer['transform'][1].aggregate; + expect(whiskerLayer['transform'][1].aggregate[whiskerAggregate.length - 1]).toEqual({ op: 'mean', as: 'mean_people', field: 'people' }); // There are correct tooltips in whisker layer - const boxLayer = normalizedSpecWithTooltip['layer'][1]; - for (const box of boxLayer['layer']) { + const boxLayer = (normalizedSpecWithTooltip as any).layer[1]; + for (const box of boxLayer.layer) { const {tooltip} = box['encoding']; expect(array(tooltip)).toEqual([ { @@ -1131,12 +1131,12 @@ describe('normalizeBoxIQR', () => { ]); } - const boxAggregate = boxLayer['transform'][0]['aggregate']; + const boxAggregate = boxLayer['transform'][0].aggregate; const customBoxAggregate = boxAggregate[0]; expect(customBoxAggregate).toEqual({op: 'mean', as: 'mean_people', field: 'people'}); // There is correct tooltips in outlier layer - const {tooltip} = normalizedSpecWithTooltip['layer'][0]['layer'][0]['encoding']; + const {tooltip} = (normalizedSpecWithTooltip as any).layer[0].layer[0]['encoding']; expect(tooltip).toEqual({field: 'year', type: 'quantitative'}); }); @@ -1159,7 +1159,7 @@ describe('normalizeBoxIQR', () => { defaultConfig ); - const filteredLayerMixins = normalizedSpec['layer'][1]; + const filteredLayerMixins = (normalizedSpec as any).layer[1]; expect(filteredLayerMixins.transform[0]).toEqual({ timeUnit: {unit: 'year'}, field, diff --git a/test/compositemark/errorbar.test.ts b/test/compositemark/errorbar.test.ts index f9d14a294a..866875aee8 100644 --- a/test/compositemark/errorbar.test.ts +++ b/test/compositemark/errorbar.test.ts @@ -268,7 +268,7 @@ describe('normalizeErrorBar with raw data input', () => { assertIsUnitSpec(outputSpec); - expect(outputSpec.encoding.y['title']).toBe('population'); + expect((outputSpec.encoding.y as any).title).toBe('population'); }); it("should not overwrite transform with errorbar's transfroms", () => { @@ -395,11 +395,11 @@ describe('normalizeErrorBar with raw data input', () => { assertIsLayerSpec(outputSpec); for (const layer of outputSpec.layer) { - if (layer['mark']) { - if (layer['mark']['type'] === 'tick') { - expect(layer['mark']['size']).toBe(size); + if ((layer as any).mark) { + if ((layer as any).mark.type === 'tick') { + expect((layer as any).mark.size).toBe(size); } else { - expect(layer['mark']['size']).toBeFalsy(); + expect((layer as any).mark.size).toBeFalsy(); } } } @@ -419,11 +419,11 @@ describe('normalizeErrorBar with raw data input', () => { assertIsLayerSpec(outputSpec); for (const layer of outputSpec.layer) { - if (layer['mark']) { - if (layer['mark']['type'] === 'tick') { - expect(layer['mark']['size']).toBe(tickSize); + if ((layer as any).mark) { + if ((layer as any).mark.type === 'tick') { + expect((layer as any).mark.size).toBe(tickSize); } else { - expect(layer['mark']['size']).toBeFalsy(); + expect((layer as any).mark.size).toBeFalsy(); } } } @@ -442,11 +442,11 @@ describe('normalizeErrorBar with raw data input', () => { assertIsLayerSpec(outputSpec); for (const layer of outputSpec.layer) { - if (layer['mark']) { - if (layer['mark']['type'] === 'tick') { - expect(layer['mark']['thickness']).toBe(thickness); + if ((layer as any).mark) { + if ((layer as any).mark.type === 'tick') { + expect((layer as any).mark.thickness).toBe(thickness); } else { - expect(layer['mark']['size']).toBe(thickness); + expect((layer as any).mark.size).toBe(thickness); } } } @@ -473,11 +473,11 @@ describe('normalizeErrorBar with raw data input', () => { assertIsLayerSpec(outputSpec); for (const layer of outputSpec.layer) { - if (layer['mark']) { - if (layer['mark']['type'] === 'tick') { - expect(layer['mark']['thickness']).toBe(tickThickness); + if ((layer as any).mark) { + if ((layer as any).mark.type === 'tick') { + expect((layer as any).mark.thickness).toBe(tickThickness); } else { - expect(layer['mark']['size']).toBe(ruleSize); + expect((layer as any).mark.size).toBe(ruleSize); } } } @@ -690,8 +690,8 @@ describe('normalizeErrorBar with aggregated upper and lower bound input', () => const encoding = outputSpec.encoding; - expect(encoding.x['field']).toBe('lower_people'); - expect(encoding.x2['field']).toBe('upper_people'); + expect((encoding.x as any).field).toBe('lower_people'); + expect((encoding.x2 as any).field).toBe('upper_people'); }); it( @@ -860,8 +860,8 @@ describe('normalizeErrorBar with aggregated error input', () => { const encoding = outputSpec.encoding; - expect(encoding.x['field']).toBe('lower_people'); - expect(encoding.x2['field']).toBe('upper_people'); + expect((encoding.x as any).field).toBe('lower_people'); + expect((encoding.x2 as any).field).toBe('upper_people'); }); it('should produce correct layered specs for horizontal errorbar with 2 aggregated error input', () => { diff --git a/test/config.test.ts b/test/config.test.ts index 22ee16fe09..8b3eea48c7 100644 --- a/test/config.test.ts +++ b/test/config.test.ts @@ -203,9 +203,9 @@ describe('config', () => { expect(output[mark]).toBeUndefined(); } expect(output.style.bar['binSpacing']).toBeUndefined(); - expect(output.style.cell['width']).toBeUndefined(); - expect(output.style.cell['height']).toBeUndefined(); - expect(output.style.cell['fill']).toBe('#eee'); + expect((output.style.cell as any).width).toBeUndefined(); + expect((output.style.cell as any).height).toBeUndefined(); + expect((output.style.cell as any).fill).toBe('#eee'); expect(output.style.bar.opacity).toBe(0.5); }); diff --git a/test/normalize/core.test.ts b/test/normalize/core.test.ts index 419ef6da3f..75318298b7 100644 --- a/test/normalize/core.test.ts +++ b/test/normalize/core.test.ts @@ -33,7 +33,7 @@ describe('normalize()', () => { } }; const normalized = normalize(spec); - expect(normalized['columns']).toBe(4); + expect((normalized as any).columns).toBe(4); expect(localLogger.warns[0]).toEqual(log.message.columnsNotSupportByRowCol('repeat')); }) ); @@ -63,7 +63,7 @@ describe('normalize()', () => { } }; const normalized = normalize(spec); - expect(normalized['layer']).toHaveLength(2); + expect((normalized as any).layer).toHaveLength(2); }); }); @@ -170,7 +170,7 @@ describe('normalize()', () => { } }; const normalized = normalize(spec); - expect(normalized['columns']).toBeUndefined(); + expect((normalized as any).columns).toBeUndefined(); expect(localLogger.warns[0]).toEqual(log.message.columnsNotSupportByRowCol('facet')); }) ); From da3dc55b15d0ea8c1fdb4db240072f6e708d08db Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Tue, 30 Jul 2024 20:00:18 -0400 Subject: [PATCH 06/24] fixes --- test/compile/axis/properties.test.ts | 2 +- test/compile/legend/parse.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/compile/axis/properties.test.ts b/test/compile/axis/properties.test.ts index d77ff5a1e8..cac5fb97dc 100644 --- a/test/compile/axis/properties.test.ts +++ b/test/compile/axis/properties.test.ts @@ -278,7 +278,7 @@ describe('compile/axis/properties', () => { }); it('correctly align y-axis labels for labelAngle and orient signals', () => { - const ast = (parseExpression(defaultLabelAlign({signal: 'a'}, {signal: 'o'}, 'y') as any) as any).signal; + const ast = parseExpression((defaultLabelAlign({signal: 'a'}, {signal: 'o'}, 'y') as any).signal); let a: number; let o: AxisOrient; // test all angles diff --git a/test/compile/legend/parse.test.ts b/test/compile/legend/parse.test.ts index 86bdeed751..8ccc9cb7d3 100644 --- a/test/compile/legend/parse.test.ts +++ b/test/compile/legend/parse.test.ts @@ -17,7 +17,7 @@ describe('compile/legend', () => { }); const legendComponent = parseLegend(unitModel); - expect(legendComponent['color'].get('format')).toEqual({ + expect(legendComponent.color.get('format')).toEqual({ signal: 'timeUnitSpecifier(["month"], {"year-month":"%b %Y ","year-month-date":"%b %d, %Y "})' }); }); @@ -32,7 +32,7 @@ describe('compile/legend', () => { }); const legendComponent = parseLegend(unitModel); - expect(legendComponent['color'].get('format')).toEqual({ + expect(legendComponent.color.get('format')).toEqual({ signal: 'timeUnitSpecifier(["quarter"], {"year-month":"%b %Y ","year-month-date":"%b %d, %Y "})' }); }); From 2587eff34dc8495ca6543069995fab8be98bc8a3 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Tue, 30 Jul 2024 20:05:54 -0400 Subject: [PATCH 07/24] correct logic --- src/compile/legend/parse.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compile/legend/parse.ts b/src/compile/legend/parse.ts index f32728192f..507e319881 100644 --- a/src/compile/legend/parse.ts +++ b/src/compile/legend/parse.ts @@ -4,7 +4,7 @@ import {DatumDef, FieldDef, getFieldOrDatumDef, isFieldDef, MarkPropDatumDef, Ma import {LegendInternal, LEGEND_SCALE_CHANNELS} from '../../legend'; import {normalizeTimeUnit} from '../../timeunit'; import {GEOJSON} from '../../type'; -import {deleteNestedProperty, hasKey, isEmpty, keys, varName} from '../../util'; +import {deleteNestedProperty, isEmpty, keys, varName} from '../../util'; import {mergeTitleComponent} from '../common'; import {guideEncodeEntry} from '../guide'; import {isUnitModel, Model} from '../model'; @@ -135,7 +135,7 @@ export function parseLegendForChannel(model: UnitModel, channel: NonPositionScal const value = property in legendRules ? legendRules[property](ruleParams) : (legend as any)[property]; if (value !== undefined) { const explicit = isExplicit(value, property, legend, model.fieldDef(channel)); - if (explicit || hasKey(config.legend, property)) { + if (explicit || (config.legend as any)[property] === undefined) { legendCmpt.set(property, value, explicit); } } From 0ca90f1fc7ca940999b3235b1b9aa4ce3b391e0d Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Tue, 30 Jul 2024 20:17:44 -0400 Subject: [PATCH 08/24] test again --- src/compile/legend/parse.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compile/legend/parse.ts b/src/compile/legend/parse.ts index 507e319881..805ecc6d53 100644 --- a/src/compile/legend/parse.ts +++ b/src/compile/legend/parse.ts @@ -81,7 +81,7 @@ function isExplicit( } } // Otherwise, things are explicit if the returned value matches the specified property - return value === (legend ?? ({} as any))[property]; + return value === (legend || ({} as any))[property]; } export function parseLegendForChannel(model: UnitModel, channel: NonPositionScaleChannel): LegendComponent { From b3164970963e77233fa16b1e7ff959f7ca4cb4ac Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Tue, 30 Jul 2024 22:19:25 -0400 Subject: [PATCH 09/24] fix config --- src/config.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/config.ts b/src/config.ts index 0a914c0639..813c18e935 100644 --- a/src/config.ts +++ b/src/config.ts @@ -571,9 +571,11 @@ export function initConfig(specifiedConfig: Config = {}): Config { const outputConfig: Config = omit(mergedConfig, configPropsWithExpr); - outputConfig['background'] = signalRefOrValue(mergedConfig['background']); - outputConfig['lineBreak'] = signalRefOrValue(mergedConfig['lineBreak']); - outputConfig['padding'] = signalRefOrValue(mergedConfig['padding']); + for (const prop of ['background', 'lineBreak', 'padding'] as const) { + if (mergedConfig[prop]) { + (outputConfig as any)[prop] = signalRefOrValue(mergedConfig[prop]); + } + } for (const markConfigType of mark.MARK_CONFIGS) { if (mergedConfig[markConfigType]) { From 9952d6761ff3b0b05ed9f980f3dbbf3239025ae8 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Tue, 30 Jul 2024 22:44:33 -0400 Subject: [PATCH 10/24] mark vega imports as type only, use with instead of assert --- rollup.config.mjs | 2 +- src/aggregate.ts | 2 +- src/axis.ts | 2 +- src/compile/axis/parse.ts | 2 +- src/compile/common.ts | 2 +- src/compile/compile.ts | 2 +- src/compile/data/facet.ts | 2 +- src/compile/data/filter.ts | 2 +- src/compile/data/filterinvalid.ts | 2 +- src/compile/data/flatten.ts | 2 +- src/compile/data/fold.ts | 2 +- src/compile/mark/encode/base.ts | 2 +- src/config.ts | 4 ++-- src/encoding.ts | 2 +- src/guide.ts | 2 +- src/header.ts | 12 +++++++++++- src/impute.ts | 2 +- src/invalid.ts | 2 +- src/legend.ts | 2 +- src/mark.ts | 14 +++----------- src/normalize/selectioncompat.ts | 4 ++-- src/normalize/toplevelselection.ts | 2 +- src/parameter.ts | 2 +- src/projection.ts | 2 +- src/scale.ts | 7 +++---- src/selection.ts | 2 +- src/spec/facet.ts | 2 +- src/spec/toplevel.ts | 2 +- src/transform.ts | 2 +- src/util.ts | 3 +-- 30 files changed, 46 insertions(+), 46 deletions(-) diff --git a/rollup.config.mjs b/rollup.config.mjs index 2600ebf98a..f84622a4be 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -5,7 +5,7 @@ import resolve from '@rollup/plugin-node-resolve'; import terser from '@rollup/plugin-terser'; import bundleSize from 'rollup-plugin-bundle-size'; -import pkg from './package.json' assert {type: 'json'}; +import pkg from './package.json' with {type: 'json'}; export function disallowedImports() { return { diff --git a/src/aggregate.ts b/src/aggregate.ts index 66f381332d..f423bf5117 100644 --- a/src/aggregate.ts +++ b/src/aggregate.ts @@ -1,4 +1,4 @@ -import {AggregateOp} from 'vega'; +import type {AggregateOp} from 'vega'; import {hasOwnProperty, isString} from 'vega-util'; import {FieldName} from './channeldef'; import {contains, Flag, hasKey} from './util'; diff --git a/src/axis.ts b/src/axis.ts index 078ba2e482..d35b615cb6 100644 --- a/src/axis.ts +++ b/src/axis.ts @@ -1,4 +1,4 @@ -import { +import type { Align, Axis as VgAxis, AxisEncode, diff --git a/src/compile/axis/parse.ts b/src/compile/axis/parse.ts index 76c7cda0cf..1c27ce5a98 100644 --- a/src/compile/axis/parse.ts +++ b/src/compile/axis/parse.ts @@ -1,4 +1,4 @@ -import {AxisEncode as VgAxisEncode, AxisOrient, SignalRef} from 'vega'; +import type {AxisEncode as VgAxisEncode, AxisOrient, SignalRef} from 'vega'; import {Axis, AXIS_PARTS, isAxisProperty, isConditionalAxisValue} from '../../axis'; import {PositionScaleChannel, POSITION_SCALE_CHANNELS} from '../../channel'; import {getFieldOrDatumDef, PositionDatumDef, PositionFieldDef} from '../../channeldef'; diff --git a/src/compile/common.ts b/src/compile/common.ts index 629d880bb0..24e3e0f78f 100644 --- a/src/compile/common.ts +++ b/src/compile/common.ts @@ -1,4 +1,4 @@ -import {ExprRef, SignalRef, Text} from 'vega'; +import type {ExprRef, SignalRef, Text} from 'vega'; import {array, isArray, stringValue} from 'vega-util'; import {AxisConfig, ConditionalAxisProperty} from '../axis'; import { diff --git a/src/compile/compile.ts b/src/compile/compile.ts index 13e76dcaa8..52a6f81993 100644 --- a/src/compile/compile.ts +++ b/src/compile/compile.ts @@ -1,4 +1,4 @@ -import {AutoSizeType, LoggerInterface, Spec as VgSpec} from 'vega'; +import type {AutoSizeType, LoggerInterface, Spec as VgSpec} from 'vega'; import {isString, mergeConfig} from 'vega-util'; import {getPositionScaleChannel} from '../channel'; import * as vlFieldDef from '../channeldef'; diff --git a/src/compile/data/facet.ts b/src/compile/data/facet.ts index 95bd6e254f..c5fb84b55a 100644 --- a/src/compile/data/facet.ts +++ b/src/compile/data/facet.ts @@ -1,4 +1,4 @@ -import {AggregateOp} from 'vega'; +import type {AggregateOp} from 'vega'; import {isArray} from 'vega-util'; import {isBinning} from '../../bin'; import {COLUMN, FACET_CHANNELS, POSITION_SCALE_CHANNELS, ROW} from '../../channel'; diff --git a/src/compile/data/filter.ts b/src/compile/data/filter.ts index e5927981b5..5e9149e560 100644 --- a/src/compile/data/filter.ts +++ b/src/compile/data/filter.ts @@ -1,4 +1,4 @@ -import {FilterTransform as VgFilterTransform} from 'vega'; +import type {FilterTransform as VgFilterTransform} from 'vega'; import {LogicalComposition} from '../../logical'; import {Predicate} from '../../predicate'; import {duplicate} from '../../util'; diff --git a/src/compile/data/filterinvalid.ts b/src/compile/data/filterinvalid.ts index 2d20b9e3c0..6bf65370c8 100644 --- a/src/compile/data/filterinvalid.ts +++ b/src/compile/data/filterinvalid.ts @@ -1,4 +1,4 @@ -import {FilterTransform as VgFilterTransform} from 'vega'; +import type {FilterTransform as VgFilterTransform} from 'vega'; import {isScaleChannel} from '../../channel'; import {TypedFieldDef, vgField as fieldRef} from '../../channeldef'; import {Dict, hash, keys} from '../../util'; diff --git a/src/compile/data/flatten.ts b/src/compile/data/flatten.ts index dd25c35506..a00b12407a 100644 --- a/src/compile/data/flatten.ts +++ b/src/compile/data/flatten.ts @@ -1,4 +1,4 @@ -import {FlattenTransform as VgFlattenTransform} from 'vega'; +import type {FlattenTransform as VgFlattenTransform} from 'vega'; import {FlattenTransform} from '../../transform'; import {duplicate, hash} from '../../util'; import {DataFlowNode} from './dataflow'; diff --git a/src/compile/data/fold.ts b/src/compile/data/fold.ts index e35a592694..0276f3993c 100644 --- a/src/compile/data/fold.ts +++ b/src/compile/data/fold.ts @@ -1,4 +1,4 @@ -import {FoldTransform as VgFoldTransform} from 'vega'; +import type {FoldTransform as VgFoldTransform} from 'vega'; import {FoldTransform} from '../../transform'; import {duplicate, hash} from '../../util'; import {DataFlowNode} from './dataflow'; diff --git a/src/compile/mark/encode/base.ts b/src/compile/mark/encode/base.ts index cc2c21944c..37a961dc2f 100644 --- a/src/compile/mark/encode/base.ts +++ b/src/compile/mark/encode/base.ts @@ -1,4 +1,4 @@ -import {MarkConfig} from 'vega'; +import type {MarkConfig} from 'vega'; import {MarkDef} from '../../../mark'; import {hasKey} from '../../../util'; import {VG_MARK_CONFIGS, VgEncodeEntry, VgValueRef} from '../../../vega.schema'; diff --git a/src/config.ts b/src/config.ts index 813c18e935..8246dd3cf1 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,5 +1,5 @@ -import {Color, InitSignal, Locale, NewSignal, RangeConfig, RangeScheme, SignalRef, writeConfig} from 'vega'; -import {isObject, mergeConfig} from 'vega-util'; +import type {Color, InitSignal, Locale, NewSignal, RangeConfig, RangeScheme, SignalRef} from 'vega'; +import {isObject, mergeConfig, writeConfig} from 'vega-util'; import {Axis, AxisConfig, AxisConfigMixins, AXIS_CONFIGS, isConditionalAxisValue} from './axis'; import {signalOrValueRefWithCondition, signalRefOrValue} from './compile/common'; import {CompositeMarkConfigMixins, getAllCompositeMarks} from './compositemark'; diff --git a/src/encoding.ts b/src/encoding.ts index d37bab1d78..662309b561 100644 --- a/src/encoding.ts +++ b/src/encoding.ts @@ -1,4 +1,4 @@ -import {AggregateOp} from 'vega'; +import type {AggregateOp} from 'vega'; import {array, isArray} from 'vega-util'; import {isArgmaxDef, isArgminDef} from './aggregate'; import {isBinned, isBinning} from './bin'; diff --git a/src/guide.ts b/src/guide.ts index 38ebc85582..a1732579a0 100644 --- a/src/guide.ts +++ b/src/guide.ts @@ -1,4 +1,4 @@ -import {SignalRef, Text} from 'vega'; +import type {SignalRef, Text} from 'vega'; import {ConditionValueDefMixins, FormatMixins, ValueDef} from './channeldef'; import {LegendConfig} from './legend'; import {VgEncodeChannel} from './vega.schema'; diff --git a/src/header.ts b/src/header.ts index 80629287ee..5c12409548 100644 --- a/src/header.ts +++ b/src/header.ts @@ -1,4 +1,14 @@ -import {Align, Color, FontStyle, FontWeight, Orient, SignalRef, TextBaseline, TitleAnchor, TitleConfig} from 'vega'; +import type { + Align, + Color, + FontStyle, + FontWeight, + Orient, + SignalRef, + TextBaseline, + TitleAnchor, + TitleConfig +} from 'vega'; import {FormatMixins} from './channeldef'; import {ExprRef} from './expr'; import {Guide, VlOnlyGuideConfig} from './guide'; diff --git a/src/impute.ts b/src/impute.ts index 0417f31061..8e93ef740b 100644 --- a/src/impute.ts +++ b/src/impute.ts @@ -1,5 +1,5 @@ import {ImputeSequence} from './transform'; -import {ImputeMethod} from 'vega'; +import type {ImputeMethod} from 'vega'; export interface ImputeParams { /** diff --git a/src/invalid.ts b/src/invalid.ts index 006d400d11..89a5f2ae1d 100644 --- a/src/invalid.ts +++ b/src/invalid.ts @@ -1,4 +1,4 @@ -import {SignalRef} from 'vega'; +import type {SignalRef} from 'vega'; import {ScaleChannel} from './channel'; import {Mark, MarkDef} from './mark'; import {isObject} from 'vega-util'; diff --git a/src/legend.ts b/src/legend.ts index 421ef5ad38..a3c18a6efa 100644 --- a/src/legend.ts +++ b/src/legend.ts @@ -1,4 +1,4 @@ -import { +import type { BaseLegend, LabelOverlap, Legend as VgLegend, diff --git a/src/mark.ts b/src/mark.ts index 71fcec9208..2b1b25486a 100644 --- a/src/mark.ts +++ b/src/mark.ts @@ -1,18 +1,10 @@ -import { - Align, - Color, - Gradient, - MarkConfig as VgMarkConfig, - Orientation, - SignalRef, - TextBaseline, - hasOwnProperty -} from 'vega'; +import type {Align, Color, Gradient, Orientation, SignalRef, TextBaseline, MarkConfig as VgMarkConfig} from 'vega'; +import {hasOwnProperty} from 'vega-util'; import {CompositeMark, CompositeMarkDef} from './compositemark'; import {ExprRef} from './expr'; +import {MarkInvalidMixins} from './invalid'; import {Flag, hasKey, keys} from './util'; import {MapExcludeValueRefAndReplaceSignalWith} from './vega.schema'; -import {MarkInvalidMixins} from './invalid'; /** * All types of primitive marks. diff --git a/src/normalize/selectioncompat.ts b/src/normalize/selectioncompat.ts index 7d3402c525..7f72b8fd4b 100644 --- a/src/normalize/selectioncompat.ts +++ b/src/normalize/selectioncompat.ts @@ -1,13 +1,13 @@ -import {isArray} from 'vega'; +import {isArray} from 'vega-util'; import {BinParams, isBinParams} from '../bin'; import {ChannelDef, Field, isConditionalDef, isFieldDef, isScaleFieldDef} from '../channeldef'; +import {Encoding} from '../encoding'; import {LogicalComposition, normalizeLogicalComposition} from '../logical'; import {FacetedUnitSpec, GenericSpec, LayerSpec, RepeatSpec, UnitSpec} from '../spec'; import {SpecMapper} from '../spec/map'; import {isBin, isFilter, isLookup} from '../transform'; import {duplicate, entries, vals} from '../util'; import {NormalizerParams} from './base'; -import {Encoding} from '../encoding'; export class SelectionCompatibilityNormalizer extends SpecMapper< NormalizerParams, diff --git a/src/normalize/toplevelselection.ts b/src/normalize/toplevelselection.ts index 4e1cb263b6..ac30a4ef2a 100644 --- a/src/normalize/toplevelselection.ts +++ b/src/normalize/toplevelselection.ts @@ -1,4 +1,4 @@ -import {isArray, isString} from 'vega'; +import {isArray, isString} from 'vega-util'; import {Field} from '../channeldef'; import {VariableParameter} from '../parameter'; import {isSelectionParameter, SelectionParameter} from '../selection'; diff --git a/src/parameter.ts b/src/parameter.ts index 2b693f2da8..f96909d60f 100644 --- a/src/parameter.ts +++ b/src/parameter.ts @@ -1,4 +1,4 @@ -import {Binding, Expr, InitSignal, NewSignal} from 'vega'; +import type {Binding, Expr, InitSignal, NewSignal} from 'vega'; import {isSelectionParameter, TopLevelSelectionParameter} from './selection'; export type ParameterName = string; diff --git a/src/projection.ts b/src/projection.ts index 1eca2fed43..1b5c770d4d 100644 --- a/src/projection.ts +++ b/src/projection.ts @@ -1,4 +1,4 @@ -import {BaseProjection, SignalRef, Vector2} from 'vega'; +import type {BaseProjection, SignalRef, Vector2} from 'vega'; import {ExprRef} from './expr'; import {MapExcludeValueRefAndReplaceSignalWith, ProjectionType} from './vega.schema'; diff --git a/src/scale.ts b/src/scale.ts index f8cc6080cc..afa5e8b0fb 100644 --- a/src/scale.ts +++ b/src/scale.ts @@ -1,5 +1,5 @@ -import { - isObject, +import type { + ColorScheme, RangeEnum, ScaleBins, ScaleInterpolateEnum, @@ -8,8 +8,7 @@ import { TimeInterval, TimeIntervalStep } from 'vega'; -import type {ColorScheme} from 'vega-typings'; -import {isString} from 'vega-util'; +import {isString, isObject} from 'vega-util'; import * as CHANNEL from './channel'; import {Channel, isColorChannel} from './channel'; import {DateTime} from './datetime'; diff --git a/src/selection.ts b/src/selection.ts index 3c8c7a0789..bf931b0055 100644 --- a/src/selection.ts +++ b/src/selection.ts @@ -1,4 +1,4 @@ -import {Binding, Color, Cursor, Stream, Vector2} from 'vega'; +import type {Binding, Color, Cursor, Stream, Vector2} from 'vega'; import {isObject} from 'vega-util'; import {SingleDefUnitChannel} from './channel'; import {FieldName, PrimitiveValue} from './channeldef'; diff --git a/src/spec/facet.ts b/src/spec/facet.ts index f8fb8aaf35..aa99ec5e18 100644 --- a/src/spec/facet.ts +++ b/src/spec/facet.ts @@ -1,4 +1,4 @@ -import {LayoutAlign, SignalRef} from 'vega'; +import type {LayoutAlign, SignalRef} from 'vega'; import {BinParams} from '../bin'; import {ChannelDef, Field, FieldName, TypedFieldDef} from '../channeldef'; import {ExprRef} from '../expr'; diff --git a/src/spec/toplevel.ts b/src/spec/toplevel.ts index 0c8e3da39b..743347aa20 100644 --- a/src/spec/toplevel.ts +++ b/src/spec/toplevel.ts @@ -1,4 +1,4 @@ -import {Color, SignalRef} from 'vega'; +import type {Color, SignalRef} from 'vega'; import {BaseSpec} from '.'; import {getPositionScaleChannel} from '../channel'; import {signalRefOrValue} from '../compile/common'; diff --git a/src/transform.ts b/src/transform.ts index 17d7ffd010..7589548daf 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -1,4 +1,4 @@ -import {AggregateOp} from 'vega'; +import type {AggregateOp} from 'vega'; import {BinParams} from './bin'; import {FieldName} from './channeldef'; import {Data} from './data'; diff --git a/src/util.ts b/src/util.ts index 2506c21622..650eb12595 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,6 +1,5 @@ -import {hasOwnProperty, isNumber, isString, splitAccessPath, stringValue, writeConfig} from 'vega-util'; +import {hasOwnProperty, isNumber, isString, splitAccessPath, stringValue, writeConfig, isObject} from 'vega-util'; import {isLogicalAnd, isLogicalNot, isLogicalOr, LogicalComposition} from './logical'; -import {isObject} from 'vega'; export const duplicate = structuredClone; From a625be68b2bc516f1ef3c5f3dfd8f01a5aacd797 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Tue, 30 Jul 2024 23:07:09 -0400 Subject: [PATCH 11/24] simplify --- src/channeldef.ts | 2 +- src/compile/data/formatparse.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/channeldef.ts b/src/channeldef.ts index bb894b134b..b826afa724 100644 --- a/src/channeldef.ts +++ b/src/channeldef.ts @@ -220,7 +220,7 @@ export type FieldName = string; export type Field = FieldName | RepeatRef; export function isRepeatRef(field: Field | any): field is RepeatRef { - return field && !isString(field) && hasKey(field, 'repeat'); + return !isString(field) && hasKey(field, 'repeat'); } /** @@hidden */ diff --git a/src/compile/data/formatparse.ts b/src/compile/data/formatparse.ts index 1d1a297030..e7de918fc1 100644 --- a/src/compile/data/formatparse.ts +++ b/src/compile/data/formatparse.ts @@ -95,7 +95,7 @@ export function getImplicitFromFilterTransform(transform: FilterTransform) { // FIXME: remove as any val = (filter as any).range[0]; } else if (isFieldOneOfPredicate(filter)) { - val = (filter.oneOf ?? (filter as any)['in'])[0]; + val = (filter.oneOf ?? (filter as any).in)[0]; } // else -- for filter expression, we can't infer anything if (val) { From 4f7753a9dad09ab4ef52b7eeaa8a74a451d802ea Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Tue, 30 Jul 2024 23:22:23 -0400 Subject: [PATCH 12/24] simplify --- src/config.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/config.ts b/src/config.ts index 8246dd3cf1..1697d987e9 100644 --- a/src/config.ts +++ b/src/config.ts @@ -734,10 +734,9 @@ export function stripAndRedirectConfig(config: Config) { redirectTitleConfig(config); // Remove empty config objects. - for (const prop in config) { - const p = prop as keyof typeof config; - if (isObject(config[p]) && isEmpty(config[p])) { - delete config[p]; + for (const prop of keys(config)) { + if (isObject(config[prop]) && isEmpty(config[prop])) { + delete config[prop]; } } From 094a813421cc1c5490719d797694c247bb968558 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Tue, 30 Jul 2024 23:33:23 -0400 Subject: [PATCH 13/24] simplify --- src/aggregate.ts | 4 ++-- src/compile/scale/parse.ts | 2 +- src/config.ts | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/aggregate.ts b/src/aggregate.ts index f423bf5117..caedc16ff3 100644 --- a/src/aggregate.ts +++ b/src/aggregate.ts @@ -50,11 +50,11 @@ export type NonArgAggregateOp = Exclude; export type Aggregate = NonArgAggregateOp | ArgmaxDef | ArgminDef; export function isArgminDef(a: Aggregate | string): a is ArgminDef { - return !!a && hasKey(a, 'argmin'); + return hasKey(a, 'argmin'); } export function isArgmaxDef(a: Aggregate | string): a is ArgmaxDef { - return !!a && hasKey(a, 'argmax'); + return hasKey(a, 'argmax'); } export function isAggregateOp(a: string | ArgminDef | ArgmaxDef): a is AggregateOp { diff --git a/src/compile/scale/parse.ts b/src/compile/scale/parse.ts index b373d88fdf..13cb318f2d 100644 --- a/src/compile/scale/parse.ts +++ b/src/compile/scale/parse.ts @@ -54,7 +54,7 @@ function parseUnitScaleCore(model: UnitModel): ScaleComponentIndex { continue; } - let specifiedScale = (fieldOrDatumDef as any)?.scale; + let specifiedScale = fieldOrDatumDef && (fieldOrDatumDef as any).scale; if (fieldOrDatumDef && specifiedScale !== null && specifiedScale !== false) { specifiedScale ??= {}; const hasNestedOffsetScale = channelHasNestedOffsetScale(encoding, channel); diff --git a/src/config.ts b/src/config.ts index 1697d987e9..79dc4f3d99 100644 --- a/src/config.ts +++ b/src/config.ts @@ -734,8 +734,10 @@ export function stripAndRedirectConfig(config: Config) { redirectTitleConfig(config); // Remove empty config objects. - for (const prop of keys(config)) { + for (const prop in config) { + // @ts-ignore if (isObject(config[prop]) && isEmpty(config[prop])) { + // @ts-ignore delete config[prop]; } } From 625037084be744b11e2448e7452c7022957f8016 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Tue, 30 Jul 2024 23:40:36 -0400 Subject: [PATCH 14/24] don't update vega yet --- package.json | 2 +- yarn.lock | 1535 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 1034 insertions(+), 503 deletions(-) diff --git a/package.json b/package.json index 734fa2fc16..f4515d80b3 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "ts-jest": "^29.2.3", "ts-json-schema-generator": "^2.3.0", "typescript": "~5.5.4", - "vega-cli": "^5.30.0", + "vega-cli": "^5.28.0", "vega-datasets": "^2.8.1", "vega-embed": "^6.26.0", "vega-tooltip": "^0.34.0", diff --git a/yarn.lock b/yarn.lock index b39a6fcdec..faa1080b32 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + "@ampproject/remapping@^2.2.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" @@ -10,7 +15,15 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.21.4", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.24.7": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.21.4", "@babel/code-frame@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.6.tgz#ab88da19344445c3d8889af2216606d3329f3ef2" + integrity sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA== + dependencies: + "@babel/highlight" "^7.24.6" + picocolors "^1.0.0" + +"@babel/code-frame@^7.22.13", "@babel/code-frame@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== @@ -18,33 +31,84 @@ "@babel/highlight" "^7.24.7" picocolors "^1.0.0" -"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.24.8", "@babel/compat-data@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.0.tgz#6b226a5da3a686db3c30519750e071dce292ad95" - integrity sha512-P4fwKI2mjEb3ZU5cnMJzvRsRKGBUcs8jvxIoRmr6ufAY9Xk2Bz7JubRTTivkw55c7WQJfTECeqYVa+HZ0FzREg== +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.6.tgz#b3600217688cabb26e25f8e467019e66d71b7ae2" + integrity sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ== + +"@babel/compat-data@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.7.tgz#d23bbea508c3883ba8251fb4164982c36ea577ed" + integrity sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw== + +"@babel/compat-data@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.2.tgz#e41928bd33475305c586f6acbbb7e3ade7a6f7f5" + integrity sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.6.tgz#8650e0e4b03589ebe886c4e4a60398db0a7ec787" + integrity sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.6" + "@babel/generator" "^7.24.6" + "@babel/helper-compilation-targets" "^7.24.6" + "@babel/helper-module-transforms" "^7.24.6" + "@babel/helpers" "^7.24.6" + "@babel/parser" "^7.24.6" + "@babel/template" "^7.24.6" + "@babel/traverse" "^7.24.6" + "@babel/types" "^7.24.6" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.24.9": - version "7.24.9" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.9.tgz#dc07c9d307162c97fa9484ea997ade65841c7c82" - integrity sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg== +"@babel/core@^7.24.9": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.2.tgz#ed8eec275118d7613e77a352894cd12ded8eba77" + integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.24.9" - "@babel/helper-compilation-targets" "^7.24.8" - "@babel/helper-module-transforms" "^7.24.9" - "@babel/helpers" "^7.24.8" - "@babel/parser" "^7.24.8" - "@babel/template" "^7.24.7" - "@babel/traverse" "^7.24.8" - "@babel/types" "^7.24.9" + "@babel/generator" "^7.25.0" + "@babel/helper-compilation-targets" "^7.25.2" + "@babel/helper-module-transforms" "^7.25.2" + "@babel/helpers" "^7.25.0" + "@babel/parser" "^7.25.0" + "@babel/template" "^7.25.0" + "@babel/traverse" "^7.25.2" + "@babel/types" "^7.25.2" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.24.9", "@babel/generator@^7.25.0", "@babel/generator@^7.7.2": +"@babel/generator@^7.24.6", "@babel/generator@^7.7.2": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.6.tgz#dfac82a228582a9d30c959fe50ad28951d4737a7" + integrity sha512-S7m4eNa6YAPJRHmKsLHIDJhNAGNKoWNiWefz1MBbpnt8g9lvMDl1hir4P9bo/57bQEmuwEhnRU/AMWsD0G/Fbg== + dependencies: + "@babel/types" "^7.24.6" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + +"@babel/generator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.7.tgz#1654d01de20ad66b4b4d99c135471bc654c55e6d" + integrity sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA== + dependencies: + "@babel/types" "^7.24.7" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + +"@babel/generator@^7.25.0": version "7.25.0" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.0.tgz#f858ddfa984350bc3d3b7f125073c9af6988f18e" integrity sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw== @@ -54,6 +118,13 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" +"@babel/helper-annotate-as-pure@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" + integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-annotate-as-pure@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz#5373c7bc8366b12a033b4be1ac13a206c6656aab" @@ -69,43 +140,85 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" -"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.24.7", "@babel/helper-compilation-targets@^7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz#b607c3161cd9d1744977d4f97139572fe778c271" - integrity sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw== +"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.6.tgz#4a51d681f7680043d38e212715e2a7b1ad29cb51" + integrity sha512-VZQ57UsDGlX/5fFA7GkVPplZhHsVc+vuErWgdOiysI9Ksnw0Pbbd6pnPiR/mmJyKHgyIW0c7KT32gmhiF+cirg== + dependencies: + "@babel/compat-data" "^7.24.6" + "@babel/helper-validator-option" "^7.24.6" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-compilation-targets@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz#4eb6c4a80d6ffeac25ab8cd9a21b5dfa48d503a9" + integrity sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg== + dependencies: + "@babel/compat-data" "^7.24.7" + "@babel/helper-validator-option" "^7.24.7" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-compilation-targets@^7.24.8", "@babel/helper-compilation-targets@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz#e1d9410a90974a3a5a66e84ff55ef62e3c02d06c" + integrity sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw== dependencies: - "@babel/compat-data" "^7.24.8" + "@babel/compat-data" "^7.25.2" "@babel/helper-validator-option" "^7.24.8" browserslist "^4.23.1" lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.24.7", "@babel/helper-create-class-features-plugin@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.0.tgz#a109bf9c3d58dfed83aaf42e85633c89f43a6253" - integrity sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ== +"@babel/helper-create-class-features-plugin@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.7.tgz#2eaed36b3a1c11c53bdf80d53838b293c52f5b3b" + integrity sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg== dependencies: "@babel/helper-annotate-as-pure" "^7.24.7" - "@babel/helper-member-expression-to-functions" "^7.24.8" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-function-name" "^7.24.7" + "@babel/helper-member-expression-to-functions" "^7.24.7" "@babel/helper-optimise-call-expression" "^7.24.7" - "@babel/helper-replace-supers" "^7.25.0" + "@babel/helper-replace-supers" "^7.24.7" "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" - "@babel/traverse" "^7.25.0" + "@babel/helper-split-export-declaration" "^7.24.7" semver "^6.3.1" -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.24.7", "@babel/helper-create-regexp-features-plugin@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.0.tgz#17afe5d23b3a833a90f0fab9c2ae69fea192de5c" - integrity sha512-q0T+dknZS+L5LDazIP+02gEZITG5unzvb6yIjcmj5i0eFrs5ToBV2m2JGH4EsE/gtP8ygEGLGApBgRIZkTm7zg== +"@babel/helper-create-regexp-features-plugin@^7.18.6": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1" + integrity sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + regexpu-core "^5.3.1" + semver "^6.3.1" + +"@babel/helper-create-regexp-features-plugin@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz#be4f435a80dc2b053c76eeb4b7d16dd22cfc89da" + integrity sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA== dependencies: "@babel/helper-annotate-as-pure" "^7.24.7" regexpu-core "^5.3.1" semver "^6.3.1" -"@babel/helper-define-polyfill-provider@^0.6.1", "@babel/helper-define-polyfill-provider@^0.6.2": - version "0.6.2" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz#18594f789c3594acb24cfdb4a7f7b7d2e8bd912d" - integrity sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ== +"@babel/helper-create-regexp-features-plugin@^7.25.0": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz#24c75974ed74183797ffd5f134169316cd1808d9" + integrity sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + regexpu-core "^5.3.1" + semver "^6.3.1" + +"@babel/helper-define-polyfill-provider@^0.6.1": + version "0.6.1" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz#fadc63f0c2ff3c8d02ed905dcea747c5b0fb74fd" + integrity sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA== dependencies: "@babel/helper-compilation-targets" "^7.22.6" "@babel/helper-plugin-utils" "^7.22.5" @@ -113,6 +226,56 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" +"@babel/helper-environment-visitor@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz#ac7ad5517821641550f6698dd5468f8cef78620d" + integrity sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g== + +"@babel/helper-environment-visitor@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz#4b31ba9551d1f90781ba83491dd59cf9b269f7d9" + integrity sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ== + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-function-name@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.24.6.tgz#cebdd063386fdb95d511d84b117e51fc68fec0c8" + integrity sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w== + dependencies: + "@babel/template" "^7.24.6" + "@babel/types" "^7.24.6" + +"@babel/helper-function-name@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz#75f1e1725742f39ac6584ee0b16d94513da38dd2" + integrity sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA== + dependencies: + "@babel/template" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-hoist-variables@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.6.tgz#8a7ece8c26756826b6ffcdd0e3cf65de275af7f9" + integrity sha512-SF/EMrC3OD7dSta1bLJIlrsVxwtd0UpjRJqLno6125epQMJ/kyFmpTT4pbvPbdQHzCHg+biQ7Syo8lnDtbR+uA== + dependencies: + "@babel/types" "^7.24.6" + +"@babel/helper-hoist-variables@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz#b4ede1cde2fd89436397f30dc9376ee06b0f25ee" + integrity sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ== + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-member-expression-to-functions@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.7.tgz#67613d068615a70e4ed5101099affc7a41c5225f" + integrity sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + "@babel/helper-member-expression-to-functions@^7.24.8": version "7.24.8" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz#6155e079c913357d24a4c20480db7c712a5c3fb6" @@ -121,7 +284,21 @@ "@babel/traverse" "^7.24.8" "@babel/types" "^7.24.8" -"@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.24.7": +"@babel/helper-module-imports@^7.18.6": + version "7.24.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz#6ac476e6d168c7c23ff3ba3cf4f7841d46ac8128" + integrity sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg== + dependencies: + "@babel/types" "^7.24.0" + +"@babel/helper-module-imports@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz#65e54ffceed6a268dc4ce11f0433b82cfff57852" + integrity sha512-a26dmxFJBF62rRO9mmpgrfTLsAuyHk4e1hKTUkD/fcMfynt8gvEKwQPQDVxWhca8dHoDck+55DFt42zV0QMw5g== + dependencies: + "@babel/types" "^7.24.6" + +"@babel/helper-module-imports@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz#f2f980392de5b84c3328fc71d38bd81bbb83042b" integrity sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA== @@ -129,15 +306,37 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" -"@babel/helper-module-transforms@^7.24.7", "@babel/helper-module-transforms@^7.24.8", "@babel/helper-module-transforms@^7.24.9", "@babel/helper-module-transforms@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.0.tgz#3ffc23c473a2769a7e40d3274495bd559fdd2ecc" - integrity sha512-bIkOa2ZJYn7FHnepzr5iX9Kmz8FjIz4UKzJ9zhX3dnYuVW0xul9RuR3skBfoLu+FPTQw90EHW9rJsSZhyLQ3fQ== +"@babel/helper-module-transforms@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.6.tgz#22346ed9df44ce84dee850d7433c5b73fab1fe4e" + integrity sha512-Y/YMPm83mV2HJTbX1Qh2sjgjqcacvOlhbzdCCsSlblOKjSYmQqEbO6rUniWQyRo9ncyfjT8hnUjlG06RXDEmcA== dependencies: + "@babel/helper-environment-visitor" "^7.24.6" + "@babel/helper-module-imports" "^7.24.6" + "@babel/helper-simple-access" "^7.24.6" + "@babel/helper-split-export-declaration" "^7.24.6" + "@babel/helper-validator-identifier" "^7.24.6" + +"@babel/helper-module-transforms@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz#31b6c9a2930679498db65b685b1698bfd6c7daf8" + integrity sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ== + dependencies: + "@babel/helper-environment-visitor" "^7.24.7" "@babel/helper-module-imports" "^7.24.7" "@babel/helper-simple-access" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" "@babel/helper-validator-identifier" "^7.24.7" - "@babel/traverse" "^7.25.0" + +"@babel/helper-module-transforms@^7.24.8", "@babel/helper-module-transforms@^7.25.0", "@babel/helper-module-transforms@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz#ee713c29768100f2776edf04d4eb23b8d27a66e6" + integrity sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ== + dependencies: + "@babel/helper-module-imports" "^7.24.7" + "@babel/helper-simple-access" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" + "@babel/traverse" "^7.25.2" "@babel/helper-optimise-call-expression@^7.24.7": version "7.24.7" @@ -146,12 +345,31 @@ dependencies: "@babel/types" "^7.24.7" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.24.8", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.6.tgz#fa02a32410a15a6e8f8185bcbf608f10528d2a24" + integrity sha512-MZG/JcWfxybKwsA9N9PmtF2lOSFSEMVCpIRrbxccZFLJPrJciJdG/UhSh5W96GEteJI2ARqm5UAHxISwRDLSNg== + +"@babel/helper-plugin-utils@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz#98c84fe6fe3d0d3ae7bfc3a5e166a46844feb2a0" + integrity sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg== + +"@babel/helper-plugin-utils@^7.24.8": version "7.24.8" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878" integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg== -"@babel/helper-remap-async-to-generator@^7.24.7", "@babel/helper-remap-async-to-generator@^7.25.0": +"@babel/helper-remap-async-to-generator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.7.tgz#b3f0f203628522713849d49403f1a414468be4c7" + integrity sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-wrap-function" "^7.24.7" + +"@babel/helper-remap-async-to-generator@^7.25.0": version "7.25.0" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz#d2f0fbba059a42d68e5e378feaf181ef6055365e" integrity sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw== @@ -160,7 +378,16 @@ "@babel/helper-wrap-function" "^7.25.0" "@babel/traverse" "^7.25.0" -"@babel/helper-replace-supers@^7.24.7", "@babel/helper-replace-supers@^7.25.0": +"@babel/helper-replace-supers@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz#f933b7eed81a1c0265740edc91491ce51250f765" + integrity sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg== + dependencies: + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-member-expression-to-functions" "^7.24.7" + "@babel/helper-optimise-call-expression" "^7.24.7" + +"@babel/helper-replace-supers@^7.25.0": version "7.25.0" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz#ff44deac1c9f619523fe2ca1fd650773792000a9" integrity sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg== @@ -169,6 +396,13 @@ "@babel/helper-optimise-call-expression" "^7.24.7" "@babel/traverse" "^7.25.0" +"@babel/helper-simple-access@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.6.tgz#1d6e04d468bba4fc963b4906f6dac6286cfedff1" + integrity sha512-nZzcMMD4ZhmB35MOOzQuiGO5RzL6tJbsT37Zx8M5L/i9KSrukGXWTjLe1knIbb/RmxoJE9GON9soq0c0VEMM5g== + dependencies: + "@babel/types" "^7.24.6" + "@babel/helper-simple-access@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz#bcade8da3aec8ed16b9c4953b74e506b51b5edb3" @@ -185,21 +419,70 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" +"@babel/helper-split-export-declaration@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz#e830068f7ba8861c53b7421c284da30ae656d7a3" + integrity sha512-CvLSkwXGWnYlF9+J3iZUvwgAxKiYzK3BWuo+mLzD/MDGOZDj7Gq8+hqaOkMxmJwmlv0iu86uH5fdADd9Hxkymw== + dependencies: + "@babel/types" "^7.24.6" + +"@babel/helper-split-export-declaration@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz#83949436890e07fa3d6873c61a96e3bbf692d856" + integrity sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA== + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-string-parser@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.6.tgz#28583c28b15f2a3339cfafafeaad42f9a0e828df" + integrity sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q== + +"@babel/helper-string-parser@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz#4d2d0f14820ede3b9807ea5fc36dfc8cd7da07f2" + integrity sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg== + "@babel/helper-string-parser@^7.24.8": version "7.24.8" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== +"@babel/helper-validator-identifier@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz#08bb6612b11bdec78f3feed3db196da682454a5e" + integrity sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw== + "@babel/helper-validator-identifier@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== -"@babel/helper-validator-option@^7.24.7", "@babel/helper-validator-option@^7.24.8": +"@babel/helper-validator-option@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.6.tgz#59d8e81c40b7d9109ab7e74457393442177f460a" + integrity sha512-Jktc8KkF3zIkePb48QO+IapbXlSapOW9S+ogZZkcO6bABgYAxtZcjZ/O005111YLf+j4M84uEgwYoidDkXbCkQ== + +"@babel/helper-validator-option@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz#24c3bb77c7a425d1742eec8fb433b5a1b38e62f6" + integrity sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw== + +"@babel/helper-validator-option@^7.24.8": version "7.24.8" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d" integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q== +"@babel/helper-wrap-function@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.24.7.tgz#52d893af7e42edca7c6d2c6764549826336aae1f" + integrity sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw== + dependencies: + "@babel/helper-function-name" "^7.24.7" + "@babel/template" "^7.24.7" + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + "@babel/helper-wrap-function@^7.25.0": version "7.25.0" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz#dab12f0f593d6ca48c0062c28bcfb14ebe812f81" @@ -209,7 +492,15 @@ "@babel/traverse" "^7.25.0" "@babel/types" "^7.25.0" -"@babel/helpers@^7.24.8": +"@babel/helpers@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.6.tgz#cd124245299e494bd4e00edda0e4ea3545c2c176" + integrity sha512-V2PI+NqnyFu1i0GyTd/O/cTpxzQCYioSkUIRmgo7gFEHKKCg5w46+r/A6WeUR1+P3TeQ49dspGPNd/E3n9AnnA== + dependencies: + "@babel/template" "^7.24.6" + "@babel/types" "^7.24.6" + +"@babel/helpers@^7.25.0": version "7.25.0" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.0.tgz#e69beb7841cb93a6505531ede34f34e6a073650a" integrity sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw== @@ -217,6 +508,16 @@ "@babel/template" "^7.25.0" "@babel/types" "^7.25.0" +"@babel/highlight@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.6.tgz#6d610c1ebd2c6e061cade0153bf69b0590b7b3df" + integrity sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ== + dependencies: + "@babel/helper-validator-identifier" "^7.24.6" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + "@babel/highlight@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" @@ -227,7 +528,17 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.24.8", "@babel/parser@^7.25.0": +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.6.tgz#5e030f440c3c6c78d195528c3b688b101a365328" + integrity sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q== + +"@babel/parser@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.7.tgz#9a5226f92f0c5c8ead550b750f5608e766c8ce85" + integrity sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw== + +"@babel/parser@^7.25.0": version "7.25.0" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.0.tgz#9fdc9237504d797b6e7b8f66e78ea7f570d256ad" integrity sha512-CzdIU9jdP0dg7HdyB+bHvDJGagUv+qtzZt5rYCWwW6tITNqV9odjp6Qu41gkG0ca5UfdDUWrKkiAnHHdGRnOrA== @@ -346,13 +657,20 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.24.7", "@babel/plugin-syntax-jsx@^7.7.2": +"@babel/plugin-syntax-jsx@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz#39a1fa4a7e3d3d7f34e2acc6be585b718d30e02d" integrity sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ== dependencies: "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz#3f6ca04b8c841811dbc3c5c5f837934e0d626c10" + integrity sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" @@ -409,13 +727,20 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.24.7", "@babel/plugin-syntax-typescript@^7.7.2": +"@babel/plugin-syntax-typescript@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz#58d458271b4d3b6bb27ee6ac9525acbb259bad1c" integrity sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA== dependencies: "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz#b3bcc51f396d15f3591683f90239de143c076844" + integrity sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-unicode-sets-regex@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" @@ -563,14 +888,14 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" -"@babel/plugin-transform-function-name@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.0.tgz#d17890029ceefb45189ea203b404a496263a8412" - integrity sha512-CQmfSnK14eYu82fu6GlCwRciHB7mp7oLN+DeyGDDwUr9cMwuSVviJKPXw/YcRYZdB1TdlLJWHHwXwnwD1WnCmQ== +"@babel/plugin-transform-function-name@^7.25.1": + version "7.25.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz#b85e773097526c1a4fc4ba27322748643f26fc37" + integrity sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA== dependencies: "@babel/helper-compilation-targets" "^7.24.8" "@babel/helper-plugin-utils" "^7.24.8" - "@babel/traverse" "^7.25.0" + "@babel/traverse" "^7.25.1" "@babel/plugin-transform-json-strings@^7.24.7": version "7.24.7" @@ -580,12 +905,12 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-json-strings" "^7.8.3" -"@babel/plugin-transform-literals@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.7.tgz#36b505c1e655151a9d7607799a9988fc5467d06c" - integrity sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ== +"@babel/plugin-transform-literals@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz#deb1ad14fc5490b9a65ed830e025bca849d8b5f3" + integrity sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" "@babel/plugin-transform-logical-assignment-operators@^7.24.7": version "7.24.7" @@ -610,7 +935,16 @@ "@babel/helper-module-transforms" "^7.24.7" "@babel/helper-plugin-utils" "^7.24.7" -"@babel/plugin-transform-modules-commonjs@^7.24.7", "@babel/plugin-transform-modules-commonjs@^7.24.8": +"@babel/plugin-transform-modules-commonjs@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.7.tgz#9fd5f7fdadee9085886b183f1ad13d1ab260f4ab" + integrity sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ== + dependencies: + "@babel/helper-module-transforms" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-simple-access" "^7.24.7" + +"@babel/plugin-transform-modules-commonjs@^7.24.8": version "7.24.8" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz#ab6421e564b717cb475d6fff70ae7f103536ea3c" integrity sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA== @@ -694,7 +1028,16 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-transform-optional-chaining@^7.24.7", "@babel/plugin-transform-optional-chaining@^7.24.8": +"@babel/plugin-transform-optional-chaining@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.7.tgz#b8f6848a80cf2da98a8a204429bec04756c6d454" + integrity sha512-tK+0N9yd4j+x/4hxF3F0e0fu/VdcxU18y5SevtyM/PCFlQvXbR0Zmlo2eBrKtVipGNFzpq56o8WsIIKcJFUCRQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-transform-optional-chaining@^7.24.8": version "7.24.8" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz#bb02a67b60ff0406085c13d104c99a835cdf365d" integrity sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw== @@ -787,14 +1130,13 @@ "@babel/helper-plugin-utils" "^7.24.8" "@babel/plugin-transform-typescript@^7.24.7": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.0.tgz#56f47fb87b86a97caa9c7770920a1967d40ac86e" - integrity sha512-LZicxFzHIw+Sa3pzgMgSz6gdpsdkfiMObHUzhSIrwKF0+/rP/nuR49u79pSS+zIFJ1FeGeqQD2Dq4QGFbOVvSw== + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.7.tgz#b006b3e0094bf0813d505e0c5485679eeaf4a881" + integrity sha512-iLD3UNkgx2n/HrjBesVbYX6j0yqn/sJktvbtKKgcaLIQ4bTTQ8obAypc1VpyHPD2y4Phh9zHOaAt8e/L14wCpw== dependencies: "@babel/helper-annotate-as-pure" "^7.24.7" - "@babel/helper-create-class-features-plugin" "^7.25.0" - "@babel/helper-plugin-utils" "^7.24.8" - "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/helper-create-class-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-typescript" "^7.24.7" "@babel/plugin-transform-unicode-escapes@^7.24.7": @@ -829,12 +1171,12 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/preset-env@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.25.0.tgz#3fe92e470311e91478129efda101816c680f0479" - integrity sha512-vYAA8PrCOeZfG4D87hmw1KJ1BPubghXP1e2MacRFwECGNKL76dkA38JEwYllbvQCpf/kLxsTtir0b8MtxKoVCw== + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.25.2.tgz#15918e9d050c4713a2ab8fa2fa82514eaf16676e" + integrity sha512-Y2Vkwy3ITW4id9c6KXshVV/x5yCGK7VdJmKkzOzNsDZMojRKfSA/033rRbLqlRozmhRXCejxWHLSJOg/wUHfzw== dependencies: - "@babel/compat-data" "^7.25.0" - "@babel/helper-compilation-targets" "^7.24.8" + "@babel/compat-data" "^7.25.2" + "@babel/helper-compilation-targets" "^7.25.2" "@babel/helper-plugin-utils" "^7.24.8" "@babel/helper-validator-option" "^7.24.8" "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.25.0" @@ -878,9 +1220,9 @@ "@babel/plugin-transform-exponentiation-operator" "^7.24.7" "@babel/plugin-transform-export-namespace-from" "^7.24.7" "@babel/plugin-transform-for-of" "^7.24.7" - "@babel/plugin-transform-function-name" "^7.25.0" + "@babel/plugin-transform-function-name" "^7.25.1" "@babel/plugin-transform-json-strings" "^7.24.7" - "@babel/plugin-transform-literals" "^7.24.7" + "@babel/plugin-transform-literals" "^7.25.2" "@babel/plugin-transform-logical-assignment-operators" "^7.24.7" "@babel/plugin-transform-member-expression-literals" "^7.24.7" "@babel/plugin-transform-modules-amd" "^7.24.7" @@ -943,13 +1285,31 @@ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime@^7.8.4": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb" - integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw== + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd" + integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA== dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.24.7", "@babel/template@^7.25.0", "@babel/template@^7.3.3": +"@babel/template@^7.24.6", "@babel/template@^7.3.3": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.6.tgz#048c347b2787a6072b24c723664c8d02b67a44f9" + integrity sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw== + dependencies: + "@babel/code-frame" "^7.24.6" + "@babel/parser" "^7.24.6" + "@babel/types" "^7.24.6" + +"@babel/template@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.7.tgz#02efcee317d0609d2c07117cb70ef8fb17ab7315" + integrity sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/parser" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/template@^7.25.0": version "7.25.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a" integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q== @@ -958,23 +1318,73 @@ "@babel/parser" "^7.25.0" "@babel/types" "^7.25.0" -"@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8", "@babel/traverse@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.0.tgz#e8533c0a57ed97921d1e5b20dd3db63d3efaebf5" - integrity sha512-ubALThHQy4GCf6mbb+5ZRNmLLCI7bJ3f8Q6LHBSRlSKSWj5a7dSUzJBLv3VuIhFrFPgjF4IzPF567YG/HSCdZA== +"@babel/traverse@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.6.tgz#0941ec50cdeaeacad0911eb67ae227a4f8424edc" + integrity sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw== + dependencies: + "@babel/code-frame" "^7.24.6" + "@babel/generator" "^7.24.6" + "@babel/helper-environment-visitor" "^7.24.6" + "@babel/helper-function-name" "^7.24.6" + "@babel/helper-hoist-variables" "^7.24.6" + "@babel/helper-split-export-declaration" "^7.24.6" + "@babel/parser" "^7.24.6" + "@babel/types" "^7.24.6" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/traverse@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.7.tgz#de2b900163fa741721ba382163fe46a936c40cf5" + integrity sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.24.7" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-function-name" "^7.24.7" + "@babel/helper-hoist-variables" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" + "@babel/parser" "^7.24.7" + "@babel/types" "^7.24.7" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/traverse@^7.24.8", "@babel/traverse@^7.25.0", "@babel/traverse@^7.25.1", "@babel/traverse@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.2.tgz#1a0a4aef53177bead359ccd0c89f4426c805b2ae" + integrity sha512-s4/r+a7xTnny2O6FcZzqgT6nE4/GHEdcqj4qAeglbUOh0TeglEfmNJFAd/OLoVtGd6ZhAO8GCVvCNUO5t/VJVQ== dependencies: "@babel/code-frame" "^7.24.7" "@babel/generator" "^7.25.0" "@babel/parser" "^7.25.0" "@babel/template" "^7.25.0" - "@babel/types" "^7.25.0" + "@babel/types" "^7.25.2" debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.24.9", "@babel/types@^7.25.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.0.tgz#e6e3656c581f28da8452ed4f69e38008ec0ba41b" - integrity sha512-LcnxQSsd9aXOIgmmSpvZ/1yo46ra2ESYyqLcryaBZOghxy5qqOBjvCWP5JfkI8yl9rlxRgdLTTMCQQRcN2hdCg== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.24.0", "@babel/types@^7.24.6", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.6.tgz#ba4e1f59870c10dc2fa95a274ac4feec23b21912" + integrity sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ== + dependencies: + "@babel/helper-string-parser" "^7.24.6" + "@babel/helper-validator-identifier" "^7.24.6" + to-fast-properties "^2.0.0" + +"@babel/types@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.7.tgz#6027fe12bc1aa724cd32ab113fb7f1988f1f66f2" + integrity sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q== + dependencies: + "@babel/helper-string-parser" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" + to-fast-properties "^2.0.0" + +"@babel/types@^7.24.8", "@babel/types@^7.25.0", "@babel/types@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.2.tgz#55fb231f7dc958cd69ea141a4c2997e819646125" + integrity sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q== dependencies: "@babel/helper-string-parser" "^7.24.8" "@babel/helper-validator-identifier" "^7.24.7" @@ -1001,9 +1411,9 @@ eslint-visitor-keys "^3.3.0" "@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" - integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== + version "4.10.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" + integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== "@eslint/eslintrc@^2.1.4": version "2.1.4" @@ -1319,9 +1729,9 @@ "@jridgewell/trace-mapping" "^0.3.25" "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" @@ -1402,6 +1812,11 @@ "@octokit/types" "^13.0.0" universal-user-agent "^6.0.0" +"@octokit/openapi-types@^22.0.1": + version "22.0.1" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-22.0.1.tgz#41f5b1c4dad3e547906ea9258837fcbea7cc72b4" + integrity sha512-1yN5m1IMNXthoBDUXFF97N1gHop04B3H8ws7wtOr8GgRyDO1gKALjwMHARNBoMBiB/2vEe/vxstrApcJZzQbnQ== + "@octokit/openapi-types@^22.2.0": version "22.2.0" resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-22.2.0.tgz#75aa7dcd440821d99def6a60b5f014207ae4968e" @@ -1455,7 +1870,14 @@ "@octokit/plugin-request-log" "^4.0.0" "@octokit/plugin-rest-endpoint-methods" "13.2.2" -"@octokit/types@^13.0.0", "@octokit/types@^13.1.0", "@octokit/types@^13.5.0": +"@octokit/types@^13.0.0", "@octokit/types@^13.1.0": + version "13.4.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.4.0.tgz#b9f6865a6fc491387352d7f327e1f030fa7be1cd" + integrity sha512-WlMegy3lPXYWASe3k9Jslc5a0anrYAYMWtsFrxBTdQjS70hvLH6C+PGvHbOsgy3RA3LouGJoU/vAt4KarecQLQ== + dependencies: + "@octokit/openapi-types" "^22.0.1" + +"@octokit/types@^13.5.0": version "13.5.0" resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.5.0.tgz#4796e56b7b267ebc7c921dcec262b3d5bfb18883" integrity sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ== @@ -1732,9 +2154,9 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.20.6" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7" - integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg== + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.5.tgz#7b7502be0aa80cc4ef22978846b983edaafcd4dd" + integrity sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ== dependencies: "@babel/types" "^7.20.7" @@ -1810,9 +2232,9 @@ "@types/d3-dsv" "*" "@types/d3-force@*": - version "3.0.10" - resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-3.0.10.tgz#6dc8fc6e1f35704f3b057090beeeb7ac674bff1a" - integrity sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw== + version "3.0.9" + resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-3.0.9.tgz#dd96ccefba4386fe4ff36b8e4ee4e120c21fcf29" + integrity sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA== "@types/d3-format@*": version "3.0.4" @@ -2013,11 +2435,11 @@ integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag== "@types/node@*": - version "22.0.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.0.0.tgz#04862a2a71e62264426083abe1e27e87cac05a30" - integrity sha512-VT7KSYudcPOzP5Q0wfbowyNLaVR8QWUdw+088uFWwfvpY6uCWaXpqV6ieLAu9WBcnTa7H4Z5RLK8I5t2FuOcqw== + version "20.12.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.7.tgz#04080362fa3dd6c5822061aa3124f5c152cff384" + integrity sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg== dependencies: - undici-types "~6.11.1" + undici-types "~5.26.4" "@types/normalize-package-data@^2.4.0", "@types/normalize-package-data@^2.4.1", "@types/normalize-package-data@^2.4.3": version "2.4.4" @@ -2064,29 +2486,29 @@ "@types/node" "*" "@typescript-eslint/eslint-plugin@^7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.17.0.tgz#c8ed1af1ad2928ede5cdd207f7e3090499e1f77b" - integrity sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A== + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz#b16d3cf3ee76bf572fdf511e79c248bdec619ea3" + integrity sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw== dependencies: "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "7.17.0" - "@typescript-eslint/type-utils" "7.17.0" - "@typescript-eslint/utils" "7.17.0" - "@typescript-eslint/visitor-keys" "7.17.0" + "@typescript-eslint/scope-manager" "7.18.0" + "@typescript-eslint/type-utils" "7.18.0" + "@typescript-eslint/utils" "7.18.0" + "@typescript-eslint/visitor-keys" "7.18.0" graphemer "^1.4.0" ignore "^5.3.1" natural-compare "^1.4.0" ts-api-utils "^1.3.0" "@typescript-eslint/parser@^7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.17.0.tgz#be8e32c159190cd40a305a2121220eadea5a88e7" - integrity sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A== - dependencies: - "@typescript-eslint/scope-manager" "7.17.0" - "@typescript-eslint/types" "7.17.0" - "@typescript-eslint/typescript-estree" "7.17.0" - "@typescript-eslint/visitor-keys" "7.17.0" + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.18.0.tgz#83928d0f1b7f4afa974098c64b5ce6f9051f96a0" + integrity sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg== + dependencies: + "@typescript-eslint/scope-manager" "7.18.0" + "@typescript-eslint/types" "7.18.0" + "@typescript-eslint/typescript-estree" "7.18.0" + "@typescript-eslint/visitor-keys" "7.18.0" debug "^4.3.4" "@typescript-eslint/scope-manager@5.62.0": @@ -2097,21 +2519,21 @@ "@typescript-eslint/types" "5.62.0" "@typescript-eslint/visitor-keys" "5.62.0" -"@typescript-eslint/scope-manager@7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.17.0.tgz#e072d0f914662a7bfd6c058165e3c2b35ea26b9d" - integrity sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA== +"@typescript-eslint/scope-manager@7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz#c928e7a9fc2c0b3ed92ab3112c614d6bd9951c83" + integrity sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA== dependencies: - "@typescript-eslint/types" "7.17.0" - "@typescript-eslint/visitor-keys" "7.17.0" + "@typescript-eslint/types" "7.18.0" + "@typescript-eslint/visitor-keys" "7.18.0" -"@typescript-eslint/type-utils@7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.17.0.tgz#c5da78feb134c9c9978cbe89e2b1a589ed22091a" - integrity sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA== +"@typescript-eslint/type-utils@7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz#2165ffaee00b1fbbdd2d40aa85232dab6998f53b" + integrity sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA== dependencies: - "@typescript-eslint/typescript-estree" "7.17.0" - "@typescript-eslint/utils" "7.17.0" + "@typescript-eslint/typescript-estree" "7.18.0" + "@typescript-eslint/utils" "7.18.0" debug "^4.3.4" ts-api-utils "^1.3.0" @@ -2120,10 +2542,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== -"@typescript-eslint/types@7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.17.0.tgz#7ce8185bdf06bc3494e73d143dbf3293111b9cff" - integrity sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A== +"@typescript-eslint/types@7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.18.0.tgz#b90a57ccdea71797ffffa0321e744f379ec838c9" + integrity sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ== "@typescript-eslint/typescript-estree@5.62.0": version "5.62.0" @@ -2138,13 +2560,13 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.17.0.tgz#dcab3fea4c07482329dd6107d3c6480e228e4130" - integrity sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw== +"@typescript-eslint/typescript-estree@7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz#b5868d486c51ce8f312309ba79bdb9f331b37931" + integrity sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA== dependencies: - "@typescript-eslint/types" "7.17.0" - "@typescript-eslint/visitor-keys" "7.17.0" + "@typescript-eslint/types" "7.18.0" + "@typescript-eslint/visitor-keys" "7.18.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" @@ -2152,15 +2574,15 @@ semver "^7.6.0" ts-api-utils "^1.3.0" -"@typescript-eslint/utils@7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.17.0.tgz#815cd85b9001845d41b699b0ce4f92d6dfb84902" - integrity sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw== +"@typescript-eslint/utils@7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.18.0.tgz#bca01cde77f95fc6a8d5b0dbcbfb3d6ca4be451f" + integrity sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw== dependencies: "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "7.17.0" - "@typescript-eslint/types" "7.17.0" - "@typescript-eslint/typescript-estree" "7.17.0" + "@typescript-eslint/scope-manager" "7.18.0" + "@typescript-eslint/types" "7.18.0" + "@typescript-eslint/typescript-estree" "7.18.0" "@typescript-eslint/utils@^5.10.0": version "5.62.0" @@ -2184,12 +2606,12 @@ "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" -"@typescript-eslint/visitor-keys@7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.17.0.tgz#680465c734be30969e564b4647f38d6cdf49bfb0" - integrity sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A== +"@typescript-eslint/visitor-keys@7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz#0564629b6124d67607378d0f0332a0495b25e7d7" + integrity sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg== dependencies: - "@typescript-eslint/types" "7.17.0" + "@typescript-eslint/types" "7.18.0" eslint-visitor-keys "^3.4.3" "@ungap/structured-clone@^1.2.0": @@ -2229,9 +2651,9 @@ acorn-jsx@^5.3.2: integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn@^8.8.2, acorn@^8.9.0: - version "8.12.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" - integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== add-stream@^1.0.0: version "1.0.0" @@ -2287,7 +2709,17 @@ ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.17.1: +ajv@^8.0.0: + version "8.13.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.13.0.tgz#a3939eaec9fb80d217ddf0c3376948c023f28c91" + integrity sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA== + dependencies: + fast-deep-equal "^3.1.3" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.4.1" + +ajv@^8.17.1: version "8.17.1" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== @@ -2438,9 +2870,9 @@ asynckit@^0.4.0: integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== axios@^1.6.1: - version "1.7.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" - integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== + version "1.6.8" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66" + integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ== dependencies: follow-redirects "^1.15.6" form-data "^4.0.0" @@ -2481,12 +2913,12 @@ babel-plugin-jest-hoist@^29.6.3: "@types/babel__traverse" "^7.0.6" babel-plugin-polyfill-corejs2@^0.4.10: - version "0.4.11" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz#30320dfe3ffe1a336c15afdcdafd6fd615b25e33" - integrity sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q== + version "0.4.10" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz#276f41710b03a64f6467433cab72cbc2653c38b1" + integrity sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ== dependencies: "@babel/compat-data" "^7.22.6" - "@babel/helper-define-polyfill-provider" "^0.6.2" + "@babel/helper-define-polyfill-provider" "^0.6.1" semver "^6.3.1" babel-plugin-polyfill-corejs3@^0.10.4: @@ -2498,11 +2930,11 @@ babel-plugin-polyfill-corejs3@^0.10.4: core-js-compat "^3.36.1" babel-plugin-polyfill-regenerator@^0.6.1: - version "0.6.2" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz#addc47e240edd1da1058ebda03021f382bba785e" - integrity sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg== + version "0.6.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz#4f08ef4c62c7a7f66a35ed4c0d75e30506acc6be" + integrity sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g== dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.2" + "@babel/helper-define-polyfill-provider" "^0.6.1" babel-preset-current-node-syntax@^1.0.0: version "1.0.1" @@ -2607,14 +3039,24 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.3: +braces@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: fill-range "^7.1.1" -browserslist@^4.23.0, browserslist@^4.23.1: +browserslist@^4.22.2, browserslist@^4.23.0: + version "4.23.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" + integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== + dependencies: + caniuse-lite "^1.0.30001587" + electron-to-chromium "^1.4.668" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + +browserslist@^4.23.1: version "4.23.2" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.2.tgz#244fe803641f1c19c28c48c4b6ec9736eb3d32ed" integrity sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA== @@ -2721,10 +3163,15 @@ camelcase@^7.0.0, camelcase@^7.0.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-7.0.1.tgz#f02e50af9fd7782bc8b88a3558c32fd3a388f048" integrity sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw== +caniuse-lite@^1.0.30001587: + version "1.0.30001608" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001608.tgz#7ae6e92ffb300e4b4ec2f795e0abab456ec06cc0" + integrity sha512-cjUJTQkk9fQlJR2s4HMuPMvTiRggl0rAVMtthQuyOlDWuqHXqN8azLq+pi8B2TjwKJ32diHjUqRIKeFX4z1FoA== + caniuse-lite@^1.0.30001640: - version "1.0.30001643" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz#9c004caef315de9452ab970c3da71085f8241dbd" - integrity sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg== + version "1.0.30001644" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001644.tgz#bcd4212a7a03bdedba1ea850b8a72bfe4bec2395" + integrity sha512-YGvlOZB4QhZuiis+ETS0VXR+MExbFf4fZYYeMTEE0aTQd/RdIjkTyZjLrbYVKnHzppDvnOhritRVv+i7Go6mHw== canvas@^2.11.2: version "2.11.2" @@ -2831,9 +3278,9 @@ ci-info@^3.2.0: integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== cjs-module-lexer@^1.0.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz#c485341ae8fd999ca4ee5af2d7a1c9ae01e0099c" - integrity sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q== + version "1.2.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" + integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== clean-stack@^4.0.0: version "4.2.0" @@ -2951,9 +3398,9 @@ commander@7: integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== commander@^12.0.0: - version "12.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" - integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== + version "12.0.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.0.0.tgz#b929db6df8546080adfd004ab215ed48cf6f2592" + integrity sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA== commander@^5.1.0: version "5.1.0" @@ -3297,7 +3744,14 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -core-js-compat@^3.36.1, core-js-compat@^3.37.1: +core-js-compat@^3.36.1: + version "3.36.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.36.1.tgz#1818695d72c99c25d621dca94e6883e190cea3c8" + integrity sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA== + dependencies: + browserslist "^4.23.0" + +core-js-compat@^3.37.1: version "3.37.1" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.37.1.tgz#c844310c7852f4bdf49b8d339730b97e17ff09ee" integrity sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg== @@ -3649,14 +4103,7 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: - version "4.3.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" - integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== - dependencies: - ms "2.1.2" - -debug@4.3.4: +debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -3696,9 +4143,9 @@ decompress-response@^6.0.0: mimic-response "^3.1.0" dedent@^1.0.0: - version "1.5.3" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" - integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== + version "1.5.1" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" + integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== deep-extend@^0.6.0: version "0.6.0" @@ -3893,10 +4340,15 @@ ejs@^3.1.10: dependencies: jake "^10.8.5" +electron-to-chromium@^1.4.668: + version "1.4.731" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.731.tgz#d3dc19f359045b750a1fb0bc42315a502d950187" + integrity sha512-+TqVfZjpRz2V/5SPpmJxq9qK620SC5SqCnxQIOi7i/U08ZDcTpKbT7Xjj9FU5CbXTMUb4fywbIr8C7cGv4hcjw== + electron-to-chromium@^1.4.820: - version "1.5.2" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.2.tgz#6126ad229ce45e781ec54ca40db0504787f23d19" - integrity sha512-kc4r3U3V3WLaaZqThjYz/Y6z8tJe+7K0bbjUVo3i+LWIypVdMx5nXCkwRe6SWbY6ILqLdc1rKcKmr3HoH7wjSQ== + version "1.5.4" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.4.tgz#cd477c830dd6fca41fbd5465c1ff6ce08ac22343" + integrity sha512-orzA81VqLyIGUEA77YkVA1D+N+nNfl2isJVjjmOyrlxuooZ19ynb+dOlaDTqd/idKRS9lDCSBmtzM+kyCsMnkA== emittery@^0.13.1: version "0.13.1" @@ -4083,9 +4535,9 @@ esprima@^4.0.0, esprima@^4.0.1: integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.4.2: - version "1.6.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" - integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== dependencies: estraverse "^5.1.0" @@ -4561,7 +5013,7 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob@^10.3.12, glob@^10.4.1: +glob@^10.3.12: version "10.4.5" resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== @@ -4573,6 +5025,18 @@ glob@^10.3.12, glob@^10.4.1: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" +glob@^10.4.1: + version "10.4.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.2.tgz#bed6b95dade5c1f80b4434daced233aee76160e5" + integrity sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + glob@^7.0.0, glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -4735,7 +5199,7 @@ has-unicode@^2.0.1: resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== -hasown@^2.0.2: +hasown@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== @@ -4762,9 +5226,9 @@ hosted-git-info@^4.0.1: lru-cache "^6.0.0" hosted-git-info@^7.0.0: - version "7.0.2" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-7.0.2.tgz#9b751acac097757667f30114607ef7b661ff4f17" - integrity sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w== + version "7.0.1" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-7.0.1.tgz#9985fcb2700467fecf7f33a4d4874e30680b5322" + integrity sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA== dependencies: lru-cache "^10.0.1" @@ -4812,10 +5276,10 @@ https-proxy-agent@5.0.1, https-proxy-agent@^5.0.0: agent-base "6" debug "4" -https-proxy-agent@^7.0.3, https-proxy-agent@^7.0.5: - version "7.0.5" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz#9e8b5013873299e11fab6fd548405da2d6c602b2" - integrity sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw== +https-proxy-agent@^7.0.2, https-proxy-agent@^7.0.3: + version "7.0.4" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz#8e97b841a029ad8ddc8731f26595bad868cb4168" + integrity sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg== dependencies: agent-base "^7.0.2" debug "4" @@ -4868,9 +5332,9 @@ import-lazy@^4.0.0: integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== import-local@^3.0.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" - integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== dependencies: pkg-dir "^4.2.0" resolve-cwd "^3.0.0" @@ -4968,12 +5432,12 @@ is-ci@3.0.1: dependencies: ci-info "^3.2.0" -is-core-module@^2.13.0, is-core-module@^2.5.0: - version "2.15.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea" - integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA== +is-core-module@^2.13.0, is-core-module@^2.5.0, is-core-module@^2.8.1: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== dependencies: - hasown "^2.0.2" + hasown "^2.0.0" is-docker@^2.0.0: version "2.2.1" @@ -5185,9 +5649,9 @@ istanbul-lib-instrument@^5.0.4: semver "^6.3.0" istanbul-lib-instrument@^6.0.0: - version "6.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" - integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + version "6.0.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz#91655936cf7380e4e473383081e38478b69993b1" + integrity sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw== dependencies: "@babel/core" "^7.23.9" "@babel/parser" "^7.23.9" @@ -5222,9 +5686,9 @@ istanbul-reports@^3.1.3: istanbul-lib-report "^3.0.0" jackspeak@^3.1.2: - version "3.4.3" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" - integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + version "3.4.0" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.0.tgz#a75763ff36ad778ede6a156d8ee8b124de445b4a" + integrity sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw== dependencies: "@isaacs/cliui" "^8.0.2" optionalDependencies: @@ -5612,9 +6076,9 @@ jest@^29.7.0: jest-cli "^29.7.0" joi@^17.11.0: - version "17.13.3" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.13.3.tgz#0f5cc1169c999b30d344366d384b12d92558bcec" - integrity sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA== + version "17.12.3" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.12.3.tgz#944646979cd3b460178547b12ba37aca8482f63d" + integrity sha512-2RRziagf555owrm9IRVtdKynOBeITiDpuZqIpgwqXShPncPKNiRQoiGsl/T8SQdq+8ugRzH2LqY67irr2y/d+g== dependencies: "@hapi/hoek" "^9.3.0" "@hapi/topo" "^5.1.0" @@ -5668,9 +6132,9 @@ json-parse-even-better-errors@^2.3.0: integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json-parse-even-better-errors@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz#b43d35e89c0f3be6b5fbbe9dc6c82467b30c28da" - integrity sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ== + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz#02bb29fb5da90b5444581749c22cedd3597c6cb0" + integrity sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg== json-schema-traverse@^0.4.1: version "0.4.1" @@ -5855,10 +6319,15 @@ lowercase-keys@^3.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2" integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ== -lru-cache@^10.0.1, lru-cache@^10.2.0: - version "10.4.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" - integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== +lru-cache@^10.0.1: + version "10.2.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3" + integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q== + +lru-cache@^10.2.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.3.0.tgz#4a4aaf10c84658ab70f79a85a9a3f1e1fb11196b" + integrity sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ== lru-cache@^5.1.1: version "5.1.1" @@ -5885,9 +6354,9 @@ macos-release@^3.1.0: integrity sha512-fSErXALFNsnowREYZ49XCdOHF8wOPWuFOGQrAhP7x5J/BqQv+B02cNsTykGpDgRVx43EKg++6ANmTaGTtW+hUA== magic-string@^0.30.3: - version "0.30.10" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.10.tgz#123d9c41a0cb5640c892b041d4cfb3bd0aa4b39e" - integrity sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ== + version "0.30.9" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.9.tgz#8927ae21bfdd856310e07a1bc8dd5e73cb6c251d" + integrity sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw== dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" @@ -5976,23 +6445,18 @@ merge2@^1.3.0, merge2@^1.4.1: integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== micromatch@^4.0.4: - version "4.0.7" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" - integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== dependencies: - braces "^3.0.3" + braces "^3.0.2" picomatch "^2.3.1" -mime-db@1.52.0: +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -"mime-db@>= 1.43.0 < 2": - version "1.53.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.53.0.tgz#3cb63cd820fc29896d9d4e8c32ab4fcd74ccb447" - integrity sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg== - mime-db@~1.33.0: version "1.33.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" @@ -6057,9 +6521,9 @@ minimatch@^5.0.1: brace-expansion "^2.0.1" minimatch@^9.0.4: - version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + version "9.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" + integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== dependencies: brace-expansion "^2.0.1" @@ -6133,9 +6597,9 @@ mute-stream@1.0.0: integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== nan@^2.17.0: - version "2.20.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3" - integrity sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw== + version "2.19.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.19.0.tgz#bb58122ad55a6c5bc973303908d5b16cfdd5a8c0" + integrity sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw== natural-compare@^1.4.0: version "1.4.0" @@ -6198,9 +6662,9 @@ node-int64@^0.4.0: integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== node-releases@^2.0.14: - version "2.0.18" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" - integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== nopt@^5.0.0: version "5.0.0" @@ -6220,11 +6684,12 @@ normalize-package-data@^3.0.2: validate-npm-package-license "^3.0.1" normalize-package-data@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-6.0.2.tgz#a7bc22167fe24025412bcff0a9651eb768b03506" - integrity sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g== + version "6.0.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-6.0.0.tgz#68a96b3c11edd462af7189c837b6b1064a484196" + integrity sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg== dependencies: hosted-git-info "^7.0.0" + is-core-module "^2.8.1" semver "^7.3.5" validate-npm-package-license "^3.0.4" @@ -6316,16 +6781,16 @@ open@10.1.0: is-wsl "^3.1.0" optionator@^0.9.3: - version "0.9.4" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" - integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" deep-is "^0.1.3" fast-levenshtein "^2.0.6" levn "^0.4.1" prelude-ls "^1.2.1" type-check "^0.4.0" - word-wrap "^1.2.5" ora@8.0.1: version "8.0.1" @@ -6435,20 +6900,20 @@ p-try@^2.0.0: integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== pac-proxy-agent@^7.0.1: - version "7.0.2" - resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-7.0.2.tgz#0fb02496bd9fb8ae7eb11cfd98386daaac442f58" - integrity sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg== + version "7.0.1" + resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz#6b9ddc002ec3ff0ba5fdf4a8a21d363bcc612d75" + integrity sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A== dependencies: "@tootallnate/quickjs-emscripten" "^0.23.0" agent-base "^7.0.2" debug "^4.3.4" get-uri "^6.0.1" http-proxy-agent "^7.0.0" - https-proxy-agent "^7.0.5" - pac-resolver "^7.0.1" - socks-proxy-agent "^8.0.4" + https-proxy-agent "^7.0.2" + pac-resolver "^7.0.0" + socks-proxy-agent "^8.0.2" -pac-resolver@^7.0.1: +pac-resolver@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-7.0.1.tgz#54675558ea368b64d210fd9c92a640b5f3b8abb6" integrity sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg== @@ -6610,7 +7075,12 @@ pend@~1.2.0: resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== -picocolors@^1.0.0, picocolors@^1.0.1: +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picocolors@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== @@ -6788,9 +7258,9 @@ rc@1.2.8, rc@^1.0.1, rc@^1.1.6: strip-json-comments "~2.0.1" react-is@^18.0.0: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" - integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== read-package-up@^11.0.0: version "11.0.0" @@ -7176,7 +7646,14 @@ semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.2: +semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + +semver@^7.6.2: version "7.6.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== @@ -7299,16 +7776,16 @@ smob@^1.0.0: resolved "https://registry.yarnpkg.com/smob/-/smob-1.5.0.tgz#85d79a1403abf128d24d3ebc1cdc5e1a9548d3ab" integrity sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig== -socks-proxy-agent@^8.0.2, socks-proxy-agent@^8.0.4: - version "8.0.4" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz#9071dca17af95f483300316f4b063578fa0db08c" - integrity sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw== +socks-proxy-agent@^8.0.2: + version "8.0.3" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz#6b2da3d77364fde6292e810b496cb70440b9b89d" + integrity sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A== dependencies: agent-base "^7.1.1" debug "^4.3.4" - socks "^2.8.3" + socks "^2.7.1" -socks@^2.8.3: +socks@^2.7.1: version "2.8.3" resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.3.tgz#1ebd0f09c52ba95a09750afe3f3f9f724a800cb5" integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw== @@ -7367,9 +7844,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.18" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz#22aa922dcf2f2885a6494a261f2d8b75345d0326" - integrity sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ== + version "3.0.17" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz#887da8aa73218e51a1d917502d79863161a93f9c" + integrity sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg== split2@^4.0.0: version "4.2.0" @@ -7434,9 +7911,9 @@ string-width@^5.0.1, string-width@^5.1.2: strip-ansi "^7.0.1" string-width@^7.0.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.2.0.tgz#b5bb8e2165ce275d4d43476dd2700ad9091db6dc" - integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== + version "7.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.1.0.tgz#d994252935224729ea3719c49f7206dc9c46550a" + integrity sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw== dependencies: emoji-regex "^10.3.0" get-east-asian-width "^1.0.0" @@ -7593,7 +8070,17 @@ tempfile@^5.0.0: dependencies: temp-dir "^3.0.0" -terser@^5.17.4, terser@^5.31.3: +terser@^5.17.4: + version "5.31.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.0.tgz#06eef86f17007dbad4593f11a574c7f5eb02c6a1" + integrity sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + +terser@^5.31.3: version "5.31.3" resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.3.tgz#b24b7beb46062f4653f049eea4f0cd165d0f0c38" integrity sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA== @@ -7712,7 +8199,12 @@ tslib@^1.8.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.1, tslib@^2.1.0, tslib@^2.6.2, tslib@^2.6.3, tslib@~2.6.3: +tslib@^2.0.1, tslib@^2.1.0, tslib@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +tslib@^2.6.3, tslib@~2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== @@ -7761,7 +8253,12 @@ type-fest@^3.8.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.13.1.tgz#bb744c1f0678bea7543a2d1ec24e83e68e8c8706" integrity sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g== -type-fest@^4.2.0, type-fest@^4.6.0, type-fest@^4.7.1: +type-fest@^4.2.0: + version "4.15.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.15.0.tgz#21da206b89c15774cc718c4f2d693e13a1a14a43" + integrity sha512-tB9lu0pQpX5KJq54g+oHOLumOx+pMep4RaM6liXh2PKmVRFF+/vAtUP0ZaJ0kOySfVNjF6doBWPHhBhISKdlIA== + +type-fest@^4.6.0, type-fest@^4.7.1: version "4.23.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.23.0.tgz#8196561a6b835175473be744f3e41e2dece1496b" integrity sha512-ZiBujro2ohr5+Z/hZWHESLz3g08BBdrdLMieYFULJO+tWc437sn8kQsWLJoZErY8alNhxre9K4p3GURAG11n+w== @@ -7784,9 +8281,9 @@ typescript@^5.4.5, typescript@~5.5.4: integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== uglify-js@^3.1.4: - version "3.19.1" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.1.tgz#2d5df6a0872c43da43187968308d7741d44b8056" - integrity sha512-y/2wiW+ceTYR2TSSptAhfnEtpLaQ4Ups5zrjB2d3kuVxHj16j/QJwPl5PvuGy9uARb39J0+iKxcRPvtpsx4A4A== + version "3.17.4" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" + integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== unbzip2-stream@1.4.3: version "1.4.3" @@ -7796,10 +8293,10 @@ unbzip2-stream@1.4.3: buffer "^5.2.1" through "^2.3.8" -undici-types@~6.11.1: - version "6.11.1" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.11.1.tgz#432ea6e8efd54a48569705a699e62d8f4981b197" - integrity sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" @@ -7846,6 +8343,14 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + update-browserslist-db@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" @@ -7880,7 +8385,7 @@ update-notifier@7.1.0: semver-diff "^4.0.0" xdg-basedir "^5.1.0" -uri-js@^4.2.2: +uri-js@^4.2.2, uri-js@^4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== @@ -7898,9 +8403,9 @@ util-deprecate@^1.0.1: integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== v8-to-istanbul@^9.0.1: - version "9.3.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" - integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + version "9.2.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz#2ed7644a245cddd83d4e087b9b33b3e62dfd10ad" + integrity sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA== dependencies: "@jridgewell/trace-mapping" "^0.3.12" "@types/istanbul-lib-coverage" "^2.0.1" @@ -7919,37 +8424,37 @@ vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== -vega-canvas@^1.2.7: +vega-canvas@^1.2.6, vega-canvas@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/vega-canvas/-/vega-canvas-1.2.7.tgz#cf62169518f5dcd91d24ad352998c2248f8974fb" integrity sha512-OkJ9CACVcN9R5Pi9uF6MZBF06pO6qFpDYHWSKBJsdHP5o724KrsgR6UvbnXFH82FdsiTOff/HqjuaG8C7FL+9Q== -vega-cli@^5.30.0: - version "5.30.0" - resolved "https://registry.yarnpkg.com/vega-cli/-/vega-cli-5.30.0.tgz#0293791e3451d45798e52b7583146fdb3b297605" - integrity sha512-qHlVNh6SU/sV96Zys30t7jtVlDKAn+2Ex2EuiU8xK+DLDB8h2t0IK5/FwR8CxE9rLWHYYXDOuCxkzRqFRzSMQQ== +vega-cli@^5.28.0: + version "5.29.0" + resolved "https://registry.yarnpkg.com/vega-cli/-/vega-cli-5.29.0.tgz#d6b2e6ecf84ce84caad689acba3b60799353187d" + integrity sha512-ndiQEjHrV0DkT7nWEroQerAuZwNZC3c9SZlmVh8a19vY9s/GsPfNdOq2apAN44mtruMtD3tzgajLLxEii7/wEA== dependencies: canvas "^2.11.2" - vega "5.30.0" + vega "5.29.0" yargs "17" -vega-crossfilter@~4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/vega-crossfilter/-/vega-crossfilter-4.1.2.tgz#810281c279b3592310f12814bc61206dd42ca61d" - integrity sha512-J7KVEXkpfRJBfRvwLxn5vNCzQCNkrnzmDvkvwhuiwT4gPm5sk7MK5TuUP8GCl/iKYw+kWeVXEtrVHwWtug+bcQ== +vega-crossfilter@~4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/vega-crossfilter/-/vega-crossfilter-4.1.1.tgz#3ff3ca0574883706f7a399dc6d60f4a0f065ece4" + integrity sha512-yesvlMcwRwxrtAd9IYjuxWJJuAMI0sl7JvAFfYtuDkkGDtqfLXUcCzHIATqW6igVIE7tWwGxnbfvQLhLNgK44Q== dependencies: d3-array "^3.2.2" - vega-dataflow "^5.7.6" - vega-util "^1.17.2" + vega-dataflow "^5.7.5" + vega-util "^1.17.1" -vega-dataflow@^5.7.6, vega-dataflow@~5.7.6: - version "5.7.6" - resolved "https://registry.yarnpkg.com/vega-dataflow/-/vega-dataflow-5.7.6.tgz#21dfad9120cb18d9aeaed578658670839d1adc95" - integrity sha512-9Md8+5iUC1MVKPKDyZ7pCEHk6I9am+DgaMzZqo/27O/KI4f23/WQXPyuI8jbNmc/mkm340P0TKREmzL5M7+2Dg== +vega-dataflow@^5.7.3, vega-dataflow@^5.7.5, vega-dataflow@~5.7.5: + version "5.7.5" + resolved "https://registry.yarnpkg.com/vega-dataflow/-/vega-dataflow-5.7.5.tgz#0d559f3c3a968831f2995e099a2e270993ddfed9" + integrity sha512-EdsIl6gouH67+8B0f22Owr2tKDiMPNNR8lEvJDcxmFw02nXd8juimclpLvjPQriqn6ta+3Dn5txqfD117H04YA== dependencies: - vega-format "^1.1.2" - vega-loader "^4.5.2" - vega-util "^1.17.2" + vega-format "^1.1.1" + vega-loader "^4.5.1" + vega-util "^1.17.1" vega-datasets@^2.8.1: version "2.8.1" @@ -7970,23 +8475,31 @@ vega-embed@^6.26.0: vega-themes "^2.15.0" vega-tooltip "^0.34.0" -vega-encode@~4.10.1: - version "4.10.1" - resolved "https://registry.yarnpkg.com/vega-encode/-/vega-encode-4.10.1.tgz#1656e20396db99c414f495704ef3d9cff99631df" - integrity sha512-d25nVKZDrg109rC65M8uxE+7iUrTxktaqgK4fU3XZBgpWlh1K4UbU5nDag7kiHVVN4tKqwgd+synEotra9TiVQ== +vega-encode@~4.10.0: + version "4.10.0" + resolved "https://registry.yarnpkg.com/vega-encode/-/vega-encode-4.10.0.tgz#def64d29a0ed897abebcc9f421dbb8953adeea00" + integrity sha512-TTWIXVWHLGMkPEUC1bLkQKZdKnHUTGcjO2JST3jxHFgnGtN/HOovjaeOm2mkOoxrHJgQERyKorpGprOttuY6Kg== dependencies: d3-array "^3.2.2" d3-interpolate "^3.0.1" - vega-dataflow "^5.7.6" - vega-scale "^7.4.1" - vega-util "^1.17.2" + vega-dataflow "^5.7.5" + vega-scale "^7.3.0" + vega-util "^1.17.1" vega-event-selector@^3.0.1, vega-event-selector@~3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/vega-event-selector/-/vega-event-selector-3.0.1.tgz#b99e92147b338158f8079d81b28b2e7199c2e259" integrity sha512-K5zd7s5tjr1LiOOkjGpcVls8GsH/f2CWCrWcpKy74gTCp+llCdwz0Enqo013ZlGaRNjfgD/o1caJRt3GSaec4A== -vega-expression@^5.0.1, vega-expression@^5.1.1, vega-expression@~5.1.1: +vega-expression@^5.0.1, vega-expression@^5.1.0, vega-expression@~5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vega-expression/-/vega-expression-5.1.0.tgz#4ec0e66b56a2faba88361eb717011303bbb1ff61" + integrity sha512-u8Rzja/cn2PEUkhQN3zUj3REwNewTA92ExrcASNKUJPCciMkHJEjESwFYuI6DWMCq4hQElQ92iosOAtwzsSTqA== + dependencies: + "@types/estree" "^1.0.0" + vega-util "^1.17.1" + +vega-expression@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/vega-expression/-/vega-expression-5.1.1.tgz#9b2d287a1f34d990577c9798ae68ec88453815ef" integrity sha512-zv9L1Hm0KHE9M7mldHyz8sXbGu3KmC0Cdk7qfHkcTNS75Jpsem6jkbu6ZAwx5cNUeW91AxUQOu77r4mygq2wUQ== @@ -7994,153 +8507,176 @@ vega-expression@^5.0.1, vega-expression@^5.1.1, vega-expression@~5.1.1: "@types/estree" "^1.0.0" vega-util "^1.17.2" -vega-force@~4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/vega-force/-/vega-force-4.2.1.tgz#bdce6ec8572867b4ff2fb7e09d2894798c5358ec" - integrity sha512-2BcuuqFr77vcCyKfcpedNFeYMxi+XEFCrlgLWNx7YV0PI8pdP5y/yPkzyuE9Tb894+KkRAvfQHZRAshcnFNcMw== +vega-force@~4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/vega-force/-/vega-force-4.2.0.tgz#5374d0dbac674c92620a9801e12b650b0966336a" + integrity sha512-aE2TlP264HXM1r3fl58AvZdKUWBNOGkIvn4EWyqeJdgO2vz46zSU7x7TzPG4ZLuo44cDRU5Ng3I1eQk23Asz6A== dependencies: d3-force "^3.0.0" - vega-dataflow "^5.7.6" - vega-util "^1.17.2" + vega-dataflow "^5.7.5" + vega-util "^1.17.1" -vega-format@^1.1.2, vega-format@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vega-format/-/vega-format-1.1.2.tgz#d344ba8a2680144e92127459c149a4181e9e7f84" - integrity sha512-0kUfAj0dg0U6GcEY0Kp6LiSTCZ8l8jl1qVdQyToMyKmtZg/q56qsiJQZy3WWRr1MtWkTIZL71xSJXgjwjeUaAw== +vega-format@^1.1.1, vega-format@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/vega-format/-/vega-format-1.1.1.tgz#92e4876e18064e7ad54f39045f7b24dede0030b8" + integrity sha512-Rll7YgpYbsgaAa54AmtEWrxaJqgOh5fXlvM2wewO4trb9vwM53KBv4Q/uBWCLK3LLGeBXIF6gjDt2LFuJAUtkQ== dependencies: d3-array "^3.2.2" d3-format "^3.1.0" d3-time-format "^4.1.0" - vega-time "^2.1.2" - vega-util "^1.17.2" + vega-time "^2.1.1" + vega-util "^1.17.1" -vega-functions@^5.15.0, vega-functions@~5.15.0: - version "5.15.0" - resolved "https://registry.yarnpkg.com/vega-functions/-/vega-functions-5.15.0.tgz#a7905e1dd6457efe265dbf954cbc0a5721c484b0" - integrity sha512-pCqmm5efd+3M65jrJGxEy3UGuRksmK6DnWijoSNocnxdCBxez+yqUUVX9o2pN8VxMe3648vZnR9/Vk5CXqRvIQ== +vega-functions@^5.13.1, vega-functions@^5.14.0, vega-functions@~5.14.0: + version "5.14.0" + resolved "https://registry.yarnpkg.com/vega-functions/-/vega-functions-5.14.0.tgz#8235157ae35c0e12f9122e3b783d693967de3c40" + integrity sha512-Q0rocHmJDfQ0tS91kdN8WcEosq1e3HPK1Yf5z36SPYPmTzKw3uxUGE52tLxC832acAYqPmi8R41wAoI/yFQTPg== dependencies: d3-array "^3.2.2" d3-color "^3.1.0" d3-geo "^3.1.0" - vega-dataflow "^5.7.6" - vega-expression "^5.1.1" - vega-scale "^7.4.1" - vega-scenegraph "^4.13.0" + vega-dataflow "^5.7.5" + vega-expression "^5.1.0" + vega-scale "^7.3.0" + vega-scenegraph "^4.10.2" vega-selections "^5.4.2" - vega-statistics "^1.9.0" - vega-time "^2.1.2" - vega-util "^1.17.2" + vega-statistics "^1.8.1" + vega-time "^2.1.1" + vega-util "^1.17.1" -vega-geo@~4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/vega-geo/-/vega-geo-4.4.2.tgz#da4a08ee39c9488bfc4fe6493779f584dd8bb412" - integrity sha512-unuV/UxUHf6UJu6GYxMZonC3SZlMfFXYLOkgEsRSvmsMPt3+CVv8FmG88dXNRUJUrdROrJepgecqx0jOwMSnGA== +vega-geo@~4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/vega-geo/-/vega-geo-4.4.1.tgz#3850232bf28c98fab5e26c5fb401acb6fb37b5e5" + integrity sha512-s4WeZAL5M3ZUV27/eqSD3v0FyJz3PlP31XNSLFy4AJXHxHUeXT3qLiDHoVQnW5Om+uBCPDtTT1ROx1smGIf2aA== dependencies: d3-array "^3.2.2" d3-color "^3.1.0" d3-geo "^3.1.0" vega-canvas "^1.2.7" - vega-dataflow "^5.7.6" - vega-projection "^1.6.1" - vega-statistics "^1.9.0" - vega-util "^1.17.2" + vega-dataflow "^5.7.5" + vega-projection "^1.6.0" + vega-statistics "^1.8.1" + vega-util "^1.17.1" -vega-hierarchy@~4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/vega-hierarchy/-/vega-hierarchy-4.1.2.tgz#e42938c42527b392b110b1e3bf89eaa456dba1b8" - integrity sha512-m+xDtT5092YPSnV0rdTLW+AWmoCb+A54JQ66MUJwiDBpKxvfKnTiQeuiWDU2YudjUoXZN9EBOcI6QHF8H2Lu2A== +vega-hierarchy@~4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/vega-hierarchy/-/vega-hierarchy-4.1.1.tgz#897974a477dfa70cc0d4efab9465b6cc79a9071f" + integrity sha512-h5mbrDtPKHBBQ9TYbvEb/bCqmGTlUX97+4CENkyH21tJs7naza319B15KRK0NWOHuhbGhFmF8T0696tg+2c8XQ== dependencies: d3-hierarchy "^3.1.2" - vega-dataflow "^5.7.6" - vega-util "^1.17.2" + vega-dataflow "^5.7.5" + vega-util "^1.17.1" vega-interpreter@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/vega-interpreter/-/vega-interpreter-1.0.5.tgz#19e1d1b5f84a4ea9cb25c4e90a05ce16cd058484" integrity sha512-po6oTOmeQqr1tzTCdD15tYxAQLeUnOVirAysgVEemzl+vfmvcEP7jQmlc51jz0jMA+WsbmE6oJywisQPu/H0Bg== -vega-label@~1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/vega-label/-/vega-label-1.3.0.tgz#21b3e5ef40e63f51ac987a449d183068c4961503" - integrity sha512-EfSFSCWAwVPsklM5g0gUEuohALgryuGC/SKMmsOH7dYT/bywmLBZhLVbrE+IHJAUauoGrMhYw1mqnXL/0giJBg== +vega-label@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/vega-label/-/vega-label-1.2.1.tgz#ea45fa5a407991c44edfea9c4ca40874d544a3db" + integrity sha512-n/ackJ5lc0Xs9PInCaGumYn2awomPjJ87EMVT47xNgk2bHmJoZV1Ve/1PUM6Eh/KauY211wPMrNp/9Im+7Ripg== dependencies: - vega-canvas "^1.2.7" - vega-dataflow "^5.7.6" - vega-scenegraph "^4.13.0" - vega-util "^1.17.2" + vega-canvas "^1.2.6" + vega-dataflow "^5.7.3" + vega-scenegraph "^4.9.2" + vega-util "^1.15.2" -vega-loader@^4.5.2, vega-loader@~4.5.2: - version "4.5.2" - resolved "https://registry.yarnpkg.com/vega-loader/-/vega-loader-4.5.2.tgz#7212f093c397b153f69f7e6cfef47817c17c5c01" - integrity sha512-ktIdGz3DRIS3XfTP9lJ6oMT5cKwC86nQkjUbXZbOtwXQFVNE2xVWBuH13GP6FKUZxg5hJCMtb5v/e/fwTvhKsQ== +vega-loader@^4.5.1, vega-loader@~4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/vega-loader/-/vega-loader-4.5.1.tgz#b85262b3cb8376487db0c014a8a13c3a5e6d52ad" + integrity sha512-qy5x32SaT0YkEujQM2yKqvLGV9XWQ2aEDSugBFTdYzu/1u4bxdUSRDREOlrJ9Km3RWIOgFiCkobPmFxo47SKuA== dependencies: d3-dsv "^3.0.1" node-fetch "^2.6.7" topojson-client "^3.1.0" - vega-format "^1.1.2" - vega-util "^1.17.2" + vega-format "^1.1.1" + vega-util "^1.17.1" -vega-parser@~6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/vega-parser/-/vega-parser-6.4.0.tgz#6a12f07f0f9178492a17842efe7e1f51a2d36bed" - integrity sha512-/hFIJs0yITxfvLIfhhcpUrcbKvu4UZYoMGmly5PSsbgo60oAsVQW8ZbX2Ji3iNFqZJh1ifoX/P0j+9wep1OISw== +vega-parser@~6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/vega-parser/-/vega-parser-6.3.0.tgz#64233674dba48b68494e7d95dd15b4c27ef15993" + integrity sha512-swS5RuP2imRarMpGWaAZusoKkXc4Z5WxWx349pkqxIAf4F7H8Ya9nThEkSWsFozd75O9nWh0QLifds8Xb7KjUg== dependencies: - vega-dataflow "^5.7.6" + vega-dataflow "^5.7.5" vega-event-selector "^3.0.1" - vega-functions "^5.15.0" - vega-scale "^7.4.1" + vega-functions "^5.14.0" + vega-scale "^7.3.1" vega-util "^1.17.2" -vega-projection@^1.6.1, vega-projection@~1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/vega-projection/-/vega-projection-1.6.1.tgz#da687abc60f4a93bb888385beb23e0a1000f8b57" - integrity sha512-sqfnAAHumU7MWU1tQN3b6HNgKGF3legek0uLHhjLKcDJQxEc7kwcD18txFz2ffQks6d5j+AUhBiq4GARWf0DEQ== +vega-projection@^1.6.0, vega-projection@~1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/vega-projection/-/vega-projection-1.6.0.tgz#921acd3220e7d9d04ccd5ce0109433afb3236966" + integrity sha512-LGUaO/kpOEYuTlul+x+lBzyuL9qmMwP1yShdUWYLW+zXoeyGbs5OZW+NbPPwLYqJr5lpXDr/vGztFuA/6g2xvQ== dependencies: d3-geo "^3.1.0" d3-geo-projection "^4.0.0" - vega-scale "^7.4.1" + vega-scale "^7.3.0" -vega-regression@~1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/vega-regression/-/vega-regression-1.3.0.tgz#3e68e234fa9460041fac082c6a3469c896d436a8" - integrity sha512-gxOQfmV7Ft/MYKpXDEo09WZyBuKOBqxqDRWay9KtfGq/E0Y4vbTPsWLv2cB1ToPJdKE6XSN6Re9tCIw5M/yMUg== +vega-regression@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/vega-regression/-/vega-regression-1.2.0.tgz#12e9df88cf49994ac1a1799f64fb9c118a77a5e0" + integrity sha512-6TZoPlhV/280VbxACjRKqlE0Nv48z5g4CSNf1FmGGTWS1rQtElPTranSoVW4d7ET5eVQ6f9QLxNAiALptvEq+g== dependencies: d3-array "^3.2.2" - vega-dataflow "^5.7.6" + vega-dataflow "^5.7.3" vega-statistics "^1.9.0" - vega-util "^1.17.2" + vega-util "^1.15.2" -vega-runtime@^6.2.0, vega-runtime@~6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/vega-runtime/-/vega-runtime-6.2.0.tgz#10f435089fff11d8e1b49cb0cbab8041731e6f06" - integrity sha512-30UXbujWjKNd5aeP+oeHuwFmzuyVYlBj4aDy9+AjfWLECu8wJt4K01vwegcaGPdCWcPLVIv4Oa9Lob4mcXn5KQ== +vega-runtime@^6.1.4, vega-runtime@~6.1.4: + version "6.1.4" + resolved "https://registry.yarnpkg.com/vega-runtime/-/vega-runtime-6.1.4.tgz#98b67160cea9554e690bfd44719f9d17f90c4220" + integrity sha512-0dDYXyFLQcxPQ2OQU0WuBVYLRZnm+/CwVu6i6N4idS7R9VXIX5581EkCh3pZ20pQ/+oaA7oJ0pR9rJgJ6rukRQ== dependencies: - vega-dataflow "^5.7.6" - vega-util "^1.17.2" + vega-dataflow "^5.7.5" + vega-util "^1.17.1" + +vega-scale@^7.3.0, vega-scale@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/vega-scale/-/vega-scale-7.3.1.tgz#5cb23d1edcf5d759e25fe40b7608a6132a62da46" + integrity sha512-tyTlaaCpHN2Ik/PPKl/j9ThadBDjPtypqW1D7IsUSkzfoZ7RPlI2jwAaoj2C/YW5jFRbEOx3njmjogp48I5CvA== + dependencies: + d3-array "^3.2.2" + d3-interpolate "^3.0.1" + d3-scale "^4.0.2" + vega-time "^2.1.1" + vega-util "^1.17.1" -vega-scale@^7.4.1, vega-scale@~7.4.1: - version "7.4.1" - resolved "https://registry.yarnpkg.com/vega-scale/-/vega-scale-7.4.1.tgz#2dcd3e39ebb00269b03a8be86e44c7b48c67442a" - integrity sha512-dArA28DbV/M92O2QvswnzCmQ4bq9WwLKUoyhqFYWCltmDwkmvX7yhqiFLFMWPItIm7mi4Qyoygby6r4DKd1X2A== +vega-scale@~7.4.0: + version "7.4.0" + resolved "https://registry.yarnpkg.com/vega-scale/-/vega-scale-7.4.0.tgz#05f12ac9cfa40d219b17adecb77f1ecdf7e3e1e8" + integrity sha512-+GxjtToQiR2OqnlvRsnVTaX/HGLG9EPiFWkIwSG5ZCLSAxm0CRiqAQvvRmj0HEeIw8F92aGRX4rSoM8qyGAK5A== dependencies: d3-array "^3.2.2" d3-interpolate "^3.0.1" d3-scale "^4.0.2" d3-scale-chromatic "^3.1.0" - vega-time "^2.1.2" - vega-util "^1.17.2" + vega-time "^2.1.1" + vega-util "^1.17.1" -vega-scenegraph@^4.13.0, vega-scenegraph@~4.13.0: - version "4.13.0" - resolved "https://registry.yarnpkg.com/vega-scenegraph/-/vega-scenegraph-4.13.0.tgz#c4fa5c82773f6244a9ca8b01a44e380adf03fabd" - integrity sha512-nfl45XtuqB5CxyIZJ+bbJ+dofzosPCRlmF+eUQo+0J23NkNXsTzur+1krJDSdhcw0SOYs4sbYRoMz1cpuOM4+Q== +vega-scenegraph@^4.10.2, vega-scenegraph@^4.9.2: + version "4.11.2" + resolved "https://registry.yarnpkg.com/vega-scenegraph/-/vega-scenegraph-4.11.2.tgz#7e9cad503c95fb5af22691bbd394faa8a0b97ce9" + integrity sha512-PXSvv/L7Ek+9mwOTPLpzgkXdfGCR+AcWV5aquPGrqCWoiIF49VJkKFNT1HWxj3RZJX0XKo2r7SuXvRBb9EJ1aA== dependencies: d3-path "^3.1.0" d3-shape "^3.2.0" vega-canvas "^1.2.7" - vega-loader "^4.5.2" - vega-scale "^7.4.1" - vega-util "^1.17.2" + vega-loader "^4.5.1" + vega-scale "^7.3.0" + vega-util "^1.17.1" + +vega-scenegraph@~4.12.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/vega-scenegraph/-/vega-scenegraph-4.12.0.tgz#ac4b08a84f6980b90c4ab2f80a186887ccf444e5" + integrity sha512-l0Us6TLRV7AAd1CxB6mvxXt9/psknqgrr0+6d1zNWtHL8tGszPE4FqllZC5m4ZtUouvE4PWKGybd5uJR0dpchw== + dependencies: + d3-path "^3.1.0" + d3-shape "^3.2.0" + vega-canvas "^1.2.7" + vega-loader "^4.5.1" + vega-scale "^7.3.0" + vega-util "^1.17.1" vega-schema-url-parser@^2.2.0: version "2.2.0" @@ -8156,7 +8692,7 @@ vega-selections@^5.4.2: vega-expression "^5.0.1" vega-util "^1.17.1" -vega-statistics@^1.9.0, vega-statistics@~1.9.0: +vega-statistics@^1.8.1, vega-statistics@^1.9.0, vega-statistics@~1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/vega-statistics/-/vega-statistics-1.9.0.tgz#7d6139cea496b22d60decfa6abd73346f70206f9" integrity sha512-GAqS7mkatpXcMCQKWtFu1eMUKLUymjInU0O8kXshWaQrVWjPIO2lllZ1VNhdgE0qGj4oOIRRS11kzuijLshGXQ== @@ -8168,14 +8704,14 @@ vega-themes@^2.15.0: resolved "https://registry.yarnpkg.com/vega-themes/-/vega-themes-2.15.0.tgz#cf7592efb45406957e9beb67d7033ee5f7b7a511" integrity sha512-DicRAKG9z+23A+rH/3w3QjJvKnlGhSbbUXGjBvYGseZ1lvj9KQ0BXZ2NS/+MKns59LNpFNHGi9us/wMlci4TOA== -vega-time@^2.1.2, vega-time@~2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/vega-time/-/vega-time-2.1.2.tgz#0c414e74780613d6d3234fb97f19b50c0ebd9f49" - integrity sha512-6rXc6JdDt8MnCRy6UzUCsa6EeFycPDmvioMddLfKw38OYCV8pRQC5nw44gyddOwXgUTJLiCtn/sp53P0iA542A== +vega-time@^2.1.1, vega-time@~2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/vega-time/-/vega-time-2.1.1.tgz#0f1fb4e220dd5ed57401b58fb2293241f049ada0" + integrity sha512-z1qbgyX0Af2kQSGFbApwBbX2meenGvsoX8Nga8uyWN8VIbiySo/xqizz1KrP6NbB6R+x5egKmkjdnyNThPeEWA== dependencies: d3-array "^3.2.2" d3-time "^3.1.0" - vega-util "^1.17.2" + vega-util "^1.17.1" vega-tooltip@^0.34.0: version "0.34.0" @@ -8184,107 +8720,107 @@ vega-tooltip@^0.34.0: dependencies: vega-util "^1.17.2" -vega-transforms@~4.12.0: - version "4.12.0" - resolved "https://registry.yarnpkg.com/vega-transforms/-/vega-transforms-4.12.0.tgz#6a69e0b67934b0c0a40a6f607fdb543bf749955e" - integrity sha512-bh/2Qbj85O70mjfLRgPKAsABArgSUP0k+GjmaY54zukIRxoGxKju+85nigeX/aR/INpEqNWif+5lL+NvmyWA5w== +vega-transforms@~4.11.1: + version "4.11.1" + resolved "https://registry.yarnpkg.com/vega-transforms/-/vega-transforms-4.11.1.tgz#bc1291c49337eb465c3ead1ac0297cd8dd98d74a" + integrity sha512-DDbqEQnvy9/qEvv0bAKPqAuzgaNb7Lh2xKJFom2Yzx4tZHCl8dnKxC1lH9JnJlAMdtZuiNLPARUkf3pCNQ/olw== dependencies: d3-array "^3.2.2" - vega-dataflow "^5.7.6" - vega-statistics "^1.9.0" - vega-time "^2.1.2" - vega-util "^1.17.2" + vega-dataflow "^5.7.5" + vega-statistics "^1.8.1" + vega-time "^2.1.1" + vega-util "^1.17.1" -vega-typings@~1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/vega-typings/-/vega-typings-1.3.1.tgz#025a6031505794b44d9b6e2c49d4551b8918d4ae" - integrity sha512-j9Sdgmvowz09jkMgTFGVfiv7ycuRP/TQkdHRPXIYwt3RDgPQn7inyFcJ8C8ABFt4MiMWdjOwbneF6KWW8TRXIw== +vega-typings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/vega-typings/-/vega-typings-1.1.0.tgz#95bee43fff8a3c9cb921dd5aee2ea87c7f4ca58b" + integrity sha512-uI6RWlMiGRhsgmw/LzJtjCc0kwhw2f0JpyNMTAnOy90kE4e4CiaZN5nJp8S9CcfcBoPEZHc166AOn2SSNrKn3A== dependencies: "@types/geojson" "7946.0.4" vega-event-selector "^3.0.1" - vega-expression "^5.1.1" + vega-expression "^5.1.0" vega-util "^1.17.2" -vega-util@^1.17.1, vega-util@^1.17.2, vega-util@~1.17.2: +vega-util@^1.15.2, vega-util@^1.17.1, vega-util@^1.17.2, vega-util@~1.17.2: version "1.17.2" resolved "https://registry.yarnpkg.com/vega-util/-/vega-util-1.17.2.tgz#f69aa09fd5d6110c19c4a0f0af9e35945b99987d" integrity sha512-omNmGiZBdjm/jnHjZlywyYqafscDdHaELHx1q96n5UOz/FlO9JO99P4B3jZg391EFG8dqhWjQilSf2JH6F1mIw== -vega-view-transforms@~4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/vega-view-transforms/-/vega-view-transforms-4.6.0.tgz#829d56ca3c8116b0dded4ec0502f4ac70253de9a" - integrity sha512-z3z66aJTA3ZRo4oBY4iBXnn+A4KqBGZT/UrlKDbm+7Ec+Ip+hK2tF8Kmhp/WNcMsDZoUWFqLJgR2VgOgvJk9RA== +vega-view-transforms@~4.5.9: + version "4.5.9" + resolved "https://registry.yarnpkg.com/vega-view-transforms/-/vega-view-transforms-4.5.9.tgz#5f109555c08ee9ac23ff9183d578eb9cbac6fe61" + integrity sha512-NxEq4ZD4QwWGRrl2yDLnBRXM9FgCI+vvYb3ZC2+nVDtkUxOlEIKZsMMw31op5GZpfClWLbjCT3mVvzO2xaTF+g== dependencies: - vega-dataflow "^5.7.6" - vega-scenegraph "^4.13.0" - vega-util "^1.17.2" + vega-dataflow "^5.7.5" + vega-scenegraph "^4.10.2" + vega-util "^1.17.1" -vega-view@~5.13.0: - version "5.13.0" - resolved "https://registry.yarnpkg.com/vega-view/-/vega-view-5.13.0.tgz#8ea96da9fcdf42fe7c0e95fe6258933477524745" - integrity sha512-ZPAAQ3iYz6YrQjJoDT+0bcxJkXt9PKF5v4OO7Omw8PFhkIv++jFXeKlQTW1bBtyQ92dkdGGHv5lYY67Djqjf3A== +vega-view@~5.12.1: + version "5.12.1" + resolved "https://registry.yarnpkg.com/vega-view/-/vega-view-5.12.1.tgz#923f81eace6344b6157d64b7eea1b2a4324f1537" + integrity sha512-9TdF35FTZNzfvfj+YM38vHOgfeGxMy2xMY+2B46ZHoustt3J/mxtfueu3RGFsGIitUGhFrmLeEHxlVHP/tY+sQ== dependencies: d3-array "^3.2.2" d3-timer "^3.0.1" - vega-dataflow "^5.7.6" - vega-format "^1.1.2" - vega-functions "^5.15.0" - vega-runtime "^6.2.0" - vega-scenegraph "^4.13.0" - vega-util "^1.17.2" + vega-dataflow "^5.7.5" + vega-format "^1.1.1" + vega-functions "^5.13.1" + vega-runtime "^6.1.4" + vega-scenegraph "^4.10.2" + vega-util "^1.17.1" -vega-voronoi@~4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/vega-voronoi/-/vega-voronoi-4.2.3.tgz#54c4bb96b9b94c3fa0160bee24695dcb9d583fe1" - integrity sha512-aYYYM+3UGqwsOx+TkVtF1IZfguy0H7AN79dR8H0nONRIc+vhk/lbnlkgwY2nSzEu0EZ4b5wZxeGoDBEVmdDEcg== +vega-voronoi@~4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/vega-voronoi/-/vega-voronoi-4.2.2.tgz#f2068ddd01d184047c4f18bceb14dbf5edab2854" + integrity sha512-Bq2YOp2MGphhQnUuLwl3dsyBs6MuEU86muTjDbBJg33+HkZtE1kIoQZr+EUHa46NBsY1NzSKddOTu8wcaFrWiQ== dependencies: d3-delaunay "^6.0.2" - vega-dataflow "^5.7.6" - vega-util "^1.17.2" + vega-dataflow "^5.7.5" + vega-util "^1.17.1" -vega-wordcloud@~4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/vega-wordcloud/-/vega-wordcloud-4.1.5.tgz#789c9e67225c77f3f35a6fc052beec1c2bdc8b5e" - integrity sha512-p+qXU3cb9VeWzJ/HEdax0TX2mqDJcSbrCIfo2d/EalOXGkvfSLKobsmMQ8DxPbtVp0uhnpvfCGDyMJw+AzcI2A== +vega-wordcloud@~4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/vega-wordcloud/-/vega-wordcloud-4.1.4.tgz#38584cf47ef52325d6a8dc38908b5d2378cc6e62" + integrity sha512-oeZLlnjiusLAU5vhk0IIdT5QEiJE0x6cYoGNq1th+EbwgQp153t4r026fcib9oq15glHFOzf81a8hHXHSJm1Jw== dependencies: vega-canvas "^1.2.7" - vega-dataflow "^5.7.6" - vega-scale "^7.4.1" - vega-statistics "^1.9.0" - vega-util "^1.17.2" + vega-dataflow "^5.7.5" + vega-scale "^7.3.0" + vega-statistics "^1.8.1" + vega-util "^1.17.1" -vega@5.30.0: - version "5.30.0" - resolved "https://registry.yarnpkg.com/vega/-/vega-5.30.0.tgz#d12350c829878b481453ab28ce10855a954df06d" - integrity sha512-ZGoC8LdfEUV0LlXIuz7hup9jxuQYhSaWek2M7r9dEHAPbPrzSQvKXZ0BbsJbrarM100TGRpTVN/l1AFxCwDkWw== +vega@5.29.0: + version "5.29.0" + resolved "https://registry.yarnpkg.com/vega/-/vega-5.29.0.tgz#84b989cb258b74ee18e3a13ad82149bd9573b139" + integrity sha512-4+pX8UIxV1rtHpIKvzHXof5CeyMTGKMDFtuN8UmSjvJ+l5FtSen++qmSxbAc/EnkLqo5i9B2iCYTr2og77EBrA== dependencies: - vega-crossfilter "~4.1.2" - vega-dataflow "~5.7.6" - vega-encode "~4.10.1" + vega-crossfilter "~4.1.1" + vega-dataflow "~5.7.5" + vega-encode "~4.10.0" vega-event-selector "~3.0.1" - vega-expression "~5.1.1" - vega-force "~4.2.1" - vega-format "~1.1.2" - vega-functions "~5.15.0" - vega-geo "~4.4.2" - vega-hierarchy "~4.1.2" - vega-label "~1.3.0" - vega-loader "~4.5.2" - vega-parser "~6.4.0" - vega-projection "~1.6.1" - vega-regression "~1.3.0" - vega-runtime "~6.2.0" - vega-scale "~7.4.1" - vega-scenegraph "~4.13.0" + vega-expression "~5.1.0" + vega-force "~4.2.0" + vega-format "~1.1.1" + vega-functions "~5.14.0" + vega-geo "~4.4.1" + vega-hierarchy "~4.1.1" + vega-label "~1.2.1" + vega-loader "~4.5.1" + vega-parser "~6.3.0" + vega-projection "~1.6.0" + vega-regression "~1.2.0" + vega-runtime "~6.1.4" + vega-scale "~7.4.0" + vega-scenegraph "~4.12.0" vega-statistics "~1.9.0" - vega-time "~2.1.2" - vega-transforms "~4.12.0" - vega-typings "~1.3.1" + vega-time "~2.1.1" + vega-transforms "~4.11.1" + vega-typings "~1.1.0" vega-util "~1.17.2" - vega-view "~5.13.0" - vega-view-transforms "~4.6.0" - vega-voronoi "~4.2.3" - vega-wordcloud "~4.1.5" + vega-view "~5.12.1" + vega-view-transforms "~4.5.9" + vega-voronoi "~4.2.2" + vega-wordcloud "~4.1.4" wait-on@^7.2.0: version "7.2.0" @@ -8369,11 +8905,6 @@ windows-release@^5.0.1: dependencies: execa "^5.1.1" -word-wrap@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" - integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== - wordwrap@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" @@ -8508,9 +9039,9 @@ yocto-queue@^0.1.0: integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== yocto-queue@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.1.1.tgz#fef65ce3ac9f8a32ceac5a634f74e17e5b232110" - integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g== + version "1.0.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" + integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== yoctocolors-cjs@^2.1.1: version "2.1.2" From a99cf45827e7ddf25995af83d2cc17125bb099a9 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Wed, 31 Jul 2024 00:01:36 -0400 Subject: [PATCH 15/24] rebuild --- build/vega-lite-schema.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build/vega-lite-schema.json b/build/vega-lite-schema.json index 446bb1cd95..ca2e5d045d 100644 --- a/build/vega-lite-schema.json +++ b/build/vega-lite-schema.json @@ -4752,8 +4752,7 @@ "set2", "set3", "tableau10", - "tableau20", - "observable10" + "tableau20" ], "type": "string" }, From ca57633289e60e00f15858145d7efd3d09918450 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Wed, 31 Jul 2024 06:59:15 -0400 Subject: [PATCH 16/24] refactor: simplify with find --- src/util.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/util.ts b/src/util.ts index 650eb12595..67abdad13d 100644 --- a/src/util.ts +++ b/src/util.ts @@ -338,12 +338,7 @@ export function accessPathDepth(path: string) { * This is a replacement for chained || for numeric properties or properties that respect null so that 0 will be included. */ export function getFirstDefined(...args: readonly T[]): T | undefined { - for (const arg of args) { - if (arg !== undefined) { - return arg; - } - } - return undefined; + return args.find(a => a !== undefined); } // variable used to generate id From b2c513085a3139a36488d05b246f1d8c5968a80d Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Wed, 31 Jul 2024 07:21:36 -0400 Subject: [PATCH 17/24] fix logic --- src/compile/scale/domain.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compile/scale/domain.ts b/src/compile/scale/domain.ts index 8da2fdbe77..187f1e88cc 100644 --- a/src/compile/scale/domain.ts +++ b/src/compile/scale/domain.ts @@ -413,8 +413,8 @@ function parseSelectionDomain(model: UnitModel, channel: ScaleChannel) { const scale = model.component.scales[channel]; const spec = model.specifiedScales[channel].domain; const bin = model.fieldDef(channel)?.bin; - const domain = isParameterDomain(spec) && spec; - const extent = isBinParams(bin) && isParameterExtent(bin.extent) && bin.extent; + const domain = isParameterDomain(spec) ? spec : undefined; + const extent = isBinParams(bin) && isParameterExtent(bin.extent) ? bin.extent : undefined; if (domain || extent) { // As scale parsing occurs before selection parsing, we cannot set From d1adb2a5824b73469675d9a67e3225e7071a5543 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Wed, 31 Jul 2024 07:54:21 -0400 Subject: [PATCH 18/24] fix runtime tests --- src/channeldef.ts | 1 - test-runtime/interval.test.ts | 2 +- test-runtime/translate.test.ts | 2 +- test-runtime/zoom.test.ts | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/channeldef.ts b/src/channeldef.ts index b826afa724..3b95b834c8 100644 --- a/src/channeldef.ts +++ b/src/channeldef.ts @@ -691,7 +691,6 @@ export function hasConditionalValueDef( export function isFieldDef( channelDef: Partial> | FieldDefBase | DatumDef ): channelDef is FieldDefBase | TypedFieldDef | SecondaryFieldDef { - // TODO: we can't use field in channelDef here as it's somehow failing runtime test return hasKey(channelDef, 'field') || (channelDef as any)?.aggregate === 'count'; } diff --git a/test-runtime/interval.test.ts b/test-runtime/interval.test.ts index 2f1f4a0c54..6fae91edcc 100644 --- a/test-runtime/interval.test.ts +++ b/test-runtime/interval.test.ts @@ -90,7 +90,7 @@ describe('interval selections at runtime in unit views', () => { { x: {aggregate: 'count', type: 'quantitative'}, y: {bin: true}, - color: {value: 'steelblue', field: null, type: null} + color: {value: 'steelblue'} } ) ); diff --git a/test-runtime/translate.test.ts b/test-runtime/translate.test.ts index 6590ddd73d..de4dbdd227 100644 --- a/test-runtime/translate.test.ts +++ b/test-runtime/translate.test.ts @@ -77,7 +77,7 @@ describe('Translate interval selections at runtime', () => { { x: {aggregate: 'count', type: 'quantitative'}, y: {bin: true}, - color: {value: 'steelblue', field: null, type: null} + color: {value: 'steelblue'} } ) ); diff --git a/test-runtime/zoom.test.ts b/test-runtime/zoom.test.ts index 2cb45f022d..41e2cff521 100644 --- a/test-runtime/zoom.test.ts +++ b/test-runtime/zoom.test.ts @@ -102,7 +102,7 @@ describe('Zoom interval selections at runtime', () => { { x: {aggregate: 'count', type: 'quantitative'}, y: {bin: true}, - color: {value: 'steelblue', field: null, type: null} + color: {value: 'steelblue'} } ) ); From 70f8a415d3b52573363e59e53a9ff7b7fa646138 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Wed, 31 Jul 2024 08:15:39 -0400 Subject: [PATCH 19/24] use hasOwnProperty for index lookups --- src/channel.ts | 2 +- src/compile/mark/encode/aria.ts | 3 ++- src/compile/scale/domain.ts | 16 ++++++++-------- src/sort.ts | 4 ++-- src/stack.ts | 4 ++-- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/channel.ts b/src/channel.ts index 16eff8c7c6..78744c312b 100644 --- a/src/channel.ts +++ b/src/channel.ts @@ -400,7 +400,7 @@ export const POSITION_SCALE_CHANNELS = keys(POSITION_SCALE_CHANNEL_INDEX); export type PositionScaleChannel = keyof typeof POSITION_SCALE_CHANNEL_INDEX; export function isXorY(channel: ExtendedChannel): channel is PositionScaleChannel { - return channel in POSITION_SCALE_CHANNEL_INDEX; + return hasOwnProperty(POSITION_SCALE_CHANNEL_INDEX, channel); } export const POLAR_POSITION_SCALE_CHANNEL_INDEX = { diff --git a/src/compile/mark/encode/aria.ts b/src/compile/mark/encode/aria.ts index 8fb2c8db1e..561d3bf9ca 100644 --- a/src/compile/mark/encode/aria.ts +++ b/src/compile/mark/encode/aria.ts @@ -1,3 +1,4 @@ +import {hasOwnProperty} from 'vega-util'; import {entries, isEmpty} from '../../../util'; import {getMarkPropOrConfig, signalOrValueRef} from '../../common'; import {VG_MARK_INDEX} from './../../../vega.schema'; @@ -37,7 +38,7 @@ function ariaRoleDescription(model: UnitModel) { return {ariaRoleDescription: {value: ariaRoleDesc}}; } - return mark in VG_MARK_INDEX ? {} : {ariaRoleDescription: {value: mark}}; + return hasOwnProperty(VG_MARK_INDEX, mark) ? {} : {ariaRoleDescription: {value: mark}}; } export function description(model: UnitModel) { diff --git a/src/compile/scale/domain.ts b/src/compile/scale/domain.ts index 187f1e88cc..6743344611 100644 --- a/src/compile/scale/domain.ts +++ b/src/compile/scale/domain.ts @@ -1,13 +1,13 @@ import type {SignalRef} from 'vega'; -import {isObject, isString} from 'vega-util'; +import {hasOwnProperty, isObject, isString} from 'vega-util'; import { Aggregate, isAggregateOp, isArgmaxDef, isArgminDef, - MULTIDOMAIN_SORT_OP_INDEX as UNIONDOMAIN_SORT_OP_INDEX, NonArgAggregateOp, - SHARED_DOMAIN_OPS + SHARED_DOMAIN_OPS, + MULTIDOMAIN_SORT_OP_INDEX as UNIONDOMAIN_SORT_OP_INDEX } from '../../aggregate'; import {isBinning, isBinParams, isParameterExtent} from '../../bin'; import {getSecondaryRangeChannel, isScaleChannel, isXorY, ScaleChannel} from '../../channel'; @@ -29,6 +29,7 @@ import {DataSourceType} from '../../data'; import {DateTime} from '../../datetime'; import {ExprRef} from '../../expr'; import * as log from '../../log'; +import {isPathMark, isRectBasedMark} from '../../mark'; import {Domain, hasDiscreteDomain, isDomainUnionWith, isParameterDomain, ScaleConfig, ScaleType} from '../../scale'; import {ParameterExtent} from '../../selection'; import {DEFAULT_SORT_OP, EncodingSortField, isSortArray, isSortByEncoding, isSortField} from '../../sort'; @@ -47,18 +48,17 @@ import { VgSortField, VgUnionSortField } from '../../vega.schema'; +import {getMarkConfig} from '../common'; import {getBinSignalName} from '../data/bin'; import {sortArrayIndexField} from '../data/calculate'; import {FACET_SCALE_PREFIX} from '../data/optimize'; +import {OFFSETTED_RECT_END_SUFFIX, OFFSETTED_RECT_START_SUFFIX} from '../data/timeunit'; +import {getScaleDataSourceForHandlingInvalidValues} from '../invalid/datasources'; import {isFacetModel, isUnitModel, Model} from '../model'; import {SignalRefWrapper} from '../signal'; import {Explicit, makeExplicit, makeImplicit, mergeValuesWithExplicit} from '../split'; import {UnitModel} from '../unit'; import {ScaleComponent, ScaleComponentIndex} from './component'; -import {isPathMark, isRectBasedMark} from '../../mark'; -import {OFFSETTED_RECT_END_SUFFIX, OFFSETTED_RECT_START_SUFFIX} from '../data/timeunit'; -import {getScaleDataSourceForHandlingInvalidValues} from '../invalid/datasources'; -import {getMarkConfig} from '../common'; export function parseScaleDomain(model: Model) { if (isUnitModel(model)) { @@ -623,7 +623,7 @@ export function mergeDomains(domains: VgNonUnionDomain[]): VgDomain { // only keep sort properties that work with unioned domains const unionDomainSorts = util.unique( sorts.map(s => { - if (util.isBoolean(s) || !('op' in s) || (isString(s.op) && s.op in UNIONDOMAIN_SORT_OP_INDEX)) { + if (util.isBoolean(s) || !('op' in s) || (isString(s.op) && hasOwnProperty(UNIONDOMAIN_SORT_OP_INDEX, s.op))) { return s as VgUnionSortField; } log.warn(log.message.domainSortDropped(s)); diff --git a/src/sort.ts b/src/sort.ts index a60bea318f..89f978da2a 100644 --- a/src/sort.ts +++ b/src/sort.ts @@ -1,4 +1,4 @@ -import {isArray} from 'vega-util'; +import {hasOwnProperty, isArray} from 'vega-util'; import {NonArgAggregateOp} from './aggregate'; import {FieldName} from './channeldef'; import {DateTime} from './datetime'; @@ -88,7 +88,7 @@ const SORT_BY_CHANNEL_INDEX = { export type SortByChannel = keyof typeof SORT_BY_CHANNEL_INDEX; export function isSortByChannel(c: string): c is SortByChannel { - return c in SORT_BY_CHANNEL_INDEX; + return hasOwnProperty(SORT_BY_CHANNEL_INDEX, c); } export type SortByChannelDesc = diff --git a/src/stack.ts b/src/stack.ts index 8d68bfb1ae..7c595c00dd 100644 --- a/src/stack.ts +++ b/src/stack.ts @@ -1,4 +1,4 @@ -import {array, isBoolean} from 'vega-util'; +import {array, hasOwnProperty, isBoolean} from 'vega-util'; import {Aggregate, SUM_OPS} from './aggregate'; import {getSecondaryRangeChannel, NonPositionChannel, NONPOSITION_CHANNELS} from './channel'; import { @@ -43,7 +43,7 @@ const STACK_OFFSET_INDEX = { export type StackOffset = keyof typeof STACK_OFFSET_INDEX; export function isStackOffset(s: string): s is StackOffset { - return s in STACK_OFFSET_INDEX; + return hasOwnProperty(STACK_OFFSET_INDEX, s); } export interface StackProperties { From 9e6fc59f73a49398675d7ac214bb3757f05d1df0 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Wed, 31 Jul 2024 08:16:56 -0400 Subject: [PATCH 20/24] use undef instead of null --- test-runtime/interval.test.ts | 2 +- test-runtime/translate.test.ts | 2 +- test-runtime/zoom.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test-runtime/interval.test.ts b/test-runtime/interval.test.ts index 6fae91edcc..f0b4690ff0 100644 --- a/test-runtime/interval.test.ts +++ b/test-runtime/interval.test.ts @@ -90,7 +90,7 @@ describe('interval selections at runtime in unit views', () => { { x: {aggregate: 'count', type: 'quantitative'}, y: {bin: true}, - color: {value: 'steelblue'} + color: {value: 'steelblue', field: undefined, type: undefined} } ) ); diff --git a/test-runtime/translate.test.ts b/test-runtime/translate.test.ts index de4dbdd227..67a7cb6ce3 100644 --- a/test-runtime/translate.test.ts +++ b/test-runtime/translate.test.ts @@ -77,7 +77,7 @@ describe('Translate interval selections at runtime', () => { { x: {aggregate: 'count', type: 'quantitative'}, y: {bin: true}, - color: {value: 'steelblue'} + color: {value: 'steelblue', field: undefined, type: undefined} } ) ); diff --git a/test-runtime/zoom.test.ts b/test-runtime/zoom.test.ts index 41e2cff521..3695765977 100644 --- a/test-runtime/zoom.test.ts +++ b/test-runtime/zoom.test.ts @@ -102,7 +102,7 @@ describe('Zoom interval selections at runtime', () => { { x: {aggregate: 'count', type: 'quantitative'}, y: {bin: true}, - color: {value: 'steelblue'} + color: {value: 'steelblue', field: undefined, type: undefined} } ) ); From 0e516824405a7e3b4ee8725767f0cbcfac5c7180 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Wed, 31 Jul 2024 08:27:10 -0400 Subject: [PATCH 21/24] rename hasKey -> hasProperty --- src/aggregate.ts | 6 +- src/bin.ts | 4 +- src/channeldef.ts | 32 +- src/compile/common.ts | 6 +- src/compile/facet.ts | 4 +- src/compile/legend/encode.ts | 6 +- src/compile/mark/encode/base.ts | 4 +- src/compile/scale/properties.ts | 2 +- src/config.ts | 4 +- src/data.ts | 14 +- src/datetime.ts | 4 +- src/expr.ts | 4 +- src/logical.ts | 8 +- src/mark.ts | 6 +- src/normalize/repeater.ts | 4 +- src/predicate.ts | 4 +- src/scale.ts | 8 +- src/sort.ts | 6 +- src/spec/base.ts | 6 +- src/spec/concat.ts | 8 +- src/spec/facet.ts | 8 +- src/spec/layer.ts | 4 +- src/spec/repeat.ts | 6 +- src/spec/unit.ts | 4 +- src/transform.ts | 46 +- src/util.ts | 11 +- src/vega.schema.ts | 12 +- test/example.test.ts | 22 + vega-lite.js | 23021 ++++++++++++++++++++++++++++++ 29 files changed, 23157 insertions(+), 117 deletions(-) create mode 100644 test/example.test.ts create mode 100644 vega-lite.js diff --git a/src/aggregate.ts b/src/aggregate.ts index caedc16ff3..196fe3826b 100644 --- a/src/aggregate.ts +++ b/src/aggregate.ts @@ -1,7 +1,7 @@ import type {AggregateOp} from 'vega'; import {hasOwnProperty, isString} from 'vega-util'; import {FieldName} from './channeldef'; -import {contains, Flag, hasKey} from './util'; +import {contains, Flag, hasProperty} from './util'; const AGGREGATE_OP_INDEX: Flag = { argmax: 1, @@ -50,11 +50,11 @@ export type NonArgAggregateOp = Exclude; export type Aggregate = NonArgAggregateOp | ArgmaxDef | ArgminDef; export function isArgminDef(a: Aggregate | string): a is ArgminDef { - return hasKey(a, 'argmin'); + return hasProperty(a, 'argmin'); } export function isArgmaxDef(a: Aggregate | string): a is ArgmaxDef { - return hasKey(a, 'argmax'); + return hasProperty(a, 'argmax'); } export function isAggregateOp(a: string | ArgminDef | ArgmaxDef): a is AggregateOp { diff --git a/src/bin.ts b/src/bin.ts index e94230b31b..0d233fc136 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -16,7 +16,7 @@ import { } from './channel'; import {normalizeBin} from './channeldef'; import {ParameterExtent} from './selection'; -import {entries, hasKey, keys, varName} from './util'; +import {entries, hasProperty, keys, varName} from './util'; export interface BaseBin { /** @@ -124,7 +124,7 @@ export function isBinParams(bin: BinParams | boolean | 'binned'): bin is BinPara } export function isParameterExtent(extent: unknown): extent is ParameterExtent { - return hasKey(extent, 'param'); + return hasProperty(extent, 'param'); } export function autoMaxBins(channel?: ExtendedChannel): number { diff --git a/src/channeldef.ts b/src/channeldef.ts index 3b95b834c8..d1110ac101 100644 --- a/src/channeldef.ts +++ b/src/channeldef.ts @@ -83,7 +83,7 @@ import { Dict, flatAccessWithDatum, getFirstDefined, - hasKey, + hasProperty, internalField, omit, removePathFromField, @@ -158,7 +158,7 @@ export type ConditionalPredicate = { export type ConditionalParameter = ParameterPredicate & CD; export function isConditionalParameter(c: Conditional): c is ConditionalParameter { - return hasKey(c, 'param'); + return hasProperty(c, 'param'); } export interface ConditionValueDefMixins { @@ -220,7 +220,7 @@ export type FieldName = string; export type Field = FieldName | RepeatRef; export function isRepeatRef(field: Field | any): field is RepeatRef { - return !isString(field) && hasKey(field, 'repeat'); + return !isString(field) && hasProperty(field, 'repeat'); } /** @@hidden */ @@ -357,7 +357,7 @@ export interface SortableFieldDef< } export function isSortableFieldDef(fieldDef: FieldDef): fieldDef is SortableFieldDef { - return hasKey(fieldDef, 'sort'); + return hasProperty(fieldDef, 'sort'); } export type ScaleFieldDef< @@ -648,7 +648,7 @@ export interface OrderOnlyDef { export function isOrderOnlyDef( orderDef: OrderFieldDef | OrderFieldDef[] | OrderValueDef | OrderOnlyDef ): orderDef is OrderOnlyDef { - return hasKey(orderDef, 'sort') && !hasKey(orderDef, 'field'); + return hasProperty(orderDef, 'sort') && !hasProperty(orderDef, 'field'); } export type OrderValueDef = ConditionValueDefMixins & NumericValueDef; @@ -661,7 +661,7 @@ export type ChannelDef = Encoding[keyof Encoding export function isConditionalDef | GuideEncodingConditionalValueDef | ExprRef | SignalRef>( channelDef: CD ): channelDef is CD & {condition: Conditional} { - return hasKey(channelDef, 'condition'); + return hasProperty(channelDef, 'condition'); } /** @@ -691,7 +691,7 @@ export function hasConditionalValueDef( export function isFieldDef( channelDef: Partial> | FieldDefBase | DatumDef ): channelDef is FieldDefBase | TypedFieldDef | SecondaryFieldDef { - return hasKey(channelDef, 'field') || (channelDef as any)?.aggregate === 'count'; + return hasProperty(channelDef, 'field') || (channelDef as any)?.aggregate === 'count'; } export function channelDefType(channelDef: ChannelDef): Type | undefined { @@ -701,7 +701,7 @@ export function channelDefType(channelDef: ChannelDef): Type export function isDatumDef( channelDef: Partial> | FieldDefBase | DatumDef ): channelDef is DatumDef { - return hasKey(channelDef, 'datum'); + return hasProperty(channelDef, 'datum'); } export function isContinuousFieldOrDatumDef( @@ -729,35 +729,35 @@ export function isFieldOrDatumDef( export function isTypedFieldDef(channelDef: ChannelDef): channelDef is TypedFieldDef { return ( channelDef && - (hasKey(channelDef, 'field') || (channelDef as any)['aggregate'] === 'count') && - hasKey(channelDef, 'type') + (hasProperty(channelDef, 'field') || (channelDef as any)['aggregate'] === 'count') && + hasProperty(channelDef, 'type') ); } export function isValueDef(channelDef: Partial>): channelDef is ValueDef { - return hasKey(channelDef, 'value'); + return hasProperty(channelDef, 'value'); } export function isScaleFieldDef(channelDef: ChannelDef): channelDef is ScaleFieldDef { - return hasKey(channelDef, 'scale') || hasKey(channelDef, 'sort'); + return hasProperty(channelDef, 'scale') || hasProperty(channelDef, 'sort'); } export function isPositionFieldOrDatumDef( channelDef: ChannelDef ): channelDef is PositionFieldDef | PositionDatumDef { - return hasKey(channelDef, 'axis') || hasKey(channelDef, 'stack') || hasKey(channelDef, 'impute'); + return hasProperty(channelDef, 'axis') || hasProperty(channelDef, 'stack') || hasProperty(channelDef, 'impute'); } export function isMarkPropFieldOrDatumDef( channelDef: ChannelDef ): channelDef is MarkPropFieldDef | MarkPropDatumDef { - return hasKey(channelDef, 'legend'); + return hasProperty(channelDef, 'legend'); } export function isStringFieldOrDatumDef( channelDef: ChannelDef ): channelDef is StringFieldDef | StringDatumDef { - return hasKey(channelDef, 'format') || hasKey(channelDef, 'formatType'); + return hasProperty(channelDef, 'format') || hasProperty(channelDef, 'formatType'); } export function toStringFieldDef(fieldDef: FieldDef): StringFieldDef { @@ -786,7 +786,7 @@ export interface FieldRefOption { function isOpFieldDef( fieldDef: FieldDefBase | WindowFieldDef | AggregatedFieldDef ): fieldDef is WindowFieldDef | AggregatedFieldDef { - return hasKey(fieldDef, 'op'); + return hasProperty(fieldDef, 'op'); } /** diff --git a/src/compile/common.ts b/src/compile/common.ts index 24e3e0f78f..1c733bcf11 100644 --- a/src/compile/common.ts +++ b/src/compile/common.ts @@ -17,7 +17,7 @@ import {isExprRef} from '../expr'; import {Mark, MarkConfig, MarkDef} from '../mark'; import {SortFields} from '../sort'; import {isText} from '../title'; -import {deepEqual, getFirstDefined, hasKey} from '../util'; +import {deepEqual, getFirstDefined, hasProperty} from '../util'; import {isSignalRef, VgEncodeChannel, VgEncodeEntry, VgValueRef} from '../vega.schema'; import {AxisComponentProps} from './axis/component'; import {Explicit} from './split'; @@ -111,7 +111,7 @@ export function getMarkPropOrConfig

[P] { const {vgChannel, ignoreVgConfig} = opt; - if (vgChannel && hasKey(mark, vgChannel)) { + if (vgChannel && hasProperty(mark, vgChannel)) { return mark[vgChannel] as any; } else if (mark[channel] !== undefined) { return mark[channel]; @@ -166,7 +166,7 @@ export function getStyleConfig

{ diff --git a/src/compile/legend/encode.ts b/src/compile/legend/encode.ts index b83e5dd336..2ab32268ff 100644 --- a/src/compile/legend/encode.ts +++ b/src/compile/legend/encode.ts @@ -13,7 +13,7 @@ import { } from '../../channeldef'; import {Encoding} from '../../encoding'; import {FILL_STROKE_CONFIG} from '../../mark'; -import {getFirstDefined, hasKey, isEmpty, varName} from '../../util'; +import {getFirstDefined, hasProperty, isEmpty, varName} from '../../util'; import {applyMarkConfig, signalOrValueRef} from '../common'; import {formatCustomType, isCustomFormatType} from '../format'; import * as mixins from '../mark/encode'; @@ -64,7 +64,7 @@ export function symbols( // for fill legend, we don't want any fill in symbol if (channel === 'fill' || (filled && channel === COLOR)) { delete out.fill; - } else if (hasKey(out.fill, 'field')) { + } else if (hasProperty(out.fill, 'field')) { // For others, set fill to some opaque value (or nothing if a color is already set) if (symbolFillColor) { delete out.fill; @@ -83,7 +83,7 @@ export function symbols( if (out.stroke) { if (channel === 'stroke' || (!filled && channel === COLOR)) { delete out.stroke; - } else if (hasKey(out.stroke, 'field') || symbolStrokeColor) { + } else if (hasProperty(out.stroke, 'field') || symbolStrokeColor) { // For others, remove stroke field delete out.stroke; } else if (isArray(out.stroke)) { diff --git a/src/compile/mark/encode/base.ts b/src/compile/mark/encode/base.ts index 37a961dc2f..28f0af867e 100644 --- a/src/compile/mark/encode/base.ts +++ b/src/compile/mark/encode/base.ts @@ -1,6 +1,6 @@ import type {MarkConfig} from 'vega'; import {MarkDef} from '../../../mark'; -import {hasKey} from '../../../util'; +import {hasProperty} from '../../../util'; import {VG_MARK_CONFIGS, VgEncodeEntry, VgValueRef} from '../../../vega.schema'; import {signalOrValueRef} from '../../common'; import {UnitModel} from '../../unit'; @@ -48,7 +48,7 @@ function colorRef(channel: 'fill' | 'stroke', valueRef: VgValueRef | VgValueRef[ function markDefProperties(mark: MarkDef, ignore: Ignore) { return VG_MARK_CONFIGS.reduce( (m, prop) => { - if (!ALWAYS_IGNORE.has(prop) && hasKey(mark, prop) && (ignore as any)[prop] !== 'ignore') { + if (!ALWAYS_IGNORE.has(prop) && hasProperty(mark, prop) && (ignore as any)[prop] !== 'ignore') { m[prop] = signalOrValueRef(mark[prop]); } return m; diff --git a/src/compile/scale/properties.ts b/src/compile/scale/properties.ts index 500f59e4e5..37522300e1 100644 --- a/src/compile/scale/properties.ts +++ b/src/compile/scale/properties.ts @@ -107,7 +107,7 @@ function parseUnitScaleProperty(model: UnitModel, property: Exclude extends BaseViewBackground { /** @@ -100,7 +100,7 @@ export const defaultViewConfig: ViewConfig = { }; export function isVgScheme(rangeScheme: string[] | RangeScheme): rangeScheme is RangeScheme { - return hasKey(rangeScheme, 'scheme'); + return hasProperty(rangeScheme, 'scheme'); } export type ColorConfig = Record; diff --git a/src/data.ts b/src/data.ts index 4e38462de6..ca5e535151 100644 --- a/src/data.ts +++ b/src/data.ts @@ -4,7 +4,7 @@ import {Vector2} from 'vega'; import {FieldName} from './channeldef'; import {VgData} from './vega.schema'; -import {hasKey} from './util'; +import {hasProperty} from './util'; export type ParseValue = null | string | 'string' | 'boolean' | 'date' | 'number'; @@ -124,15 +124,15 @@ export interface NamedData extends DataBase { } export function isUrlData(data: Partial | Partial): data is UrlData { - return hasKey(data, 'url'); + return hasProperty(data, 'url'); } export function isInlineData(data: Partial | Partial): data is InlineData { - return hasKey(data, 'values'); + return hasProperty(data, 'values'); } export function isNamedData(data: Partial | Partial): data is NamedData { - return hasKey(data, 'name') && !isUrlData(data) && !isInlineData(data) && !isGenerator(data); + return hasProperty(data, 'name') && !isUrlData(data) && !isInlineData(data) && !isGenerator(data); } export function isGenerator(data: Partial | Partial): data is Generator { @@ -140,15 +140,15 @@ export function isGenerator(data: Partial | Partial): data is Gene } export function isSequenceGenerator(data: Partial | Partial): data is SequenceGenerator { - return hasKey(data, 'sequence'); + return hasProperty(data, 'sequence'); } export function isSphereGenerator(data: Partial | Partial): data is SphereGenerator { - return hasKey(data, 'sphere'); + return hasProperty(data, 'sphere'); } export function isGraticuleGenerator(data: Partial | Partial): data is GraticuleGenerator { - return hasKey(data, 'graticule'); + return hasProperty(data, 'graticule'); } export enum DataSourceType { diff --git a/src/datetime.ts b/src/datetime.ts index 5d3498eeb2..0f566f0769 100644 --- a/src/datetime.ts +++ b/src/datetime.ts @@ -3,7 +3,7 @@ import {isNumber, isObject} from 'vega-util'; import * as log from './log'; import {TIMEUNIT_PARTS} from './timeunit'; -import {duplicate, hasKey, isNumeric, keys} from './util'; +import {duplicate, hasProperty, isNumeric, keys} from './util'; /** * @minimum 1 @@ -125,7 +125,7 @@ export interface DateTimeExpr { export function isDateTime(o: any): o is DateTime { if (o && isObject(o)) { for (const part of TIMEUNIT_PARTS) { - if (hasKey(o, part)) { + if (hasProperty(o, part)) { return true; } } diff --git a/src/expr.ts b/src/expr.ts index c28352f89f..cfb0f05ef9 100644 --- a/src/expr.ts +++ b/src/expr.ts @@ -1,5 +1,5 @@ import {signalRefOrValue} from './compile/common'; -import {Dict, hasKey, keys} from './util'; +import {Dict, hasProperty, keys} from './util'; import {MappedExclude} from './vega.schema'; export interface ExprRef { @@ -10,7 +10,7 @@ export interface ExprRef { } export function isExprRef(o: any): o is ExprRef { - return hasKey(o, 'expr'); + return hasProperty(o, 'expr'); } export function replaceExprRef>(index: T, {level}: {level: number} = {level: 0}) { diff --git a/src/logical.ts b/src/logical.ts index a51055efbe..ce889761a9 100644 --- a/src/logical.ts +++ b/src/logical.ts @@ -1,4 +1,4 @@ -import {hasKey} from './util'; +import {hasProperty} from './util'; export type LogicalComposition = LogicalNot | LogicalAnd | LogicalOr | T; @@ -15,15 +15,15 @@ export interface LogicalNot { } export function isLogicalOr(op: LogicalComposition): op is LogicalOr { - return hasKey(op, 'or'); + return hasProperty(op, 'or'); } export function isLogicalAnd(op: LogicalComposition): op is LogicalAnd { - return hasKey(op, 'and'); + return hasProperty(op, 'and'); } export function isLogicalNot(op: LogicalComposition): op is LogicalNot { - return hasKey(op, 'not'); + return hasProperty(op, 'not'); } export function forEachLeaf(op: LogicalComposition, fn: (op: T) => void) { diff --git a/src/mark.ts b/src/mark.ts index 2b1b25486a..46a4cc23e5 100644 --- a/src/mark.ts +++ b/src/mark.ts @@ -3,7 +3,7 @@ import {hasOwnProperty} from 'vega-util'; import {CompositeMark, CompositeMarkDef} from './compositemark'; import {ExprRef} from './expr'; import {MarkInvalidMixins} from './invalid'; -import {Flag, hasKey, keys} from './util'; +import {Flag, hasProperty, keys} from './util'; import {MapExcludeValueRefAndReplaceSignalWith} from './vega.schema'; /** @@ -292,7 +292,7 @@ export interface RectBinSpacingMixins { export type AnyMark = CompositeMark | CompositeMarkDef | Mark | MarkDef; export function isMarkDef(mark: string | GenericMarkDef): mark is GenericMarkDef { - return hasKey(mark, 'type'); + return hasProperty(mark, 'type'); } export function isPrimitiveMark(mark: AnyMark): mark is Mark { @@ -454,7 +454,7 @@ export interface RelativeBandSize { } export function isRelativeBandSize(o: number | RelativeBandSize | ExprRef | SignalRef): o is RelativeBandSize { - return hasKey(o, 'band'); + return hasProperty(o, 'band'); } export const BAR_CORNER_RADIUS_INDEX: Partial< diff --git a/src/normalize/repeater.ts b/src/normalize/repeater.ts index a7a4b46181..4c46378d8d 100644 --- a/src/normalize/repeater.ts +++ b/src/normalize/repeater.ts @@ -18,7 +18,7 @@ import {Encoding} from '../encoding'; import * as log from '../log'; import {isSortField} from '../sort'; import {FacetFieldDef, FacetMapping, isFacetMapping} from '../spec/facet'; -import {hasKey} from '../util'; +import {hasProperty} from '../util'; export interface RepeaterValue { row?: string; @@ -141,7 +141,7 @@ function replaceRepeaterInMapping( ): EncodingOrFacet { const out: EncodingOrFacet = {}; for (const channel in mapping) { - if (hasKey(mapping, channel)) { + if (hasProperty(mapping, channel)) { const channelDef: ChannelDef | ChannelDef[] = mapping[channel]; if (isArray(channelDef)) { diff --git a/src/predicate.ts b/src/predicate.ts index 64e2a55e21..90d9178449 100644 --- a/src/predicate.ts +++ b/src/predicate.ts @@ -6,7 +6,7 @@ import {ExprRef, replaceExprRef} from './expr'; import {LogicalComposition} from './logical'; import {ParameterName} from './parameter'; import {fieldExpr as timeUnitFieldExpr, normalizeTimeUnit, TimeUnit, TimeUnitParams, BinnedTimeUnit} from './timeunit'; -import {hasKey, stringify} from './util'; +import {hasProperty, stringify} from './util'; import {isSignalRef} from './vega.schema'; export type Predicate = @@ -48,7 +48,7 @@ export interface ParameterPredicate { } export function isSelectionPredicate(predicate: LogicalComposition): predicate is ParameterPredicate { - return hasKey(predicate, 'param'); + return hasProperty(predicate, 'param'); } export interface FieldPredicateBase { diff --git a/src/scale.ts b/src/scale.ts index dd052c04de..1d77095e77 100644 --- a/src/scale.ts +++ b/src/scale.ts @@ -17,7 +17,7 @@ import {ScaleInvalidDataConfigMixins} from './invalid'; import * as log from './log'; import {ParameterExtent} from './selection'; import {NOMINAL, ORDINAL, QUANTITATIVE, TEMPORAL, Type} from './type'; -import {contains, Flag, hasKey, keys} from './util'; +import {contains, Flag, hasProperty, keys} from './util'; export const ScaleType = { // Continuous - Quantitative @@ -494,11 +494,11 @@ export type Domain = export type Scheme = string | SchemeParams; export function isExtendedScheme(scheme: Scheme | SignalRef): scheme is SchemeParams { - return !isString(scheme) && hasKey(scheme, 'name'); + return !isString(scheme) && hasProperty(scheme, 'name'); } export function isParameterDomain(domain: Domain): domain is ParameterExtent { - return hasKey(domain, 'param'); + return hasProperty(domain, 'param'); } export interface DomainUnionWith { @@ -510,7 +510,7 @@ export interface DomainUnionWith { } export function isDomainUnionWith(domain: Domain): domain is DomainUnionWith { - return hasKey(domain, 'unionWith'); + return hasProperty(domain, 'unionWith'); } export interface FieldRange { diff --git a/src/sort.ts b/src/sort.ts index 89f978da2a..e0fed275b5 100644 --- a/src/sort.ts +++ b/src/sort.ts @@ -2,7 +2,7 @@ import {hasOwnProperty, isArray} from 'vega-util'; import {NonArgAggregateOp} from './aggregate'; import {FieldName} from './channeldef'; import {DateTime} from './datetime'; -import {hasKey} from './util'; +import {hasProperty} from './util'; export type SortOrder = 'ascending' | 'descending'; @@ -110,11 +110,11 @@ export type AllSortString = SortOrder | SortByChannel | SortByChannelDesc; export type Sort = SortArray | AllSortString | EncodingSortField | SortByEncoding | null; export function isSortByEncoding(sort: Sort): sort is SortByEncoding { - return hasKey(sort, 'encoding'); + return hasProperty(sort, 'encoding'); } export function isSortField(sort: Sort): sort is EncodingSortField { - return sort && ((sort as any).op === 'count' || hasKey(sort, 'field')); + return sort && ((sort as any).op === 'count' || hasProperty(sort, 'field')); } export function isSortArray(sort: Sort): sort is SortArray { diff --git a/src/spec/base.ts b/src/spec/base.ts index 35505386e4..862fb64a35 100644 --- a/src/spec/base.ts +++ b/src/spec/base.ts @@ -7,7 +7,7 @@ import {MarkConfig} from '../mark'; import {Resolve} from '../resolve'; import {TitleParams} from '../title'; import {Transform} from '../transform'; -import {Flag, hasKey, keys} from '../util'; +import {Flag, hasProperty, keys} from '../util'; import {LayoutAlign, RowCol} from '../vega.schema'; import {isConcatSpec, isVConcatSpec} from './concat'; import {isFacetMapping, isFacetSpec} from './facet'; @@ -74,7 +74,7 @@ export function getStepFor({step, offsetIsDiscrete}: {step: Step; offsetIsDiscre } export function isStep(size: number | Step | 'container' | 'merged'): size is Step { - return hasKey(size, 'step'); + return hasProperty(size, 'step'); } // TODO(https://github.com/vega/vega-lite/issues/2503): Make this generic so we can support some form of top-down sizing. @@ -115,7 +115,7 @@ export interface LayoutSizeMixins { } export function isFrameMixins(o: any): o is FrameMixins { - return hasKey(o, 'view') || hasKey(o, 'width') || hasKey(o, 'height'); + return hasProperty(o, 'view') || hasProperty(o, 'width') || hasProperty(o, 'height'); } export interface FrameMixins extends LayoutSizeMixins { diff --git a/src/spec/concat.ts b/src/spec/concat.ts index 538f33b5e7..a56309cea4 100644 --- a/src/spec/concat.ts +++ b/src/spec/concat.ts @@ -1,5 +1,5 @@ import {GenericSpec, NormalizedSpec} from '.'; -import {hasKey} from '../util'; +import {hasProperty} from '../util'; import {BaseSpec, BoundsMixins, GenericCompositionLayoutWithColumns, ResolveMixins} from './base'; /** @@ -69,13 +69,13 @@ export function isAnyConcatSpec(spec: BaseSpec): spec is GenericVConcatSpec } export function isConcatSpec(spec: BaseSpec): spec is GenericConcatSpec { - return hasKey(spec, 'concat'); + return hasProperty(spec, 'concat'); } export function isVConcatSpec(spec: BaseSpec): spec is GenericVConcatSpec { - return hasKey(spec, 'vconcat'); + return hasProperty(spec, 'vconcat'); } export function isHConcatSpec(spec: BaseSpec): spec is GenericHConcatSpec { - return hasKey(spec, 'hconcat'); + return hasProperty(spec, 'hconcat'); } diff --git a/src/spec/facet.ts b/src/spec/facet.ts index aa99ec5e18..07bd300f26 100644 --- a/src/spec/facet.ts +++ b/src/spec/facet.ts @@ -8,7 +8,7 @@ import {StandardType} from '../type'; import {BaseSpec, GenericCompositionLayoutWithColumns, ResolveMixins} from './base'; import {GenericLayerSpec, NormalizedLayerSpec} from './layer'; import {GenericUnitSpec, NormalizedUnitSpec} from './unit'; -import {hasKey} from '../util'; +import {hasProperty} from '../util'; export interface FacetFieldDef extends TypedFieldDef { @@ -91,7 +91,7 @@ export interface FacetMapping< export function isFacetMapping( f: FacetFieldDef | FacetMapping ): f is FacetMapping { - return hasKey(f, 'row') || hasKey(f, 'column'); + return hasProperty(f, 'row') || hasProperty(f, 'column'); } /** @@ -108,7 +108,7 @@ export interface EncodingFacetMapping(channelDef: ChannelDef): channelDef is FacetFieldDef { - return hasKey(channelDef, 'header'); + return hasProperty(channelDef, 'header'); } /** @@ -138,5 +138,5 @@ export interface GenericFacetSpec, L extends export type NormalizedFacetSpec = GenericFacetSpec; export function isFacetSpec(spec: BaseSpec): spec is GenericFacetSpec { - return hasKey(spec, 'facet'); + return hasProperty(spec, 'facet'); } diff --git a/src/spec/layer.ts b/src/spec/layer.ts index 0c2f395563..5f9da419c5 100644 --- a/src/spec/layer.ts +++ b/src/spec/layer.ts @@ -2,7 +2,7 @@ import {Field} from '../channeldef'; import {SharedCompositeEncoding} from '../compositemark'; import {ExprRef} from '../expr'; import {Projection} from '../projection'; -import {hasKey} from '../util'; +import {hasProperty} from '../util'; import {BaseSpec, FrameMixins, ResolveMixins} from './base'; import {GenericUnitSpec, NormalizedUnitSpec, UnitSpec} from './unit'; @@ -46,5 +46,5 @@ export interface LayerSpec extends BaseSpec, FrameMixins, Resol export type NormalizedLayerSpec = GenericLayerSpec; export function isLayerSpec(spec: BaseSpec): spec is GenericLayerSpec { - return hasKey(spec, 'layer'); + return hasProperty(spec, 'layer'); } diff --git a/src/spec/repeat.ts b/src/spec/repeat.ts index 60bc04e62a..9752c047d4 100644 --- a/src/spec/repeat.ts +++ b/src/spec/repeat.ts @@ -3,7 +3,7 @@ import {LayerSpec, NonNormalizedSpec} from '.'; import {Field} from '../channeldef'; import {BaseSpec, GenericCompositionLayoutWithColumns, ResolveMixins} from './base'; import {UnitSpecWithFrame} from './unit'; -import {hasKey} from '../util'; +import {hasProperty} from '../util'; export interface RepeatMapping { /** @@ -58,9 +58,9 @@ export interface LayerRepeatSpec extends BaseSpec, GenericCompositionLayoutWithC } export function isRepeatSpec(spec: BaseSpec): spec is RepeatSpec { - return hasKey(spec, 'repeat'); + return hasProperty(spec, 'repeat'); } export function isLayerRepeatSpec(spec: RepeatSpec): spec is LayerRepeatSpec { - return !isArray(spec.repeat) && hasKey(spec.repeat, 'layer'); + return !isArray(spec.repeat) && hasProperty(spec.repeat, 'layer'); } diff --git a/src/spec/unit.ts b/src/spec/unit.ts index 1051a6c787..5d910e214b 100644 --- a/src/spec/unit.ts +++ b/src/spec/unit.ts @@ -5,7 +5,7 @@ import {ExprRef} from '../expr'; import {AnyMark, Mark, MarkDef} from '../mark'; import {Projection} from '../projection'; import {SelectionParameter} from '../selection'; -import {hasKey} from '../util'; +import {hasProperty} from '../util'; import {Field} from './../channeldef'; import {BaseSpec, DataMixins, FrameMixins, GenericCompositionLayout, ResolveMixins} from './base'; import {TopLevel, TopLevelParameter} from './toplevel'; @@ -63,5 +63,5 @@ export type FacetedUnitSpec = GenericUn export type TopLevelUnitSpec = TopLevel> & DataMixins; export function isUnitSpec(spec: BaseSpec): spec is FacetedUnitSpec | NormalizedUnitSpec { - return hasKey(spec, 'mark'); + return hasProperty(spec, 'mark'); } diff --git a/src/transform.ts b/src/transform.ts index 7589548daf..3af3dd6ae9 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -8,7 +8,7 @@ import {ParameterName} from './parameter'; import {normalizePredicate, Predicate} from './predicate'; import {SortField} from './sort'; import {TimeUnit, TimeUnitTransformParams} from './timeunit'; -import {hasKey} from './util'; +import {hasProperty} from './util'; export interface FilterTransform { /** @@ -36,7 +36,7 @@ export interface FilterTransform { } export function isFilter(t: Transform): t is FilterTransform { - return hasKey(t, 'filter'); + return hasProperty(t, 'filter'); } export interface CalculateTransform { @@ -260,7 +260,7 @@ export interface ImputeSequence { } export function isImputeSequence(t: ImputeSequence | any[] | undefined): t is ImputeSequence { - return hasKey(t, 'stop'); + return hasProperty(t, 'stop'); } export interface ImputeTransform extends ImputeParams { @@ -367,15 +367,15 @@ export interface LookupTransform { } export function isLookup(t: Transform): t is LookupTransform { - return hasKey(t, 'lookup'); + return hasProperty(t, 'lookup'); } export function isLookupData(from: LookupData | LookupSelection): from is LookupData { - return hasKey(from, 'data'); + return hasProperty(from, 'data'); } export function isLookupSelection(from: LookupData | LookupSelection): from is LookupSelection { - return hasKey(from, 'param'); + return hasProperty(from, 'param'); } export interface FoldTransform { @@ -434,7 +434,7 @@ export interface PivotTransform { } export function isPivot(t: Transform): t is PivotTransform { - return hasKey(t, 'pivot'); + return hasProperty(t, 'pivot'); } export interface DensityTransform { @@ -508,7 +508,7 @@ export interface DensityTransform { } export function isDensity(t: Transform): t is DensityTransform { - return hasKey(t, 'density'); + return hasProperty(t, 'density'); } export interface QuantileTransform { @@ -541,7 +541,7 @@ export interface QuantileTransform { } export function isQuantile(t: Transform): t is QuantileTransform { - return hasKey(t, 'quantile'); + return hasProperty(t, 'quantile'); } export interface RegressionTransform { @@ -597,7 +597,7 @@ export interface RegressionTransform { } export function isRegression(t: Transform): t is RegressionTransform { - return hasKey(t, 'regression'); + return hasProperty(t, 'regression'); } export interface LoessTransform { @@ -632,54 +632,54 @@ export interface LoessTransform { } export function isLoess(t: Transform): t is LoessTransform { - return hasKey(t, 'loess'); + return hasProperty(t, 'loess'); } export function isSample(t: Transform): t is SampleTransform { - return hasKey(t, 'sample'); + return hasProperty(t, 'sample'); } export function isWindow(t: Transform): t is WindowTransform { - return hasKey(t, 'window'); + return hasProperty(t, 'window'); } export function isJoinAggregate(t: Transform): t is JoinAggregateTransform { - return hasKey(t, 'joinaggregate'); + return hasProperty(t, 'joinaggregate'); } export function isFlatten(t: Transform): t is FlattenTransform { - return hasKey(t, 'flatten'); + return hasProperty(t, 'flatten'); } export function isCalculate(t: Transform): t is CalculateTransform { - return hasKey(t, 'calculate'); + return hasProperty(t, 'calculate'); } export function isBin(t: Transform): t is BinTransform { - return hasKey(t, 'bin'); + return hasProperty(t, 'bin'); } export function isImpute(t: Transform): t is ImputeTransform { - return hasKey(t, 'impute'); + return hasProperty(t, 'impute'); } export function isTimeUnit(t: Transform): t is TimeUnitTransform { - return hasKey(t, 'timeUnit'); + return hasProperty(t, 'timeUnit'); } export function isAggregate(t: Transform): t is AggregateTransform { - return hasKey(t, 'aggregate'); + return hasProperty(t, 'aggregate'); } export function isStack(t: Transform): t is StackTransform { - return hasKey(t, 'stack'); + return hasProperty(t, 'stack'); } export function isFold(t: Transform): t is FoldTransform { - return hasKey(t, 'fold'); + return hasProperty(t, 'fold'); } export function isExtent(t: Transform): t is ExtentTransform { - return hasKey(t, 'extent') && !hasKey(t, 'density') && !hasKey(t, 'regression'); + return hasProperty(t, 'extent') && !hasProperty(t, 'density') && !hasProperty(t, 'regression'); } export type Transform = | AggregateTransform diff --git a/src/util.ts b/src/util.ts index 67abdad13d..5159e1fed0 100644 --- a/src/util.ts +++ b/src/util.ts @@ -508,15 +508,12 @@ export function stringify(data: any) { } /** - * Check if the input object has the key and it's not undefined. + * Check if the input object has the property and it's not undefined. * * @param object the object - * @param key the key to search - * @returns if the object has the key and it's not undefined. + * @param property the property to search + * @returns if the object has the property and it's not undefined. */ -// export function hasKey(object: unknown, key: string) { -// return isObject(object) && hasOwnProperty(object, key) && (object as any)[key] !== undefined; -// } -export function hasKey(obj: T, key: string | number | symbol): key is keyof T { +export function hasProperty(obj: T, key: string | number | symbol): key is keyof T { return isObject(obj) && hasOwnProperty(obj, key) && (obj as any)[key] !== undefined; } diff --git a/src/vega.schema.ts b/src/vega.schema.ts index 0f767cb19a..176630f267 100644 --- a/src/vega.schema.ts +++ b/src/vega.schema.ts @@ -45,7 +45,7 @@ import {isArray} from 'vega-util'; import {Value} from './channeldef'; import {ExprRef} from './expr'; import {SortOrder} from './sort'; -import {Dict, Flag, hasKey, keys} from './util'; +import {Dict, Flag, hasProperty, keys} from './util'; export type {VgSortField, VgUnionSortField, VgCompare, VgTitle, LayoutAlign, ProjectionType, VgExprRef}; @@ -87,7 +87,7 @@ export type VgScaleDataRefWithSort = ScaleDataRef & { }; export function isSignalRef(o: any): o is SignalRef { - return hasKey(o, 'signal'); + return hasProperty(o, 'signal'); } // TODO: add type of value (Make it VgValueRef {value?:V ...}) @@ -121,7 +121,7 @@ export type VgMultiFieldsRefWithSort = ScaleMultiFieldsRef & { export type VgRange = RangeScheme | ScaleData | RangeBand | RangeRaw; export function isVgRangeStep(range: VgRange): range is VgRangeStep { - return hasKey(range, 'step'); + return hasProperty(range, 'step'); } export interface VgRangeStep { @@ -193,21 +193,21 @@ export interface VgLayout { export function isDataRefUnionedDomain(domain: VgDomain): domain is VgScaleMultiDataRefWithSort { if (!isArray(domain)) { - return hasKey(domain, 'fields') && !hasKey(domain, 'data'); + return hasProperty(domain, 'fields') && !hasProperty(domain, 'data'); } return false; } export function isFieldRefUnionDomain(domain: VgDomain): domain is VgMultiFieldsRefWithSort { if (!isArray(domain)) { - return hasKey(domain, 'fields') && hasKey(domain, 'data'); + return hasProperty(domain, 'fields') && hasProperty(domain, 'data'); } return false; } export function isDataRefDomain(domain: VgDomain | any): domain is VgScaleDataRefWithSort { if (!isArray(domain)) { - return hasKey(domain, 'field') && hasKey(domain, 'data'); + return hasProperty(domain, 'field') && hasProperty(domain, 'data'); } return false; } diff --git a/test/example.test.ts b/test/example.test.ts new file mode 100644 index 0000000000..9fce538bac --- /dev/null +++ b/test/example.test.ts @@ -0,0 +1,22 @@ +import {compile} from '../src'; +import {spec} from '../test-runtime/util'; + +test('example test', () => { + const s = spec( + 'unit', + 1, + {type: 'interval', encodings: ['y']}, + { + x: {aggregate: 'count', type: 'quantitative'}, + y: {bin: true}, + color: {value: 'steelblue'} + } + ); + expect(s).toBeDefined(); + + console.log(JSON.stringify(s)); + + const vg = compile(s).spec; + + expect(vg).toBeDefined(); +}); diff --git a/vega-lite.js b/vega-lite.js new file mode 100644 index 0000000000..913d3fdc8f --- /dev/null +++ b/vega-lite.js @@ -0,0 +1,23021 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vega')) : + typeof define === 'function' && define.amd ? define(['exports', 'vega'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.vegaLite = {}, global.vega)); +})(this, (function (exports, vega) { 'use strict'; + + var name = "vega-lite"; + var author = "Dominik Moritz, Kanit \"Ham\" Wongsuphasawat, Arvind Satyanarayan, Jeffrey Heer"; + var version$1 = "5.20.0"; + var collaborators = [ + "Kanit Wongsuphasawat (http://kanitw.yellowpigz.com)", + "Dominik Moritz (https://www.domoritz.de)", + "Arvind Satyanarayan (https://arvindsatya.com)", + "Jeffrey Heer (https://jheer.org)" + ]; + var homepage = "https://vega.github.io/vega-lite/"; + var description$1 = "Vega-Lite is a concise high-level language for interactive visualization."; + var keywords = [ + "vega", + "chart", + "visualization" + ]; + var main$1 = "build/vega-lite.js"; + var unpkg = "build/vega-lite.min.js"; + var jsdelivr = "build/vega-lite.min.js"; + var module = "build/src/index"; + var types = "build/src/index.d.ts"; + var bin = { + vl2pdf: "./bin/vl2pdf", + vl2png: "./bin/vl2png", + vl2svg: "./bin/vl2svg", + vl2vg: "./bin/vl2vg" + }; + var files = [ + "bin", + "build", + "src", + "vega-lite*", + "tsconfig.json" + ]; + var scripts = { + changelog: "conventional-changelog -p angular -r 2", + prebuild: "yarn clean:build", + build: "yarn build:only", + "build:only": "tsc -p tsconfig.build.json && rollup -c", + "prebuild:examples": "yarn build:only", + "build:examples": "yarn data && TZ=America/Los_Angeles scripts/build-examples.sh", + "prebuild:examples-full": "yarn build:only", + "build:examples-full": "TZ=America/Los_Angeles scripts/build-examples.sh 1", + "build:example": "TZ=America/Los_Angeles scripts/build-example.sh", + "build:toc": "yarn build:jekyll && scripts/generate-toc", + "build:site": "rollup -c site/rollup.config.mjs", + "build:jekyll": "pushd site && bundle exec jekyll build -q && popd", + "build:versions": "scripts/update-version.sh", + clean: "yarn clean:build && del-cli 'site/data/*' 'examples/compiled/*.png' && find site/examples ! -name 'index.md' ! -name 'data' -type f -delete", + "clean:build": "del-cli 'build/*' !build/vega-lite-schema.json", + data: "rsync -r node_modules/vega-datasets/data/* site/data", + "build-editor-preview": "scripts/build-editor-preview.sh", + schema: "mkdir -p build && ts-json-schema-generator -f tsconfig.json -p src/index.ts -t TopLevelSpec --no-type-check --no-ref-encode > build/vega-lite-schema.json && yarn renameschema && cp build/vega-lite-schema.json site/_data/", + renameschema: "scripts/rename-schema.sh", + presite: "yarn data && yarn schema && yarn build:site && yarn build:versions && scripts/create-example-pages.sh", + site: "yarn site:only", + "site:only": "pushd site && bundle exec jekyll serve -I -l && popd", + prettierbase: "prettier '**/*.{md,css,yml}'", + format: "eslint . --fix && yarn prettierbase --write", + lint: "eslint . && yarn prettierbase --check", + test: "yarn jest test/ && yarn lint && yarn schema && yarn jest examples/ && yarn test:runtime", + "test:cover": "yarn jest --collectCoverage test/", + "test:inspect": "node --inspect-brk ./node_modules/.bin/jest --runInBand test", + "test:runtime": "TZ=America/Los_Angeles npx jest test-runtime/ --config test-runtime/jest-config.json", + "test:runtime:generate": "yarn build:only && del-cli test-runtime/resources && VL_GENERATE_TESTS=true yarn test:runtime", + watch: "tsc -p tsconfig.build.json -w", + "watch:site": "yarn build:site -w", + "watch:test": "yarn jest --watch test/", + "watch:test:runtime": "TZ=America/Los_Angeles npx jest --watch test-runtime/ --config test-runtime/jest-config.json", + release: "release-it" + }; + var repository = { + type: "git", + url: "https://github.com/vega/vega-lite.git" + }; + var license = "BSD-3-Clause"; + var bugs = { + url: "https://github.com/vega/vega-lite/issues" + }; + var devDependencies = { + "@babel/core": "^7.24.7", + "@babel/preset-env": "^7.24.7", + "@babel/preset-typescript": "^7.24.7", + "@release-it/conventional-changelog": "^8.0.1", + "@rollup/plugin-alias": "^5.1.0", + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-commonjs": "^26.0.1", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-terser": "^0.4.4", + "@types/d3": "^7.4.3", + "@types/jest": "^29.5.12", + "@types/pako": "^2.0.3", + "@typescript-eslint/eslint-plugin": "^7.13.0", + "@typescript-eslint/parser": "^7.13.0", + ajv: "^8.16.0", + "ajv-formats": "^2.1.1", + cheerio: "^1.0.0-rc.12", + "conventional-changelog-cli": "^4.1.0", + d3: "^7.9.0", + "del-cli": "^5.1.0", + eslint: "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-jest": "^27.9.0", + "eslint-plugin-prettier": "^5.1.3", + "fast-json-stable-stringify": "~2.1.0", + "highlight.js": "^11.9.0", + jest: "^29.7.0", + "jest-dev-server": "^10.0.0", + mkdirp: "^3.0.1", + pako: "^2.1.0", + prettier: "^3.3.2", + puppeteer: "^15.0.0", + "release-it": "17.2.1", + rollup: "^4.18.0", + "rollup-plugin-bundle-size": "^1.0.3", + serve: "^14.2.3", + terser: "^5.31.1", + "ts-jest": "^29.1.4", + "ts-json-schema-generator": "^1.5.0", + typescript: "~5.4.5", + "vega-cli": "^5.28.0", + "vega-datasets": "^2.8.1", + "vega-embed": "^6.25.0", + "vega-tooltip": "^0.34.0", + "yaml-front-matter": "^4.1.1" + }; + var dependencies = { + "json-stringify-pretty-compact": "~3.0.0", + tslib: "~2.6.3", + "vega-event-selector": "~3.0.1", + "vega-expression": "~5.1.0", + "vega-util": "~1.17.2", + yargs: "~17.7.2" + }; + var peerDependencies = { + vega: "^5.24.0" + }; + var engines = { + node: ">=18" + }; + var packageManager = "yarn@1.22.19"; + var pkg = { + name: name, + author: author, + version: version$1, + collaborators: collaborators, + homepage: homepage, + description: description$1, + keywords: keywords, + main: main$1, + unpkg: unpkg, + jsdelivr: jsdelivr, + module: module, + types: types, + bin: bin, + files: files, + scripts: scripts, + repository: repository, + license: license, + bugs: bugs, + devDependencies: devDependencies, + dependencies: dependencies, + peerDependencies: peerDependencies, + engines: engines, + packageManager: packageManager + }; + + function isLogicalOr(op) { + return !!op.or; + } + function isLogicalAnd(op) { + return !!op.and; + } + function isLogicalNot(op) { + return !!op.not; + } + function forEachLeaf(op, fn) { + if (isLogicalNot(op)) { + forEachLeaf(op.not, fn); + } else if (isLogicalAnd(op)) { + for (const subop of op.and) { + forEachLeaf(subop, fn); + } + } else if (isLogicalOr(op)) { + for (const subop of op.or) { + forEachLeaf(subop, fn); + } + } else { + fn(op); + } + } + function normalizeLogicalComposition(op, normalizer) { + if (isLogicalNot(op)) { + return { + not: normalizeLogicalComposition(op.not, normalizer) + }; + } else if (isLogicalAnd(op)) { + return { + and: op.and.map(o => normalizeLogicalComposition(o, normalizer)) + }; + } else if (isLogicalOr(op)) { + return { + or: op.or.map(o => normalizeLogicalComposition(o, normalizer)) + }; + } else { + return normalizer(op); + } + } + + const duplicate = structuredClone; + function never(message) { + throw new Error(message); + } + + /** + * Creates an object composed of the picked object properties. + * + * var object = {'a': 1, 'b': '2', 'c': 3}; + * pick(object, ['a', 'c']); + * // → {'a': 1, 'c': 3} + */ + // eslint-disable-next-line @typescript-eslint/ban-types + function pick(obj, props) { + const copy = {}; + for (const prop of props) { + if (vega.hasOwnProperty(obj, prop)) { + copy[prop] = obj[prop]; + } + } + return copy; + } + + /** + * The opposite of _.pick; this method creates an object composed of the own + * and inherited enumerable string keyed properties of object that are not omitted. + */ + // eslint-disable-next-line @typescript-eslint/ban-types + function omit(obj, props) { + const copy = { + ...obj + }; + for (const prop of props) { + delete copy[prop]; + } + return copy; + } + + /** + * Monkey patch Set so that `stringify` produces a string representation of sets. + */ + Set.prototype['toJSON'] = function () { + return `Set(${[...this].map(x => stringify(x)).join(',')})`; + }; + + /** + * Converts any object to a string of limited size, or a number. + */ + function hash(a) { + if (vega.isNumber(a)) { + return a; + } + const str = vega.isString(a) ? a : stringify(a); + + // short strings can be used as hash directly, longer strings are hashed to reduce memory usage + if (str.length < 250) { + return str; + } + + // from http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/ + let h = 0; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + h = (h << 5) - h + char; + h = h & h; // Convert to 32bit integer + } + return h; + } + function isNullOrFalse(x) { + return x === false || x === null; + } + function contains(array, item) { + return array.includes(item); + } + + /** + * Returns true if any item returns true. + */ + function some(arr, f) { + let i = 0; + for (const [k, a] of arr.entries()) { + if (f(a, k, i++)) { + return true; + } + } + return false; + } + + /** + * Returns true if all items return true. + */ + function every(arr, f) { + let i = 0; + for (const [k, a] of arr.entries()) { + if (!f(a, k, i++)) { + return false; + } + } + return true; + } + + /** + * Like TS Partial but applies recursively to all properties. + */ + + /** + * recursively merges src into dest + */ + function mergeDeep(dest) { + for (var _len = arguments.length, src = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + src[_key - 1] = arguments[_key]; + } + for (const s of src) { + deepMerge_(dest, s ?? {}); + } + return dest; + } + function deepMerge_(dest, src) { + for (const property of keys(src)) { + vega.writeConfig(dest, property, src[property], true); + } + } + function unique(values, f) { + const results = []; + const u = {}; + let v; + for (const val of values) { + v = f(val); + if (v in u) { + continue; + } + u[v] = 1; + results.push(val); + } + return results; + } + /** + * Returns true if the two dictionaries agree. Applies only to defined values. + */ + function isEqual(dict, other) { + const dictKeys = keys(dict); + const otherKeys = keys(other); + if (dictKeys.length !== otherKeys.length) { + return false; + } + for (const key of dictKeys) { + if (dict[key] !== other[key]) { + return false; + } + } + return true; + } + function setEqual(a, b) { + if (a.size !== b.size) { + return false; + } + for (const e of a) { + if (!b.has(e)) { + return false; + } + } + return true; + } + function hasIntersection(a, b) { + for (const key of a) { + if (b.has(key)) { + return true; + } + } + return false; + } + function prefixGenerator(a) { + const prefixes = new Set(); + for (const x of a) { + const splitField = vega.splitAccessPath(x); + // Wrap every element other than the first in `[]` + const wrappedWithAccessors = splitField.map((y, i) => i === 0 ? y : `[${y}]`); + const computedPrefixes = wrappedWithAccessors.map((_, i) => wrappedWithAccessors.slice(0, i + 1).join('')); + for (const y of computedPrefixes) { + prefixes.add(y); + } + } + return prefixes; + } + + /** + * Returns true if a and b have an intersection. Also return true if a or b are undefined + * since this means we don't know what fields a node produces or depends on. + */ + function fieldIntersection(a, b) { + if (a === undefined || b === undefined) { + return true; + } + return hasIntersection(prefixGenerator(a), prefixGenerator(b)); + } + + // eslint-disable-next-line @typescript-eslint/ban-types + function isEmpty(obj) { + return keys(obj).length === 0; + } + + // This is a stricter version of Object.keys but with better types. See https://github.com/Microsoft/TypeScript/pull/12253#issuecomment-263132208 + const keys = Object.keys; + const vals = Object.values; + const entries$1 = Object.entries; + + // Using mapped type to declare a collect of flags for a string literal type S + // https://www.typescriptlang.org/docs/handbook/advanced-types.html#mapped-types + + function isBoolean(b) { + return b === true || b === false; + } + + /** + * Convert a string into a valid variable name + */ + function varName(s) { + // Replace non-alphanumeric characters (anything besides a-zA-Z0-9_) with _ + const alphanumericS = s.replace(/\W/g, '_'); + + // Add _ if the string has leading numbers. + return (s.match(/^\d+/) ? '_' : '') + alphanumericS; + } + function logicalExpr(op, cb) { + if (isLogicalNot(op)) { + return `!(${logicalExpr(op.not, cb)})`; + } else if (isLogicalAnd(op)) { + return `(${op.and.map(and => logicalExpr(and, cb)).join(') && (')})`; + } else if (isLogicalOr(op)) { + return `(${op.or.map(or => logicalExpr(or, cb)).join(') || (')})`; + } else { + return cb(op); + } + } + + /** + * Delete nested property of an object, and delete the ancestors of the property if they become empty. + */ + function deleteNestedProperty(obj, orderedProps) { + if (orderedProps.length === 0) { + return true; + } + const prop = orderedProps.shift(); // eslint-disable-line @typescript-eslint/no-non-null-assertion + if (prop in obj && deleteNestedProperty(obj[prop], orderedProps)) { + delete obj[prop]; + } + return isEmpty(obj); + } + function titleCase(s) { + return s.charAt(0).toUpperCase() + s.substr(1); + } + + /** + * Converts a path to an access path with datum. + * @param path The field name. + * @param datum The string to use for `datum`. + */ + function accessPathWithDatum(path) { + let datum = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'datum'; + const pieces = vega.splitAccessPath(path); + const prefixes = []; + for (let i = 1; i <= pieces.length; i++) { + const prefix = `[${pieces.slice(0, i).map(vega.stringValue).join('][')}]`; + prefixes.push(`${datum}${prefix}`); + } + return prefixes.join(' && '); + } + + /** + * Return access with datum to the flattened field. + * + * @param path The field name. + * @param datum The string to use for `datum`. + */ + function flatAccessWithDatum(path) { + let datum = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'datum'; + return `${datum}[${vega.stringValue(vega.splitAccessPath(path).join('.'))}]`; + } + function escapePathAccess(string) { + return string.replace(/(\[|\]|\.|'|")/g, '\\$1'); + } + + /** + * Replaces path accesses with access to non-nested field. + * For example, `foo["bar"].baz` becomes `foo\\.bar\\.baz`. + */ + function replacePathInField(path) { + return `${vega.splitAccessPath(path).map(escapePathAccess).join('\\.')}`; + } + + /** + * Replace all occurrences of a string with another string. + * + * @param string the string to replace in + * @param find the string to replace + * @param replacement the replacement + */ + function replaceAll(string, find, replacement) { + return string.replace(new RegExp(find.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'), 'g'), replacement); + } + + /** + * Remove path accesses with access from field. + * For example, `foo["bar"].baz` becomes `foo.bar.baz`. + */ + function removePathFromField(path) { + return `${vega.splitAccessPath(path).join('.')}`; + } + + /** + * Count the depth of the path. Returns 1 for fields that are not nested. + */ + function accessPathDepth(path) { + if (!path) { + return 0; + } + return vega.splitAccessPath(path).length; + } + + /** + * This is a replacement for chained || for numeric properties or properties that respect null so that 0 will be included. + */ + function getFirstDefined() { + for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2]; + } + for (const arg of args) { + if (arg !== undefined) { + return arg; + } + } + return undefined; + } + + // variable used to generate id + let idCounter = 42; + + /** + * Returns a new random id every time it gets called. + * + * Has side effect! + */ + function uniqueId(prefix) { + const id = ++idCounter; + return prefix ? String(prefix) + id : id; + } + + /** + * Resets the id counter used in uniqueId. This can be useful for testing. + */ + function resetIdCounter() { + idCounter = 42; + } + function internalField(name) { + return isInternalField(name) ? name : `__${name}`; + } + function isInternalField(name) { + return name.startsWith('__'); + } + + /** + * Normalize angle to be within [0,360). + */ + function normalizeAngle(angle) { + if (angle === undefined) { + return undefined; + } + return (angle % 360 + 360) % 360; + } + + /** + * Returns whether the passed in value is a valid number. + */ + function isNumeric(value) { + if (vega.isNumber(value)) { + return true; + } + return !isNaN(value) && !isNaN(parseFloat(value)); + } + const clonedProto = Object.getPrototypeOf(structuredClone({})); + + /** + * Compares two values for equality, including arrays and objects. + * + * Adapted from https://github.com/epoberezkin/fast-deep-equal. + */ + function deepEqual(a, b) { + if (a === b) return true; + if (a && b && typeof a == 'object' && typeof b == 'object') { + // compare names to avoid issues with structured clone + if (a.constructor.name !== b.constructor.name) return false; + let length; + let i; + if (Array.isArray(a)) { + length = a.length; + if (length != b.length) return false; + for (i = length; i-- !== 0;) if (!deepEqual(a[i], b[i])) return false; + return true; + } + if (a instanceof Map && b instanceof Map) { + if (a.size !== b.size) return false; + for (i of a.entries()) if (!b.has(i[0])) return false; + for (i of a.entries()) if (!deepEqual(i[1], b.get(i[0]))) return false; + return true; + } + if (a instanceof Set && b instanceof Set) { + if (a.size !== b.size) return false; + for (i of a.entries()) if (!b.has(i[0])) return false; + return true; + } + if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) { + length = a.length; + if (length != b.length) return false; + for (i = length; i-- !== 0;) if (a[i] !== b[i]) return false; + return true; + } + if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags; + // also compare to structured clone prototype + if (a.valueOf !== Object.prototype.valueOf && a.valueOf !== clonedProto.valueOf) return a.valueOf() === b.valueOf(); + if (a.toString !== Object.prototype.toString && a.toString !== clonedProto.toString) return a.toString() === b.toString(); + const ks = Object.keys(a); + length = ks.length; + if (length !== Object.keys(b).length) return false; + for (i = length; i-- !== 0;) if (!Object.prototype.hasOwnProperty.call(b, ks[i])) return false; + for (i = length; i-- !== 0;) { + const key = ks[i]; + if (!deepEqual(a[key], b[key])) return false; + } + return true; + } + + // true if both NaN, false otherwise + return a !== a && b !== b; + } + + /** + * Converts any object to a string representation that can be consumed by humans. + * + * Adapted from https://github.com/epoberezkin/fast-json-stable-stringify + */ + function stringify(data) { + const seen = []; + return function _stringify(node) { + if (node && node.toJSON && typeof node.toJSON === 'function') { + node = node.toJSON(); + } + if (node === undefined) return undefined; + if (typeof node == 'number') return isFinite(node) ? '' + node : 'null'; + if (typeof node !== 'object') return JSON.stringify(node); + let i, out; + if (Array.isArray(node)) { + out = '['; + for (i = 0; i < node.length; i++) { + if (i) out += ','; + out += _stringify(node[i]) || 'null'; + } + return out + ']'; + } + if (node === null) return 'null'; + if (seen.includes(node)) { + throw new TypeError('Converting circular structure to JSON'); + } + const seenIndex = seen.push(node) - 1; + const ks = Object.keys(node).sort(); + out = ''; + for (i = 0; i < ks.length; i++) { + const key = ks[i]; + const value = _stringify(node[key]); + if (!value) continue; + if (out) out += ','; + out += JSON.stringify(key) + ':' + value; + } + seen.splice(seenIndex, 1); + return `{${out}}`; + }(data); + } + + /* + * Constants and utilities for encoding channels (Visual variables) + * such as 'x', 'y', 'color'. + */ + + // Facet + const ROW = 'row'; + const COLUMN = 'column'; + const FACET = 'facet'; + + // Position + const X = 'x'; + const Y = 'y'; + const X2 = 'x2'; + const Y2 = 'y2'; + + // Position Offset + const XOFFSET = 'xOffset'; + const YOFFSET = 'yOffset'; + + // Arc-Position + const RADIUS = 'radius'; + const RADIUS2 = 'radius2'; + const THETA = 'theta'; + const THETA2 = 'theta2'; + + // Geo Position + const LATITUDE = 'latitude'; + const LONGITUDE = 'longitude'; + const LATITUDE2 = 'latitude2'; + const LONGITUDE2 = 'longitude2'; + + // Mark property with scale + const COLOR = 'color'; + const FILL = 'fill'; + const STROKE = 'stroke'; + const SHAPE = 'shape'; + const SIZE = 'size'; + const ANGLE = 'angle'; + const OPACITY = 'opacity'; + const FILLOPACITY = 'fillOpacity'; + const STROKEOPACITY = 'strokeOpacity'; + const STROKEWIDTH = 'strokeWidth'; + const STROKEDASH = 'strokeDash'; + + // Non-scale channel + const TEXT$1 = 'text'; + const ORDER = 'order'; + const DETAIL = 'detail'; + const KEY = 'key'; + const TOOLTIP = 'tooltip'; + const HREF = 'href'; + const URL = 'url'; + const DESCRIPTION = 'description'; + const POSITION_CHANNEL_INDEX = { + x: 1, + y: 1, + x2: 1, + y2: 1 + }; + const POLAR_POSITION_CHANNEL_INDEX = { + theta: 1, + theta2: 1, + radius: 1, + radius2: 1 + }; + function isPolarPositionChannel(c) { + return c in POLAR_POSITION_CHANNEL_INDEX; + } + const GEO_POSIITON_CHANNEL_INDEX = { + longitude: 1, + longitude2: 1, + latitude: 1, + latitude2: 1 + }; + function getPositionChannelFromLatLong(channel) { + switch (channel) { + case LATITUDE: + return 'y'; + case LATITUDE2: + return 'y2'; + case LONGITUDE: + return 'x'; + case LONGITUDE2: + return 'x2'; + } + } + function isGeoPositionChannel(c) { + return c in GEO_POSIITON_CHANNEL_INDEX; + } + const GEOPOSITION_CHANNELS = keys(GEO_POSIITON_CHANNEL_INDEX); + const UNIT_CHANNEL_INDEX = { + ...POSITION_CHANNEL_INDEX, + ...POLAR_POSITION_CHANNEL_INDEX, + ...GEO_POSIITON_CHANNEL_INDEX, + xOffset: 1, + yOffset: 1, + // color + color: 1, + fill: 1, + stroke: 1, + // other non-position with scale + opacity: 1, + fillOpacity: 1, + strokeOpacity: 1, + strokeWidth: 1, + strokeDash: 1, + size: 1, + angle: 1, + shape: 1, + // channels without scales + order: 1, + text: 1, + detail: 1, + key: 1, + tooltip: 1, + href: 1, + url: 1, + description: 1 + }; + function isColorChannel(channel) { + return channel === COLOR || channel === FILL || channel === STROKE; + } + const FACET_CHANNEL_INDEX = { + row: 1, + column: 1, + facet: 1 + }; + const FACET_CHANNELS = keys(FACET_CHANNEL_INDEX); + const CHANNEL_INDEX = { + ...UNIT_CHANNEL_INDEX, + ...FACET_CHANNEL_INDEX + }; + const CHANNELS = keys(CHANNEL_INDEX); + const { + order: _o, + detail: _d, + tooltip: _tt1, + ...SINGLE_DEF_CHANNEL_INDEX + } = CHANNEL_INDEX; + const { + row: _r, + column: _c, + facet: _f, + ...SINGLE_DEF_UNIT_CHANNEL_INDEX + } = SINGLE_DEF_CHANNEL_INDEX; + function isSingleDefUnitChannel(str) { + return !!SINGLE_DEF_UNIT_CHANNEL_INDEX[str]; + } + function isChannel(str) { + return !!CHANNEL_INDEX[str]; + } + const SECONDARY_RANGE_CHANNEL = [X2, Y2, LATITUDE2, LONGITUDE2, THETA2, RADIUS2]; + function isSecondaryRangeChannel(c) { + const main = getMainRangeChannel(c); + return main !== c; + } + /** + * Get the main channel for a range channel. E.g. `x` for `x2`. + */ + function getMainRangeChannel(channel) { + switch (channel) { + case X2: + return X; + case Y2: + return Y; + case LATITUDE2: + return LATITUDE; + case LONGITUDE2: + return LONGITUDE; + case THETA2: + return THETA; + case RADIUS2: + return RADIUS; + } + return channel; + } + function getVgPositionChannel(channel) { + if (isPolarPositionChannel(channel)) { + switch (channel) { + case THETA: + return 'startAngle'; + case THETA2: + return 'endAngle'; + case RADIUS: + return 'outerRadius'; + case RADIUS2: + return 'innerRadius'; + } + } + return channel; + } + + /** + * Get the main channel for a range channel. E.g. `x` for `x2`. + */ + function getSecondaryRangeChannel(channel) { + switch (channel) { + case X: + return X2; + case Y: + return Y2; + case LATITUDE: + return LATITUDE2; + case LONGITUDE: + return LONGITUDE2; + case THETA: + return THETA2; + case RADIUS: + return RADIUS2; + } + return undefined; + } + function getSizeChannel(channel) { + switch (channel) { + case X: + case X2: + return 'width'; + case Y: + case Y2: + return 'height'; + } + return undefined; + } + + /** + * Get the main channel for a range channel. E.g. `x` for `x2`. + */ + function getOffsetChannel(channel) { + switch (channel) { + case X: + return 'xOffset'; + case Y: + return 'yOffset'; + case X2: + return 'x2Offset'; + case Y2: + return 'y2Offset'; + case THETA: + return 'thetaOffset'; + case RADIUS: + return 'radiusOffset'; + case THETA2: + return 'theta2Offset'; + case RADIUS2: + return 'radius2Offset'; + } + return undefined; + } + + /** + * Get the main channel for a range channel. E.g. `x` for `x2`. + */ + function getOffsetScaleChannel(channel) { + switch (channel) { + case X: + return 'xOffset'; + case Y: + return 'yOffset'; + } + return undefined; + } + function getMainChannelFromOffsetChannel(channel) { + switch (channel) { + case 'xOffset': + return 'x'; + case 'yOffset': + return 'y'; + } + } + + // CHANNELS without COLUMN, ROW + const UNIT_CHANNELS = keys(UNIT_CHANNEL_INDEX); + + // NONPOSITION_CHANNELS = UNIT_CHANNELS without X, Y, X2, Y2; + const { + x: _x, + y: _y, + // x2 and y2 share the same scale as x and y + x2: _x2, + y2: _y2, + // + xOffset: _xo, + yOffset: _yo, + latitude: _latitude, + longitude: _longitude, + latitude2: _latitude2, + longitude2: _longitude2, + theta: _theta, + theta2: _theta2, + radius: _radius, + radius2: _radius2, + // The rest of unit channels then have scale + ...NONPOSITION_CHANNEL_INDEX + } = UNIT_CHANNEL_INDEX; + const NONPOSITION_CHANNELS = keys(NONPOSITION_CHANNEL_INDEX); + const POSITION_SCALE_CHANNEL_INDEX = { + x: 1, + y: 1 + }; + const POSITION_SCALE_CHANNELS = keys(POSITION_SCALE_CHANNEL_INDEX); + function isXorY(channel) { + return channel in POSITION_SCALE_CHANNEL_INDEX; + } + const POLAR_POSITION_SCALE_CHANNEL_INDEX = { + theta: 1, + radius: 1 + }; + const POLAR_POSITION_SCALE_CHANNELS = keys(POLAR_POSITION_SCALE_CHANNEL_INDEX); + function getPositionScaleChannel(sizeType) { + return sizeType === 'width' ? X : Y; + } + const OFFSET_SCALE_CHANNEL_INDEX = { + xOffset: 1, + yOffset: 1 + }; + function isXorYOffset(channel) { + return channel in OFFSET_SCALE_CHANNEL_INDEX; + } + + // NON_POSITION_SCALE_CHANNEL = SCALE_CHANNELS without position / offset + const { + // x2 and y2 share the same scale as x and y + // text and tooltip have format instead of scale, + // href has neither format, nor scale + text: _t, + tooltip: _tt, + href: _hr, + url: _u, + description: _al, + // detail and order have no scale + detail: _dd, + key: _k, + order: _oo, + ...NONPOSITION_SCALE_CHANNEL_INDEX + } = NONPOSITION_CHANNEL_INDEX; + const NONPOSITION_SCALE_CHANNELS = keys(NONPOSITION_SCALE_CHANNEL_INDEX); + function isNonPositionScaleChannel(channel) { + return !!NONPOSITION_CHANNEL_INDEX[channel]; + } + + /** + * @returns whether Vega supports legends for a particular channel + */ + function supportLegend(channel) { + switch (channel) { + case COLOR: + case FILL: + case STROKE: + case SIZE: + case SHAPE: + case OPACITY: + case STROKEWIDTH: + case STROKEDASH: + return true; + case FILLOPACITY: + case STROKEOPACITY: + case ANGLE: + return false; + } + } + + // Declare SCALE_CHANNEL_INDEX + const SCALE_CHANNEL_INDEX = { + ...POSITION_SCALE_CHANNEL_INDEX, + ...POLAR_POSITION_SCALE_CHANNEL_INDEX, + ...OFFSET_SCALE_CHANNEL_INDEX, + ...NONPOSITION_SCALE_CHANNEL_INDEX + }; + + /** List of channels with scales */ + const SCALE_CHANNELS = keys(SCALE_CHANNEL_INDEX); + function isScaleChannel(channel) { + return !!SCALE_CHANNEL_INDEX[channel]; + } + /** + * Return whether a channel supports a particular mark type. + * @param channel channel name + * @param mark the mark type + * @return whether the mark supports the channel + */ + function supportMark(channel, mark) { + return getSupportedMark(channel)[mark]; + } + const ALL_MARKS = { + // all marks + arc: 'always', + area: 'always', + bar: 'always', + circle: 'always', + geoshape: 'always', + image: 'always', + line: 'always', + rule: 'always', + point: 'always', + rect: 'always', + square: 'always', + trail: 'always', + text: 'always', + tick: 'always' + }; + const { + geoshape: _g, + ...ALL_MARKS_EXCEPT_GEOSHAPE + } = ALL_MARKS; + + /** + * Return a dictionary showing whether a channel supports mark type. + * @param channel + * @return A dictionary mapping mark types to 'always', 'binned', or undefined + */ + function getSupportedMark(channel) { + switch (channel) { + case COLOR: + case FILL: + case STROKE: + // falls through + + case DESCRIPTION: + case DETAIL: + case KEY: + case TOOLTIP: + case HREF: + case ORDER: // TODO: revise (order might not support rect, which is not stackable?) + case OPACITY: + case FILLOPACITY: + case STROKEOPACITY: + case STROKEWIDTH: + + // falls through + + case FACET: + case ROW: // falls through + case COLUMN: + return ALL_MARKS; + case X: + case Y: + case XOFFSET: + case YOFFSET: + case LATITUDE: + case LONGITUDE: + // all marks except geoshape. geoshape does not use X, Y -- it uses a projection + return ALL_MARKS_EXCEPT_GEOSHAPE; + case X2: + case Y2: + case LATITUDE2: + case LONGITUDE2: + return { + area: 'always', + bar: 'always', + image: 'always', + rect: 'always', + rule: 'always', + circle: 'binned', + point: 'binned', + square: 'binned', + tick: 'binned', + line: 'binned', + trail: 'binned' + }; + case SIZE: + return { + point: 'always', + tick: 'always', + rule: 'always', + circle: 'always', + square: 'always', + bar: 'always', + text: 'always', + line: 'always', + trail: 'always' + }; + case STROKEDASH: + return { + line: 'always', + point: 'always', + tick: 'always', + rule: 'always', + circle: 'always', + square: 'always', + bar: 'always', + geoshape: 'always' + }; + case SHAPE: + return { + point: 'always', + geoshape: 'always' + }; + case TEXT$1: + return { + text: 'always' + }; + case ANGLE: + return { + point: 'always', + square: 'always', + text: 'always' + }; + case URL: + return { + image: 'always' + }; + case THETA: + return { + text: 'always', + arc: 'always' + }; + case RADIUS: + return { + text: 'always', + arc: 'always' + }; + case THETA2: + case RADIUS2: + return { + arc: 'always' + }; + } + } + function rangeType(channel) { + switch (channel) { + case X: + case Y: + case THETA: + case RADIUS: + case XOFFSET: + case YOFFSET: + case SIZE: + case ANGLE: + case STROKEWIDTH: + case OPACITY: + case FILLOPACITY: + case STROKEOPACITY: + + // X2 and Y2 use X and Y scales, so they similarly have continuous range. [falls through] + case X2: + case Y2: + case THETA2: + case RADIUS2: + return undefined; + case FACET: + case ROW: + case COLUMN: + case SHAPE: + case STROKEDASH: + // TEXT, TOOLTIP, URL, and HREF have no scale but have discrete output [falls through] + case TEXT$1: + case TOOLTIP: + case HREF: + case URL: + case DESCRIPTION: + return 'discrete'; + + // Color can be either continuous or discrete, depending on scale type. + case COLOR: + case FILL: + case STROKE: + return 'flexible'; + + // No scale, no range type. + + case LATITUDE: + case LONGITUDE: + case LATITUDE2: + case LONGITUDE2: + case DETAIL: + case KEY: + case ORDER: + return undefined; + } + } + + const AGGREGATE_OP_INDEX = { + argmax: 1, + argmin: 1, + average: 1, + count: 1, + distinct: 1, + exponential: 1, + exponentialb: 1, + product: 1, + max: 1, + mean: 1, + median: 1, + min: 1, + missing: 1, + q1: 1, + q3: 1, + ci0: 1, + ci1: 1, + stderr: 1, + stdev: 1, + stdevp: 1, + sum: 1, + valid: 1, + values: 1, + variance: 1, + variancep: 1 + }; + const MULTIDOMAIN_SORT_OP_INDEX = { + count: 1, + min: 1, + max: 1 + }; + function isArgminDef(a) { + return !!a && !!a['argmin']; + } + function isArgmaxDef(a) { + return !!a && !!a['argmax']; + } + function isAggregateOp(a) { + return vega.isString(a) && !!AGGREGATE_OP_INDEX[a]; + } + const COUNTING_OPS = new Set(['count', 'valid', 'missing', 'distinct']); + function isCountingAggregateOp(aggregate) { + return vega.isString(aggregate) && COUNTING_OPS.has(aggregate); + } + function isMinMaxOp(aggregate) { + return vega.isString(aggregate) && contains(['min', 'max'], aggregate); + } + + /** Additive-based aggregation operations. These can be applied to stack. */ + const SUM_OPS = new Set(['count', 'sum', 'distinct', 'valid', 'missing']); + + /** + * Aggregation operators that always produce values within the range [domainMin, domainMax]. + */ + const SHARED_DOMAIN_OPS = new Set(['mean', 'average', 'median', 'q1', 'q3', 'min', 'max']); + + /** + * Binning properties or boolean flag for determining whether to bin data or not. + */ + + /** + * Create a key for the bin configuration. Not for prebinned bin. + */ + function binToString(bin) { + if (vega.isBoolean(bin)) { + bin = normalizeBin(bin, undefined); + } + return 'bin' + keys(bin).map(p => isParameterExtent(bin[p]) ? varName(`_${p}_${entries$1(bin[p])}`) : varName(`_${p}_${bin[p]}`)).join(''); + } + + /** + * Vega-Lite should bin the data. + */ + function isBinning(bin) { + return bin === true || isBinParams(bin) && !bin.binned; + } + + /** + * The data is already binned and so Vega-Lite should not bin it again. + */ + function isBinned(bin) { + return bin === 'binned' || isBinParams(bin) && bin.binned === true; + } + function isBinParams(bin) { + return vega.isObject(bin); + } + function isParameterExtent(extent) { + return extent?.['param']; + } + function autoMaxBins(channel) { + switch (channel) { + case ROW: + case COLUMN: + case SIZE: + case COLOR: + case FILL: + case STROKE: + case STROKEWIDTH: + case OPACITY: + case FILLOPACITY: + case STROKEOPACITY: + // Facets and Size shouldn't have too many bins + // We choose 6 like shape to simplify the rule [falls through] + case SHAPE: + return 6; + // Vega's "shape" has 6 distinct values + case STROKEDASH: + return 4; + // We only provide 5 different stroke dash values (but 4 is more effective) + default: + return 10; + } + } + + function isExprRef(o) { + return !!o?.expr; + } + function replaceExprRef(index) { + let { + level + } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { + level: 0 + }; + const props = keys(index || {}); + const newIndex = {}; + for (const prop of props) { + newIndex[prop] = level === 0 ? signalRefOrValue(index[prop]) : replaceExprRef(index[prop], { + level: level - 1 + }); + } + return newIndex; + } + + function extractTitleConfig(titleConfig) { + const { + // These are non-mark title config that need to be hardcoded + anchor, + frame, + offset, + orient, + angle, + limit, + // color needs to be redirect to fill + color, + // subtitle properties + subtitleColor, + subtitleFont, + subtitleFontSize, + subtitleFontStyle, + subtitleFontWeight, + subtitleLineHeight, + subtitlePadding, + // The rest are mark config. + ...rest + } = titleConfig; + const titleMarkConfig = { + ...rest, + ...(color ? { + fill: color + } : {}) + }; + + // These are non-mark title config that need to be hardcoded + const nonMarkTitleProperties = { + ...(anchor ? { + anchor + } : {}), + ...(frame ? { + frame + } : {}), + ...(offset ? { + offset + } : {}), + ...(orient ? { + orient + } : {}), + ...(angle !== undefined ? { + angle + } : {}), + ...(limit !== undefined ? { + limit + } : {}) + }; + + // subtitle part can stay in config.title since header titles do not use subtitle + const subtitle = { + ...(subtitleColor ? { + subtitleColor + } : {}), + ...(subtitleFont ? { + subtitleFont + } : {}), + ...(subtitleFontSize ? { + subtitleFontSize + } : {}), + ...(subtitleFontStyle ? { + subtitleFontStyle + } : {}), + ...(subtitleFontWeight ? { + subtitleFontWeight + } : {}), + ...(subtitleLineHeight ? { + subtitleLineHeight + } : {}), + ...(subtitlePadding ? { + subtitlePadding + } : {}) + }; + const subtitleMarkConfig = pick(titleConfig, ['align', 'baseline', 'dx', 'dy', 'limit']); + return { + titleMarkConfig, + subtitleMarkConfig, + nonMarkTitleProperties, + subtitle + }; + } + function isText(v) { + return vega.isString(v) || vega.isArray(v) && vega.isString(v[0]); + } + + // TODO: make recursive (e.g. with https://stackoverflow.com/a/64900252/214950 but needs https://github.com/vega/ts-json-schema-generator/issues/568) + + // Remove ValueRefs from mapped types + + function isSignalRef(o) { + return !!o?.signal; + } + + // TODO: add type of value (Make it VgValueRef {value?:V ...}) + + // TODO: add vg prefix + + function isVgRangeStep(range) { + return !!range['step']; + } + + // Domains that are not a union of domains + + /** + * A combined type for any Vega scales that Vega-Lite can generate + */ + + function isDataRefUnionedDomain(domain) { + if (!vega.isArray(domain)) { + return 'fields' in domain && !('data' in domain); + } + return false; + } + function isFieldRefUnionDomain(domain) { + if (!vega.isArray(domain)) { + return 'fields' in domain && 'data' in domain; + } + return false; + } + function isDataRefDomain(domain) { + if (!vega.isArray(domain)) { + return 'field' in domain && 'data' in domain; + } + return false; + } + + // TODO: make export interface VgEncodeEntry { + // x?: VgValueRef + // y?: VgValueRef + // ... + // color?: VgValueRef + // ... + // } + + const VG_MARK_CONFIG_INDEX = { + aria: 1, + description: 1, + ariaRole: 1, + ariaRoleDescription: 1, + blend: 1, + opacity: 1, + fill: 1, + fillOpacity: 1, + stroke: 1, + strokeCap: 1, + strokeWidth: 1, + strokeOpacity: 1, + strokeDash: 1, + strokeDashOffset: 1, + strokeJoin: 1, + strokeOffset: 1, + strokeMiterLimit: 1, + startAngle: 1, + endAngle: 1, + padAngle: 1, + innerRadius: 1, + outerRadius: 1, + size: 1, + shape: 1, + interpolate: 1, + tension: 1, + orient: 1, + align: 1, + baseline: 1, + text: 1, + dir: 1, + dx: 1, + dy: 1, + ellipsis: 1, + limit: 1, + radius: 1, + theta: 1, + angle: 1, + font: 1, + fontSize: 1, + fontWeight: 1, + fontStyle: 1, + lineBreak: 1, + lineHeight: 1, + cursor: 1, + href: 1, + tooltip: 1, + cornerRadius: 1, + cornerRadiusTopLeft: 1, + cornerRadiusTopRight: 1, + cornerRadiusBottomLeft: 1, + cornerRadiusBottomRight: 1, + aspect: 1, + width: 1, + height: 1, + url: 1, + smooth: 1 + + // commented below are vg channel that do not have mark config. + // x: 1, + // y: 1, + // x2: 1, + // y2: 1, + + // xc'|'yc' + // clip: 1, + // path: 1, + // url: 1, + }; + const VG_MARK_CONFIGS = keys(VG_MARK_CONFIG_INDEX); + const VG_MARK_INDEX = { + arc: 1, + area: 1, + group: 1, + image: 1, + line: 1, + path: 1, + rect: 1, + rule: 1, + shape: 1, + symbol: 1, + text: 1, + trail: 1 + }; + + // Vega's cornerRadius channels. + const VG_CORNERRADIUS_CHANNELS = ['cornerRadius', 'cornerRadiusTopLeft', 'cornerRadiusTopRight', 'cornerRadiusBottomLeft', 'cornerRadiusBottomRight']; + + function signalOrValueRefWithCondition(val) { + const condition = vega.isArray(val.condition) ? val.condition.map(conditionalSignalRefOrValue) : conditionalSignalRefOrValue(val.condition); + return { + ...signalRefOrValue(val), + condition + }; + } + function signalRefOrValue(value) { + if (isExprRef(value)) { + const { + expr, + ...rest + } = value; + return { + signal: expr, + ...rest + }; + } + return value; + } + function conditionalSignalRefOrValue(value) { + if (isExprRef(value)) { + const { + expr, + ...rest + } = value; + return { + signal: expr, + ...rest + }; + } + return value; + } + function signalOrValueRef(value) { + if (isExprRef(value)) { + const { + expr, + ...rest + } = value; + return { + signal: expr, + ...rest + }; + } + if (isSignalRef(value)) { + return value; + } + return value !== undefined ? { + value + } : undefined; + } + function exprFromSignalRefOrValue(ref) { + if (isSignalRef(ref)) { + return ref.signal; + } + return vega.stringValue(ref); + } + function exprFromValueRefOrSignalRef(ref) { + if (isSignalRef(ref)) { + return ref.signal; + } + return vega.stringValue(ref.value); + } + function signalOrStringValue(v) { + if (isSignalRef(v)) { + return v.signal; + } + return v == null ? null : vega.stringValue(v); + } + function applyMarkConfig(e, model, propsList) { + for (const property of propsList) { + const value = getMarkConfig(property, model.markDef, model.config); + if (value !== undefined) { + e[property] = signalOrValueRef(value); + } + } + return e; + } + function getStyles(mark) { + return [].concat(mark.type, mark.style ?? []); + } + function getMarkPropOrConfig(channel, mark, config) { + let opt = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + const { + vgChannel, + ignoreVgConfig + } = opt; + if (vgChannel && mark[vgChannel] !== undefined) { + return mark[vgChannel]; + } else if (mark[channel] !== undefined) { + return mark[channel]; + } else if (ignoreVgConfig && (!vgChannel || vgChannel === channel)) { + return undefined; + } + return getMarkConfig(channel, mark, config, opt); + } + + /** + * Return property value from style or mark specific config property if exists. + * Otherwise, return general mark specific config. + */ + function getMarkConfig(channel, mark, config) { + let { + vgChannel + } = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + return getFirstDefined( + // style config has highest precedence + vgChannel ? getMarkStyleConfig(channel, mark, config.style) : undefined, getMarkStyleConfig(channel, mark, config.style), + // then mark-specific config + vgChannel ? config[mark.type][vgChannel] : undefined, config[mark.type][channel], + // Need to cast because MarkDef doesn't perfectly match with AnyMarkConfig, but if the type isn't available, we'll get nothing here, which is fine + + // If there is vgChannel, skip vl channel. + // For example, vl size for text is vg fontSize, but config.mark.size is only for point size. + vgChannel ? config.mark[vgChannel] : config.mark[channel] // Need to cast for the same reason as above + ); + } + function getMarkStyleConfig(prop, mark, styleConfigIndex) { + return getStyleConfig(prop, getStyles(mark), styleConfigIndex); + } + function getStyleConfig(p, styles, styleConfigIndex) { + styles = vega.array(styles); + let value; + for (const style of styles) { + const styleConfig = styleConfigIndex[style]; + if (styleConfig && styleConfig[p] !== undefined) { + value = styleConfig[p]; + } + } + return value; + } + + /** + * Return Vega sort parameters (tuple of field and order). + */ + function sortParams(orderDef, fieldRefOption) { + return vega.array(orderDef).reduce((s, orderChannelDef) => { + s.field.push(vgField(orderChannelDef, fieldRefOption)); + s.order.push(orderChannelDef.sort ?? 'ascending'); + return s; + }, { + field: [], + order: [] + }); + } + function mergeTitleFieldDefs(f1, f2) { + const merged = [...f1]; + f2.forEach(fdToMerge => { + for (const fieldDef1 of merged) { + // If already exists, no need to append to merged array + if (deepEqual(fieldDef1, fdToMerge)) { + return; + } + } + merged.push(fdToMerge); + }); + return merged; + } + function mergeTitle(title1, title2) { + if (deepEqual(title1, title2) || !title2) { + // if titles are the same or title2 is falsy + return title1; + } else if (!title1) { + // if title1 is falsy + return title2; + } else { + return [...vega.array(title1), ...vega.array(title2)].join(', '); + } + } + function mergeTitleComponent(v1, v2) { + const v1Val = v1.value; + const v2Val = v2.value; + if (v1Val == null || v2Val === null) { + return { + explicit: v1.explicit, + value: null + }; + } else if ((isText(v1Val) || isSignalRef(v1Val)) && (isText(v2Val) || isSignalRef(v2Val))) { + return { + explicit: v1.explicit, + value: mergeTitle(v1Val, v2Val) + }; + } else if (isText(v1Val) || isSignalRef(v1Val)) { + return { + explicit: v1.explicit, + value: v1Val + }; + } else if (isText(v2Val) || isSignalRef(v2Val)) { + return { + explicit: v1.explicit, + value: v2Val + }; + } else if (!isText(v1Val) && !isSignalRef(v1Val) && !isText(v2Val) && !isSignalRef(v2Val)) { + return { + explicit: v1.explicit, + value: mergeTitleFieldDefs(v1Val, v2Val) + }; + } + /* istanbul ignore next: Condition should not happen -- only for warning in development. */ + throw new Error('It should never reach here'); + } + + /** + * Collection of all Vega-Lite Error Messages + */ + + function invalidSpec(spec) { + return `Invalid specification ${stringify(spec)}. Make sure the specification includes at least one of the following properties: "mark", "layer", "facet", "hconcat", "vconcat", "concat", or "repeat".`; + } + + // FIT + const FIT_NON_SINGLE = 'Autosize "fit" only works for single views and layered views.'; + function containerSizeNonSingle(name) { + const uName = name == 'width' ? 'Width' : 'Height'; + return `${uName} "container" only works for single views and layered views.`; + } + function containerSizeNotCompatibleWithAutosize(name) { + const uName = name == 'width' ? 'Width' : 'Height'; + const fitDirection = name == 'width' ? 'x' : 'y'; + return `${uName} "container" only works well with autosize "fit" or "fit-${fitDirection}".`; + } + function droppingFit(channel) { + return channel ? `Dropping "fit-${channel}" because spec has discrete ${getSizeChannel(channel)}.` : `Dropping "fit" because spec has discrete size.`; + } + + // VIEW SIZE + + function unknownField(channel) { + return `Unknown field for ${channel}. Cannot calculate view size.`; + } + + // SELECTION + function cannotProjectOnChannelWithoutField(channel) { + return `Cannot project a selection on encoding channel "${channel}", which has no field.`; + } + function cannotProjectAggregate(channel, aggregate) { + return `Cannot project a selection on encoding channel "${channel}" as it uses an aggregate function ("${aggregate}").`; + } + function nearestNotSupportForContinuous(mark) { + return `The "nearest" transform is not supported for ${mark} marks.`; + } + function selectionNotSupported(mark) { + return `Selection not supported for ${mark} yet.`; + } + function selectionNotFound(name) { + return `Cannot find a selection named "${name}".`; + } + const SCALE_BINDINGS_CONTINUOUS = 'Scale bindings are currently only supported for scales with unbinned, continuous domains.'; + const SEQUENTIAL_SCALE_DEPRECATED = 'Sequntial scales are deprecated. The available quantitative scale type values are linear, log, pow, sqrt, symlog, time and utc'; + const LEGEND_BINDINGS_MUST_HAVE_PROJECTION = 'Legend bindings are only supported for selections over an individual field or encoding channel.'; + function cannotLookupVariableParameter(name) { + return `Lookups can only be performed on selection parameters. "${name}" is a variable parameter.`; + } + function noSameUnitLookup(name) { + return `Cannot define and lookup the "${name}" selection in the same view. ` + `Try moving the lookup into a second, layered view?`; + } + const NEEDS_SAME_SELECTION = 'The same selection must be used to override scale domains in a layered view.'; + const INTERVAL_INITIALIZED_WITH_POS = 'Interval selections should be initialized using "x", "y", "longitude", or "latitude" keys.'; + + // REPEAT + function noSuchRepeatedValue(field) { + return `Unknown repeated value "${field}".`; + } + function columnsNotSupportByRowCol(type) { + return `The "columns" property cannot be used when "${type}" has nested row/column.`; + } + + // CONCAT / REPEAT + const CONCAT_CANNOT_SHARE_AXIS = 'Axes cannot be shared in concatenated or repeated views yet (https://github.com/vega/vega-lite/issues/2415).'; + + // DATA + function unrecognizedParse(p) { + return `Unrecognized parse "${p}".`; + } + function differentParse(field, local, ancestor) { + return `An ancestor parsed field "${field}" as ${ancestor} but a child wants to parse the field as ${local}.`; + } + const ADD_SAME_CHILD_TWICE = 'Attempt to add the same child twice.'; + + // TRANSFORMS + function invalidTransformIgnored(transform) { + return `Ignoring an invalid transform: ${stringify(transform)}.`; + } + const 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.'; + + // ENCODING & FACET + + function customFormatTypeNotAllowed(channel) { + return `Config.customFormatTypes is not true, thus custom format type and format for channel ${channel} are dropped.`; + } + function projectionOverridden(opt) { + const { + parentProjection, + projection + } = opt; + return `Layer's shared projection ${stringify(parentProjection)} is overridden by a child projection ${stringify(projection)}.`; + } + const REPLACE_ANGLE_WITH_THETA = 'Arc marks uses theta channel rather than angle, replacing angle with theta.'; + function offsetNestedInsideContinuousPositionScaleDropped(mainChannel) { + return `${mainChannel}Offset dropped because ${mainChannel} is continuous`; + } + function primitiveChannelDef(channel, type, value) { + return `Channel ${channel} is a ${type}. Converted to {value: ${stringify(value)}}.`; + } + function invalidFieldType(type) { + return `Invalid field type "${type}".`; + } + function invalidFieldTypeForCountAggregate(type, aggregate) { + return `Invalid field type "${type}" for aggregate: "${aggregate}", using "quantitative" instead.`; + } + function invalidAggregate(aggregate) { + return `Invalid aggregation operator "${aggregate}".`; + } + function droppingColor(type, opt) { + const { + fill, + stroke + } = opt; + return `Dropping color ${type} as the plot also has ${fill && stroke ? 'fill and stroke' : fill ? 'fill' : 'stroke'}.`; + } + function relativeBandSizeNotSupported(sizeChannel) { + return `Position range does not support relative band size for ${sizeChannel}.`; + } + function emptyFieldDef(fieldDef, channel) { + return `Dropping ${stringify(fieldDef)} from channel "${channel}" since it does not contain any data field, datum, value, or signal.`; + } + const LINE_WITH_VARYING_SIZE = 'Line marks cannot encode size with a non-groupby field. You may want to use trail marks instead.'; + function incompatibleChannel(channel, markOrFacet, when) { + return `${channel} dropped as it is incompatible with "${markOrFacet}"${''}.`; + } + function invalidEncodingChannel(channel) { + return `${channel}-encoding is dropped as ${channel} is not a valid encoding channel.`; + } + function channelShouldBeDiscrete(channel) { + return `${channel} encoding should be discrete (ordinal / nominal / binned).`; + } + function channelShouldBeDiscreteOrDiscretizing(channel) { + return `${channel} encoding should be discrete (ordinal / nominal / binned) or use a discretizing scale (e.g. threshold).`; + } + function facetChannelDropped(channels) { + return `Facet encoding dropped as ${channels.join(' and ')} ${channels.length > 1 ? 'are' : 'is'} also specified.`; + } + function discreteChannelCannotEncode(channel, type) { + return `Using discrete channel "${channel}" to encode "${type}" field can be misleading as it does not encode ${type === 'ordinal' ? 'order' : 'magnitude'}.`; + } + + // MARK + + function rangeMarkAlignmentCannotBeExpression(align) { + return `The ${align} for range marks cannot be an expression`; + } + function lineWithRange(hasX2, hasY2) { + const channels = hasX2 && hasY2 ? 'x2 and y2' : hasX2 ? 'x2' : 'y2'; + return `Line mark is for continuous lines and thus cannot be used with ${channels}. We will use the rule mark (line segments) instead.`; + } + function orientOverridden(original, actual) { + return `Specified orient "${original}" overridden with "${actual}".`; + } + function cannotUseScalePropertyWithNonColor(prop) { + return `Cannot use the scale property "${prop}" with non-color channel.`; + } + function cannotUseRelativeBandSizeWithNonBandScale(scaleType) { + return `Cannot use the relative band size with ${scaleType} scale.`; + } + function unaggregateDomainHasNoEffectForRawField(fieldDef) { + return `Using unaggregated domain with raw field has no effect (${stringify(fieldDef)}).`; + } + function unaggregateDomainWithNonSharedDomainOp(aggregate) { + return `Unaggregated domain not applicable for "${aggregate}" since it produces values outside the origin domain of the source data.`; + } + function unaggregatedDomainWithLogScale(fieldDef) { + return `Unaggregated domain is currently unsupported for log scale (${stringify(fieldDef)}).`; + } + function cannotApplySizeToNonOrientedMark(mark) { + return `Cannot apply size to non-oriented mark "${mark}".`; + } + function scaleTypeNotWorkWithChannel(channel, scaleType, defaultScaleType) { + return `Channel "${channel}" does not work with "${scaleType}" scale. We are using "${defaultScaleType}" scale instead.`; + } + function scaleTypeNotWorkWithFieldDef(scaleType, defaultScaleType) { + return `FieldDef does not work with "${scaleType}" scale. We are using "${defaultScaleType}" scale instead.`; + } + function scalePropertyNotWorkWithScaleType(scaleType, propName, channel) { + return `${channel}-scale's "${propName}" is dropped as it does not work with ${scaleType} scale.`; + } + function stepDropped(channel) { + return `The step for "${channel}" is dropped because the ${channel === 'width' ? 'x' : 'y'} is continuous.`; + } + function mergeConflictingProperty(property, propertyOf, v1, v2) { + return `Conflicting ${propertyOf.toString()} property "${property.toString()}" (${stringify(v1)} and ${stringify(v2)}). Using ${stringify(v1)}.`; + } + function mergeConflictingDomainProperty(property, propertyOf, v1, v2) { + return `Conflicting ${propertyOf.toString()} property "${property.toString()}" (${stringify(v1)} and ${stringify(v2)}). Using the union of the two domains.`; + } + function independentScaleMeansIndependentGuide(channel) { + return `Setting the scale to be independent for "${channel}" means we also have to set the guide (axis or legend) to be independent.`; + } + function domainSortDropped(sort) { + return `Dropping sort property ${stringify(sort)} as unioned domains only support boolean or op "count", "min", and "max".`; + } + const MORE_THAN_ONE_SORT = 'Domains that should be unioned has conflicting sort properties. Sort will be set to true.'; + const FACETED_INDEPENDENT_DIFFERENT_SOURCES = '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.'; + const FACETED_INDEPENDENT_SAME_FIELDS_DIFFERENT_SOURCES = 'Detected faceted independent scales that union domain of the same fields from different source. We will assume that this is the same field from a different fork of the same data source. However, if this is not the case, the result view size may be incorrect.'; + const FACETED_INDEPENDENT_SAME_SOURCE = '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.'; + + // STACK + function cannotStackRangedMark(channel) { + return `Cannot stack "${channel}" if there is already "${channel}2".`; + } + function stackNonLinearScale(scaleType) { + return `Stack is applied to a non-linear scale (${scaleType}).`; + } + function stackNonSummativeAggregate(aggregate) { + return `Stacking is applied even though the aggregate function is non-summative ("${aggregate}").`; + } + + // TIMEUNIT + function invalidTimeUnit(unitName, value) { + return `Invalid ${unitName}: ${stringify(value)}.`; + } + function droppedDay(d) { + return `Dropping day from datetime ${stringify(d)} as day cannot be combined with other units.`; + } + function errorBarCenterAndExtentAreNotNeeded(center, extent) { + return `${extent ? 'extent ' : ''}${extent && center ? 'and ' : ''}${center ? 'center ' : ''}${extent && center ? 'are ' : 'is '}not needed when data are aggregated.`; + } + function errorBarCenterIsUsedWithWrongExtent(center, extent, mark) { + return `${center} is not usually used with ${extent} for ${mark}.`; + } + function errorBarContinuousAxisHasCustomizedAggregate(aggregate, compositeMark) { + return `Continuous axis should not have customized aggregation function ${aggregate}; ${compositeMark} already agregates the axis.`; + } + function errorBand1DNotSupport(property) { + return `1D error band does not support ${property}.`; + } + + // CHANNEL + function channelRequiredForBinned(channel) { + return `Channel ${channel} is required for "binned" bin.`; + } + function channelShouldNotBeUsedForBinned(channel) { + return `Channel ${channel} should not be used with "binned" bin.`; + } + function domainRequiredForThresholdScale(channel) { + return `Domain for ${channel} is required for threshold scale.`; + } + + /** + * Vega-Lite's singleton logger utility. + */ + + + /** + * Main (default) Vega Logger instance for Vega-Lite. + */ + const main = vega.logger(vega.Warn); + let current = main; + + /** + * Set the singleton logger to be a custom logger. + */ + function set(newLogger) { + current = newLogger; + return current; + } + + /** + * Reset the main logger to use the default Vega Logger. + */ + function reset() { + current = main; + return current; + } + function warn() { + current.warn(...arguments); + } + function debug() { + current.debug(...arguments); + } + + // DateTime definition object + + + /** + * @minimum 1 + * @maximum 12 + * @TJS-type integer + */ + + /** + * @minimum 1 + * @maximum 7 + */ + + /** + * Object for defining datetime in Vega-Lite Filter. + * If both month and quarter are provided, month has higher precedence. + * `day` cannot be combined with other date. + * We accept string for month and day names. + */ + + /** + * Internal Object for defining datetime expressions. + * This is an expression version of DateTime. + * If both month and quarter are provided, month has higher precedence. + * `day` cannot be combined with other date. + */ + + function isDateTime(o) { + if (o && vega.isObject(o)) { + for (const part of TIMEUNIT_PARTS) { + if (part in o) { + return true; + } + } + } + return false; + } + const MONTHS = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december']; + const SHORT_MONTHS = MONTHS.map(m => m.substr(0, 3)); + const DAYS = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; + const SHORT_DAYS = DAYS.map(d => d.substr(0, 3)); + function normalizeQuarter(q) { + if (isNumeric(q)) { + q = +q; + } + if (vega.isNumber(q)) { + if (q > 4) { + warn(invalidTimeUnit('quarter', q)); + } + // We accept 1-based quarter, so need to readjust to 0-based quarter + return q - 1; + } else { + // Invalid quarter + throw new Error(invalidTimeUnit('quarter', q)); + } + } + function normalizeMonth(m) { + if (isNumeric(m)) { + m = +m; + } + if (vega.isNumber(m)) { + // We accept 1-based month, so need to readjust to 0-based month + return m - 1; + } else { + const lowerM = m.toLowerCase(); + const monthIndex = MONTHS.indexOf(lowerM); + if (monthIndex !== -1) { + return monthIndex; // 0 for january, ... + } + const shortM = lowerM.substr(0, 3); + const shortMonthIndex = SHORT_MONTHS.indexOf(shortM); + if (shortMonthIndex !== -1) { + return shortMonthIndex; + } + + // Invalid month + throw new Error(invalidTimeUnit('month', m)); + } + } + function normalizeDay(d) { + if (isNumeric(d)) { + d = +d; + } + if (vega.isNumber(d)) { + // mod so that this can be both 0-based where 0 = sunday + // and 1-based where 7=sunday + return d % 7; + } else { + const lowerD = d.toLowerCase(); + const dayIndex = DAYS.indexOf(lowerD); + if (dayIndex !== -1) { + return dayIndex; // 0 for january, ... + } + const shortD = lowerD.substr(0, 3); + const shortDayIndex = SHORT_DAYS.indexOf(shortD); + if (shortDayIndex !== -1) { + return shortDayIndex; + } + // Invalid day + throw new Error(invalidTimeUnit('day', d)); + } + } + + /** + * @param d the date. + * @param normalize whether to normalize quarter, month, day. This should probably be true if d is a DateTime. + * @returns array of date time parts [year, month, day, hours, minutes, seconds, milliseconds] + */ + function dateTimeParts(d, normalize) { + const parts = []; + if (normalize && d.day !== undefined) { + if (keys(d).length > 1) { + warn(droppedDay(d)); + d = duplicate(d); + delete d.day; + } + } + if (d.year !== undefined) { + parts.push(d.year); + } else { + // Just like Vega's timeunit transform, set default year to 2012, so domain conversion will be compatible with Vega + // Note: 2012 is a leap year (and so the date February 29 is respected) that begins on a Sunday (and so days of the week will order properly at the beginning of the year). + parts.push(2012); + } + if (d.month !== undefined) { + const month = normalize ? normalizeMonth(d.month) : d.month; + parts.push(month); + } else if (d.quarter !== undefined) { + const quarter = normalize ? normalizeQuarter(d.quarter) : d.quarter; + parts.push(vega.isNumber(quarter) ? quarter * 3 : `${quarter}*3`); + } else { + parts.push(0); // months start at zero in JS + } + if (d.date !== undefined) { + parts.push(d.date); + } else if (d.day !== undefined) { + // HACK: Day only works as a standalone unit + // This is only correct because we always set year to 2006 for day + const day = normalize ? normalizeDay(d.day) : d.day; + parts.push(vega.isNumber(day) ? day + 1 : `${day}+1`); + } else { + parts.push(1); // Date starts at 1 in JS + } + + // Note: can't use TimeUnit enum here as importing it will create + // circular dependency problem! + for (const timeUnit of ['hours', 'minutes', 'seconds', 'milliseconds']) { + const unit = d[timeUnit]; + parts.push(typeof unit === 'undefined' ? 0 : unit); + } + return parts; + } + + /** + * Return Vega expression for a date time. + * + * @param d the date time. + * @returns the Vega expression. + */ + function dateTimeToExpr(d) { + const parts = dateTimeParts(d, true); + const string = parts.join(', '); + if (d.utc) { + return `utc(${string})`; + } else { + return `datetime(${string})`; + } + } + + /** + * Return Vega expression for a date time expression. + * + * @param d the internal date time object with expression. + * @returns the Vega expression. + */ + function dateTimeExprToExpr(d) { + const parts = dateTimeParts(d, false); + const string = parts.join(', '); + if (d.utc) { + return `utc(${string})`; + } else { + return `datetime(${string})`; + } + } + + /** + * @param d the date time. + * @returns the timestamp. + */ + function dateTimeToTimestamp(d) { + const parts = dateTimeParts(d, true); + if (d.utc) { + return +new Date(Date.UTC(...parts)); + } else { + return +new Date(...parts); + } + } + + /** Time Unit that only corresponds to only one part of Date objects. */ + const LOCAL_SINGLE_TIMEUNIT_INDEX = { + year: 1, + quarter: 1, + month: 1, + week: 1, + day: 1, + dayofyear: 1, + date: 1, + hours: 1, + minutes: 1, + seconds: 1, + milliseconds: 1 + }; + const TIMEUNIT_PARTS = keys(LOCAL_SINGLE_TIMEUNIT_INDEX); + function isLocalSingleTimeUnit(timeUnit) { + return !!LOCAL_SINGLE_TIMEUNIT_INDEX[timeUnit]; + } + function isBinnedTimeUnit(timeUnit) { + if (vega.isObject(timeUnit)) { + return timeUnit.binned; + } + return isBinnedTimeUnitString(timeUnit); + } + function isBinnedTimeUnitString(timeUnit) { + return timeUnit && timeUnit.startsWith('binned'); + } + function isUTCTimeUnit(t) { + return t.startsWith('utc'); + } + function getLocalTimeUnitFromUTCTimeUnit(t) { + return t.substring(3); + } + + /** + * Time Unit Params for encoding predicate, which can specified if the data is already "binned". + */ + + // matches vega time unit format specifier + + // In order of increasing specificity + const VEGALITE_TIMEFORMAT = { + 'year-month': '%b %Y ', + 'year-month-date': '%b %d, %Y ' + }; + function getTimeUnitParts(timeUnit) { + return TIMEUNIT_PARTS.filter(part => containsTimeUnit(timeUnit, part)); + } + function getSmallestTimeUnitPart(timeUnit) { + const parts = getTimeUnitParts(timeUnit); + return parts[parts.length - 1]; + } + + /** Returns true if fullTimeUnit contains the timeUnit, false otherwise. */ + function containsTimeUnit(fullTimeUnit, timeUnit) { + const index = fullTimeUnit.indexOf(timeUnit); + if (index < 0) { + return false; + } + + // exclude milliseconds + if (index > 0 && timeUnit === 'seconds' && fullTimeUnit.charAt(index - 1) === 'i') { + return false; + } + + // exclude dayofyear + if (fullTimeUnit.length > index + 3 && timeUnit === 'day' && fullTimeUnit.charAt(index + 3) === 'o') { + return false; + } + if (index > 0 && timeUnit === 'year' && fullTimeUnit.charAt(index - 1) === 'f') { + return false; + } + return true; + } + + /** + * Returns Vega expression for a given timeUnit and fieldRef + */ + function fieldExpr(fullTimeUnit, field) { + let { + end + } = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : { + end: false + }; + const fieldRef = accessPathWithDatum(field); + const utc = isUTCTimeUnit(fullTimeUnit) ? 'utc' : ''; + function func(timeUnit) { + if (timeUnit === 'quarter') { + // quarter starting at 0 (0,3,6,9). + return `(${utc}quarter(${fieldRef})-1)`; + } else { + return `${utc}${timeUnit}(${fieldRef})`; + } + } + let lastTimeUnit; + const dateExpr = {}; + for (const part of TIMEUNIT_PARTS) { + if (containsTimeUnit(fullTimeUnit, part)) { + dateExpr[part] = func(part); + lastTimeUnit = part; + } + } + if (end) { + dateExpr[lastTimeUnit] += '+1'; + } + return dateTimeExprToExpr(dateExpr); + } + function timeUnitSpecifierExpression(timeUnit) { + if (!timeUnit) { + return undefined; + } + const timeUnitParts = getTimeUnitParts(timeUnit); + return `timeUnitSpecifier(${stringify(timeUnitParts)}, ${stringify(VEGALITE_TIMEFORMAT)})`; + } + + /** + * Returns the signal expression used for axis labels for a time unit. + */ + function formatExpression(timeUnit, field, isUTCScale) { + if (!timeUnit) { + return undefined; + } + const expr = timeUnitSpecifierExpression(timeUnit); + + // We only use utcFormat for utc scale + // For utc time units, the data is already converted as a part of timeUnit transform. + // Thus, utc time units should use timeFormat to avoid shifting the time twice. + const utc = isUTCScale || isUTCTimeUnit(timeUnit); + return `${utc ? 'utc' : 'time'}Format(${field}, ${expr})`; + } + function normalizeTimeUnit(timeUnit) { + if (!timeUnit) { + return undefined; + } + let params; + if (vega.isString(timeUnit)) { + if (isBinnedTimeUnitString(timeUnit)) { + params = { + unit: timeUnit.substring(6), + binned: true + }; + } else { + params = { + unit: timeUnit + }; + } + } else if (vega.isObject(timeUnit)) { + params = { + ...timeUnit, + ...(timeUnit.unit ? { + unit: timeUnit.unit + } : {}) + }; + } + if (isUTCTimeUnit(params.unit)) { + params.utc = true; + params.unit = getLocalTimeUnitFromUTCTimeUnit(params.unit); + } + return params; + } + function timeUnitToString(tu) { + const { + utc, + ...rest + } = normalizeTimeUnit(tu); + if (rest.unit) { + return (utc ? 'utc' : '') + keys(rest).map(p => varName(`${p === 'unit' ? '' : `_${p}_`}${rest[p]}`)).join(''); + } else { + // when maxbins is specified instead of units + return (utc ? 'utc' : '') + 'timeunit' + keys(rest).map(p => varName(`_${p}_${rest[p]}`)).join(''); + } + } + function durationExpr(timeUnit) { + let wrap = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x => x; + const normalizedTimeUnit = normalizeTimeUnit(timeUnit); + const smallestUnitPart = getSmallestTimeUnitPart(normalizedTimeUnit.unit); + if (smallestUnitPart && smallestUnitPart !== 'day') { + const startDate = { + year: 2001, + // pick a non-leap year + month: 1, + date: 1, + hours: 0, + minutes: 0, + seconds: 0, + milliseconds: 0 + }; + const { + step, + part + } = getDateTimePartAndStep(smallestUnitPart, normalizedTimeUnit.step); + const endDate = { + ...startDate, + [part]: +startDate[part] + step + }; + + // Calculate timestamp duration for the smallest unit listed + return `${wrap(dateTimeToExpr(endDate))} - ${wrap(dateTimeToExpr(startDate))}`; + } + return undefined; + } + const DATE_PARTS = { + year: 1, + month: 1, + date: 1, + hours: 1, + minutes: 1, + seconds: 1, + milliseconds: 1 + }; + function isDatePart(timeUnit) { + return !!DATE_PARTS[timeUnit]; + } + function getDateTimePartAndStep(timeUnit) { + let step = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1; + if (isDatePart(timeUnit)) { + return { + part: timeUnit, + step + }; + } + switch (timeUnit) { + case 'day': + case 'dayofyear': + return { + part: 'date', + step + }; + case 'quarter': + return { + part: 'month', + step: step * 3 + }; + case 'week': + return { + part: 'date', + step: step * 7 + }; + } + } + + function isSelectionPredicate(predicate) { + return predicate?.['param']; + } + function isFieldEqualPredicate(predicate) { + return !!predicate?.field && predicate.equal !== undefined; + } + function isFieldLTPredicate(predicate) { + return !!predicate?.field && predicate.lt !== undefined; + } + function isFieldLTEPredicate(predicate) { + return !!predicate?.field && predicate.lte !== undefined; + } + function isFieldGTPredicate(predicate) { + return !!predicate?.field && predicate.gt !== undefined; + } + function isFieldGTEPredicate(predicate) { + return !!predicate?.field && predicate.gte !== undefined; + } + function isFieldRangePredicate(predicate) { + if (predicate?.field) { + if (vega.isArray(predicate.range) && predicate.range.length === 2) { + return true; + } else if (isSignalRef(predicate.range)) { + return true; + } + } + return false; + } + function isFieldOneOfPredicate(predicate) { + return !!predicate?.field && (vega.isArray(predicate.oneOf) || vega.isArray(predicate.in)) // backward compatibility + ; + } + function isFieldValidPredicate(predicate) { + return !!predicate?.field && predicate.valid !== undefined; + } + function isFieldPredicate(predicate) { + return isFieldOneOfPredicate(predicate) || isFieldEqualPredicate(predicate) || isFieldRangePredicate(predicate) || isFieldLTPredicate(predicate) || isFieldGTPredicate(predicate) || isFieldLTEPredicate(predicate) || isFieldGTEPredicate(predicate); + } + function predicateValueExpr(v, timeUnit) { + return valueExpr(v, { + timeUnit, + wrapTime: true + }); + } + function predicateValuesExpr(vals, timeUnit) { + return vals.map(v => predicateValueExpr(v, timeUnit)); + } + + // This method is used by Voyager. Do not change its behavior without changing Voyager. + function fieldFilterExpression(predicate) { + let useInRange = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; + const { + field + } = predicate; + const normalizedTimeUnit = normalizeTimeUnit(predicate.timeUnit); + const { + unit, + binned + } = normalizedTimeUnit || {}; + const rawFieldExpr = vgField(predicate, { + expr: 'datum' + }); + const fieldExpr$1 = unit ? + // For timeUnit, cast into integer with time() so we can use ===, inrange, indexOf to compare values directly. + // TODO: We calculate timeUnit on the fly here. Consider if we would like to consolidate this with timeUnit pipeline + // TODO: support utc + `time(${!binned ? fieldExpr(unit, field) : rawFieldExpr})` : rawFieldExpr; + if (isFieldEqualPredicate(predicate)) { + return `${fieldExpr$1}===${predicateValueExpr(predicate.equal, unit)}`; + } else if (isFieldLTPredicate(predicate)) { + const upper = predicate.lt; + return `${fieldExpr$1}<${predicateValueExpr(upper, unit)}`; + } else if (isFieldGTPredicate(predicate)) { + const lower = predicate.gt; + return `${fieldExpr$1}>${predicateValueExpr(lower, unit)}`; + } else if (isFieldLTEPredicate(predicate)) { + const upper = predicate.lte; + return `${fieldExpr$1}<=${predicateValueExpr(upper, unit)}`; + } else if (isFieldGTEPredicate(predicate)) { + const lower = predicate.gte; + return `${fieldExpr$1}>=${predicateValueExpr(lower, unit)}`; + } else if (isFieldOneOfPredicate(predicate)) { + return `indexof([${predicateValuesExpr(predicate.oneOf, unit).join(',')}], ${fieldExpr$1}) !== -1`; + } else if (isFieldValidPredicate(predicate)) { + return fieldValidPredicate(fieldExpr$1, predicate.valid); + } else if (isFieldRangePredicate(predicate)) { + const { + range + } = predicate; + const lower = isSignalRef(range) ? { + signal: `${range.signal}[0]` + } : range[0]; + const upper = isSignalRef(range) ? { + signal: `${range.signal}[1]` + } : range[1]; + if (lower !== null && upper !== null && useInRange) { + return 'inrange(' + fieldExpr$1 + ', [' + predicateValueExpr(lower, unit) + ', ' + predicateValueExpr(upper, unit) + '])'; + } + const exprs = []; + if (lower !== null) { + exprs.push(`${fieldExpr$1} >= ${predicateValueExpr(lower, unit)}`); + } + if (upper !== null) { + exprs.push(`${fieldExpr$1} <= ${predicateValueExpr(upper, unit)}`); + } + return exprs.length > 0 ? exprs.join(' && ') : 'true'; + } + + /* istanbul ignore next: it should never reach here */ + throw new Error(`Invalid field predicate: ${stringify(predicate)}`); + } + function fieldValidPredicate(fieldExpr) { + let valid = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; + if (valid) { + return `isValid(${fieldExpr}) && isFinite(+${fieldExpr})`; + } else { + return `!isValid(${fieldExpr}) || !isFinite(+${fieldExpr})`; + } + } + function normalizePredicate$1(f) { + if (isFieldPredicate(f) && f.timeUnit) { + return { + ...f, + timeUnit: normalizeTimeUnit(f.timeUnit) + }; + } + return f; + } + + /** + * Data type based on level of measurement + */ + const Type = { + quantitative: 'quantitative', + ordinal: 'ordinal', + temporal: 'temporal', + nominal: 'nominal', + geojson: 'geojson' + }; + function isContinuous(type) { + return type === 'quantitative' || type === 'temporal'; + } + function isDiscrete$1(type) { + return type === 'ordinal' || type === 'nominal'; + } + const QUANTITATIVE = Type.quantitative; + const ORDINAL = Type.ordinal; + const TEMPORAL = Type.temporal; + const NOMINAL = Type.nominal; + const GEOJSON = Type.geojson; + + /** + * Get full, lowercase type name for a given type. + * @param type + * @return Full type name. + */ + function getFullName(type) { + if (type) { + type = type.toLowerCase(); + switch (type) { + case 'q': + case QUANTITATIVE: + return 'quantitative'; + case 't': + case TEMPORAL: + return 'temporal'; + case 'o': + case ORDINAL: + return 'ordinal'; + case 'n': + case NOMINAL: + return 'nominal'; + case GEOJSON: + return 'geojson'; + } + } + // If we get invalid input, return undefined type. + return undefined; + } + + const ScaleType = { + // Continuous - Quantitative + LINEAR: 'linear', + LOG: 'log', + POW: 'pow', + SQRT: 'sqrt', + SYMLOG: 'symlog', + IDENTITY: 'identity', + SEQUENTIAL: 'sequential', + // Continuous - Time + TIME: 'time', + UTC: 'utc', + // Discretizing scales + QUANTILE: 'quantile', + QUANTIZE: 'quantize', + THRESHOLD: 'threshold', + BIN_ORDINAL: 'bin-ordinal', + // Discrete scales + ORDINAL: 'ordinal', + POINT: 'point', + BAND: 'band' + }; + /** + * Index for scale categories -- only scale of the same categories can be merged together. + * Current implementation is trying to be conservative and avoid merging scale type that might not work together + */ + const SCALE_CATEGORY_INDEX = { + linear: 'numeric', + log: 'numeric', + pow: 'numeric', + sqrt: 'numeric', + symlog: 'numeric', + identity: 'numeric', + sequential: 'numeric', + time: 'time', + utc: 'time', + ordinal: 'ordinal', + 'bin-ordinal': 'bin-ordinal', + // TODO: should bin-ordinal support merging with other + point: 'ordinal-position', + band: 'ordinal-position', + quantile: 'discretizing', + quantize: 'discretizing', + threshold: 'discretizing' + }; + + /** + * Whether the two given scale types can be merged together. + */ + function scaleCompatible(scaleType1, scaleType2) { + const scaleCategory1 = SCALE_CATEGORY_INDEX[scaleType1]; + const scaleCategory2 = SCALE_CATEGORY_INDEX[scaleType2]; + return scaleCategory1 === scaleCategory2 || scaleCategory1 === 'ordinal-position' && scaleCategory2 === 'time' || scaleCategory2 === 'ordinal-position' && scaleCategory1 === 'time'; + } + + /** + * Index for scale precedence -- high score = higher priority for merging. + */ + const SCALE_PRECEDENCE_INDEX = { + // numeric + linear: 0, + log: 1, + pow: 1, + sqrt: 1, + symlog: 1, + identity: 1, + sequential: 1, + // time + time: 0, + utc: 0, + // ordinal-position -- these have higher precedence than continuous scales as they support more types of data + point: 10, + band: 11, + // band has higher precedence as it is better for interaction + // non grouped types + ordinal: 0, + 'bin-ordinal': 0, + quantile: 0, + quantize: 0, + threshold: 0 + }; + + /** + * Return scale categories -- only scale of the same categories can be merged together. + */ + function scaleTypePrecedence(scaleType) { + return SCALE_PRECEDENCE_INDEX[scaleType]; + } + const QUANTITATIVE_SCALES = new Set(['linear', 'log', 'pow', 'sqrt', 'symlog']); + const CONTINUOUS_TO_CONTINUOUS_SCALES = new Set([...QUANTITATIVE_SCALES, 'time', 'utc']); + function isQuantitative(type) { + return QUANTITATIVE_SCALES.has(type); + } + const CONTINUOUS_TO_DISCRETE_SCALES = new Set(['quantile', 'quantize', 'threshold']); + const CONTINUOUS_DOMAIN_SCALES = new Set([...CONTINUOUS_TO_CONTINUOUS_SCALES, ...CONTINUOUS_TO_DISCRETE_SCALES, 'sequential', 'identity']); + const DISCRETE_DOMAIN_SCALES = new Set(['ordinal', 'bin-ordinal', 'point', 'band']); + function hasDiscreteDomain(type) { + return DISCRETE_DOMAIN_SCALES.has(type); + } + function hasContinuousDomain(type) { + return CONTINUOUS_DOMAIN_SCALES.has(type); + } + function isContinuousToContinuous(type) { + return CONTINUOUS_TO_CONTINUOUS_SCALES.has(type); + } + function isContinuousToDiscrete(type) { + return CONTINUOUS_TO_DISCRETE_SCALES.has(type); + } + const defaultScaleConfig = { + pointPadding: 0.5, + barBandPaddingInner: 0.1, + rectBandPaddingInner: 0, + tickBandPaddingInner: 0.25, + bandWithNestedOffsetPaddingInner: 0.2, + bandWithNestedOffsetPaddingOuter: 0.2, + minBandSize: 2, + minFontSize: 8, + maxFontSize: 40, + minOpacity: 0.3, + maxOpacity: 0.8, + // FIXME: revise if these *can* become ratios of width/height step + minSize: 4, + // Point size is area. For square point, 9 = 3 pixel ^ 2, not too small! + + minStrokeWidth: 1, + maxStrokeWidth: 4, + quantileCount: 4, + quantizeCount: 4, + zero: true + }; + function isExtendedScheme(scheme) { + return !vega.isString(scheme) && !!scheme['name']; + } + function isParameterDomain(domain) { + return domain?.['param']; + } + function isDomainUnionWith(domain) { + return domain?.['unionWith']; + } + function isFieldRange(range) { + return vega.isObject(range) && 'field' in range; + } + const SCALE_PROPERTY_INDEX = { + type: 1, + domain: 1, + domainMax: 1, + domainMin: 1, + domainMid: 1, + domainRaw: 1, + align: 1, + range: 1, + rangeMax: 1, + rangeMin: 1, + scheme: 1, + bins: 1, + // Other properties + reverse: 1, + round: 1, + // quantitative / time + clamp: 1, + nice: 1, + // quantitative + base: 1, + exponent: 1, + constant: 1, + interpolate: 1, + zero: 1, + // zero depends on domain + // band/point + padding: 1, + paddingInner: 1, + paddingOuter: 1 + }; + const { + type, + domain: domain$1, + range, + rangeMax, + rangeMin, + scheme, + ...NON_TYPE_DOMAIN_RANGE_VEGA_SCALE_PROPERTY_INDEX + } = SCALE_PROPERTY_INDEX; + const NON_TYPE_DOMAIN_RANGE_VEGA_SCALE_PROPERTIES = keys(NON_TYPE_DOMAIN_RANGE_VEGA_SCALE_PROPERTY_INDEX); + function scaleTypeSupportProperty(scaleType, propName) { + switch (propName) { + case 'type': + case 'domain': + case 'reverse': + case 'range': + return true; + case 'scheme': + case 'interpolate': + return !['point', 'band', 'identity'].includes(scaleType); + case 'bins': + return !['point', 'band', 'identity', 'ordinal'].includes(scaleType); + case 'round': + return isContinuousToContinuous(scaleType) || scaleType === 'band' || scaleType === 'point'; + case 'padding': + case 'rangeMin': + case 'rangeMax': + return isContinuousToContinuous(scaleType) || ['point', 'band'].includes(scaleType); + case 'paddingOuter': + case 'align': + return ['point', 'band'].includes(scaleType); + case 'paddingInner': + return scaleType === 'band'; + case 'domainMax': + case 'domainMid': + case 'domainMin': + case 'domainRaw': + case 'clamp': + return isContinuousToContinuous(scaleType); + case 'nice': + return isContinuousToContinuous(scaleType) || scaleType === 'quantize' || scaleType === 'threshold'; + case 'exponent': + return scaleType === 'pow'; + case 'base': + return scaleType === 'log'; + case 'constant': + return scaleType === 'symlog'; + case 'zero': + return hasContinuousDomain(scaleType) && !contains(['log', + // log scale cannot have zero value + 'time', 'utc', + // zero is not meaningful for time + 'threshold', + // threshold requires custom domain so zero does not matter + 'quantile' // quantile depends on distribution so zero does not matter + ], scaleType); + } + } + + /** + * Returns undefined if the input channel supports the input scale property name + */ + function channelScalePropertyIncompatability(channel, propName) { + switch (propName) { + case 'interpolate': + case 'scheme': + case 'domainMid': + if (!isColorChannel(channel)) { + return cannotUseScalePropertyWithNonColor(propName); + } + return undefined; + case 'align': + case 'type': + case 'bins': + case 'domain': + case 'domainMax': + case 'domainMin': + case 'domainRaw': + case 'range': + case 'base': + case 'exponent': + case 'constant': + case 'nice': + case 'padding': + case 'paddingInner': + case 'paddingOuter': + case 'rangeMax': + case 'rangeMin': + case 'reverse': + case 'round': + case 'clamp': + case 'zero': + return undefined; + // GOOD! + } + } + function scaleTypeSupportDataType(specifiedType, fieldDefType) { + if (contains([ORDINAL, NOMINAL], fieldDefType)) { + return specifiedType === undefined || hasDiscreteDomain(specifiedType); + } else if (fieldDefType === TEMPORAL) { + return contains([ScaleType.TIME, ScaleType.UTC, undefined], specifiedType); + } else if (fieldDefType === QUANTITATIVE) { + return isQuantitative(specifiedType) || isContinuousToDiscrete(specifiedType) || specifiedType === undefined; + } + return true; + } + function channelSupportScaleType(channel, scaleType) { + let hasNestedOffsetScale = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + if (!isScaleChannel(channel)) { + return false; + } + switch (channel) { + case X: + case Y: + case XOFFSET: + case YOFFSET: + case THETA: + case RADIUS: + if (isContinuousToContinuous(scaleType)) { + return true; + } else if (scaleType === 'band') { + return true; + } else if (scaleType === 'point') { + /* + Point scale can't be use if the position has a nested offset scale + because if there is a nested scale, then it's band. + */ + return !hasNestedOffsetScale; + } + return false; + case SIZE: // TODO: size and opacity can support ordinal with more modification + case STROKEWIDTH: + case OPACITY: + case FILLOPACITY: + case STROKEOPACITY: + case ANGLE: + // Although it generally doesn't make sense to use band with size and opacity, + // it can also work since we use band: 0.5 to get midpoint. + return isContinuousToContinuous(scaleType) || isContinuousToDiscrete(scaleType) || contains(['band', 'point', 'ordinal'], scaleType); + case COLOR: + case FILL: + case STROKE: + return scaleType !== 'band'; + // band does not make sense with color + case STROKEDASH: + case SHAPE: + return scaleType === 'ordinal' || isContinuousToDiscrete(scaleType); + } + } + + /** + * Mixins for Vega-Lite Spec's Mark Definiton (to add mark.invalid) + */ + + /** + * Mixins for Vega-Lite Spec's config.scale + */ + + function isScaleInvalidDataIncludeAsValue(invalidDataMode) { + return vega.isObject(invalidDataMode) && 'value' in invalidDataMode; + } + + /** + * All types of primitive marks. + */ + const Mark = { + arc: 'arc', + area: 'area', + bar: 'bar', + image: 'image', + line: 'line', + point: 'point', + rect: 'rect', + rule: 'rule', + text: 'text', + tick: 'tick', + trail: 'trail', + circle: 'circle', + square: 'square', + geoshape: 'geoshape' + }; + const ARC = Mark.arc; + const AREA = Mark.area; + const BAR = Mark.bar; + const IMAGE = Mark.image; + const LINE = Mark.line; + const POINT = Mark.point; + const RECT = Mark.rect; + const RULE = Mark.rule; + const TEXT = Mark.text; + const TICK = Mark.tick; + const TRAIL = Mark.trail; + const CIRCLE = Mark.circle; + const SQUARE = Mark.square; + const GEOSHAPE = Mark.geoshape; + function isPathMark(m) { + return ['line', 'area', 'trail'].includes(m); + } + function isRectBasedMark(m) { + return ['rect', 'bar', 'image', 'arc' /* arc is rect/interval in polar coordinate */].includes(m); + } + const PRIMITIVE_MARKS = new Set(keys(Mark)); + function isMarkDef(mark) { + return mark['type']; + } + const STROKE_CONFIG = ['stroke', 'strokeWidth', 'strokeDash', 'strokeDashOffset', 'strokeOpacity', 'strokeJoin', 'strokeMiterLimit']; + const FILL_CONFIG = ['fill', 'fillOpacity']; + const FILL_STROKE_CONFIG = [...STROKE_CONFIG, ...FILL_CONFIG]; + const VL_ONLY_MARK_CONFIG_INDEX = { + color: 1, + filled: 1, + invalid: 1, + order: 1, + radius2: 1, + theta2: 1, + timeUnitBandSize: 1, + timeUnitBandPosition: 1 + }; + const VL_ONLY_MARK_CONFIG_PROPERTIES = keys(VL_ONLY_MARK_CONFIG_INDEX); + const VL_ONLY_MARK_SPECIFIC_CONFIG_PROPERTY_INDEX = { + area: ['line', 'point'], + bar: ['binSpacing', 'continuousBandSize', 'discreteBandSize', 'minBandSize'], + rect: ['binSpacing', 'continuousBandSize', 'discreteBandSize', 'minBandSize'], + line: ['point'], + tick: ['bandSize', 'thickness'] + }; + const defaultMarkConfig = { + color: '#4c78a8', + invalid: 'break-paths-show-path-domains', + timeUnitBandSize: 1 + }; + + // TODO: replace with MarkConfigMixins[Mark] once https://github.com/vega/ts-json-schema-generator/issues/344 is fixed + + const MARK_CONFIG_INDEX = { + mark: 1, + arc: 1, + area: 1, + bar: 1, + circle: 1, + image: 1, + line: 1, + point: 1, + rect: 1, + rule: 1, + square: 1, + text: 1, + tick: 1, + trail: 1, + geoshape: 1 + }; + const MARK_CONFIGS = keys(MARK_CONFIG_INDEX); + function isRelativeBandSize(o) { + return o && o['band'] != undefined; + } + const BAR_CORNER_RADIUS_INDEX = { + horizontal: ['cornerRadiusTopRight', 'cornerRadiusBottomRight'], + vertical: ['cornerRadiusTopLeft', 'cornerRadiusTopRight'] + }; + + // Point/Line OverlayMixins are only for area, line, and trail but we don't want to declare multiple types of MarkDef + + const DEFAULT_RECT_BAND_SIZE = 5; + const defaultBarConfig = { + binSpacing: 1, + continuousBandSize: DEFAULT_RECT_BAND_SIZE, + minBandSize: 0.25, + timeUnitBandPosition: 0.5 + }; + const defaultRectConfig = { + binSpacing: 0, + continuousBandSize: DEFAULT_RECT_BAND_SIZE, + minBandSize: 0.25, + timeUnitBandPosition: 0.5 + }; + const defaultTickConfig = { + thickness: 1 + }; + function getMarkType(m) { + return isMarkDef(m) ? m.type : m; + } + + function normalizeInvalidDataMode(mode, _ref) { + let { + isPath + } = _ref; + if (mode === undefined || mode === 'break-paths-show-path-domains') { + return isPath ? 'break-paths-show-domains' : 'filter'; + } else if (mode === null) { + return 'show'; + } + return mode; + } + + function getScaleInvalidDataMode(_ref) { + let { + markDef, + config, + scaleChannel, + scaleType, + isCountAggregate + } = _ref; + if (!scaleType || !hasContinuousDomain(scaleType) || isCountAggregate) { + // - Discrete scales can always display null as another category + // - Count cannot output null values + return 'always-valid'; + } + const invalidMode = normalizeInvalidDataMode(getMarkPropOrConfig('invalid', markDef, config), { + isPath: isPathMark(markDef.type) + }); + const scaleOutputForInvalid = config.scale?.invalid?.[scaleChannel]; + if (scaleOutputForInvalid !== undefined) { + // Regardless of the current invalid mode, if the channel has a default value, we consider the field valid. + return 'show'; + } + return invalidMode; + } + function shouldBreakPath(mode) { + return mode === 'break-paths-filter-domains' || mode === 'break-paths-show-domains'; + } + + function scaledZeroOrMinOrMax(_ref) { + let { + scaleName, + scale, + mode + } = _ref; + const domain = `domain('${scaleName}')`; + if (!scale || !scaleName) { + return undefined; + } + const min = `${domain}[0]`; + const max = `peek(${domain})`; // peek = the last item of the array + + // If there is a scale (and hence its name) + const domainHasZero = scale.domainHasZero(); + // zeroOrMin or zeroOrMax mode + if (domainHasZero === 'definitely') { + return { + scale: scaleName, + value: 0 + }; + } else if (domainHasZero === 'maybe') { + const nonZeroValue = mode === 'zeroOrMin' ? min : max; + return { + signal: `scale('${scaleName}', inrange(0, ${domain}) ? 0 : ${nonZeroValue})` + }; + } else { + // domainHasZero === 'definitely-not' + return { + signal: `scale('${scaleName}', ${mode === 'zeroOrMin' ? min : max})` + }; + } + } + + function getConditionalValueRefForIncludingInvalidValue(_ref) { + let { + scaleChannel, + channelDef, + scale, + scaleName, + markDef, + config + } = _ref; + const scaleType = scale?.get('type'); + const fieldDef = getFieldDef(channelDef); + const isCountAggregate = isCountingAggregateOp(fieldDef?.aggregate); + const invalidDataMode = getScaleInvalidDataMode({ + scaleChannel, + markDef, + config, + scaleType, + isCountAggregate + }); + if (fieldDef && invalidDataMode === 'show') { + const includeAs = config.scale.invalid?.[scaleChannel] ?? 'zero-or-min'; + return { + test: fieldValidPredicate(vgField(fieldDef, { + expr: 'datum' + }), false), + ...refForInvalidValues(includeAs, scale, scaleName) + }; + } + return undefined; + } + function refForInvalidValues(includeAs, scale, scaleName) { + if (isScaleInvalidDataIncludeAsValue(includeAs)) { + const { + value + } = includeAs; + return isSignalRef(value) ? { + signal: value.signal + } : { + value + }; + } + return scaledZeroOrMinOrMax({ + scale, + scaleName, + mode: 'zeroOrMin' + }); + } + + /** + * Utility files for producing Vega ValueRef for marks + */ + + function midPointRefWithPositionInvalidTest(params) { + const { + channel, + channelDef, + markDef, + scale, + scaleName, + config + } = params; + const scaleChannel = getMainRangeChannel(channel); + const mainRef = midPoint(params); + const valueRefForIncludingInvalid = getConditionalValueRefForIncludingInvalidValue({ + scaleChannel, + channelDef, + scale, + scaleName, + markDef, + config + }); + return valueRefForIncludingInvalid !== undefined ? [valueRefForIncludingInvalid, mainRef] : mainRef; + } + function datumDefToExpr(datumDef) { + const { + datum + } = datumDef; + if (isDateTime(datum)) { + return dateTimeToExpr(datum); + } + return `${stringify(datum)}`; + } + function valueRefForFieldOrDatumDef(fieldDef, scaleName, opt, encode) { + const ref = {}; + if (scaleName) { + ref.scale = scaleName; + } + if (isDatumDef(fieldDef)) { + const { + datum + } = fieldDef; + if (isDateTime(datum)) { + ref.signal = dateTimeToExpr(datum); + } else if (isSignalRef(datum)) { + ref.signal = datum.signal; + } else if (isExprRef(datum)) { + ref.signal = datum.expr; + } else { + ref.value = datum; + } + } else { + ref.field = vgField(fieldDef, opt); + } + if (encode) { + const { + offset, + band + } = encode; + if (offset) { + ref.offset = offset; + } + if (band) { + ref.band = band; + } + } + return ref; + } + + /** + * Signal that returns the middle of a bin from start and end field. Should only be used with x and y. + */ + function interpolatedSignalRef(_ref) { + let { + scaleName, + fieldOrDatumDef, + fieldOrDatumDef2, + offset, + startSuffix, + endSuffix = 'end', + bandPosition = 0.5 + } = _ref; + const expr = !isSignalRef(bandPosition) && 0 < bandPosition && bandPosition < 1 ? 'datum' : undefined; + const start = vgField(fieldOrDatumDef, { + expr, + suffix: startSuffix + }); + const end = fieldOrDatumDef2 !== undefined ? vgField(fieldOrDatumDef2, { + expr + }) : vgField(fieldOrDatumDef, { + suffix: endSuffix, + expr + }); + const ref = {}; + if (bandPosition === 0 || bandPosition === 1) { + ref.scale = scaleName; + const field = bandPosition === 0 ? start : end; + ref.field = field; + } else { + const datum = isSignalRef(bandPosition) ? `(1-${bandPosition.signal}) * ${start} + ${bandPosition.signal} * ${end}` : `${1 - bandPosition} * ${start} + ${bandPosition} * ${end}`; + ref.signal = `scale("${scaleName}", ${datum})`; + } + if (offset) { + ref.offset = offset; + } + return ref; + } + function binSizeExpr(_ref2) { + let { + scaleName, + fieldDef + } = _ref2; + const start = vgField(fieldDef, { + expr: 'datum' + }); + const end = vgField(fieldDef, { + expr: 'datum', + suffix: 'end' + }); + return `abs(scale("${scaleName}", ${end}) - scale("${scaleName}", ${start}))`; + } + /** + * @returns {VgValueRef} Value Ref for xc / yc or mid point for other channels. + */ + function midPoint(_ref3) { + let { + channel, + channelDef, + channel2Def, + markDef, + config, + scaleName, + scale, + stack, + offset, + defaultRef, + bandPosition + } = _ref3; + // TODO: datum support + if (channelDef) { + /* istanbul ignore else */ + + if (isFieldOrDatumDef(channelDef)) { + const scaleType = scale?.get('type'); + if (isTypedFieldDef(channelDef)) { + bandPosition ??= getBandPosition({ + fieldDef: channelDef, + fieldDef2: channel2Def, + markDef, + config + }); + const { + bin, + timeUnit, + type + } = channelDef; + if (isBinning(bin) || bandPosition && timeUnit && type === TEMPORAL) { + // Use middle only for x an y to place marks in the center between start and end of the bin range. + // We do not use the mid point for other channels (e.g. size) so that properties of legends and marks match. + if (stack?.impute) { + // For stack, we computed bin_mid so we can impute. + return valueRefForFieldOrDatumDef(channelDef, scaleName, { + binSuffix: 'mid' + }, { + offset + }); + } + if (bandPosition && !hasDiscreteDomain(scaleType)) { + // if band = 0, no need to call interpolation + // For non-stack, we can just calculate bin mid on the fly using signal. + return interpolatedSignalRef({ + scaleName, + fieldOrDatumDef: channelDef, + bandPosition, + offset + }); + } + return valueRefForFieldOrDatumDef(channelDef, scaleName, binRequiresRange(channelDef, channel) ? { + binSuffix: 'range' + } : {}, { + offset + }); + } else if (isBinned(bin)) { + if (isFieldDef(channel2Def)) { + return interpolatedSignalRef({ + scaleName, + fieldOrDatumDef: channelDef, + fieldOrDatumDef2: channel2Def, + bandPosition, + offset + }); + } else { + const channel2 = channel === X ? X2 : Y2; + warn(channelRequiredForBinned(channel2)); + } + } + } + return valueRefForFieldOrDatumDef(channelDef, scaleName, hasDiscreteDomain(scaleType) ? { + binSuffix: 'range' + } : {}, + // no need for bin suffix if there is no scale + { + offset, + // For band, to get mid point, need to offset by half of the band + band: scaleType === 'band' ? bandPosition ?? channelDef.bandPosition ?? 0.5 : undefined + }); + } else if (isValueDef(channelDef)) { + const value = channelDef.value; + const offsetMixins = offset ? { + offset + } : {}; + return { + ...widthHeightValueOrSignalRef(channel, value), + ...offsetMixins + }; + } + + // If channelDef is neither field def or value def, it's a condition-only def. + // In such case, we will use default ref. + } + if (vega.isFunction(defaultRef)) { + defaultRef = defaultRef(); + } + if (defaultRef) { + // for non-position, ref could be undefined. + return { + ...defaultRef, + // only include offset when it is non-zero (zero = no offset) + ...(offset ? { + offset + } : {}) + }; + } + return defaultRef; + } + + /** + * Convert special "width" and "height" values in Vega-Lite into Vega value ref. + */ + function widthHeightValueOrSignalRef(channel, value) { + if (contains(['x', 'x2'], channel) && value === 'width') { + return { + field: { + group: 'width' + } + }; + } else if (contains(['y', 'y2'], channel) && value === 'height') { + return { + field: { + group: 'height' + } + }; + } + return signalOrValueRef(value); + } + + function isCustomFormatType(formatType) { + return formatType && formatType !== 'number' && formatType !== 'time'; + } + function customFormatExpr(formatType, field, format) { + return `${formatType}(${field}${format ? `, ${stringify(format)}` : ''})`; + } + const BIN_RANGE_DELIMITER = ' \u2013 '; + function formatSignalRef(_ref) { + let { + fieldOrDatumDef, + format, + formatType, + expr, + normalizeStack, + config + } = _ref; + if (isCustomFormatType(formatType)) { + return formatCustomType({ + fieldOrDatumDef, + format, + formatType, + expr, + config + }); + } + const field = fieldToFormat(fieldOrDatumDef, expr, normalizeStack); + const type = channelDefType(fieldOrDatumDef); + if (format === undefined && formatType === undefined && config.customFormatTypes) { + if (type === 'quantitative') { + if (normalizeStack && config.normalizedNumberFormatType) return formatCustomType({ + fieldOrDatumDef, + format: config.normalizedNumberFormat, + formatType: config.normalizedNumberFormatType, + expr, + config + }); + if (config.numberFormatType) { + return formatCustomType({ + fieldOrDatumDef, + format: config.numberFormat, + formatType: config.numberFormatType, + expr, + config + }); + } + } + if (type === 'temporal' && config.timeFormatType && isFieldDef(fieldOrDatumDef) && fieldOrDatumDef.timeUnit === undefined) { + return formatCustomType({ + fieldOrDatumDef, + format: config.timeFormat, + formatType: config.timeFormatType, + expr, + config + }); + } + } + if (isFieldOrDatumDefForTimeFormat(fieldOrDatumDef)) { + const signal = timeFormatExpression({ + field, + timeUnit: isFieldDef(fieldOrDatumDef) ? normalizeTimeUnit(fieldOrDatumDef.timeUnit)?.unit : undefined, + format, + formatType: config.timeFormatType, + rawTimeFormat: config.timeFormat, + isUTCScale: isScaleFieldDef(fieldOrDatumDef) && fieldOrDatumDef.scale?.type === ScaleType.UTC + }); + return signal ? { + signal + } : undefined; + } + format = numberFormat({ + type, + specifiedFormat: format, + config, + normalizeStack + }); + if (isFieldDef(fieldOrDatumDef) && isBinning(fieldOrDatumDef.bin)) { + const endField = vgField(fieldOrDatumDef, { + expr, + binSuffix: 'end' + }); + return { + signal: binFormatExpression(field, endField, format, formatType, config) + }; + } else if (format || channelDefType(fieldOrDatumDef) === 'quantitative') { + return { + signal: `${formatExpr(field, format)}` + }; + } else { + return { + signal: `isValid(${field}) ? ${field} : ""+${field}` + }; + } + } + function fieldToFormat(fieldOrDatumDef, expr, normalizeStack) { + if (isFieldDef(fieldOrDatumDef)) { + if (normalizeStack) { + return `${vgField(fieldOrDatumDef, { + expr, + suffix: 'end' + })}-${vgField(fieldOrDatumDef, { + expr, + suffix: 'start' + })}`; + } else { + return vgField(fieldOrDatumDef, { + expr + }); + } + } else { + return datumDefToExpr(fieldOrDatumDef); + } + } + function formatCustomType(_ref2) { + let { + fieldOrDatumDef, + format, + formatType, + expr, + normalizeStack, + config, + field + } = _ref2; + field ??= fieldToFormat(fieldOrDatumDef, expr, normalizeStack); + if (field !== 'datum.value' && + // For axis/legend, we can't correctly know the end of the bin from `datum` + isFieldDef(fieldOrDatumDef) && isBinning(fieldOrDatumDef.bin)) { + const endField = vgField(fieldOrDatumDef, { + expr, + binSuffix: 'end' + }); + return { + signal: binFormatExpression(field, endField, format, formatType, config) + }; + } + return { + signal: customFormatExpr(formatType, field, format) + }; + } + function guideFormat(fieldOrDatumDef, type, format, formatType, config, omitTimeFormatConfig) { + if (vega.isString(formatType) && isCustomFormatType(formatType)) { + return undefined; // handled in encode block + } else if (format === undefined && formatType === undefined && config.customFormatTypes) { + if (channelDefType(fieldOrDatumDef) === 'quantitative') { + if (config.normalizedNumberFormatType && isPositionFieldOrDatumDef(fieldOrDatumDef) && fieldOrDatumDef.stack === 'normalize') { + return undefined; // handled in encode block + } + if (config.numberFormatType) { + return undefined; // handled in encode block + } + } + } + if (isPositionFieldOrDatumDef(fieldOrDatumDef) && fieldOrDatumDef.stack === 'normalize' && config.normalizedNumberFormat) { + return numberFormat({ + type: 'quantitative', + config, + normalizeStack: true + }); + } + if (isFieldOrDatumDefForTimeFormat(fieldOrDatumDef)) { + const timeUnit = isFieldDef(fieldOrDatumDef) ? normalizeTimeUnit(fieldOrDatumDef.timeUnit)?.unit : undefined; + if (timeUnit === undefined && config.customFormatTypes && config.timeFormatType) { + return undefined; // hanlded in encode block + } + return timeFormat({ + specifiedFormat: format, + timeUnit, + config, + omitTimeFormatConfig + }); + } + return numberFormat({ + type, + specifiedFormat: format, + config + }); + } + function guideFormatType(formatType, fieldOrDatumDef, scaleType) { + if (formatType && (isSignalRef(formatType) || formatType === 'number' || formatType === 'time')) { + return formatType; + } + if (isFieldOrDatumDefForTimeFormat(fieldOrDatumDef) && scaleType !== 'time' && scaleType !== 'utc') { + return isFieldDef(fieldOrDatumDef) && normalizeTimeUnit(fieldOrDatumDef?.timeUnit)?.utc ? 'utc' : 'time'; + } + return undefined; + } + + /** + * Returns number format for a fieldDef. + */ + function numberFormat(_ref3) { + let { + type, + specifiedFormat, + config, + normalizeStack + } = _ref3; + // Specified format in axis/legend has higher precedence than fieldDef.format + if (vega.isString(specifiedFormat)) { + return specifiedFormat; + } + if (type === QUANTITATIVE) { + // we only apply the default if the field is quantitative + return normalizeStack ? config.normalizedNumberFormat : config.numberFormat; + } + return undefined; + } + + /** + * Returns time format for a fieldDef for use in guides. + */ + function timeFormat(_ref4) { + let { + specifiedFormat, + timeUnit, + config, + omitTimeFormatConfig + } = _ref4; + if (specifiedFormat) { + return specifiedFormat; + } + if (timeUnit) { + return { + signal: timeUnitSpecifierExpression(timeUnit) + }; + } + return omitTimeFormatConfig ? undefined : config.timeFormat; + } + function formatExpr(field, format) { + return `format(${field}, "${format || ''}")`; + } + function binNumberFormatExpr(field, format, formatType, config) { + if (isCustomFormatType(formatType)) { + return customFormatExpr(formatType, field, format); + } + return formatExpr(field, (vega.isString(format) ? format : undefined) ?? config.numberFormat); + } + function binFormatExpression(startField, endField, format, formatType, config) { + if (format === undefined && formatType === undefined && config.customFormatTypes && config.numberFormatType) { + return binFormatExpression(startField, endField, config.numberFormat, config.numberFormatType, config); + } + const start = binNumberFormatExpr(startField, format, formatType, config); + const end = binNumberFormatExpr(endField, format, formatType, config); + return `${fieldValidPredicate(startField, false)} ? "null" : ${start} + "${BIN_RANGE_DELIMITER}" + ${end}`; + } + + /** + * Returns the time expression used for axis/legend labels or text mark for a temporal field + */ + function timeFormatExpression(_ref5) { + let { + field, + timeUnit, + format, + formatType, + rawTimeFormat, + isUTCScale + } = _ref5; + if (!timeUnit || format) { + // If there is no time unit, or if user explicitly specifies format for axis/legend/text. + if (!timeUnit && formatType) { + return `${formatType}(${field}, '${format}')`; + } + format = vega.isString(format) ? format : rawTimeFormat; // only use provided timeFormat if there is no timeUnit. + return `${isUTCScale ? 'utc' : 'time'}Format(${field}, '${format}')`; + } else { + return formatExpression(timeUnit, field, isUTCScale); + } + } + + /** + * A sort definition for transform + */ + + const DEFAULT_SORT_OP = 'min'; + + /** + * A sort definition for sorting a discrete scale in an encoding field definition. + */ + + const SORT_BY_CHANNEL_INDEX = { + x: 1, + y: 1, + color: 1, + fill: 1, + stroke: 1, + strokeWidth: 1, + size: 1, + shape: 1, + fillOpacity: 1, + strokeOpacity: 1, + opacity: 1, + text: 1 + }; + function isSortByChannel(c) { + return c in SORT_BY_CHANNEL_INDEX; + } + function isSortByEncoding(sort) { + return !!sort?.['encoding']; + } + function isSortField(sort) { + return sort && (sort['op'] === 'count' || !!sort['field']); + } + function isSortArray(sort) { + return sort && vega.isArray(sort); + } + + function isFacetMapping(f) { + return 'row' in f || 'column' in f; + } + + /** + * Facet mapping for encoding macro + */ + + function isFacetFieldDef(channelDef) { + return !!channelDef && 'header' in channelDef; + } + + /** + * Base interface for a facet specification. + */ + + /** + * A facet specification without any shortcut / expansion syntax + */ + + function isFacetSpec(spec) { + return 'facet' in spec; + } + + /** + * Definition object for a constant value (primitive value or gradient definition) of an encoding channel. + */ + + /** + * A ValueDef with Condition where either the condition or the value are optional. + * { + * condition: {field: ...} | {value: ...}, + * value: ..., + * } + */ + + /** + * @minProperties 1 + */ + + function isConditionalParameter(c) { + return c['param']; + } + + /** + * A FieldDef with Condition + * { + * condition: {value: ...}, + * field: ..., + * ... + * } + */ + + /** + * A ValueDef with optional Condition + * { + * condition: {field: ...} | {value: ...}, + * value: ..., + * } + */ + + /** + * Reference to a repeated value. + */ + + function isRepeatRef(field) { + return field && !vega.isString(field) && 'repeat' in field; + } + + /** @@hidden */ + + function toFieldDefBase(fieldDef) { + const { + field, + timeUnit, + bin, + aggregate + } = fieldDef; + return { + ...(timeUnit ? { + timeUnit + } : {}), + ...(bin ? { + bin + } : {}), + ...(aggregate ? { + aggregate + } : {}), + field + }; + } + + /** + * Definition object for a data field, its type and transformation of an encoding channel. + */ + + function isSortableFieldDef(fieldDef) { + return 'sort' in fieldDef; + } + + /** + * A field definition of a secondary channel that shares a scale with another primary channel. For example, `x2`, `xError` and `xError2` share the same scale with `x`. + */ + // x2/y2 shouldn't have bin, but we keep bin property for simplicity of the codebase. + + /** + * Field Def without scale (and without bin: "binned" support). + */ + + // Lat long shouldn't have bin, but we keep bin property for simplicity of the codebase. + + function getBandPosition(_ref) { + let { + fieldDef, + fieldDef2, + markDef: mark, + config + } = _ref; + if (isFieldOrDatumDef(fieldDef) && fieldDef.bandPosition !== undefined) { + return fieldDef.bandPosition; + } + if (isFieldDef(fieldDef)) { + const { + timeUnit, + bin + } = fieldDef; + if (timeUnit && !fieldDef2) { + return getMarkConfig('timeUnitBandPosition', mark, config); + } else if (isBinning(bin)) { + return 0.5; + } + } + return undefined; + } + function getBandSize(_ref2) { + let { + channel, + fieldDef, + fieldDef2, + markDef: mark, + config, + scaleType, + useVlSizeChannel + } = _ref2; + const sizeChannel = getSizeChannel(channel); + const size = getMarkPropOrConfig(useVlSizeChannel ? 'size' : sizeChannel, mark, config, { + vgChannel: sizeChannel + }); + if (size !== undefined) { + return size; + } + if (isFieldDef(fieldDef)) { + const { + timeUnit, + bin + } = fieldDef; + if (timeUnit && !fieldDef2) { + return { + band: getMarkConfig('timeUnitBandSize', mark, config) + }; + } else if (isBinning(bin) && !hasDiscreteDomain(scaleType)) { + return { + band: 1 + }; + } + } + if (isRectBasedMark(mark.type)) { + if (scaleType) { + if (hasDiscreteDomain(scaleType)) { + return config[mark.type]?.discreteBandSize || { + band: 1 + }; + } else { + return config[mark.type]?.continuousBandSize; + } + } + return config[mark.type]?.discreteBandSize; + } + return undefined; + } + function hasBandEnd(fieldDef, fieldDef2, markDef, config) { + if (isBinning(fieldDef.bin) || fieldDef.timeUnit && isTypedFieldDef(fieldDef) && fieldDef.type === 'temporal') { + // Need to check bandPosition because non-rect marks (e.g., point) with timeUnit + // doesn't have to use bandEnd if there is no bandPosition. + return getBandPosition({ + fieldDef, + fieldDef2, + markDef, + config + }) !== undefined; + } + return false; + } + + /** + * Field definition of a mark property, which can contain a legend. + */ + + // Detail + + // Order Path have no scale + + function isOrderOnlyDef(orderDef) { + return orderDef && !!orderDef.sort && !orderDef['field']; + } + function isConditionalDef(channelDef) { + return channelDef && 'condition' in channelDef; + } + + /** + * Return if a channelDef is a ConditionalValueDef with ConditionFieldDef + */ + function hasConditionalFieldDef(channelDef) { + const condition = channelDef?.['condition']; + return !!condition && !vega.isArray(condition) && isFieldDef(condition); + } + function hasConditionalFieldOrDatumDef(channelDef) { + const condition = channelDef?.['condition']; + return !!condition && !vega.isArray(condition) && isFieldOrDatumDef(condition); + } + function hasConditionalValueDef(channelDef) { + const condition = channelDef?.['condition']; + return !!condition && (vega.isArray(condition) || isValueDef(condition)); + } + function isFieldDef(channelDef) { + // TODO: we can't use field in channelDef here as it's somehow failing runtime test + return channelDef && (!!channelDef['field'] || channelDef['aggregate'] === 'count'); + } + function channelDefType(channelDef) { + return channelDef?.['type']; + } + function isDatumDef(channelDef) { + return channelDef && 'datum' in channelDef; + } + function isContinuousFieldOrDatumDef(cd) { + // TODO: make datum support DateTime object + return isTypedFieldDef(cd) && !isDiscrete(cd) || isNumericDataDef(cd); + } + function isUnbinnedQuantitativeFieldOrDatumDef(cd) { + // TODO: make datum support DateTime object + return isTypedFieldDef(cd) && cd.type === 'quantitative' && !cd.bin || isNumericDataDef(cd); + } + function isNumericDataDef(cd) { + return isDatumDef(cd) && vega.isNumber(cd.datum); + } + function isFieldOrDatumDef(channelDef) { + return isFieldDef(channelDef) || isDatumDef(channelDef); + } + function isTypedFieldDef(channelDef) { + return channelDef && ('field' in channelDef || channelDef['aggregate'] === 'count') && 'type' in channelDef; + } + function isValueDef(channelDef) { + return channelDef && 'value' in channelDef && 'value' in channelDef; + } + function isScaleFieldDef(channelDef) { + return channelDef && ('scale' in channelDef || 'sort' in channelDef); + } + function isPositionFieldOrDatumDef(channelDef) { + return channelDef && ('axis' in channelDef || 'stack' in channelDef || 'impute' in channelDef); + } + function isMarkPropFieldOrDatumDef(channelDef) { + return channelDef && 'legend' in channelDef; + } + function isStringFieldOrDatumDef(channelDef) { + return channelDef && ('format' in channelDef || 'formatType' in channelDef); + } + function toStringFieldDef(fieldDef) { + // omit properties that don't exist in string field defs + return omit(fieldDef, ['legend', 'axis', 'header', 'scale']); + } + function isOpFieldDef(fieldDef) { + return 'op' in fieldDef; + } + + /** + * Get a Vega field reference from a Vega-Lite field def. + */ + function vgField(fieldDef) { + let opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + let field = fieldDef.field; + const prefix = opt.prefix; + let suffix = opt.suffix; + let argAccessor = ''; // for accessing argmin/argmax field at the end without getting escaped + + if (isCount(fieldDef)) { + field = internalField('count'); + } else { + let fn; + if (!opt.nofn) { + if (isOpFieldDef(fieldDef)) { + fn = fieldDef.op; + } else { + const { + bin, + aggregate, + timeUnit + } = fieldDef; + if (isBinning(bin)) { + fn = binToString(bin); + suffix = (opt.binSuffix ?? '') + (opt.suffix ?? ''); + } else if (aggregate) { + if (isArgmaxDef(aggregate)) { + argAccessor = `["${field}"]`; + field = `argmax_${aggregate.argmax}`; + } else if (isArgminDef(aggregate)) { + argAccessor = `["${field}"]`; + field = `argmin_${aggregate.argmin}`; + } else { + fn = String(aggregate); + } + } else if (timeUnit && !isBinnedTimeUnit(timeUnit)) { + fn = timeUnitToString(timeUnit); + suffix = (!['range', 'mid'].includes(opt.binSuffix) && opt.binSuffix || '') + (opt.suffix ?? ''); + } + } + } + if (fn) { + field = field ? `${fn}_${field}` : fn; + } + } + if (suffix) { + field = `${field}_${suffix}`; + } + if (prefix) { + field = `${prefix}_${field}`; + } + if (opt.forAs) { + return removePathFromField(field); + } else if (opt.expr) { + // Expression to access flattened field. No need to escape dots. + return flatAccessWithDatum(field, opt.expr) + argAccessor; + } else { + // We flattened all fields so paths should have become dot. + return replacePathInField(field) + argAccessor; + } + } + function isDiscrete(def) { + switch (def.type) { + case 'nominal': + case 'ordinal': + case 'geojson': + return true; + case 'quantitative': + return isFieldDef(def) && !!def.bin; + case 'temporal': + return false; + } + throw new Error(invalidFieldType(def.type)); + } + function isDiscretizing(def) { + return isScaleFieldDef(def) && isContinuousToDiscrete(def.scale?.type); + } + function isCount(fieldDef) { + return fieldDef.aggregate === 'count'; + } + function verbalTitleFormatter(fieldDef, config) { + const { + field, + bin, + timeUnit, + aggregate + } = fieldDef; + if (aggregate === 'count') { + return config.countTitle; + } else if (isBinning(bin)) { + return `${field} (binned)`; + } else if (timeUnit && !isBinnedTimeUnit(timeUnit)) { + const unit = normalizeTimeUnit(timeUnit)?.unit; + if (unit) { + return `${field} (${getTimeUnitParts(unit).join('-')})`; + } + } else if (aggregate) { + if (isArgmaxDef(aggregate)) { + return `${field} for max ${aggregate.argmax}`; + } else if (isArgminDef(aggregate)) { + return `${field} for min ${aggregate.argmin}`; + } else { + return `${titleCase(aggregate)} of ${field}`; + } + } + return field; + } + function functionalTitleFormatter(fieldDef) { + const { + aggregate, + bin, + timeUnit, + field + } = fieldDef; + if (isArgmaxDef(aggregate)) { + return `${field} for argmax(${aggregate.argmax})`; + } else if (isArgminDef(aggregate)) { + return `${field} for argmin(${aggregate.argmin})`; + } + const timeUnitParams = timeUnit && !isBinnedTimeUnit(timeUnit) ? normalizeTimeUnit(timeUnit) : undefined; + const fn = aggregate || timeUnitParams?.unit || timeUnitParams?.maxbins && 'timeunit' || isBinning(bin) && 'bin'; + if (fn) { + return `${fn.toUpperCase()}(${field})`; + } else { + return field; + } + } + const defaultTitleFormatter = (fieldDef, config) => { + switch (config.fieldTitle) { + case 'plain': + return fieldDef.field; + case 'functional': + return functionalTitleFormatter(fieldDef); + default: + return verbalTitleFormatter(fieldDef, config); + } + }; + let titleFormatter = defaultTitleFormatter; + function setTitleFormatter(formatter) { + titleFormatter = formatter; + } + function resetTitleFormatter() { + setTitleFormatter(defaultTitleFormatter); + } + function title(fieldOrDatumDef, config, _ref3) { + let { + allowDisabling, + includeDefault = true + } = _ref3; + const guideTitle = getGuide(fieldOrDatumDef)?.title; + if (!isFieldDef(fieldOrDatumDef)) { + return guideTitle ?? fieldOrDatumDef.title; + } + const fieldDef = fieldOrDatumDef; + const def = includeDefault ? defaultTitle(fieldDef, config) : undefined; + if (allowDisabling) { + return getFirstDefined(guideTitle, fieldDef.title, def); + } else { + return guideTitle ?? fieldDef.title ?? def; + } + } + function getGuide(fieldDef) { + if (isPositionFieldOrDatumDef(fieldDef) && fieldDef.axis) { + return fieldDef.axis; + } else if (isMarkPropFieldOrDatumDef(fieldDef) && fieldDef.legend) { + return fieldDef.legend; + } else if (isFacetFieldDef(fieldDef) && fieldDef.header) { + return fieldDef.header; + } + return undefined; + } + function defaultTitle(fieldDef, config) { + return titleFormatter(fieldDef, config); + } + function getFormatMixins(fieldDef) { + if (isStringFieldOrDatumDef(fieldDef)) { + const { + format, + formatType + } = fieldDef; + return { + format, + formatType + }; + } else { + const guide = getGuide(fieldDef) ?? {}; + const { + format, + formatType + } = guide; + return { + format, + formatType + }; + } + } + function defaultType$2(fieldDef, channel) { + switch (channel) { + case 'latitude': + case 'longitude': + return 'quantitative'; + case 'row': + case 'column': + case 'facet': + case 'shape': + case 'strokeDash': + return 'nominal'; + case 'order': + return 'ordinal'; + } + if (isSortableFieldDef(fieldDef) && vega.isArray(fieldDef.sort)) { + return 'ordinal'; + } + const { + aggregate, + bin, + timeUnit + } = fieldDef; + if (timeUnit) { + return 'temporal'; + } + if (bin || aggregate && !isArgmaxDef(aggregate) && !isArgminDef(aggregate)) { + return 'quantitative'; + } + if (isScaleFieldDef(fieldDef) && fieldDef.scale?.type) { + switch (SCALE_CATEGORY_INDEX[fieldDef.scale.type]) { + case 'numeric': + case 'discretizing': + return 'quantitative'; + case 'time': + return 'temporal'; + } + } + return 'nominal'; + } + + /** + * Returns the fieldDef -- either from the outer channelDef or from the condition of channelDef. + * @param channelDef + */ + + function getFieldDef(channelDef) { + if (isFieldDef(channelDef)) { + return channelDef; + } else if (hasConditionalFieldDef(channelDef)) { + return channelDef.condition; + } + return undefined; + } + function getFieldOrDatumDef(channelDef) { + if (isFieldOrDatumDef(channelDef)) { + return channelDef; + } else if (hasConditionalFieldOrDatumDef(channelDef)) { + return channelDef.condition; + } + return undefined; + } + + /** + * Convert type to full, lowercase type, or augment the fieldDef with a default type if missing. + */ + function initChannelDef(channelDef, channel, config) { + let opt = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + if (vega.isString(channelDef) || vega.isNumber(channelDef) || vega.isBoolean(channelDef)) { + const primitiveType = vega.isString(channelDef) ? 'string' : vega.isNumber(channelDef) ? 'number' : 'boolean'; + warn(primitiveChannelDef(channel, primitiveType, channelDef)); + return { + value: channelDef + }; + } + + // If a fieldDef contains a field, we need type. + if (isFieldOrDatumDef(channelDef)) { + return initFieldOrDatumDef(channelDef, channel, config, opt); + } else if (hasConditionalFieldOrDatumDef(channelDef)) { + return { + ...channelDef, + // Need to cast as normalizeFieldDef normally return FieldDef, but here we know that it is definitely Condition + condition: initFieldOrDatumDef(channelDef.condition, channel, config, opt) + }; + } + return channelDef; + } + function initFieldOrDatumDef(fd, channel, config, opt) { + if (isStringFieldOrDatumDef(fd)) { + const { + format, + formatType, + ...rest + } = fd; + if (isCustomFormatType(formatType) && !config.customFormatTypes) { + warn(customFormatTypeNotAllowed(channel)); + return initFieldOrDatumDef(rest, channel, config, opt); + } + } else { + const guideType = isPositionFieldOrDatumDef(fd) ? 'axis' : isMarkPropFieldOrDatumDef(fd) ? 'legend' : isFacetFieldDef(fd) ? 'header' : null; + if (guideType && fd[guideType]) { + const { + format, + formatType, + ...newGuide + } = fd[guideType]; + if (isCustomFormatType(formatType) && !config.customFormatTypes) { + warn(customFormatTypeNotAllowed(channel)); + return initFieldOrDatumDef({ + ...fd, + [guideType]: newGuide + }, channel, config, opt); + } + } + } + if (isFieldDef(fd)) { + return initFieldDef(fd, channel, opt); + } + return initDatumDef(fd); + } + function initDatumDef(datumDef) { + let type = datumDef['type']; + if (type) { + return datumDef; + } + const { + datum + } = datumDef; + type = vega.isNumber(datum) ? 'quantitative' : vega.isString(datum) ? 'nominal' : isDateTime(datum) ? 'temporal' : undefined; + return { + ...datumDef, + type + }; + } + function initFieldDef(fd, channel) { + let { + compositeMark = false + } = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + const { + aggregate, + timeUnit, + bin, + field + } = fd; + const fieldDef = { + ...fd + }; + + // Drop invalid aggregate + if (!compositeMark && aggregate && !isAggregateOp(aggregate) && !isArgmaxDef(aggregate) && !isArgminDef(aggregate)) { + warn(invalidAggregate(aggregate)); + delete fieldDef.aggregate; + } + + // Normalize Time Unit + if (timeUnit) { + fieldDef.timeUnit = normalizeTimeUnit(timeUnit); + } + if (field) { + fieldDef.field = `${field}`; + } + + // Normalize bin + if (isBinning(bin)) { + fieldDef.bin = normalizeBin(bin, channel); + } + if (isBinned(bin) && !isXorY(channel)) { + warn(channelShouldNotBeUsedForBinned(channel)); + } + + // Normalize Type + if (isTypedFieldDef(fieldDef)) { + const { + type + } = fieldDef; + const fullType = getFullName(type); + if (type !== fullType) { + // convert short type to full type + fieldDef.type = fullType; + } + if (type !== 'quantitative') { + if (isCountingAggregateOp(aggregate)) { + warn(invalidFieldTypeForCountAggregate(type, aggregate)); + fieldDef.type = 'quantitative'; + } + } + } else if (!isSecondaryRangeChannel(channel)) { + // If type is empty / invalid, then augment with default type + const newType = defaultType$2(fieldDef, channel); + fieldDef['type'] = newType; + } + if (isTypedFieldDef(fieldDef)) { + const { + compatible, + warning + } = channelCompatibility(fieldDef, channel) || {}; + if (compatible === false) { + warn(warning); + } + } + if (isSortableFieldDef(fieldDef) && vega.isString(fieldDef.sort)) { + const { + sort + } = fieldDef; + if (isSortByChannel(sort)) { + return { + ...fieldDef, + sort: { + encoding: sort + } + }; + } + const sub = sort.substr(1); + if (sort.charAt(0) === '-' && isSortByChannel(sub)) { + return { + ...fieldDef, + sort: { + encoding: sub, + order: 'descending' + } + }; + } + } + if (isFacetFieldDef(fieldDef)) { + const { + header + } = fieldDef; + if (header) { + const { + orient, + ...rest + } = header; + if (orient) { + return { + ...fieldDef, + header: { + ...rest, + labelOrient: header.labelOrient || orient, + titleOrient: header.titleOrient || orient + } + }; + } + } + } + return fieldDef; + } + function normalizeBin(bin, channel) { + if (vega.isBoolean(bin)) { + return { + maxbins: autoMaxBins(channel) + }; + } else if (bin === 'binned') { + return { + binned: true + }; + } else if (!bin.maxbins && !bin.step) { + return { + ...bin, + maxbins: autoMaxBins(channel) + }; + } else { + return bin; + } + } + const COMPATIBLE = { + compatible: true + }; + function channelCompatibility(fieldDef, channel) { + const type = fieldDef.type; + if (type === 'geojson' && channel !== 'shape') { + return { + compatible: false, + warning: `Channel ${channel} should not be used with a geojson data.` + }; + } + switch (channel) { + case ROW: + case COLUMN: + case FACET: + if (!isDiscrete(fieldDef)) { + return { + compatible: false, + warning: channelShouldBeDiscrete(channel) + }; + } + return COMPATIBLE; + case X: + case Y: + case XOFFSET: + case YOFFSET: + case COLOR: + case FILL: + case STROKE: + case TEXT$1: + case DETAIL: + case KEY: + case TOOLTIP: + case HREF: + case URL: + case ANGLE: + case THETA: + case RADIUS: + case DESCRIPTION: + return COMPATIBLE; + case LONGITUDE: + case LONGITUDE2: + case LATITUDE: + case LATITUDE2: + if (type !== QUANTITATIVE) { + return { + compatible: false, + warning: `Channel ${channel} should be used with a quantitative field only, not ${fieldDef.type} field.` + }; + } + return COMPATIBLE; + case OPACITY: + case FILLOPACITY: + case STROKEOPACITY: + case STROKEWIDTH: + case SIZE: + case THETA2: + case RADIUS2: + case X2: + case Y2: + if (type === 'nominal' && !fieldDef['sort']) { + return { + compatible: false, + warning: `Channel ${channel} should not be used with an unsorted discrete field.` + }; + } + return COMPATIBLE; + case SHAPE: + case STROKEDASH: + if (!isDiscrete(fieldDef) && !isDiscretizing(fieldDef)) { + return { + compatible: false, + warning: channelShouldBeDiscreteOrDiscretizing(channel) + }; + } + return COMPATIBLE; + case ORDER: + if (fieldDef.type === 'nominal' && !('sort' in fieldDef)) { + return { + compatible: false, + warning: `Channel order is inappropriate for nominal field, which has no inherent order.` + }; + } + return COMPATIBLE; + } + } + + /** + * Check if the field def uses a time format or does not use any format but is temporal + * (this does not cover field defs that are temporal but use a number format). + */ + function isFieldOrDatumDefForTimeFormat(fieldOrDatumDef) { + const { + formatType + } = getFormatMixins(fieldOrDatumDef); + return formatType === 'time' || !formatType && isTimeFieldDef(fieldOrDatumDef); + } + + /** + * Check if field def has type `temporal`. If you want to also cover field defs that use a time format, use `isTimeFormatFieldDef`. + */ + function isTimeFieldDef(def) { + return def && (def['type'] === 'temporal' || isFieldDef(def) && !!def.timeUnit); + } + + /** + * Getting a value associated with a fielddef. + * Convert the value to Vega expression if applicable (for datetime object, or string if the field def is temporal or has timeUnit) + */ + function valueExpr(v, _ref4) { + let { + timeUnit, + type, + wrapTime, + undefinedIfExprNotRequired + } = _ref4; + const unit = timeUnit && normalizeTimeUnit(timeUnit)?.unit; + let isTime = unit || type === 'temporal'; + let expr; + if (isExprRef(v)) { + expr = v.expr; + } else if (isSignalRef(v)) { + expr = v.signal; + } else if (isDateTime(v)) { + isTime = true; + expr = dateTimeToExpr(v); + } else if (vega.isString(v) || vega.isNumber(v)) { + if (isTime) { + expr = `datetime(${stringify(v)})`; + if (isLocalSingleTimeUnit(unit)) { + // for single timeUnit, we will use dateTimeToExpr to convert number/string to match the timeUnit + if (vega.isNumber(v) && v < 10000 || vega.isString(v) && isNaN(Date.parse(v))) { + expr = dateTimeToExpr({ + [unit]: v + }); + } + } + } + } + if (expr) { + return wrapTime && isTime ? `time(${expr})` : expr; + } + // number or boolean or normal string + return undefinedIfExprNotRequired ? undefined : stringify(v); + } + + /** + * Standardize value array -- convert each value to Vega expression if applicable + */ + function valueArray(fieldOrDatumDef, values) { + const { + type + } = fieldOrDatumDef; + return values.map(v => { + const timeUnit = isFieldDef(fieldOrDatumDef) && !isBinnedTimeUnit(fieldOrDatumDef.timeUnit) ? fieldOrDatumDef.timeUnit : undefined; + const expr = valueExpr(v, { + timeUnit, + type, + undefinedIfExprNotRequired: true + }); + // return signal for the expression if we need an expression + if (expr !== undefined) { + return { + signal: expr + }; + } + // otherwise just return the original value + return v; + }); + } + + /** + * Checks whether a fieldDef for a particular channel requires a computed bin range. + */ + function binRequiresRange(fieldDef, channel) { + if (!isBinning(fieldDef.bin)) { + console.warn('Only call this method for binned field defs.'); + return false; + } + + // 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. + // We could check whether the axis or legend exists (not disabled) but that seems overkill. + return isScaleChannel(channel) && ['ordinal', 'nominal'].includes(fieldDef.type); + } + + const CONDITIONAL_AXIS_PROP_INDEX = { + labelAlign: { + part: 'labels', + vgProp: 'align' + }, + labelBaseline: { + part: 'labels', + vgProp: 'baseline' + }, + labelColor: { + part: 'labels', + vgProp: 'fill' + }, + labelFont: { + part: 'labels', + vgProp: 'font' + }, + labelFontSize: { + part: 'labels', + vgProp: 'fontSize' + }, + labelFontStyle: { + part: 'labels', + vgProp: 'fontStyle' + }, + labelFontWeight: { + part: 'labels', + vgProp: 'fontWeight' + }, + labelOpacity: { + part: 'labels', + vgProp: 'opacity' + }, + labelOffset: null, + labelPadding: null, + // There is no fixed vgProp for tickSize, need to use signal. + gridColor: { + part: 'grid', + vgProp: 'stroke' + }, + gridDash: { + part: 'grid', + vgProp: 'strokeDash' + }, + gridDashOffset: { + part: 'grid', + vgProp: 'strokeDashOffset' + }, + gridOpacity: { + part: 'grid', + vgProp: 'opacity' + }, + gridWidth: { + part: 'grid', + vgProp: 'strokeWidth' + }, + tickColor: { + part: 'ticks', + vgProp: 'stroke' + }, + tickDash: { + part: 'ticks', + vgProp: 'strokeDash' + }, + tickDashOffset: { + part: 'ticks', + vgProp: 'strokeDashOffset' + }, + tickOpacity: { + part: 'ticks', + vgProp: 'opacity' + }, + tickSize: null, + // There is no fixed vgProp for tickSize, need to use signal. + tickWidth: { + part: 'ticks', + vgProp: 'strokeWidth' + } + }; + function isConditionalAxisValue(v) { + return v?.condition; + } + + // Vega axis config is the same as Vega axis base. If this is not the case, add specific type. + + const AXIS_PARTS = ['domain', 'grid', 'labels', 'ticks', 'title']; + + /** + * A dictionary listing whether a certain axis property is applicable for only main axes or only grid axes. + */ + const AXIS_PROPERTY_TYPE = { + grid: 'grid', + gridCap: 'grid', + gridColor: 'grid', + gridDash: 'grid', + gridDashOffset: 'grid', + gridOpacity: 'grid', + gridScale: 'grid', + gridWidth: 'grid', + orient: 'main', + bandPosition: 'both', + // Need to be applied to grid axis too, so the grid will align with ticks. + + aria: 'main', + description: 'main', + domain: 'main', + domainCap: 'main', + domainColor: 'main', + domainDash: 'main', + domainDashOffset: 'main', + domainOpacity: 'main', + domainWidth: 'main', + format: 'main', + formatType: 'main', + labelAlign: 'main', + labelAngle: 'main', + labelBaseline: 'main', + labelBound: 'main', + labelColor: 'main', + labelFlush: 'main', + labelFlushOffset: 'main', + labelFont: 'main', + labelFontSize: 'main', + labelFontStyle: 'main', + labelFontWeight: 'main', + labelLimit: 'main', + labelLineHeight: 'main', + labelOffset: 'main', + labelOpacity: 'main', + labelOverlap: 'main', + labelPadding: 'main', + labels: 'main', + labelSeparation: 'main', + maxExtent: 'main', + minExtent: 'main', + offset: 'both', + position: 'main', + tickCap: 'main', + tickColor: 'main', + tickDash: 'main', + tickDashOffset: 'main', + tickMinStep: 'both', + tickOffset: 'both', + // Need to be applied to grid axis too, so the grid will align with ticks. + tickOpacity: 'main', + tickRound: 'both', + // Apply rounding to grid and ticks so they are aligned. + ticks: 'main', + tickSize: 'main', + tickWidth: 'both', + title: 'main', + titleAlign: 'main', + titleAnchor: 'main', + titleAngle: 'main', + titleBaseline: 'main', + titleColor: 'main', + titleFont: 'main', + titleFontSize: 'main', + titleFontStyle: 'main', + titleFontWeight: 'main', + titleLimit: 'main', + titleLineHeight: 'main', + titleOpacity: 'main', + titlePadding: 'main', + titleX: 'main', + titleY: 'main', + encode: 'both', + // we hide this in Vega-Lite + scale: 'both', + tickBand: 'both', + tickCount: 'both', + tickExtra: 'both', + translate: 'both', + values: 'both', + zindex: 'both' // this is actually set afterward, so it doesn't matter + }; + const COMMON_AXIS_PROPERTIES_INDEX = { + orient: 1, + // other things can depend on orient + + aria: 1, + bandPosition: 1, + description: 1, + domain: 1, + domainCap: 1, + domainColor: 1, + domainDash: 1, + domainDashOffset: 1, + domainOpacity: 1, + domainWidth: 1, + format: 1, + formatType: 1, + grid: 1, + gridCap: 1, + gridColor: 1, + gridDash: 1, + gridDashOffset: 1, + gridOpacity: 1, + gridWidth: 1, + labelAlign: 1, + labelAngle: 1, + labelBaseline: 1, + labelBound: 1, + labelColor: 1, + labelFlush: 1, + labelFlushOffset: 1, + labelFont: 1, + labelFontSize: 1, + labelFontStyle: 1, + labelFontWeight: 1, + labelLimit: 1, + labelLineHeight: 1, + labelOffset: 1, + labelOpacity: 1, + labelOverlap: 1, + labelPadding: 1, + labels: 1, + labelSeparation: 1, + maxExtent: 1, + minExtent: 1, + offset: 1, + position: 1, + tickBand: 1, + tickCap: 1, + tickColor: 1, + tickCount: 1, + tickDash: 1, + tickDashOffset: 1, + tickExtra: 1, + tickMinStep: 1, + tickOffset: 1, + tickOpacity: 1, + tickRound: 1, + ticks: 1, + tickSize: 1, + tickWidth: 1, + title: 1, + titleAlign: 1, + titleAnchor: 1, + titleAngle: 1, + titleBaseline: 1, + titleColor: 1, + titleFont: 1, + titleFontSize: 1, + titleFontStyle: 1, + titleFontWeight: 1, + titleLimit: 1, + titleLineHeight: 1, + titleOpacity: 1, + titlePadding: 1, + titleX: 1, + titleY: 1, + translate: 1, + values: 1, + zindex: 1 + }; + const AXIS_PROPERTIES_INDEX = { + ...COMMON_AXIS_PROPERTIES_INDEX, + style: 1, + labelExpr: 1, + encoding: 1 + }; + function isAxisProperty(prop) { + return !!AXIS_PROPERTIES_INDEX[prop]; + } + const AXIS_CONFIGS_INDEX = { + axis: 1, + axisBand: 1, + axisBottom: 1, + axisDiscrete: 1, + axisLeft: 1, + axisPoint: 1, + axisQuantitative: 1, + axisRight: 1, + axisTemporal: 1, + axisTop: 1, + axisX: 1, + axisXBand: 1, + axisXDiscrete: 1, + axisXPoint: 1, + axisXQuantitative: 1, + axisXTemporal: 1, + axisY: 1, + axisYBand: 1, + axisYDiscrete: 1, + axisYPoint: 1, + axisYQuantitative: 1, + axisYTemporal: 1 + }; + const AXIS_CONFIGS = keys(AXIS_CONFIGS_INDEX); + + /** + * Base interface for a unit (single-view) specification. + */ + + /** + * A unit specification without any shortcut/expansion syntax. + */ + + /** + * A unit specification, which can contain either [primitive marks or composite marks](https://vega.github.io/vega-lite/docs/mark.html#types). + */ + + /** + * Unit spec that can have a composite mark and row or column channels (shorthand for a facet spec). + */ + + function isUnitSpec(spec) { + return 'mark' in spec; + } + + // TODO: replace string with Mark + + class CompositeMarkNormalizer { + constructor(name, run) { + this.name = name; + this.run = run; + } + hasMatchingType(spec) { + if (isUnitSpec(spec)) { + return getMarkType(spec.mark) === this.name; + } + return false; + } + } + + function channelHasField(encoding, channel) { + const channelDef = encoding && encoding[channel]; + if (channelDef) { + if (vega.isArray(channelDef)) { + return some(channelDef, fieldDef => !!fieldDef.field); + } else { + return isFieldDef(channelDef) || hasConditionalFieldDef(channelDef); + } + } + return false; + } + function channelHasFieldOrDatum(encoding, channel) { + const channelDef = encoding && encoding[channel]; + if (channelDef) { + if (vega.isArray(channelDef)) { + return some(channelDef, fieldDef => !!fieldDef.field); + } else { + return isFieldDef(channelDef) || isDatumDef(channelDef) || hasConditionalFieldOrDatumDef(channelDef); + } + } + return false; + } + function channelHasNestedOffsetScale(encoding, channel) { + if (isXorY(channel)) { + const fieldDef = encoding[channel]; + if ((isFieldDef(fieldDef) || isDatumDef(fieldDef)) && (isDiscrete$1(fieldDef.type) || isFieldDef(fieldDef) && fieldDef.timeUnit)) { + const offsetChannel = getOffsetScaleChannel(channel); + return channelHasFieldOrDatum(encoding, offsetChannel); + } + } + return false; + } + function isAggregate$1(encoding) { + return some(CHANNELS, channel => { + if (channelHasField(encoding, channel)) { + const channelDef = encoding[channel]; + if (vega.isArray(channelDef)) { + return some(channelDef, fieldDef => !!fieldDef.aggregate); + } else { + const fieldDef = getFieldDef(channelDef); + return fieldDef && !!fieldDef.aggregate; + } + } + return false; + }); + } + function extractTransformsFromEncoding(oldEncoding, config) { + const groupby = []; + const bins = []; + const timeUnits = []; + const aggregate = []; + const encoding = {}; + forEach(oldEncoding, (channelDef, channel) => { + // Extract potential embedded transformations along with remaining properties + if (isFieldDef(channelDef)) { + const { + field, + aggregate: aggOp, + bin, + timeUnit, + ...remaining + } = channelDef; + if (aggOp || timeUnit || bin) { + const guide = getGuide(channelDef); + const isTitleDefined = guide?.title; + let newField = vgField(channelDef, { + forAs: true + }); + const newFieldDef = { + // Only add title if it doesn't exist + ...(isTitleDefined ? [] : { + title: title(channelDef, config, { + allowDisabling: true + }) + }), + ...remaining, + // Always overwrite field + field: newField + }; + if (aggOp) { + let op; + if (isArgmaxDef(aggOp)) { + op = 'argmax'; + newField = vgField({ + op: 'argmax', + field: aggOp.argmax + }, { + forAs: true + }); + newFieldDef.field = `${newField}.${field}`; + } else if (isArgminDef(aggOp)) { + op = 'argmin'; + newField = vgField({ + op: 'argmin', + field: aggOp.argmin + }, { + forAs: true + }); + newFieldDef.field = `${newField}.${field}`; + } else if (aggOp !== 'boxplot' && aggOp !== 'errorbar' && aggOp !== 'errorband') { + op = aggOp; + } + if (op) { + const aggregateEntry = { + op, + as: newField + }; + if (field) { + aggregateEntry.field = field; + } + aggregate.push(aggregateEntry); + } + } else { + groupby.push(newField); + if (isTypedFieldDef(channelDef) && isBinning(bin)) { + bins.push({ + bin, + field, + as: newField + }); + // Add additional groupbys for range and end of bins + groupby.push(vgField(channelDef, { + binSuffix: 'end' + })); + if (binRequiresRange(channelDef, channel)) { + groupby.push(vgField(channelDef, { + binSuffix: 'range' + })); + } + // Create accompanying 'x2' or 'y2' field if channel is 'x' or 'y' respectively + if (isXorY(channel)) { + const secondaryChannel = { + field: `${newField}_end` + }; + encoding[`${channel}2`] = secondaryChannel; + } + newFieldDef.bin = 'binned'; + if (!isSecondaryRangeChannel(channel)) { + newFieldDef['type'] = QUANTITATIVE; + } + } else if (timeUnit && !isBinnedTimeUnit(timeUnit)) { + timeUnits.push({ + timeUnit, + field, + as: newField + }); + + // define the format type for later compilation + const formatType = isTypedFieldDef(channelDef) && channelDef.type !== TEMPORAL && 'time'; + if (formatType) { + if (channel === TEXT$1 || channel === TOOLTIP) { + newFieldDef['formatType'] = formatType; + } else if (isNonPositionScaleChannel(channel)) { + newFieldDef['legend'] = { + formatType, + ...newFieldDef['legend'] + }; + } else if (isXorY(channel)) { + newFieldDef['axis'] = { + formatType, + ...newFieldDef['axis'] + }; + } + } + } + } + + // now the field should refer to post-transformed field instead + encoding[channel] = newFieldDef; + } else { + groupby.push(field); + encoding[channel] = oldEncoding[channel]; + } + } else { + // For value def / signal ref / datum def, just copy + encoding[channel] = oldEncoding[channel]; + } + }); + return { + bins, + timeUnits, + aggregate, + groupby, + encoding + }; + } + function markChannelCompatible(encoding, channel, mark) { + const markSupported = supportMark(channel, mark); + if (!markSupported) { + return false; + } else if (markSupported === 'binned') { + const primaryFieldDef = encoding[channel === X2 ? X : Y]; + + // circle, point, square and tick only support x2/y2 when their corresponding x/y fieldDef + // has "binned" data and thus need x2/y2 to specify the bin-end field. + if (isFieldDef(primaryFieldDef) && isFieldDef(encoding[channel]) && isBinned(primaryFieldDef.bin)) { + return true; + } else { + return false; + } + } + return true; + } + function initEncoding(encoding, mark, filled, config) { + const normalizedEncoding = {}; + for (const key of keys(encoding)) { + if (!isChannel(key)) { + // Drop invalid channel + warn(invalidEncodingChannel(key)); + } + } + for (let channel of UNIT_CHANNELS) { + if (!encoding[channel]) { + continue; + } + const channelDef = encoding[channel]; + if (isXorYOffset(channel)) { + const mainChannel = getMainChannelFromOffsetChannel(channel); + const positionDef = normalizedEncoding[mainChannel]; + if (isFieldDef(positionDef)) { + if (isContinuous(positionDef.type)) { + if (isFieldDef(channelDef) && !positionDef.timeUnit) { + // TODO: nesting continuous field instead continuous field should + // behave like offsetting the data in data domain + warn(offsetNestedInsideContinuousPositionScaleDropped(mainChannel)); + continue; + } + } + } + } + if (channel === 'angle' && mark === 'arc' && !encoding.theta) { + warn(REPLACE_ANGLE_WITH_THETA); + channel = THETA; + } + if (!markChannelCompatible(encoding, channel, mark)) { + // Drop unsupported channel + warn(incompatibleChannel(channel, mark)); + continue; + } + + // Drop line's size if the field is aggregated. + if (channel === SIZE && mark === 'line') { + const fieldDef = getFieldDef(encoding[channel]); + if (fieldDef?.aggregate) { + warn(LINE_WITH_VARYING_SIZE); + continue; + } + } + // Drop color if either fill or stroke is specified + + if (channel === COLOR && (filled ? 'fill' in encoding : 'stroke' in encoding)) { + warn(droppingColor('encoding', { + fill: 'fill' in encoding, + stroke: 'stroke' in encoding + })); + continue; + } + if (channel === DETAIL || channel === ORDER && !vega.isArray(channelDef) && !isValueDef(channelDef) || channel === TOOLTIP && vega.isArray(channelDef)) { + if (channelDef) { + if (channel === ORDER) { + const def = encoding[channel]; + if (isOrderOnlyDef(def)) { + normalizedEncoding[channel] = def; + continue; + } + } + // Array of fieldDefs for detail channel (or production rule) + normalizedEncoding[channel] = vega.array(channelDef).reduce((defs, fieldDef) => { + if (!isFieldDef(fieldDef)) { + warn(emptyFieldDef(fieldDef, channel)); + } else { + defs.push(initFieldDef(fieldDef, channel)); + } + return defs; + }, []); + } + } else { + if (channel === TOOLTIP && channelDef === null) { + // Preserve null so we can use it to disable tooltip + normalizedEncoding[channel] = null; + } else if (!isFieldDef(channelDef) && !isDatumDef(channelDef) && !isValueDef(channelDef) && !isConditionalDef(channelDef) && !isSignalRef(channelDef)) { + warn(emptyFieldDef(channelDef, channel)); + continue; + } + normalizedEncoding[channel] = initChannelDef(channelDef, channel, config); + } + } + return normalizedEncoding; + } + + /** + * For composite marks, we have to call initChannelDef during init so we can infer types earlier. + */ + function normalizeEncoding(encoding, config) { + const normalizedEncoding = {}; + for (const channel of keys(encoding)) { + const newChannelDef = initChannelDef(encoding[channel], channel, config, { + compositeMark: true + }); + normalizedEncoding[channel] = newChannelDef; + } + return normalizedEncoding; + } + function fieldDefs(encoding) { + const arr = []; + for (const channel of keys(encoding)) { + if (channelHasField(encoding, channel)) { + const channelDef = encoding[channel]; + const channelDefArray = vega.array(channelDef); + for (const def of channelDefArray) { + if (isFieldDef(def)) { + arr.push(def); + } else if (hasConditionalFieldDef(def)) { + arr.push(def.condition); + } + } + } + } + return arr; + } + function forEach(mapping, f, thisArg) { + if (!mapping) { + return; + } + for (const channel of keys(mapping)) { + const el = mapping[channel]; + if (vega.isArray(el)) { + for (const channelDef of el) { + f.call(thisArg, channelDef, channel); + } + } else { + f.call(thisArg, el, channel); + } + } + } + function reduce(mapping, f, init, thisArg) { + if (!mapping) { + return init; + } + return keys(mapping).reduce((r, channel) => { + const map = mapping[channel]; + if (vega.isArray(map)) { + return map.reduce((r1, channelDef) => { + return f.call(thisArg, r1, channelDef, channel); + }, r); + } else { + return f.call(thisArg, r, map, channel); + } + }, init); + } + + /** + * Returns list of path grouping fields for the given encoding + */ + function pathGroupingFields(mark, encoding) { + return keys(encoding).reduce((details, channel) => { + switch (channel) { + // x, y, x2, y2, lat, long, lat1, long2, order, tooltip, href, aria label, cursor should not cause lines to group + case X: + case Y: + case HREF: + case DESCRIPTION: + case URL: + case X2: + case Y2: + case XOFFSET: + case YOFFSET: + case THETA: + case THETA2: + case RADIUS: + case RADIUS2: + // falls through + + case LATITUDE: + case LONGITUDE: + case LATITUDE2: + case LONGITUDE2: + // TODO: case 'cursor': + + // text, shape, shouldn't be a part of line/trail/area [falls through] + case TEXT$1: + case SHAPE: + case ANGLE: + // falls through + + // tooltip fields should not be added to group by [falls through] + case TOOLTIP: + return details; + case ORDER: + // order should not group line / trail + if (mark === 'line' || mark === 'trail') { + return details; + } + // but order should group area for stacking (falls through) + + case DETAIL: + case KEY: + { + const channelDef = encoding[channel]; + if (vega.isArray(channelDef) || isFieldDef(channelDef)) { + for (const fieldDef of vega.array(channelDef)) { + if (!fieldDef.aggregate) { + details.push(vgField(fieldDef, {})); + } + } + } + return details; + } + case SIZE: + if (mark === 'trail') { + // For trail, size should not group trail lines. + return details; + } + // For line, size should group lines. + + // falls through + case COLOR: + case FILL: + case STROKE: + case OPACITY: + case FILLOPACITY: + case STROKEOPACITY: + case STROKEDASH: + case STROKEWIDTH: + { + // TODO strokeDashOffset: + // falls through + + const fieldDef = getFieldDef(encoding[channel]); + if (fieldDef && !fieldDef.aggregate) { + details.push(vgField(fieldDef, {})); + } + return details; + } + } + }, []); + } + + // Parts mixins can be any mark type. We could make a more specific type for each part. + + function filterTooltipWithAggregatedField(oldEncoding) { + const { + tooltip, + ...filteredEncoding + } = oldEncoding; + if (!tooltip) { + return { + filteredEncoding + }; + } + let customTooltipWithAggregatedField; + let customTooltipWithoutAggregatedField; + if (vega.isArray(tooltip)) { + for (const t of tooltip) { + if (t.aggregate) { + if (!customTooltipWithAggregatedField) { + customTooltipWithAggregatedField = []; + } + customTooltipWithAggregatedField.push(t); + } else { + if (!customTooltipWithoutAggregatedField) { + customTooltipWithoutAggregatedField = []; + } + customTooltipWithoutAggregatedField.push(t); + } + } + if (customTooltipWithAggregatedField) { + filteredEncoding.tooltip = customTooltipWithAggregatedField; + } + } else { + if (tooltip['aggregate']) { + filteredEncoding.tooltip = tooltip; + } else { + customTooltipWithoutAggregatedField = tooltip; + } + } + if (vega.isArray(customTooltipWithoutAggregatedField) && customTooltipWithoutAggregatedField.length === 1) { + customTooltipWithoutAggregatedField = customTooltipWithoutAggregatedField[0]; + } + return { + customTooltipWithoutAggregatedField, + filteredEncoding + }; + } + function getCompositeMarkTooltip(tooltipSummary, continuousAxisChannelDef, encodingWithoutContinuousAxis) { + let withFieldName = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; + if ('tooltip' in encodingWithoutContinuousAxis) { + return { + tooltip: encodingWithoutContinuousAxis.tooltip + }; + } + const fiveSummaryTooltip = tooltipSummary.map(_ref => { + let { + fieldPrefix, + titlePrefix + } = _ref; + const mainTitle = withFieldName ? ` of ${getTitle(continuousAxisChannelDef)}` : ''; + return { + field: fieldPrefix + continuousAxisChannelDef.field, + type: continuousAxisChannelDef.type, + title: isSignalRef(titlePrefix) ? { + signal: `${titlePrefix}"${escape(mainTitle)}"` + } : titlePrefix + mainTitle + }; + }); + const tooltipFieldDefs = fieldDefs(encodingWithoutContinuousAxis).map(toStringFieldDef); + return { + tooltip: [...fiveSummaryTooltip, + // need to cast because TextFieldDef supports fewer types of bin + ...unique(tooltipFieldDefs, hash)] + }; + } + function getTitle(continuousAxisChannelDef) { + const { + title, + field + } = continuousAxisChannelDef; + return getFirstDefined(title, field); + } + function makeCompositeAggregatePartFactory(compositeMarkDef, continuousAxis, continuousAxisChannelDef, sharedEncoding, compositeMarkConfig) { + const { + scale, + axis + } = continuousAxisChannelDef; + return _ref2 => { + let { + partName, + mark, + positionPrefix, + endPositionPrefix = undefined, + extraEncoding = {} + } = _ref2; + const title = getTitle(continuousAxisChannelDef); + return partLayerMixins(compositeMarkDef, partName, compositeMarkConfig, { + mark, + // TODO better remove this method and just have mark as a parameter of the method + encoding: { + [continuousAxis]: { + field: `${positionPrefix}_${continuousAxisChannelDef.field}`, + type: continuousAxisChannelDef.type, + ...(title !== undefined ? { + title + } : {}), + ...(scale !== undefined ? { + scale + } : {}), + ...(axis !== undefined ? { + axis + } : {}) + }, + ...(vega.isString(endPositionPrefix) ? { + [`${continuousAxis}2`]: { + field: `${endPositionPrefix}_${continuousAxisChannelDef.field}` + } + } : {}), + ...sharedEncoding, + ...extraEncoding + } + }); + }; + } + function partLayerMixins(markDef, part, compositeMarkConfig, partBaseSpec) { + const { + clip, + color, + opacity + } = markDef; + const mark = markDef.type; + if (markDef[part] || markDef[part] === undefined && compositeMarkConfig[part]) { + return [{ + ...partBaseSpec, + mark: { + ...compositeMarkConfig[part], + ...(clip ? { + clip + } : {}), + ...(color ? { + color + } : {}), + ...(opacity ? { + opacity + } : {}), + ...(isMarkDef(partBaseSpec.mark) ? partBaseSpec.mark : { + type: partBaseSpec.mark + }), + style: `${mark}-${String(part)}`, + ...(vega.isBoolean(markDef[part]) ? {} : markDef[part]) + } + }]; + } + return []; + } + function compositeMarkContinuousAxis(spec, orient, compositeMark) { + const { + encoding + } = spec; + const continuousAxis = orient === 'vertical' ? 'y' : 'x'; + const continuousAxisChannelDef = encoding[continuousAxis]; // Safe to cast because if x is not continuous fielddef, the orient would not be horizontal. + const continuousAxisChannelDef2 = encoding[`${continuousAxis}2`]; + const continuousAxisChannelDefError = encoding[`${continuousAxis}Error`]; + const continuousAxisChannelDefError2 = encoding[`${continuousAxis}Error2`]; + return { + continuousAxisChannelDef: filterAggregateFromChannelDef(continuousAxisChannelDef, compositeMark), + continuousAxisChannelDef2: filterAggregateFromChannelDef(continuousAxisChannelDef2, compositeMark), + continuousAxisChannelDefError: filterAggregateFromChannelDef(continuousAxisChannelDefError, compositeMark), + continuousAxisChannelDefError2: filterAggregateFromChannelDef(continuousAxisChannelDefError2, compositeMark), + continuousAxis + }; + } + function filterAggregateFromChannelDef(continuousAxisChannelDef, compositeMark) { + if (continuousAxisChannelDef?.aggregate) { + const { + aggregate, + ...continuousAxisWithoutAggregate + } = continuousAxisChannelDef; + if (aggregate !== compositeMark) { + warn(errorBarContinuousAxisHasCustomizedAggregate(aggregate, compositeMark)); + } + return continuousAxisWithoutAggregate; + } else { + return continuousAxisChannelDef; + } + } + function compositeMarkOrient(spec, compositeMark) { + const { + mark, + encoding + } = spec; + const { + x, + y + } = encoding; + if (isMarkDef(mark) && mark.orient) { + return mark.orient; + } + if (isContinuousFieldOrDatumDef(x)) { + // x is continuous + if (isContinuousFieldOrDatumDef(y)) { + // both x and y are continuous + const xAggregate = isFieldDef(x) && x.aggregate; + const yAggregate = isFieldDef(y) && y.aggregate; + if (!xAggregate && yAggregate === compositeMark) { + return 'vertical'; + } else if (!yAggregate && xAggregate === compositeMark) { + return 'horizontal'; + } else if (xAggregate === compositeMark && yAggregate === compositeMark) { + throw new Error('Both x and y cannot have aggregate'); + } else { + if (isFieldOrDatumDefForTimeFormat(y) && !isFieldOrDatumDefForTimeFormat(x)) { + // y is temporal but x is not + return 'horizontal'; + } + + // default orientation for two continuous + return 'vertical'; + } + } + return 'horizontal'; + } else if (isContinuousFieldOrDatumDef(y)) { + // y is continuous but x is not + return 'vertical'; + } else { + // Neither x nor y is continuous. + throw new Error(`Need a valid continuous axis for ${compositeMark}s`); + } + } + + const BOXPLOT = 'boxplot'; + const BOXPLOT_PARTS = ['box', 'median', 'outliers', 'rule', 'ticks']; + const boxPlotNormalizer = new CompositeMarkNormalizer(BOXPLOT, normalizeBoxPlot); + function getBoxPlotType(extent) { + if (vega.isNumber(extent)) { + return 'tukey'; + } + // Ham: If we ever want to, we could add another extent syntax `{kIQR: number}` for the original [Q1-k*IQR, Q3+k*IQR] whisker and call this boxPlotType = `kIQR`. However, I'm not exposing this for now. + return extent; + } + function normalizeBoxPlot(spec, _ref) { + let { + config + } = _ref; + // Need to initEncoding first so we can infer type + spec = { + ...spec, + encoding: normalizeEncoding(spec.encoding, config) + }; + const { + mark, + encoding: _encoding, + params, + projection: _p, + ...outerSpec + } = spec; + const markDef = isMarkDef(mark) ? mark : { + type: mark + }; + + // TODO(https://github.com/vega/vega-lite/issues/3702): add selection support + if (params) { + warn(selectionNotSupported('boxplot')); + } + const extent = markDef.extent ?? config.boxplot.extent; + const sizeValue = getMarkPropOrConfig('size', markDef, + // TODO: https://github.com/vega/vega-lite/issues/6245 + config); + const invalid = markDef.invalid; + const boxPlotType = getBoxPlotType(extent); + const { + bins, + timeUnits, + transform, + continuousAxisChannelDef, + continuousAxis, + groupby, + aggregate, + encodingWithoutContinuousAxis, + ticksOrient, + boxOrient, + customTooltipWithoutAggregatedField + } = boxParams(spec, extent, config); + const aliasedFieldName = removePathFromField(continuousAxisChannelDef.field); + const { + color, + size, + ...encodingWithoutSizeColorAndContinuousAxis + } = encodingWithoutContinuousAxis; + const makeBoxPlotPart = sharedEncoding => { + return makeCompositeAggregatePartFactory(markDef, continuousAxis, continuousAxisChannelDef, sharedEncoding, config.boxplot); + }; + const makeBoxPlotExtent = makeBoxPlotPart(encodingWithoutSizeColorAndContinuousAxis); + const makeBoxPlotBox = makeBoxPlotPart(encodingWithoutContinuousAxis); + const defaultBoxColor = (vega.isObject(config.boxplot.box) ? config.boxplot.box.color : config.mark.color) || '#4c78a8'; + const makeBoxPlotMidTick = makeBoxPlotPart({ + ...encodingWithoutSizeColorAndContinuousAxis, + ...(size ? { + size + } : {}), + color: { + condition: { + test: `datum['lower_box_${continuousAxisChannelDef.field}'] >= datum['upper_box_${continuousAxisChannelDef.field}']`, + ...(color || { + value: defaultBoxColor + }) + } + } + }); + const fiveSummaryTooltipEncoding = getCompositeMarkTooltip([{ + fieldPrefix: boxPlotType === 'min-max' ? 'upper_whisker_' : 'max_', + titlePrefix: 'Max' + }, { + fieldPrefix: 'upper_box_', + titlePrefix: 'Q3' + }, { + fieldPrefix: 'mid_box_', + titlePrefix: 'Median' + }, { + fieldPrefix: 'lower_box_', + titlePrefix: 'Q1' + }, { + fieldPrefix: boxPlotType === 'min-max' ? 'lower_whisker_' : 'min_', + titlePrefix: 'Min' + }], continuousAxisChannelDef, encodingWithoutContinuousAxis); + + // ## Whisker Layers + + const endTick = { + type: 'tick', + color: 'black', + opacity: 1, + orient: ticksOrient, + invalid, + aria: false + }; + const whiskerTooltipEncoding = boxPlotType === 'min-max' ? fiveSummaryTooltipEncoding // for min-max, show five-summary tooltip for whisker + : + // for tukey / k-IQR, just show upper/lower-whisker + getCompositeMarkTooltip([{ + fieldPrefix: 'upper_whisker_', + titlePrefix: 'Upper Whisker' + }, { + fieldPrefix: 'lower_whisker_', + titlePrefix: 'Lower Whisker' + }], continuousAxisChannelDef, encodingWithoutContinuousAxis); + const whiskerLayers = [...makeBoxPlotExtent({ + partName: 'rule', + mark: { + type: 'rule', + invalid, + aria: false + }, + positionPrefix: 'lower_whisker', + endPositionPrefix: 'lower_box', + extraEncoding: whiskerTooltipEncoding + }), ...makeBoxPlotExtent({ + partName: 'rule', + mark: { + type: 'rule', + invalid, + aria: false + }, + positionPrefix: 'upper_box', + endPositionPrefix: 'upper_whisker', + extraEncoding: whiskerTooltipEncoding + }), ...makeBoxPlotExtent({ + partName: 'ticks', + mark: endTick, + positionPrefix: 'lower_whisker', + extraEncoding: whiskerTooltipEncoding + }), ...makeBoxPlotExtent({ + partName: 'ticks', + mark: endTick, + positionPrefix: 'upper_whisker', + extraEncoding: whiskerTooltipEncoding + })]; + + // ## Box Layers + + // TODO: support hiding certain mark parts + const boxLayers = [...(boxPlotType !== 'tukey' ? whiskerLayers : []), ...makeBoxPlotBox({ + partName: 'box', + mark: { + type: 'bar', + ...(sizeValue ? { + size: sizeValue + } : {}), + orient: boxOrient, + invalid, + ariaRoleDescription: 'box' + }, + positionPrefix: 'lower_box', + endPositionPrefix: 'upper_box', + extraEncoding: fiveSummaryTooltipEncoding + }), ...makeBoxPlotMidTick({ + partName: 'median', + mark: { + type: 'tick', + invalid, + ...(vega.isObject(config.boxplot.median) && config.boxplot.median.color ? { + color: config.boxplot.median.color + } : {}), + ...(sizeValue ? { + size: sizeValue + } : {}), + orient: ticksOrient, + aria: false + }, + positionPrefix: 'mid_box', + extraEncoding: fiveSummaryTooltipEncoding + })]; + if (boxPlotType === 'min-max') { + return { + ...outerSpec, + transform: (outerSpec.transform ?? []).concat(transform), + layer: boxLayers + }; + } + + // Tukey Box Plot + + const lowerBoxExpr = `datum["lower_box_${continuousAxisChannelDef.field}"]`; + const upperBoxExpr = `datum["upper_box_${continuousAxisChannelDef.field}"]`; + const iqrExpr = `(${upperBoxExpr} - ${lowerBoxExpr})`; + const lowerWhiskerExpr = `${lowerBoxExpr} - ${extent} * ${iqrExpr}`; + const upperWhiskerExpr = `${upperBoxExpr} + ${extent} * ${iqrExpr}`; + const fieldExpr = `datum["${continuousAxisChannelDef.field}"]`; + const joinaggregateTransform = { + joinaggregate: boxParamsQuartiles(continuousAxisChannelDef.field), + groupby + }; + const filteredWhiskerSpec = { + transform: [{ + filter: `(${lowerWhiskerExpr} <= ${fieldExpr}) && (${fieldExpr} <= ${upperWhiskerExpr})` + }, { + aggregate: [{ + op: 'min', + field: continuousAxisChannelDef.field, + as: `lower_whisker_${aliasedFieldName}` + }, { + op: 'max', + field: continuousAxisChannelDef.field, + as: `upper_whisker_${aliasedFieldName}` + }, + // preserve lower_box / upper_box + { + op: 'min', + field: `lower_box_${continuousAxisChannelDef.field}`, + as: `lower_box_${aliasedFieldName}` + }, { + op: 'max', + field: `upper_box_${continuousAxisChannelDef.field}`, + as: `upper_box_${aliasedFieldName}` + }, ...aggregate], + groupby + }], + layer: whiskerLayers + }; + const { + tooltip, + ...encodingWithoutSizeColorContinuousAxisAndTooltip + } = encodingWithoutSizeColorAndContinuousAxis; + const { + scale, + axis + } = continuousAxisChannelDef; + const title = getTitle(continuousAxisChannelDef); + const axisWithoutTitle = omit(axis, ['title']); + const outlierLayersMixins = partLayerMixins(markDef, 'outliers', config.boxplot, { + transform: [{ + filter: `(${fieldExpr} < ${lowerWhiskerExpr}) || (${fieldExpr} > ${upperWhiskerExpr})` + }], + mark: 'point', + encoding: { + [continuousAxis]: { + field: continuousAxisChannelDef.field, + type: continuousAxisChannelDef.type, + ...(title !== undefined ? { + title + } : {}), + ...(scale !== undefined ? { + scale + } : {}), + // add axis without title since we already added the title above + ...(isEmpty(axisWithoutTitle) ? {} : { + axis: axisWithoutTitle + }) + }, + ...encodingWithoutSizeColorContinuousAxisAndTooltip, + ...(color ? { + color + } : {}), + ...(customTooltipWithoutAggregatedField ? { + tooltip: customTooltipWithoutAggregatedField + } : {}) + } + })[0]; + let filteredLayersMixins; + const filteredLayersMixinsTransforms = [...bins, ...timeUnits, joinaggregateTransform]; + if (outlierLayersMixins) { + filteredLayersMixins = { + transform: filteredLayersMixinsTransforms, + layer: [outlierLayersMixins, filteredWhiskerSpec] + }; + } else { + filteredLayersMixins = filteredWhiskerSpec; + filteredLayersMixins.transform.unshift(...filteredLayersMixinsTransforms); + } + return { + ...outerSpec, + layer: [filteredLayersMixins, { + // boxplot + transform, + layer: boxLayers + }] + }; + } + function boxParamsQuartiles(continousAxisField) { + const aliasedFieldName = removePathFromField(continousAxisField); + return [{ + op: 'q1', + field: continousAxisField, + as: `lower_box_${aliasedFieldName}` + }, { + op: 'q3', + field: continousAxisField, + as: `upper_box_${aliasedFieldName}` + }]; + } + function boxParams(spec, extent, config) { + const orient = compositeMarkOrient(spec, BOXPLOT); + const { + continuousAxisChannelDef, + continuousAxis + } = compositeMarkContinuousAxis(spec, orient, BOXPLOT); + const continuousFieldName = continuousAxisChannelDef.field; + const aliasedFieldName = removePathFromField(continuousFieldName); + const boxPlotType = getBoxPlotType(extent); + const boxplotSpecificAggregate = [...boxParamsQuartiles(continuousFieldName), { + op: 'median', + field: continuousFieldName, + as: `mid_box_${aliasedFieldName}` + }, { + op: 'min', + field: continuousFieldName, + as: (boxPlotType === 'min-max' ? 'lower_whisker_' : 'min_') + aliasedFieldName + }, { + op: 'max', + field: continuousFieldName, + as: (boxPlotType === 'min-max' ? 'upper_whisker_' : 'max_') + aliasedFieldName + }]; + const postAggregateCalculates = boxPlotType === 'min-max' || boxPlotType === 'tukey' ? [] : [ + // This is for the original k-IQR, which we do not expose + { + calculate: `datum["upper_box_${aliasedFieldName}"] - datum["lower_box_${aliasedFieldName}"]`, + as: `iqr_${aliasedFieldName}` + }, { + calculate: `min(datum["upper_box_${aliasedFieldName}"] + datum["iqr_${aliasedFieldName}"] * ${extent}, datum["max_${aliasedFieldName}"])`, + as: `upper_whisker_${aliasedFieldName}` + }, { + calculate: `max(datum["lower_box_${aliasedFieldName}"] - datum["iqr_${aliasedFieldName}"] * ${extent}, datum["min_${aliasedFieldName}"])`, + as: `lower_whisker_${aliasedFieldName}` + }]; + const { + [continuousAxis]: oldContinuousAxisChannelDef, + ...oldEncodingWithoutContinuousAxis + } = spec.encoding; + const { + customTooltipWithoutAggregatedField, + filteredEncoding + } = filterTooltipWithAggregatedField(oldEncodingWithoutContinuousAxis); + const { + bins, + timeUnits, + aggregate, + groupby, + encoding: encodingWithoutContinuousAxis + } = extractTransformsFromEncoding(filteredEncoding, config); + const ticksOrient = orient === 'vertical' ? 'horizontal' : 'vertical'; + const boxOrient = orient; + const transform = [...bins, ...timeUnits, { + aggregate: [...aggregate, ...boxplotSpecificAggregate], + groupby + }, ...postAggregateCalculates]; + return { + bins, + timeUnits, + transform, + groupby, + aggregate, + continuousAxisChannelDef, + continuousAxis, + encodingWithoutContinuousAxis, + ticksOrient, + boxOrient, + customTooltipWithoutAggregatedField + }; + } + + const ERRORBAR = 'errorbar'; + const ERRORBAR_PARTS = ['ticks', 'rule']; + const errorBarNormalizer = new CompositeMarkNormalizer(ERRORBAR, normalizeErrorBar); + function normalizeErrorBar(spec, _ref) { + let { + config + } = _ref; + // Need to initEncoding first so we can infer type + spec = { + ...spec, + encoding: normalizeEncoding(spec.encoding, config) + }; + const { + transform, + continuousAxisChannelDef, + continuousAxis, + encodingWithoutContinuousAxis, + ticksOrient, + markDef, + outerSpec, + tooltipEncoding + } = errorBarParams(spec, ERRORBAR, config); + delete encodingWithoutContinuousAxis['size']; + const makeErrorBarPart = makeCompositeAggregatePartFactory(markDef, continuousAxis, continuousAxisChannelDef, encodingWithoutContinuousAxis, config.errorbar); + const thickness = markDef.thickness; + const size = markDef.size; + const tick = { + type: 'tick', + orient: ticksOrient, + aria: false, + ...(thickness !== undefined ? { + thickness + } : {}), + ...(size !== undefined ? { + size + } : {}) + }; + const layer = [...makeErrorBarPart({ + partName: 'ticks', + mark: tick, + positionPrefix: 'lower', + extraEncoding: tooltipEncoding + }), ...makeErrorBarPart({ + partName: 'ticks', + mark: tick, + positionPrefix: 'upper', + extraEncoding: tooltipEncoding + }), ...makeErrorBarPart({ + partName: 'rule', + mark: { + type: 'rule', + ariaRoleDescription: 'errorbar', + ...(thickness !== undefined ? { + size: thickness + } : {}) + }, + positionPrefix: 'lower', + endPositionPrefix: 'upper', + extraEncoding: tooltipEncoding + })]; + return { + ...outerSpec, + transform, + ...(layer.length > 1 ? { + layer + } : { + ...layer[0] + }) + }; + } + function errorBarOrientAndInputType(spec, compositeMark) { + const { + encoding + } = spec; + if (errorBarIsInputTypeRaw(encoding)) { + return { + orient: compositeMarkOrient(spec, compositeMark), + inputType: 'raw' + }; + } + const isTypeAggregatedUpperLower = errorBarIsInputTypeAggregatedUpperLower(encoding); + const isTypeAggregatedError = errorBarIsInputTypeAggregatedError(encoding); + const x = encoding.x; + const y = encoding.y; + if (isTypeAggregatedUpperLower) { + // type is aggregated-upper-lower + + if (isTypeAggregatedError) { + throw new Error(`${compositeMark} cannot be both type aggregated-upper-lower and aggregated-error`); + } + const x2 = encoding.x2; + const y2 = encoding.y2; + if (isFieldOrDatumDef(x2) && isFieldOrDatumDef(y2)) { + // having both x, x2 and y, y2 + throw new Error(`${compositeMark} cannot have both x2 and y2`); + } else if (isFieldOrDatumDef(x2)) { + if (isContinuousFieldOrDatumDef(x)) { + // having x, x2 quantitative and field y, y2 are not specified + return { + orient: 'horizontal', + inputType: 'aggregated-upper-lower' + }; + } else { + // having x, x2 that are not both quantitative + throw new Error(`Both x and x2 have to be quantitative in ${compositeMark}`); + } + } else if (isFieldOrDatumDef(y2)) { + // y2 is a FieldDef + if (isContinuousFieldOrDatumDef(y)) { + // having y, y2 quantitative and field x, x2 are not specified + return { + orient: 'vertical', + inputType: 'aggregated-upper-lower' + }; + } else { + // having y, y2 that are not both quantitative + throw new Error(`Both y and y2 have to be quantitative in ${compositeMark}`); + } + } + throw new Error('No ranged axis'); + } else { + // type is aggregated-error + + const xError = encoding.xError; + const xError2 = encoding.xError2; + const yError = encoding.yError; + const yError2 = encoding.yError2; + if (isFieldOrDatumDef(xError2) && !isFieldOrDatumDef(xError)) { + // having xError2 without xError + throw new Error(`${compositeMark} cannot have xError2 without xError`); + } + if (isFieldOrDatumDef(yError2) && !isFieldOrDatumDef(yError)) { + // having yError2 without yError + throw new Error(`${compositeMark} cannot have yError2 without yError`); + } + if (isFieldOrDatumDef(xError) && isFieldOrDatumDef(yError)) { + // having both xError and yError + throw new Error(`${compositeMark} cannot have both xError and yError with both are quantiative`); + } else if (isFieldOrDatumDef(xError)) { + if (isContinuousFieldOrDatumDef(x)) { + // having x and xError that are all quantitative + return { + orient: 'horizontal', + inputType: 'aggregated-error' + }; + } else { + // having x, xError, and xError2 that are not all quantitative + throw new Error('All x, xError, and xError2 (if exist) have to be quantitative'); + } + } else if (isFieldOrDatumDef(yError)) { + if (isContinuousFieldOrDatumDef(y)) { + // having y and yError that are all quantitative + return { + orient: 'vertical', + inputType: 'aggregated-error' + }; + } else { + // having y, yError, and yError2 that are not all quantitative + throw new Error('All y, yError, and yError2 (if exist) have to be quantitative'); + } + } + throw new Error('No ranged axis'); + } + } + function errorBarIsInputTypeRaw(encoding) { + return (isFieldOrDatumDef(encoding.x) || isFieldOrDatumDef(encoding.y)) && !isFieldOrDatumDef(encoding.x2) && !isFieldOrDatumDef(encoding.y2) && !isFieldOrDatumDef(encoding.xError) && !isFieldOrDatumDef(encoding.xError2) && !isFieldOrDatumDef(encoding.yError) && !isFieldOrDatumDef(encoding.yError2); + } + function errorBarIsInputTypeAggregatedUpperLower(encoding) { + return isFieldOrDatumDef(encoding.x2) || isFieldOrDatumDef(encoding.y2); + } + function errorBarIsInputTypeAggregatedError(encoding) { + return isFieldOrDatumDef(encoding.xError) || isFieldOrDatumDef(encoding.xError2) || isFieldOrDatumDef(encoding.yError) || isFieldOrDatumDef(encoding.yError2); + } + function errorBarParams(spec, compositeMark, config) { + // TODO: use selection + const { + mark, + encoding, + params, + projection: _p, + ...outerSpec + } = spec; + const markDef = isMarkDef(mark) ? mark : { + type: mark + }; + + // TODO(https://github.com/vega/vega-lite/issues/3702): add selection support + if (params) { + warn(selectionNotSupported(compositeMark)); + } + const { + orient, + inputType + } = errorBarOrientAndInputType(spec, compositeMark); + const { + continuousAxisChannelDef, + continuousAxisChannelDef2, + continuousAxisChannelDefError, + continuousAxisChannelDefError2, + continuousAxis + } = compositeMarkContinuousAxis(spec, orient, compositeMark); + const { + errorBarSpecificAggregate, + postAggregateCalculates, + tooltipSummary, + tooltipTitleWithFieldName + } = errorBarAggregationAndCalculation(markDef, continuousAxisChannelDef, continuousAxisChannelDef2, continuousAxisChannelDefError, continuousAxisChannelDefError2, inputType, compositeMark, config); + const { + [continuousAxis]: oldContinuousAxisChannelDef, + [continuousAxis === 'x' ? 'x2' : 'y2']: oldContinuousAxisChannelDef2, + [continuousAxis === 'x' ? 'xError' : 'yError']: oldContinuousAxisChannelDefError, + [continuousAxis === 'x' ? 'xError2' : 'yError2']: oldContinuousAxisChannelDefError2, + ...oldEncodingWithoutContinuousAxis + } = encoding; + const { + bins, + timeUnits, + aggregate: oldAggregate, + groupby: oldGroupBy, + encoding: encodingWithoutContinuousAxis + } = extractTransformsFromEncoding(oldEncodingWithoutContinuousAxis, config); + const aggregate = [...oldAggregate, ...errorBarSpecificAggregate]; + const groupby = inputType !== 'raw' ? [] : oldGroupBy; + const tooltipEncoding = getCompositeMarkTooltip(tooltipSummary, continuousAxisChannelDef, encodingWithoutContinuousAxis, tooltipTitleWithFieldName); + return { + transform: [...(outerSpec.transform ?? []), ...bins, ...timeUnits, ...(aggregate.length === 0 ? [] : [{ + aggregate, + groupby + }]), ...postAggregateCalculates], + groupby, + continuousAxisChannelDef, + continuousAxis, + encodingWithoutContinuousAxis, + ticksOrient: orient === 'vertical' ? 'horizontal' : 'vertical', + markDef, + outerSpec, + tooltipEncoding + }; + } + function errorBarAggregationAndCalculation(markDef, continuousAxisChannelDef, continuousAxisChannelDef2, continuousAxisChannelDefError, continuousAxisChannelDefError2, inputType, compositeMark, config) { + let errorBarSpecificAggregate = []; + let postAggregateCalculates = []; + const continuousFieldName = continuousAxisChannelDef.field; + let tooltipSummary; + let tooltipTitleWithFieldName = false; + if (inputType === 'raw') { + const center = markDef.center ? markDef.center : markDef.extent ? markDef.extent === 'iqr' ? 'median' : 'mean' : config.errorbar.center; + const extent = markDef.extent ? markDef.extent : center === 'mean' ? 'stderr' : 'iqr'; + if (center === 'median' !== (extent === 'iqr')) { + warn(errorBarCenterIsUsedWithWrongExtent(center, extent, compositeMark)); + } + if (extent === 'stderr' || extent === 'stdev') { + errorBarSpecificAggregate = [{ + op: extent, + field: continuousFieldName, + as: `extent_${continuousFieldName}` + }, { + op: center, + field: continuousFieldName, + as: `center_${continuousFieldName}` + }]; + postAggregateCalculates = [{ + calculate: `datum["center_${continuousFieldName}"] + datum["extent_${continuousFieldName}"]`, + as: `upper_${continuousFieldName}` + }, { + calculate: `datum["center_${continuousFieldName}"] - datum["extent_${continuousFieldName}"]`, + as: `lower_${continuousFieldName}` + }]; + tooltipSummary = [{ + fieldPrefix: 'center_', + titlePrefix: titleCase(center) + }, { + fieldPrefix: 'upper_', + titlePrefix: getTitlePrefix(center, extent, '+') + }, { + fieldPrefix: 'lower_', + titlePrefix: getTitlePrefix(center, extent, '-') + }]; + tooltipTitleWithFieldName = true; + } else { + let centerOp; + let lowerExtentOp; + let upperExtentOp; + if (extent === 'ci') { + centerOp = 'mean'; + lowerExtentOp = 'ci0'; + upperExtentOp = 'ci1'; + } else { + centerOp = 'median'; + lowerExtentOp = 'q1'; + upperExtentOp = 'q3'; + } + errorBarSpecificAggregate = [{ + op: lowerExtentOp, + field: continuousFieldName, + as: `lower_${continuousFieldName}` + }, { + op: upperExtentOp, + field: continuousFieldName, + as: `upper_${continuousFieldName}` + }, { + op: centerOp, + field: continuousFieldName, + as: `center_${continuousFieldName}` + }]; + tooltipSummary = [{ + fieldPrefix: 'upper_', + titlePrefix: title({ + field: continuousFieldName, + aggregate: upperExtentOp, + type: 'quantitative' + }, config, { + allowDisabling: false + }) + }, { + fieldPrefix: 'lower_', + titlePrefix: title({ + field: continuousFieldName, + aggregate: lowerExtentOp, + type: 'quantitative' + }, config, { + allowDisabling: false + }) + }, { + fieldPrefix: 'center_', + titlePrefix: title({ + field: continuousFieldName, + aggregate: centerOp, + type: 'quantitative' + }, config, { + allowDisabling: false + }) + }]; + } + } else { + if (markDef.center || markDef.extent) { + warn(errorBarCenterAndExtentAreNotNeeded(markDef.center, markDef.extent)); + } + if (inputType === 'aggregated-upper-lower') { + tooltipSummary = []; + postAggregateCalculates = [{ + calculate: `datum["${continuousAxisChannelDef2.field}"]`, + as: `upper_${continuousFieldName}` + }, { + calculate: `datum["${continuousFieldName}"]`, + as: `lower_${continuousFieldName}` + }]; + } else if (inputType === 'aggregated-error') { + tooltipSummary = [{ + fieldPrefix: '', + titlePrefix: continuousFieldName + }]; + postAggregateCalculates = [{ + calculate: `datum["${continuousFieldName}"] + datum["${continuousAxisChannelDefError.field}"]`, + as: `upper_${continuousFieldName}` + }]; + if (continuousAxisChannelDefError2) { + postAggregateCalculates.push({ + calculate: `datum["${continuousFieldName}"] + datum["${continuousAxisChannelDefError2.field}"]`, + as: `lower_${continuousFieldName}` + }); + } else { + postAggregateCalculates.push({ + calculate: `datum["${continuousFieldName}"] - datum["${continuousAxisChannelDefError.field}"]`, + as: `lower_${continuousFieldName}` + }); + } + } + for (const postAggregateCalculate of postAggregateCalculates) { + tooltipSummary.push({ + fieldPrefix: postAggregateCalculate.as.substring(0, 6), + titlePrefix: replaceAll(replaceAll(postAggregateCalculate.calculate, 'datum["', ''), '"]', '') + }); + } + } + return { + postAggregateCalculates, + errorBarSpecificAggregate, + tooltipSummary, + tooltipTitleWithFieldName + }; + } + function getTitlePrefix(center, extent, operation) { + return `${titleCase(center)} ${operation} ${extent}`; + } + + const ERRORBAND = 'errorband'; + const ERRORBAND_PARTS = ['band', 'borders']; + const errorBandNormalizer = new CompositeMarkNormalizer(ERRORBAND, normalizeErrorBand); + function normalizeErrorBand(spec, _ref) { + let { + config + } = _ref; + // Need to initEncoding first so we can infer type + spec = { + ...spec, + encoding: normalizeEncoding(spec.encoding, config) + }; + const { + transform, + continuousAxisChannelDef, + continuousAxis, + encodingWithoutContinuousAxis, + markDef, + outerSpec, + tooltipEncoding + } = errorBarParams(spec, ERRORBAND, config); + const errorBandDef = markDef; + const makeErrorBandPart = makeCompositeAggregatePartFactory(errorBandDef, continuousAxis, continuousAxisChannelDef, encodingWithoutContinuousAxis, config.errorband); + const is2D = spec.encoding.x !== undefined && spec.encoding.y !== undefined; + let bandMark = { + type: is2D ? 'area' : 'rect' + }; + let bordersMark = { + type: is2D ? 'line' : 'rule' + }; + const interpolate = { + ...(errorBandDef.interpolate ? { + interpolate: errorBandDef.interpolate + } : {}), + ...(errorBandDef.tension && errorBandDef.interpolate ? { + tension: errorBandDef.tension + } : {}) + }; + if (is2D) { + bandMark = { + ...bandMark, + ...interpolate, + ariaRoleDescription: 'errorband' + }; + bordersMark = { + ...bordersMark, + ...interpolate, + aria: false + }; + } else if (errorBandDef.interpolate) { + warn(errorBand1DNotSupport('interpolate')); + } else if (errorBandDef.tension) { + warn(errorBand1DNotSupport('tension')); + } + return { + ...outerSpec, + transform, + layer: [...makeErrorBandPart({ + partName: 'band', + mark: bandMark, + positionPrefix: 'lower', + endPositionPrefix: 'upper', + extraEncoding: tooltipEncoding + }), ...makeErrorBandPart({ + partName: 'borders', + mark: bordersMark, + positionPrefix: 'lower', + extraEncoding: tooltipEncoding + }), ...makeErrorBandPart({ + partName: 'borders', + mark: bordersMark, + positionPrefix: 'upper', + extraEncoding: tooltipEncoding + })] + }; + } + + /** + * Registry index for all composite mark's normalizer + */ + const compositeMarkRegistry = {}; + function add(mark, run, parts) { + const normalizer = new CompositeMarkNormalizer(mark, run); + compositeMarkRegistry[mark] = { + normalizer, + parts + }; + } + function getAllCompositeMarks() { + return keys(compositeMarkRegistry); + } + add(BOXPLOT, normalizeBoxPlot, BOXPLOT_PARTS); + add(ERRORBAR, normalizeErrorBar, ERRORBAR_PARTS); + add(ERRORBAND, normalizeErrorBand, ERRORBAND_PARTS); + + const VL_ONLY_LEGEND_CONFIG = ['gradientHorizontalMaxLength', 'gradientHorizontalMinLength', 'gradientVerticalMaxLength', 'gradientVerticalMinLength', 'unselectedOpacity']; + + const HEADER_TITLE_PROPERTIES_MAP = { + titleAlign: 'align', + titleAnchor: 'anchor', + titleAngle: 'angle', + titleBaseline: 'baseline', + titleColor: 'color', + titleFont: 'font', + titleFontSize: 'fontSize', + titleFontStyle: 'fontStyle', + titleFontWeight: 'fontWeight', + titleLimit: 'limit', + titleLineHeight: 'lineHeight', + titleOrient: 'orient', + titlePadding: 'offset' + }; + const HEADER_LABEL_PROPERTIES_MAP = { + labelAlign: 'align', + labelAnchor: 'anchor', + labelAngle: 'angle', + labelBaseline: 'baseline', + labelColor: 'color', + labelFont: 'font', + labelFontSize: 'fontSize', + labelFontStyle: 'fontStyle', + labelFontWeight: 'fontWeight', + labelLimit: 'limit', + labelLineHeight: 'lineHeight', + labelOrient: 'orient', + labelPadding: 'offset' + }; + const HEADER_TITLE_PROPERTIES = keys(HEADER_TITLE_PROPERTIES_MAP); + const HEADER_LABEL_PROPERTIES = keys(HEADER_LABEL_PROPERTIES_MAP); + + /** + * Headers of row / column channels for faceted plots. + */ + + const HEADER_CONFIGS_INDEX = { + header: 1, + headerRow: 1, + headerColumn: 1, + headerFacet: 1 + }; + const HEADER_CONFIGS = keys(HEADER_CONFIGS_INDEX); + + const LEGEND_SCALE_CHANNELS = ['size', 'shape', 'fill', 'stroke', 'strokeDash', 'strokeWidth', 'opacity']; + + /** + * Properties of a legend or boolean flag for determining whether to show it. + */ + + // Change comments to be Vega-Lite specific + + const defaultLegendConfig = { + gradientHorizontalMaxLength: 200, + gradientHorizontalMinLength: 100, + gradientVerticalMaxLength: 200, + gradientVerticalMinLength: 64, + // This is Vega's minimum. + unselectedOpacity: 0.35 + }; + const COMMON_LEGEND_PROPERTY_INDEX = { + aria: 1, + clipHeight: 1, + columnPadding: 1, + columns: 1, + cornerRadius: 1, + description: 1, + direction: 1, + fillColor: 1, + format: 1, + formatType: 1, + gradientLength: 1, + gradientOpacity: 1, + gradientStrokeColor: 1, + gradientStrokeWidth: 1, + gradientThickness: 1, + gridAlign: 1, + labelAlign: 1, + labelBaseline: 1, + labelColor: 1, + labelFont: 1, + labelFontSize: 1, + labelFontStyle: 1, + labelFontWeight: 1, + labelLimit: 1, + labelOffset: 1, + labelOpacity: 1, + labelOverlap: 1, + labelPadding: 1, + labelSeparation: 1, + legendX: 1, + legendY: 1, + offset: 1, + orient: 1, + padding: 1, + rowPadding: 1, + strokeColor: 1, + symbolDash: 1, + symbolDashOffset: 1, + symbolFillColor: 1, + symbolLimit: 1, + symbolOffset: 1, + symbolOpacity: 1, + symbolSize: 1, + symbolStrokeColor: 1, + symbolStrokeWidth: 1, + symbolType: 1, + tickCount: 1, + tickMinStep: 1, + title: 1, + titleAlign: 1, + titleAnchor: 1, + titleBaseline: 1, + titleColor: 1, + titleFont: 1, + titleFontSize: 1, + titleFontStyle: 1, + titleFontWeight: 1, + titleLimit: 1, + titleLineHeight: 1, + titleOpacity: 1, + titleOrient: 1, + titlePadding: 1, + type: 1, + values: 1, + zindex: 1 + }; + + const SELECTION_ID = '_vgsid_'; + + // Similar to BaseMarkConfig but the field documentations are specificly for an interval mark. + + const defaultConfig$1 = { + point: { + on: 'click', + fields: [SELECTION_ID], + toggle: 'event.shiftKey', + resolve: 'global', + clear: 'dblclick' + }, + interval: { + on: '[pointerdown, window:pointerup] > window:pointermove!', + encodings: ['x', 'y'], + translate: '[pointerdown, window:pointerup] > window:pointermove!', + zoom: 'wheel!', + mark: { + fill: '#333', + fillOpacity: 0.125, + stroke: 'white' + }, + resolve: 'global', + clear: 'dblclick' + } + }; + function isLegendBinding(bind) { + return bind === 'legend' || !!bind?.legend; + } + function isLegendStreamBinding(bind) { + return isLegendBinding(bind) && vega.isObject(bind); + } + function isSelectionParameter(param) { + return !!param?.['select']; + } + + function assembleParameterSignals(params) { + const signals = []; + for (const param of params || []) { + // Selection parameters are handled separately via assembleSelectionTopLevelSignals + // and assembleSignals methods registered on the Model. + if (isSelectionParameter(param)) continue; + const { + expr, + bind, + ...rest + } = param; + if (bind && expr) { + // Vega's InitSignal -- apply expr to "init" + const signal = { + ...rest, + bind, + init: expr + }; + signals.push(signal); + } else { + const signal = { + ...rest, + ...(expr ? { + update: expr + } : {}), + ...(bind ? { + bind + } : {}) + }; + signals.push(signal); + } + } + return signals; + } + + /** + * Base layout mixins for V/HConcatSpec, which should not have RowCol generic fo its property. + */ + + /** + * Base interface for a generalized concatenation specification. + */ + + /** + * Base interface for a vertical concatenation specification. + */ + + /** + * Base interface for a horizontal concatenation specification. + */ + + /** A concat spec without any shortcut/expansion syntax */ + + function isAnyConcatSpec(spec) { + return isVConcatSpec(spec) || isHConcatSpec(spec) || isConcatSpec(spec); + } + function isConcatSpec(spec) { + return 'concat' in spec; + } + function isVConcatSpec(spec) { + return 'vconcat' in spec; + } + function isHConcatSpec(spec) { + return 'hconcat' in spec; + } + + /** + * Common properties for all types of specification + */ + + function getStepFor(_ref) { + let { + step, + offsetIsDiscrete + } = _ref; + if (offsetIsDiscrete) { + return step.for ?? 'offset'; + } else { + return 'position'; + } + } + function isStep(size) { + return vega.isObject(size) && size['step'] !== undefined; + } + + // TODO(https://github.com/vega/vega-lite/issues/2503): Make this generic so we can support some form of top-down sizing. + /** + * Common properties for specifying width and height of unit and layer specifications. + */ + + function isFrameMixins(o) { + return o['view'] || o['width'] || o['height']; + } + + /** + * Base layout for FacetSpec and RepeatSpec. + * This is named "GenericComposition" layout as ConcatLayout is a GenericCompositionLayout too + * (but _not_ vice versa). + */ + + const DEFAULT_SPACING = 20; + const COMPOSITION_LAYOUT_INDEX = { + align: 1, + bounds: 1, + center: 1, + columns: 1, + spacing: 1 + }; + const COMPOSITION_LAYOUT_PROPERTIES = keys(COMPOSITION_LAYOUT_INDEX); + function extractCompositionLayout(spec, specType, config) { + const compositionConfig = config[specType]; + const layout = {}; + + // Apply config first + const { + spacing: spacingConfig, + columns + } = compositionConfig; + if (spacingConfig !== undefined) { + layout.spacing = spacingConfig; + } + if (columns !== undefined) { + if (isFacetSpec(spec) && !isFacetMapping(spec.facet) || isConcatSpec(spec)) { + layout.columns = columns; + } + } + if (isVConcatSpec(spec)) { + layout.columns = 1; + } + + // Then copy properties from the spec + for (const prop of COMPOSITION_LAYOUT_PROPERTIES) { + if (spec[prop] !== undefined) { + if (prop === 'spacing') { + const spacing = spec[prop]; + layout[prop] = vega.isNumber(spacing) ? spacing : { + row: spacing.row ?? spacingConfig, + column: spacing.column ?? spacingConfig + }; + } else { + layout[prop] = spec[prop]; + } + } + } + return layout; + } + + function getViewConfigContinuousSize(viewConfig, channel) { + return viewConfig[channel] ?? viewConfig[channel === 'width' ? 'continuousWidth' : 'continuousHeight']; // get width/height for backwards compatibility + } + function getViewConfigDiscreteStep(viewConfig, channel) { + const size = getViewConfigDiscreteSize(viewConfig, channel); + return isStep(size) ? size.step : DEFAULT_STEP; + } + function getViewConfigDiscreteSize(viewConfig, channel) { + const size = viewConfig[channel] ?? viewConfig[channel === 'width' ? 'discreteWidth' : 'discreteHeight']; // get width/height for backwards compatibility + return getFirstDefined(size, { + step: viewConfig.step + }); + } + const DEFAULT_STEP = 20; + const defaultViewConfig = { + continuousWidth: 200, + continuousHeight: 200, + step: DEFAULT_STEP + }; + const defaultConfig = { + background: 'white', + padding: 5, + timeFormat: '%b %d, %Y', + countTitle: 'Count of Records', + view: defaultViewConfig, + mark: defaultMarkConfig, + arc: {}, + area: {}, + bar: defaultBarConfig, + circle: {}, + geoshape: {}, + image: {}, + line: {}, + point: {}, + rect: defaultRectConfig, + rule: { + color: 'black' + }, + // Need this to override default color in mark config + square: {}, + text: { + color: 'black' + }, + // Need this to override default color in mark config + tick: defaultTickConfig, + trail: {}, + boxplot: { + size: 14, + extent: 1.5, + box: {}, + median: { + color: 'white' + }, + outliers: {}, + rule: {}, + ticks: null + }, + errorbar: { + center: 'mean', + rule: true, + ticks: false + }, + errorband: { + band: { + opacity: 0.3 + }, + borders: false + }, + scale: defaultScaleConfig, + projection: {}, + legend: defaultLegendConfig, + header: { + titlePadding: 10, + labelPadding: 10 + }, + headerColumn: {}, + headerRow: {}, + headerFacet: {}, + selection: defaultConfig$1, + style: {}, + title: {}, + facet: { + spacing: DEFAULT_SPACING + }, + concat: { + spacing: DEFAULT_SPACING + }, + normalizedNumberFormat: '.0%' + }; + + // Tableau10 color palette, copied from `vegaScale.scheme('tableau10')` + const tab10 = ['#4c78a8', '#f58518', '#e45756', '#72b7b2', '#54a24b', '#eeca3b', '#b279a2', '#ff9da6', '#9d755d', '#bab0ac']; + const DEFAULT_FONT_SIZE = { + text: 11, + guideLabel: 10, + guideTitle: 11, + groupTitle: 13, + groupSubtitle: 12 + }; + const DEFAULT_COLOR = { + blue: tab10[0], + orange: tab10[1], + red: tab10[2], + teal: tab10[3], + green: tab10[4], + yellow: tab10[5], + purple: tab10[6], + pink: tab10[7], + brown: tab10[8], + gray0: '#000', + gray1: '#111', + gray2: '#222', + gray3: '#333', + gray4: '#444', + gray5: '#555', + gray6: '#666', + gray7: '#777', + gray8: '#888', + gray9: '#999', + gray10: '#aaa', + gray11: '#bbb', + gray12: '#ccc', + gray13: '#ddd', + gray14: '#eee', + gray15: '#fff' + }; + function colorSignalConfig() { + let color = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + return { + signals: [{ + name: 'color', + value: vega.isObject(color) ? { + ...DEFAULT_COLOR, + ...color + } : DEFAULT_COLOR + }], + mark: { + color: { + signal: 'color.blue' + } + }, + rule: { + color: { + signal: 'color.gray0' + } + }, + text: { + color: { + signal: 'color.gray0' + } + }, + style: { + 'guide-label': { + fill: { + signal: 'color.gray0' + } + }, + 'guide-title': { + fill: { + signal: 'color.gray0' + } + }, + 'group-title': { + fill: { + signal: 'color.gray0' + } + }, + 'group-subtitle': { + fill: { + signal: 'color.gray0' + } + }, + cell: { + stroke: { + signal: 'color.gray8' + } + } + }, + axis: { + domainColor: { + signal: 'color.gray13' + }, + gridColor: { + signal: 'color.gray8' + }, + tickColor: { + signal: 'color.gray13' + } + }, + range: { + category: [{ + signal: 'color.blue' + }, { + signal: 'color.orange' + }, { + signal: 'color.red' + }, { + signal: 'color.teal' + }, { + signal: 'color.green' + }, { + signal: 'color.yellow' + }, { + signal: 'color.purple' + }, { + signal: 'color.pink' + }, { + signal: 'color.brown' + }, { + signal: 'color.grey8' + }] + } + }; + } + function fontSizeSignalConfig(fontSize) { + return { + signals: [{ + name: 'fontSize', + value: vega.isObject(fontSize) ? { + ...DEFAULT_FONT_SIZE, + ...fontSize + } : DEFAULT_FONT_SIZE + }], + text: { + fontSize: { + signal: 'fontSize.text' + } + }, + style: { + 'guide-label': { + fontSize: { + signal: 'fontSize.guideLabel' + } + }, + 'guide-title': { + fontSize: { + signal: 'fontSize.guideTitle' + } + }, + 'group-title': { + fontSize: { + signal: 'fontSize.groupTitle' + } + }, + 'group-subtitle': { + fontSize: { + signal: 'fontSize.groupSubtitle' + } + } + } + }; + } + function fontConfig(font) { + return { + text: { + font + }, + style: { + 'guide-label': { + font + }, + 'guide-title': { + font + }, + 'group-title': { + font + }, + 'group-subtitle': { + font + } + } + }; + } + function getAxisConfigInternal(axisConfig) { + const props = keys(axisConfig || {}); + const axisConfigInternal = {}; + for (const prop of props) { + const val = axisConfig[prop]; + axisConfigInternal[prop] = isConditionalAxisValue(val) ? signalOrValueRefWithCondition(val) : signalRefOrValue(val); + } + return axisConfigInternal; + } + function getStyleConfigInternal(styleConfig) { + const props = keys(styleConfig); + const styleConfigInternal = {}; + for (const prop of props) { + // We need to cast to cheat a bit here since styleConfig can be either mark config or axis config + styleConfigInternal[prop] = getAxisConfigInternal(styleConfig[prop]); + } + return styleConfigInternal; + } + const configPropsWithExpr = [...MARK_CONFIGS, ...AXIS_CONFIGS, ...HEADER_CONFIGS, 'background', 'padding', 'legend', 'lineBreak', 'scale', 'style', 'title', 'view']; + + /** + * Merge specified config with default config and config for the `color` flag, + * then replace all expressions with signals + */ + function initConfig() { + let specifiedConfig = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + const { + color, + font, + fontSize, + selection, + ...restConfig + } = specifiedConfig; + const mergedConfig = vega.mergeConfig({}, duplicate(defaultConfig), font ? fontConfig(font) : {}, color ? colorSignalConfig(color) : {}, fontSize ? fontSizeSignalConfig(fontSize) : {}, restConfig || {}); + + // mergeConfig doesn't recurse and overrides object values. + if (selection) { + vega.writeConfig(mergedConfig, 'selection', selection, true); + } + const outputConfig = omit(mergedConfig, configPropsWithExpr); + for (const prop of ['background', 'lineBreak', 'padding']) { + if (mergedConfig[prop]) { + outputConfig[prop] = signalRefOrValue(mergedConfig[prop]); + } + } + for (const markConfigType of MARK_CONFIGS) { + if (mergedConfig[markConfigType]) { + // FIXME: outputConfig[markConfigType] expects that types are replaced recursively but replaceExprRef only replaces one level deep + outputConfig[markConfigType] = replaceExprRef(mergedConfig[markConfigType]); + } + } + for (const axisConfigType of AXIS_CONFIGS) { + if (mergedConfig[axisConfigType]) { + outputConfig[axisConfigType] = getAxisConfigInternal(mergedConfig[axisConfigType]); + } + } + for (const headerConfigType of HEADER_CONFIGS) { + if (mergedConfig[headerConfigType]) { + outputConfig[headerConfigType] = replaceExprRef(mergedConfig[headerConfigType]); + } + } + if (mergedConfig.legend) { + outputConfig.legend = replaceExprRef(mergedConfig.legend); + } + if (mergedConfig.scale) { + const { + invalid, + ...otherScaleConfig + } = mergedConfig.scale; + const newScaleInvalid = replaceExprRef(invalid, { + level: 1 + }); + outputConfig.scale = { + ...replaceExprRef(otherScaleConfig), + ...(keys(newScaleInvalid).length > 0 ? { + invalid: newScaleInvalid + } : {}) + }; + } + if (mergedConfig.style) { + outputConfig.style = getStyleConfigInternal(mergedConfig.style); + } + if (mergedConfig.title) { + outputConfig.title = replaceExprRef(mergedConfig.title); + } + if (mergedConfig.view) { + outputConfig.view = replaceExprRef(mergedConfig.view); + } + return outputConfig; + } + const MARK_STYLES = new Set(['view', ...PRIMITIVE_MARKS]); + const VL_ONLY_CONFIG_PROPERTIES = ['color', 'fontSize', 'background', + // We apply background to the spec directly. + 'padding', 'facet', 'concat', 'numberFormat', 'numberFormatType', 'normalizedNumberFormat', 'normalizedNumberFormatType', 'timeFormat', 'countTitle', 'header', 'axisQuantitative', 'axisTemporal', 'axisDiscrete', 'axisPoint', 'axisXBand', 'axisXPoint', 'axisXDiscrete', 'axisXQuantitative', 'axisXTemporal', 'axisYBand', 'axisYPoint', 'axisYDiscrete', 'axisYQuantitative', 'axisYTemporal', 'scale', 'selection', 'overlay' // FIXME: Redesign and unhide this + ]; + const VL_ONLY_ALL_MARK_SPECIFIC_CONFIG_PROPERTY_INDEX = { + view: ['continuousWidth', 'continuousHeight', 'discreteWidth', 'discreteHeight', 'step'], + ...VL_ONLY_MARK_SPECIFIC_CONFIG_PROPERTY_INDEX + }; + function stripAndRedirectConfig(config) { + config = duplicate(config); + for (const prop of VL_ONLY_CONFIG_PROPERTIES) { + delete config[prop]; + } + if (config.axis) { + // delete condition axis config + for (const prop in config.axis) { + if (isConditionalAxisValue(config.axis[prop])) { + delete config.axis[prop]; + } + } + } + if (config.legend) { + for (const prop of VL_ONLY_LEGEND_CONFIG) { + delete config.legend[prop]; + } + } + + // Remove Vega-Lite only generic mark config + if (config.mark) { + for (const prop of VL_ONLY_MARK_CONFIG_PROPERTIES) { + delete config.mark[prop]; + } + if (config.mark.tooltip && vega.isObject(config.mark.tooltip)) { + delete config.mark.tooltip; + } + } + if (config.params) { + config.signals = (config.signals || []).concat(assembleParameterSignals(config.params)); + delete config.params; + } + for (const markType of MARK_STYLES) { + // Remove Vega-Lite-only mark config + for (const prop of VL_ONLY_MARK_CONFIG_PROPERTIES) { + delete config[markType][prop]; + } + + // Remove Vega-Lite only mark-specific config + const vlOnlyMarkSpecificConfigs = VL_ONLY_ALL_MARK_SPECIFIC_CONFIG_PROPERTY_INDEX[markType]; + if (vlOnlyMarkSpecificConfigs) { + for (const prop of vlOnlyMarkSpecificConfigs) { + delete config[markType][prop]; + } + } + + // Redirect mark config to config.style so that mark config only affect its own mark type + // without affecting other marks that share the same underlying Vega marks. + // For example, config.rect should not affect bar marks. + redirectConfigToStyleConfig(config, markType); + } + for (const m of getAllCompositeMarks()) { + // Clean up the composite mark config as we don't need them in the output specs anymore + delete config[m]; + } + redirectTitleConfig(config); + + // Remove empty config objects. + for (const prop in config) { + if (vega.isObject(config[prop]) && isEmpty(config[prop])) { + delete config[prop]; + } + } + return isEmpty(config) ? undefined : config; + } + + /** + * + * Redirect config.title -- so that title config do not affect header labels, + * which also uses `title` directive to implement. + * + * For subtitle configs in config.title, keep them in config.title as header titles never have subtitles. + */ + function redirectTitleConfig(config) { + const { + titleMarkConfig, + subtitleMarkConfig, + subtitle + } = extractTitleConfig(config.title); + + // set config.style if title/subtitleMarkConfig is not an empty object + if (!isEmpty(titleMarkConfig)) { + config.style['group-title'] = { + ...config.style['group-title'], + ...titleMarkConfig // config.title has higher precedence than config.style.group-title in Vega + }; + } + if (!isEmpty(subtitleMarkConfig)) { + config.style['group-subtitle'] = { + ...config.style['group-subtitle'], + ...subtitleMarkConfig + }; + } + + // subtitle part can stay in config.title since header titles do not use subtitle + if (!isEmpty(subtitle)) { + config.title = subtitle; + } else { + delete config.title; + } + } + function redirectConfigToStyleConfig(config, prop, + // string = composite mark + toProp, compositeMarkPart) { + const propConfig = config[prop]; + if (prop === 'view') { + toProp = 'cell'; // View's default style is "cell" + } + const style = { + ...propConfig, + ...config.style[toProp ?? prop] + }; + + // set config.style if it is not an empty object + if (!isEmpty(style)) { + config.style[toProp ?? prop] = style; + } + { + // For composite mark, so don't delete the whole config yet as we have to do multiple redirections. + delete config[prop]; + } + } + + /** + * Base interface for a layer specification. + */ + + /** + * A full layered plot specification, which may contains `encoding` and `projection` properties that will be applied to underlying unit (single-view) specifications. + */ + + /** + * A layered specification without any shortcut/expansion syntax. + */ + + function isLayerSpec(spec) { + return 'layer' in spec; + } + + /** + * Base interface for a repeat specification. + */ + + function isRepeatSpec(spec) { + return 'repeat' in spec; + } + function isLayerRepeatSpec(spec) { + return !vega.isArray(spec.repeat) && spec.repeat['layer']; + } + + class SpecMapper { + map(spec, params) { + if (isFacetSpec(spec)) { + return this.mapFacet(spec, params); + } else if (isRepeatSpec(spec)) { + return this.mapRepeat(spec, params); + } else if (isHConcatSpec(spec)) { + return this.mapHConcat(spec, params); + } else if (isVConcatSpec(spec)) { + return this.mapVConcat(spec, params); + } else if (isConcatSpec(spec)) { + return this.mapConcat(spec, params); + } else { + return this.mapLayerOrUnit(spec, params); + } + } + mapLayerOrUnit(spec, params) { + if (isLayerSpec(spec)) { + return this.mapLayer(spec, params); + } else if (isUnitSpec(spec)) { + return this.mapUnit(spec, params); + } + throw new Error(invalidSpec(spec)); + } + mapLayer(spec, params) { + return { + ...spec, + layer: spec.layer.map(subspec => this.mapLayerOrUnit(subspec, params)) + }; + } + mapHConcat(spec, params) { + return { + ...spec, + hconcat: spec.hconcat.map(subspec => this.map(subspec, params)) + }; + } + mapVConcat(spec, params) { + return { + ...spec, + vconcat: spec.vconcat.map(subspec => this.map(subspec, params)) + }; + } + mapConcat(spec, params) { + const { + concat, + ...rest + } = spec; + return { + ...rest, + concat: concat.map(subspec => this.map(subspec, params)) + }; + } + mapFacet(spec, params) { + return { + // as any is required here since TS cannot infer that FO may only be FieldName or Field, but not RepeatRef + ...spec, + // TODO: remove "any" once we support all facet listed in https://github.com/vega/vega-lite/issues/2760 + spec: this.map(spec.spec, params) + }; + } + mapRepeat(spec, params) { + return { + ...spec, + // as any is required here since TS cannot infer that the output type satisfies the input type + spec: this.map(spec.spec, params) + }; + } + } + + const STACK_OFFSET_INDEX = { + zero: 1, + center: 1, + normalize: 1 + }; + function isStackOffset(s) { + return s in STACK_OFFSET_INDEX; + } + const STACKABLE_MARKS = new Set([ARC, BAR, AREA, RULE, POINT, CIRCLE, SQUARE, LINE, TEXT, TICK]); + const STACK_BY_DEFAULT_MARKS = new Set([BAR, AREA, ARC]); + function isUnbinnedQuantitative(channelDef) { + return isFieldDef(channelDef) && channelDefType(channelDef) === 'quantitative' && !channelDef.bin; + } + function potentialStackedChannel(encoding, x, _ref) { + let { + orient, + type: mark + } = _ref; + const y = x === 'x' ? 'y' : 'radius'; + const isCartesianBarOrArea = x === 'x' && ['bar', 'area'].includes(mark); + const xDef = encoding[x]; + const yDef = encoding[y]; + if (isFieldDef(xDef) && isFieldDef(yDef)) { + if (isUnbinnedQuantitative(xDef) && isUnbinnedQuantitative(yDef)) { + if (xDef.stack) { + return x; + } else if (yDef.stack) { + return y; + } + const xAggregate = isFieldDef(xDef) && !!xDef.aggregate; + const yAggregate = isFieldDef(yDef) && !!yDef.aggregate; + // if there is no explicit stacking, only apply stack if there is only one aggregate for x or y + if (xAggregate !== yAggregate) { + return xAggregate ? x : y; + } + if (isCartesianBarOrArea) { + if (orient === 'vertical') { + return y; + } else if (orient === 'horizontal') { + return x; + } + } + } else if (isUnbinnedQuantitative(xDef)) { + return x; + } else if (isUnbinnedQuantitative(yDef)) { + return y; + } + } else if (isUnbinnedQuantitative(xDef)) { + if (isCartesianBarOrArea && orient === 'vertical') { + return undefined; + } + return x; + } else if (isUnbinnedQuantitative(yDef)) { + if (isCartesianBarOrArea && orient === 'horizontal') { + return undefined; + } + return y; + } + return undefined; + } + function getDimensionChannel(channel) { + switch (channel) { + case 'x': + return 'y'; + case 'y': + return 'x'; + case 'theta': + return 'radius'; + case 'radius': + return 'theta'; + } + } + function stack(m, encoding) { + const markDef = isMarkDef(m) ? m : { + type: m + }; + const mark = markDef.type; + + // Should have stackable mark + if (!STACKABLE_MARKS.has(mark)) { + return null; + } + + // Run potential stacked twice, one for Cartesian and another for Polar, + // so text marks can be stacked in any of the coordinates. + + // Note: The logic here is not perfectly correct. If we want to support stacked dot plots where each dot is a pie chart with label, we have to change the stack logic here to separate Cartesian stacking for polar stacking. + // However, since we probably never want to do that, let's just note the limitation here. + const fieldChannel = potentialStackedChannel(encoding, 'x', markDef) || potentialStackedChannel(encoding, 'theta', markDef); + if (!fieldChannel) { + return null; + } + const stackedFieldDef = encoding[fieldChannel]; + const stackedField = isFieldDef(stackedFieldDef) ? vgField(stackedFieldDef, {}) : undefined; + const dimensionChannel = getDimensionChannel(fieldChannel); + const groupbyChannels = []; + const groupbyFields = new Set(); + if (encoding[dimensionChannel]) { + const dimensionDef = encoding[dimensionChannel]; + const dimensionField = isFieldDef(dimensionDef) ? vgField(dimensionDef, {}) : undefined; + if (dimensionField && dimensionField !== stackedField) { + // avoid grouping by the stacked field + groupbyChannels.push(dimensionChannel); + groupbyFields.add(dimensionField); + } + } + const dimensionOffsetChannel = dimensionChannel === 'x' ? 'xOffset' : 'yOffset'; + const dimensionOffsetDef = encoding[dimensionOffsetChannel]; + const dimensionOffsetField = isFieldDef(dimensionOffsetDef) ? vgField(dimensionOffsetDef, {}) : undefined; + if (dimensionOffsetField && dimensionOffsetField !== stackedField) { + // avoid grouping by the stacked field + groupbyChannels.push(dimensionOffsetChannel); + groupbyFields.add(dimensionOffsetField); + } + + // If the dimension has offset, don't stack anymore + + // Should have grouping level of detail that is different from the dimension field + const stackBy = NONPOSITION_CHANNELS.reduce((sc, channel) => { + // Ignore tooltip in stackBy (https://github.com/vega/vega-lite/issues/4001) + if (channel !== 'tooltip' && channelHasField(encoding, channel)) { + const channelDef = encoding[channel]; + for (const cDef of vega.array(channelDef)) { + const fieldDef = getFieldDef(cDef); + if (fieldDef.aggregate) { + continue; + } + + // Check whether the channel's field is identical to x/y's field or if the channel is a repeat + const f = vgField(fieldDef, {}); + if ( + // if fielddef is a repeat, just include it in the stack by + !f || + // otherwise, the field must be different from the groupBy fields. + !groupbyFields.has(f)) { + sc.push({ + channel, + fieldDef + }); + } + } + } + return sc; + }, []); + + // Automatically determine offset + let offset; + if (stackedFieldDef.stack !== undefined) { + if (vega.isBoolean(stackedFieldDef.stack)) { + offset = stackedFieldDef.stack ? 'zero' : null; + } else { + offset = stackedFieldDef.stack; + } + } else if (STACK_BY_DEFAULT_MARKS.has(mark)) { + offset = 'zero'; + } + if (!offset || !isStackOffset(offset)) { + return null; + } + if (isAggregate$1(encoding) && stackBy.length === 0) { + return null; + } + + // warn when stacking non-linear + if (stackedFieldDef?.scale?.type && stackedFieldDef?.scale?.type !== ScaleType.LINEAR) { + if (stackedFieldDef?.stack) { + warn(stackNonLinearScale(stackedFieldDef.scale.type)); + } + } + + // Check if it is a ranged mark + if (isFieldOrDatumDef(encoding[getSecondaryRangeChannel(fieldChannel)])) { + if (stackedFieldDef.stack !== undefined) { + warn(cannotStackRangedMark(fieldChannel)); + } + return null; + } + + // Warn if stacking non-summative aggregate + if (isFieldDef(stackedFieldDef) && stackedFieldDef.aggregate && !SUM_OPS.has(stackedFieldDef.aggregate)) { + warn(stackNonSummativeAggregate(stackedFieldDef.aggregate)); + } + return { + groupbyChannels, + groupbyFields, + fieldChannel, + impute: stackedFieldDef.impute === null ? false : isPathMark(mark), + stackBy, + offset + }; + } + + function initMarkdef(originalMarkDef, encoding, config) { + // FIXME: markDef expects that exprRefs are replaced recursively but replaceExprRef only replaces the top level + const markDef = replaceExprRef(originalMarkDef); + + // set orient, which can be overridden by rules as sometimes the specified orient is invalid. + const specifiedOrient = getMarkPropOrConfig('orient', markDef, config); + markDef.orient = orient(markDef.type, encoding, specifiedOrient); + if (specifiedOrient !== undefined && specifiedOrient !== markDef.orient) { + warn(orientOverridden(markDef.orient, specifiedOrient)); + } + if (markDef.type === 'bar' && markDef.orient) { + const cornerRadiusEnd = getMarkPropOrConfig('cornerRadiusEnd', markDef, config); + if (cornerRadiusEnd !== undefined) { + const newProps = markDef.orient === 'horizontal' && encoding.x2 || markDef.orient === 'vertical' && encoding.y2 ? ['cornerRadius'] : BAR_CORNER_RADIUS_INDEX[markDef.orient]; + for (const newProp of newProps) { + markDef[newProp] = cornerRadiusEnd; + } + if (markDef.cornerRadiusEnd !== undefined) { + delete markDef.cornerRadiusEnd; // no need to keep the original cap cornerRadius + } + } + } + + // set opacity and filled if not specified in mark config + const specifiedOpacity = getMarkPropOrConfig('opacity', markDef, config); + const specifiedfillOpacity = getMarkPropOrConfig('fillOpacity', markDef, config); + if (specifiedOpacity === undefined && specifiedfillOpacity === undefined) { + markDef.opacity = opacity(markDef.type, encoding); + } + + // set cursor, which should be pointer if href channel is present unless otherwise specified + const specifiedCursor = getMarkPropOrConfig('cursor', markDef, config); + if (specifiedCursor === undefined) { + markDef.cursor = cursor(markDef, encoding, config); + } + return markDef; + } + function cursor(markDef, encoding, config) { + if (encoding.href || markDef.href || getMarkPropOrConfig('href', markDef, config)) { + return 'pointer'; + } + return markDef.cursor; + } + function opacity(mark, encoding) { + if (contains([POINT, TICK, CIRCLE, SQUARE], mark)) { + // point-based marks + if (!isAggregate$1(encoding)) { + return 0.7; + } + } + return undefined; + } + function defaultFilled(markDef, config, _ref) { + let { + graticule + } = _ref; + if (graticule) { + return false; + } + const filledConfig = getMarkConfig('filled', markDef, config); + const mark = markDef.type; + return getFirstDefined(filledConfig, mark !== POINT && mark !== LINE && mark !== RULE); + } + function orient(mark, encoding, specifiedOrient) { + switch (mark) { + case POINT: + case CIRCLE: + case SQUARE: + case TEXT: + case RECT: + case IMAGE: + // orient is meaningless for these marks. + return undefined; + } + const { + x, + y, + x2, + y2 + } = encoding; + switch (mark) { + case BAR: + if (isFieldDef(x) && (isBinned(x.bin) || isFieldDef(y) && y.aggregate && !x.aggregate)) { + return 'vertical'; + } + if (isFieldDef(y) && (isBinned(y.bin) || isFieldDef(x) && x.aggregate && !y.aggregate)) { + return 'horizontal'; + } + if (y2 || x2) { + // Ranged bar does not always have clear orientation, so we allow overriding + if (specifiedOrient) { + return specifiedOrient; + } + + // If y is range and x is non-range, non-bin Q + if (!x2) { + if (isFieldDef(x) && x.type === QUANTITATIVE && !isBinning(x.bin) || isNumericDataDef(x)) { + if (isFieldDef(y) && isBinned(y.bin)) { + return 'horizontal'; + } + } + return 'vertical'; + } + + // If x is range and y is non-range, non-bin Q + if (!y2) { + if (isFieldDef(y) && y.type === QUANTITATIVE && !isBinning(y.bin) || isNumericDataDef(y)) { + if (isFieldDef(x) && isBinned(x.bin)) { + return 'vertical'; + } + } + return 'horizontal'; + } + } + + // falls through + case RULE: + // return undefined for line segment rule and bar with both axis ranged + // we have to ignore the case that the data are already binned + if (x2 && !(isFieldDef(x) && isBinned(x.bin)) && y2 && !(isFieldDef(y) && isBinned(y.bin))) { + return undefined; + } + + // falls through + case AREA: + // If there are range for both x and y, y (vertical) has higher precedence. + if (y2) { + if (isFieldDef(y) && isBinned(y.bin)) { + return 'horizontal'; + } else { + return 'vertical'; + } + } else if (x2) { + if (isFieldDef(x) && isBinned(x.bin)) { + return 'vertical'; + } else { + return 'horizontal'; + } + } else if (mark === RULE) { + if (x && !y) { + return 'vertical'; + } else if (y && !x) { + return 'horizontal'; + } + } + + // falls through + case LINE: + case TICK: + { + const xIsMeasure = isUnbinnedQuantitativeFieldOrDatumDef(x); + const yIsMeasure = isUnbinnedQuantitativeFieldOrDatumDef(y); + if (specifiedOrient) { + return specifiedOrient; + } else if (xIsMeasure && !yIsMeasure) { + // Tick is opposite to bar, line, area + return mark !== 'tick' ? 'horizontal' : 'vertical'; + } else if (!xIsMeasure && yIsMeasure) { + // Tick is opposite to bar, line, area + return mark !== 'tick' ? 'vertical' : 'horizontal'; + } else if (xIsMeasure && yIsMeasure) { + return 'vertical'; + } else { + const xIsTemporal = isTypedFieldDef(x) && x.type === TEMPORAL; + const yIsTemporal = isTypedFieldDef(y) && y.type === TEMPORAL; + + // x: T, y: N --> vertical tick + if (xIsTemporal && !yIsTemporal) { + return 'vertical'; + } else if (!xIsTemporal && yIsTemporal) { + return 'horizontal'; + } + } + return undefined; + } + } + return 'vertical'; + } + + function dropLineAndPoint(markDef) { + const { + point: _point, + line: _line, + ...mark + } = markDef; + return keys(mark).length > 1 ? mark : mark.type; + } + function dropLineAndPointFromConfig(config) { + for (const mark of ['line', 'area', 'rule', 'trail']) { + if (config[mark]) { + config = { + ...config, + // TODO: remove as any + [mark]: omit(config[mark], ['point', 'line']) + }; + } + } + return config; + } + function getPointOverlay(markDef) { + let markConfig = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + let encoding = arguments.length > 2 ? arguments[2] : undefined; + if (markDef.point === 'transparent') { + return { + opacity: 0 + }; + } else if (markDef.point) { + // truthy : true or object + return vega.isObject(markDef.point) ? markDef.point : {}; + } else if (markDef.point !== undefined) { + // false or null + return null; + } else { + // undefined (not disabled) + if (markConfig.point || encoding.shape) { + // enable point overlay if config[mark].point is truthy or if encoding.shape is provided + return vega.isObject(markConfig.point) ? markConfig.point : {}; + } + // markDef.point is defined as falsy + return undefined; + } + } + function getLineOverlay(markDef) { + let markConfig = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + if (markDef.line) { + // true or object + return markDef.line === true ? {} : markDef.line; + } else if (markDef.line !== undefined) { + // false or null + return null; + } else { + // undefined (not disabled) + if (markConfig.line) { + // enable line overlay if config[mark].line is truthy + return markConfig.line === true ? {} : markConfig.line; + } + // markDef.point is defined as falsy + return undefined; + } + } + class PathOverlayNormalizer { + name = 'path-overlay'; + hasMatchingType(spec, config) { + if (isUnitSpec(spec)) { + const { + mark, + encoding + } = spec; + const markDef = isMarkDef(mark) ? mark : { + type: mark + }; + switch (markDef.type) { + case 'line': + case 'rule': + case 'trail': + return !!getPointOverlay(markDef, config[markDef.type], encoding); + case 'area': + return ( + // false / null are also included as we want to remove the properties + !!getPointOverlay(markDef, config[markDef.type], encoding) || !!getLineOverlay(markDef, config[markDef.type]) + ); + } + } + return false; + } + run(spec, normParams, normalize) { + const { + config + } = normParams; + const { + params, + projection, + mark, + name, + encoding: e, + ...outerSpec + } = spec; + + // Need to call normalizeEncoding because we need the inferred types to correctly determine stack + const encoding = normalizeEncoding(e, config); + const markDef = isMarkDef(mark) ? mark : { + type: mark + }; + const pointOverlay = getPointOverlay(markDef, config[markDef.type], encoding); + const lineOverlay = markDef.type === 'area' && getLineOverlay(markDef, config[markDef.type]); + const layer = [{ + name, + ...(params ? { + params + } : {}), + mark: dropLineAndPoint({ + // TODO: extract this 0.7 to be shared with default opacity for point/tick/... + ...(markDef.type === 'area' && markDef.opacity === undefined && markDef.fillOpacity === undefined ? { + opacity: 0.7 + } : {}), + ...markDef + }), + // drop shape from encoding as this might be used to trigger point overlay + encoding: omit(encoding, ['shape']) + }]; + + // FIXME: determine rules for applying selections. + + // Need to copy stack config to overlayed layer + // FIXME: normalizer shouldn't call `initMarkdef`, a method from an init phase. + const stackProps = stack(initMarkdef(markDef, encoding, config), encoding); + let overlayEncoding = encoding; + if (stackProps) { + const { + fieldChannel: stackFieldChannel, + offset + } = stackProps; + overlayEncoding = { + ...encoding, + [stackFieldChannel]: { + ...encoding[stackFieldChannel], + ...(offset ? { + stack: offset + } : {}) + } + }; + } + + // overlay line layer should be on the edge of area but passing y2/x2 makes + // it as "rule" mark so that it draws unwanted vertical/horizontal lines. + // point overlay also should not have y2/x2 as it does not support. + overlayEncoding = omit(overlayEncoding, ['y2', 'x2']); + if (lineOverlay) { + layer.push({ + ...(projection ? { + projection + } : {}), + mark: { + type: 'line', + ...pick(markDef, ['clip', 'interpolate', 'tension', 'tooltip']), + ...lineOverlay + }, + encoding: overlayEncoding + }); + } + if (pointOverlay) { + layer.push({ + ...(projection ? { + projection + } : {}), + mark: { + type: 'point', + opacity: 1, + filled: true, + ...pick(markDef, ['clip', 'tooltip']), + ...pointOverlay + }, + encoding: overlayEncoding + }); + } + return normalize({ + ...outerSpec, + layer + }, { + ...normParams, + config: dropLineAndPointFromConfig(config) + }); + } + } + + function replaceRepeaterInFacet(facet, repeater) { + if (!repeater) { + return facet; + } + if (isFacetMapping(facet)) { + return replaceRepeaterInMapping(facet, repeater); + } + return replaceRepeaterInFieldDef(facet, repeater); + } + function replaceRepeaterInEncoding(encoding, repeater) { + if (!repeater) { + return encoding; + } + return replaceRepeaterInMapping(encoding, repeater); + } + + /** + * Replaces repeated value and returns if the repeated value is valid. + */ + function replaceRepeatInProp(prop, o, repeater) { + const val = o[prop]; + if (isRepeatRef(val)) { + if (val.repeat in repeater) { + return { + ...o, + [prop]: repeater[val.repeat] + }; + } else { + warn(noSuchRepeatedValue(val.repeat)); + return undefined; + } + } + return o; + } + + /** + * Replace repeater values in a field def with the concrete field name. + */ + + function replaceRepeaterInFieldDef(fieldDef, repeater) { + fieldDef = replaceRepeatInProp('field', fieldDef, repeater); + if (fieldDef === undefined) { + // the field def should be ignored + return undefined; + } else if (fieldDef === null) { + return null; + } + if (isSortableFieldDef(fieldDef) && isSortField(fieldDef.sort)) { + const sort = replaceRepeatInProp('field', fieldDef.sort, repeater); + fieldDef = { + ...fieldDef, + ...(sort ? { + sort + } : {}) + }; + } + return fieldDef; + } + function replaceRepeaterInFieldOrDatumDef(def, repeater) { + if (isFieldDef(def)) { + return replaceRepeaterInFieldDef(def, repeater); + } else { + const datumDef = replaceRepeatInProp('datum', def, repeater); + if (datumDef !== def && !datumDef.type) { + datumDef.type = 'nominal'; + } + return datumDef; + } + } + function replaceRepeaterInChannelDef(channelDef, repeater) { + if (isFieldOrDatumDef(channelDef)) { + const fd = replaceRepeaterInFieldOrDatumDef(channelDef, repeater); + if (fd) { + return fd; + } else if (isConditionalDef(channelDef)) { + return { + condition: channelDef.condition + }; + } + } else { + if (hasConditionalFieldOrDatumDef(channelDef)) { + const fd = replaceRepeaterInFieldOrDatumDef(channelDef.condition, repeater); + if (fd) { + return { + ...channelDef, + condition: fd + }; + } else { + const { + condition, + ...channelDefWithoutCondition + } = channelDef; + return channelDefWithoutCondition; + } + } + return channelDef; + } + return undefined; + } + function replaceRepeaterInMapping(mapping, repeater) { + const out = {}; + for (const channel in mapping) { + if (vega.hasOwnProperty(mapping, channel)) { + const channelDef = mapping[channel]; + if (vega.isArray(channelDef)) { + // array cannot have condition + out[channel] = channelDef // somehow we need to cast it here + .map(cd => replaceRepeaterInChannelDef(cd, repeater)).filter(cd => cd); + } else { + const cd = replaceRepeaterInChannelDef(channelDef, repeater); + if (cd !== undefined) { + out[channel] = cd; + } + } + } + } + return out; + } + + class RuleForRangedLineNormalizer { + name = 'RuleForRangedLine'; + hasMatchingType(spec) { + if (isUnitSpec(spec)) { + const { + encoding, + mark + } = spec; + if (mark === 'line' || isMarkDef(mark) && mark.type === 'line') { + for (const channel of SECONDARY_RANGE_CHANNEL) { + const mainChannel = getMainRangeChannel(channel); + const mainChannelDef = encoding[mainChannel]; + if (encoding[channel]) { + if (isFieldDef(mainChannelDef) && !isBinned(mainChannelDef.bin) || isDatumDef(mainChannelDef)) { + return true; + } + } + } + } + } + return false; + } + run(spec, params, normalize) { + const { + encoding, + mark + } = spec; + warn(lineWithRange(!!encoding.x2, !!encoding.y2)); + return normalize({ + ...spec, + mark: vega.isObject(mark) ? { + ...mark, + type: 'rule' + } : 'rule' + }, params); + } + } + + class CoreNormalizer extends SpecMapper { + nonFacetUnitNormalizers = [boxPlotNormalizer, errorBarNormalizer, errorBandNormalizer, new PathOverlayNormalizer(), new RuleForRangedLineNormalizer()]; + map(spec, params) { + // Special handling for a faceted unit spec as it can return a facet spec, not just a layer or unit spec like a normal unit spec. + if (isUnitSpec(spec)) { + const hasRow = channelHasField(spec.encoding, ROW); + const hasColumn = channelHasField(spec.encoding, COLUMN); + const hasFacet = channelHasField(spec.encoding, FACET); + if (hasRow || hasColumn || hasFacet) { + return this.mapFacetedUnit(spec, params); + } + } + return super.map(spec, params); + } + + // This is for normalizing non-facet unit + mapUnit(spec, params) { + const { + parentEncoding, + parentProjection + } = params; + const encoding = replaceRepeaterInEncoding(spec.encoding, params.repeater); + const specWithReplacedEncoding = { + ...spec, + ...(spec.name ? { + name: [params.repeaterPrefix, spec.name].filter(n => n).join('_') + } : {}), + ...(encoding ? { + encoding + } : {}) + }; + if (parentEncoding || parentProjection) { + return this.mapUnitWithParentEncodingOrProjection(specWithReplacedEncoding, params); + } + const normalizeLayerOrUnit = this.mapLayerOrUnit.bind(this); + for (const unitNormalizer of this.nonFacetUnitNormalizers) { + if (unitNormalizer.hasMatchingType(specWithReplacedEncoding, params.config)) { + return unitNormalizer.run(specWithReplacedEncoding, params, normalizeLayerOrUnit); + } + } + return specWithReplacedEncoding; + } + mapRepeat(spec, params) { + if (isLayerRepeatSpec(spec)) { + return this.mapLayerRepeat(spec, params); + } else { + return this.mapNonLayerRepeat(spec, params); + } + } + mapLayerRepeat(spec, params) { + const { + repeat, + spec: childSpec, + ...rest + } = spec; + const { + row, + column, + layer + } = repeat; + const { + repeater = {}, + repeaterPrefix = '' + } = params; + if (row || column) { + return this.mapRepeat({ + ...spec, + repeat: { + ...(row ? { + row + } : {}), + ...(column ? { + column + } : {}) + }, + spec: { + repeat: { + layer + }, + spec: childSpec + } + }, params); + } else { + return { + ...rest, + layer: layer.map(layerValue => { + const childRepeater = { + ...repeater, + layer: layerValue + }; + const childName = `${(childSpec.name ? `${childSpec.name}_` : '') + repeaterPrefix}child__layer_${varName(layerValue)}`; + const child = this.mapLayerOrUnit(childSpec, { + ...params, + repeater: childRepeater, + repeaterPrefix: childName + }); + child.name = childName; + return child; + }) + }; + } + } + mapNonLayerRepeat(spec, params) { + const { + repeat, + spec: childSpec, + data, + ...remainingProperties + } = spec; + if (!vega.isArray(repeat) && spec.columns) { + // is repeat with row/column + spec = omit(spec, ['columns']); + warn(columnsNotSupportByRowCol('repeat')); + } + const concat = []; + const { + repeater = {}, + repeaterPrefix = '' + } = params; + const row = !vega.isArray(repeat) && repeat.row || [repeater ? repeater.row : null]; + const column = !vega.isArray(repeat) && repeat.column || [repeater ? repeater.column : null]; + const repeatValues = vega.isArray(repeat) && repeat || [repeater ? repeater.repeat : null]; + + // cross product + for (const repeatValue of repeatValues) { + for (const rowValue of row) { + for (const columnValue of column) { + const childRepeater = { + repeat: repeatValue, + row: rowValue, + column: columnValue, + layer: repeater.layer + }; + const childName = (childSpec.name ? `${childSpec.name}_` : '') + repeaterPrefix + 'child__' + (vega.isArray(repeat) ? `${varName(repeatValue)}` : (repeat.row ? `row_${varName(rowValue)}` : '') + (repeat.column ? `column_${varName(columnValue)}` : '')); + const child = this.map(childSpec, { + ...params, + repeater: childRepeater, + repeaterPrefix: childName + }); + child.name = childName; + + // we move data up + concat.push(omit(child, ['data'])); + } + } + } + const columns = vega.isArray(repeat) ? spec.columns : repeat.column ? repeat.column.length : 1; + return { + data: childSpec.data ?? data, + // data from child spec should have precedence + align: 'all', + ...remainingProperties, + columns, + concat + }; + } + mapFacet(spec, params) { + const { + facet + } = spec; + if (isFacetMapping(facet) && spec.columns) { + // is facet with row/column + spec = omit(spec, ['columns']); + warn(columnsNotSupportByRowCol('facet')); + } + return super.mapFacet(spec, params); + } + mapUnitWithParentEncodingOrProjection(spec, params) { + const { + encoding, + projection + } = spec; + const { + parentEncoding, + parentProjection, + config + } = params; + const mergedProjection = mergeProjection({ + parentProjection, + projection + }); + const mergedEncoding = mergeEncoding({ + parentEncoding, + encoding: replaceRepeaterInEncoding(encoding, params.repeater) + }); + return this.mapUnit({ + ...spec, + ...(mergedProjection ? { + projection: mergedProjection + } : {}), + ...(mergedEncoding ? { + encoding: mergedEncoding + } : {}) + }, { + config + }); + } + mapFacetedUnit(spec, normParams) { + // New encoding in the inside spec should not contain row / column + // as row/column should be moved to facet + const { + row, + column, + facet, + ...encoding + } = spec.encoding; + + // Mark and encoding should be moved into the inner spec + const { + mark, + width, + projection, + height, + view, + params, + encoding: _, + ...outerSpec + } = spec; + const { + facetMapping, + layout + } = this.getFacetMappingAndLayout({ + row, + column, + facet + }, normParams); + const newEncoding = replaceRepeaterInEncoding(encoding, normParams.repeater); + return this.mapFacet({ + ...outerSpec, + ...layout, + // row / column has higher precedence than facet + facet: facetMapping, + spec: { + ...(width ? { + width + } : {}), + ...(height ? { + height + } : {}), + ...(view ? { + view + } : {}), + ...(projection ? { + projection + } : {}), + mark, + encoding: newEncoding, + ...(params ? { + params + } : {}) + } + }, normParams); + } + getFacetMappingAndLayout(facets, params) { + const { + row, + column, + facet + } = facets; + if (row || column) { + if (facet) { + warn(facetChannelDropped([...(row ? [ROW] : []), ...(column ? [COLUMN] : [])])); + } + const facetMapping = {}; + const layout = {}; + for (const channel of [ROW, COLUMN]) { + const def = facets[channel]; + if (def) { + const { + align, + center, + spacing, + columns, + ...defWithoutLayout + } = def; + facetMapping[channel] = defWithoutLayout; + for (const prop of ['align', 'center', 'spacing']) { + if (def[prop] !== undefined) { + layout[prop] ??= {}; + layout[prop][channel] = def[prop]; + } + } + } + } + return { + facetMapping, + layout + }; + } else { + const { + align, + center, + spacing, + columns, + ...facetMapping + } = facet; + return { + facetMapping: replaceRepeaterInFacet(facetMapping, params.repeater), + layout: { + ...(align ? { + align + } : {}), + ...(center ? { + center + } : {}), + ...(spacing ? { + spacing + } : {}), + ...(columns ? { + columns + } : {}) + } + }; + } + } + mapLayer(spec, _ref) { + let { + parentEncoding, + parentProjection, + ...otherParams + } = _ref; + // Special handling for extended layer spec + + const { + encoding, + projection, + ...rest + } = spec; + const params = { + ...otherParams, + parentEncoding: mergeEncoding({ + parentEncoding, + encoding, + layer: true + }), + parentProjection: mergeProjection({ + parentProjection, + projection + }) + }; + return super.mapLayer({ + ...rest, + ...(spec.name ? { + name: [params.repeaterPrefix, spec.name].filter(n => n).join('_') + } : {}) + }, params); + } + } + function mergeEncoding(_ref2) { + let { + parentEncoding, + encoding = {}, + layer + } = _ref2; + let merged = {}; + if (parentEncoding) { + const channels = new Set([...keys(parentEncoding), ...keys(encoding)]); + for (const channel of channels) { + const channelDef = encoding[channel]; + const parentChannelDef = parentEncoding[channel]; + if (isFieldOrDatumDef(channelDef)) { + // Field/Datum Def can inherit properties from its parent + // Note that parentChannelDef doesn't have to be a field/datum def if the channelDef is already one. + const mergedChannelDef = { + ...parentChannelDef, + ...channelDef + }; + merged[channel] = mergedChannelDef; + } else if (hasConditionalFieldOrDatumDef(channelDef)) { + merged[channel] = { + ...channelDef, + condition: { + ...parentChannelDef, + ...channelDef.condition + } + }; + } else if (channelDef || channelDef === null) { + merged[channel] = channelDef; + } else if (layer || isValueDef(parentChannelDef) || isSignalRef(parentChannelDef) || isFieldOrDatumDef(parentChannelDef) || vega.isArray(parentChannelDef)) { + merged[channel] = parentChannelDef; + } + } + } else { + merged = encoding; + } + return !merged || isEmpty(merged) ? undefined : merged; + } + function mergeProjection(opt) { + const { + parentProjection, + projection + } = opt; + if (parentProjection && projection) { + warn(projectionOverridden({ + parentProjection, + projection + })); + } + return projection ?? parentProjection; + } + + function isFilter(t) { + return 'filter' in t; + } + function isImputeSequence(t) { + return t?.['stop'] !== undefined; + } + function isLookup(t) { + return 'lookup' in t; + } + function isLookupData(from) { + return 'data' in from; + } + function isLookupSelection(from) { + return 'param' in from; + } + function isPivot(t) { + return 'pivot' in t; + } + function isDensity(t) { + return 'density' in t; + } + function isQuantile(t) { + return 'quantile' in t; + } + function isRegression(t) { + return 'regression' in t; + } + function isLoess(t) { + return 'loess' in t; + } + function isSample(t) { + return 'sample' in t; + } + function isWindow(t) { + return 'window' in t; + } + function isJoinAggregate(t) { + return 'joinaggregate' in t; + } + function isFlatten(t) { + return 'flatten' in t; + } + function isCalculate(t) { + return 'calculate' in t; + } + function isBin(t) { + return 'bin' in t; + } + function isImpute(t) { + return 'impute' in t; + } + function isTimeUnit(t) { + return 'timeUnit' in t; + } + function isAggregate(t) { + return 'aggregate' in t; + } + function isStack(t) { + return 'stack' in t; + } + function isFold(t) { + return 'fold' in t; + } + function isExtent(t) { + return 'extent' in t && !('density' in t) && !('regression' in t); + } + function normalizeTransform(transform) { + return transform.map(t => { + if (isFilter(t)) { + return { + filter: normalizeLogicalComposition(t.filter, normalizePredicate$1) + }; + } + return t; + }); + } + + class SelectionCompatibilityNormalizer extends SpecMapper { + map(spec, normParams) { + normParams.emptySelections ??= {}; + normParams.selectionPredicates ??= {}; + spec = normalizeTransforms(spec, normParams); + return super.map(spec, normParams); + } + mapLayerOrUnit(spec, normParams) { + spec = normalizeTransforms(spec, normParams); + if (spec.encoding) { + const encoding = {}; + for (const [channel, enc] of entries$1(spec.encoding)) { + encoding[channel] = normalizeChannelDef(enc, normParams); + } + spec = { + ...spec, + encoding + }; + } + return super.mapLayerOrUnit(spec, normParams); + } + mapUnit(spec, normParams) { + const { + selection, + ...rest + } = spec; + if (selection) { + return { + ...rest, + params: entries$1(selection).map(_ref => { + let [name, selDef] = _ref; + const { + init: value, + bind, + empty, + ...select + } = selDef; + if (select.type === 'single') { + select.type = 'point'; + select.toggle = false; + } else if (select.type === 'multi') { + select.type = 'point'; + } + + // Propagate emptiness forwards and backwards + normParams.emptySelections[name] = empty !== 'none'; + for (const pred of vals(normParams.selectionPredicates[name] ?? {})) { + pred.empty = empty !== 'none'; + } + return { + name, + value, + select, + bind + }; + }) + }; + } + return spec; + } + } + function normalizeTransforms(spec, normParams) { + const { + transform: tx, + ...rest + } = spec; + if (tx) { + const transform = tx.map(t => { + if (isFilter(t)) { + return { + filter: normalizePredicate(t, normParams) + }; + } else if (isBin(t) && isBinParams(t.bin)) { + return { + ...t, + bin: normalizeBinExtent(t.bin) + }; + } else if (isLookup(t)) { + const { + selection: param, + ...from + } = t.from; + return param ? { + ...t, + from: { + param, + ...from + } + } : t; + } + return t; + }); + return { + ...rest, + transform + }; + } + return spec; + } + function normalizeChannelDef(obj, normParams) { + const enc = duplicate(obj); + if (isFieldDef(enc) && isBinParams(enc.bin)) { + enc.bin = normalizeBinExtent(enc.bin); + } + if (isScaleFieldDef(enc) && enc.scale?.domain?.selection) { + const { + selection: param, + ...domain + } = enc.scale.domain; + enc.scale.domain = { + ...domain, + ...(param ? { + param + } : {}) + }; + } + if (isConditionalDef(enc)) { + if (vega.isArray(enc.condition)) { + enc.condition = enc.condition.map(c => { + const { + selection, + param, + test, + ...cond + } = c; + return param ? c : { + ...cond, + test: normalizePredicate(c, normParams) + }; + }); + } else { + const { + selection, + param, + test, + ...cond + } = normalizeChannelDef(enc.condition, normParams); + enc.condition = param ? enc.condition : { + ...cond, + test: normalizePredicate(enc.condition, normParams) + }; + } + } + return enc; + } + function normalizeBinExtent(bin) { + const ext = bin.extent; + if (ext?.selection) { + const { + selection: param, + ...rest + } = ext; + return { + ...bin, + extent: { + ...rest, + param + } + }; + } + return bin; + } + function normalizePredicate(op, normParams) { + // Normalize old compositions of selection names (e.g., selection: {and: ["one", "two"]}) + const normalizeSelectionComposition = o => { + return normalizeLogicalComposition(o, param => { + const empty = normParams.emptySelections[param] ?? true; + const pred = { + param, + empty + }; + normParams.selectionPredicates[param] ??= []; + normParams.selectionPredicates[param].push(pred); + return pred; + }); + }; + return op.selection ? normalizeSelectionComposition(op.selection) : normalizeLogicalComposition(op.test || op.filter, o => o.selection ? normalizeSelectionComposition(o.selection) : o); + } + + class TopLevelSelectionsNormalizer extends SpecMapper { + map(spec, normParams) { + const selections = normParams.selections ?? []; + if (spec.params && !isUnitSpec(spec)) { + const params = []; + for (const param of spec.params) { + if (isSelectionParameter(param)) { + selections.push(param); + } else { + params.push(param); + } + } + spec.params = params; + } + normParams.selections = selections; + return super.map(spec, normParams); + } + mapUnit(spec, normParams) { + const selections = normParams.selections; + if (!selections || !selections.length) return spec; + const path = (normParams.path ?? []).concat(spec.name); + const params = []; + for (const selection of selections) { + // By default, apply selections to all unit views. + if (!selection.views || !selection.views.length) { + params.push(selection); + } else { + for (const view of selection.views) { + // view is either a specific unit name, or a partial path through the spec tree. + if (vega.isString(view) && (view === spec.name || path.includes(view)) || vega.isArray(view) && + // logic for backwards compatibility with view paths before we had unique names + // @ts-ignore + view.map(v => path.indexOf(v)).every((v, i, arr) => v !== -1 && (i === 0 || v > arr[i - 1]))) { + params.push(selection); + } + } + } + } + if (params.length) spec.params = params; + return spec; + } + } + for (const method of ['mapFacet', 'mapRepeat', 'mapHConcat', 'mapVConcat', 'mapLayer']) { + const proto = TopLevelSelectionsNormalizer.prototype[method]; + TopLevelSelectionsNormalizer.prototype[method] = function (spec, params) { + return proto.call(this, spec, addSpecNameToParams(spec, params)); + }; + } + function addSpecNameToParams(spec, params) { + return spec.name ? { + ...params, + path: (params.path ?? []).concat(spec.name) + } : params; + } + + function normalize(spec, config) { + if (config === undefined) { + config = initConfig(spec.config); + } + const normalizedSpec = normalizeGenericSpec(spec, config); + const { + width, + height + } = spec; + const autosize = normalizeAutoSize(normalizedSpec, { + width, + height, + autosize: spec.autosize + }, config); + return { + ...normalizedSpec, + ...(autosize ? { + autosize + } : {}) + }; + } + const coreNormalizer = new CoreNormalizer(); + const selectionCompatNormalizer = new SelectionCompatibilityNormalizer(); + const topLevelSelectionNormalizer = new TopLevelSelectionsNormalizer(); + + /** + * Decompose extended unit specs into composition of pure unit specs. + * And push top-level selection definitions down to unit specs. + */ + function normalizeGenericSpec(spec) { + let config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + const normParams = { + config + }; + return topLevelSelectionNormalizer.map(coreNormalizer.map(selectionCompatNormalizer.map(spec, normParams), normParams), normParams); + } + function _normalizeAutoSize(autosize) { + return vega.isString(autosize) ? { + type: autosize + } : autosize ?? {}; + } + + /** + * Normalize autosize and deal with width or height == "container". + */ + function normalizeAutoSize(spec, sizeInfo, config) { + let { + width, + height + } = sizeInfo; + const isFitCompatible = isUnitSpec(spec) || isLayerSpec(spec); + const autosizeDefault = {}; + if (!isFitCompatible) { + // If spec is not compatible with autosize == "fit", discard width/height == container + if (width == 'container') { + warn(containerSizeNonSingle('width')); + width = undefined; + } + if (height == 'container') { + warn(containerSizeNonSingle('height')); + height = undefined; + } + } else { + // Default autosize parameters to fit when width/height is "container" + if (width == 'container' && height == 'container') { + autosizeDefault.type = 'fit'; + autosizeDefault.contains = 'padding'; + } else if (width == 'container') { + autosizeDefault.type = 'fit-x'; + autosizeDefault.contains = 'padding'; + } else if (height == 'container') { + autosizeDefault.type = 'fit-y'; + autosizeDefault.contains = 'padding'; + } + } + const autosize = { + type: 'pad', + ...autosizeDefault, + ...(config ? _normalizeAutoSize(config.autosize) : {}), + ..._normalizeAutoSize(spec.autosize) + }; + if (autosize.type === 'fit' && !isFitCompatible) { + warn(FIT_NON_SINGLE); + autosize.type = 'pad'; + } + if (width == 'container' && !(autosize.type == 'fit' || autosize.type == 'fit-x')) { + warn(containerSizeNotCompatibleWithAutosize('width')); + } + if (height == 'container' && !(autosize.type == 'fit' || autosize.type == 'fit-y')) { + warn(containerSizeNotCompatibleWithAutosize('height')); + } + + // Delete autosize property if it's Vega's default + if (deepEqual(autosize, { + type: 'pad' + })) { + return undefined; + } + return autosize; + } + + /** + * @minimum 0 + */ + + /** + * Shared properties between Top-Level specs and Config + */ + + function isFitType(autoSizeType) { + return autoSizeType === 'fit' || autoSizeType === 'fit-x' || autoSizeType === 'fit-y'; + } + function getFitType(sizeType) { + return sizeType ? `fit-${getPositionScaleChannel(sizeType)}` : 'fit'; + } + const TOP_LEVEL_PROPERTIES = ['background', 'padding' + // We do not include "autosize" here as it is supported by only unit and layer specs and thus need to be normalized + ]; + function extractTopLevelProperties(t, includeParams) { + const o = {}; + for (const p of TOP_LEVEL_PROPERTIES) { + if (t && t[p] !== undefined) { + o[p] = signalRefOrValue(t[p]); + } + } + if (includeParams) { + o.params = t.params; + } + return o; + } + + /** + * Generic class for storing properties that are explicitly specified + * and implicitly determined by the compiler. + * This is important for scale/axis/legend merging as + * we want to prioritize properties that users explicitly specified. + */ + // eslint-disable-next-line @typescript-eslint/ban-types + class Split { + constructor() { + let explicit = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + let implicit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + this.explicit = explicit; + this.implicit = implicit; + } + clone() { + return new Split(duplicate(this.explicit), duplicate(this.implicit)); + } + combine() { + return { + ...this.explicit, + // Explicit properties comes first + ...this.implicit + }; + } + get(key) { + // Explicit has higher precedence + return getFirstDefined(this.explicit[key], this.implicit[key]); + } + getWithExplicit(key) { + // Explicit has higher precedence + if (this.explicit[key] !== undefined) { + return { + explicit: true, + value: this.explicit[key] + }; + } else if (this.implicit[key] !== undefined) { + return { + explicit: false, + value: this.implicit[key] + }; + } + return { + explicit: false, + value: undefined + }; + } + setWithExplicit(key, _ref) { + let { + value, + explicit + } = _ref; + if (value !== undefined) { + this.set(key, value, explicit); + } + } + set(key, value, explicit) { + delete this[explicit ? 'implicit' : 'explicit'][key]; + this[explicit ? 'explicit' : 'implicit'][key] = value; + return this; + } + copyKeyFromSplit(key, _ref2) { + let { + explicit, + implicit + } = _ref2; + // Explicit has higher precedence + if (explicit[key] !== undefined) { + this.set(key, explicit[key], true); + } else if (implicit[key] !== undefined) { + this.set(key, implicit[key], false); + } + } + copyKeyFromObject(key, s) { + // Explicit has higher precedence + if (s[key] !== undefined) { + this.set(key, s[key], true); + } + } + + /** + * Merge split object into this split object. Properties from the other split + * overwrite properties from this split. + */ + copyAll(other) { + for (const key of keys(other.combine())) { + const val = other.getWithExplicit(key); + this.setWithExplicit(key, val); + } + } + } + function makeExplicit(value) { + return { + explicit: true, + value + }; + } + function makeImplicit(value) { + return { + explicit: false, + value + }; + } + function tieBreakByComparing(compare) { + return (v1, v2, property, propertyOf) => { + const diff = compare(v1.value, v2.value); + if (diff > 0) { + return v1; + } else if (diff < 0) { + return v2; + } + return defaultTieBreaker(v1, v2, property, propertyOf); + }; + } + function defaultTieBreaker(v1, v2, property, propertyOf) { + if (v1.explicit && v2.explicit) { + warn(mergeConflictingProperty(property, propertyOf, v1.value, v2.value)); + } + // If equal score, prefer v1. + return v1; + } + function mergeValuesWithExplicit(v1, v2, property, propertyOf) { + let tieBreaker = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : defaultTieBreaker; + if (v1 === undefined || v1.value === undefined) { + // For first run + return v2; + } + if (v1.explicit && !v2.explicit) { + return v1; + } else if (v2.explicit && !v1.explicit) { + return v2; + } else if (deepEqual(v1.value, v2.value)) { + return v1; + } else { + return tieBreaker(v1, v2, property, propertyOf); + } + } + + /** + * Class to track interesting properties (see https://15721.courses.cs.cmu.edu/spring2016/papers/graefe-ieee1995.pdf) + * about how fields have been parsed or whether they have been derived in a transform. We use this to not parse the + * same field again (or differently). + */ + class AncestorParse extends Split { + constructor() { + let explicit = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + let implicit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + let parseNothing = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + super(explicit, implicit); + this.explicit = explicit; + this.implicit = implicit; + this.parseNothing = parseNothing; + } + clone() { + const clone = super.clone(); + clone.parseNothing = this.parseNothing; + return clone; + } + } + + /* + * Constants and utilities for data. + */ + + // eslint-disable-next-line @typescript-eslint/ban-types + + function isUrlData(data) { + return 'url' in data; + } + function isInlineData(data) { + return 'values' in data; + } + function isNamedData(data) { + return 'name' in data && !isUrlData(data) && !isInlineData(data) && !isGenerator(data); + } + function isGenerator(data) { + return data && (isSequenceGenerator(data) || isSphereGenerator(data) || isGraticuleGenerator(data)); + } + function isSequenceGenerator(data) { + return 'sequence' in data; + } + function isSphereGenerator(data) { + return 'sphere' in data; + } + function isGraticuleGenerator(data) { + return 'graticule' in data; + } + let DataSourceType = /*#__PURE__*/function (DataSourceType) { + DataSourceType[DataSourceType["Raw"] = 0] = "Raw"; + DataSourceType[DataSourceType["Main"] = 1] = "Main"; + DataSourceType[DataSourceType["Row"] = 2] = "Row"; + DataSourceType[DataSourceType["Column"] = 3] = "Column"; + DataSourceType[DataSourceType["Lookup"] = 4] = "Lookup"; + DataSourceType[DataSourceType["PreFilterInvalid"] = 5] = "PreFilterInvalid"; + DataSourceType[DataSourceType["PostFilterInvalid"] = 6] = "PostFilterInvalid"; + return DataSourceType; + }({}); + + function getDataSourcesForHandlingInvalidValues(_ref) { + let { + invalid, + isPath + } = _ref; + const normalizedInvalid = normalizeInvalidDataMode(invalid, { + isPath + }); + switch (normalizedInvalid) { + case 'filter': + // Both marks and scales use post-filter data + return { + marks: 'exclude-invalid-values', + scales: 'exclude-invalid-values' + }; + case 'break-paths-show-domains': + return { + // Path-based marks use pre-filter data so we know to skip these invalid points in the path. + // For non-path based marks, we skip by not showing them at all. + marks: isPath ? 'include-invalid-values' : 'exclude-invalid-values', + scales: 'include-invalid-values' + }; + case 'break-paths-filter-domains': + // For path marks, the marks will use unfiltered data (and skip points). But we need a separate data sources to feed the domain. + // For non-path marks, we can use the filtered data for both marks and scales. + return { + marks: isPath ? 'include-invalid-values' : 'exclude-invalid-values', + // Unlike 'break-paths-show-domains', 'break-paths-filter-domains' uses post-filter data to feed scale. + scales: 'exclude-invalid-values' + }; + case 'show': + return { + marks: 'include-invalid-values', + scales: 'include-invalid-values' + }; + } + } + function getScaleDataSourceForHandlingInvalidValues(props) { + const { + marks, + scales + } = getDataSourcesForHandlingInvalidValues(props); + if (marks === scales) { + // If both marks and scales use the same data, there is only the main data source. + return DataSourceType.Main; + } + // If marks and scales use differetnt data, return the pre/post-filter data source accordingly. + return scales === 'include-invalid-values' ? DataSourceType.PreFilterInvalid : DataSourceType.PostFilterInvalid; + } + + function assembleProjection(proj) { + const { + signals, + hasLegend, + index, + ...rest + } = proj; + rest.field = replacePathInField(rest.field); + return rest; + } + function assembleInit(init) { + let isExpr = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; + let wrap = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : vega.identity; + if (vega.isArray(init)) { + const assembled = init.map(v => assembleInit(v, isExpr, wrap)); + return isExpr ? `[${assembled.join(', ')}]` : assembled; + } else if (isDateTime(init)) { + if (isExpr) { + return wrap(dateTimeToExpr(init)); + } else { + return wrap(dateTimeToTimestamp(init)); + } + } + return isExpr ? wrap(stringify(init)) : init; + } + function assembleUnitSelectionSignals(model, signals) { + for (const selCmpt of vals(model.component.selection ?? {})) { + const name = selCmpt.name; + let modifyExpr = `${name}${TUPLE}, ${selCmpt.resolve === 'global' ? 'true' : `{unit: ${unitName(model)}}`}`; + for (const c of selectionCompilers) { + if (!c.defined(selCmpt)) continue; + if (c.signals) signals = c.signals(model, selCmpt, signals); + if (c.modifyExpr) modifyExpr = c.modifyExpr(model, selCmpt, modifyExpr); + } + signals.push({ + name: name + MODIFY, + on: [{ + events: { + signal: selCmpt.name + TUPLE + }, + update: `modify(${vega.stringValue(selCmpt.name + STORE)}, ${modifyExpr})` + }] + }); + } + return cleanupEmptyOnArray(signals); + } + function assembleFacetSignals(model, signals) { + if (model.component.selection && keys(model.component.selection).length) { + const name = vega.stringValue(model.getName('cell')); + signals.unshift({ + name: 'facet', + value: {}, + on: [{ + events: vega.parseSelector('pointermove', 'scope'), + update: `isTuple(facet) ? facet : group(${name}).datum` + }] + }); + } + return cleanupEmptyOnArray(signals); + } + function assembleTopLevelSignals(model, signals) { + let hasSelections = false; + for (const selCmpt of vals(model.component.selection ?? {})) { + const name = selCmpt.name; + const store = vega.stringValue(name + STORE); + const hasSg = signals.filter(s => s.name === name); + if (hasSg.length === 0) { + const resolve = selCmpt.resolve === 'global' ? 'union' : selCmpt.resolve; + const isPoint = selCmpt.type === 'point' ? ', true, true)' : ')'; + signals.push({ + name: selCmpt.name, + update: `${VL_SELECTION_RESOLVE}(${store}, ${vega.stringValue(resolve)}${isPoint}` + }); + } + hasSelections = true; + for (const c of selectionCompilers) { + if (c.defined(selCmpt) && c.topLevelSignals) { + signals = c.topLevelSignals(model, selCmpt, signals); + } + } + } + if (hasSelections) { + const hasUnit = signals.filter(s => s.name === 'unit'); + if (hasUnit.length === 0) { + signals.unshift({ + name: 'unit', + value: {}, + on: [{ + events: 'pointermove', + update: 'isTuple(group()) ? group() : unit' + }] + }); + } + } + return cleanupEmptyOnArray(signals); + } + function assembleUnitSelectionData(model, data) { + const dataCopy = [...data]; + const unit = unitName(model, { + escape: false + }); + for (const selCmpt of vals(model.component.selection ?? {})) { + const store = { + name: selCmpt.name + STORE + }; + if (selCmpt.project.hasSelectionId) { + store.transform = [{ + type: 'collect', + sort: { + field: SELECTION_ID + } + }]; + } + if (selCmpt.init) { + const fields = selCmpt.project.items.map(assembleProjection); + store.values = selCmpt.project.hasSelectionId ? selCmpt.init.map(i => ({ + unit, + [SELECTION_ID]: assembleInit(i, false)[0] + })) : selCmpt.init.map(i => ({ + unit, + fields, + values: assembleInit(i, false) + })); + } + const contains = dataCopy.filter(d => d.name === selCmpt.name + STORE); + if (!contains.length) { + dataCopy.push(store); + } + } + return dataCopy; + } + function assembleUnitSelectionMarks(model, marks) { + for (const selCmpt of vals(model.component.selection ?? {})) { + for (const c of selectionCompilers) { + if (c.defined(selCmpt) && c.marks) { + marks = c.marks(model, selCmpt, marks); + } + } + } + return marks; + } + function assembleLayerSelectionMarks(model, marks) { + for (const child of model.children) { + if (isUnitModel(child)) { + marks = assembleUnitSelectionMarks(child, marks); + } + } + return marks; + } + function assembleSelectionScaleDomain(model, extent, scaleCmpt, domain) { + const parsedExtent = parseSelectionExtent(model, extent.param, extent); + return { + signal: hasContinuousDomain(scaleCmpt.get('type')) && vega.isArray(domain) && domain[0] > domain[1] ? `isValid(${parsedExtent}) && reverse(${parsedExtent})` : parsedExtent + }; + } + function cleanupEmptyOnArray(signals) { + return signals.map(s => { + if (s.on && !s.on.length) delete s.on; + return s; + }); + } + + /** + * A node in the dataflow tree. + */ + class DataFlowNode { + _children = []; + _parent = null; + constructor(parent, debugName) { + this.debugName = debugName; + if (parent) { + this.parent = parent; + } + } + + /** + * Clone this node with a deep copy but don't clone links to children or parents. + */ + clone() { + throw new Error('Cannot clone node'); + } + + /** + * Return a hash of the node. + */ + + /** + * Set of fields that this node depends on. + */ + + /** + * Set of fields that are being created by this node. + */ + + get parent() { + return this._parent; + } + + /** + * Set the parent of the node and also add this node to the parent's children. + */ + set parent(parent) { + this._parent = parent; + if (parent) { + parent.addChild(this); + } + } + get children() { + return this._children; + } + numChildren() { + return this._children.length; + } + addChild(child, loc) { + // do not add the same child twice + if (this._children.includes(child)) { + warn(ADD_SAME_CHILD_TWICE); + return; + } + if (loc !== undefined) { + this._children.splice(loc, 0, child); + } else { + this._children.push(child); + } + } + removeChild(oldChild) { + const loc = this._children.indexOf(oldChild); + this._children.splice(loc, 1); + return loc; + } + + /** + * Remove node from the dataflow. + */ + remove() { + let loc = this._parent.removeChild(this); + for (const child of this._children) { + // do not use the set method because we want to insert at a particular location + child._parent = this._parent; + this._parent.addChild(child, loc++); + } + } + + /** + * Insert another node as a parent of this node. + */ + insertAsParentOf(other) { + const parent = other.parent; + parent.removeChild(this); + this.parent = parent; + other.parent = this; + } + swapWithParent() { + const parent = this._parent; + const newParent = parent.parent; + + // reconnect the children + for (const child of this._children) { + child.parent = parent; + } + + // remove old links + this._children = []; // equivalent to removing every child link one by one + parent.removeChild(this); + const loc = parent.parent.removeChild(parent); + + // swap two nodes but maintain order in children + this._parent = newParent; + newParent.addChild(this, loc); + parent.parent = this; + } + } + class OutputNode extends DataFlowNode { + clone() { + const cloneObj = new this.constructor(); + cloneObj.debugName = `clone_${this.debugName}`; + cloneObj._source = this._source; + cloneObj._name = `clone_${this._name}`; + cloneObj.type = this.type; + cloneObj.refCounts = this.refCounts; + cloneObj.refCounts[cloneObj._name] = 0; + return cloneObj; + } + + /** + * @param source The name of the source. Will change in assemble. + * @param type The type of the output node. + * @param refCounts A global ref counter map. + */ + constructor(parent, source, type, refCounts) { + super(parent, source); + this.type = type; + this.refCounts = refCounts; + this._source = this._name = source; + if (this.refCounts && !(this._name in this.refCounts)) { + this.refCounts[this._name] = 0; + } + } + dependentFields() { + return new Set(); + } + producedFields() { + return new Set(); + } + hash() { + if (this._hash === undefined) { + this._hash = `Output ${uniqueId()}`; + } + return this._hash; + } + + /** + * Request the datasource name and increase the ref counter. + * + * During the parsing phase, this will return the simple name such as 'main' or 'raw'. + * It is crucial to request the name from an output node to mark it as a required node. + * If nobody ever requests the name, this datasource will not be instantiated in the assemble phase. + * + * In the assemble phase, this will return the correct name. + */ + getSource() { + this.refCounts[this._name]++; + return this._source; + } + isRequired() { + return !!this.refCounts[this._name]; + } + setSource(source) { + this._source = source; + } + } + + function isTimeUnitTransformComponent(timeUnitComponent) { + return timeUnitComponent.as !== undefined; + } + function offsetAs(field) { + return `${field}_end`; + } + class TimeUnitNode extends DataFlowNode { + clone() { + return new TimeUnitNode(null, duplicate(this.timeUnits)); + } + constructor(parent, timeUnits) { + super(parent); + this.timeUnits = timeUnits; + } + static makeFromEncoding(parent, model) { + const formula = model.reduceFieldDef((timeUnitComponent, fieldDef, channel) => { + const { + field, + timeUnit + } = fieldDef; + if (timeUnit) { + let component; + if (isBinnedTimeUnit(timeUnit)) { + // For binned time unit, only produce end if the mark is a rect-based mark (rect, bar, image, arc), which needs "range". + + if (isUnitModel(model)) { + const { + mark, + markDef, + config + } = model; + const bandPosition = getBandPosition({ + fieldDef, + markDef, + config + }); + if (isRectBasedMark(mark) || !!bandPosition) { + component = { + timeUnit: normalizeTimeUnit(timeUnit), + field + }; + } + } + } else { + component = { + as: vgField(fieldDef, { + forAs: true + }), + field, + timeUnit + }; + } + if (isUnitModel(model)) { + const { + mark, + markDef, + config + } = model; + const bandPosition = getBandPosition({ + fieldDef, + markDef, + config + }); + if (isRectBasedMark(mark) && isXorY(channel) && bandPosition !== 0.5) { + component.rectBandPosition = bandPosition; + } + } + if (component) { + timeUnitComponent[hash(component)] = component; + } + } + return timeUnitComponent; + }, {}); + if (isEmpty(formula)) { + return null; + } + return new TimeUnitNode(parent, formula); + } + static makeFromTransform(parent, t) { + const { + timeUnit, + ...other + } = { + ...t + }; + const normalizedTimeUnit = normalizeTimeUnit(timeUnit); + const component = { + ...other, + timeUnit: normalizedTimeUnit + }; + return new TimeUnitNode(parent, { + [hash(component)]: component + }); + } + + /** + * Merge together TimeUnitNodes assigning the children of `other` to `this` + * and removing `other`. + */ + merge(other) { + this.timeUnits = { + ...this.timeUnits + }; + + // if the same hash happen twice, merge + for (const key in other.timeUnits) { + if (!this.timeUnits[key]) { + // copy if it's not a duplicate + this.timeUnits[key] = other.timeUnits[key]; + } + } + for (const child of other.children) { + other.removeChild(child); + child.parent = this; + } + other.remove(); + } + + /** + * Remove time units coming from the other node. + */ + removeFormulas(fields) { + const newFormula = {}; + for (const [key, timeUnitComponent] of entries$1(this.timeUnits)) { + const fieldAs = isTimeUnitTransformComponent(timeUnitComponent) ? timeUnitComponent.as : `${timeUnitComponent.field}_end`; + if (!fields.has(fieldAs)) { + newFormula[key] = timeUnitComponent; + } + } + this.timeUnits = newFormula; + } + producedFields() { + return new Set(vals(this.timeUnits).map(f => { + return isTimeUnitTransformComponent(f) ? f.as : offsetAs(f.field); + })); + } + dependentFields() { + return new Set(vals(this.timeUnits).map(f => f.field)); + } + hash() { + return `TimeUnit ${hash(this.timeUnits)}`; + } + assemble() { + const transforms = []; + for (const f of vals(this.timeUnits)) { + const { + rectBandPosition + } = f; + const normalizedTimeUnit = normalizeTimeUnit(f.timeUnit); + if (isTimeUnitTransformComponent(f)) { + const { + field, + as + } = f; + const { + unit, + utc, + ...params + } = normalizedTimeUnit; + const startEnd = [as, `${as}_end`]; + transforms.push({ + field: replacePathInField(field), + type: 'timeunit', + ...(unit ? { + units: getTimeUnitParts(unit) + } : {}), + ...(utc ? { + timezone: 'utc' + } : {}), + ...params, + as: startEnd + }); + transforms.push(...offsetedRectFormulas(startEnd, rectBandPosition, normalizedTimeUnit)); + } else if (f) { + const { + field: escapedField + } = f; + // since this is a expression, we want the unescaped field name + const field = escapedField.replaceAll('\\.', '.'); + const expr = offsetExpr({ + timeUnit: normalizedTimeUnit, + field + }); + const endAs = offsetAs(field); + transforms.push({ + type: 'formula', + expr, + as: endAs + }); + transforms.push(...offsetedRectFormulas([field, endAs], rectBandPosition, normalizedTimeUnit)); + } + } + return transforms; + } + } + const OFFSETTED_RECT_START_SUFFIX = 'offsetted_rect_start'; + const OFFSETTED_RECT_END_SUFFIX = 'offsetted_rect_end'; + function offsetExpr(_ref) { + let { + timeUnit, + field, + reverse + } = _ref; + const { + unit, + utc + } = timeUnit; + const smallestUnit = getSmallestTimeUnitPart(unit); + const { + part, + step + } = getDateTimePartAndStep(smallestUnit, timeUnit.step); + const offsetFn = utc ? 'utcOffset' : 'timeOffset'; + const expr = `${offsetFn}('${part}', datum['${field}'], ${reverse ? -step : step})`; + return expr; + } + function offsetedRectFormulas(_ref2, rectBandPosition, timeUnit) { + let [startField, endField] = _ref2; + if (rectBandPosition !== undefined && rectBandPosition !== 0.5) { + const startExpr = `datum['${startField}']`; + const endExpr = `datum['${endField}']`; + return [{ + type: 'formula', + expr: interpolateExpr([offsetExpr({ + timeUnit, + field: startField, + reverse: true + }), startExpr], rectBandPosition + 0.5), + as: `${startField}_${OFFSETTED_RECT_START_SUFFIX}` + }, { + type: 'formula', + expr: interpolateExpr([startExpr, endExpr], rectBandPosition + 0.5), + as: `${startField}_${OFFSETTED_RECT_END_SUFFIX}` + }]; + } + return []; + } + function interpolateExpr(_ref3, fraction) { + let [start, end] = _ref3; + return `${1 - fraction} * ${start} + ${fraction} * ${end}`; + } + + const TUPLE_FIELDS = '_tuple_fields'; + + /** + * Whether the selection tuples hold enumerated or ranged values for a field. + */ + + class SelectionProjectionComponent { + constructor() { + for (var _len = arguments.length, items = new Array(_len), _key = 0; _key < _len; _key++) { + items[_key] = arguments[_key]; + } + this.items = items; + this.hasChannel = {}; + this.hasField = {}; + this.hasSelectionId = false; + } + } + const project = { + defined: () => { + return true; // This transform handles its own defaults, so always run parse. + }, + parse: (model, selCmpt, selDef) => { + const name = selCmpt.name; + const proj = selCmpt.project ??= new SelectionProjectionComponent(); + const parsed = {}; + const timeUnits = {}; + const signals = new Set(); + const signalName = (p, range) => { + const suffix = range === 'visual' ? p.channel : p.field; + let sg = varName(`${name}_${suffix}`); + for (let counter = 1; signals.has(sg); counter++) { + sg = varName(`${name}_${suffix}_${counter}`); + } + signals.add(sg); + return { + [range]: sg + }; + }; + const type = selCmpt.type; + const cfg = model.config.selection[type]; + const init = selDef.value !== undefined ? vega.array(selDef.value) : null; + + // If no explicit projection (either fields or encodings) is specified, set some defaults. + // If an initial value is set, try to infer projections. + let { + fields, + encodings + } = vega.isObject(selDef.select) ? selDef.select : {}; + if (!fields && !encodings && init) { + for (const initVal of init) { + // initVal may be a scalar value to smoothen varParam -> pointSelection gradient. + if (!vega.isObject(initVal)) { + continue; + } + for (const key of keys(initVal)) { + if (isSingleDefUnitChannel(key)) { + (encodings || (encodings = [])).push(key); + } else { + if (type === 'interval') { + warn(INTERVAL_INITIALIZED_WITH_POS); + encodings = cfg.encodings; + } else { + (fields ??= []).push(key); + } + } + } + } + } + + // If no initial value is specified, use the default configuration. + // We break this out as a separate if block (instead of an else condition) + // to account for unprojected point selections that have scalar initial values + if (!fields && !encodings) { + encodings = cfg.encodings; + if ('fields' in cfg) { + fields = cfg.fields; + } + } + for (const channel of encodings ?? []) { + const fieldDef = model.fieldDef(channel); + if (fieldDef) { + let field = fieldDef.field; + if (fieldDef.aggregate) { + warn(cannotProjectAggregate(channel, fieldDef.aggregate)); + continue; + } else if (!field) { + warn(cannotProjectOnChannelWithoutField(channel)); + continue; + } + if (fieldDef.timeUnit && !isBinnedTimeUnit(fieldDef.timeUnit)) { + field = model.vgField(channel); + // Construct TimeUnitComponents which will be combined into a + // TimeUnitNode. This node may need to be inserted into the + // dataflow if the selection is used across views that do not + // have these time units defined. + const component = { + timeUnit: fieldDef.timeUnit, + as: field, + field: fieldDef.field + }; + timeUnits[hash(component)] = component; + } + + // Prevent duplicate projections on the same field. + // TODO: what if the same field is bound to multiple channels (e.g., SPLOM diag). + if (!parsed[field]) { + // Determine whether the tuple will store enumerated or ranged values. + // Interval selections store ranges for continuous scales, and enumerations otherwise. + // Single/multi selections store ranges for binned fields, and enumerations otherwise. + const tplType = type === 'interval' && isScaleChannel(channel) && hasContinuousDomain(model.getScaleComponent(channel).get('type')) ? 'R' : fieldDef.bin ? 'R-RE' : 'E'; + const p = { + field, + channel, + type: tplType, + index: proj.items.length + }; + p.signals = { + ...signalName(p, 'data'), + ...signalName(p, 'visual') + }; + proj.items.push(parsed[field] = p); + proj.hasField[field] = parsed[field]; + proj.hasSelectionId = proj.hasSelectionId || field === SELECTION_ID; + if (isGeoPositionChannel(channel)) { + p.geoChannel = channel; + p.channel = getPositionChannelFromLatLong(channel); + proj.hasChannel[p.channel] = parsed[field]; + } else { + proj.hasChannel[channel] = parsed[field]; + } + } + } else { + warn(cannotProjectOnChannelWithoutField(channel)); + } + } + for (const field of fields ?? []) { + if (proj.hasField[field]) continue; + const p = { + type: 'E', + field, + index: proj.items.length + }; + p.signals = { + ...signalName(p, 'data') + }; + proj.items.push(p); + proj.hasField[field] = p; + proj.hasSelectionId = proj.hasSelectionId || field === SELECTION_ID; + } + if (init) { + selCmpt.init = init.map(v => { + // Selections can be initialized either with a full object that maps projections to values + // or scalar values to smoothen the abstraction gradient from variable params to point selections. + return proj.items.map(p => vega.isObject(v) ? v[p.geoChannel || p.channel] !== undefined ? v[p.geoChannel || p.channel] : v[p.field] : v); + }); + } + if (!isEmpty(timeUnits)) { + proj.timeUnit = new TimeUnitNode(null, timeUnits); + } + }, + signals: (model, selCmpt, allSignals) => { + const name = selCmpt.name + TUPLE_FIELDS; + const hasSignal = allSignals.filter(s => s.name === name); + return hasSignal.length > 0 || selCmpt.project.hasSelectionId ? allSignals : allSignals.concat({ + name, + value: selCmpt.project.items.map(assembleProjection) + }); + } + }; + + const scaleBindings = { + defined: selCmpt => { + return selCmpt.type === 'interval' && selCmpt.resolve === 'global' && selCmpt.bind && selCmpt.bind === 'scales'; + }, + parse: (model, selCmpt) => { + const bound = selCmpt.scales = []; + for (const proj of selCmpt.project.items) { + const channel = proj.channel; + if (!isScaleChannel(channel)) { + continue; + } + const scale = model.getScaleComponent(channel); + const scaleType = scale ? scale.get('type') : undefined; + if (scaleType == 'sequential') { + warn(SEQUENTIAL_SCALE_DEPRECATED); + } + if (!scale || !hasContinuousDomain(scaleType)) { + warn(SCALE_BINDINGS_CONTINUOUS); + continue; + } + scale.set('selectionExtent', { + param: selCmpt.name, + field: proj.field + }, true); + bound.push(proj); + } + }, + topLevelSignals: (model, selCmpt, signals) => { + const bound = selCmpt.scales.filter(proj => signals.filter(s => s.name === proj.signals.data).length === 0); + + // Top-level signals are only needed for multiview displays and if this + // view's top-level signals haven't already been generated. + if (!model.parent || isTopLevelLayer(model) || bound.length === 0) { + return signals; + } + + // vlSelectionResolve does not account for the behavior of bound scales in + // multiview displays. Each unit view adds a tuple to the store, but the + // state of the selection is the unit selection most recently updated. This + // state is captured by the top-level signals that we insert and "push + // outer" to from within the units. We need to reassemble this state into + // the top-level named signal, except no single selCmpt has a global view. + const namedSg = signals.filter(s => s.name === selCmpt.name)[0]; + let update = namedSg.update; + if (update.indexOf(VL_SELECTION_RESOLVE) >= 0) { + namedSg.update = `{${bound.map(proj => `${vega.stringValue(replacePathInField(proj.field))}: ${proj.signals.data}`).join(', ')}}`; + } else { + for (const proj of bound) { + const mapping = `${vega.stringValue(replacePathInField(proj.field))}: ${proj.signals.data}`; + if (!update.includes(mapping)) { + update = `${update.substring(0, update.length - 1)}, ${mapping}}`; + } + } + namedSg.update = update; + } + return signals.concat(bound.map(proj => ({ + name: proj.signals.data + }))); + }, + signals: (model, selCmpt, signals) => { + // Nested signals need only push to top-level signals with multiview displays. + if (model.parent && !isTopLevelLayer(model)) { + for (const proj of selCmpt.scales) { + const signal = signals.find(s => s.name === proj.signals.data); + signal.push = 'outer'; + delete signal.value; + delete signal.update; + } + } + return signals; + } + }; + function domain(model, channel) { + const scale = vega.stringValue(model.scaleName(channel)); + return `domain(${scale})`; + } + function isTopLevelLayer(model) { + return model.parent && isLayerModel(model.parent) && (!model.parent.parent ?? isTopLevelLayer(model.parent.parent)); + } + + const BRUSH = '_brush'; + const SCALE_TRIGGER = '_scale_trigger'; + const GEO_INIT_TICK = 'geo_interval_init_tick'; // Workaround for https://github.com/vega/vega/issues/3481 + const INIT = '_init'; + const CENTER = '_center'; + + // Separate type because the "fields" property is only used internally and we don't want to leak it to the schema. + + const interval = { + defined: selCmpt => selCmpt.type === 'interval', + parse: (model, selCmpt, selDef) => { + if (model.hasProjection) { + const def = { + ...(vega.isObject(selDef.select) ? selDef.select : {}) + }; + def.fields = [SELECTION_ID]; + if (!def.encodings) { + // Remap default x/y projection + def.encodings = selDef.value ? keys(selDef.value) : [LONGITUDE, LATITUDE]; + } + selDef.select = { + type: 'interval', + ...def + }; + } + if (selCmpt.translate && !scaleBindings.defined(selCmpt)) { + const filterExpr = `!event.item || event.item.mark.name !== ${vega.stringValue(selCmpt.name + BRUSH)}`; + for (const evt of selCmpt.events) { + if (!evt.between) { + warn(`${evt} is not an ordered event stream for interval selections.`); + continue; + } + const filters = vega.array(evt.between[0].filter ??= []); + if (filters.indexOf(filterExpr) < 0) { + filters.push(filterExpr); + } + } + } + }, + signals: (model, selCmpt, signals) => { + const name = selCmpt.name; + const tupleSg = name + TUPLE; + const channels = vals(selCmpt.project.hasChannel).filter(p => p.channel === X || p.channel === Y); + const init = selCmpt.init ? selCmpt.init[0] : null; + signals.push(...channels.reduce((arr, proj) => arr.concat(channelSignals(model, selCmpt, proj, init && init[proj.index])), [])); + if (!model.hasProjection) { + // Proxy scale reactions to ensure that an infinite loop doesn't occur + // when an interval selection filter touches the scale. + if (!scaleBindings.defined(selCmpt)) { + const triggerSg = name + SCALE_TRIGGER; + const scaleTriggers = channels.map(proj => { + const channel = proj.channel; + const { + data: dname, + visual: vname + } = proj.signals; + const scaleName = vega.stringValue(model.scaleName(channel)); + const scaleType = model.getScaleComponent(channel).get('type'); + const toNum = hasContinuousDomain(scaleType) ? '+' : ''; + return `(!isArray(${dname}) || ` + `(${toNum}invert(${scaleName}, ${vname})[0] === ${toNum}${dname}[0] && ` + `${toNum}invert(${scaleName}, ${vname})[1] === ${toNum}${dname}[1]))`; + }); + if (scaleTriggers.length) { + signals.push({ + name: triggerSg, + value: {}, + on: [{ + events: channels.map(proj => ({ + scale: model.scaleName(proj.channel) + })), + update: scaleTriggers.join(' && ') + ` ? ${triggerSg} : {}` + }] + }); + } + } + + // Only add an interval to the store if it has valid data extents. Data extents + // are set to null if pixel extents are equal to account for intervals over + // ordinal/nominal domains which, when inverted, will still produce a valid datum. + const dataSignals = channels.map(proj => proj.signals.data); + const update = `unit: ${unitName(model)}, fields: ${name + TUPLE_FIELDS}, values`; + return signals.concat({ + name: tupleSg, + ...(init ? { + init: `{${update}: ${assembleInit(init)}}` + } : {}), + ...(dataSignals.length ? { + on: [{ + events: [{ + signal: dataSignals.join(' || ') + }], + // Prevents double invocation, see https://github.com/vega/vega/issues/1672. + update: `${dataSignals.join(' && ')} ? {${update}: [${dataSignals}]} : null` + }] + } : {}) + }); + } else { + const projection = vega.stringValue(model.projectionName()); + const centerSg = model.projectionName() + CENTER; + const { + x, + y + } = selCmpt.project.hasChannel; + const xvname = x && x.signals.visual; + const yvname = y && y.signals.visual; + const xinit = x ? init && init[x.index] : `${centerSg}[0]`; + const yinit = y ? init && init[y.index] : `${centerSg}[1]`; + const sizeSg = layout => model.getSizeSignalRef(layout).signal; + const bbox = `[` + `[${xvname ? xvname + '[0]' : '0'}, ${yvname ? yvname + '[0]' : '0'}],` + `[${xvname ? xvname + '[1]' : sizeSg('width')}, ` + `${yvname ? yvname + '[1]' : sizeSg('height')}]` + `]`; + if (init) { + signals.unshift({ + name: name + INIT, + init: `[scale(${projection}, [${x ? xinit[0] : xinit}, ${y ? yinit[0] : yinit}]), ` + `scale(${projection}, [${x ? xinit[1] : xinit}, ${y ? yinit[1] : yinit}])]` + }); + if (!x || !y) { + // If initializing a uni-dimensional brush, use the center of the view to determine the other coord + const hasCenterSg = signals.find(s => s.name === centerSg); + if (!hasCenterSg) { + signals.unshift({ + name: centerSg, + update: `invert(${projection}, [${sizeSg('width')}/2, ${sizeSg('height')}/2])` + }); + } + } + } + const intersect = `intersect(${bbox}, {markname: ${vega.stringValue(model.getName('marks'))}}, unit.mark)`; + const base = `{unit: ${unitName(model)}}`; + const update = `vlSelectionTuples(${intersect}, ${base})`; + const visualSignals = channels.map(proj => proj.signals.visual); + return signals.concat({ + name: tupleSg, + on: [{ + events: [...(visualSignals.length ? [{ + signal: visualSignals.join(' || ') + }] : []), ...(init ? [{ + signal: GEO_INIT_TICK + }] : [])], + update + }] + }); + } + }, + topLevelSignals: (model, selCmpt, signals) => { + if (isUnitModel(model) && model.hasProjection && selCmpt.init) { + // Workaround for https://github.com/vega/vega/issues/3481 + // The scenegraph isn't populated on the first pulse. So we use a timer signal + // to re-pulse the dataflow as soon as possible. We return an object to ensure + // this only occurs once. + const hasTick = signals.filter(s => s.name === GEO_INIT_TICK); + if (!hasTick.length) { + signals.unshift({ + name: GEO_INIT_TICK, + value: null, + on: [{ + events: 'timer{1}', + update: `${GEO_INIT_TICK} === null ? {} : ${GEO_INIT_TICK}` + }] + }); + } + } + return signals; + }, + marks: (model, selCmpt, marks) => { + const name = selCmpt.name; + const { + x, + y + } = selCmpt.project.hasChannel; + const xvname = x?.signals.visual; + const yvname = y?.signals.visual; + const store = `data(${vega.stringValue(selCmpt.name + STORE)})`; + + // Do not add a brush if we're binding to scales + // or we don't have a valid interval projection + if (scaleBindings.defined(selCmpt) || !x && !y) { + return marks; + } + const update = { + x: x !== undefined ? { + signal: `${xvname}[0]` + } : { + value: 0 + }, + y: y !== undefined ? { + signal: `${yvname}[0]` + } : { + value: 0 + }, + x2: x !== undefined ? { + signal: `${xvname}[1]` + } : { + field: { + group: 'width' + } + }, + y2: y !== undefined ? { + signal: `${yvname}[1]` + } : { + field: { + group: 'height' + } + } + }; + + // If the selection is resolved to global, only a single interval is in + // the store. Wrap brush mark's encodings with a production rule to test + // this based on the `unit` property. Hide the brush mark if it corresponds + // to a unit different from the one in the store. + if (selCmpt.resolve === 'global') { + for (const key of keys(update)) { + update[key] = [{ + test: `${store}.length && ${store}[0].unit === ${unitName(model)}`, + ...update[key] + }, { + value: 0 + }]; + } + } + + // Two brush marks ensure that fill colors and other aesthetic choices do + // not interefere with the core marks, but that the brushed region can still + // be interacted with (e.g., dragging it around). + const { + fill, + fillOpacity, + cursor, + ...stroke + } = selCmpt.mark; + const vgStroke = keys(stroke).reduce((def, k) => { + def[k] = [{ + test: [x !== undefined && `${xvname}[0] !== ${xvname}[1]`, y !== undefined && `${yvname}[0] !== ${yvname}[1]`].filter(t => t).join(' && '), + value: stroke[k] + }, { + value: null + }]; + return def; + }, {}); + + // Set cursor to move unless the brush cannot be translated + const vgCursor = cursor ?? (selCmpt.translate ? 'move' : null); + return [{ + name: `${name + BRUSH}_bg`, + type: 'rect', + clip: true, + encode: { + enter: { + fill: { + value: fill + }, + fillOpacity: { + value: fillOpacity + } + }, + update + } + }, ...marks, { + name: name + BRUSH, + type: 'rect', + clip: true, + encode: { + enter: { + ...(vgCursor ? { + cursor: { + value: vgCursor + } + } : {}), + fill: { + value: 'transparent' + } + }, + update: { + ...update, + ...vgStroke + } + } + }]; + } + }; + + /** + * Returns the visual and data signals for an interval selection. + */ + function channelSignals(model, selCmpt, proj, init) { + const scaledInterval = !model.hasProjection; + const channel = proj.channel; + const vname = proj.signals.visual; + const scaleName = vega.stringValue(scaledInterval ? model.scaleName(channel) : model.projectionName()); + const scaled = str => `scale(${scaleName}, ${str})`; + const size = model.getSizeSignalRef(channel === X ? 'width' : 'height').signal; + const coord = `${channel}(unit)`; + const von = selCmpt.events.reduce((def, evt) => { + return [...def, { + events: evt.between[0], + update: `[${coord}, ${coord}]` + }, + // Brush Start + { + events: evt, + update: `[${vname}[0], clamp(${coord}, 0, ${size})]` + } // Brush End + ]; + }, []); + if (scaledInterval) { + const dname = proj.signals.data; + const hasScales = scaleBindings.defined(selCmpt); + const scale = model.getScaleComponent(channel); + const scaleType = scale ? scale.get('type') : undefined; + const vinit = init ? { + init: assembleInit(init, true, scaled) + } : { + value: [] + }; + + // React to pan/zooms of continuous scales. Non-continuous scales + // (band, point) cannot be pan/zoomed and any other changes + // to their domains (e.g., filtering) should clear the brushes. + von.push({ + events: { + signal: selCmpt.name + SCALE_TRIGGER + }, + update: hasContinuousDomain(scaleType) ? `[${scaled(`${dname}[0]`)}, ${scaled(`${dname}[1]`)}]` : `[0, 0]` + }); + return hasScales ? [{ + name: dname, + on: [] + }] : [{ + name: vname, + ...vinit, + on: von + }, { + name: dname, + ...(init ? { + init: assembleInit(init) + } : {}), + // Cannot be `value` as `init` may require datetime exprs. + on: [{ + events: { + signal: vname + }, + update: `${vname}[0] === ${vname}[1] ? null : invert(${scaleName}, ${vname})` + }] + }]; + } else { + const initIdx = channel === X ? 0 : 1; + const initSg = selCmpt.name + INIT; + const vinit = init ? { + init: `[${initSg}[0][${initIdx}], ${initSg}[1][${initIdx}]]` + } : { + value: [] + }; + return [{ + name: vname, + ...vinit, + on: von + }]; + } + } + + const point$1 = { + defined: selCmpt => selCmpt.type === 'point', + signals: (model, selCmpt, signals) => { + const name = selCmpt.name; + const fieldsSg = name + TUPLE_FIELDS; + const project = selCmpt.project; + const datum = '(item().isVoronoi ? datum.datum : datum)'; + + // Only add a discrete selection to the store if a datum is present _and_ + // the interaction isn't occurring on a group mark. This guards against + // polluting interactive state with invalid values in faceted displays + // as the group marks are also data-driven. We force the update to account + // for constant null states but varying toggles (e.g., shift-click in + // whitespace followed by a click in whitespace; the store should only + // be cleared on the second click). + const brushes = vals(model.component.selection ?? {}).reduce((acc, cmpt) => { + return cmpt.type === 'interval' ? acc.concat(cmpt.name + BRUSH) : acc; + }, []).map(b => `indexof(item().mark.name, '${b}') < 0`).join(' && '); + const test = `datum && item().mark.marktype !== 'group' && indexof(item().mark.role, 'legend') < 0${brushes ? ` && ${brushes}` : ''}`; + let update = `unit: ${unitName(model)}, `; + if (selCmpt.project.hasSelectionId) { + update += `${SELECTION_ID}: ${datum}[${vega.stringValue(SELECTION_ID)}]`; + } else { + const values = project.items.map(p => { + const fieldDef = model.fieldDef(p.channel); + // Binned fields should capture extents, for a range test against the raw field. + return fieldDef?.bin ? `[${datum}[${vega.stringValue(model.vgField(p.channel, {}))}], ` + `${datum}[${vega.stringValue(model.vgField(p.channel, { + binSuffix: 'end' + }))}]]` : `${datum}[${vega.stringValue(p.field)}]`; + }).join(', '); + update += `fields: ${fieldsSg}, values: [${values}]`; + } + const events = selCmpt.events; + return signals.concat([{ + name: name + TUPLE, + on: events ? [{ + events, + update: `${test} ? {${update}} : null`, + force: true + }] : [] + }]); + } + }; + + /** + * Return a VgEncodeEntry that includes a Vega production rule for a scale channel's encoding or guide encoding, which includes: + * (1) the conditional rules (if provided as part of channelDef) + * (2) invalidValueRef for handling invalid values (if provided as a parameter of this method) + * (3) main reference for the encoded data. + */ + function wrapCondition(_ref) { + let { + model, + channelDef, + vgChannel, + invalidValueRef, + mainRefFn + } = _ref; + const condition = isConditionalDef(channelDef) && channelDef.condition; + let valueRefs = []; + if (condition) { + const conditions = vega.array(condition); + valueRefs = conditions.map(c => { + const conditionValueRef = mainRefFn(c); + if (isConditionalParameter(c)) { + const { + param, + empty + } = c; + const test = parseSelectionPredicate(model, { + param, + empty + }); + return { + test, + ...conditionValueRef + }; + } else { + const test = expression(model, c.test); // FIXME: remove casting once TS is no longer dumb about it + return { + test, + ...conditionValueRef + }; + } + }); + } + if (invalidValueRef !== undefined) { + valueRefs.push(invalidValueRef); + } + const mainValueRef = mainRefFn(channelDef); + if (mainValueRef !== undefined) { + valueRefs.push(mainValueRef); + } + if (valueRefs.length > 1 || valueRefs.length === 1 && Boolean(valueRefs[0].test) // We must use array form valueRefs if test exists, otherwise Vega won't execute the test. + ) { + return { + [vgChannel]: valueRefs + }; + } else if (valueRefs.length === 1) { + return { + [vgChannel]: valueRefs[0] + }; + } + return {}; + } + + function text$1(model) { + let channel = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'text'; + const channelDef = model.encoding[channel]; + return wrapCondition({ + model, + channelDef, + vgChannel: channel, + mainRefFn: cDef => textRef(cDef, model.config), + invalidValueRef: undefined // text encoding doesn't have continuous scales and thus can't have invalid values + }); + } + function textRef(channelDef, config) { + let expr = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'datum'; + // text + if (channelDef) { + if (isValueDef(channelDef)) { + return signalOrValueRef(channelDef.value); + } + if (isFieldOrDatumDef(channelDef)) { + const { + format, + formatType + } = getFormatMixins(channelDef); + return formatSignalRef({ + fieldOrDatumDef: channelDef, + format, + formatType, + expr, + config + }); + } + } + return undefined; + } + + function tooltip(model) { + let opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + const { + encoding, + markDef, + config, + stack + } = model; + const channelDef = encoding.tooltip; + if (vega.isArray(channelDef)) { + return { + tooltip: tooltipRefForEncoding({ + tooltip: channelDef + }, stack, config, opt) + }; + } else { + const datum = opt.reactiveGeom ? 'datum.datum' : 'datum'; + const mainRefFn = cDef => { + // use valueRef based on channelDef first + const tooltipRefFromChannelDef = textRef(cDef, config, datum); + if (tooltipRefFromChannelDef) { + return tooltipRefFromChannelDef; + } + if (cDef === null) { + // Allow using encoding.tooltip = null to disable tooltip + return undefined; + } + let markTooltip = getMarkPropOrConfig('tooltip', markDef, config); + if (markTooltip === true) { + markTooltip = { + content: 'encoding' + }; + } + if (vega.isString(markTooltip)) { + return { + value: markTooltip + }; + } else if (vega.isObject(markTooltip)) { + // `tooltip` is `{fields: 'encodings' | 'fields'}` + if (isSignalRef(markTooltip)) { + return markTooltip; + } else if (markTooltip.content === 'encoding') { + return tooltipRefForEncoding(encoding, stack, config, opt); + } else { + return { + signal: datum + }; + } + } + return undefined; + }; + return wrapCondition({ + model, + channelDef, + vgChannel: 'tooltip', + mainRefFn, + invalidValueRef: undefined // tooltip encoding doesn't have continuous scales and thus can't have invalid values + }); + } + } + function tooltipData(encoding, stack, config) { + let { + reactiveGeom + } = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + const formatConfig = { + ...config, + ...config.tooltipFormat + }; + const toSkip = {}; + const expr = reactiveGeom ? 'datum.datum' : 'datum'; + const tuples = []; + function add(fDef, channel) { + const mainChannel = getMainRangeChannel(channel); + const fieldDef = isTypedFieldDef(fDef) ? fDef : { + ...fDef, + type: encoding[mainChannel].type // for secondary field def, copy type from main channel + }; + const title = fieldDef.title || defaultTitle(fieldDef, formatConfig); + const key = vega.array(title).join(', ').replaceAll(/"/g, '\\"'); + let value; + if (isXorY(channel)) { + const channel2 = channel === 'x' ? 'x2' : 'y2'; + const fieldDef2 = getFieldDef(encoding[channel2]); + if (isBinned(fieldDef.bin) && fieldDef2) { + const startField = vgField(fieldDef, { + expr + }); + const endField = vgField(fieldDef2, { + expr + }); + const { + format, + formatType + } = getFormatMixins(fieldDef); + value = binFormatExpression(startField, endField, format, formatType, formatConfig); + toSkip[channel2] = true; + } + } + if ((isXorY(channel) || channel === THETA || channel === RADIUS) && stack && stack.fieldChannel === channel && stack.offset === 'normalize') { + const { + format, + formatType + } = getFormatMixins(fieldDef); + value = formatSignalRef({ + fieldOrDatumDef: fieldDef, + format, + formatType, + expr, + config: formatConfig, + normalizeStack: true + }).signal; + } + value ??= textRef(fieldDef, formatConfig, expr).signal; + tuples.push({ + channel, + key, + value + }); + } + forEach(encoding, (channelDef, channel) => { + if (isFieldDef(channelDef)) { + add(channelDef, channel); + } else if (hasConditionalFieldDef(channelDef)) { + add(channelDef.condition, channel); + } + }); + const out = {}; + for (const { + channel, + key, + value + } of tuples) { + if (!toSkip[channel] && !out[key]) { + out[key] = value; + } + } + return out; + } + function tooltipRefForEncoding(encoding, stack, config) { + let { + reactiveGeom + } = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + const data = tooltipData(encoding, stack, config, { + reactiveGeom + }); + const keyValues = entries$1(data).map(_ref => { + let [key, value] = _ref; + return `"${key}": ${value}`; + }); + return keyValues.length > 0 ? { + signal: `{${keyValues.join(', ')}}` + } : undefined; + } + + function aria(model) { + const { + markDef, + config + } = model; + const enableAria = getMarkPropOrConfig('aria', markDef, config); + + // We can ignore other aria properties if ariaHidden is true. + if (enableAria === false) { + // getMarkGroups sets aria to false already so we don't have to set it in the encode block + return {}; + } + return { + ...(enableAria ? { + aria: enableAria + } : {}), + ...ariaRoleDescription(model), + ...description(model) + }; + } + function ariaRoleDescription(model) { + const { + mark, + markDef, + config + } = model; + if (config.aria === false) { + return {}; + } + const ariaRoleDesc = getMarkPropOrConfig('ariaRoleDescription', markDef, config); + if (ariaRoleDesc != null) { + return { + ariaRoleDescription: { + value: ariaRoleDesc + } + }; + } + return mark in VG_MARK_INDEX ? {} : { + ariaRoleDescription: { + value: mark + } + }; + } + function description(model) { + const { + encoding, + markDef, + config, + stack + } = model; + const channelDef = encoding.description; + if (channelDef) { + return wrapCondition({ + model, + channelDef, + vgChannel: 'description', + mainRefFn: cDef => textRef(cDef, model.config), + invalidValueRef: undefined // aria encoding doesn't have continuous scales and thus can't have invalid values + }); + } + + // Use default from mark def or config if defined. + // Functions in encode usually just return undefined but since we are defining a default below, we need to check the default here. + const descriptionValue = getMarkPropOrConfig('description', markDef, config); + if (descriptionValue != null) { + return { + description: signalOrValueRef(descriptionValue) + }; + } + if (config.aria === false) { + return {}; + } + const data = tooltipData(encoding, stack, config); + if (isEmpty(data)) { + return undefined; + } + return { + description: { + signal: entries$1(data).map((_ref, index) => { + let [key, value] = _ref; + return `"${index > 0 ? '; ' : ''}${key}: " + (${value})`; + }).join(' + ') + } + }; + } + + /** + * Return encode for non-positional channels with scales. (Text doesn't have scale.) + */ + function nonPosition(channel, model) { + let opt = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + const { + markDef, + encoding, + config + } = model; + const { + vgChannel + } = opt; + let { + defaultRef, + defaultValue + } = opt; + if (defaultRef === undefined) { + // prettier-ignore + defaultValue ??= getMarkPropOrConfig(channel, markDef, config, { + vgChannel, + ignoreVgConfig: true + }); + if (defaultValue !== undefined) { + defaultRef = signalOrValueRef(defaultValue); + } + } + const channelDef = encoding[channel]; + const commonProps = { + markDef, + config, + scaleName: model.scaleName(channel), + scale: model.getScaleComponent(channel) + }; + const invalidValueRef = getConditionalValueRefForIncludingInvalidValue({ + ...commonProps, + scaleChannel: channel, + channelDef + }); + const mainRefFn = cDef => { + return midPoint({ + ...commonProps, + channel, + channelDef: cDef, + stack: null, + // No need to provide stack for non-position as it does not affect mid point + defaultRef + }); + }; + return wrapCondition({ + model, + channelDef, + vgChannel: vgChannel ?? channel, + invalidValueRef, + mainRefFn + }); + } + + function color(model) { + let opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { + filled: undefined + }; + const { + markDef, + encoding, + config + } = model; + const { + type: markType + } = markDef; + + // Allow filled to be overridden (for trail's "filled") + const filled = opt.filled ?? getMarkPropOrConfig('filled', markDef, config); + const transparentIfNeeded = contains(['bar', 'point', 'circle', 'square', 'geoshape'], markType) ? 'transparent' : undefined; + const defaultFill = getMarkPropOrConfig(filled === true ? 'color' : undefined, markDef, config, { + vgChannel: 'fill' + }) ?? + // need to add this manually as getMarkConfig normally drops config.mark[channel] if vgChannel is specified + config.mark[filled === true && 'color'] ?? + // If there is no fill, always fill symbols, bar, geoshape + // with transparent fills https://github.com/vega/vega-lite/issues/1316 + transparentIfNeeded; + const defaultStroke = getMarkPropOrConfig(filled === false ? 'color' : undefined, markDef, config, { + vgChannel: 'stroke' + }) ?? + // need to add this manually as getMarkConfig normally drops config.mark[channel] if vgChannel is specified + config.mark[filled === false && 'color']; + const colorVgChannel = filled ? 'fill' : 'stroke'; + const fillStrokeMarkDefAndConfig = { + ...(defaultFill ? { + fill: signalOrValueRef(defaultFill) + } : {}), + ...(defaultStroke ? { + stroke: signalOrValueRef(defaultStroke) + } : {}) + }; + if (markDef.color && (filled ? markDef.fill : markDef.stroke)) { + warn(droppingColor('property', { + fill: 'fill' in markDef, + stroke: 'stroke' in markDef + })); + } + return { + ...fillStrokeMarkDefAndConfig, + ...nonPosition('color', model, { + vgChannel: colorVgChannel, + defaultValue: filled ? defaultFill : defaultStroke + }), + ...nonPosition('fill', model, { + // if there is encoding.fill, include default fill just in case we have conditional-only fill encoding + defaultValue: encoding.fill ? defaultFill : undefined + }), + ...nonPosition('stroke', model, { + // if there is encoding.stroke, include default fill just in case we have conditional-only stroke encoding + defaultValue: encoding.stroke ? defaultStroke : undefined + }) + }; + } + + function zindex(model) { + const { + encoding, + mark + } = model; + const order = encoding.order; + if (!isPathMark(mark) && isValueDef(order)) { + return wrapCondition({ + model, + channelDef: order, + vgChannel: 'zindex', + mainRefFn: cd => signalOrValueRef(cd.value), + invalidValueRef: undefined // zindex encoding doesn't have continuous scales and thus can't have invalid values + }); + } + return {}; + } + + /** + * Utility files for producing Vega ValueRef for marks + */ + + function positionOffset(_ref) { + let { + channel: baseChannel, + markDef, + encoding = {}, + model, + bandPosition + } = _ref; + const channel = `${baseChannel}Offset`; // Need to cast as the type can't be inferred automatically + + const defaultValue = markDef[channel]; + const channelDef = encoding[channel]; + if ((channel === 'xOffset' || channel === 'yOffset') && channelDef) { + const ref = midPoint({ + channel: channel, + channelDef, + markDef, + config: model?.config, + scaleName: model.scaleName(channel), + scale: model.getScaleComponent(channel), + stack: null, + defaultRef: signalOrValueRef(defaultValue), + bandPosition + }); + return { + offsetType: 'encoding', + offset: ref + }; + } + const markDefOffsetValue = markDef[channel]; + if (markDefOffsetValue) { + return { + offsetType: 'visual', + offset: markDefOffsetValue + }; + } + return {}; + } + + /** + * Return encode for point (non-band) position channels. + */ + function pointPosition(channel, model, _ref) { + let { + defaultPos, + vgChannel + } = _ref; + const { + encoding, + markDef, + config, + stack + } = model; + const channelDef = encoding[channel]; + const channel2Def = encoding[getSecondaryRangeChannel(channel)]; + const scaleName = model.scaleName(channel); + const scale = model.getScaleComponent(channel); + const { + offset, + offsetType + } = positionOffset({ + channel, + markDef, + encoding, + model, + bandPosition: 0.5 + }); + + // Get default position or position from mark def + const defaultRef = pointPositionDefaultRef({ + model, + defaultPos, + channel, + scaleName, + scale + }); + const valueRef = !channelDef && isXorY(channel) && (encoding.latitude || encoding.longitude) ? + // use geopoint output if there are lat/long and there is no point position overriding lat/long. + { + field: model.getName(channel) + } : positionRef({ + channel, + channelDef, + channel2Def, + markDef, + config, + scaleName, + scale, + stack, + offset, + defaultRef, + bandPosition: offsetType === 'encoding' ? 0 : undefined + }); + return valueRef ? { + [vgChannel || channel]: valueRef + } : undefined; + } + + // TODO: we need to find a way to refactor these so that scaleName is a part of scale + // but that's complicated. For now, this is a huge step moving forward. + + /** + * @return Vega ValueRef for normal x- or y-position without projection + */ + function positionRef(params) { + const { + channel, + channelDef, + scaleName, + stack, + offset, + markDef + } = params; + + // This isn't a part of midPoint because we use midPoint for non-position too + if (isFieldOrDatumDef(channelDef) && stack && channel === stack.fieldChannel) { + if (isFieldDef(channelDef)) { + let bandPosition = channelDef.bandPosition; + if (bandPosition === undefined && markDef.type === 'text' && (channel === 'radius' || channel === 'theta')) { + // theta and radius of text mark should use bandPosition = 0.5 by default + // so that labels for arc marks are centered automatically + bandPosition = 0.5; + } + if (bandPosition !== undefined) { + return interpolatedSignalRef({ + scaleName, + fieldOrDatumDef: channelDef, + // positionRef always have type + startSuffix: 'start', + bandPosition, + offset + }); + } + } + // x or y use stack_end so that stacked line's point mark use stack_end too. + return valueRefForFieldOrDatumDef(channelDef, scaleName, { + suffix: 'end' + }, { + offset + }); + } + return midPointRefWithPositionInvalidTest(params); + } + function pointPositionDefaultRef(_ref2) { + let { + model, + defaultPos, + channel, + scaleName, + scale + } = _ref2; + const { + markDef, + config + } = model; + return () => { + const mainChannel = getMainRangeChannel(channel); + const vgChannel = getVgPositionChannel(channel); + const definedValueOrConfig = getMarkPropOrConfig(channel, markDef, config, { + vgChannel + }); + if (definedValueOrConfig !== undefined) { + return widthHeightValueOrSignalRef(channel, definedValueOrConfig); + } + switch (defaultPos) { + case 'zeroOrMin': + return zeroOrMinOrMaxPosition({ + scaleName, + scale, + mode: 'zeroOrMin', + mainChannel, + config + }); + case 'zeroOrMax': + return zeroOrMinOrMaxPosition({ + scaleName, + scale, + mode: { + zeroOrMax: { + widthSignal: model.width.signal, + heightSignal: model.height.signal + } + }, + mainChannel, + config + }); + case 'mid': + { + const sizeRef = model[getSizeChannel(channel)]; + return { + ...sizeRef, + mult: 0.5 + }; + } + } + // defaultPos === null + return undefined; + }; + } + function zeroOrMinOrMaxPosition(_ref3) { + let { + mainChannel, + config, + ...otherProps + } = _ref3; + const scaledValueRef = scaledZeroOrMinOrMax(otherProps); + const { + mode + } = otherProps; + if (scaledValueRef) { + return scaledValueRef; + } + switch (mainChannel) { + case 'radius': + { + if (mode === 'zeroOrMin') { + return { + value: 0 + }; // min value + } + const { + widthSignal, + heightSignal + } = mode.zeroOrMax; + // max of radius is min(width, height) / 2 + return { + signal: `min(${widthSignal},${heightSignal})/2` + }; + } + case 'theta': + return mode === 'zeroOrMin' ? { + value: 0 + } : { + signal: '2*PI' + }; + case 'x': + return mode === 'zeroOrMin' ? { + value: 0 + } : { + field: { + group: 'width' + } + }; + case 'y': + return mode === 'zeroOrMin' ? { + field: { + group: 'height' + } + } : { + value: 0 + }; + } + } + + const ALIGNED_X_CHANNEL = { + left: 'x', + center: 'xc', + right: 'x2' + }; + const BASELINED_Y_CHANNEL = { + top: 'y', + middle: 'yc', + bottom: 'y2' + }; + function vgAlignedPositionChannel(channel, markDef, config) { + let defaultAlign = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'middle'; + if (channel === 'radius' || channel === 'theta') { + return getVgPositionChannel(channel); + } + const alignChannel = channel === 'x' ? 'align' : 'baseline'; + const align = getMarkPropOrConfig(alignChannel, markDef, config); + let alignExcludingSignal; + if (isSignalRef(align)) { + warn(rangeMarkAlignmentCannotBeExpression(alignChannel)); + alignExcludingSignal = undefined; + } else { + alignExcludingSignal = align; + } + if (channel === 'x') { + return ALIGNED_X_CHANNEL[alignExcludingSignal || (defaultAlign === 'top' ? 'left' : 'center')]; + } else { + return BASELINED_Y_CHANNEL[alignExcludingSignal || defaultAlign]; + } + } + + /** + * Utility for area/rule position, which can be either point or range. + * (One of the axes should be point and the other should be range.) + */ + function pointOrRangePosition(channel, model, _ref) { + let { + defaultPos, + defaultPos2, + range + } = _ref; + if (range) { + return rangePosition(channel, model, { + defaultPos, + defaultPos2 + }); + } + return pointPosition(channel, model, { + defaultPos + }); + } + function rangePosition(channel, model, _ref2) { + let { + defaultPos, + defaultPos2 + } = _ref2; + const { + markDef, + config + } = model; + const channel2 = getSecondaryRangeChannel(channel); + const sizeChannel = getSizeChannel(channel); + const pos2Mixins = pointPosition2OrSize(model, defaultPos2, channel2); + const vgChannel = pos2Mixins[sizeChannel] ? + // If there is width/height, we need to position the marks based on the alignment. + vgAlignedPositionChannel(channel, markDef, config) : + // Otherwise, make sure to apply to the right Vg Channel (for arc mark) + getVgPositionChannel(channel); + return { + ...pointPosition(channel, model, { + defaultPos, + vgChannel + }), + ...pos2Mixins + }; + } + + /** + * Return encode for x2, y2. + * If channel is not specified, return one channel based on orientation. + */ + function pointPosition2OrSize(model, defaultPos, channel) { + const { + encoding, + mark, + markDef, + stack, + config + } = model; + const baseChannel = getMainRangeChannel(channel); + const sizeChannel = getSizeChannel(channel); + const vgChannel = getVgPositionChannel(channel); + const channelDef = encoding[baseChannel]; + const scaleName = model.scaleName(baseChannel); + const scale = model.getScaleComponent(baseChannel); + const { + offset + } = channel in encoding || channel in markDef ? positionOffset({ + channel, + markDef, + encoding, + model + }) : positionOffset({ + channel: baseChannel, + markDef, + encoding, + model + }); + if (!channelDef && (channel === 'x2' || channel === 'y2') && (encoding.latitude || encoding.longitude)) { + const vgSizeChannel = getSizeChannel(channel); + const size = model.markDef[vgSizeChannel]; + if (size != null) { + return { + [vgSizeChannel]: { + value: size + } + }; + } else { + return { + [vgChannel]: { + field: model.getName(channel) + } + }; + } + } + const valueRef = position2Ref({ + channel, + channelDef, + channel2Def: encoding[channel], + markDef, + config, + scaleName, + scale, + stack, + offset, + defaultRef: undefined + }); + if (valueRef !== undefined) { + return { + [vgChannel]: valueRef + }; + } + + // TODO: check width/height encoding here once we add them + + // no x2/y2 encoding, then try to read x2/y2 or width/height based on precedence: + // markDef > config.style > mark-specific config (config[mark]) > general mark config (config.mark) + + return position2orSize(channel, markDef) || position2orSize(channel, { + [channel]: getMarkStyleConfig(channel, markDef, config.style), + [sizeChannel]: getMarkStyleConfig(sizeChannel, markDef, config.style) + }) || position2orSize(channel, config[mark]) || position2orSize(channel, config.mark) || { + [vgChannel]: pointPositionDefaultRef({ + model, + defaultPos, + channel, + scaleName, + scale + })() + }; + } + function position2Ref(_ref3) { + let { + channel, + channelDef, + channel2Def, + markDef, + config, + scaleName, + scale, + stack, + offset, + defaultRef + } = _ref3; + if (isFieldOrDatumDef(channelDef) && stack && + // If fieldChannel is X and channel is X2 (or Y and Y2) + channel.charAt(0) === stack.fieldChannel.charAt(0)) { + return valueRefForFieldOrDatumDef(channelDef, scaleName, { + suffix: 'start' + }, { + offset + }); + } + return midPointRefWithPositionInvalidTest({ + channel, + channelDef: channel2Def, + scaleName, + scale, + stack, + markDef, + config, + offset, + defaultRef + }); + } + function position2orSize(channel, markDef) { + const sizeChannel = getSizeChannel(channel); + const vgChannel = getVgPositionChannel(channel); + if (markDef[vgChannel] !== undefined) { + return { + [vgChannel]: widthHeightValueOrSignalRef(channel, markDef[vgChannel]) + }; + } else if (markDef[channel] !== undefined) { + return { + [vgChannel]: widthHeightValueOrSignalRef(channel, markDef[channel]) + }; + } else if (markDef[sizeChannel]) { + const dimensionSize = markDef[sizeChannel]; + if (isRelativeBandSize(dimensionSize)) { + warn(relativeBandSizeNotSupported(sizeChannel)); + } else { + return { + [sizeChannel]: widthHeightValueOrSignalRef(channel, dimensionSize) + }; + } + } + return undefined; + } + + function rectPosition(model, channel) { + const { + config, + encoding, + markDef + } = model; + const mark = markDef.type; + const channel2 = getSecondaryRangeChannel(channel); + const sizeChannel = getSizeChannel(channel); + const channelDef = encoding[channel]; + const channelDef2 = encoding[channel2]; + const scale = model.getScaleComponent(channel); + const scaleType = scale ? scale.get('type') : undefined; + const orient = markDef.orient; + const hasSizeDef = encoding[sizeChannel] ?? encoding.size ?? getMarkPropOrConfig('size', markDef, config, { + vgChannel: sizeChannel + }); + const offsetScaleChannel = getOffsetChannel(channel); + const isBarBand = mark === 'bar' && (channel === 'x' ? orient === 'vertical' : orient === 'horizontal'); + + // x, x2, and width -- we must specify two of these in all conditions + if (isFieldDef(channelDef) && (isBinning(channelDef.bin) || isBinned(channelDef.bin) || channelDef.timeUnit && !channelDef2) && !(hasSizeDef && !isRelativeBandSize(hasSizeDef)) && !encoding[offsetScaleChannel] && !hasDiscreteDomain(scaleType)) { + return rectBinPosition({ + fieldDef: channelDef, + fieldDef2: channelDef2, + channel, + model + }); + } else if ((isFieldOrDatumDef(channelDef) && hasDiscreteDomain(scaleType) || isBarBand) && !channelDef2) { + return positionAndSize(channelDef, channel, model); + } else { + return rangePosition(channel, model, { + defaultPos: 'zeroOrMax', + defaultPos2: 'zeroOrMin' + }); + } + } + function defaultSizeRef(sizeChannel, scaleName, scale, config, bandSize, hasFieldDef, mark) { + if (isRelativeBandSize(bandSize)) { + if (scale) { + const scaleType = scale.get('type'); + if (scaleType === 'band') { + let bandWidth = `bandwidth('${scaleName}')`; + if (bandSize.band !== 1) { + bandWidth = `${bandSize.band} * ${bandWidth}`; + } + const minBandSize = getMarkConfig('minBandSize', { + type: mark + }, config); + return { + signal: minBandSize ? `max(${signalOrStringValue(minBandSize)}, ${bandWidth})` : bandWidth + }; + } else if (bandSize.band !== 1) { + warn(cannotUseRelativeBandSizeWithNonBandScale(scaleType)); + bandSize = undefined; + } + } else { + return { + mult: bandSize.band, + field: { + group: sizeChannel + } + }; + } + } else if (isSignalRef(bandSize)) { + return bandSize; + } else if (bandSize) { + return { + value: bandSize + }; + } + + // no valid band size + if (scale) { + const scaleRange = scale.get('range'); + if (isVgRangeStep(scaleRange) && vega.isNumber(scaleRange.step)) { + return { + value: scaleRange.step - 2 + }; + } + } + if (!hasFieldDef) { + const { + bandPaddingInner, + barBandPaddingInner, + rectBandPaddingInner + } = config.scale; + const padding = getFirstDefined(bandPaddingInner, mark === 'bar' ? barBandPaddingInner : rectBandPaddingInner); // this part is like paddingInner in scale.ts + if (isSignalRef(padding)) { + return { + signal: `(1 - (${padding.signal})) * ${sizeChannel}` + }; + } else if (vega.isNumber(padding)) { + return { + signal: `${1 - padding} * ${sizeChannel}` + }; + } + } + const defaultStep = getViewConfigDiscreteStep(config.view, sizeChannel); + return { + value: defaultStep - 2 + }; + } + + /** + * Output position encoding and its size encoding for continuous, point, and band scales. + */ + function positionAndSize(fieldDef, channel, model) { + const { + markDef, + encoding, + config, + stack + } = model; + const orient = markDef.orient; + const scaleName = model.scaleName(channel); + const scale = model.getScaleComponent(channel); + const vgSizeChannel = getSizeChannel(channel); + const channel2 = getSecondaryRangeChannel(channel); + const offsetScaleChannel = getOffsetChannel(channel); + const offsetScaleName = model.scaleName(offsetScaleChannel); + const offsetScale = model.getScaleComponent(getOffsetScaleChannel(channel)); + + // use "size" channel for bars, if there is orient and the channel matches the right orientation + const useVlSizeChannel = orient === 'horizontal' && channel === 'y' || orient === 'vertical' && channel === 'x'; + + // Use size encoding / mark property / config if it exists + let sizeMixins; + if (encoding.size || markDef.size) { + if (useVlSizeChannel) { + sizeMixins = nonPosition('size', model, { + vgChannel: vgSizeChannel, + defaultRef: signalOrValueRef(markDef.size) + }); + } else { + warn(cannotApplySizeToNonOrientedMark(markDef.type)); + } + } + const hasSizeFromMarkOrEncoding = !!sizeMixins; + + // Otherwise, apply default value + const bandSize = getBandSize({ + channel, + fieldDef, + markDef, + config, + scaleType: (scale || offsetScale)?.get('type'), + useVlSizeChannel + }); + sizeMixins = sizeMixins || { + [vgSizeChannel]: defaultSizeRef(vgSizeChannel, offsetScaleName || scaleName, offsetScale || scale, config, bandSize, !!fieldDef, markDef.type) + }; + + /* + Band scales with size value and all point scales, use xc/yc + band=0.5 + Otherwise (band scales that has size based on a band ref), use x/y with position band = (1 - size_band) / 2. + In this case, size_band is the band specified in the x/y-encoding. + By default band is 1, so `(1 - band) / 2` = 0. + If band is 0.6, the the x/y position in such case should be `(1 - band) / 2` = 0.2 + */ + + const defaultBandAlign = (scale || offsetScale)?.get('type') === 'band' && isRelativeBandSize(bandSize) && !hasSizeFromMarkOrEncoding ? 'top' : 'middle'; + const vgChannel = vgAlignedPositionChannel(channel, markDef, config, defaultBandAlign); + const center = vgChannel === 'xc' || vgChannel === 'yc'; + const { + offset, + offsetType + } = positionOffset({ + channel, + markDef, + encoding, + model, + bandPosition: center ? 0.5 : 0 + }); + const posRef = midPointRefWithPositionInvalidTest({ + channel, + channelDef: fieldDef, + markDef, + config, + scaleName, + scale, + stack, + offset, + defaultRef: pointPositionDefaultRef({ + model, + defaultPos: 'mid', + channel, + scaleName, + scale + }), + bandPosition: center ? offsetType === 'encoding' ? 0 : 0.5 : isSignalRef(bandSize) ? { + signal: `(1-${bandSize})/2` + } : isRelativeBandSize(bandSize) ? (1 - bandSize.band) / 2 : 0 + }); + if (vgSizeChannel) { + return { + [vgChannel]: posRef, + ...sizeMixins + }; + } else { + // otherwise, we must simulate size by setting position2 = position + size + // (for theta/radius since Vega doesn't have thetaWidth/radiusWidth) + const vgChannel2 = getVgPositionChannel(channel2); + const sizeRef = sizeMixins[vgSizeChannel]; + const sizeOffset = offset ? { + ...sizeRef, + offset + } : sizeRef; + return { + [vgChannel]: posRef, + // posRef might be an array that wraps position invalid test + [vgChannel2]: vega.isArray(posRef) ? [posRef[0], { + ...posRef[1], + offset: sizeOffset + }] : { + ...posRef, + offset: sizeOffset + } + }; + } + } + function getBinSpacing(channel, spacing, reverse, axisTranslate, offset, minBandSize, bandSizeExpr) { + if (isPolarPositionChannel(channel)) { + return 0; + } + const isEnd = channel === 'x' || channel === 'y2'; + const spacingOffset = isEnd ? -spacing / 2 : spacing / 2; + if (isSignalRef(reverse) || isSignalRef(offset) || isSignalRef(axisTranslate) || minBandSize) { + const reverseExpr = signalOrStringValue(reverse); + const offsetExpr = signalOrStringValue(offset); + const axisTranslateExpr = signalOrStringValue(axisTranslate); + const minBandSizeExpr = signalOrStringValue(minBandSize); + const sign = isEnd ? '' : '-'; + const spacingAndSizeOffset = minBandSize ? `(${bandSizeExpr} < ${minBandSizeExpr} ? ${sign}0.5 * (${minBandSizeExpr} - (${bandSizeExpr})) : ${spacingOffset})` : spacingOffset; + const t = axisTranslateExpr ? `${axisTranslateExpr} + ` : ''; + const r = reverseExpr ? `(${reverseExpr} ? -1 : 1) * ` : ''; + const o = offsetExpr ? `(${offsetExpr} + ${spacingAndSizeOffset})` : spacingAndSizeOffset; + return { + signal: t + r + o + }; + } else { + offset = offset || 0; + return axisTranslate + (reverse ? -offset - spacingOffset : +offset + spacingOffset); + } + } + function rectBinPosition(_ref) { + let { + fieldDef, + fieldDef2, + channel, + model + } = _ref; + const { + config, + markDef, + encoding + } = model; + const scale = model.getScaleComponent(channel); + const scaleName = model.scaleName(channel); + const scaleType = scale ? scale.get('type') : undefined; + const reverse = scale.get('reverse'); + const bandSize = getBandSize({ + channel, + fieldDef, + markDef, + config, + scaleType + }); + const axis = model.component.axes[channel]?.[0]; + const axisTranslate = axis?.get('translate') ?? 0.5; // vega default is 0.5 + + const spacing = isXorY(channel) ? getMarkPropOrConfig('binSpacing', markDef, config) ?? 0 : 0; + const channel2 = getSecondaryRangeChannel(channel); + const vgChannel = getVgPositionChannel(channel); + const vgChannel2 = getVgPositionChannel(channel2); + const minBandSize = getMarkConfig('minBandSize', markDef, config); + const { + offset + } = positionOffset({ + channel, + markDef, + encoding, + model, + bandPosition: 0 + }); + const { + offset: offset2 + } = positionOffset({ + channel: channel2, + markDef, + encoding, + model, + bandPosition: 0 + }); + const bandSizeExpr = binSizeExpr({ + fieldDef, + scaleName + }); + const binSpacingOffset = getBinSpacing(channel, spacing, reverse, axisTranslate, offset, minBandSize, bandSizeExpr); + const binSpacingOffset2 = getBinSpacing(channel2, spacing, reverse, axisTranslate, offset2 ?? offset, minBandSize, bandSizeExpr); + const bandPositionForBandSize = isSignalRef(bandSize) ? { + signal: `(1-${bandSize.signal})/2` + } : isRelativeBandSize(bandSize) ? (1 - bandSize.band) / 2 : 0.5; + const bandPosition = getBandPosition({ + fieldDef, + fieldDef2, + markDef, + config + }); + if (isBinning(fieldDef.bin) || fieldDef.timeUnit) { + const useRectOffsetField = fieldDef.timeUnit && bandPosition !== 0.5; + return { + [vgChannel2]: rectBinRef({ + fieldDef, + scaleName, + bandPosition: bandPositionForBandSize, + offset: binSpacingOffset2, + useRectOffsetField + }), + [vgChannel]: rectBinRef({ + fieldDef, + scaleName, + bandPosition: isSignalRef(bandPositionForBandSize) ? { + signal: `1-${bandPositionForBandSize.signal}` + } : 1 - bandPositionForBandSize, + offset: binSpacingOffset, + useRectOffsetField + }) + }; + } else if (isBinned(fieldDef.bin)) { + const startRef = valueRefForFieldOrDatumDef(fieldDef, scaleName, {}, { + offset: binSpacingOffset2 + }); + if (isFieldDef(fieldDef2)) { + return { + [vgChannel2]: startRef, + [vgChannel]: valueRefForFieldOrDatumDef(fieldDef2, scaleName, {}, { + offset: binSpacingOffset + }) + }; + } else if (isBinParams(fieldDef.bin) && fieldDef.bin.step) { + return { + [vgChannel2]: startRef, + [vgChannel]: { + signal: `scale("${scaleName}", ${vgField(fieldDef, { + expr: 'datum' + })} + ${fieldDef.bin.step})`, + offset: binSpacingOffset + } + }; + } + } + warn(channelRequiredForBinned(channel2)); + return undefined; + } + + /** + * Value Ref for binned fields + */ + function rectBinRef(_ref2) { + let { + fieldDef, + scaleName, + bandPosition, + offset, + useRectOffsetField + } = _ref2; + return interpolatedSignalRef({ + scaleName, + fieldOrDatumDef: fieldDef, + bandPosition, + offset, + ...(useRectOffsetField ? { + startSuffix: OFFSETTED_RECT_START_SUFFIX, + endSuffix: OFFSETTED_RECT_END_SUFFIX + } : {}) + }); + } + + const ALWAYS_IGNORE = new Set(['aria', 'width', 'height']); + function baseEncodeEntry(model, ignore) { + const { + fill = undefined, + stroke = undefined + } = ignore.color === 'include' ? color(model) : {}; + return { + ...markDefProperties(model.markDef, ignore), + ...colorRef('fill', fill), + ...colorRef('stroke', stroke), + ...nonPosition('opacity', model), + ...nonPosition('fillOpacity', model), + ...nonPosition('strokeOpacity', model), + ...nonPosition('strokeWidth', model), + ...nonPosition('strokeDash', model), + ...zindex(model), + ...tooltip(model), + ...text$1(model, 'href'), + ...aria(model) + }; + } + function colorRef(channel, valueRef) { + return valueRef ? { + [channel]: valueRef + } : {}; + } + function markDefProperties(mark, ignore) { + return VG_MARK_CONFIGS.reduce((m, prop) => { + if (!ALWAYS_IGNORE.has(prop) && mark[prop] !== undefined && ignore[prop] !== 'ignore') { + m[prop] = signalOrValueRef(mark[prop]); + } + return m; + }, {}); + } + + /** + * Create Vega's "defined" encoding to break paths in a path mark for invalid values. + */ + function defined(model) { + const { + config, + markDef + } = model; + + // For each channel (x/y), add fields to break path to a set first. + const fieldsToBreakPath = new Set(); + model.forEachFieldDef((fieldDef, channel) => { + let scaleType; + if (!isScaleChannel(channel) || !(scaleType = model.getScaleType(channel))) { + // Skip if the channel is not a scale channel or does not have a scale + return; + } + const isCountAggregate = isCountingAggregateOp(fieldDef.aggregate); + const invalidDataMode = getScaleInvalidDataMode({ + scaleChannel: channel, + markDef, + config, + scaleType, + isCountAggregate + }); + if (shouldBreakPath(invalidDataMode)) { + const field = model.vgField(channel, { + expr: 'datum', + binSuffix: model.stack?.impute ? 'mid' : undefined + }); + if (field) { + fieldsToBreakPath.add(field); + } + } + }); + + // If the set is not empty, return a defined signal. + if (fieldsToBreakPath.size > 0) { + const signal = [...fieldsToBreakPath].map(field => fieldValidPredicate(field, true)).join(' && '); + return { + defined: { + signal + } + }; + } + return undefined; + } + function valueIfDefined(prop, value) { + if (value !== undefined) { + return { + [prop]: signalOrValueRef(value) + }; + } + return undefined; + } + + const VORONOI = 'voronoi'; + const nearest = { + defined: selCmpt => { + return selCmpt.type === 'point' && selCmpt.nearest; + }, + parse: (model, selCmpt) => { + // Scope selection events to the voronoi mark to prevent capturing + // events that occur on the group mark (https://github.com/vega/vega/issues/2112). + if (selCmpt.events) { + for (const s of selCmpt.events) { + s.markname = model.getName(VORONOI); + } + } + }, + marks: (model, selCmpt, marks) => { + const { + x, + y + } = selCmpt.project.hasChannel; + const markType = model.mark; + if (isPathMark(markType)) { + warn(nearestNotSupportForContinuous(markType)); + return marks; + } + const cellDef = { + name: model.getName(VORONOI), + type: 'path', + interactive: true, + from: { + data: model.getName('marks') + }, + encode: { + update: { + fill: { + value: 'transparent' + }, + strokeWidth: { + value: 0.35 + }, + stroke: { + value: 'transparent' + }, + isVoronoi: { + value: true + }, + ...tooltip(model, { + reactiveGeom: true + }) + } + }, + transform: [{ + type: 'voronoi', + x: { + expr: x || !y ? 'datum.datum.x || 0' : '0' + }, + y: { + expr: y || !x ? 'datum.datum.y || 0' : '0' + }, + size: [model.getSizeSignalRef('width'), model.getSizeSignalRef('height')] + }] + }; + let index = 0; + let exists = false; + marks.forEach((mark, i) => { + const name = mark.name ?? ''; + if (name === model.component.mark[0].name) { + index = i; + } else if (name.indexOf(VORONOI) >= 0) { + exists = true; + } + }); + if (!exists) { + marks.splice(index + 1, 0, cellDef); + } + return marks; + } + }; + + const inputBindings = { + defined: selCmpt => { + return selCmpt.type === 'point' && selCmpt.resolve === 'global' && selCmpt.bind && selCmpt.bind !== 'scales' && !isLegendBinding(selCmpt.bind); + }, + parse: (model, selCmpt, selDef) => disableDirectManipulation(selCmpt, selDef), + topLevelSignals: (model, selCmpt, signals) => { + const name = selCmpt.name; + const proj = selCmpt.project; + const bind = selCmpt.bind; + const init = selCmpt.init && selCmpt.init[0]; // Can only exist on single selections (one initial value). + const datum = nearest.defined(selCmpt) ? '(item().isVoronoi ? datum.datum : datum)' : 'datum'; + proj.items.forEach((p, i) => { + const sgname = varName(`${name}_${p.field}`); + const hasSignal = signals.filter(s => s.name === sgname); + if (!hasSignal.length) { + signals.unshift({ + name: sgname, + ...(init ? { + init: assembleInit(init[i]) + } : { + value: null + }), + on: selCmpt.events ? [{ + events: selCmpt.events, + update: `datum && item().mark.marktype !== 'group' ? ${datum}[${vega.stringValue(p.field)}] : null` + }] : [], + bind: bind[p.field] ?? bind[p.channel] ?? bind + }); + } + }); + return signals; + }, + signals: (model, selCmpt, signals) => { + const name = selCmpt.name; + const proj = selCmpt.project; + const signal = signals.filter(s => s.name === name + TUPLE)[0]; + const fields = name + TUPLE_FIELDS; + const values = proj.items.map(p => varName(`${name}_${p.field}`)); + const valid = values.map(v => `${v} !== null`).join(' && '); + if (values.length) { + signal.update = `${valid} ? {fields: ${fields}, values: [${values.join(', ')}]} : null`; + } + delete signal.value; + delete signal.on; + return signals; + } + }; + + const TOGGLE = '_toggle'; + const toggle = { + defined: selCmpt => { + return selCmpt.type === 'point' && !!selCmpt.toggle; + }, + signals: (model, selCmpt, signals) => { + return signals.concat({ + name: selCmpt.name + TOGGLE, + value: false, + on: [{ + events: selCmpt.events, + update: selCmpt.toggle + }] + }); + }, + modifyExpr: (model, selCmpt) => { + const tpl = selCmpt.name + TUPLE; + const signal = selCmpt.name + TOGGLE; + return `${signal} ? null : ${tpl}, ` + (selCmpt.resolve === 'global' ? `${signal} ? null : true, ` : `${signal} ? null : {unit: ${unitName(model)}}, `) + `${signal} ? ${tpl} : null`; + } + }; + + const clear = { + defined: selCmpt => { + return selCmpt.clear !== undefined && selCmpt.clear !== false; + }, + parse: (model, selCmpt) => { + if (selCmpt.clear) { + selCmpt.clear = vega.isString(selCmpt.clear) ? vega.parseSelector(selCmpt.clear, 'view') : selCmpt.clear; + } + }, + topLevelSignals: (model, selCmpt, signals) => { + if (inputBindings.defined(selCmpt)) { + for (const proj of selCmpt.project.items) { + const idx = signals.findIndex(n => n.name === varName(`${selCmpt.name}_${proj.field}`)); + if (idx !== -1) { + signals[idx].on.push({ + events: selCmpt.clear, + update: 'null' + }); + } + } + } + return signals; + }, + signals: (model, selCmpt, signals) => { + function addClear(idx, update) { + if (idx !== -1 && signals[idx].on) { + signals[idx].on.push({ + events: selCmpt.clear, + update + }); + } + } + + // Be as minimalist as possible when adding clear triggers to minimize dataflow execution. + if (selCmpt.type === 'interval') { + for (const proj of selCmpt.project.items) { + const vIdx = signals.findIndex(n => n.name === proj.signals.visual); + addClear(vIdx, '[0, 0]'); + if (vIdx === -1) { + const dIdx = signals.findIndex(n => n.name === proj.signals.data); + addClear(dIdx, 'null'); + } + } + } else { + let tIdx = signals.findIndex(n => n.name === selCmpt.name + TUPLE); + addClear(tIdx, 'null'); + if (toggle.defined(selCmpt)) { + tIdx = signals.findIndex(n => n.name === selCmpt.name + TOGGLE); + addClear(tIdx, 'false'); + } + } + return signals; + } + }; + + const legendBindings = { + defined: selCmpt => { + const spec = selCmpt.resolve === 'global' && selCmpt.bind && isLegendBinding(selCmpt.bind); + const projLen = selCmpt.project.items.length === 1 && selCmpt.project.items[0].field !== SELECTION_ID; + if (spec && !projLen) { + warn(LEGEND_BINDINGS_MUST_HAVE_PROJECTION); + } + return spec && projLen; + }, + parse: (model, selCmpt, selDef) => { + // Allow legend items to be toggleable by default even though direct manipulation is disabled. + const selDef_ = duplicate(selDef); + selDef_.select = vega.isString(selDef_.select) ? { + type: selDef_.select, + toggle: selCmpt.toggle + } : { + ...selDef_.select, + toggle: selCmpt.toggle + }; + disableDirectManipulation(selCmpt, selDef_); + if (vega.isObject(selDef.select) && (selDef.select.on || selDef.select.clear)) { + const legendFilter = 'event.item && indexof(event.item.mark.role, "legend") < 0'; + for (const evt of selCmpt.events) { + evt.filter = vega.array(evt.filter ?? []); + if (!evt.filter.includes(legendFilter)) { + evt.filter.push(legendFilter); + } + } + } + const evt = isLegendStreamBinding(selCmpt.bind) ? selCmpt.bind.legend : 'click'; + const stream = vega.isString(evt) ? vega.parseSelector(evt, 'view') : vega.array(evt); + selCmpt.bind = { + legend: { + merge: stream + } + }; + }, + topLevelSignals: (model, selCmpt, signals) => { + const selName = selCmpt.name; + const stream = isLegendStreamBinding(selCmpt.bind) && selCmpt.bind.legend; + const markName = name => s => { + const ds = duplicate(s); + ds.markname = name; + return ds; + }; + for (const proj of selCmpt.project.items) { + if (!proj.hasLegend) continue; + const prefix = `${varName(proj.field)}_legend`; + const sgName = `${selName}_${prefix}`; + const hasSignal = signals.filter(s => s.name === sgName); + if (hasSignal.length === 0) { + const events = stream.merge.map(markName(`${prefix}_symbols`)).concat(stream.merge.map(markName(`${prefix}_labels`))).concat(stream.merge.map(markName(`${prefix}_entries`))); + signals.unshift({ + name: sgName, + ...(!selCmpt.init ? { + value: null + } : {}), + on: [ + // Legend entries do not store values, so we need to walk the scenegraph to the symbol datum. + { + events, + update: 'isDefined(datum.value) ? datum.value : item().items[0].items[0].datum.value', + force: true + }, { + events: stream.merge, + update: `!event.item || !datum ? null : ${sgName}`, + force: true + }] + }); + } + } + return signals; + }, + signals: (model, selCmpt, signals) => { + const name = selCmpt.name; + const proj = selCmpt.project; + const tuple = signals.find(s => s.name === name + TUPLE); + const fields = name + TUPLE_FIELDS; + const values = proj.items.filter(p => p.hasLegend).map(p => varName(`${name}_${varName(p.field)}_legend`)); + const valid = values.map(v => `${v} !== null`).join(' && '); + const update = `${valid} ? {fields: ${fields}, values: [${values.join(', ')}]} : null`; + if (selCmpt.events && values.length > 0) { + tuple.on.push({ + events: values.map(signal => ({ + signal + })), + update + }); + } else if (values.length > 0) { + tuple.update = update; + delete tuple.value; + delete tuple.on; + } + const toggle = signals.find(s => s.name === name + TOGGLE); + const events = isLegendStreamBinding(selCmpt.bind) && selCmpt.bind.legend; + if (toggle) { + if (!selCmpt.events) toggle.on[0].events = events;else toggle.on.push({ + ...toggle.on[0], + events + }); + } + return signals; + } + }; + function parseInteractiveLegend(model, channel, legendCmpt) { + const field = model.fieldDef(channel)?.field; + for (const selCmpt of vals(model.component.selection ?? {})) { + const proj = selCmpt.project.hasField[field] ?? selCmpt.project.hasChannel[channel]; + if (proj && legendBindings.defined(selCmpt)) { + const legendSelections = legendCmpt.get('selections') ?? []; + legendSelections.push(selCmpt.name); + legendCmpt.set('selections', legendSelections, false); + proj.hasLegend = true; + } + } + } + + const ANCHOR$1 = '_translate_anchor'; + const DELTA$1 = '_translate_delta'; + const translate = { + defined: selCmpt => { + return selCmpt.type === 'interval' && selCmpt.translate; + }, + signals: (model, selCmpt, signals) => { + const name = selCmpt.name; + const boundScales = scaleBindings.defined(selCmpt); + const anchor = name + ANCHOR$1; + const { + x, + y + } = selCmpt.project.hasChannel; + let events = vega.parseSelector(selCmpt.translate, 'scope'); + if (!boundScales) { + events = events.map(e => (e.between[0].markname = name + BRUSH, e)); + } + signals.push({ + name: anchor, + value: {}, + on: [{ + events: events.map(e => e.between[0]), + update: '{x: x(unit), y: y(unit)' + (x !== undefined ? `, extent_x: ${boundScales ? domain(model, X) : `slice(${x.signals.visual})`}` : '') + (y !== undefined ? `, extent_y: ${boundScales ? domain(model, Y) : `slice(${y.signals.visual})`}` : '') + '}' + }] + }, { + name: name + DELTA$1, + value: {}, + on: [{ + events, + update: `{x: ${anchor}.x - x(unit), y: ${anchor}.y - y(unit)}` + }] + }); + if (x !== undefined) { + onDelta$1(model, selCmpt, x, 'width', signals); + } + if (y !== undefined) { + onDelta$1(model, selCmpt, y, 'height', signals); + } + return signals; + } + }; + function onDelta$1(model, selCmpt, proj, size, signals) { + const name = selCmpt.name; + const anchor = name + ANCHOR$1; + const delta = name + DELTA$1; + const channel = proj.channel; + const boundScales = scaleBindings.defined(selCmpt); + const signal = signals.filter(s => s.name === proj.signals[boundScales ? 'data' : 'visual'])[0]; + const sizeSg = model.getSizeSignalRef(size).signal; + const scaleCmpt = model.getScaleComponent(channel); + const scaleType = scaleCmpt && scaleCmpt.get('type'); + const reversed = scaleCmpt && scaleCmpt.get('reverse'); // scale parsing sets this flag for fieldDef.sort + const sign = !boundScales ? '' : channel === X ? reversed ? '' : '-' : reversed ? '-' : ''; + const extent = `${anchor}.extent_${channel}`; + const offset = `${sign}${delta}.${channel} / ${boundScales ? `${sizeSg}` : `span(${extent})`}`; + const panFn = !boundScales || !scaleCmpt ? 'panLinear' : scaleType === 'log' ? 'panLog' : scaleType === 'symlog' ? 'panSymlog' : scaleType === 'pow' ? 'panPow' : 'panLinear'; + const arg = !boundScales ? '' : scaleType === 'pow' ? `, ${scaleCmpt.get('exponent') ?? 1}` : scaleType === 'symlog' ? `, ${scaleCmpt.get('constant') ?? 1}` : ''; + const update = `${panFn}(${extent}, ${offset}${arg})`; + signal.on.push({ + events: { + signal: delta + }, + update: boundScales ? update : `clampRange(${update}, 0, ${sizeSg})` + }); + } + + const ANCHOR = '_zoom_anchor'; + const DELTA = '_zoom_delta'; + const zoom = { + defined: selCmpt => { + return selCmpt.type === 'interval' && selCmpt.zoom; + }, + signals: (model, selCmpt, signals) => { + const name = selCmpt.name; + const boundScales = scaleBindings.defined(selCmpt); + const delta = name + DELTA; + const { + x, + y + } = selCmpt.project.hasChannel; + const sx = vega.stringValue(model.scaleName(X)); + const sy = vega.stringValue(model.scaleName(Y)); + let events = vega.parseSelector(selCmpt.zoom, 'scope'); + if (!boundScales) { + events = events.map(e => (e.markname = name + BRUSH, e)); + } + signals.push({ + name: name + ANCHOR, + on: [{ + events, + update: !boundScales ? `{x: x(unit), y: y(unit)}` : '{' + [sx ? `x: invert(${sx}, x(unit))` : '', sy ? `y: invert(${sy}, y(unit))` : ''].filter(expr => expr).join(', ') + '}' + }] + }, { + name: delta, + on: [{ + events, + force: true, + update: 'pow(1.001, event.deltaY * pow(16, event.deltaMode))' + }] + }); + if (x !== undefined) { + onDelta(model, selCmpt, x, 'width', signals); + } + if (y !== undefined) { + onDelta(model, selCmpt, y, 'height', signals); + } + return signals; + } + }; + function onDelta(model, selCmpt, proj, size, signals) { + const name = selCmpt.name; + const channel = proj.channel; + const boundScales = scaleBindings.defined(selCmpt); + const signal = signals.filter(s => s.name === proj.signals[boundScales ? 'data' : 'visual'])[0]; + const sizeSg = model.getSizeSignalRef(size).signal; + const scaleCmpt = model.getScaleComponent(channel); + const scaleType = scaleCmpt && scaleCmpt.get('type'); + const base = boundScales ? domain(model, channel) : signal.name; + const delta = name + DELTA; + const anchor = `${name}${ANCHOR}.${channel}`; + const zoomFn = !boundScales || !scaleCmpt ? 'zoomLinear' : scaleType === 'log' ? 'zoomLog' : scaleType === 'symlog' ? 'zoomSymlog' : scaleType === 'pow' ? 'zoomPow' : 'zoomLinear'; + const arg = !boundScales ? '' : scaleType === 'pow' ? `, ${scaleCmpt.get('exponent') ?? 1}` : scaleType === 'symlog' ? `, ${scaleCmpt.get('constant') ?? 1}` : ''; + const update = `${zoomFn}(${base}, ${anchor}, ${delta}${arg})`; + signal.on.push({ + events: { + signal: delta + }, + update: boundScales ? update : `clampRange(${update}, 0, ${sizeSg})` + }); + } + + const STORE = '_store'; + const TUPLE = '_tuple'; + const MODIFY = '_modify'; + const VL_SELECTION_RESOLVE = 'vlSelectionResolve'; + // Order matters for parsing and assembly. + const selectionCompilers = [point$1, interval, project, toggle, + // Bindings may disable direct manipulation. + inputBindings, scaleBindings, legendBindings, clear, translate, zoom, nearest]; + function getFacetModel(model) { + let parent = model.parent; + while (parent) { + if (isFacetModel(parent)) break; + parent = parent.parent; + } + return parent; + } + function unitName(model) { + let { + escape + } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { + escape: true + }; + let name = escape ? vega.stringValue(model.name) : model.name; + const facetModel = getFacetModel(model); + if (facetModel) { + const { + facet + } = facetModel; + for (const channel of FACET_CHANNELS) { + if (facet[channel]) { + name += ` + '__facet_${channel}_' + (facet[${vega.stringValue(facetModel.vgField(channel))}])`; + } + } + } + return name; + } + function requiresSelectionId(model) { + return vals(model.component.selection ?? {}).reduce((identifier, selCmpt) => { + return identifier || selCmpt.project.hasSelectionId; + }, false); + } + + // Binding a point selection to query widgets or legends disables default direct manipulation interaction. + // A user can choose to re-enable it by explicitly specifying triggering input events. + function disableDirectManipulation(selCmpt, selDef) { + if (vega.isString(selDef.select) || !selDef.select.on) delete selCmpt.events; + if (vega.isString(selDef.select) || !selDef.select.clear) delete selCmpt.clear; + if (vega.isString(selDef.select) || !selDef.select.toggle) delete selCmpt.toggle; + } + + function getName(node) { + const name = []; + if (node.type === 'Identifier') { + return [node.name]; + } + if (node.type === 'Literal') { + return [node.value]; + } + if (node.type === 'MemberExpression') { + name.push(...getName(node.object)); + name.push(...getName(node.property)); + } + return name; + } + function startsWithDatum(node) { + if (node.object.type === 'MemberExpression') { + return startsWithDatum(node.object); + } + return node.object.name === 'datum'; + } + function getDependentFields(expression) { + const ast = vega.parseExpression(expression); + const dependents = new Set(); + // visit is missing in types https://github.com/vega/vega/issues/3298 + ast.visit(node => { + if (node.type === 'MemberExpression' && startsWithDatum(node)) { + dependents.add(getName(node).slice(1).join('.')); + } + }); + return dependents; + } + + class FilterNode extends DataFlowNode { + clone() { + return new FilterNode(null, this.model, duplicate(this.filter)); + } + constructor(parent, model, filter) { + super(parent); + + // TODO: refactor this to not take a node and + // then add a static function makeFromOperand and make the constructor take only an expression + this.model = model; + this.filter = filter; + this.expr = expression(this.model, this.filter, this); + this._dependentFields = getDependentFields(this.expr); + } + dependentFields() { + return this._dependentFields; + } + producedFields() { + return new Set(); // filter does not produce any new fields + } + assemble() { + return { + type: 'filter', + expr: this.expr + }; + } + hash() { + return `Filter ${this.expr}`; + } + } + + function parseUnitSelection(model, selDefs) { + const selCmpts = {}; + const selectionConfig = model.config.selection; + if (!selDefs || !selDefs.length) return selCmpts; + for (const def of selDefs) { + const name = varName(def.name); + const selDef = def.select; + const type = vega.isString(selDef) ? selDef : selDef.type; + const defaults = vega.isObject(selDef) ? duplicate(selDef) : { + type + }; + + // Set default values from config if a property hasn't been specified, + // or if it is true. E.g., "translate": true should use the default + // event handlers for translate. However, true may be a valid value for + // a property (e.g., "nearest": true). + const cfg = selectionConfig[type]; + for (const key in cfg) { + // Project transform applies its defaults. + if (key === 'fields' || key === 'encodings') { + continue; + } + if (key === 'mark') { + defaults[key] = { + ...cfg[key], + ...defaults[key] + }; + } + if (defaults[key] === undefined || defaults[key] === true) { + defaults[key] = duplicate(cfg[key] ?? defaults[key]); + } + } + const selCmpt = selCmpts[name] = { + ...defaults, + name, + type, + init: def.value, + bind: def.bind, + events: vega.isString(defaults.on) ? vega.parseSelector(defaults.on, 'scope') : vega.array(duplicate(defaults.on)) + }; + const def_ = duplicate(def); // defensive copy to prevent compilers from causing side effects + for (const c of selectionCompilers) { + if (c.defined(selCmpt) && c.parse) { + c.parse(model, selCmpt, def_); + } + } + } + return selCmpts; + } + function parseSelectionPredicate(model, pred, dfnode) { + let datum = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'datum'; + const name = vega.isString(pred) ? pred : pred.param; + const vname = varName(name); + const store = vega.stringValue(vname + STORE); + let selCmpt; + try { + selCmpt = model.getSelectionComponent(vname, name); + } catch (e) { + // If a selection isn't found, treat as a variable parameter and coerce to boolean. + return `!!${vname}`; + } + if (selCmpt.project.timeUnit) { + const child = dfnode ?? model.component.data.raw; + const tunode = selCmpt.project.timeUnit.clone(); + if (child.parent) { + tunode.insertAsParentOf(child); + } else { + child.parent = tunode; + } + } + const fn = selCmpt.project.hasSelectionId ? 'vlSelectionIdTest(' : 'vlSelectionTest('; + const resolve = selCmpt.resolve === 'global' ? ')' : `, ${vega.stringValue(selCmpt.resolve)})`; + const test = `${fn}${store}, ${datum}${resolve}`; + const length = `length(data(${store}))`; + return pred.empty === false ? `${length} && ${test}` : `!${length} || ${test}`; + } + function parseSelectionExtent(model, name, extent) { + const vname = varName(name); + const encoding = extent['encoding']; + let field = extent['field']; + let selCmpt; + try { + selCmpt = model.getSelectionComponent(vname, name); + } catch (e) { + // If a selection isn't found, treat it as a variable parameter. + return vname; + } + if (!encoding && !field) { + field = selCmpt.project.items[0].field; + if (selCmpt.project.items.length > 1) { + warn('A "field" or "encoding" must be specified when using a selection as a scale domain. ' + `Using "field": ${vega.stringValue(field)}.`); + } + } else if (encoding && !field) { + const encodings = selCmpt.project.items.filter(p => p.channel === encoding); + if (!encodings.length || encodings.length > 1) { + field = selCmpt.project.items[0].field; + warn((!encodings.length ? 'No ' : 'Multiple ') + `matching ${vega.stringValue(encoding)} encoding found for selection ${vega.stringValue(extent.param)}. ` + `Using "field": ${vega.stringValue(field)}.`); + } else { + field = encodings[0].field; + } + } + return `${selCmpt.name}[${vega.stringValue(replacePathInField(field))}]`; + } + function materializeSelections(model, main) { + for (const [selection, selCmpt] of entries$1(model.component.selection ?? {})) { + const lookupName = model.getName(`lookup_${selection}`); + model.component.data.outputNodes[lookupName] = selCmpt.materialized = new OutputNode(new FilterNode(main, model, { + param: selection + }), lookupName, DataSourceType.Lookup, model.component.data.outputNodeRefCounts); + } + } + + /** + * Converts a predicate into an expression. + */ + // model is only used for selection filters. + function expression(model, filterOp, node) { + return logicalExpr(filterOp, predicate => { + if (vega.isString(predicate)) { + return predicate; + } else if (isSelectionPredicate(predicate)) { + return parseSelectionPredicate(model, predicate, node); + } else { + // Filter Object + return fieldFilterExpression(predicate); + } + }); + } + + function assembleTitle(title, config) { + if (!title) { + return undefined; + } + if (vega.isArray(title) && !isText(title)) { + return title.map(fieldDef => defaultTitle(fieldDef, config)).join(', '); + } + return title; + } + function setAxisEncode(axis, part, vgProp, vgRef) { + axis.encode ??= {}; + axis.encode[part] ??= {}; + axis.encode[part].update ??= {}; + // TODO: remove as any after https://github.com/prisma/nexus-prisma/issues/291 + axis.encode[part].update[vgProp] = vgRef; + } + function assembleAxis(axisCmpt, kind, config) { + let opt = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : { + header: false + }; + const { + disable, + orient, + scale, + labelExpr, + title, + zindex, + ...axis + } = axisCmpt.combine(); + if (disable) { + return undefined; + } + for (const prop in axis) { + const propType = AXIS_PROPERTY_TYPE[prop]; + const propValue = axis[prop]; + if (propType && propType !== kind && propType !== 'both') { + // Remove properties that are not valid for this kind of axis + delete axis[prop]; + } else if (isConditionalAxisValue(propValue)) { + // deal with conditional axis value + + const { + condition, + ...valueOrSignalRef + } = propValue; + const conditions = vega.array(condition); + const propIndex = CONDITIONAL_AXIS_PROP_INDEX[prop]; + if (propIndex) { + const { + vgProp, + part + } = propIndex; + // If there is a corresponding Vega property for the channel, + // use Vega's custom axis encoding and delete the original axis property to avoid conflicts + + const vgRef = [...conditions.map(c => { + const { + test, + ...valueOrSignalCRef + } = c; + return { + test: expression(null, test), + ...valueOrSignalCRef + }; + }), valueOrSignalRef]; + setAxisEncode(axis, part, vgProp, vgRef); + delete axis[prop]; + } else if (propIndex === null) { + // If propIndex is null, this means we support conditional axis property by converting the condition to signal instead. + const signalRef = { + signal: conditions.map(c => { + const { + test, + ...valueOrSignalCRef + } = c; + return `${expression(null, test)} ? ${exprFromValueRefOrSignalRef(valueOrSignalCRef)} : `; + }).join('') + exprFromValueRefOrSignalRef(valueOrSignalRef) + }; + axis[prop] = signalRef; + } + } else if (isSignalRef(propValue)) { + const propIndex = CONDITIONAL_AXIS_PROP_INDEX[prop]; + if (propIndex) { + const { + vgProp, + part + } = propIndex; + setAxisEncode(axis, part, vgProp, propValue); + delete axis[prop]; + } // else do nothing since the property already supports signal + } + + // Do not pass labelAlign/Baseline = null to Vega since it won't pass the schema + // Note that we need to use null so the default labelAlign is preserved. + if (contains(['labelAlign', 'labelBaseline'], prop) && axis[prop] === null) { + delete axis[prop]; + } + } + if (kind === 'grid') { + if (!axis.grid) { + return undefined; + } + + // Remove unnecessary encode block + if (axis.encode) { + // Only need to keep encode block for grid + const { + grid + } = axis.encode; + axis.encode = { + ...(grid ? { + grid + } : {}) + }; + if (isEmpty(axis.encode)) { + delete axis.encode; + } + } + return { + scale, + orient, + ...axis, + domain: false, + labels: false, + aria: false, + // always hide grid axis + + // Always set min/maxExtent to 0 to ensure that `config.axis*.minExtent` and `config.axis*.maxExtent` + // would not affect gridAxis + maxExtent: 0, + minExtent: 0, + ticks: false, + zindex: getFirstDefined(zindex, 0) // put grid behind marks by default + }; + } else { + // kind === 'main' + + if (!opt.header && axisCmpt.mainExtracted) { + // if mainExtracted has been extracted to a separate facet + return undefined; + } + if (labelExpr !== undefined) { + let expr = labelExpr; + if (axis.encode?.labels?.update && isSignalRef(axis.encode.labels.update.text)) { + expr = replaceAll(labelExpr, 'datum.label', axis.encode.labels.update.text.signal); + } + setAxisEncode(axis, 'labels', 'text', { + signal: expr + }); + } + if (axis.labelAlign === null) { + delete axis.labelAlign; + } + + // Remove unnecessary encode block + if (axis.encode) { + for (const part of AXIS_PARTS) { + if (!axisCmpt.hasAxisPart(part)) { + delete axis.encode[part]; + } + } + if (isEmpty(axis.encode)) { + delete axis.encode; + } + } + const titleString = assembleTitle(title, config); + return { + scale, + orient, + grid: false, + ...(titleString ? { + title: titleString + } : {}), + ...axis, + ...(config.aria === false ? { + aria: false + } : {}), + zindex: getFirstDefined(zindex, 0) // put axis line above marks by default + }; + } + } + + /** + * Add axis signals so grid line works correctly + * (Fix https://github.com/vega/vega-lite/issues/4226) + */ + function assembleAxisSignals(model) { + const { + axes + } = model.component; + const signals = []; + for (const channel of POSITION_SCALE_CHANNELS) { + if (axes[channel]) { + for (const axis of axes[channel]) { + if (!axis.get('disable') && !axis.get('gridScale')) { + // If there is x-axis but no y-scale for gridScale, need to set height/width so x-axis can draw the grid with the right height. Same for y-axis and width. + + const sizeType = channel === 'x' ? 'height' : 'width'; + const update = model.getSizeSignalRef(sizeType).signal; + if (sizeType !== update) { + signals.push({ + name: sizeType, + update + }); + } + } + } + } + } + return signals; + } + function assembleAxes(axisComponents, config) { + const { + x = [], + y = [] + } = axisComponents; + return [...x.map(a => assembleAxis(a, 'grid', config)), ...y.map(a => assembleAxis(a, 'grid', config)), ...x.map(a => assembleAxis(a, 'main', config)), ...y.map(a => assembleAxis(a, 'main', config))].filter(a => a); // filter undefined + } + + function getAxisConfigFromConfigTypes(configTypes, config, channel, orient) { + // TODO: add special casing to add conditional value based on orient signal + return Object.assign.apply(null, [{}, ...configTypes.map(configType => { + if (configType === 'axisOrient') { + const orient1 = channel === 'x' ? 'bottom' : 'left'; + const orientConfig1 = config[channel === 'x' ? 'axisBottom' : 'axisLeft'] || {}; + const orientConfig2 = config[channel === 'x' ? 'axisTop' : 'axisRight'] || {}; + const props = new Set([...keys(orientConfig1), ...keys(orientConfig2)]); + const conditionalOrientAxisConfig = {}; + for (const prop of props.values()) { + conditionalOrientAxisConfig[prop] = { + // orient is surely signal in this case + signal: `${orient['signal']} === "${orient1}" ? ${signalOrStringValue(orientConfig1[prop])} : ${signalOrStringValue(orientConfig2[prop])}` + }; + } + return conditionalOrientAxisConfig; + } + return config[configType]; + })]); + } + function getAxisConfigs(channel, scaleType, orient, config) { + const typeBasedConfigTypes = scaleType === 'band' ? ['axisDiscrete', 'axisBand'] : scaleType === 'point' ? ['axisDiscrete', 'axisPoint'] : isQuantitative(scaleType) ? ['axisQuantitative'] : scaleType === 'time' || scaleType === 'utc' ? ['axisTemporal'] : []; + const axisChannel = channel === 'x' ? 'axisX' : 'axisY'; + const axisOrient = isSignalRef(orient) ? 'axisOrient' : `axis${titleCase(orient)}`; // axisTop, axisBottom, ... + + const vlOnlyConfigTypes = [ + // technically Vega does have axisBand, but if we make another separation here, + // it will further introduce complexity in the code + ...typeBasedConfigTypes, ...typeBasedConfigTypes.map(c => axisChannel + c.substr(4))]; + const vgConfigTypes = ['axis', axisOrient, axisChannel]; + return { + vlOnlyAxisConfig: getAxisConfigFromConfigTypes(vlOnlyConfigTypes, config, channel, orient), + vgAxisConfig: getAxisConfigFromConfigTypes(vgConfigTypes, config, channel, orient), + axisConfigStyle: getAxisConfigStyle([...vgConfigTypes, ...vlOnlyConfigTypes], config) + }; + } + function getAxisConfigStyle(axisConfigTypes, config) { + const toMerge = [{}]; + for (const configType of axisConfigTypes) { + // TODO: add special casing to add conditional value based on orient signal + let style = config[configType]?.style; + if (style) { + style = vega.array(style); + for (const s of style) { + toMerge.push(config.style[s]); + } + } + } + return Object.assign.apply(null, toMerge); + } + function getAxisConfig(property, styleConfigIndex, style) { + let axisConfigs = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + const styleConfig = getStyleConfig(property, style, styleConfigIndex); + if (styleConfig !== undefined) { + return { + configFrom: 'style', + configValue: styleConfig + }; + } + for (const configFrom of ['vlOnlyAxisConfig', 'vgAxisConfig', 'axisConfigStyle']) { + if (axisConfigs[configFrom]?.[property] !== undefined) { + return { + configFrom, + configValue: axisConfigs[configFrom][property] + }; + } + } + return {}; + } + + const axisRules = { + scale: _ref => { + let { + model, + channel + } = _ref; + return model.scaleName(channel); + }, + format: _ref2 => { + let { + format + } = _ref2; + return format; + }, + // we already calculate this in parse + + formatType: _ref3 => { + let { + formatType + } = _ref3; + return formatType; + }, + // we already calculate this in parse + + grid: _ref4 => { + let { + fieldOrDatumDef, + axis, + scaleType + } = _ref4; + return axis.grid ?? defaultGrid(scaleType, fieldOrDatumDef); + }, + gridScale: _ref5 => { + let { + model, + channel + } = _ref5; + return gridScale(model, channel); + }, + labelAlign: _ref6 => { + let { + axis, + labelAngle, + orient, + channel + } = _ref6; + return axis.labelAlign || defaultLabelAlign(labelAngle, orient, channel); + }, + labelAngle: _ref7 => { + let { + labelAngle + } = _ref7; + return labelAngle; + }, + // we already calculate this in parse + + labelBaseline: _ref8 => { + let { + axis, + labelAngle, + orient, + channel + } = _ref8; + return axis.labelBaseline || defaultLabelBaseline(labelAngle, orient, channel); + }, + labelFlush: _ref9 => { + let { + axis, + fieldOrDatumDef, + channel + } = _ref9; + return axis.labelFlush ?? defaultLabelFlush(fieldOrDatumDef.type, channel); + }, + labelOverlap: _ref10 => { + let { + axis, + fieldOrDatumDef, + scaleType + } = _ref10; + return axis.labelOverlap ?? defaultLabelOverlap$1(fieldOrDatumDef.type, scaleType, isFieldDef(fieldOrDatumDef) && !!fieldOrDatumDef.timeUnit, isFieldDef(fieldOrDatumDef) ? fieldOrDatumDef.sort : undefined); + }, + // we already calculate orient in parse + orient: _ref11 => { + let { + orient + } = _ref11; + return orient; + }, + // Need to cast until Vega supports signal + + tickCount: _ref12 => { + let { + channel, + model, + axis, + fieldOrDatumDef, + scaleType + } = _ref12; + const sizeType = channel === 'x' ? 'width' : channel === 'y' ? 'height' : undefined; + const size = sizeType ? model.getSizeSignalRef(sizeType) : undefined; + return axis.tickCount ?? defaultTickCount({ + fieldOrDatumDef, + scaleType, + size, + values: axis.values + }); + }, + tickMinStep: defaultTickMinStep, + title: _ref13 => { + let { + axis, + model, + channel + } = _ref13; + if (axis.title !== undefined) { + return axis.title; + } + const fieldDefTitle = getFieldDefTitle(model, channel); + if (fieldDefTitle !== undefined) { + return fieldDefTitle; + } + const fieldDef = model.typedFieldDef(channel); + const channel2 = channel === 'x' ? 'x2' : 'y2'; + const fieldDef2 = model.fieldDef(channel2); + + // If title not specified, store base parts of fieldDef (and fieldDef2 if exists) + return mergeTitleFieldDefs(fieldDef ? [toFieldDefBase(fieldDef)] : [], isFieldDef(fieldDef2) ? [toFieldDefBase(fieldDef2)] : []); + }, + values: _ref14 => { + let { + axis, + fieldOrDatumDef + } = _ref14; + return values$1(axis, fieldOrDatumDef); + }, + zindex: _ref15 => { + let { + axis, + fieldOrDatumDef, + mark + } = _ref15; + return axis.zindex ?? defaultZindex(mark, fieldOrDatumDef); + } + }; + + // TODO: we need to refactor this method after we take care of config refactoring + /** + * Default rules for whether to show a grid should be shown for a channel. + * If `grid` is unspecified, the default value is `true` for ordinal scales that are not binned + */ + + function defaultGrid(scaleType, fieldDef) { + return !hasDiscreteDomain(scaleType) && isFieldDef(fieldDef) && !isBinning(fieldDef?.bin) && !isBinned(fieldDef?.bin); + } + function gridScale(model, channel) { + const gridChannel = channel === 'x' ? 'y' : 'x'; + if (model.getScaleComponent(gridChannel)) { + return model.scaleName(gridChannel); + } + return undefined; + } + function getLabelAngle(fieldOrDatumDef, axis, channel, styleConfig, axisConfigs) { + const labelAngle = axis?.labelAngle; + // try axis value + if (labelAngle !== undefined) { + return isSignalRef(labelAngle) ? labelAngle : normalizeAngle(labelAngle); + } else { + // try axis config value + const { + configValue: angle + } = getAxisConfig('labelAngle', styleConfig, axis?.style, axisConfigs); + if (angle !== undefined) { + return normalizeAngle(angle); + } else { + // get default value + if (channel === X && contains([NOMINAL, ORDINAL], fieldOrDatumDef.type) && !(isFieldDef(fieldOrDatumDef) && fieldOrDatumDef.timeUnit)) { + return 270; + } + // no default + return undefined; + } + } + } + function normalizeAngleExpr(angle) { + return `(((${angle.signal} % 360) + 360) % 360)`; + } + function defaultLabelBaseline(angle, orient, channel, alwaysIncludeMiddle) { + if (angle !== undefined) { + if (channel === 'x') { + if (isSignalRef(angle)) { + const a = normalizeAngleExpr(angle); + const orientIsTop = isSignalRef(orient) ? `(${orient.signal} === "top")` : orient === 'top'; + return { + signal: `(45 < ${a} && ${a} < 135) || (225 < ${a} && ${a} < 315) ? "middle" :` + `(${a} <= 45 || 315 <= ${a}) === ${orientIsTop} ? "bottom" : "top"` + }; + } + if (45 < angle && angle < 135 || 225 < angle && angle < 315) { + return 'middle'; + } + if (isSignalRef(orient)) { + const op = angle <= 45 || 315 <= angle ? '===' : '!=='; + return { + signal: `${orient.signal} ${op} "top" ? "bottom" : "top"` + }; + } + return (angle <= 45 || 315 <= angle) === (orient === 'top') ? 'bottom' : 'top'; + } else { + if (isSignalRef(angle)) { + const a = normalizeAngleExpr(angle); + const orientIsLeft = isSignalRef(orient) ? `(${orient.signal} === "left")` : orient === 'left'; + const middle = alwaysIncludeMiddle ? '"middle"' : 'null'; + return { + signal: `${a} <= 45 || 315 <= ${a} || (135 <= ${a} && ${a} <= 225) ? ${middle} : (45 <= ${a} && ${a} <= 135) === ${orientIsLeft} ? "top" : "bottom"` + }; + } + if (angle <= 45 || 315 <= angle || 135 <= angle && angle <= 225) { + return alwaysIncludeMiddle ? 'middle' : null; + } + if (isSignalRef(orient)) { + const op = 45 <= angle && angle <= 135 ? '===' : '!=='; + return { + signal: `${orient.signal} ${op} "left" ? "top" : "bottom"` + }; + } + return (45 <= angle && angle <= 135) === (orient === 'left') ? 'top' : 'bottom'; + } + } + return undefined; + } + function defaultLabelAlign(angle, orient, channel) { + if (angle === undefined) { + return undefined; + } + const isX = channel === 'x'; + const startAngle = isX ? 0 : 90; + const mainOrient = isX ? 'bottom' : 'left'; + if (isSignalRef(angle)) { + const a = normalizeAngleExpr(angle); + const orientIsMain = isSignalRef(orient) ? `(${orient.signal} === "${mainOrient}")` : orient === mainOrient; + return { + signal: `(${startAngle ? `(${a} + 90)` : a} % 180 === 0) ? ${isX ? null : '"center"'} :` + `(${startAngle} < ${a} && ${a} < ${180 + startAngle}) === ${orientIsMain} ? "left" : "right"` + }; + } + if ((angle + startAngle) % 180 === 0) { + // For bottom, use default label align so label flush still works + return isX ? null : 'center'; + } + if (isSignalRef(orient)) { + const op = startAngle < angle && angle < 180 + startAngle ? '===' : '!=='; + const orientIsMain = `${orient.signal} ${op} "${mainOrient}"`; + return { + signal: `${orientIsMain} ? "left" : "right"` + }; + } + if ((startAngle < angle && angle < 180 + startAngle) === (orient === mainOrient)) { + return 'left'; + } + return 'right'; + } + function defaultLabelFlush(type, channel) { + if (channel === 'x' && contains(['quantitative', 'temporal'], type)) { + return true; + } + return undefined; + } + function defaultLabelOverlap$1(type, scaleType, hasTimeUnit, sort) { + // do not prevent overlap for nominal data because there is no way to infer what the missing labels are + if (hasTimeUnit && !vega.isObject(sort) || type !== 'nominal' && type !== 'ordinal') { + if (scaleType === 'log' || scaleType === 'symlog') { + return 'greedy'; + } + return true; + } + return undefined; + } + function defaultOrient(channel) { + return channel === 'x' ? 'bottom' : 'left'; + } + function defaultTickCount(_ref16) { + let { + fieldOrDatumDef, + scaleType, + size, + values: vals + } = _ref16; + if (!vals && !hasDiscreteDomain(scaleType) && scaleType !== 'log') { + if (isFieldDef(fieldOrDatumDef)) { + if (isBinning(fieldOrDatumDef.bin)) { + // for binned data, we don't want more ticks than maxbins + return { + signal: `ceil(${size.signal}/10)` + }; + } + if (fieldOrDatumDef.timeUnit && contains(['month', 'hours', 'day', 'quarter'], normalizeTimeUnit(fieldOrDatumDef.timeUnit)?.unit)) { + return undefined; + } + } + return { + signal: `ceil(${size.signal}/40)` + }; + } + return undefined; + } + function defaultTickMinStep(_ref17) { + let { + format, + fieldOrDatumDef + } = _ref17; + if (format === 'd') { + return 1; + } + if (isFieldDef(fieldOrDatumDef)) { + const { + timeUnit + } = fieldOrDatumDef; + if (timeUnit) { + const signal = durationExpr(timeUnit); + if (signal) { + return { + signal + }; + } + } + } + return undefined; + } + function getFieldDefTitle(model, channel) { + const channel2 = channel === 'x' ? 'x2' : 'y2'; + const fieldDef = model.fieldDef(channel); + const fieldDef2 = model.fieldDef(channel2); + const title1 = fieldDef ? fieldDef.title : undefined; + const title2 = fieldDef2 ? fieldDef2.title : undefined; + if (title1 && title2) { + return mergeTitle(title1, title2); + } else if (title1) { + return title1; + } else if (title2) { + return title2; + } else if (title1 !== undefined) { + // falsy value to disable config + return title1; + } else if (title2 !== undefined) { + // falsy value to disable config + return title2; + } + return undefined; + } + function values$1(axis, fieldOrDatumDef) { + const vals = axis.values; + if (vega.isArray(vals)) { + return valueArray(fieldOrDatumDef, vals); + } else if (isSignalRef(vals)) { + return vals; + } + return undefined; + } + function defaultZindex(mark, fieldDef) { + if (mark === 'rect' && isDiscrete(fieldDef)) { + return 1; + } + return 0; + } + + class CalculateNode extends DataFlowNode { + clone() { + return new CalculateNode(null, duplicate(this.transform)); + } + constructor(parent, transform) { + super(parent); + this.transform = transform; + this._dependentFields = getDependentFields(this.transform.calculate); + } + static parseAllForSortIndex(parent, model) { + // get all the encoding with sort fields from model + model.forEachFieldDef((fieldDef, channel) => { + if (!isScaleFieldDef(fieldDef)) { + return; + } + if (isSortArray(fieldDef.sort)) { + const { + field, + timeUnit + } = fieldDef; + const sort = fieldDef.sort; + // generate `datum["a"] === val0 ? 0 : datum["a"] === val1 ? 1 : ... : n` via FieldEqualPredicate + const calculate = sort.map((sortValue, i) => { + return `${fieldFilterExpression({ + field, + timeUnit, + equal: sortValue + })} ? ${i} : `; + }).join('') + sort.length; + parent = new CalculateNode(parent, { + calculate, + as: sortArrayIndexField(fieldDef, channel, { + forAs: true + }) + }); + } + }); + return parent; + } + producedFields() { + return new Set([this.transform.as]); + } + dependentFields() { + return this._dependentFields; + } + assemble() { + return { + type: 'formula', + expr: this.transform.calculate, + as: this.transform.as + }; + } + hash() { + return `Calculate ${hash(this.transform)}`; + } + } + function sortArrayIndexField(fieldDef, channel, opt) { + return vgField(fieldDef, { + prefix: channel, + suffix: 'sort_index', + ...opt + }); + } + + /** + * Get header channel, which can be different from facet channel when orient is specified or when the facet channel is facet. + */ + function getHeaderChannel(channel, orient) { + if (contains(['top', 'bottom'], orient)) { + return 'column'; + } else if (contains(['left', 'right'], orient)) { + return 'row'; + } + return channel === 'row' ? 'row' : 'column'; + } + function getHeaderProperty(prop, header, config, channel) { + const headerSpecificConfig = channel === 'row' ? config.headerRow : channel === 'column' ? config.headerColumn : config.headerFacet; + return getFirstDefined((header || {})[prop], headerSpecificConfig[prop], config.header[prop]); + } + function getHeaderProperties(properties, header, config, channel) { + const props = {}; + for (const prop of properties) { + const value = getHeaderProperty(prop, header || {}, config, channel); + if (value !== undefined) { + props[prop] = value; + } + } + return props; + } + + /** + * Utility for generating row / column headers + */ + + const HEADER_CHANNELS = ['row', 'column']; + const HEADER_TYPES = ['header', 'footer']; + + /** + * A component that represents all header, footers and title of a Vega group with layout directive. + */ + + /** + * A component that represents one group of row/column-header/footer. + */ + + /** + * Utility for generating row / column headers + */ + + + // TODO: rename to assembleHeaderTitleGroup + function assembleTitleGroup(model, channel) { + const title = model.component.layoutHeaders[channel].title; + const config = model.config ? model.config : undefined; + const facetFieldDef = model.component.layoutHeaders[channel].facetFieldDef ? model.component.layoutHeaders[channel].facetFieldDef : undefined; + const { + titleAnchor, + titleAngle: ta, + titleOrient + } = getHeaderProperties(['titleAnchor', 'titleAngle', 'titleOrient'], facetFieldDef.header, config, channel); + const headerChannel = getHeaderChannel(channel, titleOrient); + const titleAngle = normalizeAngle(ta); + return { + name: `${channel}-title`, + type: 'group', + role: `${headerChannel}-title`, + title: { + text: title, + ...(channel === 'row' ? { + orient: 'left' + } : {}), + style: 'guide-title', + ...defaultHeaderGuideBaseline(titleAngle, headerChannel), + ...defaultHeaderGuideAlign(headerChannel, titleAngle, titleAnchor), + ...assembleHeaderProperties(config, facetFieldDef, channel, HEADER_TITLE_PROPERTIES, HEADER_TITLE_PROPERTIES_MAP) + } + }; + } + function defaultHeaderGuideAlign(headerChannel, angle) { + let anchor = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'middle'; + switch (anchor) { + case 'start': + return { + align: 'left' + }; + case 'end': + return { + align: 'right' + }; + } + const align = defaultLabelAlign(angle, headerChannel === 'row' ? 'left' : 'top', headerChannel === 'row' ? 'y' : 'x'); + return align ? { + align + } : {}; + } + function defaultHeaderGuideBaseline(angle, channel) { + const baseline = defaultLabelBaseline(angle, channel === 'row' ? 'left' : 'top', channel === 'row' ? 'y' : 'x', true); + return baseline ? { + baseline + } : {}; + } + function assembleHeaderGroups(model, channel) { + const layoutHeader = model.component.layoutHeaders[channel]; + const groups = []; + for (const headerType of HEADER_TYPES) { + if (layoutHeader[headerType]) { + for (const headerComponent of layoutHeader[headerType]) { + const group = assembleHeaderGroup(model, channel, headerType, layoutHeader, headerComponent); + if (group != null) { + groups.push(group); + } + } + } + } + return groups; + } + function getSort$1(facetFieldDef, channel) { + const { + sort + } = facetFieldDef; + if (isSortField(sort)) { + return { + field: vgField(sort, { + expr: 'datum' + }), + order: sort.order ?? 'ascending' + }; + } else if (vega.isArray(sort)) { + return { + field: sortArrayIndexField(facetFieldDef, channel, { + expr: 'datum' + }), + order: 'ascending' + }; + } else { + return { + field: vgField(facetFieldDef, { + expr: 'datum' + }), + order: sort ?? 'ascending' + }; + } + } + function assembleLabelTitle(facetFieldDef, channel, config) { + const { + format, + formatType, + labelAngle, + labelAnchor, + labelOrient, + labelExpr + } = getHeaderProperties(['format', 'formatType', 'labelAngle', 'labelAnchor', 'labelOrient', 'labelExpr'], facetFieldDef.header, config, channel); + const titleTextExpr = formatSignalRef({ + fieldOrDatumDef: facetFieldDef, + format, + formatType, + expr: 'parent', + config + }).signal; + const headerChannel = getHeaderChannel(channel, labelOrient); + return { + text: { + signal: labelExpr ? replaceAll(replaceAll(labelExpr, 'datum.label', titleTextExpr), 'datum.value', vgField(facetFieldDef, { + expr: 'parent' + })) : titleTextExpr + }, + ...(channel === 'row' ? { + orient: 'left' + } : {}), + style: 'guide-label', + frame: 'group', + ...defaultHeaderGuideBaseline(labelAngle, headerChannel), + ...defaultHeaderGuideAlign(headerChannel, labelAngle, labelAnchor), + ...assembleHeaderProperties(config, facetFieldDef, channel, HEADER_LABEL_PROPERTIES, HEADER_LABEL_PROPERTIES_MAP) + }; + } + function assembleHeaderGroup(model, channel, headerType, layoutHeader, headerComponent) { + if (headerComponent) { + let title = null; + const { + facetFieldDef + } = layoutHeader; + const config = model.config ? model.config : undefined; + if (facetFieldDef && headerComponent.labels) { + const { + labelOrient + } = getHeaderProperties(['labelOrient'], facetFieldDef.header, config, channel); + + // Include label title in the header if orient aligns with the channel + if (channel === 'row' && !contains(['top', 'bottom'], labelOrient) || channel === 'column' && !contains(['left', 'right'], labelOrient)) { + title = assembleLabelTitle(facetFieldDef, channel, config); + } + } + const isFacetWithoutRowCol = isFacetModel(model) && !isFacetMapping(model.facet); + const axes = headerComponent.axes; + const hasAxes = axes?.length > 0; + if (title || hasAxes) { + const sizeChannel = channel === 'row' ? 'height' : 'width'; + return { + name: model.getName(`${channel}_${headerType}`), + type: 'group', + role: `${channel}-${headerType}`, + ...(layoutHeader.facetFieldDef ? { + from: { + data: model.getName(`${channel}_domain`) + }, + sort: getSort$1(facetFieldDef, channel) + } : {}), + ...(hasAxes && isFacetWithoutRowCol ? { + from: { + data: model.getName(`facet_domain_${channel}`) + } + } : {}), + ...(title ? { + title + } : {}), + ...(headerComponent.sizeSignal ? { + encode: { + update: { + [sizeChannel]: headerComponent.sizeSignal + } + } + } : {}), + ...(hasAxes ? { + axes + } : {}) + }; + } + } + return null; + } + const LAYOUT_TITLE_BAND = { + column: { + start: 0, + end: 1 + }, + row: { + start: 1, + end: 0 + } + }; + function getLayoutTitleBand(titleAnchor, headerChannel) { + return LAYOUT_TITLE_BAND[headerChannel][titleAnchor]; + } + function assembleLayoutTitleBand(headerComponentIndex, config) { + const titleBand = {}; + for (const channel of FACET_CHANNELS) { + const headerComponent = headerComponentIndex[channel]; + if (headerComponent?.facetFieldDef) { + const { + titleAnchor, + titleOrient + } = getHeaderProperties(['titleAnchor', 'titleOrient'], headerComponent.facetFieldDef.header, config, channel); + const headerChannel = getHeaderChannel(channel, titleOrient); + const band = getLayoutTitleBand(titleAnchor, headerChannel); + if (band !== undefined) { + titleBand[headerChannel] = band; + } + } + } + return isEmpty(titleBand) ? undefined : titleBand; + } + function assembleHeaderProperties(config, facetFieldDef, channel, properties, propertiesMap) { + const props = {}; + for (const prop of properties) { + if (!propertiesMap[prop]) { + continue; + } + const value = getHeaderProperty(prop, facetFieldDef?.header, config, channel); + if (value !== undefined) { + props[propertiesMap[prop]] = value; + } + } + return props; + } + + function assembleLayoutSignals(model) { + return [...sizeSignals(model, 'width'), ...sizeSignals(model, 'height'), ...sizeSignals(model, 'childWidth'), ...sizeSignals(model, 'childHeight')]; + } + function sizeSignals(model, sizeType) { + const channel = sizeType === 'width' ? 'x' : 'y'; + const size = model.component.layoutSize.get(sizeType); + if (!size || size === 'merged') { + return []; + } + + // Read size signal name from name map, just in case it is the top-level size signal that got renamed. + const name = model.getSizeSignalRef(sizeType).signal; + if (size === 'step') { + const scaleComponent = model.getScaleComponent(channel); + if (scaleComponent) { + const type = scaleComponent.get('type'); + const range = scaleComponent.get('range'); + if (hasDiscreteDomain(type) && isVgRangeStep(range)) { + const scaleName = model.scaleName(channel); + if (isFacetModel(model.parent)) { + // If parent is facet and this is an independent scale, return only signal signal + // as the width/height will be calculated using the cardinality from + // facet's aggregate rather than reading from scale domain + const parentResolve = model.parent.component.resolve; + if (parentResolve.scale[channel] === 'independent') { + return [stepSignal(scaleName, range)]; + } + } + return [stepSignal(scaleName, range), { + name, + update: sizeExpr(scaleName, scaleComponent, `domain('${scaleName}').length`) + }]; + } + } + /* istanbul ignore next: Condition should not happen -- only for warning in development. */ + throw new Error('layout size is step although width/height is not step.'); + } else if (size == 'container') { + const isWidth = name.endsWith('width'); + const expr = isWidth ? 'containerSize()[0]' : 'containerSize()[1]'; + const defaultValue = getViewConfigContinuousSize(model.config.view, isWidth ? 'width' : 'height'); + const safeExpr = `isFinite(${expr}) ? ${expr} : ${defaultValue}`; + return [{ + name, + init: safeExpr, + on: [{ + update: safeExpr, + events: 'window:resize' + }] + }]; + } else { + return [{ + name, + value: size + }]; + } + } + function stepSignal(scaleName, range) { + const name = `${scaleName}_step`; + if (isSignalRef(range.step)) { + return { + name, + update: range.step.signal + }; + } else { + return { + name, + value: range.step + }; + } + } + function sizeExpr(scaleName, scaleComponent, cardinality) { + const type = scaleComponent.get('type'); + const padding = scaleComponent.get('padding'); + const paddingOuter = getFirstDefined(scaleComponent.get('paddingOuter'), padding); + let paddingInner = scaleComponent.get('paddingInner'); + paddingInner = type === 'band' ? + // only band has real paddingInner + paddingInner !== undefined ? paddingInner : padding : + // For point, as calculated in https://github.com/vega/vega-scale/blob/master/src/band.js#L128, + // it's equivalent to have paddingInner = 1 since there is only n-1 steps between n points. + 1; + return `bandspace(${cardinality}, ${signalOrStringValue(paddingInner)}, ${signalOrStringValue(paddingOuter)}) * ${scaleName}_step`; + } + + function getSizeTypeFromLayoutSizeType(layoutSizeType) { + return layoutSizeType === 'childWidth' ? 'width' : layoutSizeType === 'childHeight' ? 'height' : layoutSizeType; + } + + function guideEncodeEntry(encoding, model) { + return keys(encoding).reduce((encode, channel) => { + return { + ...encode, + ...wrapCondition({ + model, + channelDef: encoding[channel], + vgChannel: channel, + mainRefFn: def => signalOrValueRef(def.value), + invalidValueRef: undefined // guide encoding won't show invalid values for the scale + }) + }; + }, {}); + } + + function defaultScaleResolve(channel, model) { + if (isFacetModel(model)) { + return channel === 'theta' ? 'independent' : 'shared'; + } else if (isLayerModel(model)) { + return 'shared'; + } else if (isConcatModel(model)) { + return isXorY(channel) || channel === 'theta' || channel === 'radius' ? 'independent' : 'shared'; + } + /* istanbul ignore next: should never reach here. */ + throw new Error('invalid model type for resolve'); + } + function parseGuideResolve(resolve, channel) { + const channelScaleResolve = resolve.scale[channel]; + const guide = isXorY(channel) ? 'axis' : 'legend'; + if (channelScaleResolve === 'independent') { + if (resolve[guide][channel] === 'shared') { + warn(independentScaleMeansIndependentGuide(channel)); + } + return 'independent'; + } + return resolve[guide][channel] || 'shared'; + } + + const LEGEND_COMPONENT_PROPERTY_INDEX = { + ...COMMON_LEGEND_PROPERTY_INDEX, + disable: 1, + labelExpr: 1, + selections: 1, + // channel scales + opacity: 1, + shape: 1, + stroke: 1, + fill: 1, + size: 1, + strokeWidth: 1, + strokeDash: 1, + // encode + encode: 1 + }; + const LEGEND_COMPONENT_PROPERTIES = keys(LEGEND_COMPONENT_PROPERTY_INDEX); + class LegendComponent extends Split {} + + const legendEncodeRules = { + symbols, + gradient, + labels: labels$1, + entries + }; + function symbols(symbolsSpec, _ref) { + let { + fieldOrDatumDef, + model, + channel, + legendCmpt, + legendType + } = _ref; + if (legendType !== 'symbol') { + return undefined; + } + const { + markDef, + encoding, + config, + mark + } = model; + const filled = markDef.filled && mark !== 'trail'; + let out = { + ...applyMarkConfig({}, model, FILL_STROKE_CONFIG), + ...color(model, { + filled + }) + }; // FIXME: remove this when VgEncodeEntry is compatible with SymbolEncodeEntry + + const symbolOpacity = legendCmpt.get('symbolOpacity') ?? config.legend.symbolOpacity; + const symbolFillColor = legendCmpt.get('symbolFillColor') ?? config.legend.symbolFillColor; + const symbolStrokeColor = legendCmpt.get('symbolStrokeColor') ?? config.legend.symbolStrokeColor; + const opacity = symbolOpacity === undefined ? getMaxValue(encoding.opacity) ?? markDef.opacity : undefined; + if (out.fill) { + // for fill legend, we don't want any fill in symbol + if (channel === 'fill' || filled && channel === COLOR) { + delete out.fill; + } else { + if (out.fill['field']) { + // For others, set fill to some opaque value (or nothing if a color is already set) + if (symbolFillColor) { + delete out.fill; + } else { + out.fill = signalOrValueRef(config.legend.symbolBaseFillColor ?? 'black'); + out.fillOpacity = signalOrValueRef(opacity ?? 1); + } + } else if (vega.isArray(out.fill)) { + const fill = getFirstConditionValue(encoding.fill ?? encoding.color) ?? markDef.fill ?? (filled && markDef.color); + if (fill) { + out.fill = signalOrValueRef(fill); + } + } + } + } + if (out.stroke) { + if (channel === 'stroke' || !filled && channel === COLOR) { + delete out.stroke; + } else { + if (out.stroke['field'] || symbolStrokeColor) { + // For others, remove stroke field + delete out.stroke; + } else if (vega.isArray(out.stroke)) { + const stroke = getFirstDefined(getFirstConditionValue(encoding.stroke || encoding.color), markDef.stroke, filled ? markDef.color : undefined); + if (stroke) { + out.stroke = { + value: stroke + }; + } + } + } + } + if (channel !== OPACITY) { + const condition = isFieldDef(fieldOrDatumDef) && selectedCondition(model, legendCmpt, fieldOrDatumDef); + if (condition) { + out.opacity = [{ + test: condition, + ...signalOrValueRef(opacity ?? 1) + }, signalOrValueRef(config.legend.unselectedOpacity)]; + } else if (opacity) { + out.opacity = signalOrValueRef(opacity); + } + } + out = { + ...out, + ...symbolsSpec + }; + return isEmpty(out) ? undefined : out; + } + function gradient(gradientSpec, _ref2) { + let { + model, + legendType, + legendCmpt + } = _ref2; + if (legendType !== 'gradient') { + return undefined; + } + const { + config, + markDef, + encoding + } = model; + let out = {}; + const gradientOpacity = legendCmpt.get('gradientOpacity') ?? config.legend.gradientOpacity; + const opacity = gradientOpacity === undefined ? getMaxValue(encoding.opacity) || markDef.opacity : undefined; + if (opacity) { + // only apply opacity if it is neither zero or undefined + out.opacity = signalOrValueRef(opacity); + } + out = { + ...out, + ...gradientSpec + }; + return isEmpty(out) ? undefined : out; + } + function labels$1(specifiedlabelsSpec, _ref3) { + let { + fieldOrDatumDef, + model, + channel, + legendCmpt + } = _ref3; + const legend = model.legend(channel) || {}; + const config = model.config; + const condition = isFieldDef(fieldOrDatumDef) ? selectedCondition(model, legendCmpt, fieldOrDatumDef) : undefined; + const opacity = condition ? [{ + test: condition, + value: 1 + }, { + value: config.legend.unselectedOpacity + }] : undefined; + const { + format, + formatType + } = legend; + let text = undefined; + if (isCustomFormatType(formatType)) { + text = formatCustomType({ + fieldOrDatumDef, + field: 'datum.value', + format, + formatType, + config + }); + } else if (format === undefined && formatType === undefined && config.customFormatTypes) { + if (fieldOrDatumDef.type === 'quantitative' && config.numberFormatType) { + text = formatCustomType({ + fieldOrDatumDef, + field: 'datum.value', + format: config.numberFormat, + formatType: config.numberFormatType, + config + }); + } else if (fieldOrDatumDef.type === 'temporal' && config.timeFormatType && isFieldDef(fieldOrDatumDef) && fieldOrDatumDef.timeUnit === undefined) { + text = formatCustomType({ + fieldOrDatumDef, + field: 'datum.value', + format: config.timeFormat, + formatType: config.timeFormatType, + config + }); + } + } + const labelsSpec = { + ...(opacity ? { + opacity + } : {}), + ...(text ? { + text + } : {}), + ...specifiedlabelsSpec + }; + return isEmpty(labelsSpec) ? undefined : labelsSpec; + } + function entries(entriesSpec, _ref4) { + let { + legendCmpt + } = _ref4; + const selections = legendCmpt.get('selections'); + return selections?.length ? { + ...entriesSpec, + fill: { + value: 'transparent' + } + } : entriesSpec; + } + function getMaxValue(channelDef) { + return getConditionValue(channelDef, (v, conditionalDef) => Math.max(v, conditionalDef.value)); + } + function getFirstConditionValue(channelDef) { + return getConditionValue(channelDef, (v, conditionalDef) => { + return getFirstDefined(v, conditionalDef.value); + }); + } + function getConditionValue(channelDef, reducer) { + if (hasConditionalValueDef(channelDef)) { + return vega.array(channelDef.condition).reduce(reducer, channelDef.value); + } else if (isValueDef(channelDef)) { + return channelDef.value; + } + return undefined; + } + function selectedCondition(model, legendCmpt, fieldDef) { + const selections = legendCmpt.get('selections'); + if (!selections?.length) return undefined; + const field = vega.stringValue(fieldDef.field); + return selections.map(name => { + const store = vega.stringValue(varName(name) + STORE); + return `(!length(data(${store})) || (${name}[${field}] && indexof(${name}[${field}], datum.value) >= 0))`; + }).join(' || '); + } + + const legendRules = { + direction: _ref => { + let { + direction + } = _ref; + return direction; + }, + format: _ref2 => { + let { + fieldOrDatumDef, + legend, + config + } = _ref2; + const { + format, + formatType + } = legend; + return guideFormat(fieldOrDatumDef, fieldOrDatumDef.type, format, formatType, config, false); + }, + formatType: _ref3 => { + let { + legend, + fieldOrDatumDef, + scaleType + } = _ref3; + const { + formatType + } = legend; + return guideFormatType(formatType, fieldOrDatumDef, scaleType); + }, + gradientLength: params => { + const { + legend, + legendConfig + } = params; + return legend.gradientLength ?? legendConfig.gradientLength ?? defaultGradientLength(params); + }, + labelOverlap: _ref4 => { + let { + legend, + legendConfig, + scaleType + } = _ref4; + return legend.labelOverlap ?? legendConfig.labelOverlap ?? defaultLabelOverlap(scaleType); + }, + symbolType: _ref5 => { + let { + legend, + markDef, + channel, + encoding + } = _ref5; + return legend.symbolType ?? defaultSymbolType(markDef.type, channel, encoding.shape, markDef.shape); + }, + title: _ref6 => { + let { + fieldOrDatumDef, + config + } = _ref6; + return title(fieldOrDatumDef, config, { + allowDisabling: true + }); + }, + type: _ref7 => { + let { + legendType, + scaleType, + channel + } = _ref7; + if (isColorChannel(channel) && isContinuousToContinuous(scaleType)) { + if (legendType === 'gradient') { + return undefined; + } + } else if (legendType === 'symbol') { + return undefined; + } + return legendType; + }, + // depended by other property, let's define upfront + + values: _ref8 => { + let { + fieldOrDatumDef, + legend + } = _ref8; + return values(legend, fieldOrDatumDef); + } + }; + function values(legend, fieldOrDatumDef) { + const vals = legend.values; + if (vega.isArray(vals)) { + return valueArray(fieldOrDatumDef, vals); + } else if (isSignalRef(vals)) { + return vals; + } + return undefined; + } + function defaultSymbolType(mark, channel, shapeChannelDef, markShape) { + if (channel !== 'shape') { + // use the value from the shape encoding or the mark config if they exist + const shape = getFirstConditionValue(shapeChannelDef) ?? markShape; + if (shape) { + return shape; + } + } + switch (mark) { + case 'bar': + case 'rect': + case 'image': + case 'square': + return 'square'; + case 'line': + case 'trail': + case 'rule': + return 'stroke'; + case 'arc': + case 'point': + case 'circle': + case 'tick': + case 'geoshape': + case 'area': + case 'text': + return 'circle'; + } + } + function getLegendType(params) { + const { + legend + } = params; + return getFirstDefined(legend.type, defaultType$1(params)); + } + function defaultType$1(_ref9) { + let { + channel, + timeUnit, + scaleType + } = _ref9; + // Following the logic in https://github.com/vega/vega-parser/blob/master/src/parsers/legend.js + + if (isColorChannel(channel)) { + if (contains(['quarter', 'month', 'day'], timeUnit)) { + return 'symbol'; + } + if (isContinuousToContinuous(scaleType)) { + return 'gradient'; + } + } + return 'symbol'; + } + function getDirection(_ref10) { + let { + legendConfig, + legendType, + orient, + legend + } = _ref10; + return legend.direction ?? legendConfig[legendType ? 'gradientDirection' : 'symbolDirection'] ?? defaultDirection(orient, legendType); + } + function defaultDirection(orient, legendType) { + switch (orient) { + case 'top': + case 'bottom': + return 'horizontal'; + case 'left': + case 'right': + case 'none': + case undefined: + // undefined = "right" in Vega + return undefined; + // vertical is Vega's default + default: + // top-left / ... + // For inner legend, uses compact layout like Tableau + return legendType === 'gradient' ? 'horizontal' : undefined; + } + } + function defaultGradientLength(_ref11) { + let { + legendConfig, + model, + direction, + orient, + scaleType + } = _ref11; + const { + gradientHorizontalMaxLength, + gradientHorizontalMinLength, + gradientVerticalMaxLength, + gradientVerticalMinLength + } = legendConfig; + if (isContinuousToContinuous(scaleType)) { + if (direction === 'horizontal') { + if (orient === 'top' || orient === 'bottom') { + return gradientLengthSignal(model, 'width', gradientHorizontalMinLength, gradientHorizontalMaxLength); + } else { + return gradientHorizontalMinLength; + } + } else { + // vertical / undefined (Vega uses vertical by default) + return gradientLengthSignal(model, 'height', gradientVerticalMinLength, gradientVerticalMaxLength); + } + } + return undefined; + } + function gradientLengthSignal(model, sizeType, min, max) { + const sizeSignal = model.getSizeSignalRef(sizeType).signal; + return { + signal: `clamp(${sizeSignal}, ${min}, ${max})` + }; + } + function defaultLabelOverlap(scaleType) { + if (contains(['quantile', 'threshold', 'log', 'symlog'], scaleType)) { + return 'greedy'; + } + return undefined; + } + + function parseLegend(model) { + const legendComponent = isUnitModel(model) ? parseUnitLegend(model) : parseNonUnitLegend(model); + model.component.legends = legendComponent; + return legendComponent; + } + function parseUnitLegend(model) { + const { + encoding + } = model; + const legendComponent = {}; + for (const channel of [COLOR, ...LEGEND_SCALE_CHANNELS]) { + const def = getFieldOrDatumDef(encoding[channel]); + if (!def || !model.getScaleComponent(channel)) { + continue; + } + if (channel === SHAPE && isFieldDef(def) && def.type === GEOJSON) { + continue; + } + legendComponent[channel] = parseLegendForChannel(model, channel); + } + return legendComponent; + } + function getLegendDefWithScale(model, channel) { + const scale = model.scaleName(channel); + if (model.mark === 'trail') { + if (channel === 'color') { + // trail is a filled mark, but its default symbolType ("stroke") should use "stroke" + return { + stroke: scale + }; + } else if (channel === 'size') { + return { + strokeWidth: scale + }; + } + } + if (channel === 'color') { + return model.markDef.filled ? { + fill: scale + } : { + stroke: scale + }; + } + return { + [channel]: scale + }; + } + + // eslint-disable-next-line @typescript-eslint/ban-types + function isExplicit$1(value, property, legend, fieldDef) { + switch (property) { + case 'disable': + return legend !== undefined; + // if axis is specified or null/false, then its enable/disable state is explicit + case 'values': + // specified legend.values is already respected, but may get transformed. + return !!legend?.values; + case 'title': + // title can be explicit if fieldDef.title is set + if (property === 'title' && value === fieldDef?.title) { + return true; + } + } + // Otherwise, things are explicit if the returned value matches the specified property + return value === (legend || {})[property]; + } + function parseLegendForChannel(model, channel) { + let legend = model.legend(channel); + const { + markDef, + encoding, + config + } = model; + const legendConfig = config.legend; + const legendCmpt = new LegendComponent({}, getLegendDefWithScale(model, channel)); + parseInteractiveLegend(model, channel, legendCmpt); + const disable = legend !== undefined ? !legend : legendConfig.disable; + legendCmpt.set('disable', disable, legend !== undefined); + if (disable) { + return legendCmpt; + } + legend = legend || {}; + const scaleType = model.getScaleComponent(channel).get('type'); + const fieldOrDatumDef = getFieldOrDatumDef(encoding[channel]); + const timeUnit = isFieldDef(fieldOrDatumDef) ? normalizeTimeUnit(fieldOrDatumDef.timeUnit)?.unit : undefined; + const orient = legend.orient || config.legend.orient || 'right'; + const legendType = getLegendType({ + legend, + channel, + timeUnit, + scaleType + }); + const direction = getDirection({ + legend, + legendType, + orient, + legendConfig + }); + const ruleParams = { + legend, + channel, + model, + markDef, + encoding, + fieldOrDatumDef, + legendConfig, + config, + scaleType, + orient, + legendType, + direction + }; + for (const property of LEGEND_COMPONENT_PROPERTIES) { + if (legendType === 'gradient' && property.startsWith('symbol') || legendType === 'symbol' && property.startsWith('gradient')) { + continue; + } + const value = property in legendRules ? legendRules[property](ruleParams) : legend[property]; + if (value !== undefined) { + const explicit = isExplicit$1(value, property, legend, model.fieldDef(channel)); + if (explicit || config.legend[property] === undefined) { + legendCmpt.set(property, value, explicit); + } + } + } + const legendEncoding = legend?.encoding ?? {}; + const selections = legendCmpt.get('selections'); + const legendEncode = {}; + const legendEncodeParams = { + fieldOrDatumDef, + model, + channel, + legendCmpt, + legendType + }; + for (const part of ['labels', 'legend', 'title', 'symbols', 'gradient', 'entries']) { + const legendEncodingPart = guideEncodeEntry(legendEncoding[part] ?? {}, model); + const value = part in legendEncodeRules ? legendEncodeRules[part](legendEncodingPart, legendEncodeParams) // apply rule + : legendEncodingPart; // no rule -- just default values + + if (value !== undefined && !isEmpty(value)) { + legendEncode[part] = { + ...(selections?.length && isFieldDef(fieldOrDatumDef) ? { + name: `${varName(fieldOrDatumDef.field)}_legend_${part}` + } : {}), + ...(selections?.length ? { + interactive: !!selections + } : {}), + update: value + }; + } + } + if (!isEmpty(legendEncode)) { + legendCmpt.set('encode', legendEncode, !!legend?.encoding); + } + return legendCmpt; + } + function parseNonUnitLegend(model) { + const { + legends, + resolve + } = model.component; + for (const child of model.children) { + parseLegend(child); + for (const channel of keys(child.component.legends)) { + resolve.legend[channel] = parseGuideResolve(model.component.resolve, channel); + if (resolve.legend[channel] === 'shared') { + // If the resolve says shared (and has not been overridden) + // We will try to merge and see if there is a conflict + + legends[channel] = mergeLegendComponent(legends[channel], child.component.legends[channel]); + if (!legends[channel]) { + // If merge returns nothing, there is a conflict so we cannot make the legend shared. + // Thus, mark legend as independent and remove the legend component. + resolve.legend[channel] = 'independent'; + delete legends[channel]; + } + } + } + } + for (const channel of keys(legends)) { + for (const child of model.children) { + if (!child.component.legends[channel]) { + // skip if the child does not have a particular legend + continue; + } + if (resolve.legend[channel] === 'shared') { + // After merging shared legend, make sure to remove legend from child + delete child.component.legends[channel]; + } + } + } + return legends; + } + function mergeLegendComponent(mergedLegend, childLegend) { + if (!mergedLegend) { + return childLegend.clone(); + } + const mergedOrient = mergedLegend.getWithExplicit('orient'); + const childOrient = childLegend.getWithExplicit('orient'); + if (mergedOrient.explicit && childOrient.explicit && mergedOrient.value !== childOrient.value) { + // TODO: throw warning if resolve is explicit (We don't have info about explicit/implicit resolve yet.) + // Cannot merge due to inconsistent orient + return undefined; + } + let typeMerged = false; + // Otherwise, let's merge + for (const prop of LEGEND_COMPONENT_PROPERTIES) { + const mergedValueWithExplicit = mergeValuesWithExplicit(mergedLegend.getWithExplicit(prop), childLegend.getWithExplicit(prop), prop, 'legend', + // Tie breaker function + (v1, v2) => { + switch (prop) { + case 'symbolType': + return mergeSymbolType(v1, v2); + case 'title': + return mergeTitleComponent(v1, v2); + case 'type': + // There are only two types. If we have different types, then prefer symbol over gradient. + typeMerged = true; + return makeImplicit('symbol'); + } + return defaultTieBreaker(v1, v2, prop, 'legend'); + }); + mergedLegend.setWithExplicit(prop, mergedValueWithExplicit); + } + if (typeMerged) { + if (mergedLegend.implicit?.encode?.gradient) { + deleteNestedProperty(mergedLegend.implicit, ['encode', 'gradient']); + } + if (mergedLegend.explicit?.encode?.gradient) { + deleteNestedProperty(mergedLegend.explicit, ['encode', 'gradient']); + } + } + return mergedLegend; + } + function mergeSymbolType(st1, st2) { + if (st2.value === 'circle') { + // prefer "circle" over "stroke" + return st2; + } + return st1; + } + + function setLegendEncode(legend, part, vgProp, vgRef) { + legend.encode ??= {}; + legend.encode[part] ??= {}; + legend.encode[part].update ??= {}; + // TODO: remove as any after https://github.com/prisma/nexus-prisma/issues/291 + legend.encode[part].update[vgProp] = vgRef; + } + function assembleLegends(model) { + const legendComponentIndex = model.component.legends; + const legendByDomain = {}; + for (const channel of keys(legendComponentIndex)) { + const scaleComponent = model.getScaleComponent(channel); + const domainHash = stringify(scaleComponent.get('domains')); + if (legendByDomain[domainHash]) { + for (const mergedLegendComponent of legendByDomain[domainHash]) { + const merged = mergeLegendComponent(mergedLegendComponent, legendComponentIndex[channel]); + if (!merged) { + // If cannot merge, need to add this legend separately + legendByDomain[domainHash].push(legendComponentIndex[channel]); + } + } + } else { + legendByDomain[domainHash] = [legendComponentIndex[channel].clone()]; + } + } + const legends = vals(legendByDomain).flat().map(l => assembleLegend(l, model.config)).filter(l => l !== undefined); + return legends; + } + function assembleLegend(legendCmpt, config) { + const { + disable, + labelExpr, + selections, + ...legend + } = legendCmpt.combine(); + if (disable) { + return undefined; + } + if (config.aria === false && legend.aria == undefined) { + legend.aria = false; + } + if (legend.encode?.symbols) { + const out = legend.encode.symbols.update; + if (out.fill && out.fill['value'] !== 'transparent' && !out.stroke && !legend.stroke) { + // For non color channel's legend, we need to override symbol stroke config from Vega config if stroke channel is not used. + out.stroke = { + value: 'transparent' + }; + } + + // Remove properties that the legend is encoding. + for (const property of LEGEND_SCALE_CHANNELS) { + if (legend[property]) { + delete out[property]; + } + } + } + if (!legend.title) { + // title schema doesn't include null, '' + delete legend.title; + } + if (labelExpr !== undefined) { + let expr = labelExpr; + if (legend.encode?.labels?.update && isSignalRef(legend.encode.labels.update.text)) { + expr = replaceAll(labelExpr, 'datum.label', legend.encode.labels.update.text.signal); + } + setLegendEncode(legend, 'labels', 'text', { + signal: expr + }); + } + return legend; + } + + function assembleProjections(model) { + if (isLayerModel(model) || isConcatModel(model)) { + return assembleProjectionsForModelAndChildren(model); + } else { + return assembleProjectionForModel(model); + } + } + function assembleProjectionsForModelAndChildren(model) { + return model.children.reduce((projections, child) => { + return projections.concat(child.assembleProjections()); + }, assembleProjectionForModel(model)); + } + function assembleProjectionForModel(model) { + const component = model.component.projection; + if (!component || component.merged) { + return []; + } + const projection = component.combine(); + const { + name + } = projection; // we need to extract name so that it is always present in the output and pass TS type validation + + if (!component.data) { + // generate custom projection, no automatic fitting + return [{ + name, + // translate to center by default + translate: { + signal: '[width / 2, height / 2]' + }, + // parameters, overwrite default translate if specified + ...projection + }]; + } else { + // generate projection that uses extent fitting + const size = { + signal: `[${component.size.map(ref => ref.signal).join(', ')}]` + }; + const fits = component.data.reduce((sources, data) => { + const source = isSignalRef(data) ? data.signal : `data('${model.lookupDataSource(data)}')`; + if (!contains(sources, source)) { + // build a unique list of sources + sources.push(source); + } + return sources; + }, []); + if (fits.length <= 0) { + throw new Error("Projection's fit didn't find any data sources"); + } + return [{ + name, + size, + fit: { + signal: fits.length > 1 ? `[${fits.join(', ')}]` : fits[0] + }, + ...projection + }]; + } + } + + /** + * Any property of Projection can be in config + */ + + const PROJECTION_PROPERTIES = ['type', 'clipAngle', 'clipExtent', 'center', 'rotate', 'precision', 'reflectX', 'reflectY', 'coefficient', 'distance', 'fraction', 'lobes', 'parallel', 'radius', 'ratio', 'spacing', 'tilt']; + + class ProjectionComponent extends Split { + merged = false; + constructor(name, specifiedProjection, size, data) { + super({ + ...specifiedProjection + }, + // all explicit properties of projection + { + name + } // name as initial implicit property + ); + this.specifiedProjection = specifiedProjection; + this.size = size; + this.data = data; + } + + /** + * Whether the projection parameters should fit provided data. + */ + get isFit() { + return !!this.data; + } + } + + function parseProjection(model) { + model.component.projection = isUnitModel(model) ? parseUnitProjection(model) : parseNonUnitProjections(model); + } + function parseUnitProjection(model) { + if (model.hasProjection) { + const proj = replaceExprRef(model.specifiedProjection); + const fit = !(proj && (proj.scale != null || proj.translate != null)); + const size = fit ? [model.getSizeSignalRef('width'), model.getSizeSignalRef('height')] : undefined; + const data = fit ? gatherFitData(model) : undefined; + const projComp = new ProjectionComponent(model.projectionName(true), { + ...replaceExprRef(model.config.projection), + ...proj + }, size, data); + if (!projComp.get('type')) { + projComp.set('type', 'equalEarth', false); + } + return projComp; + } + return undefined; + } + function gatherFitData(model) { + const data = []; + const { + encoding + } = model; + for (const posssiblePair of [[LONGITUDE, LATITUDE], [LONGITUDE2, LATITUDE2]]) { + if (getFieldOrDatumDef(encoding[posssiblePair[0]]) || getFieldOrDatumDef(encoding[posssiblePair[1]])) { + data.push({ + signal: model.getName(`geojson_${data.length}`) + }); + } + } + if (model.channelHasField(SHAPE) && model.typedFieldDef(SHAPE).type === GEOJSON) { + data.push({ + signal: model.getName(`geojson_${data.length}`) + }); + } + if (data.length === 0) { + // main source is geojson, so we can just use that + data.push(model.requestDataName(DataSourceType.Main)); + } + return data; + } + function mergeIfNoConflict(first, second) { + const allPropertiesShared = every(PROJECTION_PROPERTIES, prop => { + // neither has the property + if (!vega.hasOwnProperty(first.explicit, prop) && !vega.hasOwnProperty(second.explicit, prop)) { + return true; + } + // both have property and an equal value for property + if (vega.hasOwnProperty(first.explicit, prop) && vega.hasOwnProperty(second.explicit, prop) && + // some properties might be signals or objects and require hashing for comparison + deepEqual(first.get(prop), second.get(prop))) { + return true; + } + return false; + }); + const size = deepEqual(first.size, second.size); + if (size) { + if (allPropertiesShared) { + return first; + } else if (deepEqual(first.explicit, {})) { + return second; + } else if (deepEqual(second.explicit, {})) { + return first; + } + } + + // if all properties don't match, let each unit spec have its own projection + return null; + } + function parseNonUnitProjections(model) { + if (model.children.length === 0) { + return undefined; + } + let nonUnitProjection; + + // parse all children first + for (const child of model.children) { + parseProjection(child); + } + + // analyze parsed projections, attempt to merge + const mergable = every(model.children, child => { + const projection = child.component.projection; + if (!projection) { + // child layer does not use a projection + return true; + } else if (!nonUnitProjection) { + // cached 'projection' is null, cache this one + nonUnitProjection = projection; + return true; + } else { + const merge = mergeIfNoConflict(nonUnitProjection, projection); + if (merge) { + nonUnitProjection = merge; + } + return !!merge; + } + }); + + // if cached one and all other children share the same projection, + if (nonUnitProjection && mergable) { + // so we can elevate it to the layer level + const name = model.projectionName(true); + const modelProjection = new ProjectionComponent(name, nonUnitProjection.specifiedProjection, nonUnitProjection.size, duplicate(nonUnitProjection.data)); + + // rename and assign all others as merged + for (const child of model.children) { + const projection = child.component.projection; + if (projection) { + if (projection.isFit) { + modelProjection.data.push(...child.component.projection.data); + } + child.renameProjection(projection.get('name'), name); + projection.merged = true; + } + } + return modelProjection; + } + return undefined; + } + + function rangeFormula(model, fieldDef, channel, config) { + if (binRequiresRange(fieldDef, channel)) { + // read format from axis or legend, if there is no format then use config.numberFormat + + const guide = isUnitModel(model) ? model.axis(channel) ?? model.legend(channel) ?? {} : {}; + const startField = vgField(fieldDef, { + expr: 'datum' + }); + const endField = vgField(fieldDef, { + expr: 'datum', + binSuffix: 'end' + }); + return { + formulaAs: vgField(fieldDef, { + binSuffix: 'range', + forAs: true + }), + formula: binFormatExpression(startField, endField, guide.format, guide.formatType, config) + }; + } + return {}; + } + function binKey(bin, field) { + return `${binToString(bin)}_${field}`; + } + function getSignalsFromModel(model, key) { + return { + signal: model.getName(`${key}_bins`), + extentSignal: model.getName(`${key}_extent`) + }; + } + function getBinSignalName(model, field, bin) { + const normalizedBin = normalizeBin(bin, undefined) ?? {}; + const key = binKey(normalizedBin, field); + return model.getName(`${key}_bins`); + } + function isBinTransform(t) { + return 'as' in t; + } + function createBinComponent(t, bin, model) { + let as; + let span; + if (isBinTransform(t)) { + as = vega.isString(t.as) ? [t.as, `${t.as}_end`] : [t.as[0], t.as[1]]; + } else { + as = [vgField(t, { + forAs: true + }), vgField(t, { + binSuffix: 'end', + forAs: true + })]; + } + const normalizedBin = { + ...normalizeBin(bin, undefined) + }; + const key = binKey(normalizedBin, t.field); + const { + signal, + extentSignal + } = getSignalsFromModel(model, key); + if (isParameterExtent(normalizedBin.extent)) { + const ext = normalizedBin.extent; + span = parseSelectionExtent(model, ext.param, ext); + delete normalizedBin.extent; // Vega-Lite selection extent map to Vega's span property. + } + const binComponent = { + bin: normalizedBin, + field: t.field, + as: [as], + ...(signal ? { + signal + } : {}), + ...(extentSignal ? { + extentSignal + } : {}), + ...(span ? { + span + } : {}) + }; + return { + key, + binComponent + }; + } + class BinNode extends DataFlowNode { + clone() { + return new BinNode(null, duplicate(this.bins)); + } + constructor(parent, bins) { + super(parent); + this.bins = bins; + } + static makeFromEncoding(parent, model) { + const bins = model.reduceFieldDef((binComponentIndex, fieldDef, channel) => { + if (isTypedFieldDef(fieldDef) && isBinning(fieldDef.bin)) { + const { + key, + binComponent + } = createBinComponent(fieldDef, fieldDef.bin, model); + binComponentIndex[key] = { + ...binComponent, + ...binComponentIndex[key], + ...rangeFormula(model, fieldDef, channel, model.config) + }; + } + return binComponentIndex; + }, {}); + if (isEmpty(bins)) { + return null; + } + return new BinNode(parent, bins); + } + + /** + * Creates a bin node from BinTransform. + * The optional parameter should provide + */ + static makeFromTransform(parent, t, model) { + const { + key, + binComponent + } = createBinComponent(t, t.bin, model); + return new BinNode(parent, { + [key]: binComponent + }); + } + + /** + * Merge bin nodes. This method either integrates the bin config from the other node + * or if this node already has a bin config, renames the corresponding signal in the model. + */ + merge(other, renameSignal) { + for (const key of keys(other.bins)) { + if (key in this.bins) { + renameSignal(other.bins[key].signal, this.bins[key].signal); + // Ensure that we don't have duplicate names for signal pairs + this.bins[key].as = unique([...this.bins[key].as, ...other.bins[key].as], hash); + } else { + this.bins[key] = other.bins[key]; + } + } + for (const child of other.children) { + other.removeChild(child); + child.parent = this; + } + other.remove(); + } + producedFields() { + return new Set(vals(this.bins).map(c => c.as).flat(2)); + } + dependentFields() { + return new Set(vals(this.bins).map(c => c.field)); + } + hash() { + return `Bin ${hash(this.bins)}`; + } + assemble() { + return vals(this.bins).flatMap(bin => { + const transform = []; + const [binAs, ...remainingAs] = bin.as; + const { + extent, + ...params + } = bin.bin; + const binTrans = { + type: 'bin', + field: replacePathInField(bin.field), + as: binAs, + signal: bin.signal, + ...(!isParameterExtent(extent) ? { + extent + } : { + extent: null + }), + ...(bin.span ? { + span: { + signal: `span(${bin.span})` + } + } : {}), + ...params + }; + if (!extent && bin.extentSignal) { + transform.push({ + type: 'extent', + field: replacePathInField(bin.field), + signal: bin.extentSignal + }); + binTrans.extent = { + signal: bin.extentSignal + }; + } + transform.push(binTrans); + for (const as of remainingAs) { + for (let i = 0; i < 2; i++) { + transform.push({ + type: 'formula', + expr: vgField({ + field: binAs[i] + }, { + expr: 'datum' + }), + as: as[i] + }); + } + } + if (bin.formula) { + transform.push({ + type: 'formula', + expr: bin.formula, + as: bin.formulaAs + }); + } + return transform; + }); + } + } + + function addDimension(dims, channel, fieldDef, model) { + const channelDef2 = isUnitModel(model) ? model.encoding[getSecondaryRangeChannel(channel)] : undefined; + if (isTypedFieldDef(fieldDef) && isUnitModel(model) && hasBandEnd(fieldDef, channelDef2, model.markDef, model.config)) { + dims.add(vgField(fieldDef, {})); + dims.add(vgField(fieldDef, { + suffix: 'end' + })); + const { + mark, + markDef, + config + } = model; + const bandPosition = getBandPosition({ + fieldDef, + markDef, + config + }); + if (isRectBasedMark(mark) && bandPosition !== 0.5 && isXorY(channel)) { + dims.add(vgField(fieldDef, { + suffix: OFFSETTED_RECT_START_SUFFIX + })); + dims.add(vgField(fieldDef, { + suffix: OFFSETTED_RECT_END_SUFFIX + })); + } + if (fieldDef.bin && binRequiresRange(fieldDef, channel)) { + dims.add(vgField(fieldDef, { + binSuffix: 'range' + })); + } + } else if (isGeoPositionChannel(channel)) { + const posChannel = getPositionChannelFromLatLong(channel); + dims.add(model.getName(posChannel)); + } else { + dims.add(vgField(fieldDef)); + } + if (isScaleFieldDef(fieldDef) && isFieldRange(fieldDef.scale?.range)) { + dims.add(fieldDef.scale.range.field); + } + return dims; + } + function mergeMeasures(parentMeasures, childMeasures) { + for (const field of keys(childMeasures)) { + // when we merge a measure, we either have to add an aggregation operator or even a new field + const ops = childMeasures[field]; + for (const op of keys(ops)) { + if (field in parentMeasures) { + // add operator to existing measure field + parentMeasures[field][op] = new Set([...(parentMeasures[field][op] ?? []), ...ops[op]]); + } else { + parentMeasures[field] = { + [op]: ops[op] + }; + } + } + } + } + class AggregateNode extends DataFlowNode { + clone() { + return new AggregateNode(null, new Set(this.dimensions), duplicate(this.measures)); + } + + /** + * @param dimensions string set for dimensions + * @param measures dictionary mapping field name => dict of aggregation functions and names to use + */ + constructor(parent, dimensions, measures) { + super(parent); + this.dimensions = dimensions; + this.measures = measures; + } + get groupBy() { + return this.dimensions; + } + static makeFromEncoding(parent, model) { + let isAggregate = false; + model.forEachFieldDef(fd => { + if (fd.aggregate) { + isAggregate = true; + } + }); + const meas = {}; + const dims = new Set(); + if (!isAggregate) { + // no need to create this node if the model has no aggregation + return null; + } + model.forEachFieldDef((fieldDef, channel) => { + const { + aggregate, + field + } = fieldDef; + if (aggregate) { + if (aggregate === 'count') { + meas['*'] ??= {}; + meas['*']['count'] = new Set([vgField(fieldDef, { + forAs: true + })]); + } else { + if (isArgminDef(aggregate) || isArgmaxDef(aggregate)) { + const op = isArgminDef(aggregate) ? 'argmin' : 'argmax'; + const argField = aggregate[op]; + meas[argField] ??= {}; + meas[argField][op] = new Set([vgField({ + op, + field: argField + }, { + forAs: true + })]); + } else { + meas[field] ??= {}; + meas[field][aggregate] = new Set([vgField(fieldDef, { + forAs: true + })]); + } + + // For scale channel with domain === 'unaggregated', add min/max so we can use their union as unaggregated domain + if (isScaleChannel(channel) && model.scaleDomain(channel) === 'unaggregated') { + meas[field] ??= {}; + meas[field]['min'] = new Set([vgField({ + field, + aggregate: 'min' + }, { + forAs: true + })]); + meas[field]['max'] = new Set([vgField({ + field, + aggregate: 'max' + }, { + forAs: true + })]); + } + } + } else { + addDimension(dims, channel, fieldDef, model); + } + }); + if (dims.size + keys(meas).length === 0) { + return null; + } + return new AggregateNode(parent, dims, meas); + } + static makeFromTransform(parent, t) { + const dims = new Set(); + const meas = {}; + for (const s of t.aggregate) { + const { + op, + field, + as + } = s; + if (op) { + if (op === 'count') { + meas['*'] ??= {}; + meas['*']['count'] = new Set([as ? as : vgField(s, { + forAs: true + })]); + } else { + meas[field] ??= {}; + meas[field][op] ??= new Set(); + meas[field][op].add(as ? as : vgField(s, { + forAs: true + })); + } + } + } + for (const s of t.groupby ?? []) { + dims.add(s); + } + if (dims.size + keys(meas).length === 0) { + return null; + } + return new AggregateNode(parent, dims, meas); + } + merge(other) { + if (setEqual(this.dimensions, other.dimensions)) { + mergeMeasures(this.measures, other.measures); + return true; + } + debug('different dimensions, cannot merge'); + return false; + } + addDimensions(fields) { + fields.forEach(this.dimensions.add, this.dimensions); + } + dependentFields() { + return new Set([...this.dimensions, ...keys(this.measures)]); + } + producedFields() { + const out = new Set(); + for (const field of keys(this.measures)) { + for (const op of keys(this.measures[field])) { + const m = this.measures[field][op]; + if (m.size === 0) { + out.add(`${op}_${field}`); + } else { + m.forEach(out.add, out); + } + } + } + return out; + } + hash() { + return `Aggregate ${hash({ + dimensions: this.dimensions, + measures: this.measures + })}`; + } + assemble() { + const ops = []; + const fields = []; + const as = []; + for (const field of keys(this.measures)) { + for (const op of keys(this.measures[field])) { + for (const alias of this.measures[field][op]) { + as.push(alias); + ops.push(op); + fields.push(field === '*' ? null : replacePathInField(field)); + } + } + } + const result = { + type: 'aggregate', + groupby: [...this.dimensions].map(replacePathInField), + ops, + fields, + as + }; + return result; + } + } + + /** + * A node that helps us track what fields we are faceting by. + */ + class FacetNode extends DataFlowNode { + /** + * @param model The facet model. + * @param name The name that this facet source will have. + * @param data The source data for this facet data. + */ + constructor(parent, model, name, data) { + super(parent); + this.model = model; + this.name = name; + this.data = data; + for (const channel of FACET_CHANNELS) { + const fieldDef = model.facet[channel]; + if (fieldDef) { + const { + bin, + sort + } = fieldDef; + this[channel] = { + name: model.getName(`${channel}_domain`), + fields: [vgField(fieldDef), ...(isBinning(bin) ? [vgField(fieldDef, { + binSuffix: 'end' + })] : [])], + ...(isSortField(sort) ? { + sortField: sort + } : vega.isArray(sort) ? { + sortIndexField: sortArrayIndexField(fieldDef, channel) + } : {}) + }; + } + } + this.childModel = model.child; + } + hash() { + let out = `Facet`; + for (const channel of FACET_CHANNELS) { + if (this[channel]) { + out += ` ${channel.charAt(0)}:${hash(this[channel])}`; + } + } + return out; + } + get fields() { + const f = []; + for (const channel of FACET_CHANNELS) { + if (this[channel]?.fields) { + f.push(...this[channel].fields); + } + } + return f; + } + dependentFields() { + const depFields = new Set(this.fields); + for (const channel of FACET_CHANNELS) { + if (this[channel]) { + if (this[channel].sortField) { + depFields.add(this[channel].sortField.field); + } + if (this[channel].sortIndexField) { + depFields.add(this[channel].sortIndexField); + } + } + } + return depFields; + } + producedFields() { + return new Set(); // facet does not produce any new fields + } + + /** + * The name to reference this source is its name. + */ + getSource() { + return this.name; + } + getChildIndependentFieldsWithStep() { + const childIndependentFieldsWithStep = {}; + for (const channel of POSITION_SCALE_CHANNELS) { + const childScaleComponent = this.childModel.component.scales[channel]; + if (childScaleComponent && !childScaleComponent.merged) { + // independent scale + const type = childScaleComponent.get('type'); + const range = childScaleComponent.get('range'); + if (hasDiscreteDomain(type) && isVgRangeStep(range)) { + const domain = assembleDomain(this.childModel, channel); + const field = getFieldFromDomain(domain); + if (field) { + childIndependentFieldsWithStep[channel] = field; + } else { + warn(unknownField(channel)); + } + } + } + } + return childIndependentFieldsWithStep; + } + assembleRowColumnHeaderData(channel, crossedDataName, childIndependentFieldsWithStep) { + const childChannel = { + row: 'y', + column: 'x', + facet: undefined + }[channel]; + const fields = []; + const ops = []; + const as = []; + if (childChannel && childIndependentFieldsWithStep && childIndependentFieldsWithStep[childChannel]) { + if (crossedDataName) { + // If there is a crossed data, calculate max + fields.push(`distinct_${childIndependentFieldsWithStep[childChannel]}`); + ops.push('max'); + } else { + // If there is no crossed data, just calculate distinct + fields.push(childIndependentFieldsWithStep[childChannel]); + ops.push('distinct'); + } + // Although it is technically a max, just name it distinct so it's easier to refer to it + as.push(`distinct_${childIndependentFieldsWithStep[childChannel]}`); + } + const { + sortField, + sortIndexField + } = this[channel]; + if (sortField) { + const { + op = DEFAULT_SORT_OP, + field + } = sortField; + fields.push(field); + ops.push(op); + as.push(vgField(sortField, { + forAs: true + })); + } else if (sortIndexField) { + fields.push(sortIndexField); + ops.push('max'); + as.push(sortIndexField); + } + return { + name: this[channel].name, + // Use data from the crossed one if it exist + source: crossedDataName ?? this.data, + transform: [{ + type: 'aggregate', + groupby: this[channel].fields, + ...(fields.length ? { + fields, + ops, + as + } : {}) + }] + }; + } + assembleFacetHeaderData(childIndependentFieldsWithStep) { + const { + columns + } = this.model.layout; + const { + layoutHeaders + } = this.model.component; + const data = []; + const hasSharedAxis = {}; + for (const headerChannel of HEADER_CHANNELS) { + for (const headerType of HEADER_TYPES) { + const headers = (layoutHeaders[headerChannel] && layoutHeaders[headerChannel][headerType]) ?? []; + for (const header of headers) { + if (header.axes?.length > 0) { + hasSharedAxis[headerChannel] = true; + break; + } + } + } + if (hasSharedAxis[headerChannel]) { + const cardinality = `length(data("${this.facet.name}"))`; + const stop = headerChannel === 'row' ? columns ? { + signal: `ceil(${cardinality} / ${columns})` + } : 1 : columns ? { + signal: `min(${cardinality}, ${columns})` + } : { + signal: cardinality + }; + data.push({ + name: `${this.facet.name}_${headerChannel}`, + transform: [{ + type: 'sequence', + start: 0, + stop + }] + }); + } + } + const { + row, + column + } = hasSharedAxis; + if (row || column) { + data.unshift(this.assembleRowColumnHeaderData('facet', null, childIndependentFieldsWithStep)); + } + return data; + } + assemble() { + const data = []; + let crossedDataName = null; + const childIndependentFieldsWithStep = this.getChildIndependentFieldsWithStep(); + const { + column, + row, + facet + } = this; + if (column && row && (childIndependentFieldsWithStep.x || childIndependentFieldsWithStep.y)) { + // Need to create a cross dataset to correctly calculate cardinality + crossedDataName = `cross_${this.column.name}_${this.row.name}`; + const fields = [].concat(childIndependentFieldsWithStep.x ?? [], childIndependentFieldsWithStep.y ?? []); + const ops = fields.map(() => 'distinct'); + data.push({ + name: crossedDataName, + source: this.data, + transform: [{ + type: 'aggregate', + groupby: this.fields, + fields, + ops + }] + }); + } + for (const channel of [COLUMN, ROW]) { + if (this[channel]) { + data.push(this.assembleRowColumnHeaderData(channel, crossedDataName, childIndependentFieldsWithStep)); + } + } + if (facet) { + const facetData = this.assembleFacetHeaderData(childIndependentFieldsWithStep); + if (facetData) { + data.push(...facetData); + } + } + return data; + } + } + + /** + * Remove quotes from a string. + */ + function unquote(pattern) { + if (pattern.startsWith("'") && pattern.endsWith("'") || pattern.startsWith('"') && pattern.endsWith('"')) { + return pattern.slice(1, -1); + } + return pattern; + } + + /** + * @param field The field. + * @param parse What to parse the field as. + */ + function parseExpression(field, parse) { + const f = accessPathWithDatum(field); + if (parse === 'number') { + return `toNumber(${f})`; + } else if (parse === 'boolean') { + return `toBoolean(${f})`; + } else if (parse === 'string') { + return `toString(${f})`; + } else if (parse === 'date') { + return `toDate(${f})`; + } else if (parse === 'flatten') { + return f; + } else if (parse.startsWith('date:')) { + const specifier = unquote(parse.slice(5, parse.length)); + return `timeParse(${f},'${specifier}')`; + } else if (parse.startsWith('utc:')) { + const specifier = unquote(parse.slice(4, parse.length)); + return `utcParse(${f},'${specifier}')`; + } else { + warn(unrecognizedParse(parse)); + return null; + } + } + function getImplicitFromFilterTransform(transform) { + const implicit = {}; + forEachLeaf(transform.filter, filter => { + if (isFieldPredicate(filter)) { + // Automatically add a parse node for filters with filter objects + let val = null; + + // For EqualFilter, just use the equal property. + // For RangeFilter and OneOfFilter, all array members should have + // the same type, so we only use the first one. + if (isFieldEqualPredicate(filter)) { + val = signalRefOrValue(filter.equal); + } else if (isFieldLTEPredicate(filter)) { + val = signalRefOrValue(filter.lte); + } else if (isFieldLTPredicate(filter)) { + val = signalRefOrValue(filter.lt); + } else if (isFieldGTPredicate(filter)) { + val = signalRefOrValue(filter.gt); + } else if (isFieldGTEPredicate(filter)) { + val = signalRefOrValue(filter.gte); + } else if (isFieldRangePredicate(filter)) { + val = filter.range[0]; + } else if (isFieldOneOfPredicate(filter)) { + val = (filter.oneOf ?? filter['in'])[0]; + } // else -- for filter expression, we can't infer anything + + if (val) { + if (isDateTime(val)) { + implicit[filter.field] = 'date'; + } else if (vega.isNumber(val)) { + implicit[filter.field] = 'number'; + } else if (vega.isString(val)) { + implicit[filter.field] = 'string'; + } + } + if (filter.timeUnit) { + implicit[filter.field] = 'date'; + } + } + }); + return implicit; + } + + /** + * Creates a parse node for implicit parsing from a model and updates ancestorParse. + */ + function getImplicitFromEncoding(model) { + const implicit = {}; + function add(fieldDef) { + if (isFieldOrDatumDefForTimeFormat(fieldDef)) { + implicit[fieldDef.field] = 'date'; + } else if (fieldDef.type === 'quantitative' && isMinMaxOp(fieldDef.aggregate) // we need to parse numbers to support correct min and max + ) { + implicit[fieldDef.field] = 'number'; + } else if (accessPathDepth(fieldDef.field) > 1) { + // For non-date/non-number (strings and booleans), derive a flattened field for a referenced nested field. + // (Parsing numbers / dates already flattens numeric and temporal fields.) + if (!(fieldDef.field in implicit)) { + implicit[fieldDef.field] = 'flatten'; + } + } else if (isScaleFieldDef(fieldDef) && isSortField(fieldDef.sort) && accessPathDepth(fieldDef.sort.field) > 1) { + // Flatten fields that we sort by but that are not otherwise flattened. + if (!(fieldDef.sort.field in implicit)) { + implicit[fieldDef.sort.field] = 'flatten'; + } + } + } + if (isUnitModel(model) || isFacetModel(model)) { + // Parse encoded fields + model.forEachFieldDef((fieldDef, channel) => { + if (isTypedFieldDef(fieldDef)) { + add(fieldDef); + } else { + const mainChannel = getMainRangeChannel(channel); + const mainFieldDef = model.fieldDef(mainChannel); + add({ + ...fieldDef, + type: mainFieldDef.type + }); + } + }); + } + + // Parse quantitative dimension fields of path marks as numbers so that we sort them correctly. + if (isUnitModel(model)) { + const { + mark, + markDef, + encoding + } = model; + if (isPathMark(mark) && + // No need to sort by dimension if we have a connected scatterplot (order channel is present) + !model.encoding.order) { + const dimensionChannel = markDef.orient === 'horizontal' ? 'y' : 'x'; + const dimensionChannelDef = encoding[dimensionChannel]; + if (isFieldDef(dimensionChannelDef) && dimensionChannelDef.type === 'quantitative' && !(dimensionChannelDef.field in implicit)) { + implicit[dimensionChannelDef.field] = 'number'; + } + } + } + return implicit; + } + + /** + * Creates a parse node for implicit parsing from a model and updates ancestorParse. + */ + function getImplicitFromSelection(model) { + const implicit = {}; + if (isUnitModel(model) && model.component.selection) { + for (const name of keys(model.component.selection)) { + const selCmpt = model.component.selection[name]; + for (const proj of selCmpt.project.items) { + if (!proj.channel && accessPathDepth(proj.field) > 1) { + implicit[proj.field] = 'flatten'; + } + } + } + } + return implicit; + } + class ParseNode extends DataFlowNode { + clone() { + return new ParseNode(null, duplicate(this._parse)); + } + constructor(parent, parse) { + super(parent); + this._parse = parse; + } + hash() { + return `Parse ${hash(this._parse)}`; + } + + /** + * Creates a parse node from a data.format.parse and updates ancestorParse. + */ + static makeExplicit(parent, model, ancestorParse) { + // Custom parse + let explicit = {}; + const data = model.data; + if (!isGenerator(data) && data?.format?.parse) { + explicit = data.format.parse; + } + return this.makeWithAncestors(parent, explicit, {}, ancestorParse); + } + + /** + * Creates a parse node from "explicit" parse and "implicit" parse and updates ancestorParse. + */ + static makeWithAncestors(parent, explicit, implicit, ancestorParse) { + // 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. + for (const field of keys(implicit)) { + const parsedAs = ancestorParse.getWithExplicit(field); + if (parsedAs.value !== undefined) { + // We always ignore derived fields even if they are implicitly defined because we expect users to create the right types. + if (parsedAs.explicit || parsedAs.value === implicit[field] || parsedAs.value === 'derived' || implicit[field] === 'flatten') { + delete implicit[field]; + } else { + warn(differentParse(field, implicit[field], parsedAs.value)); + } + } + } + for (const field of keys(explicit)) { + const parsedAs = ancestorParse.get(field); + if (parsedAs !== undefined) { + // Don't parse a field again if it has been parsed with the same type already. + if (parsedAs === explicit[field]) { + delete explicit[field]; + } else { + warn(differentParse(field, explicit[field], parsedAs)); + } + } + } + const parse = new Split(explicit, implicit); + + // add the format parse from this model so that children don't parse the same field again + ancestorParse.copyAll(parse); + + // copy only non-null parses + const p = {}; + for (const key of keys(parse.combine())) { + const val = parse.get(key); + if (val !== null) { + p[key] = val; + } + } + if (keys(p).length === 0 || ancestorParse.parseNothing) { + return null; + } + return new ParseNode(parent, p); + } + get parse() { + return this._parse; + } + merge(other) { + this._parse = { + ...this._parse, + ...other.parse + }; + other.remove(); + } + + /** + * Assemble an object for Vega's format.parse property. + */ + assembleFormatParse() { + const formatParse = {}; + for (const field of keys(this._parse)) { + const p = this._parse[field]; + if (accessPathDepth(field) === 1) { + formatParse[field] = p; + } + } + return formatParse; + } + + // format parse depends and produces all fields in its parse + producedFields() { + return new Set(keys(this._parse)); + } + dependentFields() { + return new Set(keys(this._parse)); + } + assembleTransforms() { + let onlyNested = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + return keys(this._parse).filter(field => onlyNested ? accessPathDepth(field) > 1 : true).map(field => { + const expr = parseExpression(field, this._parse[field]); + if (!expr) { + return null; + } + const formula = { + type: 'formula', + expr, + as: removePathFromField(field) // Vega output is always flattened + }; + return formula; + }).filter(t => t !== null); + } + } + + class IdentifierNode extends DataFlowNode { + clone() { + return new IdentifierNode(null); + } + constructor(parent) { + super(parent); + } + dependentFields() { + return new Set(); + } + producedFields() { + return new Set([SELECTION_ID]); + } + hash() { + return 'Identifier'; + } + assemble() { + return { + type: 'identifier', + as: SELECTION_ID + }; + } + } + + class GraticuleNode extends DataFlowNode { + clone() { + return new GraticuleNode(null, this.params); + } + constructor(parent, params) { + super(parent); + this.params = params; + } + dependentFields() { + return new Set(); + } + producedFields() { + return undefined; // there should never be a node before graticule + } + hash() { + return `Graticule ${hash(this.params)}`; + } + assemble() { + return { + type: 'graticule', + ...(this.params === true ? {} : this.params) + }; + } + } + + class SequenceNode extends DataFlowNode { + clone() { + return new SequenceNode(null, this.params); + } + constructor(parent, params) { + super(parent); + this.params = params; + } + dependentFields() { + return new Set(); + } + producedFields() { + return new Set([this.params.as ?? 'data']); + } + hash() { + return `Hash ${hash(this.params)}`; + } + assemble() { + return { + type: 'sequence', + ...this.params + }; + } + } + + class SourceNode extends DataFlowNode { + constructor(data) { + super(null); // source cannot have parent + + data ??= { + name: 'source' + }; + let format; + if (!isGenerator(data)) { + format = data.format ? { + ...omit(data.format, ['parse']) + } : {}; + } + if (isInlineData(data)) { + this._data = { + values: data.values + }; + } else if (isUrlData(data)) { + this._data = { + url: data.url + }; + if (!format.type) { + // Extract extension from URL using snippet from + // http://stackoverflow.com/questions/680929/how-to-extract-extension-from-filename-string-in-javascript + let defaultExtension = /(?:\.([^.]+))?$/.exec(data.url)[1]; + if (!contains(['json', 'csv', 'tsv', 'dsv', 'topojson'], defaultExtension)) { + defaultExtension = 'json'; + } + + // defaultExtension has type string but we ensure that it is DataFormatType above + format.type = defaultExtension; + } + } else if (isSphereGenerator(data)) { + // hardwire GeoJSON sphere data into output specification + this._data = { + values: [{ + type: 'Sphere' + }] + }; + } else if (isNamedData(data) || isGenerator(data)) { + this._data = {}; + } + + // set flag to check if generator + this._generator = isGenerator(data); + + // any dataset can be named + if (data.name) { + this._name = data.name; + } + if (format && !isEmpty(format)) { + this._data.format = format; + } + } + dependentFields() { + return new Set(); + } + producedFields() { + return undefined; // we don't know what this source produces + } + get data() { + return this._data; + } + hasName() { + return !!this._name; + } + get isGenerator() { + return this._generator; + } + get dataName() { + return this._name; + } + set dataName(name) { + this._name = name; + } + set parent(parent) { + throw new Error('Source nodes have to be roots.'); + } + remove() { + throw new Error('Source nodes are roots and cannot be removed.'); + } + hash() { + throw new Error('Cannot hash sources'); + } + assemble() { + return { + name: this._name, + ...this._data, + transform: [] + }; + } + } + + /** + * Whether this dataflow node is the source of the dataflow that produces data i.e. a source or a generator. + */ + function isDataSourceNode(node) { + return node instanceof SourceNode || node instanceof GraticuleNode || node instanceof SequenceNode; + } + + /** + * Abstract base class for Dataflow optimizers. + * Contains only mutation handling logic. Subclasses need to implement iteration logic. + */ + class Optimizer { + #modified; + constructor() { + this.#modified = false; + } + + // Once true, #modified is never set to false + setModified() { + this.#modified = true; + } + get modifiedFlag() { + return this.#modified; + } + + /** + * Run the optimization for the tree with the provided root. + */ + } + + /** + * Starts from a node and runs the optimization function (the "run" method) upwards to the root, + * depending on the continue and modified flag values returned by the optimization function. + */ + class BottomUpOptimizer extends Optimizer { + /** + * Run the optimizer at the node. This method should not change the parent of the passed in node (it should only affect children). + */ + + /** + * Compute a map of node depths that we can use to determine a topological sort order. + */ + getNodeDepths(node, depth, depths) { + depths.set(node, depth); + for (const child of node.children) { + this.getNodeDepths(child, depth + 1, depths); + } + return depths; + } + + /** + * Run the optimizer on all nodes starting from the leaves. + */ + optimize(node) { + const depths = this.getNodeDepths(node, 0, new Map()); + const topologicalSort = [...depths.entries()].sort((a, b) => b[1] - a[1]); + for (const tuple of topologicalSort) { + this.run(tuple[0]); + } + return this.modifiedFlag; + } + } + + /** + * The optimizer function (the "run" method), is invoked on the given node and then continues recursively. + */ + class TopDownOptimizer extends Optimizer { + /** + * Run the optimizer at the node. + */ + + /** + * Run the optimizer depth first on all nodes starting from the roots. + */ + optimize(node) { + this.run(node); + for (const child of node.children) { + this.optimize(child); + } + return this.modifiedFlag; + } + } + + /** + * Merge identical nodes at forks by comparing hashes. + * + * Does not need to iterate from leaves so we implement this with recursion as it's a bit simpler. + */ + class MergeIdenticalNodes extends TopDownOptimizer { + mergeNodes(parent, nodes) { + const mergedNode = nodes.shift(); + for (const node of nodes) { + parent.removeChild(node); + node.parent = mergedNode; + node.remove(); + } + } + run(node) { + const hashes = node.children.map(x => x.hash()); + const buckets = {}; + for (let i = 0; i < hashes.length; i++) { + if (buckets[hashes[i]] === undefined) { + buckets[hashes[i]] = [node.children[i]]; + } else { + buckets[hashes[i]].push(node.children[i]); + } + } + for (const k of keys(buckets)) { + if (buckets[k].length > 1) { + this.setModified(); + this.mergeNodes(node, buckets[k]); + } + } + } + } + + /** + * Optimizer that removes identifier nodes that are not needed for selections. + */ + class RemoveUnnecessaryIdentifierNodes extends TopDownOptimizer { + constructor(model) { + super(); + this.requiresSelectionId = model && requiresSelectionId(model); + } + run(node) { + if (node instanceof IdentifierNode) { + // Only preserve IdentifierNodes if we have default discrete selections + // in our model tree, and if the nodes come after tuple producing nodes. + if (!(this.requiresSelectionId && (isDataSourceNode(node.parent) || node.parent instanceof AggregateNode || node.parent instanceof ParseNode))) { + this.setModified(); + node.remove(); + } + } + } + } + + /** + * Removes duplicate time unit nodes (as determined by the name of the output field) that may be generated due to + * selections projected over time units. Only keeps the first time unit in any branch. + * + * This optimizer is a custom top down optimizer that keep track of produced fields in a branch. + */ + class RemoveDuplicateTimeUnits extends Optimizer { + optimize(node) { + this.run(node, new Set()); + return this.modifiedFlag; + } + run(node, timeUnitFields) { + let producedFields = new Set(); + if (node instanceof TimeUnitNode) { + producedFields = node.producedFields(); + if (hasIntersection(producedFields, timeUnitFields)) { + this.setModified(); + node.removeFormulas(timeUnitFields); + if (node.producedFields.length === 0) { + node.remove(); + } + } + } + for (const child of node.children) { + this.run(child, new Set([...timeUnitFields, ...producedFields])); + } + } + } + + /** + * Remove output nodes that are not required. + */ + class RemoveUnnecessaryOutputNodes extends TopDownOptimizer { + constructor() { + super(); + } + run(node) { + if (node instanceof OutputNode && !node.isRequired()) { + this.setModified(); + node.remove(); + } + } + } + + /** + * Move parse nodes up to forks and merges them if possible. + */ + class MoveParseUp extends BottomUpOptimizer { + run(node) { + if (isDataSourceNode(node)) { + return; + } + if (node.numChildren() > 1) { + // Don't move parse further up but continue with parent. + return; + } + for (const child of node.children) { + if (child instanceof ParseNode) { + if (node instanceof ParseNode) { + this.setModified(); + node.merge(child); + } else { + // Don't swap with nodes that produce something that the parse node depends on (e.g. lookup). + if (fieldIntersection(node.producedFields(), child.dependentFields())) { + continue; + } + this.setModified(); + child.swapWithParent(); + } + } + } + return; + } + } + + /** + * Inserts an intermediate ParseNode containing all non-conflicting parse fields and removes the empty ParseNodes. + * + * We assume that dependent paths that do not have a parse node can be just merged. + */ + class MergeParse extends BottomUpOptimizer { + run(node) { + const originalChildren = [...node.children]; + const parseChildren = node.children.filter(child => child instanceof ParseNode); + if (node.numChildren() > 1 && parseChildren.length >= 1) { + const commonParse = {}; + const conflictingParse = new Set(); + for (const parseNode of parseChildren) { + const parse = parseNode.parse; + for (const k of keys(parse)) { + if (!(k in commonParse)) { + commonParse[k] = parse[k]; + } else if (commonParse[k] !== parse[k]) { + conflictingParse.add(k); + } + } + } + for (const field of conflictingParse) { + delete commonParse[field]; + } + if (!isEmpty(commonParse)) { + this.setModified(); + const mergedParseNode = new ParseNode(node, commonParse); + for (const childNode of originalChildren) { + if (childNode instanceof ParseNode) { + for (const key of keys(commonParse)) { + delete childNode.parse[key]; + } + } + node.removeChild(childNode); + childNode.parent = mergedParseNode; + + // remove empty parse nodes + if (childNode instanceof ParseNode && keys(childNode.parse).length === 0) { + childNode.remove(); + } + } + } + } + } + } + + /** + * Repeatedly remove leaf nodes that are not output or facet nodes. + * The reason is that we don't need subtrees that don't have any output nodes. + * Facet nodes are needed for the row or column domains. + */ + class RemoveUnusedSubtrees extends BottomUpOptimizer { + run(node) { + if (node instanceof OutputNode || node.numChildren() > 0 || node instanceof FacetNode) ; else if (node instanceof SourceNode) ; else { + this.setModified(); + node.remove(); + } + } + } + + /** + * Merge adjacent time unit nodes. + */ + class MergeTimeUnits extends BottomUpOptimizer { + run(node) { + const timeUnitChildren = node.children.filter(x => x instanceof TimeUnitNode); + const combination = timeUnitChildren.pop(); + for (const timeUnit of timeUnitChildren) { + this.setModified(); + combination.merge(timeUnit); + } + } + } + class MergeAggregates extends BottomUpOptimizer { + run(node) { + const aggChildren = node.children.filter(child => child instanceof AggregateNode); + + // Object which we'll use to map the fields which an aggregate is grouped by to + // the set of aggregates with that grouping. This is useful as only aggregates + // with the same group by can be merged + const groupedAggregates = {}; + + // Build groupedAggregates + for (const agg of aggChildren) { + const groupBys = hash(agg.groupBy); + if (!(groupBys in groupedAggregates)) { + groupedAggregates[groupBys] = []; + } + groupedAggregates[groupBys].push(agg); + } + + // Merge aggregateNodes with same key in groupedAggregates + for (const group of keys(groupedAggregates)) { + const mergeableAggs = groupedAggregates[group]; + if (mergeableAggs.length > 1) { + const mergedAggs = mergeableAggs.pop(); + for (const agg of mergeableAggs) { + if (mergedAggs.merge(agg)) { + node.removeChild(agg); + agg.parent = mergedAggs; + agg.remove(); + this.setModified(); + } + } + } + } + } + } + + /** + * Merge bin nodes and move them up through forks. Stop at filters, parse, identifier as we want them to stay before the bin node. + */ + class MergeBins extends BottomUpOptimizer { + constructor(model) { + super(); + this.model = model; + } + run(node) { + const moveBinsUp = !(isDataSourceNode(node) || node instanceof FilterNode || node instanceof ParseNode || node instanceof IdentifierNode); + const promotableBins = []; + const remainingBins = []; + for (const child of node.children) { + if (child instanceof BinNode) { + if (moveBinsUp && !fieldIntersection(node.producedFields(), child.dependentFields())) { + promotableBins.push(child); + } else { + remainingBins.push(child); + } + } + } + if (promotableBins.length > 0) { + const promotedBin = promotableBins.pop(); + for (const bin of promotableBins) { + promotedBin.merge(bin, this.model.renameSignal.bind(this.model)); + } + this.setModified(); + if (node instanceof BinNode) { + node.merge(promotedBin, this.model.renameSignal.bind(this.model)); + } else { + promotedBin.swapWithParent(); + } + } + if (remainingBins.length > 1) { + const remainingBin = remainingBins.pop(); + for (const bin of remainingBins) { + remainingBin.merge(bin, this.model.renameSignal.bind(this.model)); + } + this.setModified(); + } + } + } + + /** + * This optimizer takes output nodes that are at a fork and moves them before the fork. + * + * The algorithm iterates over the children and tries to find the last output node in a chain of output nodes. + * It then moves all output nodes before that main output node. All other children (and the children of the output nodes) + * are inserted after the main output node. + */ + class MergeOutputs extends BottomUpOptimizer { + run(node) { + const children = [...node.children]; + const hasOutputChild = some(children, child => child instanceof OutputNode); + if (!hasOutputChild || node.numChildren() <= 1) { + return; + } + const otherChildren = []; + + // The output node we will connect all other nodes to. + // Output nodes will be added before the new node, other nodes after. + let mainOutput; + for (const child of children) { + if (child instanceof OutputNode) { + let lastOutput = child; + while (lastOutput.numChildren() === 1) { + const [theChild] = lastOutput.children; + if (theChild instanceof OutputNode) { + lastOutput = theChild; + } else { + break; + } + } + otherChildren.push(...lastOutput.children); + if (mainOutput) { + // Move the output nodes before the mainOutput. We do this by setting + // the parent of the first not to the parent of the main output and + // the main output's parent to the last output. + + // note: the child is the first output + node.removeChild(child); + child.parent = mainOutput.parent; + mainOutput.parent.removeChild(mainOutput); + mainOutput.parent = lastOutput; + this.setModified(); + } else { + mainOutput = lastOutput; + } + } else { + otherChildren.push(child); + } + } + if (otherChildren.length) { + this.setModified(); + for (const child of otherChildren) { + child.parent.removeChild(child); + child.parent = mainOutput; + } + } + } + } + + /** + * A class for the join aggregate transform nodes. + */ + class JoinAggregateTransformNode extends DataFlowNode { + clone() { + return new JoinAggregateTransformNode(null, duplicate(this.transform)); + } + constructor(parent, transform) { + super(parent); + this.transform = transform; + } + addDimensions(fields) { + this.transform.groupby = unique(this.transform.groupby.concat(fields), d => d); + } + dependentFields() { + const out = new Set(); + if (this.transform.groupby) { + this.transform.groupby.forEach(out.add, out); + } + this.transform.joinaggregate.map(w => w.field).filter(f => f !== undefined).forEach(out.add, out); + return out; + } + producedFields() { + return new Set(this.transform.joinaggregate.map(this.getDefaultName)); + } + getDefaultName(joinAggregateFieldDef) { + return joinAggregateFieldDef.as ?? vgField(joinAggregateFieldDef); + } + hash() { + return `JoinAggregateTransform ${hash(this.transform)}`; + } + assemble() { + const fields = []; + const ops = []; + const as = []; + for (const joinaggregate of this.transform.joinaggregate) { + ops.push(joinaggregate.op); + as.push(this.getDefaultName(joinaggregate)); + fields.push(joinaggregate.field === undefined ? null : joinaggregate.field); + } + const groupby = this.transform.groupby; + return { + type: 'joinaggregate', + as, + ops, + fields, + ...(groupby !== undefined ? { + groupby + } : {}) + }; + } + } + + class FilterInvalidNode extends DataFlowNode { + clone() { + return new FilterInvalidNode(null, { + ...this.filter + }); + } + constructor(parent, filter) { + super(parent); + this.filter = filter; + } + static make(parent, model, dataSourcesForHandlingInvalidValues) { + const { + config, + markDef + } = model; + const { + marks, + scales + } = dataSourcesForHandlingInvalidValues; + if (marks === 'include-invalid-values' && scales === 'include-invalid-values') { + // If neither marks nor scale domains need data source to filter null values, then don't add the filter. + return null; + } + const filter = model.reduceFieldDef((aggregator, fieldDef, channel) => { + const scaleComponent = isScaleChannel(channel) && model.getScaleComponent(channel); + if (scaleComponent) { + const scaleType = scaleComponent.get('type'); + const { + aggregate + } = fieldDef; + const invalidDataMode = getScaleInvalidDataMode({ + scaleChannel: channel, + markDef, + config, + scaleType, + isCountAggregate: isCountingAggregateOp(aggregate) + }); + + // If the invalid data mode is include or always-valid, we don't need to filter invalid values as the scale can handle invalid values. + if (invalidDataMode !== 'show' && invalidDataMode !== 'always-valid') { + aggregator[fieldDef.field] = fieldDef; // we know that the fieldDef is a typed field def + } + } + return aggregator; + }, {}); + if (!keys(filter).length) { + return null; + } + return new FilterInvalidNode(parent, filter); + } + dependentFields() { + return new Set(keys(this.filter)); + } + producedFields() { + return new Set(); // filter does not produce any new fields + } + hash() { + return `FilterInvalid ${hash(this.filter)}`; + } + + /** + * Create the VgTransforms for each of the filtered fields. + */ + assemble() { + const filters = keys(this.filter).reduce((vegaFilters, field) => { + const fieldDef = this.filter[field]; + const ref = vgField(fieldDef, { + expr: 'datum' + }); + if (fieldDef !== null) { + if (fieldDef.type === 'temporal') { + vegaFilters.push(`(isDate(${ref}) || (${isValidFiniteNumberExpr(ref)}))`); + } else if (fieldDef.type === 'quantitative') { + vegaFilters.push(isValidFiniteNumberExpr(ref)); + } else ; + } + return vegaFilters; + }, []); + return filters.length > 0 ? { + type: 'filter', + expr: filters.join(' && ') + } : null; + } + } + function isValidFiniteNumberExpr(ref) { + return `isValid(${ref}) && isFinite(+${ref})`; + } + + function getStackByFields(model) { + return model.stack.stackBy.reduce((fields, by) => { + const fieldDef = by.fieldDef; + const _field = vgField(fieldDef); + if (_field) { + fields.push(_field); + } + return fields; + }, []); + } + function isValidAsArray(as) { + return vega.isArray(as) && as.every(s => vega.isString(s)) && as.length > 1; + } + class StackNode extends DataFlowNode { + clone() { + return new StackNode(null, duplicate(this._stack)); + } + constructor(parent, stack) { + super(parent); + this._stack = stack; + } + static makeFromTransform(parent, stackTransform) { + const { + stack, + groupby, + as, + offset = 'zero' + } = stackTransform; + const sortFields = []; + const sortOrder = []; + if (stackTransform.sort !== undefined) { + for (const sortField of stackTransform.sort) { + sortFields.push(sortField.field); + sortOrder.push(getFirstDefined(sortField.order, 'ascending')); + } + } + const sort = { + field: sortFields, + order: sortOrder + }; + let normalizedAs; + if (isValidAsArray(as)) { + normalizedAs = as; + } else if (vega.isString(as)) { + normalizedAs = [as, `${as}_end`]; + } else { + normalizedAs = [`${stackTransform.stack}_start`, `${stackTransform.stack}_end`]; + } + return new StackNode(parent, { + dimensionFieldDefs: [], + stackField: stack, + groupby, + offset, + sort, + facetby: [], + as: normalizedAs + }); + } + static makeFromEncoding(parent, model) { + const stackProperties = model.stack; + const { + encoding + } = model; + if (!stackProperties) { + return null; + } + const { + groupbyChannels, + fieldChannel, + offset, + impute + } = stackProperties; + const dimensionFieldDefs = groupbyChannels.map(groupbyChannel => { + const cDef = encoding[groupbyChannel]; + return getFieldDef(cDef); + }).filter(def => !!def); + const stackby = getStackByFields(model); + const orderDef = model.encoding.order; + let sort; + if (vega.isArray(orderDef) || isFieldDef(orderDef)) { + sort = sortParams(orderDef); + } else { + const sortOrder = isOrderOnlyDef(orderDef) ? orderDef.sort : fieldChannel === 'y' ? 'descending' : 'ascending'; + // default = descending by stackFields + // FIXME is the default here correct for binned fields? + sort = stackby.reduce((s, field) => { + if (!s.field.includes(field)) { + s.field.push(field); + s.order.push(sortOrder); + } + return s; + }, { + field: [], + order: [] + }); + } + return new StackNode(parent, { + dimensionFieldDefs, + stackField: model.vgField(fieldChannel), + facetby: [], + stackby, + sort, + offset, + impute, + as: [model.vgField(fieldChannel, { + suffix: 'start', + forAs: true + }), model.vgField(fieldChannel, { + suffix: 'end', + forAs: true + })] + }); + } + get stack() { + return this._stack; + } + addDimensions(fields) { + this._stack.facetby.push(...fields); + } + dependentFields() { + const out = new Set(); + out.add(this._stack.stackField); + this.getGroupbyFields().forEach(out.add, out); + this._stack.facetby.forEach(out.add, out); + this._stack.sort.field.forEach(out.add, out); + return out; + } + producedFields() { + return new Set(this._stack.as); + } + hash() { + return `Stack ${hash(this._stack)}`; + } + getGroupbyFields() { + const { + dimensionFieldDefs, + impute, + groupby + } = this._stack; + if (dimensionFieldDefs.length > 0) { + return dimensionFieldDefs.map(dimensionFieldDef => { + if (dimensionFieldDef.bin) { + if (impute) { + // For binned group by field with impute, we calculate bin_mid + // as we cannot impute two fields simultaneously + return [vgField(dimensionFieldDef, { + binSuffix: 'mid' + })]; + } + return [ + // For binned group by field without impute, we need both bin (start) and bin_end + vgField(dimensionFieldDef, {}), vgField(dimensionFieldDef, { + binSuffix: 'end' + })]; + } + return [vgField(dimensionFieldDef)]; + }).flat(); + } + return groupby ?? []; + } + assemble() { + const transform = []; + const { + facetby, + dimensionFieldDefs, + stackField: field, + stackby, + sort, + offset, + impute, + as + } = this._stack; + + // Impute + if (impute) { + for (const dimensionFieldDef of dimensionFieldDefs) { + const { + bandPosition = 0.5, + bin + } = dimensionFieldDef; + if (bin) { + // As we can only impute one field at a time, we need to calculate + // mid point for a binned field + + const binStart = vgField(dimensionFieldDef, { + expr: 'datum' + }); + const binEnd = vgField(dimensionFieldDef, { + expr: 'datum', + binSuffix: 'end' + }); + transform.push({ + type: 'formula', + expr: `${isValidFiniteNumberExpr(binStart)} ? ${bandPosition}*${binStart}+${1 - bandPosition}*${binEnd} : ${binStart}`, + as: vgField(dimensionFieldDef, { + binSuffix: 'mid', + forAs: true + }) + }); + } + transform.push({ + type: 'impute', + field, + groupby: [...stackby, ...facetby], + key: vgField(dimensionFieldDef, { + binSuffix: 'mid' + }), + method: 'value', + value: 0 + }); + } + } + + // Stack + transform.push({ + type: 'stack', + groupby: [...this.getGroupbyFields(), ...facetby], + field, + sort, + as, + offset + }); + return transform; + } + } + + /** + * A class for the window transform nodes + */ + class WindowTransformNode extends DataFlowNode { + clone() { + return new WindowTransformNode(null, duplicate(this.transform)); + } + constructor(parent, transform) { + super(parent); + this.transform = transform; + } + addDimensions(fields) { + this.transform.groupby = unique(this.transform.groupby.concat(fields), d => d); + } + dependentFields() { + const out = new Set(); + (this.transform.groupby ?? []).forEach(out.add, out); + (this.transform.sort ?? []).forEach(m => out.add(m.field)); + this.transform.window.map(w => w.field).filter(f => f !== undefined).forEach(out.add, out); + return out; + } + producedFields() { + return new Set(this.transform.window.map(this.getDefaultName)); + } + getDefaultName(windowFieldDef) { + return windowFieldDef.as ?? vgField(windowFieldDef); + } + hash() { + return `WindowTransform ${hash(this.transform)}`; + } + assemble() { + const fields = []; + const ops = []; + const as = []; + const params = []; + for (const window of this.transform.window) { + ops.push(window.op); + as.push(this.getDefaultName(window)); + params.push(window.param === undefined ? null : window.param); + fields.push(window.field === undefined ? null : window.field); + } + const frame = this.transform.frame; + const groupby = this.transform.groupby; + if (frame && frame[0] === null && frame[1] === null && ops.every(o => isAggregateOp(o))) { + // when the window does not rely on any particular window ops or frame, switch to a simpler and more efficient joinaggregate + return { + type: 'joinaggregate', + as, + ops: ops, + fields, + ...(groupby !== undefined ? { + groupby + } : {}) + }; + } + const sortFields = []; + const sortOrder = []; + if (this.transform.sort !== undefined) { + for (const sortField of this.transform.sort) { + sortFields.push(sortField.field); + sortOrder.push(sortField.order ?? 'ascending'); + } + } + const sort = { + field: sortFields, + order: sortOrder + }; + const ignorePeers = this.transform.ignorePeers; + return { + type: 'window', + params, + as, + ops, + fields, + sort, + ...(ignorePeers !== undefined ? { + ignorePeers + } : {}), + ...(groupby !== undefined ? { + groupby + } : {}), + ...(frame !== undefined ? { + frame + } : {}) + }; + } + } + + /** + * Clones the subtree and ignores output nodes except for the leaves, which are renamed. + */ + function cloneSubtree(facet) { + function clone(node) { + if (!(node instanceof FacetNode)) { + const copy = node.clone(); + if (copy instanceof OutputNode) { + const newName = FACET_SCALE_PREFIX + copy.getSource(); + copy.setSource(newName); + facet.model.component.data.outputNodes[newName] = copy; + } else if (copy instanceof AggregateNode || copy instanceof StackNode || copy instanceof WindowTransformNode || copy instanceof JoinAggregateTransformNode) { + copy.addDimensions(facet.fields); + } + for (const n of node.children.flatMap(clone)) { + n.parent = copy; + } + return [copy]; + } + return node.children.flatMap(clone); + } + return clone; + } + + /** + * Move facet nodes down to the next fork or output node. Also pull the main output with the facet node. + * After moving down the facet node, make a copy of the subtree and make it a child of the main output. + */ + function moveFacetDown(node) { + if (node instanceof FacetNode) { + if (node.numChildren() === 1 && !(node.children[0] instanceof OutputNode)) { + // move down until we hit a fork or output node + const child = node.children[0]; + if (child instanceof AggregateNode || child instanceof StackNode || child instanceof WindowTransformNode || child instanceof JoinAggregateTransformNode) { + child.addDimensions(node.fields); + } + child.swapWithParent(); + moveFacetDown(node); + } else { + // move main to facet + + const facetMain = node.model.component.data.main; + moveMainDownToFacet(facetMain); + + // replicate the subtree and place it before the facet's main node + const cloner = cloneSubtree(node); + const copy = node.children.map(cloner).flat(); + for (const c of copy) { + c.parent = facetMain; + } + } + } else { + node.children.map(moveFacetDown); + } + } + function moveMainDownToFacet(node) { + if (node instanceof OutputNode && node.type === DataSourceType.Main) { + if (node.numChildren() === 1) { + const child = node.children[0]; + if (!(child instanceof FacetNode)) { + child.swapWithParent(); + moveMainDownToFacet(node); + } + } + } + } + + const FACET_SCALE_PREFIX = 'scale_'; + const MAX_OPTIMIZATION_RUNS = 5; + + /** + * Iterates over a dataflow graph and checks whether all links are consistent. + */ + function checkLinks(nodes) { + for (const node of nodes) { + for (const child of node.children) { + if (child.parent !== node) { + // log.error('Dataflow graph is inconsistent.', node, child); + return false; + } + } + if (!checkLinks(node.children)) { + return false; + } + } + return true; + } + + /** + * Run the specified optimizer on the provided nodes. + * + * @param optimizer The optimizer instance to run. + * @param nodes A set of nodes to optimize. + */ + function runOptimizer(optimizer, nodes) { + let modified = false; + for (const node of nodes) { + modified = optimizer.optimize(node) || modified; + } + return modified; + } + function optimizationDataflowHelper(dataComponent, model, firstPass) { + let roots = dataComponent.sources; + let modified = false; + modified = runOptimizer(new RemoveUnnecessaryOutputNodes(), roots) || modified; + modified = runOptimizer(new RemoveUnnecessaryIdentifierNodes(model), roots) || modified; + + // remove source nodes that don't have any children because they also don't have output nodes + roots = roots.filter(r => r.numChildren() > 0); + modified = runOptimizer(new RemoveUnusedSubtrees(), roots) || modified; + roots = roots.filter(r => r.numChildren() > 0); + if (!firstPass) { + // Only run these optimizations after the optimizer has moved down the facet node. + // With this change, we can be more aggressive in the optimizations. + modified = runOptimizer(new MoveParseUp(), roots) || modified; + modified = runOptimizer(new MergeBins(model), roots) || modified; + modified = runOptimizer(new RemoveDuplicateTimeUnits(), roots) || modified; + modified = runOptimizer(new MergeParse(), roots) || modified; + modified = runOptimizer(new MergeAggregates(), roots) || modified; + modified = runOptimizer(new MergeTimeUnits(), roots) || modified; + modified = runOptimizer(new MergeIdenticalNodes(), roots) || modified; + modified = runOptimizer(new MergeOutputs(), roots) || modified; + } + dataComponent.sources = roots; + return modified; + } + + /** + * Optimizes the dataflow of the passed in data component. + */ + function optimizeDataflow(data, model) { + // check before optimizations + checkLinks(data.sources); + let firstPassCounter = 0; + let secondPassCounter = 0; + for (let i = 0; i < MAX_OPTIMIZATION_RUNS; i++) { + if (!optimizationDataflowHelper(data, model, true)) { + break; + } + firstPassCounter++; + } + + // move facets down and make a copy of the subtree so that we can have scales at the top level + data.sources.map(moveFacetDown); + for (let i = 0; i < MAX_OPTIMIZATION_RUNS; i++) { + if (!optimizationDataflowHelper(data, model, false)) { + break; + } + secondPassCounter++; + } + + // check after optimizations + checkLinks(data.sources); + if (Math.max(firstPassCounter, secondPassCounter) === MAX_OPTIMIZATION_RUNS) { + warn(`Maximum optimization runs(${MAX_OPTIMIZATION_RUNS}) reached.`); + } + } + + /** + * A class that behaves like a SignalRef but lazily generates the signal. + * The provided generator function should use `Model.getSignalName` to use the correct signal name. + */ + class SignalRefWrapper { + constructor(exprGenerator) { + Object.defineProperty(this, 'signal', { + enumerable: true, + get: exprGenerator + }); + } + // for ts + + static fromName(rename, signalName) { + return new SignalRefWrapper(() => rename(signalName)); + } + } + + function parseScaleDomain(model) { + if (isUnitModel(model)) { + parseUnitScaleDomain(model); + } else { + parseNonUnitScaleDomain(model); + } + } + function parseUnitScaleDomain(model) { + const localScaleComponents = model.component.scales; + for (const channel of keys(localScaleComponents)) { + const domains = parseDomainForChannel(model, channel); + const localScaleCmpt = localScaleComponents[channel]; + localScaleCmpt.setWithExplicit('domains', domains); + parseSelectionDomain(model, channel); + if (model.component.data.isFaceted) { + // get resolve from closest facet parent as this decides whether we need to refer to cloned subtree or not + let facetParent = model; + while (!isFacetModel(facetParent) && facetParent.parent) { + facetParent = facetParent.parent; + } + const resolve = facetParent.component.resolve.scale[channel]; + if (resolve === 'shared') { + for (const domain of domains.value) { + // Replace the scale domain with data output from a cloned subtree after the facet. + if (isDataRefDomain(domain)) { + // use data from cloned subtree (which is the same as data but with a prefix added once) + domain.data = FACET_SCALE_PREFIX + domain.data.replace(FACET_SCALE_PREFIX, ''); + } + } + } + } + } + } + function parseNonUnitScaleDomain(model) { + for (const child of model.children) { + parseScaleDomain(child); + } + const localScaleComponents = model.component.scales; + for (const channel of keys(localScaleComponents)) { + let domains; + let selectionExtent = null; + for (const child of model.children) { + const childComponent = child.component.scales[channel]; + if (childComponent) { + if (domains === undefined) { + domains = childComponent.getWithExplicit('domains'); + } else { + domains = mergeValuesWithExplicit(domains, childComponent.getWithExplicit('domains'), 'domains', 'scale', domainsTieBreaker); + } + const se = childComponent.get('selectionExtent'); + if (selectionExtent && se && selectionExtent.param !== se.param) { + warn(NEEDS_SAME_SELECTION); + } + selectionExtent = se; + } + } + localScaleComponents[channel].setWithExplicit('domains', domains); + if (selectionExtent) { + localScaleComponents[channel].set('selectionExtent', selectionExtent, true); + } + } + } + + /** + * Remove unaggregated domain if it is not applicable + * Add unaggregated domain if domain is not specified and config.scale.useUnaggregatedDomain is true. + */ + function normalizeUnaggregatedDomain(domain, fieldDef, scaleType, scaleConfig) { + if (domain === 'unaggregated') { + const { + valid, + reason + } = canUseUnaggregatedDomain(fieldDef, scaleType); + if (!valid) { + warn(reason); + return undefined; + } + } else if (domain === undefined && scaleConfig.useUnaggregatedDomain) { + // Apply config if domain is not specified. + const { + valid + } = canUseUnaggregatedDomain(fieldDef, scaleType); + if (valid) { + return 'unaggregated'; + } + } + return domain; + } + function parseDomainForChannel(model, channel) { + const scaleType = model.getScaleComponent(channel).get('type'); + const { + encoding + } = model; + const domain = normalizeUnaggregatedDomain(model.scaleDomain(channel), model.typedFieldDef(channel), scaleType, model.config.scale); + if (domain !== model.scaleDomain(channel)) { + model.specifiedScales[channel] = { + ...model.specifiedScales[channel], + domain + }; + } + + // If channel is either X or Y then union them with X2 & Y2 if they exist + if (channel === 'x' && getFieldOrDatumDef(encoding.x2)) { + if (getFieldOrDatumDef(encoding.x)) { + return mergeValuesWithExplicit(parseSingleChannelDomain(scaleType, domain, model, 'x'), parseSingleChannelDomain(scaleType, domain, model, 'x2'), 'domain', 'scale', domainsTieBreaker); + } else { + return parseSingleChannelDomain(scaleType, domain, model, 'x2'); + } + } else if (channel === 'y' && getFieldOrDatumDef(encoding.y2)) { + if (getFieldOrDatumDef(encoding.y)) { + return mergeValuesWithExplicit(parseSingleChannelDomain(scaleType, domain, model, 'y'), parseSingleChannelDomain(scaleType, domain, model, 'y2'), 'domain', 'scale', domainsTieBreaker); + } else { + return parseSingleChannelDomain(scaleType, domain, model, 'y2'); + } + } + return parseSingleChannelDomain(scaleType, domain, model, channel); + } + function mapDomainToDataSignal(domain, type, timeUnit) { + return domain.map(v => { + const data = valueExpr(v, { + timeUnit, + type + }); + return { + signal: `{data: ${data}}` + }; + }); + } + function convertDomainIfItIsDateTime(domain, type, timeUnit) { + // explicit value + const normalizedTimeUnit = normalizeTimeUnit(timeUnit)?.unit; + if (type === 'temporal' || normalizedTimeUnit) { + return mapDomainToDataSignal(domain, type, normalizedTimeUnit); + } + return [domain]; // Date time won't make sense + } + function parseSingleChannelDomain(scaleType, domain, model, channel) { + const { + encoding, + markDef, + mark, + config, + stack + } = model; + const fieldOrDatumDef = getFieldOrDatumDef(encoding[channel]); + const { + type + } = fieldOrDatumDef; + const timeUnit = fieldOrDatumDef['timeUnit']; + const dataSourceTypeForScaleDomain = getScaleDataSourceForHandlingInvalidValues({ + invalid: getMarkConfig('invalid', markDef, config), + isPath: isPathMark(mark) + }); + if (isDomainUnionWith(domain)) { + const defaultDomain = parseSingleChannelDomain(scaleType, undefined, model, channel); + const unionWith = convertDomainIfItIsDateTime(domain.unionWith, type, timeUnit); + return makeExplicit([...unionWith, ...defaultDomain.value]); + } else if (isSignalRef(domain)) { + return makeExplicit([domain]); + } else if (domain && domain !== 'unaggregated' && !isParameterDomain(domain)) { + return makeExplicit(convertDomainIfItIsDateTime(domain, type, timeUnit)); + } + if (stack && channel === stack.fieldChannel) { + if (stack.offset === 'normalize') { + return makeImplicit([[0, 1]]); + } + const data = model.requestDataName(dataSourceTypeForScaleDomain); + return makeImplicit([{ + data, + field: model.vgField(channel, { + suffix: 'start' + }) + }, { + data, + field: model.vgField(channel, { + suffix: 'end' + }) + }]); + } + const sort = isScaleChannel(channel) && isFieldDef(fieldOrDatumDef) ? domainSort(model, channel, scaleType) : undefined; + if (isDatumDef(fieldOrDatumDef)) { + const d = convertDomainIfItIsDateTime([fieldOrDatumDef.datum], type, timeUnit); + return makeImplicit(d); + } + const fieldDef = fieldOrDatumDef; // now we can be sure it's a fieldDef + if (domain === 'unaggregated') { + const { + field + } = fieldOrDatumDef; + return makeImplicit([{ + data: model.requestDataName(dataSourceTypeForScaleDomain), + field: vgField({ + field, + aggregate: 'min' + }) + }, { + data: model.requestDataName(dataSourceTypeForScaleDomain), + field: vgField({ + field, + aggregate: 'max' + }) + }]); + } else if (isBinning(fieldDef.bin)) { + if (hasDiscreteDomain(scaleType)) { + if (scaleType === 'bin-ordinal') { + // we can omit the domain as it is inferred from the `bins` property + return makeImplicit([]); + } + + // ordinal bin scale takes domain from bin_range, ordered by bin start + // This is useful for both axis-based scale (x/y) and legend-based scale (other channels). + return makeImplicit([{ + // If sort by aggregation of a specified sort field, we need to use RAW table, + // so we can aggregate values for the scale independently from the main aggregation. + data: isBoolean(sort) ? model.requestDataName(dataSourceTypeForScaleDomain) : model.requestDataName(DataSourceType.Raw), + // Use range if we added it and the scale does not support computing a range as a signal. + field: model.vgField(channel, binRequiresRange(fieldDef, channel) ? { + binSuffix: 'range' + } : {}), + // we have to use a sort object if sort = true to make the sort correct by bin start + sort: sort === true || !vega.isObject(sort) ? { + field: model.vgField(channel, {}), + op: 'min' // min or max doesn't matter since we sort by the start of the bin range + } : sort + }]); + } else { + // continuous scales + const { + bin + } = fieldDef; + if (isBinning(bin)) { + const binSignal = getBinSignalName(model, fieldDef.field, bin); + return makeImplicit([new SignalRefWrapper(() => { + const signal = model.getSignalName(binSignal); + return `[${signal}.start, ${signal}.stop]`; + })]); + } else { + return makeImplicit([{ + data: model.requestDataName(dataSourceTypeForScaleDomain), + field: model.vgField(channel, {}) + }]); + } + } + } else if (fieldDef.timeUnit && contains(['time', 'utc'], scaleType)) { + const fieldDef2 = encoding[getSecondaryRangeChannel(channel)]; + if (hasBandEnd(fieldDef, fieldDef2, markDef, config)) { + const data = model.requestDataName(dataSourceTypeForScaleDomain); + const bandPosition = getBandPosition({ + fieldDef, + fieldDef2, + markDef, + config + }); + const isRectWithOffset = isRectBasedMark(mark) && bandPosition !== 0.5 && isXorY(channel); + return makeImplicit([{ + data, + field: model.vgField(channel, isRectWithOffset ? { + suffix: OFFSETTED_RECT_START_SUFFIX + } : {}) + }, { + data, + field: model.vgField(channel, { + suffix: isRectWithOffset ? OFFSETTED_RECT_END_SUFFIX : 'end' + }) + }]); + } + } + if (sort) { + return makeImplicit([{ + // If sort by aggregation of a specified sort field, we need to use RAW table, + // so we can aggregate values for the scale independently from the main aggregation. + data: isBoolean(sort) ? model.requestDataName(dataSourceTypeForScaleDomain) : model.requestDataName(DataSourceType.Raw), + field: model.vgField(channel), + sort + }]); + } else { + return makeImplicit([{ + data: model.requestDataName(dataSourceTypeForScaleDomain), + field: model.vgField(channel) + }]); + } + } + function normalizeSortField(sort, isStackedMeasure) { + const { + op, + field, + order + } = sort; + return { + // Apply default op + op: op ?? (isStackedMeasure ? 'sum' : DEFAULT_SORT_OP), + // flatten nested fields + ...(field ? { + field: replacePathInField(field) + } : {}), + ...(order ? { + order + } : {}) + }; + } + function parseSelectionDomain(model, channel) { + const scale = model.component.scales[channel]; + const spec = model.specifiedScales[channel].domain; + const bin = model.fieldDef(channel)?.bin; + const domain = isParameterDomain(spec) && spec; + const extent = isBinParams(bin) && isParameterExtent(bin.extent) && bin.extent; + if (domain || extent) { + // As scale parsing occurs before selection parsing, we cannot set + // domainRaw directly. So instead, we store the selectionExtent on + // the scale component, and then add domainRaw during scale assembly. + scale.set('selectionExtent', domain ?? extent, true); + } + } + function domainSort(model, channel, scaleType) { + if (!hasDiscreteDomain(scaleType)) { + return undefined; + } + + // save to cast as the only exception is the geojson type for shape, which would not generate a scale + const fieldDef = model.fieldDef(channel); + const sort = fieldDef.sort; + + // if the sort is specified with array, use the derived sort index field + if (isSortArray(sort)) { + return { + op: 'min', + field: sortArrayIndexField(fieldDef, channel), + order: 'ascending' + }; + } + const { + stack + } = model; + const stackDimensions = stack ? new Set([...stack.groupbyFields, ...stack.stackBy.map(s => s.fieldDef.field)]) : undefined; + + // Sorted based on an aggregate calculation over a specified sort field (only for ordinal scale) + if (isSortField(sort)) { + const isStackedMeasure = stack && !stackDimensions.has(sort.field); + return normalizeSortField(sort, isStackedMeasure); + } else if (isSortByEncoding(sort)) { + const { + encoding, + order + } = sort; + const fieldDefToSortBy = model.fieldDef(encoding); + const { + aggregate, + field + } = fieldDefToSortBy; + const isStackedMeasure = stack && !stackDimensions.has(field); + if (isArgminDef(aggregate) || isArgmaxDef(aggregate)) { + return normalizeSortField({ + field: vgField(fieldDefToSortBy), + order + }, isStackedMeasure); + } else if (isAggregateOp(aggregate) || !aggregate) { + return normalizeSortField({ + op: aggregate, + // can't be argmin/argmax since we don't support them in encoding field def + field, + order + }, isStackedMeasure); + } + } else if (sort === 'descending') { + return { + op: 'min', + field: model.vgField(channel), + order: 'descending' + }; + } else if (contains(['ascending', undefined /* default =ascending*/], sort)) { + return true; + } + + // sort == null + return undefined; + } + + /** + * Determine if a scale can use unaggregated domain. + * @return {Boolean} Returns true if all of the following conditions apply: + * 1. `scale.domain` is `unaggregated` + * 2. Aggregation function is not `count` or `sum` + * 3. The scale is quantitative or time scale. + */ + function canUseUnaggregatedDomain(fieldDef, scaleType) { + const { + aggregate, + type + } = fieldDef; + if (!aggregate) { + return { + valid: false, + reason: unaggregateDomainHasNoEffectForRawField(fieldDef) + }; + } + if (vega.isString(aggregate) && !SHARED_DOMAIN_OPS.has(aggregate)) { + return { + valid: false, + reason: unaggregateDomainWithNonSharedDomainOp(aggregate) + }; + } + if (type === 'quantitative') { + if (scaleType === 'log') { + return { + valid: false, + reason: unaggregatedDomainWithLogScale(fieldDef) + }; + } + } + return { + valid: true + }; + } + + /** + * Tie breaker for mergeValuesWithExplicit for domains. We concat the specified values. + */ + function domainsTieBreaker(v1, v2, property, propertyOf) { + if (v1.explicit && v2.explicit) { + warn(mergeConflictingDomainProperty(property, propertyOf, v1.value, v2.value)); + } + // If equal score, concat the domains so that we union them later. + return { + explicit: v1.explicit, + value: [...v1.value, ...v2.value] + }; + } + + /** + * Converts an array of domains to a single Vega scale domain. + */ + function mergeDomains(domains) { + const uniqueDomains = unique(domains.map(domain => { + // ignore sort property when computing the unique domains + if (isDataRefDomain(domain)) { + const { + sort: _s, + ...domainWithoutSort + } = domain; + return domainWithoutSort; + } + return domain; + }), hash); + const sorts = unique(domains.map(d => { + if (isDataRefDomain(d)) { + const s = d.sort; + if (s !== undefined && !isBoolean(s)) { + if ('op' in s && s.op === 'count') { + // let's make sure that if op is count, we don't use a field + delete s.field; + } + if (s.order === 'ascending') { + // drop order: ascending as it is the default + delete s.order; + } + } + return s; + } + return undefined; + }).filter(s => s !== undefined), hash); + if (uniqueDomains.length === 0) { + return undefined; + } else if (uniqueDomains.length === 1) { + const domain = domains[0]; + if (isDataRefDomain(domain) && sorts.length > 0) { + let sort = sorts[0]; + if (sorts.length > 1) { + warn(MORE_THAN_ONE_SORT); + // Get sorts with non-default ops + const filteredSorts = sorts.filter(s => vega.isObject(s) && 'op' in s && s.op !== 'min'); + if (sorts.every(s => vega.isObject(s) && 'op' in s) && filteredSorts.length === 1) { + sort = filteredSorts[0]; + } else { + sort = true; + } + } else { + // Simplify domain sort by removing field and op when the field is the same as the domain field. + if (vega.isObject(sort) && 'field' in sort) { + const sortField = sort.field; + if (domain.field === sortField) { + sort = sort.order ? { + order: sort.order + } : true; + } + } + } + return { + ...domain, + sort + }; + } + return domain; + } + + // only keep sort properties that work with unioned domains + const unionDomainSorts = unique(sorts.map(s => { + if (isBoolean(s) || !('op' in s) || vega.isString(s.op) && s.op in MULTIDOMAIN_SORT_OP_INDEX) { + return s; + } + warn(domainSortDropped(s)); + return true; + }), hash); + let sort; + if (unionDomainSorts.length === 1) { + sort = unionDomainSorts[0]; + } else if (unionDomainSorts.length > 1) { + warn(MORE_THAN_ONE_SORT); + sort = true; + } + const allData = unique(domains.map(d => { + if (isDataRefDomain(d)) { + return d.data; + } + return null; + }), x => x); + if (allData.length === 1 && allData[0] !== null) { + // create a union domain of different fields with a single data source + const domain = { + data: allData[0], + fields: uniqueDomains.map(d => d.field), + ...(sort ? { + sort + } : {}) + }; + return domain; + } + return { + fields: uniqueDomains, + ...(sort ? { + sort + } : {}) + }; + } + + /** + * Return a field if a scale uses a single field. + * Return `undefined` otherwise. + */ + function getFieldFromDomain(domain) { + if (isDataRefDomain(domain) && vega.isString(domain.field)) { + return domain.field; + } else if (isDataRefUnionedDomain(domain)) { + let field; + for (const nonUnionDomain of domain.fields) { + if (isDataRefDomain(nonUnionDomain) && vega.isString(nonUnionDomain.field)) { + if (!field) { + field = nonUnionDomain.field; + } else if (field !== nonUnionDomain.field) { + warn(FACETED_INDEPENDENT_DIFFERENT_SOURCES); + return field; + } + } + } + warn(FACETED_INDEPENDENT_SAME_FIELDS_DIFFERENT_SOURCES); + return field; + } else if (isFieldRefUnionDomain(domain)) { + warn(FACETED_INDEPENDENT_SAME_SOURCE); + const field = domain.fields[0]; + return vega.isString(field) ? field : undefined; + } + return undefined; + } + function assembleDomain(model, channel) { + const scaleComponent = model.component.scales[channel]; + const domains = scaleComponent.get('domains').map(domain => { + // Correct references to data as the original domain's data was determined + // in parseScale, which happens before parseData. Thus the original data + // reference can be incorrect. + if (isDataRefDomain(domain)) { + domain.data = model.lookupDataSource(domain.data); + } + return domain; + }); + + // domains is an array that has to be merged into a single vega domain + return mergeDomains(domains); + } + + function assembleScales(model) { + if (isLayerModel(model) || isConcatModel(model)) { + // For concat and layer, include scales of children too + return model.children.reduce((scales, child) => { + return scales.concat(assembleScales(child)); + }, assembleScalesForModel(model)); + } else { + // For facet, child scales would not be included in the parent's scope. + // For unit, there is no child. + return assembleScalesForModel(model); + } + } + function assembleScalesForModel(model) { + return keys(model.component.scales).reduce((scales, channel) => { + const scaleComponent = model.component.scales[channel]; + if (scaleComponent.merged) { + // Skipped merged scales + return scales; + } + const scale = scaleComponent.combine(); + const { + name, + type, + selectionExtent, + domains: _d, + range: _r, + reverse, + ...otherScaleProps + } = scale; + const range = assembleScaleRange(scale.range, name, channel, model); + const domain = assembleDomain(model, channel); + const domainRaw = selectionExtent ? assembleSelectionScaleDomain(model, selectionExtent, scaleComponent, domain) : null; + scales.push({ + name, + type, + ...(domain ? { + domain + } : {}), + ...(domainRaw ? { + domainRaw + } : {}), + range, + ...(reverse !== undefined ? { + reverse: reverse + } : {}), + ...otherScaleProps + }); + return scales; + }, []); + } + function assembleScaleRange(scaleRange, scaleName, channel, model) { + // add signals to x/y range + if (isXorY(channel)) { + if (isVgRangeStep(scaleRange)) { + // For width/height step, use a signal created in layout assemble instead of a constant step. + return { + step: { + signal: `${scaleName}_step` + } + }; + } + } else if (vega.isObject(scaleRange) && isDataRefDomain(scaleRange)) { + return { + ...scaleRange, + data: model.lookupDataSource(scaleRange.data) + }; + } + return scaleRange; + } + + /** + * All VgDomain property except domain. + * (We exclude domain as we have a special "domains" array that allow us merge them all at once in assemble.) + */ + + class ScaleComponent extends Split { + merged = false; + constructor(name, typeWithExplicit) { + super({}, + // no initial explicit property + { + name + } // name as initial implicit property + ); + this.setWithExplicit('type', typeWithExplicit); + } + + /** + * Whether the scale definitely includes or not include zero in the domain + */ + domainHasZero() { + const scaleType = this.get('type'); + if (contains([ScaleType.LOG, ScaleType.TIME, ScaleType.UTC], scaleType)) { + // Log scales cannot have zero. + // Zero in time scale is arbitrary, and does not affect ratio. + // (Time is an interval level of measurement, not ratio). + // See https://en.wikipedia.org/wiki/Level_of_measurement for more info. + return 'definitely-not'; + } + const scaleZero = this.get('zero'); + if (scaleZero === true || + // If zero is undefined, linear/sqrt/pow scales have zero by default. + scaleZero === undefined && contains([ScaleType.LINEAR, ScaleType.SQRT, ScaleType.POW], scaleType)) { + return 'definitely'; + } + const domains = this.get('domains'); + if (domains.length > 0) { + let hasExplicitDomainWithZero = false; + let hasExplicitDomainWithoutZero = false; + let hasDomainBasedOnField = false; + for (const d of domains) { + if (vega.isArray(d)) { + const first = d[0]; + const last = d[d.length - 1]; + if (vega.isNumber(first) && vega.isNumber(last)) { + if (first <= 0 && last >= 0) { + hasExplicitDomainWithZero = true; + continue; + } else { + hasExplicitDomainWithoutZero = true; + continue; + } + } + } + hasDomainBasedOnField = true; + } + if (hasExplicitDomainWithZero) { + return 'definitely'; + } else if (hasExplicitDomainWithoutZero && !hasDomainBasedOnField) { + return 'definitely-not'; + } + } + return 'maybe'; + } + } + + const RANGE_PROPERTIES = ['range', 'scheme']; + function parseUnitScaleRange(model) { + const localScaleComponents = model.component.scales; + + // use SCALE_CHANNELS instead of scales[channel] to ensure that x, y come first! + for (const channel of SCALE_CHANNELS) { + const localScaleCmpt = localScaleComponents[channel]; + if (!localScaleCmpt) { + continue; + } + const rangeWithExplicit = parseRangeForChannel(channel, model); + localScaleCmpt.setWithExplicit('range', rangeWithExplicit); + } + } + function getBinStepSignal(model, channel) { + const fieldDef = model.fieldDef(channel); + if (fieldDef?.bin) { + const { + bin, + field + } = fieldDef; + const sizeType = getSizeChannel(channel); + const sizeSignal = model.getName(sizeType); + if (vega.isObject(bin) && bin.binned && bin.step !== undefined) { + return new SignalRefWrapper(() => { + const scaleName = model.scaleName(channel); + const binCount = `(domain("${scaleName}")[1] - domain("${scaleName}")[0]) / ${bin.step}`; + return `${model.getSignalName(sizeSignal)} / (${binCount})`; + }); + } else if (isBinning(bin)) { + const binSignal = getBinSignalName(model, field, bin); + + // TODO: extract this to be range step signal + return new SignalRefWrapper(() => { + const updatedName = model.getSignalName(binSignal); + const binCount = `(${updatedName}.stop - ${updatedName}.start) / ${updatedName}.step`; + return `${model.getSignalName(sizeSignal)} / (${binCount})`; + }); + } + } + return undefined; + } + + /** + * Return mixins that includes one of the Vega range types (explicit range, range.step, range.scheme). + */ + function parseRangeForChannel(channel, model) { + const specifiedScale = model.specifiedScales[channel]; + const { + size + } = model; + const mergedScaleCmpt = model.getScaleComponent(channel); + const scaleType = mergedScaleCmpt.get('type'); + + // Check if any of the range properties is specified. + // If so, check if it is compatible and make sure that we only output one of the properties + for (const property of RANGE_PROPERTIES) { + if (specifiedScale[property] !== undefined) { + const supportedByScaleType = scaleTypeSupportProperty(scaleType, property); + const channelIncompatability = channelScalePropertyIncompatability(channel, property); + if (!supportedByScaleType) { + warn(scalePropertyNotWorkWithScaleType(scaleType, property, channel)); + } else if (channelIncompatability) { + // channel + warn(channelIncompatability); + } else { + switch (property) { + case 'range': + { + const range = specifiedScale.range; + if (vega.isArray(range)) { + if (isXorY(channel)) { + return makeExplicit(range.map(v => { + if (v === 'width' || v === 'height') { + // get signal for width/height + + // Just like default range logic below, we use SignalRefWrapper to account for potential merges and renames. + + const sizeSignal = model.getName(v); + const getSignalName = model.getSignalName.bind(model); + return SignalRefWrapper.fromName(getSignalName, sizeSignal); + } + return v; + })); + } + } else if (vega.isObject(range)) { + return makeExplicit({ + data: model.requestDataName(DataSourceType.Main), + field: range.field, + sort: { + op: 'min', + field: model.vgField(channel) + } + }); + } + return makeExplicit(range); + } + case 'scheme': + return makeExplicit(parseScheme(specifiedScale[property])); + } + } + } + } + const sizeChannel = channel === X || channel === 'xOffset' ? 'width' : 'height'; + const sizeValue = size[sizeChannel]; + if (isStep(sizeValue)) { + if (isXorY(channel)) { + if (hasDiscreteDomain(scaleType)) { + const step = getPositionStep(sizeValue, model, channel); + // Need to be explicit so layer with step wins over layer without step + if (step) { + return makeExplicit({ + step + }); + } + } else { + warn(stepDropped(sizeChannel)); + } + } else if (isXorYOffset(channel)) { + const positionChannel = channel === XOFFSET ? 'x' : 'y'; + const positionScaleCmpt = model.getScaleComponent(positionChannel); + const positionScaleType = positionScaleCmpt.get('type'); + if (positionScaleType === 'band') { + const step = getOffsetStep(sizeValue, scaleType); + if (step) { + return makeExplicit(step); + } + } + } + } + const { + rangeMin, + rangeMax + } = specifiedScale; + const d = defaultRange(channel, model); + if ((rangeMin !== undefined || rangeMax !== undefined) && + // it's ok to check just rangeMin's compatibility since rangeMin/rangeMax are the same + scaleTypeSupportProperty(scaleType, 'rangeMin') && vega.isArray(d) && d.length === 2) { + return makeExplicit([rangeMin ?? d[0], rangeMax ?? d[1]]); + } + return makeImplicit(d); + } + function parseScheme(scheme) { + if (isExtendedScheme(scheme)) { + return { + scheme: scheme.name, + ...omit(scheme, ['name']) + }; + } + return { + scheme + }; + } + function fullWidthOrHeightRange(channel, model, scaleType) { + let { + center + } = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + // If step is null, use zero to width or height. + // Note that we use SignalRefWrapper to account for potential merges and renames. + const sizeType = getSizeChannel(channel); + const sizeSignal = model.getName(sizeType); + const getSignalName = model.getSignalName.bind(model); + if (channel === Y && hasContinuousDomain(scaleType)) { + // For y continuous scale, we have to start from the height as the bottom part has the max value. + return center ? [SignalRefWrapper.fromName(name => `${getSignalName(name)}/2`, sizeSignal), SignalRefWrapper.fromName(name => `-${getSignalName(name)}/2`, sizeSignal)] : [SignalRefWrapper.fromName(getSignalName, sizeSignal), 0]; + } else { + return center ? [SignalRefWrapper.fromName(name => `-${getSignalName(name)}/2`, sizeSignal), SignalRefWrapper.fromName(name => `${getSignalName(name)}/2`, sizeSignal)] : [0, SignalRefWrapper.fromName(getSignalName, sizeSignal)]; + } + } + function defaultRange(channel, model) { + const { + size, + config, + mark, + encoding + } = model; + const { + type + } = getFieldOrDatumDef(encoding[channel]); + const mergedScaleCmpt = model.getScaleComponent(channel); + const scaleType = mergedScaleCmpt.get('type'); + const { + domain, + domainMid + } = model.specifiedScales[channel]; + switch (channel) { + case X: + case Y: + { + // If there is no explicit width/height for discrete x/y scales + if (contains(['point', 'band'], scaleType)) { + const positionSize = getDiscretePositionSize(channel, size, config.view); + if (isStep(positionSize)) { + const step = getPositionStep(positionSize, model, channel); + return { + step + }; + } + } + return fullWidthOrHeightRange(channel, model, scaleType); + } + case XOFFSET: + case YOFFSET: + return getOffsetRange(channel, model, scaleType); + case SIZE: + { + // TODO: support custom rangeMin, rangeMax + const rangeMin = sizeRangeMin(mark, config); + const rangeMax = sizeRangeMax(mark, size, model, config); + if (isContinuousToDiscrete(scaleType)) { + return interpolateRange(rangeMin, rangeMax, defaultContinuousToDiscreteCount(scaleType, config, domain, channel)); + } else { + return [rangeMin, rangeMax]; + } + } + case THETA: + return [0, Math.PI * 2]; + case ANGLE: + // TODO: add config.scale.min/maxAngleDegree (for point and text) and config.scale.min/maxAngleRadian (for arc) once we add arc marks. + // (It's weird to add just config.scale.min/maxAngleDegree for now) + return [0, 360]; + case RADIUS: + { + // max radius = half od min(width,height) + + return [0, new SignalRefWrapper(() => { + const w = model.getSignalName(isFacetModel(model.parent) ? 'child_width' : 'width'); + const h = model.getSignalName(isFacetModel(model.parent) ? 'child_height' : 'height'); + return `min(${w},${h})/2`; + })]; + } + case STROKEWIDTH: + // TODO: support custom rangeMin, rangeMax + return [config.scale.minStrokeWidth, config.scale.maxStrokeWidth]; + case STROKEDASH: + return [ + // TODO: add this to Vega's config.range? + [1, 0], [4, 2], [2, 1], [1, 1], [1, 2, 4, 2]]; + case SHAPE: + return 'symbol'; + case COLOR: + case FILL: + case STROKE: + if (scaleType === 'ordinal') { + // Only nominal data uses ordinal scale by default + return type === 'nominal' ? 'category' : 'ordinal'; + } else { + if (domainMid !== undefined) { + return 'diverging'; + } else { + return mark === 'rect' || mark === 'geoshape' ? 'heatmap' : 'ramp'; + } + } + case OPACITY: + case FILLOPACITY: + case STROKEOPACITY: + // TODO: support custom rangeMin, rangeMax + return [config.scale.minOpacity, config.scale.maxOpacity]; + } + } + function getPositionStep(step, model, channel) { + const { + encoding + } = model; + const mergedScaleCmpt = model.getScaleComponent(channel); + const offsetChannel = getOffsetScaleChannel(channel); + const offsetDef = encoding[offsetChannel]; + const stepFor = getStepFor({ + step, + offsetIsDiscrete: isFieldOrDatumDef(offsetDef) && isDiscrete$1(offsetDef.type) + }); + if (stepFor === 'offset' && channelHasFieldOrDatum(encoding, offsetChannel)) { + const offsetScaleCmpt = model.getScaleComponent(offsetChannel); + const offsetScaleName = model.scaleName(offsetChannel); + let stepCount = `domain('${offsetScaleName}').length`; + if (offsetScaleCmpt.get('type') === 'band') { + const offsetPaddingInner = offsetScaleCmpt.get('paddingInner') ?? offsetScaleCmpt.get('padding') ?? 0; + const offsetPaddingOuter = offsetScaleCmpt.get('paddingOuter') ?? offsetScaleCmpt.get('padding') ?? 0; + stepCount = `bandspace(${stepCount}, ${offsetPaddingInner}, ${offsetPaddingOuter})`; + } + const paddingInner = mergedScaleCmpt.get('paddingInner') ?? mergedScaleCmpt.get('padding'); + return { + signal: `${step.step} * ${stepCount} / (1-${exprFromSignalRefOrValue(paddingInner)})` + }; + } else { + return step.step; + } + } + function getOffsetStep(step, offsetScaleType) { + const stepFor = getStepFor({ + step, + offsetIsDiscrete: hasDiscreteDomain(offsetScaleType) + }); + if (stepFor === 'offset') { + return { + step: step.step + }; + } + return undefined; + } + function getOffsetRange(channel, model, offsetScaleType) { + const positionChannel = channel === XOFFSET ? 'x' : 'y'; + const positionScaleCmpt = model.getScaleComponent(positionChannel); + if (!positionScaleCmpt) { + return fullWidthOrHeightRange(positionChannel, model, offsetScaleType, { + center: true + }); + } + const positionScaleType = positionScaleCmpt.get('type'); + const positionScaleName = model.scaleName(positionChannel); + const { + markDef, + config + } = model; + if (positionScaleType === 'band') { + const size = getDiscretePositionSize(positionChannel, model.size, model.config.view); + if (isStep(size)) { + // step is for offset + const step = getOffsetStep(size, offsetScaleType); + if (step) { + return step; + } + } + // otherwise use the position + return [0, { + signal: `bandwidth('${positionScaleName}')` + }]; + } else { + // continuous scale + const positionDef = model.encoding[positionChannel]; + if (isFieldDef(positionDef) && positionDef.timeUnit) { + const duration = durationExpr(positionDef.timeUnit, expr => `scale('${positionScaleName}', ${expr})`); + const padding = model.config.scale.bandWithNestedOffsetPaddingInner; + const bandPositionOffset = getBandPosition({ + fieldDef: positionDef, + markDef, + config + }) - 0.5; + const bandPositionOffsetExpr = bandPositionOffset !== 0 ? ` + ${bandPositionOffset}` : ''; + if (padding) { + const startRatio = isSignalRef(padding) ? `${padding.signal}/2` + bandPositionOffsetExpr : `${padding / 2 + bandPositionOffset}`; + const endRatio = isSignalRef(padding) ? `(1 - ${padding.signal}/2)` + bandPositionOffsetExpr : `${1 - padding / 2 + bandPositionOffset}`; + return [{ + signal: `${startRatio} * (${duration})` + }, { + signal: `${endRatio} * (${duration})` + }]; + } + return [0, { + signal: duration + }]; + } + return never(`Cannot use ${channel} scale if ${positionChannel} scale is not discrete.`); + } + } + function getDiscretePositionSize(channel, size, viewConfig) { + const sizeChannel = channel === X ? 'width' : 'height'; + const sizeValue = size[sizeChannel]; + if (sizeValue) { + return sizeValue; + } + return getViewConfigDiscreteSize(viewConfig, sizeChannel); + } + function defaultContinuousToDiscreteCount(scaleType, config, domain, channel) { + switch (scaleType) { + case 'quantile': + return config.scale.quantileCount; + case 'quantize': + return config.scale.quantizeCount; + case 'threshold': + if (domain !== undefined && vega.isArray(domain)) { + return domain.length + 1; + } else { + warn(domainRequiredForThresholdScale(channel)); + // default threshold boundaries for threshold scale since domain has cardinality of 2 + return 3; + } + } + } + + /** + * Returns the linear interpolation of the range according to the cardinality + * + * @param rangeMin start of the range + * @param rangeMax end of the range + * @param cardinality number of values in the output range + */ + function interpolateRange(rangeMin, rangeMax, cardinality) { + // always return a signal since it's better to compute the sequence in Vega later + const f = () => { + const rMax = signalOrStringValue(rangeMax); + const rMin = signalOrStringValue(rangeMin); + const step = `(${rMax} - ${rMin}) / (${cardinality} - 1)`; + return `sequence(${rMin}, ${rMax} + ${step}, ${step})`; + }; + if (isSignalRef(rangeMax)) { + return new SignalRefWrapper(f); + } else { + return { + signal: f() + }; + } + } + function sizeRangeMin(mark, config) { + switch (mark) { + case 'bar': + case 'tick': + return config.scale.minBandSize; + case 'line': + case 'trail': + case 'rule': + return config.scale.minStrokeWidth; + case 'text': + return config.scale.minFontSize; + case 'point': + case 'square': + case 'circle': + return config.scale.minSize; + } + /* istanbul ignore next: should never reach here */ + // sizeRangeMin not implemented for the mark + throw new Error(incompatibleChannel('size', mark)); + } + const MAX_SIZE_RANGE_STEP_RATIO = 0.95; + function sizeRangeMax(mark, size, model, config) { + const xyStepSignals = { + x: getBinStepSignal(model, 'x'), + y: getBinStepSignal(model, 'y') + }; + switch (mark) { + case 'bar': + case 'tick': + { + if (config.scale.maxBandSize !== undefined) { + return config.scale.maxBandSize; + } + const min = minXYStep(size, xyStepSignals, config.view); + if (vega.isNumber(min)) { + return min - 1; + } else { + return new SignalRefWrapper(() => `${min.signal} - 1`); + } + } + case 'line': + case 'trail': + case 'rule': + return config.scale.maxStrokeWidth; + case 'text': + return config.scale.maxFontSize; + case 'point': + case 'square': + case 'circle': + { + if (config.scale.maxSize) { + return config.scale.maxSize; + } + const pointStep = minXYStep(size, xyStepSignals, config.view); + if (vega.isNumber(pointStep)) { + return Math.pow(MAX_SIZE_RANGE_STEP_RATIO * pointStep, 2); + } else { + return new SignalRefWrapper(() => `pow(${MAX_SIZE_RANGE_STEP_RATIO} * ${pointStep.signal}, 2)`); + } + } + } + /* istanbul ignore next: should never reach here */ + // sizeRangeMax not implemented for the mark + throw new Error(incompatibleChannel('size', mark)); + } + + /** + * @returns {number} Range step of x or y or minimum between the two if both are ordinal scale. + */ + function minXYStep(size, xyStepSignals, viewConfig) { + const widthStep = isStep(size.width) ? size.width.step : getViewConfigDiscreteStep(viewConfig, 'width'); + const heightStep = isStep(size.height) ? size.height.step : getViewConfigDiscreteStep(viewConfig, 'height'); + if (xyStepSignals.x || xyStepSignals.y) { + return new SignalRefWrapper(() => { + const exprs = [xyStepSignals.x ? xyStepSignals.x.signal : widthStep, xyStepSignals.y ? xyStepSignals.y.signal : heightStep]; + return `min(${exprs.join(', ')})`; + }); + } + return Math.min(widthStep, heightStep); + } + + function parseScaleProperty(model, property) { + if (isUnitModel(model)) { + parseUnitScaleProperty(model, property); + } else { + parseNonUnitScaleProperty(model, property); + } + } + function parseUnitScaleProperty(model, property) { + const localScaleComponents = model.component.scales; + const { + config, + encoding, + markDef, + specifiedScales + } = model; + for (const channel of keys(localScaleComponents)) { + const specifiedScale = specifiedScales[channel]; + const localScaleCmpt = localScaleComponents[channel]; + const mergedScaleCmpt = model.getScaleComponent(channel); + const fieldOrDatumDef = getFieldOrDatumDef(encoding[channel]); + const specifiedValue = specifiedScale[property]; + const scaleType = mergedScaleCmpt.get('type'); + const scalePadding = mergedScaleCmpt.get('padding'); + const scalePaddingInner = mergedScaleCmpt.get('paddingInner'); + const supportedByScaleType = scaleTypeSupportProperty(scaleType, property); + const channelIncompatability = channelScalePropertyIncompatability(channel, property); + if (specifiedValue !== undefined) { + // If there is a specified value, check if it is compatible with scale type and channel + if (!supportedByScaleType) { + warn(scalePropertyNotWorkWithScaleType(scaleType, property, channel)); + } else if (channelIncompatability) { + // channel + warn(channelIncompatability); + } + } + if (supportedByScaleType && channelIncompatability === undefined) { + if (specifiedValue !== undefined) { + const timeUnit = fieldOrDatumDef['timeUnit']; + const type = fieldOrDatumDef.type; + switch (property) { + // domainMax/Min to signal if the value is a datetime object + case 'domainMax': + case 'domainMin': + if (isDateTime(specifiedScale[property]) || type === 'temporal' || timeUnit) { + localScaleCmpt.set(property, { + signal: valueExpr(specifiedScale[property], { + type, + timeUnit + }) + }, true); + } else { + localScaleCmpt.set(property, specifiedScale[property], true); + } + break; + default: + localScaleCmpt.copyKeyFromObject(property, specifiedScale); + } + } else { + const value = property in scaleRules ? scaleRules[property]({ + model, + channel, + fieldOrDatumDef, + scaleType, + scalePadding, + scalePaddingInner, + domain: specifiedScale.domain, + domainMin: specifiedScale.domainMin, + domainMax: specifiedScale.domainMax, + markDef, + config, + hasNestedOffsetScale: channelHasNestedOffsetScale(encoding, channel), + hasSecondaryRangeChannel: !!encoding[getSecondaryRangeChannel(channel)] + }) : config.scale[property]; + if (value !== undefined) { + localScaleCmpt.set(property, value, false); + } + } + } + } + } + const scaleRules = { + bins: _ref => { + let { + model, + fieldOrDatumDef + } = _ref; + return isFieldDef(fieldOrDatumDef) ? bins(model, fieldOrDatumDef) : undefined; + }, + interpolate: _ref2 => { + let { + channel, + fieldOrDatumDef + } = _ref2; + return interpolate(channel, fieldOrDatumDef.type); + }, + nice: _ref3 => { + let { + scaleType, + channel, + domain, + domainMin, + domainMax, + fieldOrDatumDef + } = _ref3; + return nice(scaleType, channel, domain, domainMin, domainMax, fieldOrDatumDef); + }, + padding: _ref4 => { + let { + channel, + scaleType, + fieldOrDatumDef, + markDef, + config + } = _ref4; + return padding(channel, scaleType, config.scale, fieldOrDatumDef, markDef, config.bar); + }, + paddingInner: _ref5 => { + let { + scalePadding, + channel, + markDef, + scaleType, + config, + hasNestedOffsetScale + } = _ref5; + return paddingInner(scalePadding, channel, markDef.type, scaleType, config.scale, hasNestedOffsetScale); + }, + paddingOuter: _ref6 => { + let { + scalePadding, + channel, + scaleType, + scalePaddingInner, + config, + hasNestedOffsetScale + } = _ref6; + return paddingOuter(scalePadding, channel, scaleType, scalePaddingInner, config.scale, hasNestedOffsetScale); + }, + reverse: _ref7 => { + let { + fieldOrDatumDef, + scaleType, + channel, + config + } = _ref7; + const sort = isFieldDef(fieldOrDatumDef) ? fieldOrDatumDef.sort : undefined; + return reverse(scaleType, sort, channel, config.scale); + }, + zero: _ref8 => { + let { + channel, + fieldOrDatumDef, + domain, + markDef, + scaleType, + config, + hasSecondaryRangeChannel + } = _ref8; + return zero(channel, fieldOrDatumDef, domain, markDef, scaleType, config.scale, hasSecondaryRangeChannel); + } + }; + + // This method is here rather than in range.ts to avoid circular dependency. + function parseScaleRange(model) { + if (isUnitModel(model)) { + parseUnitScaleRange(model); + } else { + parseNonUnitScaleProperty(model, 'range'); + } + } + function parseNonUnitScaleProperty(model, property) { + const localScaleComponents = model.component.scales; + for (const child of model.children) { + if (property === 'range') { + parseScaleRange(child); + } else { + parseScaleProperty(child, property); + } + } + for (const channel of keys(localScaleComponents)) { + let valueWithExplicit; + for (const child of model.children) { + const childComponent = child.component.scales[channel]; + if (childComponent) { + const childValueWithExplicit = childComponent.getWithExplicit(property); + valueWithExplicit = mergeValuesWithExplicit(valueWithExplicit, childValueWithExplicit, property, 'scale', tieBreakByComparing((v1, v2) => { + switch (property) { + case 'range': + // For step, prefer larger step + if (v1.step && v2.step) { + return v1.step - v2.step; + } + return 0; + // TODO: precedence rule for other properties + } + return 0; + })); + } + } + localScaleComponents[channel].setWithExplicit(property, valueWithExplicit); + } + } + function bins(model, fieldDef) { + const bin = fieldDef.bin; + if (isBinning(bin)) { + const binSignal = getBinSignalName(model, fieldDef.field, bin); + return new SignalRefWrapper(() => { + return model.getSignalName(binSignal); + }); + } else if (isBinned(bin) && isBinParams(bin) && bin.step !== undefined) { + // start and stop will be determined from the scale domain + return { + step: bin.step + }; + } + return undefined; + } + function interpolate(channel, type) { + if (contains([COLOR, FILL, STROKE], channel) && type !== 'nominal') { + return 'hcl'; + } + return undefined; + } + function nice(scaleType, channel, specifiedDomain, domainMin, domainMax, fieldOrDatumDef) { + if (getFieldDef(fieldOrDatumDef)?.bin || vega.isArray(specifiedDomain) || domainMax != null || domainMin != null || contains([ScaleType.TIME, ScaleType.UTC], scaleType)) { + return undefined; + } + return isXorY(channel) ? true : undefined; + } + function padding(channel, scaleType, scaleConfig, fieldOrDatumDef, markDef, barConfig) { + if (isXorY(channel)) { + if (isContinuousToContinuous(scaleType)) { + if (scaleConfig.continuousPadding !== undefined) { + return scaleConfig.continuousPadding; + } + const { + type, + orient + } = markDef; + if (type === 'bar' && !(isFieldDef(fieldOrDatumDef) && (fieldOrDatumDef.bin || fieldOrDatumDef.timeUnit))) { + if (orient === 'vertical' && channel === 'x' || orient === 'horizontal' && channel === 'y') { + return barConfig.continuousBandSize; + } + } + } + if (scaleType === ScaleType.POINT) { + return scaleConfig.pointPadding; + } + } + return undefined; + } + function paddingInner(paddingValue, channel, mark, scaleType, scaleConfig) { + let hasNestedOffsetScale = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false; + if (paddingValue !== undefined) { + // If user has already manually specified "padding", no need to add default paddingInner. + return undefined; + } + if (isXorY(channel)) { + // Padding is only set for X and Y by default. + // Basically it doesn't make sense to add padding for color and size. + + // paddingOuter would only be called if it's a band scale, just return the default for bandScale. + const { + bandPaddingInner, + barBandPaddingInner, + rectBandPaddingInner, + tickBandPaddingInner, + bandWithNestedOffsetPaddingInner + } = scaleConfig; + if (hasNestedOffsetScale) { + return bandWithNestedOffsetPaddingInner; + } + return getFirstDefined(bandPaddingInner, mark === 'bar' ? barBandPaddingInner : mark === 'tick' ? tickBandPaddingInner : rectBandPaddingInner); + } else if (isXorYOffset(channel)) { + if (scaleType === ScaleType.BAND) { + return scaleConfig.offsetBandPaddingInner; + } + } + return undefined; + } + function paddingOuter(paddingValue, channel, scaleType, paddingInnerValue, scaleConfig) { + let hasNestedOffsetScale = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false; + if (paddingValue !== undefined) { + // If user has already manually specified "padding", no need to add default paddingOuter. + return undefined; + } + if (isXorY(channel)) { + const { + bandPaddingOuter, + bandWithNestedOffsetPaddingOuter + } = scaleConfig; + if (hasNestedOffsetScale) { + return bandWithNestedOffsetPaddingOuter; + } + // Padding is only set for X and Y by default. + // Basically it doesn't make sense to add padding for color and size. + if (scaleType === ScaleType.BAND) { + return getFirstDefined(bandPaddingOuter, + /* By default, paddingOuter is paddingInner / 2. The reason is that + size (width/height) = step * (cardinality - paddingInner + 2 * paddingOuter). + and we want the width/height to be integer by default. + Note that step (by default) and cardinality are integers.) */ + isSignalRef(paddingInnerValue) ? { + signal: `${paddingInnerValue.signal}/2` + } : paddingInnerValue / 2); + } + } else if (isXorYOffset(channel)) { + if (scaleType === ScaleType.POINT) { + return 0.5; // so the point positions align with centers of band scales. + } else if (scaleType === ScaleType.BAND) { + return scaleConfig.offsetBandPaddingOuter; + } + } + return undefined; + } + function reverse(scaleType, sort, channel, scaleConfig) { + if (channel === 'x' && scaleConfig.xReverse !== undefined) { + if (hasContinuousDomain(scaleType) && sort === 'descending') { + if (isSignalRef(scaleConfig.xReverse)) { + return { + signal: `!${scaleConfig.xReverse.signal}` + }; + } else { + return !scaleConfig.xReverse; + } + } + return scaleConfig.xReverse; + } + if (hasContinuousDomain(scaleType) && sort === 'descending') { + // For continuous domain scales, Vega does not support domain sort. + // Thus, we reverse range instead if sort is descending + return true; + } + return undefined; + } + function zero(channel, fieldDef, specifiedDomain, markDef, scaleType, scaleConfig, hasSecondaryRangeChannel) { + // If users explicitly provide a domain, we should not augment zero as that will be unexpected. + const hasCustomDomain = !!specifiedDomain && specifiedDomain !== 'unaggregated'; + if (hasCustomDomain) { + if (hasContinuousDomain(scaleType)) { + if (vega.isArray(specifiedDomain)) { + const first = specifiedDomain[0]; + const last = specifiedDomain[specifiedDomain.length - 1]; + if (vega.isNumber(first) && first <= 0 && vega.isNumber(last) && last >= 0) { + // if the domain includes zero, make zero remain true + return true; + } + } + return false; + } + } + + // If there is no custom domain, return configZero value (=`true` as default) only for the following cases: + + // 1) using quantitative field with size + // While this can be either ratio or interval fields, our assumption is that + // ratio are more common. However, if the scaleType is discretizing scale, we want to return + // false so that range doesn't start at zero + if (channel === 'size' && fieldDef.type === 'quantitative' && !isContinuousToDiscrete(scaleType)) { + return true; + } + + // 2) non-binned, quantitative x-scale or y-scale + // (For binning, we should not include zero by default because binning are calculated without zero.) + // (For area/bar charts with ratio scale chart, we should always include zero.) + if (!(isFieldDef(fieldDef) && fieldDef.bin) && contains([...POSITION_SCALE_CHANNELS, ...POLAR_POSITION_SCALE_CHANNELS], channel)) { + const { + orient, + type + } = markDef; + if (contains(['bar', 'area', 'line', 'trail'], type)) { + if (orient === 'horizontal' && channel === 'y' || orient === 'vertical' && channel === 'x') { + return false; + } + } + if (contains(['bar', 'area'], type) && !hasSecondaryRangeChannel) { + return true; + } + return scaleConfig?.zero; + } + return false; + } + + /** + * Determine if there is a specified scale type and if it is appropriate, + * or determine default type if type is unspecified or inappropriate. + */ + // NOTE: CompassQL uses this method. + function scaleType(specifiedScale, channel, fieldDef, mark) { + let hasNestedOffsetScale = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; + const defaultScaleType = defaultType(channel, fieldDef, mark, hasNestedOffsetScale); + const { + type + } = specifiedScale; + if (!isScaleChannel(channel)) { + // There is no scale for these channels + return null; + } + if (type !== undefined) { + // Check if explicitly specified scale type is supported by the channel + if (!channelSupportScaleType(channel, type)) { + warn(scaleTypeNotWorkWithChannel(channel, type, defaultScaleType)); + return defaultScaleType; + } + + // Check if explicitly specified scale type is supported by the data type + if (isFieldDef(fieldDef) && !scaleTypeSupportDataType(type, fieldDef.type)) { + warn(scaleTypeNotWorkWithFieldDef(type, defaultScaleType)); + return defaultScaleType; + } + return type; + } + return defaultScaleType; + } + + /** + * Determine appropriate default scale type. + */ + // NOTE: Voyager uses this method. + function defaultType(channel, fieldDef, mark, hasNestedOffsetScale) { + switch (fieldDef.type) { + case 'nominal': + case 'ordinal': + { + if (isColorChannel(channel) || rangeType(channel) === 'discrete') { + if (channel === 'shape' && fieldDef.type === 'ordinal') { + warn(discreteChannelCannotEncode(channel, 'ordinal')); + } + return 'ordinal'; + } + if (isXorY(channel) || isXorYOffset(channel)) { + if (contains(['rect', 'bar', 'image', 'rule', 'tick'], mark.type)) { + // The rect/bar/tick mark should fit into a band. + // For rule, using band scale to make rule align with axis ticks better https://github.com/vega/vega-lite/issues/3429 + return 'band'; + } + if (hasNestedOffsetScale) { + // If there is a nested offset scale, then there is a "band" for the span of the nested scale. + return 'band'; + } + } else if (mark.type === 'arc' && channel in POLAR_POSITION_SCALE_CHANNEL_INDEX) { + return 'band'; + } + const dimensionSize = mark[getSizeChannel(channel)]; + if (isRelativeBandSize(dimensionSize)) { + return 'band'; + } + if (isPositionFieldOrDatumDef(fieldDef) && fieldDef.axis?.tickBand) { + return 'band'; + } + // Otherwise, use ordinal point scale so we can easily get center positions of the marks. + return 'point'; + } + case 'temporal': + if (isColorChannel(channel)) { + return 'time'; + } else if (rangeType(channel) === 'discrete') { + warn(discreteChannelCannotEncode(channel, 'temporal')); + // TODO: consider using quantize (equivalent to binning) once we have it + return 'ordinal'; + } else if (isFieldDef(fieldDef) && fieldDef.timeUnit && normalizeTimeUnit(fieldDef.timeUnit).utc) { + return 'utc'; + } + return 'time'; + case 'quantitative': + if (isColorChannel(channel)) { + if (isFieldDef(fieldDef) && isBinning(fieldDef.bin)) { + return 'bin-ordinal'; + } + return 'linear'; + } else if (rangeType(channel) === 'discrete') { + warn(discreteChannelCannotEncode(channel, 'quantitative')); + // TODO: consider using quantize (equivalent to binning) once we have it + return 'ordinal'; + } + return 'linear'; + case 'geojson': + return undefined; + } + + /* istanbul ignore next: should never reach this */ + throw new Error(invalidFieldType(fieldDef.type)); + } + + function parseScales(model) { + let { + ignoreRange + } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + parseScaleCore(model); + parseScaleDomain(model); + for (const prop of NON_TYPE_DOMAIN_RANGE_VEGA_SCALE_PROPERTIES) { + parseScaleProperty(model, prop); + } + if (!ignoreRange) { + // range depends on zero + parseScaleRange(model); + } + } + function parseScaleCore(model) { + if (isUnitModel(model)) { + model.component.scales = parseUnitScaleCore(model); + } else { + model.component.scales = parseNonUnitScaleCore(model); + } + } + + /** + * Parse scales for all channels of a model. + */ + function parseUnitScaleCore(model) { + const { + encoding, + mark, + markDef + } = model; + const scaleComponents = {}; + for (const channel of SCALE_CHANNELS) { + const fieldOrDatumDef = getFieldOrDatumDef(encoding[channel]); // must be typed def to have scale + + // Don't generate scale for shape of geoshape + if (fieldOrDatumDef && mark === GEOSHAPE && channel === SHAPE && fieldOrDatumDef.type === GEOJSON) { + continue; + } + let specifiedScale = fieldOrDatumDef && fieldOrDatumDef['scale']; + if (fieldOrDatumDef && specifiedScale !== null && specifiedScale !== false) { + specifiedScale ??= {}; + const hasNestedOffsetScale = channelHasNestedOffsetScale(encoding, channel); + const sType = scaleType(specifiedScale, channel, fieldOrDatumDef, markDef, hasNestedOffsetScale); + scaleComponents[channel] = new ScaleComponent(model.scaleName(`${channel}`, true), { + value: sType, + explicit: specifiedScale.type === sType + }); + } + } + return scaleComponents; + } + const scaleTypeTieBreaker = tieBreakByComparing((st1, st2) => scaleTypePrecedence(st1) - scaleTypePrecedence(st2)); + function parseNonUnitScaleCore(model) { + const scaleComponents = model.component.scales = {}; + const scaleTypeWithExplicitIndex = {}; + const resolve = model.component.resolve; + + // Parse each child scale and determine if a particular channel can be merged. + for (const child of model.children) { + parseScaleCore(child); + + // Instead of always merging right away -- check if it is compatible to merge first! + for (const channel of keys(child.component.scales)) { + // if resolve is undefined, set default first + resolve.scale[channel] ??= defaultScaleResolve(channel, model); + if (resolve.scale[channel] === 'shared') { + const explicitScaleType = scaleTypeWithExplicitIndex[channel]; + const childScaleType = child.component.scales[channel].getWithExplicit('type'); + if (explicitScaleType) { + if (scaleCompatible(explicitScaleType.value, childScaleType.value)) { + // merge scale component if type are compatible + scaleTypeWithExplicitIndex[channel] = mergeValuesWithExplicit(explicitScaleType, childScaleType, 'type', 'scale', scaleTypeTieBreaker); + } else { + // Otherwise, update conflicting channel to be independent + resolve.scale[channel] = 'independent'; + // Remove from the index so they don't get merged + delete scaleTypeWithExplicitIndex[channel]; + } + } else { + scaleTypeWithExplicitIndex[channel] = childScaleType; + } + } + } + } + + // Merge each channel listed in the index + for (const channel of keys(scaleTypeWithExplicitIndex)) { + // Create new merged scale component + const name = model.scaleName(channel, true); + const typeWithExplicit = scaleTypeWithExplicitIndex[channel]; + scaleComponents[channel] = new ScaleComponent(name, typeWithExplicit); + + // rename each child and mark them as merged + for (const child of model.children) { + const childScale = child.component.scales[channel]; + if (childScale) { + child.renameScale(childScale.get('name'), name); + childScale.merged = true; + } + } + } + return scaleComponents; + } + + /** + * Composable Components that are intermediate results of the parsing phase of the + * compilations. The components represents parts of the specification in a form that + * can be easily merged (during parsing for composite specs). + * In addition, these components are easily transformed into Vega specifications + * during the "assemble" phase, which is the last phase of the compilation step. + */ + + class NameMap { + constructor() { + this.nameMap = {}; + } + rename(oldName, newName) { + this.nameMap[oldName] = newName; + } + has(name) { + return this.nameMap[name] !== undefined; + } + get(name) { + // If the name appears in the _nameMap, we need to read its new name. + // We have to loop over the dict just in case the new name also gets renamed. + while (this.nameMap[name] && name !== this.nameMap[name]) { + name = this.nameMap[name]; + } + return name; + } + } + + /* + We use type guards instead of `instanceof` as `instanceof` makes + different parts of the compiler depend on the actual implementation of + the model classes, which in turn depend on different parts of the compiler. + Thus, `instanceof` leads to circular dependency problems. + + On the other hand, type guards only make different parts of the compiler + depend on the type of the model classes, but not the actual implementation. + */ + + function isUnitModel(model) { + return model?.type === 'unit'; + } + function isFacetModel(model) { + return model?.type === 'facet'; + } + function isConcatModel(model) { + return model?.type === 'concat'; + } + function isLayerModel(model) { + return model?.type === 'layer'; + } + class Model { + /** Name map for scales, which can be renamed by a model's parent. */ + + /** Name map for projections, which can be renamed by a model's parent. */ + + /** Name map for signals, which can be renamed by a model's parent. */ + + constructor(spec, type, parent, parentGivenName, config, resolve, view) { + this.type = type; + this.parent = parent; + this.config = config; + this.parent = parent; + this.config = config; + this.view = replaceExprRef(view); + + // If name is not provided, always use parent's givenName to avoid name conflicts. + this.name = spec.name ?? parentGivenName; + this.title = isText(spec.title) ? { + text: spec.title + } : spec.title ? replaceExprRef(spec.title) : undefined; + + // Shared name maps + this.scaleNameMap = parent ? parent.scaleNameMap : new NameMap(); + this.projectionNameMap = parent ? parent.projectionNameMap : new NameMap(); + this.signalNameMap = parent ? parent.signalNameMap : new NameMap(); + this.data = spec.data; + this.description = spec.description; + this.transforms = normalizeTransform(spec.transform ?? []); + this.layout = type === 'layer' || type === 'unit' ? {} : extractCompositionLayout(spec, type, config); + this.component = { + data: { + sources: parent ? parent.component.data.sources : [], + outputNodes: parent ? parent.component.data.outputNodes : {}, + outputNodeRefCounts: parent ? parent.component.data.outputNodeRefCounts : {}, + // data is faceted if the spec is a facet spec or the parent has faceted data and data is undefined + isFaceted: isFacetSpec(spec) || parent?.component.data.isFaceted && spec.data === undefined + }, + layoutSize: new Split(), + layoutHeaders: { + row: {}, + column: {}, + facet: {} + }, + mark: null, + resolve: { + scale: {}, + axis: {}, + legend: {}, + ...(resolve ? duplicate(resolve) : {}) + }, + selection: null, + scales: null, + projection: null, + axes: {}, + legends: {} + }; + } + get width() { + return this.getSizeSignalRef('width'); + } + get height() { + return this.getSizeSignalRef('height'); + } + parse() { + this.parseScale(); + this.parseLayoutSize(); // depends on scale + this.renameTopLevelLayoutSizeSignal(); + this.parseSelections(); + this.parseProjection(); + this.parseData(); // (pathorder) depends on markDef; selection filters depend on parsed selections; depends on projection because some transforms require the finalized projection name. + this.parseAxesAndHeaders(); // depends on scale and layout size + this.parseLegends(); // depends on scale, markDef + this.parseMarkGroup(); // depends on data name, scale, layout size, axisGroup, and children's scale, axis, legend and mark. + } + parseScale() { + parseScales(this); + } + parseProjection() { + parseProjection(this); + } + /** + * Rename top-level spec's size to be just width / height, ignoring model name. + * This essentially merges the top-level spec's width/height signals with the width/height signals + * to help us reduce redundant signals declaration. + */ + renameTopLevelLayoutSizeSignal() { + if (this.getName('width') !== 'width') { + this.renameSignal(this.getName('width'), 'width'); + } + if (this.getName('height') !== 'height') { + this.renameSignal(this.getName('height'), 'height'); + } + } + parseLegends() { + parseLegend(this); + } + assembleEncodeFromView(view) { + // Exclude "style" + const { + style: _, + ...baseView + } = view; + const e = {}; + for (const property of keys(baseView)) { + const value = baseView[property]; + if (value !== undefined) { + e[property] = signalOrValueRef(value); + } + } + return e; + } + assembleGroupEncodeEntry(isTopLevel) { + let encodeEntry = {}; + if (this.view) { + encodeEntry = this.assembleEncodeFromView(this.view); + } + if (!isTopLevel) { + // Descriptions are already added to the top-level description so we only need to add them to the inner views. + if (this.description) { + encodeEntry['description'] = signalOrValueRef(this.description); + } + + // For top-level spec, we can set the global width and height signal to adjust the group size. + // For other child specs, we have to manually set width and height in the encode entry. + if (this.type === 'unit' || this.type === 'layer') { + return { + width: this.getSizeSignalRef('width'), + height: this.getSizeSignalRef('height'), + ...encodeEntry + }; + } + } + return isEmpty(encodeEntry) ? undefined : encodeEntry; + } + assembleLayout() { + if (!this.layout) { + return undefined; + } + const { + spacing, + ...layout + } = this.layout; + const { + component, + config + } = this; + const titleBand = assembleLayoutTitleBand(component.layoutHeaders, config); + return { + padding: spacing, + ...this.assembleDefaultLayout(), + ...layout, + ...(titleBand ? { + titleBand + } : {}) + }; + } + assembleDefaultLayout() { + return {}; + } + assembleHeaderMarks() { + const { + layoutHeaders + } = this.component; + let headerMarks = []; + for (const channel of FACET_CHANNELS) { + if (layoutHeaders[channel].title) { + headerMarks.push(assembleTitleGroup(this, channel)); + } + } + for (const channel of HEADER_CHANNELS) { + headerMarks = headerMarks.concat(assembleHeaderGroups(this, channel)); + } + return headerMarks; + } + assembleAxes() { + return assembleAxes(this.component.axes, this.config); + } + assembleLegends() { + return assembleLegends(this); + } + assembleProjections() { + return assembleProjections(this); + } + assembleTitle() { + const { + encoding, + ...titleNoEncoding + } = this.title ?? {}; + const title = { + ...extractTitleConfig(this.config.title).nonMarkTitleProperties, + ...titleNoEncoding, + ...(encoding ? { + encode: { + update: encoding + } + } : {}) + }; + if (title.text) { + if (contains(['unit', 'layer'], this.type)) { + // Unit/Layer + if (contains(['middle', undefined], title.anchor)) { + title.frame ??= 'group'; + } + } else { + // composition with Vega layout + + // Set title = "start" by default for composition as "middle" does not look nice + // https://github.com/vega/vega/issues/960#issuecomment-471360328 + title.anchor ??= 'start'; + } + return isEmpty(title) ? undefined : title; + } + return undefined; + } + + /** + * 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. + */ + assembleGroup() { + let signals = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + const group = {}; + signals = signals.concat(this.assembleSignals()); + if (signals.length > 0) { + group.signals = signals; + } + const layout = this.assembleLayout(); + if (layout) { + group.layout = layout; + } + group.marks = [].concat(this.assembleHeaderMarks(), this.assembleMarks()); + + // Only include scales if this spec is top-level or if parent is facet. + // (Otherwise, it will be merged with upper-level's scope.) + const scales = !this.parent || isFacetModel(this.parent) ? assembleScales(this) : []; + if (scales.length > 0) { + group.scales = scales; + } + const axes = this.assembleAxes(); + if (axes.length > 0) { + group.axes = axes; + } + const legends = this.assembleLegends(); + if (legends.length > 0) { + group.legends = legends; + } + return group; + } + getName(text) { + return varName((this.name ? `${this.name}_` : '') + text); + } + getDataName(type) { + return this.getName(DataSourceType[type].toLowerCase()); + } + + /** + * 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(). + * You can lookup the correct dataset name in assemble with `lookupDataSource`. + */ + requestDataName(name) { + const fullName = this.getDataName(name); + + // Increase ref count. This is critical because otherwise we won't create a data source. + // We also increase the ref counts on OutputNode.getSource() calls. + const refCounts = this.component.data.outputNodeRefCounts; + refCounts[fullName] = (refCounts[fullName] || 0) + 1; + return fullName; + } + getSizeSignalRef(layoutSizeType) { + if (isFacetModel(this.parent)) { + const sizeType = getSizeTypeFromLayoutSizeType(layoutSizeType); + const channel = getPositionScaleChannel(sizeType); + const scaleComponent = this.component.scales[channel]; + if (scaleComponent && !scaleComponent.merged) { + // independent scale + const type = scaleComponent.get('type'); + const range = scaleComponent.get('range'); + if (hasDiscreteDomain(type) && isVgRangeStep(range)) { + const scaleName = scaleComponent.get('name'); + const domain = assembleDomain(this, channel); + const field = getFieldFromDomain(domain); + if (field) { + const fieldRef = vgField({ + aggregate: 'distinct', + field + }, { + expr: 'datum' + }); + return { + signal: sizeExpr(scaleName, scaleComponent, fieldRef) + }; + } else { + warn(unknownField(channel)); + return null; + } + } + } + } + return { + signal: this.signalNameMap.get(this.getName(layoutSizeType)) + }; + } + + /** + * Lookup the name of the datasource for an output node. You probably want to call this in assemble. + */ + lookupDataSource(name) { + const node = this.component.data.outputNodes[name]; + if (!node) { + // Name not found in map so let's just return what we got. + // This can happen if we already have the correct name. + return name; + } + return node.getSource(); + } + getSignalName(oldSignalName) { + return this.signalNameMap.get(oldSignalName); + } + renameSignal(oldName, newName) { + this.signalNameMap.rename(oldName, newName); + } + renameScale(oldName, newName) { + this.scaleNameMap.rename(oldName, newName); + } + renameProjection(oldName, newName) { + this.projectionNameMap.rename(oldName, newName); + } + + /** + * @return scale name for a given channel after the scale has been parsed and named. + */ + scaleName(originalScaleName, parse) { + if (parse) { + // During the parse phase always return a value + // No need to refer to rename map because a scale can't be renamed + // before it has the original name. + return this.getName(originalScaleName); + } + + // If there is a scale for the channel, it should either + // be in the scale component or exist in the name map + if ( + // If there is a scale for the channel, there should be a local scale component for it + isChannel(originalScaleName) && isScaleChannel(originalScaleName) && this.component.scales[originalScaleName] || + // in the scale name map (the scale get merged by its parent) + this.scaleNameMap.has(this.getName(originalScaleName))) { + return this.scaleNameMap.get(this.getName(originalScaleName)); + } + return undefined; + } + + /** + * @return projection name after the projection has been parsed and named. + */ + projectionName(parse) { + if (parse) { + // During the parse phase always return a value + // No need to refer to rename map because a projection can't be renamed + // before it has the original name. + return this.getName('projection'); + } + if (this.component.projection && !this.component.projection.merged || this.projectionNameMap.has(this.getName('projection'))) { + return this.projectionNameMap.get(this.getName('projection')); + } + return undefined; + } + + /** + * Corrects the data references in marks after assemble. + */ + correctDataNames = mark => { + // TODO: make this correct + + // for normal data references + if (mark.from?.data) { + mark.from.data = this.lookupDataSource(mark.from.data); + } + + // for access to facet data + if (mark.from?.facet?.data) { + mark.from.facet.data = this.lookupDataSource(mark.from.facet.data); + } + return mark; + }; + + /** + * Traverse a model's hierarchy to get the scale component for a particular channel. + */ + getScaleComponent(channel) { + /* istanbul ignore next: This is warning for debugging test */ + if (!this.component.scales) { + throw new Error('getScaleComponent cannot be called before parseScale(). Make sure you have called parseScale or use parseUnitModelWithScale().'); + } + const localScaleComponent = this.component.scales[channel]; + if (localScaleComponent && !localScaleComponent.merged) { + return localScaleComponent; + } + return this.parent ? this.parent.getScaleComponent(channel) : undefined; + } + getScaleType(channel) { + const scaleComponent = this.getScaleComponent(channel); + return scaleComponent ? scaleComponent.get('type') : undefined; + } + + /** + * Traverse a model's hierarchy to get a particular selection component. + */ + getSelectionComponent(variableName, origName) { + let sel = this.component.selection[variableName]; + if (!sel && this.parent) { + sel = this.parent.getSelectionComponent(variableName, origName); + } + if (!sel) { + throw new Error(selectionNotFound(origName)); + } + return sel; + } + + /** + * Returns true if the model has a signalRef for an axis orient. + */ + hasAxisOrientSignalRef() { + return this.component.axes.x?.some(a => a.hasOrientSignalRef()) || this.component.axes.y?.some(a => a.hasOrientSignalRef()); + } + } + + /** Abstract class for UnitModel and FacetModel. Both of which can contain fieldDefs as a part of its own specification. */ + class ModelWithField extends Model { + /** Get "field" reference for Vega */ + vgField(channel) { + let opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + const fieldDef = this.fieldDef(channel); + if (!fieldDef) { + return undefined; + } + return vgField(fieldDef, opt); + } + reduceFieldDef(f, init) { + return reduce(this.getMapping(), (acc, cd, c) => { + const fieldDef = getFieldDef(cd); + if (fieldDef) { + return f(acc, fieldDef, c); + } + return acc; + }, init); + } + forEachFieldDef(f, t) { + forEach(this.getMapping(), (cd, c) => { + const fieldDef = getFieldDef(cd); + if (fieldDef) { + f(fieldDef, c); + } + }, t); + } + } + + /** + * A class for density transform nodes + */ + class DensityTransformNode extends DataFlowNode { + clone() { + return new DensityTransformNode(null, duplicate(this.transform)); + } + constructor(parent, transform) { + super(parent); + this.transform = transform; + this.transform = duplicate(transform); // duplicate to prevent side effects + const specifiedAs = this.transform.as ?? [undefined, undefined]; + this.transform.as = [specifiedAs[0] ?? 'value', specifiedAs[1] ?? 'density']; + const resolve = this.transform.resolve ?? 'shared'; + this.transform.resolve = resolve; + } + dependentFields() { + return new Set([this.transform.density, ...(this.transform.groupby ?? [])]); + } + producedFields() { + return new Set(this.transform.as); + } + hash() { + return `DensityTransform ${hash(this.transform)}`; + } + assemble() { + const { + density, + ...rest + } = this.transform; + const result = { + type: 'kde', + field: density, + ...rest + }; + result.resolve = this.transform.resolve; + return result; + } + } + + /** + * A class for flatten transform nodes + */ + class ExtentTransformNode extends DataFlowNode { + clone() { + return new ExtentTransformNode(null, duplicate(this.transform)); + } + constructor(parent, transform) { + super(parent); + this.transform = transform; + this.transform = duplicate(transform); + } + dependentFields() { + return new Set([this.transform.extent]); + } + producedFields() { + return new Set([]); + } + hash() { + return `ExtentTransform ${hash(this.transform)}`; + } + assemble() { + const { + extent, + param + } = this.transform; + const result = { + type: 'extent', + field: extent, + signal: param + }; + return result; + } + } + + /** + * A class for flatten transform nodes + */ + class FlattenTransformNode extends DataFlowNode { + clone() { + return new FlattenTransformNode(this.parent, duplicate(this.transform)); + } + constructor(parent, transform) { + super(parent); + this.transform = transform; + this.transform = duplicate(transform); // duplicate to prevent side effects + const { + flatten, + as = [] + } = this.transform; + this.transform.as = flatten.map((f, i) => as[i] ?? f); + } + dependentFields() { + return new Set(this.transform.flatten); + } + producedFields() { + return new Set(this.transform.as); + } + hash() { + return `FlattenTransform ${hash(this.transform)}`; + } + assemble() { + const { + flatten: fields, + as + } = this.transform; + const result = { + type: 'flatten', + fields, + as + }; + return result; + } + } + + /** + * A class for flatten transform nodes + */ + class FoldTransformNode extends DataFlowNode { + clone() { + return new FoldTransformNode(null, duplicate(this.transform)); + } + constructor(parent, transform) { + super(parent); + this.transform = transform; + this.transform = duplicate(transform); // duplicate to prevent side effects + const specifiedAs = this.transform.as ?? [undefined, undefined]; + this.transform.as = [specifiedAs[0] ?? 'key', specifiedAs[1] ?? 'value']; + } + dependentFields() { + return new Set(this.transform.fold); + } + producedFields() { + return new Set(this.transform.as); + } + hash() { + return `FoldTransform ${hash(this.transform)}`; + } + assemble() { + const { + fold, + as + } = this.transform; + const result = { + type: 'fold', + fields: fold, + as + }; + return result; + } + } + + class GeoJSONNode extends DataFlowNode { + clone() { + return new GeoJSONNode(null, duplicate(this.fields), this.geojson, this.signal); + } + static parseAll(parent, model) { + if (model.component.projection && !model.component.projection.isFit) { + return parent; + } + let geoJsonCounter = 0; + for (const coordinates of [[LONGITUDE, LATITUDE], [LONGITUDE2, LATITUDE2]]) { + const pair = coordinates.map(channel => { + const def = getFieldOrDatumDef(model.encoding[channel]); + return isFieldDef(def) ? def.field : isDatumDef(def) ? { + expr: `${def.datum}` + } : isValueDef(def) ? { + expr: `${def['value']}` + } : undefined; + }); + if (pair[0] || pair[1]) { + parent = new GeoJSONNode(parent, pair, null, model.getName(`geojson_${geoJsonCounter++}`)); + } + } + if (model.channelHasField(SHAPE)) { + const fieldDef = model.typedFieldDef(SHAPE); + if (fieldDef.type === GEOJSON) { + parent = new GeoJSONNode(parent, null, fieldDef.field, model.getName(`geojson_${geoJsonCounter++}`)); + } + } + return parent; + } + constructor(parent, fields, geojson, signal) { + super(parent); + this.fields = fields; + this.geojson = geojson; + this.signal = signal; + } + dependentFields() { + const fields = (this.fields ?? []).filter(vega.isString); + return new Set([...(this.geojson ? [this.geojson] : []), ...fields]); + } + producedFields() { + return new Set(); + } + hash() { + return `GeoJSON ${this.geojson} ${this.signal} ${hash(this.fields)}`; + } + assemble() { + return [...(this.geojson ? [{ + type: 'filter', + expr: `isValid(datum["${this.geojson}"])` + }] : []), { + type: 'geojson', + ...(this.fields ? { + fields: this.fields + } : {}), + ...(this.geojson ? { + geojson: this.geojson + } : {}), + signal: this.signal + }]; + } + } + + class GeoPointNode extends DataFlowNode { + clone() { + return new GeoPointNode(null, this.projection, duplicate(this.fields), duplicate(this.as)); + } + constructor(parent, projection, fields, as) { + super(parent); + this.projection = projection; + this.fields = fields; + this.as = as; + } + static parseAll(parent, model) { + if (!model.projectionName()) { + return parent; + } + for (const coordinates of [[LONGITUDE, LATITUDE], [LONGITUDE2, LATITUDE2]]) { + const pair = coordinates.map(channel => { + const def = getFieldOrDatumDef(model.encoding[channel]); + return isFieldDef(def) ? def.field : isDatumDef(def) ? { + expr: `${def.datum}` + } : isValueDef(def) ? { + expr: `${def['value']}` + } : undefined; + }); + const suffix = coordinates[0] === LONGITUDE2 ? '2' : ''; + if (pair[0] || pair[1]) { + parent = new GeoPointNode(parent, model.projectionName(), pair, [model.getName(`x${suffix}`), model.getName(`y${suffix}`)]); + } + } + return parent; + } + dependentFields() { + return new Set(this.fields.filter(vega.isString)); + } + producedFields() { + return new Set(this.as); + } + hash() { + return `Geopoint ${this.projection} ${hash(this.fields)} ${hash(this.as)}`; + } + assemble() { + return { + type: 'geopoint', + projection: this.projection, + fields: this.fields, + as: this.as + }; + } + } + + class ImputeNode extends DataFlowNode { + clone() { + return new ImputeNode(null, duplicate(this.transform)); + } + constructor(parent, transform) { + super(parent); + this.transform = transform; + } + dependentFields() { + return new Set([this.transform.impute, this.transform.key, ...(this.transform.groupby ?? [])]); + } + producedFields() { + return new Set([this.transform.impute]); + } + processSequence(keyvals) { + const { + start = 0, + stop, + step + } = keyvals; + const result = [start, stop, ...(step ? [step] : [])].join(','); + return { + signal: `sequence(${result})` + }; + } + static makeFromTransform(parent, imputeTransform) { + return new ImputeNode(parent, imputeTransform); + } + static makeFromEncoding(parent, model) { + const encoding = model.encoding; + const xDef = encoding.x; + const yDef = encoding.y; + if (isFieldDef(xDef) && isFieldDef(yDef)) { + const imputedChannel = xDef.impute ? xDef : yDef.impute ? yDef : undefined; + if (imputedChannel === undefined) { + return undefined; + } + const keyChannel = xDef.impute ? yDef : yDef.impute ? xDef : undefined; + const { + method, + value, + frame, + keyvals + } = imputedChannel.impute; + const groupbyFields = pathGroupingFields(model.mark, encoding); + return new ImputeNode(parent, { + impute: imputedChannel.field, + key: keyChannel.field, + ...(method ? { + method + } : {}), + ...(value !== undefined ? { + value + } : {}), + ...(frame ? { + frame + } : {}), + ...(keyvals !== undefined ? { + keyvals + } : {}), + ...(groupbyFields.length ? { + groupby: groupbyFields + } : {}) + }); + } + return null; + } + hash() { + return `Impute ${hash(this.transform)}`; + } + assemble() { + const { + impute, + key, + keyvals, + method, + groupby, + value, + frame = [null, null] + } = this.transform; + const imputeTransform = { + type: 'impute', + field: impute, + key, + ...(keyvals ? { + keyvals: isImputeSequence(keyvals) ? this.processSequence(keyvals) : keyvals + } : {}), + method: 'value', + ...(groupby ? { + groupby + } : {}), + value: !method || method === 'value' ? value : null + }; + if (method && method !== 'value') { + const deriveNewField = { + type: 'window', + as: [`imputed_${impute}_value`], + ops: [method], + fields: [impute], + frame, + ignorePeers: false, + ...(groupby ? { + groupby + } : {}) + }; + const replaceOriginal = { + type: 'formula', + expr: `datum.${impute} === null ? datum.imputed_${impute}_value : datum.${impute}`, + as: impute + }; + return [imputeTransform, deriveNewField, replaceOriginal]; + } else { + return [imputeTransform]; + } + } + } + + /** + * A class for loess transform nodes + */ + class LoessTransformNode extends DataFlowNode { + clone() { + return new LoessTransformNode(null, duplicate(this.transform)); + } + constructor(parent, transform) { + super(parent); + this.transform = transform; + this.transform = duplicate(transform); // duplicate to prevent side effects + const specifiedAs = this.transform.as ?? [undefined, undefined]; + this.transform.as = [specifiedAs[0] ?? transform.on, specifiedAs[1] ?? transform.loess]; + } + dependentFields() { + return new Set([this.transform.loess, this.transform.on, ...(this.transform.groupby ?? [])]); + } + producedFields() { + return new Set(this.transform.as); + } + hash() { + return `LoessTransform ${hash(this.transform)}`; + } + assemble() { + const { + loess, + on, + ...rest + } = this.transform; + const result = { + type: 'loess', + x: on, + y: loess, + ...rest + }; + return result; + } + } + + class LookupNode extends DataFlowNode { + clone() { + return new LookupNode(null, duplicate(this.transform), this.secondary); + } + constructor(parent, transform, secondary) { + super(parent); + this.transform = transform; + this.secondary = secondary; + } + static make(parent, model, transform, counter) { + const sources = model.component.data.sources; + const { + from + } = transform; + let fromOutputNode = null; + if (isLookupData(from)) { + let fromSource = findSource(from.data, sources); + if (!fromSource) { + fromSource = new SourceNode(from.data); + sources.push(fromSource); + } + const fromOutputName = model.getName(`lookup_${counter}`); + fromOutputNode = new OutputNode(fromSource, fromOutputName, DataSourceType.Lookup, model.component.data.outputNodeRefCounts); + model.component.data.outputNodes[fromOutputName] = fromOutputNode; + } else if (isLookupSelection(from)) { + const selName = from.param; + transform = { + as: selName, + ...transform + }; + let selCmpt; + try { + selCmpt = model.getSelectionComponent(varName(selName), selName); + } catch (e) { + throw new Error(cannotLookupVariableParameter(selName)); + } + fromOutputNode = selCmpt.materialized; + if (!fromOutputNode) { + throw new Error(noSameUnitLookup(selName)); + } + } + return new LookupNode(parent, transform, fromOutputNode.getSource()); + } + dependentFields() { + return new Set([this.transform.lookup]); + } + producedFields() { + return new Set(this.transform.as ? vega.array(this.transform.as) : this.transform.from.fields); + } + hash() { + return `Lookup ${hash({ + transform: this.transform, + secondary: this.secondary + })}`; + } + assemble() { + let foreign; + if (this.transform.from.fields) { + // lookup a few fields and add create a flat output + foreign = { + values: this.transform.from.fields, + ...(this.transform.as ? { + as: vega.array(this.transform.as) + } : {}) + }; + } else { + // lookup full record and nest it + let asName = this.transform.as; + if (!vega.isString(asName)) { + warn(NO_FIELDS_NEEDS_AS); + asName = '_lookup'; + } + foreign = { + as: [asName] + }; + } + return { + type: 'lookup', + from: this.secondary, + key: this.transform.from.key, + fields: [this.transform.lookup], + ...foreign, + ...(this.transform.default ? { + default: this.transform.default + } : {}) + }; + } + } + + /** + * A class for quantile transform nodes + */ + class QuantileTransformNode extends DataFlowNode { + clone() { + return new QuantileTransformNode(null, duplicate(this.transform)); + } + constructor(parent, transform) { + super(parent); + this.transform = transform; + this.transform = duplicate(transform); // duplicate to prevent side effects + const specifiedAs = this.transform.as ?? [undefined, undefined]; + this.transform.as = [specifiedAs[0] ?? 'prob', specifiedAs[1] ?? 'value']; + } + dependentFields() { + return new Set([this.transform.quantile, ...(this.transform.groupby ?? [])]); + } + producedFields() { + return new Set(this.transform.as); + } + hash() { + return `QuantileTransform ${hash(this.transform)}`; + } + assemble() { + const { + quantile, + ...rest + } = this.transform; + const result = { + type: 'quantile', + field: quantile, + ...rest + }; + return result; + } + } + + /** + * A class for regression transform nodes + */ + class RegressionTransformNode extends DataFlowNode { + clone() { + return new RegressionTransformNode(null, duplicate(this.transform)); + } + constructor(parent, transform) { + super(parent); + this.transform = transform; + this.transform = duplicate(transform); // duplicate to prevent side effects + const specifiedAs = this.transform.as ?? [undefined, undefined]; + this.transform.as = [specifiedAs[0] ?? transform.on, specifiedAs[1] ?? transform.regression]; + } + dependentFields() { + return new Set([this.transform.regression, this.transform.on, ...(this.transform.groupby ?? [])]); + } + producedFields() { + return new Set(this.transform.as); + } + hash() { + return `RegressionTransform ${hash(this.transform)}`; + } + assemble() { + const { + regression, + on, + ...rest + } = this.transform; + const result = { + type: 'regression', + x: on, + y: regression, + ...rest + }; + return result; + } + } + + /** + * A class for pivot transform nodes. + */ + class PivotTransformNode extends DataFlowNode { + clone() { + return new PivotTransformNode(null, duplicate(this.transform)); + } + constructor(parent, transform) { + super(parent); + this.transform = transform; + } + addDimensions(fields) { + this.transform.groupby = unique((this.transform.groupby ?? []).concat(fields), d => d); + } + producedFields() { + return undefined; // return undefined so that potentially everything can depend on the pivot + } + dependentFields() { + return new Set([this.transform.pivot, this.transform.value, ...(this.transform.groupby ?? [])]); + } + hash() { + return `PivotTransform ${hash(this.transform)}`; + } + assemble() { + const { + pivot, + value, + groupby, + limit, + op + } = this.transform; + return { + type: 'pivot', + field: pivot, + value, + ...(limit !== undefined ? { + limit + } : {}), + ...(op !== undefined ? { + op + } : {}), + ...(groupby !== undefined ? { + groupby + } : {}) + }; + } + } + + /** + * A class for the sample transform nodes + */ + class SampleTransformNode extends DataFlowNode { + clone() { + return new SampleTransformNode(null, duplicate(this.transform)); + } + constructor(parent, transform) { + super(parent); + this.transform = transform; + } + dependentFields() { + return new Set(); + } + producedFields() { + return new Set(); + } + hash() { + return `SampleTransform ${hash(this.transform)}`; + } + assemble() { + return { + type: 'sample', + size: this.transform.sample + }; + } + } + + function makeWalkTree(data) { + // to name datasources + let datasetIndex = 0; + + /** + * Recursively walk down the tree. + */ + function walkTree(node, dataSource) { + if (node instanceof SourceNode) { + // If the source is a named data source or a data source with values, we need + // to put it in a different data source. Otherwise, Vega may override the data. + if (!node.isGenerator && !isUrlData(node.data)) { + data.push(dataSource); + const newData = { + name: null, + source: dataSource.name, + transform: [] + }; + dataSource = newData; + } + } + if (node instanceof ParseNode) { + if (node.parent instanceof SourceNode && !dataSource.source) { + // If node's parent is a root source and the data source does not refer to another data source, use normal format parse + dataSource.format = { + ...dataSource.format, + parse: node.assembleFormatParse() + }; + + // add calculates for all nested fields + dataSource.transform.push(...node.assembleTransforms(true)); + } else { + // Otherwise use Vega expression to parse + dataSource.transform.push(...node.assembleTransforms()); + } + } + if (node instanceof FacetNode) { + if (!dataSource.name) { + dataSource.name = `data_${datasetIndex++}`; + } + if (!dataSource.source || dataSource.transform.length > 0) { + data.push(dataSource); + node.data = dataSource.name; + } else { + node.data = dataSource.source; + } + data.push(...node.assemble()); + + // break here because the rest of the tree has to be taken care of by the facet. + return; + } + if (node instanceof GraticuleNode || node instanceof SequenceNode || node instanceof FilterInvalidNode || node instanceof FilterNode || node instanceof CalculateNode || node instanceof GeoPointNode || node instanceof AggregateNode || node instanceof LookupNode || node instanceof WindowTransformNode || node instanceof JoinAggregateTransformNode || node instanceof FoldTransformNode || node instanceof FlattenTransformNode || node instanceof DensityTransformNode || node instanceof LoessTransformNode || node instanceof QuantileTransformNode || node instanceof RegressionTransformNode || node instanceof IdentifierNode || node instanceof SampleTransformNode || node instanceof PivotTransformNode || node instanceof ExtentTransformNode) { + dataSource.transform.push(node.assemble()); + } + if (node instanceof BinNode || node instanceof TimeUnitNode || node instanceof ImputeNode || node instanceof StackNode || node instanceof GeoJSONNode) { + dataSource.transform.push(...node.assemble()); + } + if (node instanceof OutputNode) { + if (dataSource.source && dataSource.transform.length === 0) { + node.setSource(dataSource.source); + } else if (node.parent instanceof OutputNode) { + // Note that an output node may be required but we still do not assemble a + // separate data source for it. + node.setSource(dataSource.name); + } else { + if (!dataSource.name) { + dataSource.name = `data_${datasetIndex++}`; + } + + // Here we set the name of the datasource we generated. From now on + // other assemblers can use it. + node.setSource(dataSource.name); + + // if this node has more than one child, we will add a datasource automatically + if (node.numChildren() === 1) { + data.push(dataSource); + const newData = { + name: null, + source: dataSource.name, + transform: [] + }; + dataSource = newData; + } + } + } + switch (node.numChildren()) { + case 0: + // done + if (node instanceof OutputNode && (!dataSource.source || dataSource.transform.length > 0)) { + // do not push empty datasources that are simply references + data.push(dataSource); + } + break; + case 1: + walkTree(node.children[0], dataSource); + break; + default: + { + if (!dataSource.name) { + dataSource.name = `data_${datasetIndex++}`; + } + let source = dataSource.name; + if (!dataSource.source || dataSource.transform.length > 0) { + data.push(dataSource); + } else { + source = dataSource.source; + } + for (const child of node.children) { + const newData = { + name: null, + source, + transform: [] + }; + walkTree(child, newData); + } + break; + } + } + } + return walkTree; + } + + /** + * Assemble data sources that are derived from faceted data. + */ + function assembleFacetData(root) { + const data = []; + const walkTree = makeWalkTree(data); + for (const child of root.children) { + walkTree(child, { + source: root.name, + name: null, + transform: [] + }); + } + return data; + } + + /** + * Create Vega data array from a given compiled model and append all of them to the given array + * + * @param model + * @param data array + * @return modified data array + */ + function assembleRootData(dataComponent, datasets) { + const data = []; + + // dataComponent.sources.forEach(debug); + // draw(dataComponent.sources); + + const walkTree = makeWalkTree(data); + let sourceIndex = 0; + for (const root of dataComponent.sources) { + // assign a name if the source does not have a name yet + if (!root.hasName()) { + root.dataName = `source_${sourceIndex++}`; + } + const newData = root.assemble(); + walkTree(root, newData); + } + + // remove empty transform arrays for cleaner output + for (const d of data) { + if (d.transform.length === 0) { + delete d.transform; + } + } + + // move sources without transforms (the ones that are potentially used in lookups) to the beginning + let whereTo = 0; + for (const [i, d] of data.entries()) { + if ((d.transform ?? []).length === 0 && !d.source) { + data.splice(whereTo++, 0, data.splice(i, 1)[0]); + } + } + + // now fix the from references in lookup transforms + for (const d of data) { + for (const t of d.transform ?? []) { + if (t.type === 'lookup') { + t.from = dataComponent.outputNodes[t.from].getSource(); + } + } + } + + // inline values for datasets that are in the datastore + for (const d of data) { + if (d.name in datasets) { + d.values = datasets[d.name]; + } + } + return data; + } + + function getHeaderType(orient) { + if (orient === 'top' || orient === 'left' || isSignalRef(orient)) { + // we always use header for orient signal since we can't dynamically make header becomes footer + return 'header'; + } + return 'footer'; + } + function parseFacetHeaders(model) { + for (const channel of FACET_CHANNELS) { + parseFacetHeader(model, channel); + } + mergeChildAxis(model, 'x'); + mergeChildAxis(model, 'y'); + } + function parseFacetHeader(model, channel) { + const { + facet, + config, + child, + component + } = model; + if (model.channelHasField(channel)) { + const fieldDef = facet[channel]; + const titleConfig = getHeaderProperty('title', null, config, channel); + let title$1 = title(fieldDef, config, { + allowDisabling: true, + includeDefault: titleConfig === undefined || !!titleConfig + }); + if (child.component.layoutHeaders[channel].title) { + // TODO: better handle multiline titles + title$1 = vega.isArray(title$1) ? title$1.join(', ') : title$1; + + // merge title with child to produce "Title / Subtitle / Sub-subtitle" + title$1 += ` / ${child.component.layoutHeaders[channel].title}`; + child.component.layoutHeaders[channel].title = null; + } + const labelOrient = getHeaderProperty('labelOrient', fieldDef.header, config, channel); + const labels = fieldDef.header !== null ? getFirstDefined(fieldDef.header?.labels, config.header.labels, true) : false; + const headerType = contains(['bottom', 'right'], labelOrient) ? 'footer' : 'header'; + component.layoutHeaders[channel] = { + title: fieldDef.header !== null ? title$1 : null, + facetFieldDef: fieldDef, + [headerType]: channel === 'facet' ? [] : [makeHeaderComponent(model, channel, labels)] + }; + } + } + function makeHeaderComponent(model, channel, labels) { + const sizeType = channel === 'row' ? 'height' : 'width'; + return { + labels, + sizeSignal: model.child.component.layoutSize.get(sizeType) ? model.child.getSizeSignalRef(sizeType) : undefined, + axes: [] + }; + } + function mergeChildAxis(model, channel) { + const { + child + } = model; + if (child.component.axes[channel]) { + const { + layoutHeaders, + resolve + } = model.component; + resolve.axis[channel] = parseGuideResolve(resolve, channel); + if (resolve.axis[channel] === 'shared') { + // For shared axis, move the axes to facet's header or footer + const headerChannel = channel === 'x' ? 'column' : 'row'; + const layoutHeader = layoutHeaders[headerChannel]; + for (const axisComponent of child.component.axes[channel]) { + const headerType = getHeaderType(axisComponent.get('orient')); + layoutHeader[headerType] ??= [makeHeaderComponent(model, headerChannel, false)]; + + // FIXME: assemble shouldn't be called here, but we do it this way so we only extract the main part of the axes + const mainAxis = assembleAxis(axisComponent, 'main', model.config, { + header: true + }); + if (mainAxis) { + // LayoutHeader no longer keep track of property precedence, thus let's combine. + layoutHeader[headerType][0].axes.push(mainAxis); + } + axisComponent.mainExtracted = true; + } + } + } + } + + function parseLayerLayoutSize(model) { + parseChildrenLayoutSize(model); + parseNonUnitLayoutSizeForChannel(model, 'width'); + parseNonUnitLayoutSizeForChannel(model, 'height'); + } + function parseConcatLayoutSize(model) { + parseChildrenLayoutSize(model); + + // for columns === 1 (vconcat), we can completely merge width. Otherwise, we can treat merged width as childWidth. + const widthType = model.layout.columns === 1 ? 'width' : 'childWidth'; + + // for columns === undefined (hconcat), we can completely merge height. Otherwise, we can treat merged height as childHeight. + const heightType = model.layout.columns === undefined ? 'height' : 'childHeight'; + parseNonUnitLayoutSizeForChannel(model, widthType); + parseNonUnitLayoutSizeForChannel(model, heightType); + } + function parseChildrenLayoutSize(model) { + for (const child of model.children) { + child.parseLayoutSize(); + } + } + + /** + * Merge child layout size (width or height). + */ + function parseNonUnitLayoutSizeForChannel(model, layoutSizeType) { + /* + * For concat, the parent width or height might not be the same as the children's shared height. + * For example, hconcat's subviews may share width, but the shared width is not the hconcat view's width. + * + * layoutSizeType represents the output of the view (could be childWidth/childHeight/width/height) + * while the sizeType represents the properties of the child. + */ + const sizeType = getSizeTypeFromLayoutSizeType(layoutSizeType); + const channel = getPositionScaleChannel(sizeType); + const resolve = model.component.resolve; + const layoutSizeCmpt = model.component.layoutSize; + let mergedSize; + // Try to merge layout size + for (const child of model.children) { + const childSize = child.component.layoutSize.getWithExplicit(sizeType); + const scaleResolve = resolve.scale[channel] ?? defaultScaleResolve(channel, model); + if (scaleResolve === 'independent' && childSize.value === 'step') { + // Do not merge independent scales with range-step as their size depends + // on the scale domains, which can be different between scales. + mergedSize = undefined; + break; + } + if (mergedSize) { + if (scaleResolve === 'independent' && mergedSize.value !== childSize.value) { + // For independent scale, only merge if all the sizes are the same. + // If the values are different, abandon the merge! + mergedSize = undefined; + break; + } + mergedSize = mergeValuesWithExplicit(mergedSize, childSize, sizeType, ''); + } else { + mergedSize = childSize; + } + } + if (mergedSize) { + // If merged, rename size and set size of all children. + for (const child of model.children) { + model.renameSignal(child.getName(sizeType), model.getName(layoutSizeType)); + child.component.layoutSize.set(sizeType, 'merged', false); + } + layoutSizeCmpt.setWithExplicit(layoutSizeType, mergedSize); + } else { + layoutSizeCmpt.setWithExplicit(layoutSizeType, { + explicit: false, + value: undefined + }); + } + } + function parseUnitLayoutSize(model) { + const { + size, + component + } = model; + for (const channel of POSITION_SCALE_CHANNELS) { + const sizeType = getSizeChannel(channel); + if (size[sizeType]) { + const specifiedSize = size[sizeType]; + component.layoutSize.set(sizeType, isStep(specifiedSize) ? 'step' : specifiedSize, true); + } else { + const defaultSize = defaultUnitSize(model, sizeType); + component.layoutSize.set(sizeType, defaultSize, false); + } + } + } + function defaultUnitSize(model, sizeType) { + const channel = sizeType === 'width' ? 'x' : 'y'; + const config = model.config; + const scaleComponent = model.getScaleComponent(channel); + if (scaleComponent) { + const scaleType = scaleComponent.get('type'); + const range = scaleComponent.get('range'); + if (hasDiscreteDomain(scaleType)) { + const size = getViewConfigDiscreteSize(config.view, sizeType); + if (isVgRangeStep(range) || isStep(size)) { + // For discrete domain with range.step, use dynamic width/height + return 'step'; + } else { + return size; + } + } else { + return getViewConfigContinuousSize(config.view, sizeType); + } + } else if (model.hasProjection || model.mark === 'arc') { + // arc should use continuous size by default otherwise the pie is extremely small + return getViewConfigContinuousSize(config.view, sizeType); + } else { + const size = getViewConfigDiscreteSize(config.view, sizeType); + return isStep(size) ? size.step : size; + } + } + + function facetSortFieldName(fieldDef, sort, opt) { + return vgField(sort, { + suffix: `by_${vgField(fieldDef)}`, + ...opt + }); + } + class FacetModel extends ModelWithField { + constructor(spec, parent, parentGivenName, config) { + super(spec, 'facet', parent, parentGivenName, config, spec.resolve); + this.child = buildModel(spec.spec, this, this.getName('child'), undefined, config); + this.children = [this.child]; + this.facet = this.initFacet(spec.facet); + } + initFacet(facet) { + // clone to prevent side effect to the original spec + if (!isFacetMapping(facet)) { + return { + facet: this.initFacetFieldDef(facet, 'facet') + }; + } + const channels = keys(facet); + const normalizedFacet = {}; + for (const channel of channels) { + if (![ROW, COLUMN].includes(channel)) { + // Drop unsupported channel + warn(incompatibleChannel(channel, 'facet')); + break; + } + const fieldDef = facet[channel]; + if (fieldDef.field === undefined) { + warn(emptyFieldDef(fieldDef, channel)); + break; + } + normalizedFacet[channel] = this.initFacetFieldDef(fieldDef, channel); + } + return normalizedFacet; + } + initFacetFieldDef(fieldDef, channel) { + // Cast because we call initFieldDef, which assumes general FieldDef. + // However, FacetFieldDef is a bit more constrained than the general FieldDef + const facetFieldDef = initFieldDef(fieldDef, channel); + if (facetFieldDef.header) { + facetFieldDef.header = replaceExprRef(facetFieldDef.header); + } else if (facetFieldDef.header === null) { + facetFieldDef.header = null; + } + return facetFieldDef; + } + channelHasField(channel) { + return !!this.facet[channel]; + } + fieldDef(channel) { + return this.facet[channel]; + } + parseData() { + this.component.data = parseData(this); + this.child.parseData(); + } + parseLayoutSize() { + parseChildrenLayoutSize(this); + } + parseSelections() { + // As a facet has a single child, the selection components are the same. + // The child maintains its selections to assemble signals, which remain + // within its unit. + this.child.parseSelections(); + this.component.selection = this.child.component.selection; + } + parseMarkGroup() { + this.child.parseMarkGroup(); + } + parseAxesAndHeaders() { + this.child.parseAxesAndHeaders(); + parseFacetHeaders(this); + } + assembleSelectionTopLevelSignals(signals) { + return this.child.assembleSelectionTopLevelSignals(signals); + } + assembleSignals() { + this.child.assembleSignals(); + return []; + } + assembleSelectionData(data) { + return this.child.assembleSelectionData(data); + } + getHeaderLayoutMixins() { + const layoutMixins = {}; + for (const channel of FACET_CHANNELS) { + for (const headerType of HEADER_TYPES) { + const layoutHeaderComponent = this.component.layoutHeaders[channel]; + const headerComponent = layoutHeaderComponent[headerType]; + const { + facetFieldDef + } = layoutHeaderComponent; + if (facetFieldDef) { + const titleOrient = getHeaderProperty('titleOrient', facetFieldDef.header, this.config, channel); + if (['right', 'bottom'].includes(titleOrient)) { + const headerChannel = getHeaderChannel(channel, titleOrient); + layoutMixins.titleAnchor ??= {}; + layoutMixins.titleAnchor[headerChannel] = 'end'; + } + } + if (headerComponent?.[0]) { + // set header/footerBand + const sizeType = channel === 'row' ? 'height' : 'width'; + const bandType = headerType === 'header' ? 'headerBand' : 'footerBand'; + if (channel !== 'facet' && !this.child.component.layoutSize.get(sizeType)) { + // If facet child does not have size signal, then apply headerBand + layoutMixins[bandType] ??= {}; + layoutMixins[bandType][channel] = 0.5; + } + if (layoutHeaderComponent.title) { + layoutMixins.offset ??= {}; + layoutMixins.offset[channel === 'row' ? 'rowTitle' : 'columnTitle'] = 10; + } + } + } + } + return layoutMixins; + } + assembleDefaultLayout() { + const { + column, + row + } = this.facet; + const columns = column ? this.columnDistinctSignal() : row ? 1 : undefined; + let align = 'all'; + + // Do not align the cells if the scale corresponding to the direction is indepent. + // We always align when we facet into both row and column. + if (!row && this.component.resolve.scale.x === 'independent') { + align = 'none'; + } else if (!column && this.component.resolve.scale.y === 'independent') { + align = 'none'; + } + return { + ...this.getHeaderLayoutMixins(), + ...(columns ? { + columns + } : {}), + bounds: 'full', + align + }; + } + assembleLayoutSignals() { + // FIXME(https://github.com/vega/vega-lite/issues/1193): this can be incorrect if we have independent scales. + return this.child.assembleLayoutSignals(); + } + columnDistinctSignal() { + if (this.parent && this.parent instanceof FacetModel) { + // For nested facet, we will add columns to group mark instead + // See discussion in https://github.com/vega/vega/issues/952 + // and https://github.com/vega/vega-view/releases/tag/v1.2.6 + return undefined; + } else { + // In facetNode.assemble(), the name is always this.getName('column') + '_layout'. + const facetLayoutDataName = this.getName('column_domain'); + return { + signal: `length(data('${facetLayoutDataName}'))` + }; + } + } + assembleGroupStyle() { + return undefined; + } + assembleGroup(signals) { + if (this.parent && this.parent instanceof FacetModel) { + // Provide number of columns for layout. + // See discussion in https://github.com/vega/vega/issues/952 + // and https://github.com/vega/vega-view/releases/tag/v1.2.6 + return { + ...(this.channelHasField('column') ? { + encode: { + update: { + // TODO(https://github.com/vega/vega-lite/issues/2759): + // Correct the signal for facet of concat of facet_column + columns: { + field: vgField(this.facet.column, { + prefix: 'distinct' + }) + } + } + } + } : {}), + ...super.assembleGroup(signals) + }; + } + return super.assembleGroup(signals); + } + + /** + * Aggregate cardinality for calculating size + */ + getCardinalityAggregateForChild() { + const fields = []; + const ops = []; + const as = []; + if (this.child instanceof FacetModel) { + if (this.child.channelHasField('column')) { + const field = vgField(this.child.facet.column); + fields.push(field); + ops.push('distinct'); + as.push(`distinct_${field}`); + } + } else { + for (const channel of POSITION_SCALE_CHANNELS) { + const childScaleComponent = this.child.component.scales[channel]; + if (childScaleComponent && !childScaleComponent.merged) { + const type = childScaleComponent.get('type'); + const range = childScaleComponent.get('range'); + if (hasDiscreteDomain(type) && isVgRangeStep(range)) { + const domain = assembleDomain(this.child, channel); + const field = getFieldFromDomain(domain); + if (field) { + fields.push(field); + ops.push('distinct'); + as.push(`distinct_${field}`); + } else { + warn(unknownField(channel)); + } + } + } + } + } + return { + fields, + ops, + as + }; + } + assembleFacet() { + const { + name, + data + } = this.component.data.facetRoot; + const { + row, + column + } = this.facet; + const { + fields, + ops, + as + } = this.getCardinalityAggregateForChild(); + const groupby = []; + for (const channel of FACET_CHANNELS) { + const fieldDef = this.facet[channel]; + if (fieldDef) { + groupby.push(vgField(fieldDef)); + const { + bin, + sort + } = fieldDef; + if (isBinning(bin)) { + groupby.push(vgField(fieldDef, { + binSuffix: 'end' + })); + } + if (isSortField(sort)) { + const { + field, + op = DEFAULT_SORT_OP + } = sort; + const outputName = facetSortFieldName(fieldDef, sort); + if (row && column) { + // For crossed facet, use pre-calculate field as it requires a different groupby + // For each calculated field, apply max and assign them to the same name as + // all values of the same group should be the same anyway. + fields.push(outputName); + ops.push('max'); + as.push(outputName); + } else { + fields.push(field); + ops.push(op); + as.push(outputName); + } + } else if (vega.isArray(sort)) { + const outputName = sortArrayIndexField(fieldDef, channel); + fields.push(outputName); + ops.push('max'); + as.push(outputName); + } + } + } + const cross = !!row && !!column; + return { + name, + data, + groupby, + ...(cross || fields.length > 0 ? { + aggregate: { + ...(cross ? { + cross + } : {}), + ...(fields.length ? { + fields, + ops, + as + } : {}) + } + } : {}) + }; + } + facetSortFields(channel) { + const { + facet + } = this; + const fieldDef = facet[channel]; + if (fieldDef) { + if (isSortField(fieldDef.sort)) { + return [facetSortFieldName(fieldDef, fieldDef.sort, { + expr: 'datum' + })]; + } else if (vega.isArray(fieldDef.sort)) { + return [sortArrayIndexField(fieldDef, channel, { + expr: 'datum' + })]; + } + return [vgField(fieldDef, { + expr: 'datum' + })]; + } + return []; + } + facetSortOrder(channel) { + const { + facet + } = this; + const fieldDef = facet[channel]; + if (fieldDef) { + const { + sort + } = fieldDef; + const order = (isSortField(sort) ? sort.order : !vega.isArray(sort) && sort) || 'ascending'; + return [order]; + } + return []; + } + assembleLabelTitle() { + const { + facet, + config + } = this; + if (facet.facet) { + // Facet always uses title to display labels + return assembleLabelTitle(facet.facet, 'facet', config); + } + const ORTHOGONAL_ORIENT = { + row: ['top', 'bottom'], + column: ['left', 'right'] + }; + for (const channel of HEADER_CHANNELS) { + if (facet[channel]) { + const labelOrient = getHeaderProperty('labelOrient', facet[channel]?.header, config, channel); + if (ORTHOGONAL_ORIENT[channel].includes(labelOrient)) { + // Row/Column with orthogonal labelOrient must use title to display labels + return assembleLabelTitle(facet[channel], channel, config); + } + } + } + return undefined; + } + assembleMarks() { + const { + child + } = this; + + // If we facet by two dimensions, we need to add a cross operator to the aggregation + // so that we create all groups + const facetRoot = this.component.data.facetRoot; + const data = assembleFacetData(facetRoot); + const encodeEntry = child.assembleGroupEncodeEntry(false); + const title = this.assembleLabelTitle() || child.assembleTitle(); + const style = child.assembleGroupStyle(); + const markGroup = { + name: this.getName('cell'), + type: 'group', + ...(title ? { + title + } : {}), + ...(style ? { + style + } : {}), + from: { + facet: this.assembleFacet() + }, + // TODO: move this to after data + sort: { + field: FACET_CHANNELS.map(c => this.facetSortFields(c)).flat(), + order: FACET_CHANNELS.map(c => this.facetSortOrder(c)).flat() + }, + ...(data.length > 0 ? { + data + } : {}), + ...(encodeEntry ? { + encode: { + update: encodeEntry + } + } : {}), + ...child.assembleGroup(assembleFacetSignals(this, [])) + }; + return [markGroup]; + } + getMapping() { + return this.facet; + } + } + + function makeJoinAggregateFromFacet(parent, facet) { + const { + row, + column + } = facet; + if (row && column) { + let newParent = null; + // only need to make one for crossed facet + for (const fieldDef of [row, column]) { + if (isSortField(fieldDef.sort)) { + const { + field, + op = DEFAULT_SORT_OP + } = fieldDef.sort; + parent = newParent = new JoinAggregateTransformNode(parent, { + joinaggregate: [{ + op, + field, + as: facetSortFieldName(fieldDef, fieldDef.sort, { + forAs: true + }) + }], + groupby: [vgField(fieldDef)] + }); + } + } + return newParent; + } + return null; + } + + function findSource(data, sources) { + for (const other of sources) { + const otherData = other.data; + + // if both datasets have a name defined, we cannot merge + if (data.name && other.hasName() && data.name !== other.dataName) { + continue; + } + const formatMesh = data['format']?.mesh; + const otherFeature = otherData.format?.feature; + + // feature and mesh are mutually exclusive + if (formatMesh && otherFeature) { + continue; + } + + // we have to extract the same feature or mesh + const formatFeature = data['format']?.feature; + if ((formatFeature || otherFeature) && formatFeature !== otherFeature) { + continue; + } + const otherMesh = otherData.format?.mesh; + if ((formatMesh || otherMesh) && formatMesh !== otherMesh) { + continue; + } + if (isInlineData(data) && isInlineData(otherData)) { + if (deepEqual(data.values, otherData.values)) { + return other; + } + } else if (isUrlData(data) && isUrlData(otherData)) { + if (data.url === otherData.url) { + return other; + } + } else if (isNamedData(data)) { + if (data.name === other.dataName) { + return other; + } + } + } + return null; + } + function parseRoot(model, sources) { + if (model.data || !model.parent) { + // if the model defines a data source or is the root, create a source node + + if (model.data === null) { + // data: null means we should ignore the parent's data so we just create a new data source + const source = new SourceNode({ + values: [] + }); + sources.push(source); + return source; + } + const existingSource = findSource(model.data, sources); + if (existingSource) { + if (!isGenerator(model.data)) { + existingSource.data.format = mergeDeep({}, model.data.format, existingSource.data.format); + } + + // if the new source has a name but the existing one does not, we can set it + if (!existingSource.hasName() && model.data.name) { + existingSource.dataName = model.data.name; + } + return existingSource; + } else { + const source = new SourceNode(model.data); + sources.push(source); + return source; + } + } else { + // If we don't have a source defined (overriding parent's data), use the parent's facet root or main. + return model.parent.component.data.facetRoot ? model.parent.component.data.facetRoot : model.parent.component.data.main; + } + } + + /** + * Parses a transform array into a chain of connected dataflow nodes. + */ + function parseTransformArray(head, model, ancestorParse) { + let lookupCounter = 0; + for (const t of model.transforms) { + let derivedType = undefined; + let transformNode; + if (isCalculate(t)) { + transformNode = head = new CalculateNode(head, t); + derivedType = 'derived'; + } else if (isFilter(t)) { + const implicit = getImplicitFromFilterTransform(t); + transformNode = head = ParseNode.makeWithAncestors(head, {}, implicit, ancestorParse) ?? head; + head = new FilterNode(head, model, t.filter); + } else if (isBin(t)) { + transformNode = head = BinNode.makeFromTransform(head, t, model); + derivedType = 'number'; + } else if (isTimeUnit(t)) { + derivedType = 'date'; + const parsedAs = ancestorParse.getWithExplicit(t.field); + // Create parse node because the input to time unit is always date. + if (parsedAs.value === undefined) { + head = new ParseNode(head, { + [t.field]: derivedType + }); + ancestorParse.set(t.field, derivedType, false); + } + transformNode = head = TimeUnitNode.makeFromTransform(head, t); + } else if (isAggregate(t)) { + transformNode = head = AggregateNode.makeFromTransform(head, t); + derivedType = 'number'; + if (requiresSelectionId(model)) { + head = new IdentifierNode(head); + } + } else if (isLookup(t)) { + transformNode = head = LookupNode.make(head, model, t, lookupCounter++); + derivedType = 'derived'; + } else if (isWindow(t)) { + transformNode = head = new WindowTransformNode(head, t); + derivedType = 'number'; + } else if (isJoinAggregate(t)) { + transformNode = head = new JoinAggregateTransformNode(head, t); + derivedType = 'number'; + } else if (isStack(t)) { + transformNode = head = StackNode.makeFromTransform(head, t); + derivedType = 'derived'; + } else if (isFold(t)) { + transformNode = head = new FoldTransformNode(head, t); + derivedType = 'derived'; + } else if (isExtent(t)) { + transformNode = head = new ExtentTransformNode(head, t); + derivedType = 'derived'; + } else if (isFlatten(t)) { + transformNode = head = new FlattenTransformNode(head, t); + derivedType = 'derived'; + } else if (isPivot(t)) { + transformNode = head = new PivotTransformNode(head, t); + derivedType = 'derived'; + } else if (isSample(t)) { + head = new SampleTransformNode(head, t); + } else if (isImpute(t)) { + transformNode = head = ImputeNode.makeFromTransform(head, t); + derivedType = 'derived'; + } else if (isDensity(t)) { + transformNode = head = new DensityTransformNode(head, t); + derivedType = 'derived'; + } else if (isQuantile(t)) { + transformNode = head = new QuantileTransformNode(head, t); + derivedType = 'derived'; + } else if (isRegression(t)) { + transformNode = head = new RegressionTransformNode(head, t); + derivedType = 'derived'; + } else if (isLoess(t)) { + transformNode = head = new LoessTransformNode(head, t); + derivedType = 'derived'; + } else { + warn(invalidTransformIgnored(t)); + continue; + } + if (transformNode && derivedType !== undefined) { + for (const field of transformNode.producedFields() ?? []) { + ancestorParse.set(field, derivedType, false); + } + } + } + return head; + } + + /* + Description of the dataflow (http://asciiflow.com/): + +--------+ + | Source | + +---+----+ + | + v + FormatParse + (explicit) + | + v + Transforms + (Filter, Calculate, Binning, TimeUnit, Aggregate, Window, ...) + | + v + FormatParse + (implicit) + | + v + Binning (in `encoding`) + | + v + Timeunit (in `encoding`) + | + v + Formula From Sort Array + | + v + +--+--+ + | Raw | + +-----+ + | + v + Aggregate (in `encoding`) + | + v + Stack (in `encoding`) + | + v + +- - - - - - - - - - -+ + | PreFilterInvalid | - - - -> scale domains + |(when scales need it)| + +- - - - - - - - - - -+ + | + v + Invalid Filter (if the main data source needs it) + | + v + +----------+ + | Main | - - - -> scale domains + +----------+ + | + v + +- - - - - - - - - - -+ + | PostFilterInvalid | - - - -> scale domains + |(when scales need it)| + +- - - - - - - - - - -+ + | + v + +-------+ + | Facet |----> "column", "column-layout", and "row" + +-------+ + | + v + ...Child data... + */ + + function parseData(model) { + let head = parseRoot(model, model.component.data.sources); + const { + outputNodes, + outputNodeRefCounts + } = model.component.data; + const data = model.data; + const newData = data && (isGenerator(data) || isUrlData(data) || isInlineData(data)); + const ancestorParse = !newData && model.parent ? model.parent.component.data.ancestorParse.clone() : new AncestorParse(); + if (isGenerator(data)) { + // insert generator transform + if (isSequenceGenerator(data)) { + head = new SequenceNode(head, data.sequence); + } else if (isGraticuleGenerator(data)) { + head = new GraticuleNode(head, data.graticule); + } + // no parsing necessary for generator + ancestorParse.parseNothing = true; + } else if (data?.format?.parse === null) { + // format.parse: null means disable parsing + ancestorParse.parseNothing = true; + } + head = ParseNode.makeExplicit(head, model, ancestorParse) ?? head; + + // Default discrete selections require an identifer transform to + // uniquely identify data points. Add this transform at the head of + // the pipeline such that the identifier field is available for all + // subsequent datasets. During optimization, we will remove this + // transform if it proves to be unnecessary. Additional identifier + // transforms will be necessary when new tuples are constructed + // (e.g., post-aggregation). + head = new IdentifierNode(head); + + // HACK: This is equivalent for merging bin extent for union scale. + // FIXME(https://github.com/vega/vega-lite/issues/2270): Correctly merge extent / bin node for shared bin scale + const parentIsLayer = model.parent && isLayerModel(model.parent); + if (isUnitModel(model) || isFacetModel(model)) { + if (parentIsLayer) { + head = BinNode.makeFromEncoding(head, model) ?? head; + } + } + if (model.transforms.length > 0) { + head = parseTransformArray(head, model, ancestorParse); + } + + // create parse nodes for fields that need to be parsed (or flattened) implicitly + const implicitSelection = getImplicitFromSelection(model); + const implicitEncoding = getImplicitFromEncoding(model); + head = ParseNode.makeWithAncestors(head, {}, { + ...implicitSelection, + ...implicitEncoding + }, ancestorParse) ?? head; + if (isUnitModel(model)) { + head = GeoJSONNode.parseAll(head, model); + head = GeoPointNode.parseAll(head, model); + } + if (isUnitModel(model) || isFacetModel(model)) { + if (!parentIsLayer) { + head = BinNode.makeFromEncoding(head, model) ?? head; + } + head = TimeUnitNode.makeFromEncoding(head, model) ?? head; + head = CalculateNode.parseAllForSortIndex(head, model); + } + + // add an output node pre aggregation + const raw = head = makeOutputNode(DataSourceType.Raw, model, head); + if (isUnitModel(model)) { + const agg = AggregateNode.makeFromEncoding(head, model); + if (agg) { + head = agg; + if (requiresSelectionId(model)) { + head = new IdentifierNode(head); + } + } + head = ImputeNode.makeFromEncoding(head, model) ?? head; + head = StackNode.makeFromEncoding(head, model) ?? head; + } + let preFilterInvalid; + let dataSourcesForHandlingInvalidValues; + if (isUnitModel(model)) { + const { + markDef, + mark, + config + } = model; + const invalid = getMarkPropOrConfig('invalid', markDef, config); + const { + marks, + scales + } = dataSourcesForHandlingInvalidValues = getDataSourcesForHandlingInvalidValues({ + invalid, + isPath: isPathMark(mark) + }); + if (marks !== scales && scales === 'include-invalid-values') { + // Create a seperate preFilterInvalid dataSource if scales need pre-filter data but marks needs post-filter. + preFilterInvalid = head = makeOutputNode(DataSourceType.PreFilterInvalid, model, head); + } + if (marks === 'exclude-invalid-values') { + head = FilterInvalidNode.make(head, model, dataSourcesForHandlingInvalidValues) ?? head; + } + } + + // output "main" node for marks + const main = head = makeOutputNode(DataSourceType.Main, model, head); + let postFilterInvalid; + if (isUnitModel(model) && dataSourcesForHandlingInvalidValues) { + const { + marks, + scales + } = dataSourcesForHandlingInvalidValues; + if (marks === 'include-invalid-values' && scales === 'exclude-invalid-values') { + // Create a seperate postFilterInvalid dataSource if scales need post-filter data but marks needs pre-filter. + head = FilterInvalidNode.make(head, model, dataSourcesForHandlingInvalidValues) ?? head; + postFilterInvalid = head = makeOutputNode(DataSourceType.PostFilterInvalid, model, head); + } + } + if (isUnitModel(model)) { + materializeSelections(model, main); + } + + // add facet marker + let facetRoot = null; + if (isFacetModel(model)) { + const facetName = model.getName('facet'); + + // Derive new aggregate for facet's sort field + // augment data source with new fields for crossed facet + head = makeJoinAggregateFromFacet(head, model.facet) ?? head; + facetRoot = new FacetNode(head, model, facetName, main.getSource()); + outputNodes[facetName] = facetRoot; + } + return { + ...model.component.data, + outputNodes, + outputNodeRefCounts, + raw, + main, + facetRoot, + ancestorParse, + preFilterInvalid, + postFilterInvalid + }; + } + function makeOutputNode(dataSourceType, model, head) { + const { + outputNodes, + outputNodeRefCounts + } = model.component.data; + const name = model.getDataName(dataSourceType); + const node = new OutputNode(head, name, dataSourceType, outputNodeRefCounts); + outputNodes[name] = node; + return node; + } + + class ConcatModel extends Model { + constructor(spec, parent, parentGivenName, config) { + super(spec, 'concat', parent, parentGivenName, config, spec.resolve); + if (spec.resolve?.axis?.x === 'shared' || spec.resolve?.axis?.y === 'shared') { + warn(CONCAT_CANNOT_SHARE_AXIS); + } + this.children = this.getChildren(spec).map((child, i) => { + return buildModel(child, this, this.getName(`concat_${i}`), undefined, config); + }); + } + parseData() { + this.component.data = parseData(this); + for (const child of this.children) { + child.parseData(); + } + } + parseSelections() { + // Merge selections up the hierarchy so that they may be referenced + // across unit specs. Persist their definitions within each child + // to assemble signals which remain within output Vega unit groups. + this.component.selection = {}; + for (const child of this.children) { + child.parseSelections(); + for (const key of keys(child.component.selection)) { + this.component.selection[key] = child.component.selection[key]; + } + } + } + parseMarkGroup() { + for (const child of this.children) { + child.parseMarkGroup(); + } + } + parseAxesAndHeaders() { + for (const child of this.children) { + child.parseAxesAndHeaders(); + } + + // TODO(#2415): support shared axes + } + getChildren(spec) { + if (isVConcatSpec(spec)) { + return spec.vconcat; + } else if (isHConcatSpec(spec)) { + return spec.hconcat; + } + return spec.concat; + } + parseLayoutSize() { + parseConcatLayoutSize(this); + } + parseAxisGroup() { + return null; + } + assembleSelectionTopLevelSignals(signals) { + return this.children.reduce((sg, child) => child.assembleSelectionTopLevelSignals(sg), signals); + } + assembleSignals() { + this.children.forEach(child => child.assembleSignals()); + return []; + } + assembleLayoutSignals() { + const layoutSignals = assembleLayoutSignals(this); + for (const child of this.children) { + layoutSignals.push(...child.assembleLayoutSignals()); + } + return layoutSignals; + } + assembleSelectionData(data) { + return this.children.reduce((db, child) => child.assembleSelectionData(db), data); + } + assembleMarks() { + // only children have marks + return this.children.map(child => { + const title = child.assembleTitle(); + const style = child.assembleGroupStyle(); + const encodeEntry = child.assembleGroupEncodeEntry(false); + return { + type: 'group', + name: child.getName('group'), + ...(title ? { + title + } : {}), + ...(style ? { + style + } : {}), + ...(encodeEntry ? { + encode: { + update: encodeEntry + } + } : {}), + ...child.assembleGroup() + }; + }); + } + assembleGroupStyle() { + return undefined; + } + assembleDefaultLayout() { + const columns = this.layout.columns; + return { + ...(columns != null ? { + columns + } : {}), + bounds: 'full', + // Use align each so it can work with multiple plots with different size + align: 'each' + }; + } + } + + function isFalseOrNull(v) { + return v === false || v === null; + } + const AXIS_COMPONENT_PROPERTIES_INDEX = { + disable: 1, + gridScale: 1, + scale: 1, + ...COMMON_AXIS_PROPERTIES_INDEX, + labelExpr: 1, + encode: 1 + }; + const AXIS_COMPONENT_PROPERTIES = keys(AXIS_COMPONENT_PROPERTIES_INDEX); + class AxisComponent extends Split { + constructor() { + let explicit = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + let implicit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + let mainExtracted = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + super(); + this.explicit = explicit; + this.implicit = implicit; + this.mainExtracted = mainExtracted; + } + clone() { + return new AxisComponent(duplicate(this.explicit), duplicate(this.implicit), this.mainExtracted); + } + hasAxisPart(part) { + // FIXME(https://github.com/vega/vega-lite/issues/2552) this method can be wrong if users use a Vega theme. + + if (part === 'axis') { + // always has the axis container part + return true; + } + if (part === 'grid' || part === 'title') { + return !!this.get(part); + } + // Other parts are enabled by default, so they should not be false or null. + return !isFalseOrNull(this.get(part)); + } + hasOrientSignalRef() { + return isSignalRef(this.explicit.orient); + } + } + + function labels(model, channel, specifiedLabelsSpec) { + const { + encoding, + config + } = model; + const fieldOrDatumDef = getFieldOrDatumDef(encoding[channel]) ?? getFieldOrDatumDef(encoding[getSecondaryRangeChannel(channel)]); + const axis = model.axis(channel) || {}; + const { + format, + formatType + } = axis; + if (isCustomFormatType(formatType)) { + return { + text: formatCustomType({ + fieldOrDatumDef, + field: 'datum.value', + format, + formatType, + config + }), + ...specifiedLabelsSpec + }; + } else if (format === undefined && formatType === undefined && config.customFormatTypes) { + if (channelDefType(fieldOrDatumDef) === 'quantitative') { + if (isPositionFieldOrDatumDef(fieldOrDatumDef) && fieldOrDatumDef.stack === 'normalize' && config.normalizedNumberFormatType) { + return { + text: formatCustomType({ + fieldOrDatumDef, + field: 'datum.value', + format: config.normalizedNumberFormat, + formatType: config.normalizedNumberFormatType, + config + }), + ...specifiedLabelsSpec + }; + } else if (config.numberFormatType) { + return { + text: formatCustomType({ + fieldOrDatumDef, + field: 'datum.value', + format: config.numberFormat, + formatType: config.numberFormatType, + config + }), + ...specifiedLabelsSpec + }; + } + } + if (channelDefType(fieldOrDatumDef) === 'temporal' && config.timeFormatType && isFieldDef(fieldOrDatumDef) && !fieldOrDatumDef.timeUnit) { + return { + text: formatCustomType({ + fieldOrDatumDef, + field: 'datum.value', + format: config.timeFormat, + formatType: config.timeFormatType, + config + }), + ...specifiedLabelsSpec + }; + } + } + return specifiedLabelsSpec; + } + + function parseUnitAxes(model) { + return POSITION_SCALE_CHANNELS.reduce((axis, channel) => { + if (model.component.scales[channel]) { + axis[channel] = [parseAxis(channel, model)]; + } + return axis; + }, {}); + } + const OPPOSITE_ORIENT = { + bottom: 'top', + top: 'bottom', + left: 'right', + right: 'left' + }; + function parseLayerAxes(model) { + const { + axes, + resolve + } = model.component; + const axisCount = { + top: 0, + bottom: 0, + right: 0, + left: 0 + }; + for (const child of model.children) { + child.parseAxesAndHeaders(); + for (const channel of keys(child.component.axes)) { + resolve.axis[channel] = parseGuideResolve(model.component.resolve, channel); + if (resolve.axis[channel] === 'shared') { + // If the resolve says shared (and has not been overridden) + // We will try to merge and see if there is a conflict + + axes[channel] = mergeAxisComponents(axes[channel], child.component.axes[channel]); + if (!axes[channel]) { + // If merge returns nothing, there is a conflict so we cannot make the axis shared. + // Thus, mark axis as independent and remove the axis component. + resolve.axis[channel] = 'independent'; + delete axes[channel]; + } + } + } + } + + // Move axes to layer's axis component and merge shared axes + for (const channel of POSITION_SCALE_CHANNELS) { + for (const child of model.children) { + if (!child.component.axes[channel]) { + // skip if the child does not have a particular axis + continue; + } + if (resolve.axis[channel] === 'independent') { + // If axes are independent, concat the axisComponent array. + axes[channel] = (axes[channel] ?? []).concat(child.component.axes[channel]); + + // Automatically adjust orient + for (const axisComponent of child.component.axes[channel]) { + const { + value: orient, + explicit + } = axisComponent.getWithExplicit('orient'); + if (isSignalRef(orient)) { + continue; + } + if (axisCount[orient] > 0 && !explicit) { + // Change axis orient if the number do not match + const oppositeOrient = OPPOSITE_ORIENT[orient]; + if (axisCount[orient] > axisCount[oppositeOrient]) { + axisComponent.set('orient', oppositeOrient, false); + } + } + axisCount[orient]++; + + // TODO(https://github.com/vega/vega-lite/issues/2634): automatically add extra offset? + } + } + + // After merging, make sure to remove axes from child + delete child.component.axes[channel]; + } + + // Show gridlines for first axis only for dual-axis chart + if (resolve.axis[channel] === 'independent' && axes[channel] && axes[channel].length > 1) { + for (const [index, axisCmpt] of (axes[channel] || []).entries()) { + if (index > 0 && !!axisCmpt.get('grid') && !axisCmpt.explicit.grid) { + axisCmpt.implicit.grid = false; + } + } + } + } + } + function mergeAxisComponents(mergedAxisCmpts, childAxisCmpts) { + if (mergedAxisCmpts) { + // FIXME: this is a bit wrong once we support multiple axes + if (mergedAxisCmpts.length !== childAxisCmpts.length) { + return undefined; // Cannot merge axis component with different number of axes. + } + const length = mergedAxisCmpts.length; + for (let i = 0; i < length; i++) { + const merged = mergedAxisCmpts[i]; + const child = childAxisCmpts[i]; + if (!!merged !== !!child) { + return undefined; + } else if (merged && child) { + const mergedOrient = merged.getWithExplicit('orient'); + const childOrient = child.getWithExplicit('orient'); + if (mergedOrient.explicit && childOrient.explicit && mergedOrient.value !== childOrient.value) { + // TODO: throw warning if resolve is explicit (We don't have info about explicit/implicit resolve yet.) + + // Cannot merge due to inconsistent orient + return undefined; + } else { + mergedAxisCmpts[i] = mergeAxisComponent(merged, child); + } + } + } + } else { + // For first one, return a copy of the child + return childAxisCmpts.map(axisComponent => axisComponent.clone()); + } + return mergedAxisCmpts; + } + function mergeAxisComponent(merged, child) { + for (const prop of AXIS_COMPONENT_PROPERTIES) { + const mergedValueWithExplicit = mergeValuesWithExplicit(merged.getWithExplicit(prop), child.getWithExplicit(prop), prop, 'axis', + // Tie breaker function + (v1, v2) => { + switch (prop) { + case 'title': + return mergeTitleComponent(v1, v2); + case 'gridScale': + return { + explicit: v1.explicit, + // keep the old explicit + value: getFirstDefined(v1.value, v2.value) + }; + } + return defaultTieBreaker(v1, v2, prop, 'axis'); + }); + merged.setWithExplicit(prop, mergedValueWithExplicit); + } + return merged; + } + function isExplicit(value, property, axis, model, channel) { + if (property === 'disable') { + return axis !== undefined; // if axis is specified or null/false, then its enable/disable state is explicit + } + axis = axis || {}; + switch (property) { + case 'titleAngle': + case 'labelAngle': + return value === (isSignalRef(axis.labelAngle) ? axis.labelAngle : normalizeAngle(axis.labelAngle)); + case 'values': + return !!axis.values; + // specified axis.values is already respected, but may get transformed. + case 'encode': + // both VL axis.encoding and axis.labelAngle affect VG axis.encode + return !!axis.encoding || !!axis.labelAngle; + case 'title': + // title can be explicit if fieldDef.title is set + if (value === getFieldDefTitle(model, channel)) { + return true; + } + } + // Otherwise, things are explicit if the returned value matches the specified property + return value === axis[property]; + } + + /** + * Properties to always include values from config + */ + const propsToAlwaysIncludeConfig = new Set(['grid', + // Grid is an exception because we need to set grid = true to generate another grid axis + 'translate', + // translate has dependent logic for bar's bin position and it's 0.5 by default in Vega. If a config overrides this value, we need to know. + // the rest are not axis configs in Vega, but are in VL, so we need to set too. + 'format', 'formatType', 'orient', 'labelExpr', 'tickCount', 'position', 'tickMinStep']); + function parseAxis(channel, model) { + let axis = model.axis(channel); + const axisComponent = new AxisComponent(); + const fieldOrDatumDef = getFieldOrDatumDef(model.encoding[channel]); + const { + mark, + config + } = model; + const orient = axis?.orient || config[channel === 'x' ? 'axisX' : 'axisY']?.orient || config.axis?.orient || defaultOrient(channel); + const scaleType = model.getScaleComponent(channel).get('type'); + const axisConfigs = getAxisConfigs(channel, scaleType, orient, model.config); + const disable = axis !== undefined ? !axis : getAxisConfig('disable', config.style, axis?.style, axisConfigs).configValue; + axisComponent.set('disable', disable, axis !== undefined); + if (disable) { + return axisComponent; + } + axis = axis || {}; + const labelAngle = getLabelAngle(fieldOrDatumDef, axis, channel, config.style, axisConfigs); + const formatType = guideFormatType(axis.formatType, fieldOrDatumDef, scaleType); + const format = guideFormat(fieldOrDatumDef, fieldOrDatumDef.type, axis.format, axis.formatType, config, true); + const ruleParams = { + fieldOrDatumDef, + axis, + channel, + model, + scaleType, + orient, + labelAngle, + format, + formatType, + mark, + config + }; + // 1.2. Add properties + for (const property of AXIS_COMPONENT_PROPERTIES) { + const value = property in axisRules ? axisRules[property](ruleParams) : isAxisProperty(property) ? axis[property] : undefined; + const hasValue = value !== undefined; + const explicit = isExplicit(value, property, axis, model, channel); + if (hasValue && explicit) { + axisComponent.set(property, value, explicit); + } else { + const { + configValue = undefined, + configFrom = undefined + } = isAxisProperty(property) && property !== 'values' ? getAxisConfig(property, config.style, axis.style, axisConfigs) : {}; + const hasConfigValue = configValue !== undefined; + if (hasValue && !hasConfigValue) { + // only set property if it is explicitly set or has no config value (otherwise we will accidentally override config) + axisComponent.set(property, value, explicit); + } else if ( + // Cases need implicit values + // 1. Axis config that aren't available in Vega + !(configFrom === 'vgAxisConfig') || + // 2. Certain properties are always included (see `propsToAlwaysIncludeConfig`'s declaration for more details) + propsToAlwaysIncludeConfig.has(property) && hasConfigValue || + // 3. Conditional axis values and signals + isConditionalAxisValue(configValue) || isSignalRef(configValue)) { + // If a config is specified and is conditional, copy conditional value from axis config + axisComponent.set(property, configValue, false); + } + } + } + + // 2) Add guide encode definition groups + const axisEncoding = axis.encoding ?? {}; + const axisEncode = AXIS_PARTS.reduce((e, part) => { + if (!axisComponent.hasAxisPart(part)) { + // No need to create encode for a disabled part. + return e; + } + const axisEncodingPart = guideEncodeEntry(axisEncoding[part] ?? {}, model); + const value = part === 'labels' ? labels(model, channel, axisEncodingPart) : axisEncodingPart; + if (value !== undefined && !isEmpty(value)) { + e[part] = { + update: value + }; + } + return e; + }, {}); + + // FIXME: By having encode as one property, we won't have fine grained encode merging. + if (!isEmpty(axisEncode)) { + axisComponent.set('encode', axisEncode, !!axis.encoding || axis.labelAngle !== undefined); + } + return axisComponent; + } + + function initLayoutSize(_ref) { + let { + encoding, + size + } = _ref; + for (const channel of POSITION_SCALE_CHANNELS) { + const sizeType = getSizeChannel(channel); + if (isStep(size[sizeType])) { + if (isContinuousFieldOrDatumDef(encoding[channel])) { + delete size[sizeType]; + warn(stepDropped(sizeType)); + } + } + } + return size; + } + + const arc = { + vgMark: 'arc', + encodeEntry: model => { + return { + ...baseEncodeEntry(model, { + align: 'ignore', + baseline: 'ignore', + color: 'include', + size: 'ignore', + orient: 'ignore', + theta: 'ignore' + }), + ...pointPosition('x', model, { + defaultPos: 'mid' + }), + ...pointPosition('y', model, { + defaultPos: 'mid' + }), + // arcs are rectangles in polar coordinates + ...rectPosition(model, 'radius'), + ...rectPosition(model, 'theta') + }; + } + }; + + const area = { + vgMark: 'area', + encodeEntry: model => { + return { + ...baseEncodeEntry(model, { + align: 'ignore', + baseline: 'ignore', + color: 'include', + orient: 'include', + size: 'ignore', + theta: 'ignore' + }), + ...pointOrRangePosition('x', model, { + defaultPos: 'zeroOrMin', + defaultPos2: 'zeroOrMin', + range: model.markDef.orient === 'horizontal' + }), + ...pointOrRangePosition('y', model, { + defaultPos: 'zeroOrMin', + defaultPos2: 'zeroOrMin', + range: model.markDef.orient === 'vertical' + }), + ...defined(model) + }; + } + }; + + const bar = { + vgMark: 'rect', + encodeEntry: model => { + return { + ...baseEncodeEntry(model, { + align: 'ignore', + baseline: 'ignore', + color: 'include', + orient: 'ignore', + size: 'ignore', + theta: 'ignore' + }), + ...rectPosition(model, 'x'), + ...rectPosition(model, 'y') + }; + } + }; + + const geoshape = { + vgMark: 'shape', + encodeEntry: model => { + return { + ...baseEncodeEntry(model, { + align: 'ignore', + baseline: 'ignore', + color: 'include', + size: 'ignore', + orient: 'ignore', + theta: 'ignore' + }) + }; + }, + postEncodingTransform: model => { + const { + encoding + } = model; + const shapeDef = encoding.shape; + const transform = { + type: 'geoshape', + projection: model.projectionName(), + // as: 'shape', + ...(shapeDef && isFieldDef(shapeDef) && shapeDef.type === GEOJSON ? { + field: vgField(shapeDef, { + expr: 'datum' + }) + } : {}) + }; + return [transform]; + } + }; + + const image = { + vgMark: 'image', + encodeEntry: model => { + return { + ...baseEncodeEntry(model, { + align: 'ignore', + baseline: 'ignore', + color: 'ignore', + orient: 'ignore', + size: 'ignore', + theta: 'ignore' + }), + ...rectPosition(model, 'x'), + ...rectPosition(model, 'y'), + ...text$1(model, 'url') + }; + } + }; + + const line = { + vgMark: 'line', + encodeEntry: model => { + return { + ...baseEncodeEntry(model, { + align: 'ignore', + baseline: 'ignore', + color: 'include', + size: 'ignore', + orient: 'ignore', + theta: 'ignore' + }), + ...pointPosition('x', model, { + defaultPos: 'mid' + }), + ...pointPosition('y', model, { + defaultPos: 'mid' + }), + ...nonPosition('size', model, { + vgChannel: 'strokeWidth' // VL's line size is strokeWidth + }), + ...defined(model) + }; + } + }; + const trail = { + vgMark: 'trail', + encodeEntry: model => { + return { + ...baseEncodeEntry(model, { + align: 'ignore', + baseline: 'ignore', + color: 'include', + size: 'include', + orient: 'ignore', + theta: 'ignore' + }), + ...pointPosition('x', model, { + defaultPos: 'mid' + }), + ...pointPosition('y', model, { + defaultPos: 'mid' + }), + ...nonPosition('size', model), + ...defined(model) + }; + } + }; + + function encodeEntry(model, fixedShape) { + const { + config + } = model; + return { + ...baseEncodeEntry(model, { + align: 'ignore', + baseline: 'ignore', + color: 'include', + size: 'include', + orient: 'ignore', + theta: 'ignore' + }), + ...pointPosition('x', model, { + defaultPos: 'mid' + }), + ...pointPosition('y', model, { + defaultPos: 'mid' + }), + ...nonPosition('size', model), + ...nonPosition('angle', model), + ...shapeMixins(model, config, fixedShape) + }; + } + function shapeMixins(model, config, fixedShape) { + if (fixedShape) { + return { + shape: { + value: fixedShape + } + }; + } + return nonPosition('shape', model); + } + const point = { + vgMark: 'symbol', + encodeEntry: model => { + return encodeEntry(model); + } + }; + const circle = { + vgMark: 'symbol', + encodeEntry: model => { + return encodeEntry(model, 'circle'); + } + }; + const square = { + vgMark: 'symbol', + encodeEntry: model => { + return encodeEntry(model, 'square'); + } + }; + + const rect = { + vgMark: 'rect', + encodeEntry: model => { + return { + ...baseEncodeEntry(model, { + align: 'ignore', + baseline: 'ignore', + color: 'include', + orient: 'ignore', + size: 'ignore', + theta: 'ignore' + }), + ...rectPosition(model, 'x'), + ...rectPosition(model, 'y') + }; + } + }; + + const rule = { + vgMark: 'rule', + encodeEntry: model => { + const { + markDef + } = model; + const orient = markDef.orient; + if (!model.encoding.x && !model.encoding.y && !model.encoding.latitude && !model.encoding.longitude) { + // Show nothing if we have none of x, y, lat, and long. + return {}; + } + return { + ...baseEncodeEntry(model, { + align: 'ignore', + baseline: 'ignore', + color: 'include', + orient: 'ignore', + size: 'ignore', + theta: 'ignore' + }), + ...pointOrRangePosition('x', model, { + defaultPos: orient === 'horizontal' ? 'zeroOrMax' : 'mid', + defaultPos2: 'zeroOrMin', + range: orient !== 'vertical' // include x2 for horizontal or line segment rule + }), + ...pointOrRangePosition('y', model, { + defaultPos: orient === 'vertical' ? 'zeroOrMax' : 'mid', + defaultPos2: 'zeroOrMin', + range: orient !== 'horizontal' // include y2 for vertical or line segment rule + }), + ...nonPosition('size', model, { + vgChannel: 'strokeWidth' // VL's rule size is strokeWidth + }) + }; + } + }; + + const text = { + vgMark: 'text', + encodeEntry: model => { + const { + config, + encoding + } = model; + return { + ...baseEncodeEntry(model, { + align: 'include', + baseline: 'include', + color: 'include', + size: 'ignore', + orient: 'ignore', + theta: 'include' + }), + ...pointPosition('x', model, { + defaultPos: 'mid' + }), + ...pointPosition('y', model, { + defaultPos: 'mid' + }), + ...text$1(model), + ...nonPosition('size', model, { + vgChannel: 'fontSize' // VL's text size is fontSize + }), + ...nonPosition('angle', model), + ...valueIfDefined('align', align(model.markDef, encoding, config)), + ...valueIfDefined('baseline', baseline(model.markDef, encoding, config)), + ...pointPosition('radius', model, { + defaultPos: null + }), + ...pointPosition('theta', model, { + defaultPos: null + }) + }; + } + }; + function align(markDef, encoding, config) { + const a = getMarkPropOrConfig('align', markDef, config); + if (a === undefined) { + return 'center'; + } + // If there is a config, Vega-parser will process this already. + return undefined; + } + function baseline(markDef, encoding, config) { + const b = getMarkPropOrConfig('baseline', markDef, config); + if (b === undefined) { + return 'middle'; + } + // If there is a config, Vega-parser will process this already. + return undefined; + } + + const tick = { + vgMark: 'rect', + encodeEntry: model => { + const { + config, + markDef + } = model; + const orient = markDef.orient; + const vgSizeChannel = orient === 'horizontal' ? 'width' : 'height'; + const vgThicknessChannel = orient === 'horizontal' ? 'height' : 'width'; + return { + ...baseEncodeEntry(model, { + align: 'ignore', + baseline: 'ignore', + color: 'include', + orient: 'ignore', + size: 'ignore', + theta: 'ignore' + }), + ...pointPosition('x', model, { + defaultPos: 'mid', + vgChannel: 'xc' + }), + ...pointPosition('y', model, { + defaultPos: 'mid', + vgChannel: 'yc' + }), + // size / thickness => width / height + ...nonPosition('size', model, { + defaultRef: defaultSize(model), + vgChannel: vgSizeChannel + }), + [vgThicknessChannel]: signalOrValueRef(getMarkPropOrConfig('thickness', markDef, config)) + }; + } + }; + function defaultSize(model) { + const { + config, + markDef + } = model; + const { + orient + } = markDef; + const vgSizeChannel = orient === 'horizontal' ? 'width' : 'height'; + const positionChannel = orient === 'horizontal' ? 'x' : 'y'; + const offsetScaleChannel = getOffsetScaleChannel(positionChannel); + + // Use offset scale if exists + const scale = model.getScaleComponent(offsetScaleChannel) || model.getScaleComponent(positionChannel); + const scaleName = model.scaleName(offsetScaleChannel) || model.scaleName(positionChannel); + const markPropOrConfig = getMarkPropOrConfig('size', markDef, config, { + vgChannel: vgSizeChannel + }) ?? config.tick.bandSize; + if (markPropOrConfig !== undefined) { + return signalOrValueRef(markPropOrConfig); + } else if (scale?.get('type') === 'band') { + return { + scale: scaleName, + band: 1 + }; + } + const scaleRange = scale?.get('range'); + const { + tickBandPaddingInner + } = config.scale; + const step = scaleRange && isVgRangeStep(scaleRange) ? scaleRange.step : getViewConfigDiscreteStep(config.view, vgSizeChannel); + if (vega.isNumber(step) && vega.isNumber(tickBandPaddingInner)) { + return { + value: step * (1 - tickBandPaddingInner) + }; + } else { + return { + signal: `${exprFromSignalRefOrValue(tickBandPaddingInner)} * ${exprFromSignalRefOrValue(step)}` + }; + } + } + + const markCompiler = { + arc, + area, + bar, + circle, + geoshape, + image, + line, + point, + rect, + rule, + square, + text, + tick, + trail + }; + function parseMarkGroups(model) { + if (contains([LINE, AREA, TRAIL], model.mark)) { + const details = pathGroupingFields(model.mark, model.encoding); + if (details.length > 0) { + return getPathGroups(model, details); + } + // otherwise use standard mark groups + } else if (model.mark === BAR) { + const hasCornerRadius = VG_CORNERRADIUS_CHANNELS.some(prop => getMarkPropOrConfig(prop, model.markDef, model.config)); + if (model.stack && !model.fieldDef('size') && hasCornerRadius) { + return getGroupsForStackedBarWithCornerRadius(model); + } + } + return getMarkGroup(model); + } + const FACETED_PATH_PREFIX = 'faceted_path_'; + function getPathGroups(model, details) { + // TODO: for non-stacked plot, map order to zindex. (Maybe rename order for layer to zindex?) + + return [{ + name: model.getName('pathgroup'), + type: 'group', + from: { + facet: { + name: FACETED_PATH_PREFIX + model.requestDataName(DataSourceType.Main), + data: model.requestDataName(DataSourceType.Main), + groupby: details + } + }, + encode: { + update: { + width: { + field: { + group: 'width' + } + }, + height: { + field: { + group: 'height' + } + } + } + }, + // With subfacet for line/area group, need to use faceted data from above. + marks: getMarkGroup(model, { + fromPrefix: FACETED_PATH_PREFIX + }) + }]; + } + const STACK_GROUP_PREFIX = 'stack_group_'; + + /** + * We need to put stacked bars into groups in order to enable cornerRadius for stacks. + * If stack is used and the model doesn't have size encoding, we put the mark into groups, + * and apply cornerRadius properties at the group. + */ + function getGroupsForStackedBarWithCornerRadius(model) { + // Generate the mark + const [mark] = getMarkGroup(model, { + fromPrefix: STACK_GROUP_PREFIX + }); + + // Get the scale for the stacked field + const fieldScale = model.scaleName(model.stack.fieldChannel); + const stackField = function () { + let opt = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + return model.vgField(model.stack.fieldChannel, opt); + }; + // Find the min/max of the pixel value on the stacked direction + const stackFieldGroup = (func, expr) => { + const vgFieldMinMax = [stackField({ + prefix: 'min', + suffix: 'start', + expr + }), stackField({ + prefix: 'max', + suffix: 'start', + expr + }), stackField({ + prefix: 'min', + suffix: 'end', + expr + }), stackField({ + prefix: 'max', + suffix: 'end', + expr + })]; + return `${func}(${vgFieldMinMax.map(field => `scale('${fieldScale}',${field})`).join(',')})`; + }; + let groupUpdate; + let innerGroupUpdate; + + // Build the encoding for group and an inner group + if (model.stack.fieldChannel === 'x') { + // Move cornerRadius, y/yc/y2/height properties to group + // Group x/x2 should be the min/max of the marks within + groupUpdate = { + ...pick(mark.encode.update, ['y', 'yc', 'y2', 'height', ...VG_CORNERRADIUS_CHANNELS]), + x: { + signal: stackFieldGroup('min', 'datum') + }, + x2: { + signal: stackFieldGroup('max', 'datum') + }, + clip: { + value: true + } + }; + // Inner group should revert the x translation, and pass height through + innerGroupUpdate = { + x: { + field: { + group: 'x' + }, + mult: -1 + }, + height: { + field: { + group: 'height' + } + } + }; + // The marks should use the same height as group, without y/yc/y2 properties (because it's already done by group) + // This is why size encoding is not supported yet + mark.encode.update = { + ...omit(mark.encode.update, ['y', 'yc', 'y2']), + height: { + field: { + group: 'height' + } + } + }; + } else { + groupUpdate = { + ...pick(mark.encode.update, ['x', 'xc', 'x2', 'width']), + y: { + signal: stackFieldGroup('min', 'datum') + }, + y2: { + signal: stackFieldGroup('max', 'datum') + }, + clip: { + value: true + } + }; + innerGroupUpdate = { + y: { + field: { + group: 'y' + }, + mult: -1 + }, + width: { + field: { + group: 'width' + } + } + }; + mark.encode.update = { + ...omit(mark.encode.update, ['x', 'xc', 'x2']), + width: { + field: { + group: 'width' + } + } + }; + } + + // Deal with cornerRadius properties + for (const key of VG_CORNERRADIUS_CHANNELS) { + const configValue = getMarkConfig(key, model.markDef, model.config); + // Move from mark to group + if (mark.encode.update[key]) { + groupUpdate[key] = mark.encode.update[key]; + delete mark.encode.update[key]; + } else if (configValue) { + groupUpdate[key] = signalOrValueRef(configValue); + } + // Overwrite any cornerRadius on mark set by config --- they are already moved to the group + if (configValue) { + mark.encode.update[key] = { + value: 0 + }; + } + } + const groupby = []; + if (model.stack.groupbyChannels?.length > 0) { + for (const groupbyChannel of model.stack.groupbyChannels) { + // For bin and time unit, we have to add bin/timeunit -end channels. + const groupByField = model.fieldDef(groupbyChannel); + const field = vgField(groupByField); + if (field) { + groupby.push(field); + } + if (groupByField?.bin || groupByField?.timeUnit) { + groupby.push(vgField(groupByField, { + binSuffix: 'end' + })); + } + } + } + const strokeProperties = ['stroke', 'strokeWidth', 'strokeJoin', 'strokeCap', 'strokeDash', 'strokeDashOffset', 'strokeMiterLimit', 'strokeOpacity']; + + // Generate stroke properties for the group + groupUpdate = strokeProperties.reduce((encode, prop) => { + if (mark.encode.update[prop]) { + return { + ...encode, + [prop]: mark.encode.update[prop] + }; + } else { + const configValue = getMarkConfig(prop, model.markDef, model.config); + if (configValue !== undefined) { + return { + ...encode, + [prop]: signalOrValueRef(configValue) + }; + } else { + return encode; + } + } + }, groupUpdate); + + // Apply strokeForeground and strokeOffset if stroke is used + if (groupUpdate.stroke) { + groupUpdate.strokeForeground = { + value: true + }; + groupUpdate.strokeOffset = { + value: 0 + }; + } + return [{ + type: 'group', + from: { + facet: { + data: model.requestDataName(DataSourceType.Main), + name: STACK_GROUP_PREFIX + model.requestDataName(DataSourceType.Main), + groupby, + aggregate: { + fields: [stackField({ + suffix: 'start' + }), stackField({ + suffix: 'start' + }), stackField({ + suffix: 'end' + }), stackField({ + suffix: 'end' + })], + ops: ['min', 'max', 'min', 'max'] + } + } + }, + encode: { + update: groupUpdate + }, + marks: [{ + type: 'group', + encode: { + update: innerGroupUpdate + }, + marks: [mark] + }] + }]; + } + function getSort(model) { + const { + encoding, + stack, + mark, + markDef, + config + } = model; + const order = encoding.order; + if (!vega.isArray(order) && isValueDef(order) && isNullOrFalse(order.value) || !order && isNullOrFalse(getMarkPropOrConfig('order', markDef, config))) { + return undefined; + } else if ((vega.isArray(order) || isFieldDef(order)) && !stack) { + // Sort by the order field if it is specified and the field is not stacked. (For stacked field, order specify stack order.) + return sortParams(order, { + expr: 'datum' + }); + } else if (isPathMark(mark)) { + // For both line and area, we sort values based on dimension by default + const dimensionChannel = markDef.orient === 'horizontal' ? 'y' : 'x'; + const dimensionChannelDef = encoding[dimensionChannel]; + if (isFieldDef(dimensionChannelDef)) { + return { + field: dimensionChannel + }; + } + } + return undefined; + } + function getMarkGroup(model) { + let opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { + fromPrefix: '' + }; + const { + mark, + markDef, + encoding, + config + } = model; + const clip = getFirstDefined(markDef.clip, scaleClip(model), projectionClip(model)); + const style = getStyles(markDef); + const key = encoding.key; + const sort = getSort(model); + const interactive = interactiveFlag(model); + const aria = getMarkPropOrConfig('aria', markDef, config); + const postEncodingTransform = markCompiler[mark].postEncodingTransform ? markCompiler[mark].postEncodingTransform(model) : null; + return [{ + name: model.getName('marks'), + type: markCompiler[mark].vgMark, + ...(clip ? { + clip + } : {}), + ...(style ? { + style + } : {}), + ...(key ? { + key: key.field + } : {}), + ...(sort ? { + sort + } : {}), + ...(interactive ? interactive : {}), + ...(aria === false ? { + aria + } : {}), + from: { + data: opt.fromPrefix + model.requestDataName(DataSourceType.Main) + }, + encode: { + update: markCompiler[mark].encodeEntry(model) + }, + ...(postEncodingTransform ? { + transform: postEncodingTransform + } : {}) + }]; + } + + /** + * If scales are bound to interval selections, we want to automatically clip + * marks to account for panning/zooming interactions. We identify bound scales + * by the selectionExtent property, which gets added during scale parsing. + */ + function scaleClip(model) { + const xScale = model.getScaleComponent('x'); + const yScale = model.getScaleComponent('y'); + return xScale?.get('selectionExtent') || yScale?.get('selectionExtent') ? true : undefined; + } + + /** + * If we use a custom projection with auto-fitting to the geodata extent, + * we need to clip to ensure the chart size doesn't explode. + */ + function projectionClip(model) { + const projection = model.component.projection; + return projection && !projection.isFit ? true : undefined; + } + + /** + * Only output interactive flags if we have selections defined somewhere in our model hierarchy. + */ + function interactiveFlag(model) { + if (!model.component.selection) return null; + const unitCount = keys(model.component.selection).length; + let parentCount = unitCount; + let parent = model.parent; + while (parent && parentCount === 0) { + parentCount = keys(parent.component.selection).length; + parent = parent.parent; + } + return parentCount ? { + interactive: unitCount > 0 || model.mark === 'geoshape' || !!model.encoding.tooltip || !!model.markDef.tooltip + } : null; + } + + /** + * Internal model of Vega-Lite specification for the compiler. + */ + class UnitModel extends ModelWithField { + specifiedScales = {}; + specifiedAxes = {}; + specifiedLegends = {}; + specifiedProjection = {}; + selection = []; + children = []; + constructor(spec, parent, parentGivenName) { + let parentGivenSize = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + let config = arguments.length > 4 ? arguments[4] : undefined; + super(spec, 'unit', parent, parentGivenName, config, undefined, isFrameMixins(spec) ? spec.view : undefined); + const markDef = isMarkDef(spec.mark) ? { + ...spec.mark + } : { + type: spec.mark + }; + const mark = markDef.type; + + // Need to init filled before other mark properties because encoding depends on filled but other mark properties depend on types inside encoding + if (markDef.filled === undefined) { + markDef.filled = defaultFilled(markDef, config, { + graticule: spec.data && isGraticuleGenerator(spec.data) + }); + } + const encoding = this.encoding = initEncoding(spec.encoding || {}, mark, markDef.filled, config); + this.markDef = initMarkdef(markDef, encoding, config); + this.size = initLayoutSize({ + encoding, + size: isFrameMixins(spec) ? { + ...parentGivenSize, + ...(spec.width ? { + width: spec.width + } : {}), + ...(spec.height ? { + height: spec.height + } : {}) + } : parentGivenSize + }); + + // calculate stack properties + this.stack = stack(this.markDef, encoding); + this.specifiedScales = this.initScales(mark, encoding); + this.specifiedAxes = this.initAxes(encoding); + this.specifiedLegends = this.initLegends(encoding); + this.specifiedProjection = spec.projection; + + // Selections will be initialized upon parse. + this.selection = (spec.params ?? []).filter(p => isSelectionParameter(p)); + } + get hasProjection() { + const { + encoding + } = this; + const isGeoShapeMark = this.mark === GEOSHAPE; + const hasGeoPosition = encoding && GEOPOSITION_CHANNELS.some(channel => isFieldOrDatumDef(encoding[channel])); + return isGeoShapeMark || hasGeoPosition; + } + + /** + * Return specified Vega-Lite scale domain for a particular channel + * @param channel + */ + scaleDomain(channel) { + const scale = this.specifiedScales[channel]; + return scale ? scale.domain : undefined; + } + axis(channel) { + return this.specifiedAxes[channel]; + } + legend(channel) { + return this.specifiedLegends[channel]; + } + initScales(mark, encoding) { + return SCALE_CHANNELS.reduce((scales, channel) => { + const fieldOrDatumDef = getFieldOrDatumDef(encoding[channel]); + if (fieldOrDatumDef) { + scales[channel] = this.initScale(fieldOrDatumDef.scale ?? {}); + } + return scales; + }, {}); + } + initScale(scale) { + const { + domain, + range + } = scale; + // TODO: we could simplify this function if we had a recursive replace function + const scaleInternal = replaceExprRef(scale); + if (vega.isArray(domain)) { + scaleInternal.domain = domain.map(signalRefOrValue); + } + if (vega.isArray(range)) { + scaleInternal.range = range.map(signalRefOrValue); + } + return scaleInternal; + } + initAxes(encoding) { + return POSITION_SCALE_CHANNELS.reduce((_axis, channel) => { + // Position Axis + + // TODO: handle ConditionFieldDef + const channelDef = encoding[channel]; + if (isFieldOrDatumDef(channelDef) || channel === X && isFieldOrDatumDef(encoding.x2) || channel === Y && isFieldOrDatumDef(encoding.y2)) { + const axisSpec = isFieldOrDatumDef(channelDef) ? channelDef.axis : undefined; + _axis[channel] = axisSpec ? this.initAxis({ + ...axisSpec + }) // convert truthy value to object + : axisSpec; + } + return _axis; + }, {}); + } + initAxis(axis) { + const props = keys(axis); + const axisInternal = {}; + for (const prop of props) { + const val = axis[prop]; + axisInternal[prop] = isConditionalAxisValue(val) ? signalOrValueRefWithCondition(val) : signalRefOrValue(val); + } + return axisInternal; + } + initLegends(encoding) { + return NONPOSITION_SCALE_CHANNELS.reduce((_legend, channel) => { + const fieldOrDatumDef = getFieldOrDatumDef(encoding[channel]); + if (fieldOrDatumDef && supportLegend(channel)) { + const legend = fieldOrDatumDef.legend; + _legend[channel] = legend ? replaceExprRef(legend) // convert truthy value to object + : legend; + } + return _legend; + }, {}); + } + parseData() { + this.component.data = parseData(this); + } + parseLayoutSize() { + parseUnitLayoutSize(this); + } + parseSelections() { + this.component.selection = parseUnitSelection(this, this.selection); + } + parseMarkGroup() { + this.component.mark = parseMarkGroups(this); + } + parseAxesAndHeaders() { + this.component.axes = parseUnitAxes(this); + } + assembleSelectionTopLevelSignals(signals) { + return assembleTopLevelSignals(this, signals); + } + assembleSignals() { + return [...assembleAxisSignals(this), ...assembleUnitSelectionSignals(this, [])]; + } + assembleSelectionData(data) { + return assembleUnitSelectionData(this, data); + } + assembleLayout() { + return null; + } + assembleLayoutSignals() { + return assembleLayoutSignals(this); + } + assembleMarks() { + let marks = this.component.mark ?? []; + + // If this unit is part of a layer, selections should augment + // all in concert rather than each unit individually. This + // ensures correct interleaving of clipping and brushed marks. + if (!this.parent || !isLayerModel(this.parent)) { + marks = assembleUnitSelectionMarks(this, marks); + } + return marks.map(this.correctDataNames); + } + assembleGroupStyle() { + const { + style + } = this.view || {}; + if (style !== undefined) { + return style; + } + if (this.encoding.x || this.encoding.y) { + return 'cell'; + } else { + return 'view'; + } + } + getMapping() { + return this.encoding; + } + get mark() { + return this.markDef.type; + } + channelHasField(channel) { + return channelHasField(this.encoding, channel); + } + fieldDef(channel) { + const channelDef = this.encoding[channel]; + return getFieldDef(channelDef); + } + typedFieldDef(channel) { + const fieldDef = this.fieldDef(channel); + if (isTypedFieldDef(fieldDef)) { + return fieldDef; + } + return null; + } + } + + class LayerModel extends Model { + // HACK: This should be (LayerModel | UnitModel)[], but setting the correct type leads to weird error. + // So I'm just putting generic Model for now + + constructor(spec, parent, parentGivenName, parentGivenSize, config) { + super(spec, 'layer', parent, parentGivenName, config, spec.resolve, spec.view); + const layoutSize = { + ...parentGivenSize, + ...(spec.width ? { + width: spec.width + } : {}), + ...(spec.height ? { + height: spec.height + } : {}) + }; + this.children = spec.layer.map((layer, i) => { + if (isLayerSpec(layer)) { + return new LayerModel(layer, this, this.getName(`layer_${i}`), layoutSize, config); + } else if (isUnitSpec(layer)) { + return new UnitModel(layer, this, this.getName(`layer_${i}`), layoutSize, config); + } + throw new Error(invalidSpec(layer)); + }); + } + parseData() { + this.component.data = parseData(this); + for (const child of this.children) { + child.parseData(); + } + } + parseLayoutSize() { + parseLayerLayoutSize(this); + } + parseSelections() { + // Merge selections up the hierarchy so that they may be referenced + // across unit specs. Persist their definitions within each child + // to assemble signals which remain within output Vega unit groups. + this.component.selection = {}; + for (const child of this.children) { + child.parseSelections(); + for (const key of keys(child.component.selection)) { + this.component.selection[key] = child.component.selection[key]; + } + } + } + parseMarkGroup() { + for (const child of this.children) { + child.parseMarkGroup(); + } + } + parseAxesAndHeaders() { + parseLayerAxes(this); + } + assembleSelectionTopLevelSignals(signals) { + return this.children.reduce((sg, child) => child.assembleSelectionTopLevelSignals(sg), signals); + } + + // TODO: Support same named selections across children. + assembleSignals() { + return this.children.reduce((signals, child) => { + return signals.concat(child.assembleSignals()); + }, assembleAxisSignals(this)); + } + assembleLayoutSignals() { + return this.children.reduce((signals, child) => { + return signals.concat(child.assembleLayoutSignals()); + }, assembleLayoutSignals(this)); + } + assembleSelectionData(data) { + return this.children.reduce((db, child) => child.assembleSelectionData(db), data); + } + assembleGroupStyle() { + const uniqueStyles = new Set(); + for (const child of this.children) { + for (const style of vega.array(child.assembleGroupStyle())) { + uniqueStyles.add(style); + } + } + const styles = Array.from(uniqueStyles); + return styles.length > 1 ? styles : styles.length === 1 ? styles[0] : undefined; + } + assembleTitle() { + let title = super.assembleTitle(); + if (title) { + return title; + } + // If title does not provide layer, look into children + for (const child of this.children) { + title = child.assembleTitle(); + if (title) { + return title; + } + } + return undefined; + } + assembleLayout() { + return null; + } + assembleMarks() { + return assembleLayerSelectionMarks(this, this.children.flatMap(child => { + return child.assembleMarks(); + })); + } + assembleLegends() { + return this.children.reduce((legends, child) => { + return legends.concat(child.assembleLegends()); + }, assembleLegends(this)); + } + } + + function buildModel(spec, parent, parentGivenName, unitSize, config) { + if (isFacetSpec(spec)) { + return new FacetModel(spec, parent, parentGivenName, config); + } else if (isLayerSpec(spec)) { + return new LayerModel(spec, parent, parentGivenName, unitSize, config); + } else if (isUnitSpec(spec)) { + return new UnitModel(spec, parent, parentGivenName, unitSize, config); + } else if (isAnyConcatSpec(spec)) { + return new ConcatModel(spec, parent, parentGivenName, config); + } + throw new Error(invalidSpec(spec)); + } + + /** + * Vega-Lite's main function, for compiling Vega-Lite spec into Vega spec. + * + * At a high-level, we make the following transformations in different phases: + * + * Input spec + * | + * | (Normalization) + * v + * Normalized Spec (Row/Column channels in single-view specs becomes faceted specs, composite marks becomes layered specs.) + * | + * | (Build Model) + * v + * A model tree of the spec + * | + * | (Parse) + * v + * A model tree with parsed components (intermediate structure of visualization primitives in a format that can be easily merged) + * | + * | (Optimize) + * v + * A model tree with parsed components with the data component optimized + * | + * | (Assemble) + * v + * Vega spec + * + * @param inputSpec The Vega-Lite specification. + * @param opt Optional arguments passed to the Vega-Lite compiler. + * @returns An object containing the compiled Vega spec and normalized Vega-Lite spec. + */ + function compile(inputSpec) { + let opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + // 0. Augment opt with default opts + if (opt.logger) { + // set the singleton logger to the provided logger + set(opt.logger); + } + if (opt.fieldTitle) { + // set the singleton field title formatter + setTitleFormatter(opt.fieldTitle); + } + try { + // 1. Initialize config by deep merging default config with the config provided via option and the input spec. + const config = initConfig(vega.mergeConfig(opt.config, inputSpec.config)); + + // 2. Normalize: Convert input spec -> normalized spec + + // - 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. + // - Normalize autosize and width or height spec + const spec = normalize(inputSpec, config); + + // 3. Build Model: normalized spec -> Model (a tree structure) + + // 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. + // See the abstract `Model` class and its children (UnitModel, LayerModel, FacetModel, ConcatModel) for different types of models. + const model = buildModel(spec, null, '', undefined, config); + + // 4 Parse: Model --> Model with components + + // Note that components = intermediate representations that are equivalent to Vega specs. + // We need these intermediate representation because we need to merge many visualization "components" like projections, scales, axes, and legends. + // We will later convert these components into actual Vega specs in the assemble phase. + + // In this phase, we do a bottom-up traversal over the whole tree to + // parse for each type of components once (e.g., data, layout, mark, scale). + // By doing bottom-up traversal, we start parsing components of unit specs and + // then merge child components of parent composite specs. + // + // Please see inside model.parse() for order of different components parsed. + model.parse(); + + // drawDataflow(model.component.data.sources); + + // 5. Optimize the dataflow. This will modify the data component of the model. + optimizeDataflow(model.component.data, model); + + // drawDataflow(model.component.data.sources); + + // 6. Assemble: convert model components --> Vega Spec. + const vgSpec = assembleTopLevelModel(model, getTopLevelProperties(inputSpec, spec.autosize, config, model), inputSpec.datasets, inputSpec.usermeta); + return { + spec: vgSpec, + normalized: spec + }; + } finally { + // Reset the singleton logger if a logger is provided + if (opt.logger) { + reset(); + } + // Reset the singleton field title formatter if provided + if (opt.fieldTitle) { + resetTitleFormatter(); + } + } + } + function getTopLevelProperties(inputSpec, autosize, config, model) { + const width = model.component.layoutSize.get('width'); + const height = model.component.layoutSize.get('height'); + if (autosize === undefined) { + autosize = { + type: 'pad' + }; + if (model.hasAxisOrientSignalRef()) { + autosize.resize = true; + } + } else if (vega.isString(autosize)) { + autosize = { + type: autosize + }; + } + if (width && height && isFitType(autosize.type)) { + if (width === 'step' && height === 'step') { + warn(droppingFit()); + autosize.type = 'pad'; + } else if (width === 'step' || height === 'step') { + // effectively XOR, because else if + + // get step dimension + const sizeType = width === 'step' ? 'width' : 'height'; + // log that we're dropping fit for respective channel + warn(droppingFit(getPositionScaleChannel(sizeType))); + + // setting type to inverse fit (so if we dropped fit-x, type is now fit-y) + const inverseSizeType = sizeType === 'width' ? 'height' : 'width'; + autosize.type = getFitType(inverseSizeType); + } + } + return { + ...(keys(autosize).length === 1 && autosize.type ? autosize.type === 'pad' ? {} : { + autosize: autosize.type + } : { + autosize + }), + ...extractTopLevelProperties(config, false), + ...extractTopLevelProperties(inputSpec, true) + }; + } + + /* + * Assemble the top-level model to a Vega spec. + * + * Note: this couldn't be `model.assemble()` since the top-level model + * needs some special treatment to generate top-level properties. + */ + function assembleTopLevelModel(model, topLevelProperties) { + let datasets = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + let usermeta = arguments.length > 3 ? arguments[3] : undefined; + // Config with Vega-Lite only config removed. + const vgConfig = model.config ? stripAndRedirectConfig(model.config) : undefined; + const data = [].concat(model.assembleSelectionData([]), + // only assemble data in the root + assembleRootData(model.component.data, datasets)); + const projections = model.assembleProjections(); + const title = model.assembleTitle(); + const style = model.assembleGroupStyle(); + const encodeEntry = model.assembleGroupEncodeEntry(true); + let layoutSignals = model.assembleLayoutSignals(); + + // move width and height signals with values to top level + layoutSignals = layoutSignals.filter(signal => { + if ((signal.name === 'width' || signal.name === 'height') && signal.value !== undefined) { + topLevelProperties[signal.name] = +signal.value; + return false; + } + return true; + }); + const { + params, + ...otherTopLevelProps + } = topLevelProperties; + return { + $schema: 'https://vega.github.io/schema/vega/v5.json', + ...(model.description ? { + description: model.description + } : {}), + ...otherTopLevelProps, + ...(title ? { + title + } : {}), + ...(style ? { + style + } : {}), + ...(encodeEntry ? { + encode: { + update: encodeEntry + } + } : {}), + data, + ...(projections.length > 0 ? { + projections + } : {}), + ...model.assembleGroup([...layoutSignals, ...model.assembleSelectionTopLevelSignals([]), ...assembleParameterSignals(params)]), + ...(vgConfig ? { + config: vgConfig + } : {}), + ...(usermeta ? { + usermeta + } : {}) + }; + } + + const version = pkg.version; + + exports.accessPathDepth = accessPathDepth; + exports.accessPathWithDatum = accessPathWithDatum; + exports.compile = compile; + exports.contains = contains; + exports.deepEqual = deepEqual; + exports.deleteNestedProperty = deleteNestedProperty; + exports.duplicate = duplicate; + exports.entries = entries$1; + exports.every = every; + exports.fieldIntersection = fieldIntersection; + exports.flatAccessWithDatum = flatAccessWithDatum; + exports.getFirstDefined = getFirstDefined; + exports.hasIntersection = hasIntersection; + exports.hash = hash; + exports.internalField = internalField; + exports.isBoolean = isBoolean; + exports.isEmpty = isEmpty; + exports.isEqual = isEqual; + exports.isInternalField = isInternalField; + exports.isNullOrFalse = isNullOrFalse; + exports.isNumeric = isNumeric; + exports.keys = keys; + exports.logicalExpr = logicalExpr; + exports.mergeDeep = mergeDeep; + exports.never = never; + exports.normalize = normalize; + exports.normalizeAngle = normalizeAngle; + exports.omit = omit; + exports.pick = pick; + exports.prefixGenerator = prefixGenerator; + exports.removePathFromField = removePathFromField; + exports.replaceAll = replaceAll; + exports.replacePathInField = replacePathInField; + exports.resetIdCounter = resetIdCounter; + exports.setEqual = setEqual; + exports.some = some; + exports.stringify = stringify; + exports.titleCase = titleCase; + exports.unique = unique; + exports.uniqueId = uniqueId; + exports.vals = vals; + exports.varName = varName; + exports.version = version; + +})); +//# sourceMappingURL=vega-lite.js.map From 19ecd691914b77aa0c9bb6d6f89bf5bf1dbed125 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Thu, 1 Aug 2024 09:45:35 -0400 Subject: [PATCH 22/24] clean up --- vega-lite.js | 23021 ------------------------------------------------- 1 file changed, 23021 deletions(-) delete mode 100644 vega-lite.js diff --git a/vega-lite.js b/vega-lite.js deleted file mode 100644 index 913d3fdc8f..0000000000 --- a/vega-lite.js +++ /dev/null @@ -1,23021 +0,0 @@ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vega')) : - typeof define === 'function' && define.amd ? define(['exports', 'vega'], factory) : - (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.vegaLite = {}, global.vega)); -})(this, (function (exports, vega) { 'use strict'; - - var name = "vega-lite"; - var author = "Dominik Moritz, Kanit \"Ham\" Wongsuphasawat, Arvind Satyanarayan, Jeffrey Heer"; - var version$1 = "5.20.0"; - var collaborators = [ - "Kanit Wongsuphasawat (http://kanitw.yellowpigz.com)", - "Dominik Moritz (https://www.domoritz.de)", - "Arvind Satyanarayan (https://arvindsatya.com)", - "Jeffrey Heer (https://jheer.org)" - ]; - var homepage = "https://vega.github.io/vega-lite/"; - var description$1 = "Vega-Lite is a concise high-level language for interactive visualization."; - var keywords = [ - "vega", - "chart", - "visualization" - ]; - var main$1 = "build/vega-lite.js"; - var unpkg = "build/vega-lite.min.js"; - var jsdelivr = "build/vega-lite.min.js"; - var module = "build/src/index"; - var types = "build/src/index.d.ts"; - var bin = { - vl2pdf: "./bin/vl2pdf", - vl2png: "./bin/vl2png", - vl2svg: "./bin/vl2svg", - vl2vg: "./bin/vl2vg" - }; - var files = [ - "bin", - "build", - "src", - "vega-lite*", - "tsconfig.json" - ]; - var scripts = { - changelog: "conventional-changelog -p angular -r 2", - prebuild: "yarn clean:build", - build: "yarn build:only", - "build:only": "tsc -p tsconfig.build.json && rollup -c", - "prebuild:examples": "yarn build:only", - "build:examples": "yarn data && TZ=America/Los_Angeles scripts/build-examples.sh", - "prebuild:examples-full": "yarn build:only", - "build:examples-full": "TZ=America/Los_Angeles scripts/build-examples.sh 1", - "build:example": "TZ=America/Los_Angeles scripts/build-example.sh", - "build:toc": "yarn build:jekyll && scripts/generate-toc", - "build:site": "rollup -c site/rollup.config.mjs", - "build:jekyll": "pushd site && bundle exec jekyll build -q && popd", - "build:versions": "scripts/update-version.sh", - clean: "yarn clean:build && del-cli 'site/data/*' 'examples/compiled/*.png' && find site/examples ! -name 'index.md' ! -name 'data' -type f -delete", - "clean:build": "del-cli 'build/*' !build/vega-lite-schema.json", - data: "rsync -r node_modules/vega-datasets/data/* site/data", - "build-editor-preview": "scripts/build-editor-preview.sh", - schema: "mkdir -p build && ts-json-schema-generator -f tsconfig.json -p src/index.ts -t TopLevelSpec --no-type-check --no-ref-encode > build/vega-lite-schema.json && yarn renameschema && cp build/vega-lite-schema.json site/_data/", - renameschema: "scripts/rename-schema.sh", - presite: "yarn data && yarn schema && yarn build:site && yarn build:versions && scripts/create-example-pages.sh", - site: "yarn site:only", - "site:only": "pushd site && bundle exec jekyll serve -I -l && popd", - prettierbase: "prettier '**/*.{md,css,yml}'", - format: "eslint . --fix && yarn prettierbase --write", - lint: "eslint . && yarn prettierbase --check", - test: "yarn jest test/ && yarn lint && yarn schema && yarn jest examples/ && yarn test:runtime", - "test:cover": "yarn jest --collectCoverage test/", - "test:inspect": "node --inspect-brk ./node_modules/.bin/jest --runInBand test", - "test:runtime": "TZ=America/Los_Angeles npx jest test-runtime/ --config test-runtime/jest-config.json", - "test:runtime:generate": "yarn build:only && del-cli test-runtime/resources && VL_GENERATE_TESTS=true yarn test:runtime", - watch: "tsc -p tsconfig.build.json -w", - "watch:site": "yarn build:site -w", - "watch:test": "yarn jest --watch test/", - "watch:test:runtime": "TZ=America/Los_Angeles npx jest --watch test-runtime/ --config test-runtime/jest-config.json", - release: "release-it" - }; - var repository = { - type: "git", - url: "https://github.com/vega/vega-lite.git" - }; - var license = "BSD-3-Clause"; - var bugs = { - url: "https://github.com/vega/vega-lite/issues" - }; - var devDependencies = { - "@babel/core": "^7.24.7", - "@babel/preset-env": "^7.24.7", - "@babel/preset-typescript": "^7.24.7", - "@release-it/conventional-changelog": "^8.0.1", - "@rollup/plugin-alias": "^5.1.0", - "@rollup/plugin-babel": "^6.0.4", - "@rollup/plugin-commonjs": "^26.0.1", - "@rollup/plugin-json": "^6.1.0", - "@rollup/plugin-node-resolve": "^15.2.3", - "@rollup/plugin-terser": "^0.4.4", - "@types/d3": "^7.4.3", - "@types/jest": "^29.5.12", - "@types/pako": "^2.0.3", - "@typescript-eslint/eslint-plugin": "^7.13.0", - "@typescript-eslint/parser": "^7.13.0", - ajv: "^8.16.0", - "ajv-formats": "^2.1.1", - cheerio: "^1.0.0-rc.12", - "conventional-changelog-cli": "^4.1.0", - d3: "^7.9.0", - "del-cli": "^5.1.0", - eslint: "^8.57.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-jest": "^27.9.0", - "eslint-plugin-prettier": "^5.1.3", - "fast-json-stable-stringify": "~2.1.0", - "highlight.js": "^11.9.0", - jest: "^29.7.0", - "jest-dev-server": "^10.0.0", - mkdirp: "^3.0.1", - pako: "^2.1.0", - prettier: "^3.3.2", - puppeteer: "^15.0.0", - "release-it": "17.2.1", - rollup: "^4.18.0", - "rollup-plugin-bundle-size": "^1.0.3", - serve: "^14.2.3", - terser: "^5.31.1", - "ts-jest": "^29.1.4", - "ts-json-schema-generator": "^1.5.0", - typescript: "~5.4.5", - "vega-cli": "^5.28.0", - "vega-datasets": "^2.8.1", - "vega-embed": "^6.25.0", - "vega-tooltip": "^0.34.0", - "yaml-front-matter": "^4.1.1" - }; - var dependencies = { - "json-stringify-pretty-compact": "~3.0.0", - tslib: "~2.6.3", - "vega-event-selector": "~3.0.1", - "vega-expression": "~5.1.0", - "vega-util": "~1.17.2", - yargs: "~17.7.2" - }; - var peerDependencies = { - vega: "^5.24.0" - }; - var engines = { - node: ">=18" - }; - var packageManager = "yarn@1.22.19"; - var pkg = { - name: name, - author: author, - version: version$1, - collaborators: collaborators, - homepage: homepage, - description: description$1, - keywords: keywords, - main: main$1, - unpkg: unpkg, - jsdelivr: jsdelivr, - module: module, - types: types, - bin: bin, - files: files, - scripts: scripts, - repository: repository, - license: license, - bugs: bugs, - devDependencies: devDependencies, - dependencies: dependencies, - peerDependencies: peerDependencies, - engines: engines, - packageManager: packageManager - }; - - function isLogicalOr(op) { - return !!op.or; - } - function isLogicalAnd(op) { - return !!op.and; - } - function isLogicalNot(op) { - return !!op.not; - } - function forEachLeaf(op, fn) { - if (isLogicalNot(op)) { - forEachLeaf(op.not, fn); - } else if (isLogicalAnd(op)) { - for (const subop of op.and) { - forEachLeaf(subop, fn); - } - } else if (isLogicalOr(op)) { - for (const subop of op.or) { - forEachLeaf(subop, fn); - } - } else { - fn(op); - } - } - function normalizeLogicalComposition(op, normalizer) { - if (isLogicalNot(op)) { - return { - not: normalizeLogicalComposition(op.not, normalizer) - }; - } else if (isLogicalAnd(op)) { - return { - and: op.and.map(o => normalizeLogicalComposition(o, normalizer)) - }; - } else if (isLogicalOr(op)) { - return { - or: op.or.map(o => normalizeLogicalComposition(o, normalizer)) - }; - } else { - return normalizer(op); - } - } - - const duplicate = structuredClone; - function never(message) { - throw new Error(message); - } - - /** - * Creates an object composed of the picked object properties. - * - * var object = {'a': 1, 'b': '2', 'c': 3}; - * pick(object, ['a', 'c']); - * // → {'a': 1, 'c': 3} - */ - // eslint-disable-next-line @typescript-eslint/ban-types - function pick(obj, props) { - const copy = {}; - for (const prop of props) { - if (vega.hasOwnProperty(obj, prop)) { - copy[prop] = obj[prop]; - } - } - return copy; - } - - /** - * The opposite of _.pick; this method creates an object composed of the own - * and inherited enumerable string keyed properties of object that are not omitted. - */ - // eslint-disable-next-line @typescript-eslint/ban-types - function omit(obj, props) { - const copy = { - ...obj - }; - for (const prop of props) { - delete copy[prop]; - } - return copy; - } - - /** - * Monkey patch Set so that `stringify` produces a string representation of sets. - */ - Set.prototype['toJSON'] = function () { - return `Set(${[...this].map(x => stringify(x)).join(',')})`; - }; - - /** - * Converts any object to a string of limited size, or a number. - */ - function hash(a) { - if (vega.isNumber(a)) { - return a; - } - const str = vega.isString(a) ? a : stringify(a); - - // short strings can be used as hash directly, longer strings are hashed to reduce memory usage - if (str.length < 250) { - return str; - } - - // from http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/ - let h = 0; - for (let i = 0; i < str.length; i++) { - const char = str.charCodeAt(i); - h = (h << 5) - h + char; - h = h & h; // Convert to 32bit integer - } - return h; - } - function isNullOrFalse(x) { - return x === false || x === null; - } - function contains(array, item) { - return array.includes(item); - } - - /** - * Returns true if any item returns true. - */ - function some(arr, f) { - let i = 0; - for (const [k, a] of arr.entries()) { - if (f(a, k, i++)) { - return true; - } - } - return false; - } - - /** - * Returns true if all items return true. - */ - function every(arr, f) { - let i = 0; - for (const [k, a] of arr.entries()) { - if (!f(a, k, i++)) { - return false; - } - } - return true; - } - - /** - * Like TS Partial but applies recursively to all properties. - */ - - /** - * recursively merges src into dest - */ - function mergeDeep(dest) { - for (var _len = arguments.length, src = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - src[_key - 1] = arguments[_key]; - } - for (const s of src) { - deepMerge_(dest, s ?? {}); - } - return dest; - } - function deepMerge_(dest, src) { - for (const property of keys(src)) { - vega.writeConfig(dest, property, src[property], true); - } - } - function unique(values, f) { - const results = []; - const u = {}; - let v; - for (const val of values) { - v = f(val); - if (v in u) { - continue; - } - u[v] = 1; - results.push(val); - } - return results; - } - /** - * Returns true if the two dictionaries agree. Applies only to defined values. - */ - function isEqual(dict, other) { - const dictKeys = keys(dict); - const otherKeys = keys(other); - if (dictKeys.length !== otherKeys.length) { - return false; - } - for (const key of dictKeys) { - if (dict[key] !== other[key]) { - return false; - } - } - return true; - } - function setEqual(a, b) { - if (a.size !== b.size) { - return false; - } - for (const e of a) { - if (!b.has(e)) { - return false; - } - } - return true; - } - function hasIntersection(a, b) { - for (const key of a) { - if (b.has(key)) { - return true; - } - } - return false; - } - function prefixGenerator(a) { - const prefixes = new Set(); - for (const x of a) { - const splitField = vega.splitAccessPath(x); - // Wrap every element other than the first in `[]` - const wrappedWithAccessors = splitField.map((y, i) => i === 0 ? y : `[${y}]`); - const computedPrefixes = wrappedWithAccessors.map((_, i) => wrappedWithAccessors.slice(0, i + 1).join('')); - for (const y of computedPrefixes) { - prefixes.add(y); - } - } - return prefixes; - } - - /** - * Returns true if a and b have an intersection. Also return true if a or b are undefined - * since this means we don't know what fields a node produces or depends on. - */ - function fieldIntersection(a, b) { - if (a === undefined || b === undefined) { - return true; - } - return hasIntersection(prefixGenerator(a), prefixGenerator(b)); - } - - // eslint-disable-next-line @typescript-eslint/ban-types - function isEmpty(obj) { - return keys(obj).length === 0; - } - - // This is a stricter version of Object.keys but with better types. See https://github.com/Microsoft/TypeScript/pull/12253#issuecomment-263132208 - const keys = Object.keys; - const vals = Object.values; - const entries$1 = Object.entries; - - // Using mapped type to declare a collect of flags for a string literal type S - // https://www.typescriptlang.org/docs/handbook/advanced-types.html#mapped-types - - function isBoolean(b) { - return b === true || b === false; - } - - /** - * Convert a string into a valid variable name - */ - function varName(s) { - // Replace non-alphanumeric characters (anything besides a-zA-Z0-9_) with _ - const alphanumericS = s.replace(/\W/g, '_'); - - // Add _ if the string has leading numbers. - return (s.match(/^\d+/) ? '_' : '') + alphanumericS; - } - function logicalExpr(op, cb) { - if (isLogicalNot(op)) { - return `!(${logicalExpr(op.not, cb)})`; - } else if (isLogicalAnd(op)) { - return `(${op.and.map(and => logicalExpr(and, cb)).join(') && (')})`; - } else if (isLogicalOr(op)) { - return `(${op.or.map(or => logicalExpr(or, cb)).join(') || (')})`; - } else { - return cb(op); - } - } - - /** - * Delete nested property of an object, and delete the ancestors of the property if they become empty. - */ - function deleteNestedProperty(obj, orderedProps) { - if (orderedProps.length === 0) { - return true; - } - const prop = orderedProps.shift(); // eslint-disable-line @typescript-eslint/no-non-null-assertion - if (prop in obj && deleteNestedProperty(obj[prop], orderedProps)) { - delete obj[prop]; - } - return isEmpty(obj); - } - function titleCase(s) { - return s.charAt(0).toUpperCase() + s.substr(1); - } - - /** - * Converts a path to an access path with datum. - * @param path The field name. - * @param datum The string to use for `datum`. - */ - function accessPathWithDatum(path) { - let datum = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'datum'; - const pieces = vega.splitAccessPath(path); - const prefixes = []; - for (let i = 1; i <= pieces.length; i++) { - const prefix = `[${pieces.slice(0, i).map(vega.stringValue).join('][')}]`; - prefixes.push(`${datum}${prefix}`); - } - return prefixes.join(' && '); - } - - /** - * Return access with datum to the flattened field. - * - * @param path The field name. - * @param datum The string to use for `datum`. - */ - function flatAccessWithDatum(path) { - let datum = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'datum'; - return `${datum}[${vega.stringValue(vega.splitAccessPath(path).join('.'))}]`; - } - function escapePathAccess(string) { - return string.replace(/(\[|\]|\.|'|")/g, '\\$1'); - } - - /** - * Replaces path accesses with access to non-nested field. - * For example, `foo["bar"].baz` becomes `foo\\.bar\\.baz`. - */ - function replacePathInField(path) { - return `${vega.splitAccessPath(path).map(escapePathAccess).join('\\.')}`; - } - - /** - * Replace all occurrences of a string with another string. - * - * @param string the string to replace in - * @param find the string to replace - * @param replacement the replacement - */ - function replaceAll(string, find, replacement) { - return string.replace(new RegExp(find.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'), 'g'), replacement); - } - - /** - * Remove path accesses with access from field. - * For example, `foo["bar"].baz` becomes `foo.bar.baz`. - */ - function removePathFromField(path) { - return `${vega.splitAccessPath(path).join('.')}`; - } - - /** - * Count the depth of the path. Returns 1 for fields that are not nested. - */ - function accessPathDepth(path) { - if (!path) { - return 0; - } - return vega.splitAccessPath(path).length; - } - - /** - * This is a replacement for chained || for numeric properties or properties that respect null so that 0 will be included. - */ - function getFirstDefined() { - for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { - args[_key2] = arguments[_key2]; - } - for (const arg of args) { - if (arg !== undefined) { - return arg; - } - } - return undefined; - } - - // variable used to generate id - let idCounter = 42; - - /** - * Returns a new random id every time it gets called. - * - * Has side effect! - */ - function uniqueId(prefix) { - const id = ++idCounter; - return prefix ? String(prefix) + id : id; - } - - /** - * Resets the id counter used in uniqueId. This can be useful for testing. - */ - function resetIdCounter() { - idCounter = 42; - } - function internalField(name) { - return isInternalField(name) ? name : `__${name}`; - } - function isInternalField(name) { - return name.startsWith('__'); - } - - /** - * Normalize angle to be within [0,360). - */ - function normalizeAngle(angle) { - if (angle === undefined) { - return undefined; - } - return (angle % 360 + 360) % 360; - } - - /** - * Returns whether the passed in value is a valid number. - */ - function isNumeric(value) { - if (vega.isNumber(value)) { - return true; - } - return !isNaN(value) && !isNaN(parseFloat(value)); - } - const clonedProto = Object.getPrototypeOf(structuredClone({})); - - /** - * Compares two values for equality, including arrays and objects. - * - * Adapted from https://github.com/epoberezkin/fast-deep-equal. - */ - function deepEqual(a, b) { - if (a === b) return true; - if (a && b && typeof a == 'object' && typeof b == 'object') { - // compare names to avoid issues with structured clone - if (a.constructor.name !== b.constructor.name) return false; - let length; - let i; - if (Array.isArray(a)) { - length = a.length; - if (length != b.length) return false; - for (i = length; i-- !== 0;) if (!deepEqual(a[i], b[i])) return false; - return true; - } - if (a instanceof Map && b instanceof Map) { - if (a.size !== b.size) return false; - for (i of a.entries()) if (!b.has(i[0])) return false; - for (i of a.entries()) if (!deepEqual(i[1], b.get(i[0]))) return false; - return true; - } - if (a instanceof Set && b instanceof Set) { - if (a.size !== b.size) return false; - for (i of a.entries()) if (!b.has(i[0])) return false; - return true; - } - if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) { - length = a.length; - if (length != b.length) return false; - for (i = length; i-- !== 0;) if (a[i] !== b[i]) return false; - return true; - } - if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags; - // also compare to structured clone prototype - if (a.valueOf !== Object.prototype.valueOf && a.valueOf !== clonedProto.valueOf) return a.valueOf() === b.valueOf(); - if (a.toString !== Object.prototype.toString && a.toString !== clonedProto.toString) return a.toString() === b.toString(); - const ks = Object.keys(a); - length = ks.length; - if (length !== Object.keys(b).length) return false; - for (i = length; i-- !== 0;) if (!Object.prototype.hasOwnProperty.call(b, ks[i])) return false; - for (i = length; i-- !== 0;) { - const key = ks[i]; - if (!deepEqual(a[key], b[key])) return false; - } - return true; - } - - // true if both NaN, false otherwise - return a !== a && b !== b; - } - - /** - * Converts any object to a string representation that can be consumed by humans. - * - * Adapted from https://github.com/epoberezkin/fast-json-stable-stringify - */ - function stringify(data) { - const seen = []; - return function _stringify(node) { - if (node && node.toJSON && typeof node.toJSON === 'function') { - node = node.toJSON(); - } - if (node === undefined) return undefined; - if (typeof node == 'number') return isFinite(node) ? '' + node : 'null'; - if (typeof node !== 'object') return JSON.stringify(node); - let i, out; - if (Array.isArray(node)) { - out = '['; - for (i = 0; i < node.length; i++) { - if (i) out += ','; - out += _stringify(node[i]) || 'null'; - } - return out + ']'; - } - if (node === null) return 'null'; - if (seen.includes(node)) { - throw new TypeError('Converting circular structure to JSON'); - } - const seenIndex = seen.push(node) - 1; - const ks = Object.keys(node).sort(); - out = ''; - for (i = 0; i < ks.length; i++) { - const key = ks[i]; - const value = _stringify(node[key]); - if (!value) continue; - if (out) out += ','; - out += JSON.stringify(key) + ':' + value; - } - seen.splice(seenIndex, 1); - return `{${out}}`; - }(data); - } - - /* - * Constants and utilities for encoding channels (Visual variables) - * such as 'x', 'y', 'color'. - */ - - // Facet - const ROW = 'row'; - const COLUMN = 'column'; - const FACET = 'facet'; - - // Position - const X = 'x'; - const Y = 'y'; - const X2 = 'x2'; - const Y2 = 'y2'; - - // Position Offset - const XOFFSET = 'xOffset'; - const YOFFSET = 'yOffset'; - - // Arc-Position - const RADIUS = 'radius'; - const RADIUS2 = 'radius2'; - const THETA = 'theta'; - const THETA2 = 'theta2'; - - // Geo Position - const LATITUDE = 'latitude'; - const LONGITUDE = 'longitude'; - const LATITUDE2 = 'latitude2'; - const LONGITUDE2 = 'longitude2'; - - // Mark property with scale - const COLOR = 'color'; - const FILL = 'fill'; - const STROKE = 'stroke'; - const SHAPE = 'shape'; - const SIZE = 'size'; - const ANGLE = 'angle'; - const OPACITY = 'opacity'; - const FILLOPACITY = 'fillOpacity'; - const STROKEOPACITY = 'strokeOpacity'; - const STROKEWIDTH = 'strokeWidth'; - const STROKEDASH = 'strokeDash'; - - // Non-scale channel - const TEXT$1 = 'text'; - const ORDER = 'order'; - const DETAIL = 'detail'; - const KEY = 'key'; - const TOOLTIP = 'tooltip'; - const HREF = 'href'; - const URL = 'url'; - const DESCRIPTION = 'description'; - const POSITION_CHANNEL_INDEX = { - x: 1, - y: 1, - x2: 1, - y2: 1 - }; - const POLAR_POSITION_CHANNEL_INDEX = { - theta: 1, - theta2: 1, - radius: 1, - radius2: 1 - }; - function isPolarPositionChannel(c) { - return c in POLAR_POSITION_CHANNEL_INDEX; - } - const GEO_POSIITON_CHANNEL_INDEX = { - longitude: 1, - longitude2: 1, - latitude: 1, - latitude2: 1 - }; - function getPositionChannelFromLatLong(channel) { - switch (channel) { - case LATITUDE: - return 'y'; - case LATITUDE2: - return 'y2'; - case LONGITUDE: - return 'x'; - case LONGITUDE2: - return 'x2'; - } - } - function isGeoPositionChannel(c) { - return c in GEO_POSIITON_CHANNEL_INDEX; - } - const GEOPOSITION_CHANNELS = keys(GEO_POSIITON_CHANNEL_INDEX); - const UNIT_CHANNEL_INDEX = { - ...POSITION_CHANNEL_INDEX, - ...POLAR_POSITION_CHANNEL_INDEX, - ...GEO_POSIITON_CHANNEL_INDEX, - xOffset: 1, - yOffset: 1, - // color - color: 1, - fill: 1, - stroke: 1, - // other non-position with scale - opacity: 1, - fillOpacity: 1, - strokeOpacity: 1, - strokeWidth: 1, - strokeDash: 1, - size: 1, - angle: 1, - shape: 1, - // channels without scales - order: 1, - text: 1, - detail: 1, - key: 1, - tooltip: 1, - href: 1, - url: 1, - description: 1 - }; - function isColorChannel(channel) { - return channel === COLOR || channel === FILL || channel === STROKE; - } - const FACET_CHANNEL_INDEX = { - row: 1, - column: 1, - facet: 1 - }; - const FACET_CHANNELS = keys(FACET_CHANNEL_INDEX); - const CHANNEL_INDEX = { - ...UNIT_CHANNEL_INDEX, - ...FACET_CHANNEL_INDEX - }; - const CHANNELS = keys(CHANNEL_INDEX); - const { - order: _o, - detail: _d, - tooltip: _tt1, - ...SINGLE_DEF_CHANNEL_INDEX - } = CHANNEL_INDEX; - const { - row: _r, - column: _c, - facet: _f, - ...SINGLE_DEF_UNIT_CHANNEL_INDEX - } = SINGLE_DEF_CHANNEL_INDEX; - function isSingleDefUnitChannel(str) { - return !!SINGLE_DEF_UNIT_CHANNEL_INDEX[str]; - } - function isChannel(str) { - return !!CHANNEL_INDEX[str]; - } - const SECONDARY_RANGE_CHANNEL = [X2, Y2, LATITUDE2, LONGITUDE2, THETA2, RADIUS2]; - function isSecondaryRangeChannel(c) { - const main = getMainRangeChannel(c); - return main !== c; - } - /** - * Get the main channel for a range channel. E.g. `x` for `x2`. - */ - function getMainRangeChannel(channel) { - switch (channel) { - case X2: - return X; - case Y2: - return Y; - case LATITUDE2: - return LATITUDE; - case LONGITUDE2: - return LONGITUDE; - case THETA2: - return THETA; - case RADIUS2: - return RADIUS; - } - return channel; - } - function getVgPositionChannel(channel) { - if (isPolarPositionChannel(channel)) { - switch (channel) { - case THETA: - return 'startAngle'; - case THETA2: - return 'endAngle'; - case RADIUS: - return 'outerRadius'; - case RADIUS2: - return 'innerRadius'; - } - } - return channel; - } - - /** - * Get the main channel for a range channel. E.g. `x` for `x2`. - */ - function getSecondaryRangeChannel(channel) { - switch (channel) { - case X: - return X2; - case Y: - return Y2; - case LATITUDE: - return LATITUDE2; - case LONGITUDE: - return LONGITUDE2; - case THETA: - return THETA2; - case RADIUS: - return RADIUS2; - } - return undefined; - } - function getSizeChannel(channel) { - switch (channel) { - case X: - case X2: - return 'width'; - case Y: - case Y2: - return 'height'; - } - return undefined; - } - - /** - * Get the main channel for a range channel. E.g. `x` for `x2`. - */ - function getOffsetChannel(channel) { - switch (channel) { - case X: - return 'xOffset'; - case Y: - return 'yOffset'; - case X2: - return 'x2Offset'; - case Y2: - return 'y2Offset'; - case THETA: - return 'thetaOffset'; - case RADIUS: - return 'radiusOffset'; - case THETA2: - return 'theta2Offset'; - case RADIUS2: - return 'radius2Offset'; - } - return undefined; - } - - /** - * Get the main channel for a range channel. E.g. `x` for `x2`. - */ - function getOffsetScaleChannel(channel) { - switch (channel) { - case X: - return 'xOffset'; - case Y: - return 'yOffset'; - } - return undefined; - } - function getMainChannelFromOffsetChannel(channel) { - switch (channel) { - case 'xOffset': - return 'x'; - case 'yOffset': - return 'y'; - } - } - - // CHANNELS without COLUMN, ROW - const UNIT_CHANNELS = keys(UNIT_CHANNEL_INDEX); - - // NONPOSITION_CHANNELS = UNIT_CHANNELS without X, Y, X2, Y2; - const { - x: _x, - y: _y, - // x2 and y2 share the same scale as x and y - x2: _x2, - y2: _y2, - // - xOffset: _xo, - yOffset: _yo, - latitude: _latitude, - longitude: _longitude, - latitude2: _latitude2, - longitude2: _longitude2, - theta: _theta, - theta2: _theta2, - radius: _radius, - radius2: _radius2, - // The rest of unit channels then have scale - ...NONPOSITION_CHANNEL_INDEX - } = UNIT_CHANNEL_INDEX; - const NONPOSITION_CHANNELS = keys(NONPOSITION_CHANNEL_INDEX); - const POSITION_SCALE_CHANNEL_INDEX = { - x: 1, - y: 1 - }; - const POSITION_SCALE_CHANNELS = keys(POSITION_SCALE_CHANNEL_INDEX); - function isXorY(channel) { - return channel in POSITION_SCALE_CHANNEL_INDEX; - } - const POLAR_POSITION_SCALE_CHANNEL_INDEX = { - theta: 1, - radius: 1 - }; - const POLAR_POSITION_SCALE_CHANNELS = keys(POLAR_POSITION_SCALE_CHANNEL_INDEX); - function getPositionScaleChannel(sizeType) { - return sizeType === 'width' ? X : Y; - } - const OFFSET_SCALE_CHANNEL_INDEX = { - xOffset: 1, - yOffset: 1 - }; - function isXorYOffset(channel) { - return channel in OFFSET_SCALE_CHANNEL_INDEX; - } - - // NON_POSITION_SCALE_CHANNEL = SCALE_CHANNELS without position / offset - const { - // x2 and y2 share the same scale as x and y - // text and tooltip have format instead of scale, - // href has neither format, nor scale - text: _t, - tooltip: _tt, - href: _hr, - url: _u, - description: _al, - // detail and order have no scale - detail: _dd, - key: _k, - order: _oo, - ...NONPOSITION_SCALE_CHANNEL_INDEX - } = NONPOSITION_CHANNEL_INDEX; - const NONPOSITION_SCALE_CHANNELS = keys(NONPOSITION_SCALE_CHANNEL_INDEX); - function isNonPositionScaleChannel(channel) { - return !!NONPOSITION_CHANNEL_INDEX[channel]; - } - - /** - * @returns whether Vega supports legends for a particular channel - */ - function supportLegend(channel) { - switch (channel) { - case COLOR: - case FILL: - case STROKE: - case SIZE: - case SHAPE: - case OPACITY: - case STROKEWIDTH: - case STROKEDASH: - return true; - case FILLOPACITY: - case STROKEOPACITY: - case ANGLE: - return false; - } - } - - // Declare SCALE_CHANNEL_INDEX - const SCALE_CHANNEL_INDEX = { - ...POSITION_SCALE_CHANNEL_INDEX, - ...POLAR_POSITION_SCALE_CHANNEL_INDEX, - ...OFFSET_SCALE_CHANNEL_INDEX, - ...NONPOSITION_SCALE_CHANNEL_INDEX - }; - - /** List of channels with scales */ - const SCALE_CHANNELS = keys(SCALE_CHANNEL_INDEX); - function isScaleChannel(channel) { - return !!SCALE_CHANNEL_INDEX[channel]; - } - /** - * Return whether a channel supports a particular mark type. - * @param channel channel name - * @param mark the mark type - * @return whether the mark supports the channel - */ - function supportMark(channel, mark) { - return getSupportedMark(channel)[mark]; - } - const ALL_MARKS = { - // all marks - arc: 'always', - area: 'always', - bar: 'always', - circle: 'always', - geoshape: 'always', - image: 'always', - line: 'always', - rule: 'always', - point: 'always', - rect: 'always', - square: 'always', - trail: 'always', - text: 'always', - tick: 'always' - }; - const { - geoshape: _g, - ...ALL_MARKS_EXCEPT_GEOSHAPE - } = ALL_MARKS; - - /** - * Return a dictionary showing whether a channel supports mark type. - * @param channel - * @return A dictionary mapping mark types to 'always', 'binned', or undefined - */ - function getSupportedMark(channel) { - switch (channel) { - case COLOR: - case FILL: - case STROKE: - // falls through - - case DESCRIPTION: - case DETAIL: - case KEY: - case TOOLTIP: - case HREF: - case ORDER: // TODO: revise (order might not support rect, which is not stackable?) - case OPACITY: - case FILLOPACITY: - case STROKEOPACITY: - case STROKEWIDTH: - - // falls through - - case FACET: - case ROW: // falls through - case COLUMN: - return ALL_MARKS; - case X: - case Y: - case XOFFSET: - case YOFFSET: - case LATITUDE: - case LONGITUDE: - // all marks except geoshape. geoshape does not use X, Y -- it uses a projection - return ALL_MARKS_EXCEPT_GEOSHAPE; - case X2: - case Y2: - case LATITUDE2: - case LONGITUDE2: - return { - area: 'always', - bar: 'always', - image: 'always', - rect: 'always', - rule: 'always', - circle: 'binned', - point: 'binned', - square: 'binned', - tick: 'binned', - line: 'binned', - trail: 'binned' - }; - case SIZE: - return { - point: 'always', - tick: 'always', - rule: 'always', - circle: 'always', - square: 'always', - bar: 'always', - text: 'always', - line: 'always', - trail: 'always' - }; - case STROKEDASH: - return { - line: 'always', - point: 'always', - tick: 'always', - rule: 'always', - circle: 'always', - square: 'always', - bar: 'always', - geoshape: 'always' - }; - case SHAPE: - return { - point: 'always', - geoshape: 'always' - }; - case TEXT$1: - return { - text: 'always' - }; - case ANGLE: - return { - point: 'always', - square: 'always', - text: 'always' - }; - case URL: - return { - image: 'always' - }; - case THETA: - return { - text: 'always', - arc: 'always' - }; - case RADIUS: - return { - text: 'always', - arc: 'always' - }; - case THETA2: - case RADIUS2: - return { - arc: 'always' - }; - } - } - function rangeType(channel) { - switch (channel) { - case X: - case Y: - case THETA: - case RADIUS: - case XOFFSET: - case YOFFSET: - case SIZE: - case ANGLE: - case STROKEWIDTH: - case OPACITY: - case FILLOPACITY: - case STROKEOPACITY: - - // X2 and Y2 use X and Y scales, so they similarly have continuous range. [falls through] - case X2: - case Y2: - case THETA2: - case RADIUS2: - return undefined; - case FACET: - case ROW: - case COLUMN: - case SHAPE: - case STROKEDASH: - // TEXT, TOOLTIP, URL, and HREF have no scale but have discrete output [falls through] - case TEXT$1: - case TOOLTIP: - case HREF: - case URL: - case DESCRIPTION: - return 'discrete'; - - // Color can be either continuous or discrete, depending on scale type. - case COLOR: - case FILL: - case STROKE: - return 'flexible'; - - // No scale, no range type. - - case LATITUDE: - case LONGITUDE: - case LATITUDE2: - case LONGITUDE2: - case DETAIL: - case KEY: - case ORDER: - return undefined; - } - } - - const AGGREGATE_OP_INDEX = { - argmax: 1, - argmin: 1, - average: 1, - count: 1, - distinct: 1, - exponential: 1, - exponentialb: 1, - product: 1, - max: 1, - mean: 1, - median: 1, - min: 1, - missing: 1, - q1: 1, - q3: 1, - ci0: 1, - ci1: 1, - stderr: 1, - stdev: 1, - stdevp: 1, - sum: 1, - valid: 1, - values: 1, - variance: 1, - variancep: 1 - }; - const MULTIDOMAIN_SORT_OP_INDEX = { - count: 1, - min: 1, - max: 1 - }; - function isArgminDef(a) { - return !!a && !!a['argmin']; - } - function isArgmaxDef(a) { - return !!a && !!a['argmax']; - } - function isAggregateOp(a) { - return vega.isString(a) && !!AGGREGATE_OP_INDEX[a]; - } - const COUNTING_OPS = new Set(['count', 'valid', 'missing', 'distinct']); - function isCountingAggregateOp(aggregate) { - return vega.isString(aggregate) && COUNTING_OPS.has(aggregate); - } - function isMinMaxOp(aggregate) { - return vega.isString(aggregate) && contains(['min', 'max'], aggregate); - } - - /** Additive-based aggregation operations. These can be applied to stack. */ - const SUM_OPS = new Set(['count', 'sum', 'distinct', 'valid', 'missing']); - - /** - * Aggregation operators that always produce values within the range [domainMin, domainMax]. - */ - const SHARED_DOMAIN_OPS = new Set(['mean', 'average', 'median', 'q1', 'q3', 'min', 'max']); - - /** - * Binning properties or boolean flag for determining whether to bin data or not. - */ - - /** - * Create a key for the bin configuration. Not for prebinned bin. - */ - function binToString(bin) { - if (vega.isBoolean(bin)) { - bin = normalizeBin(bin, undefined); - } - return 'bin' + keys(bin).map(p => isParameterExtent(bin[p]) ? varName(`_${p}_${entries$1(bin[p])}`) : varName(`_${p}_${bin[p]}`)).join(''); - } - - /** - * Vega-Lite should bin the data. - */ - function isBinning(bin) { - return bin === true || isBinParams(bin) && !bin.binned; - } - - /** - * The data is already binned and so Vega-Lite should not bin it again. - */ - function isBinned(bin) { - return bin === 'binned' || isBinParams(bin) && bin.binned === true; - } - function isBinParams(bin) { - return vega.isObject(bin); - } - function isParameterExtent(extent) { - return extent?.['param']; - } - function autoMaxBins(channel) { - switch (channel) { - case ROW: - case COLUMN: - case SIZE: - case COLOR: - case FILL: - case STROKE: - case STROKEWIDTH: - case OPACITY: - case FILLOPACITY: - case STROKEOPACITY: - // Facets and Size shouldn't have too many bins - // We choose 6 like shape to simplify the rule [falls through] - case SHAPE: - return 6; - // Vega's "shape" has 6 distinct values - case STROKEDASH: - return 4; - // We only provide 5 different stroke dash values (but 4 is more effective) - default: - return 10; - } - } - - function isExprRef(o) { - return !!o?.expr; - } - function replaceExprRef(index) { - let { - level - } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { - level: 0 - }; - const props = keys(index || {}); - const newIndex = {}; - for (const prop of props) { - newIndex[prop] = level === 0 ? signalRefOrValue(index[prop]) : replaceExprRef(index[prop], { - level: level - 1 - }); - } - return newIndex; - } - - function extractTitleConfig(titleConfig) { - const { - // These are non-mark title config that need to be hardcoded - anchor, - frame, - offset, - orient, - angle, - limit, - // color needs to be redirect to fill - color, - // subtitle properties - subtitleColor, - subtitleFont, - subtitleFontSize, - subtitleFontStyle, - subtitleFontWeight, - subtitleLineHeight, - subtitlePadding, - // The rest are mark config. - ...rest - } = titleConfig; - const titleMarkConfig = { - ...rest, - ...(color ? { - fill: color - } : {}) - }; - - // These are non-mark title config that need to be hardcoded - const nonMarkTitleProperties = { - ...(anchor ? { - anchor - } : {}), - ...(frame ? { - frame - } : {}), - ...(offset ? { - offset - } : {}), - ...(orient ? { - orient - } : {}), - ...(angle !== undefined ? { - angle - } : {}), - ...(limit !== undefined ? { - limit - } : {}) - }; - - // subtitle part can stay in config.title since header titles do not use subtitle - const subtitle = { - ...(subtitleColor ? { - subtitleColor - } : {}), - ...(subtitleFont ? { - subtitleFont - } : {}), - ...(subtitleFontSize ? { - subtitleFontSize - } : {}), - ...(subtitleFontStyle ? { - subtitleFontStyle - } : {}), - ...(subtitleFontWeight ? { - subtitleFontWeight - } : {}), - ...(subtitleLineHeight ? { - subtitleLineHeight - } : {}), - ...(subtitlePadding ? { - subtitlePadding - } : {}) - }; - const subtitleMarkConfig = pick(titleConfig, ['align', 'baseline', 'dx', 'dy', 'limit']); - return { - titleMarkConfig, - subtitleMarkConfig, - nonMarkTitleProperties, - subtitle - }; - } - function isText(v) { - return vega.isString(v) || vega.isArray(v) && vega.isString(v[0]); - } - - // TODO: make recursive (e.g. with https://stackoverflow.com/a/64900252/214950 but needs https://github.com/vega/ts-json-schema-generator/issues/568) - - // Remove ValueRefs from mapped types - - function isSignalRef(o) { - return !!o?.signal; - } - - // TODO: add type of value (Make it VgValueRef {value?:V ...}) - - // TODO: add vg prefix - - function isVgRangeStep(range) { - return !!range['step']; - } - - // Domains that are not a union of domains - - /** - * A combined type for any Vega scales that Vega-Lite can generate - */ - - function isDataRefUnionedDomain(domain) { - if (!vega.isArray(domain)) { - return 'fields' in domain && !('data' in domain); - } - return false; - } - function isFieldRefUnionDomain(domain) { - if (!vega.isArray(domain)) { - return 'fields' in domain && 'data' in domain; - } - return false; - } - function isDataRefDomain(domain) { - if (!vega.isArray(domain)) { - return 'field' in domain && 'data' in domain; - } - return false; - } - - // TODO: make export interface VgEncodeEntry { - // x?: VgValueRef - // y?: VgValueRef - // ... - // color?: VgValueRef - // ... - // } - - const VG_MARK_CONFIG_INDEX = { - aria: 1, - description: 1, - ariaRole: 1, - ariaRoleDescription: 1, - blend: 1, - opacity: 1, - fill: 1, - fillOpacity: 1, - stroke: 1, - strokeCap: 1, - strokeWidth: 1, - strokeOpacity: 1, - strokeDash: 1, - strokeDashOffset: 1, - strokeJoin: 1, - strokeOffset: 1, - strokeMiterLimit: 1, - startAngle: 1, - endAngle: 1, - padAngle: 1, - innerRadius: 1, - outerRadius: 1, - size: 1, - shape: 1, - interpolate: 1, - tension: 1, - orient: 1, - align: 1, - baseline: 1, - text: 1, - dir: 1, - dx: 1, - dy: 1, - ellipsis: 1, - limit: 1, - radius: 1, - theta: 1, - angle: 1, - font: 1, - fontSize: 1, - fontWeight: 1, - fontStyle: 1, - lineBreak: 1, - lineHeight: 1, - cursor: 1, - href: 1, - tooltip: 1, - cornerRadius: 1, - cornerRadiusTopLeft: 1, - cornerRadiusTopRight: 1, - cornerRadiusBottomLeft: 1, - cornerRadiusBottomRight: 1, - aspect: 1, - width: 1, - height: 1, - url: 1, - smooth: 1 - - // commented below are vg channel that do not have mark config. - // x: 1, - // y: 1, - // x2: 1, - // y2: 1, - - // xc'|'yc' - // clip: 1, - // path: 1, - // url: 1, - }; - const VG_MARK_CONFIGS = keys(VG_MARK_CONFIG_INDEX); - const VG_MARK_INDEX = { - arc: 1, - area: 1, - group: 1, - image: 1, - line: 1, - path: 1, - rect: 1, - rule: 1, - shape: 1, - symbol: 1, - text: 1, - trail: 1 - }; - - // Vega's cornerRadius channels. - const VG_CORNERRADIUS_CHANNELS = ['cornerRadius', 'cornerRadiusTopLeft', 'cornerRadiusTopRight', 'cornerRadiusBottomLeft', 'cornerRadiusBottomRight']; - - function signalOrValueRefWithCondition(val) { - const condition = vega.isArray(val.condition) ? val.condition.map(conditionalSignalRefOrValue) : conditionalSignalRefOrValue(val.condition); - return { - ...signalRefOrValue(val), - condition - }; - } - function signalRefOrValue(value) { - if (isExprRef(value)) { - const { - expr, - ...rest - } = value; - return { - signal: expr, - ...rest - }; - } - return value; - } - function conditionalSignalRefOrValue(value) { - if (isExprRef(value)) { - const { - expr, - ...rest - } = value; - return { - signal: expr, - ...rest - }; - } - return value; - } - function signalOrValueRef(value) { - if (isExprRef(value)) { - const { - expr, - ...rest - } = value; - return { - signal: expr, - ...rest - }; - } - if (isSignalRef(value)) { - return value; - } - return value !== undefined ? { - value - } : undefined; - } - function exprFromSignalRefOrValue(ref) { - if (isSignalRef(ref)) { - return ref.signal; - } - return vega.stringValue(ref); - } - function exprFromValueRefOrSignalRef(ref) { - if (isSignalRef(ref)) { - return ref.signal; - } - return vega.stringValue(ref.value); - } - function signalOrStringValue(v) { - if (isSignalRef(v)) { - return v.signal; - } - return v == null ? null : vega.stringValue(v); - } - function applyMarkConfig(e, model, propsList) { - for (const property of propsList) { - const value = getMarkConfig(property, model.markDef, model.config); - if (value !== undefined) { - e[property] = signalOrValueRef(value); - } - } - return e; - } - function getStyles(mark) { - return [].concat(mark.type, mark.style ?? []); - } - function getMarkPropOrConfig(channel, mark, config) { - let opt = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; - const { - vgChannel, - ignoreVgConfig - } = opt; - if (vgChannel && mark[vgChannel] !== undefined) { - return mark[vgChannel]; - } else if (mark[channel] !== undefined) { - return mark[channel]; - } else if (ignoreVgConfig && (!vgChannel || vgChannel === channel)) { - return undefined; - } - return getMarkConfig(channel, mark, config, opt); - } - - /** - * Return property value from style or mark specific config property if exists. - * Otherwise, return general mark specific config. - */ - function getMarkConfig(channel, mark, config) { - let { - vgChannel - } = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; - return getFirstDefined( - // style config has highest precedence - vgChannel ? getMarkStyleConfig(channel, mark, config.style) : undefined, getMarkStyleConfig(channel, mark, config.style), - // then mark-specific config - vgChannel ? config[mark.type][vgChannel] : undefined, config[mark.type][channel], - // Need to cast because MarkDef doesn't perfectly match with AnyMarkConfig, but if the type isn't available, we'll get nothing here, which is fine - - // If there is vgChannel, skip vl channel. - // For example, vl size for text is vg fontSize, but config.mark.size is only for point size. - vgChannel ? config.mark[vgChannel] : config.mark[channel] // Need to cast for the same reason as above - ); - } - function getMarkStyleConfig(prop, mark, styleConfigIndex) { - return getStyleConfig(prop, getStyles(mark), styleConfigIndex); - } - function getStyleConfig(p, styles, styleConfigIndex) { - styles = vega.array(styles); - let value; - for (const style of styles) { - const styleConfig = styleConfigIndex[style]; - if (styleConfig && styleConfig[p] !== undefined) { - value = styleConfig[p]; - } - } - return value; - } - - /** - * Return Vega sort parameters (tuple of field and order). - */ - function sortParams(orderDef, fieldRefOption) { - return vega.array(orderDef).reduce((s, orderChannelDef) => { - s.field.push(vgField(orderChannelDef, fieldRefOption)); - s.order.push(orderChannelDef.sort ?? 'ascending'); - return s; - }, { - field: [], - order: [] - }); - } - function mergeTitleFieldDefs(f1, f2) { - const merged = [...f1]; - f2.forEach(fdToMerge => { - for (const fieldDef1 of merged) { - // If already exists, no need to append to merged array - if (deepEqual(fieldDef1, fdToMerge)) { - return; - } - } - merged.push(fdToMerge); - }); - return merged; - } - function mergeTitle(title1, title2) { - if (deepEqual(title1, title2) || !title2) { - // if titles are the same or title2 is falsy - return title1; - } else if (!title1) { - // if title1 is falsy - return title2; - } else { - return [...vega.array(title1), ...vega.array(title2)].join(', '); - } - } - function mergeTitleComponent(v1, v2) { - const v1Val = v1.value; - const v2Val = v2.value; - if (v1Val == null || v2Val === null) { - return { - explicit: v1.explicit, - value: null - }; - } else if ((isText(v1Val) || isSignalRef(v1Val)) && (isText(v2Val) || isSignalRef(v2Val))) { - return { - explicit: v1.explicit, - value: mergeTitle(v1Val, v2Val) - }; - } else if (isText(v1Val) || isSignalRef(v1Val)) { - return { - explicit: v1.explicit, - value: v1Val - }; - } else if (isText(v2Val) || isSignalRef(v2Val)) { - return { - explicit: v1.explicit, - value: v2Val - }; - } else if (!isText(v1Val) && !isSignalRef(v1Val) && !isText(v2Val) && !isSignalRef(v2Val)) { - return { - explicit: v1.explicit, - value: mergeTitleFieldDefs(v1Val, v2Val) - }; - } - /* istanbul ignore next: Condition should not happen -- only for warning in development. */ - throw new Error('It should never reach here'); - } - - /** - * Collection of all Vega-Lite Error Messages - */ - - function invalidSpec(spec) { - return `Invalid specification ${stringify(spec)}. Make sure the specification includes at least one of the following properties: "mark", "layer", "facet", "hconcat", "vconcat", "concat", or "repeat".`; - } - - // FIT - const FIT_NON_SINGLE = 'Autosize "fit" only works for single views and layered views.'; - function containerSizeNonSingle(name) { - const uName = name == 'width' ? 'Width' : 'Height'; - return `${uName} "container" only works for single views and layered views.`; - } - function containerSizeNotCompatibleWithAutosize(name) { - const uName = name == 'width' ? 'Width' : 'Height'; - const fitDirection = name == 'width' ? 'x' : 'y'; - return `${uName} "container" only works well with autosize "fit" or "fit-${fitDirection}".`; - } - function droppingFit(channel) { - return channel ? `Dropping "fit-${channel}" because spec has discrete ${getSizeChannel(channel)}.` : `Dropping "fit" because spec has discrete size.`; - } - - // VIEW SIZE - - function unknownField(channel) { - return `Unknown field for ${channel}. Cannot calculate view size.`; - } - - // SELECTION - function cannotProjectOnChannelWithoutField(channel) { - return `Cannot project a selection on encoding channel "${channel}", which has no field.`; - } - function cannotProjectAggregate(channel, aggregate) { - return `Cannot project a selection on encoding channel "${channel}" as it uses an aggregate function ("${aggregate}").`; - } - function nearestNotSupportForContinuous(mark) { - return `The "nearest" transform is not supported for ${mark} marks.`; - } - function selectionNotSupported(mark) { - return `Selection not supported for ${mark} yet.`; - } - function selectionNotFound(name) { - return `Cannot find a selection named "${name}".`; - } - const SCALE_BINDINGS_CONTINUOUS = 'Scale bindings are currently only supported for scales with unbinned, continuous domains.'; - const SEQUENTIAL_SCALE_DEPRECATED = 'Sequntial scales are deprecated. The available quantitative scale type values are linear, log, pow, sqrt, symlog, time and utc'; - const LEGEND_BINDINGS_MUST_HAVE_PROJECTION = 'Legend bindings are only supported for selections over an individual field or encoding channel.'; - function cannotLookupVariableParameter(name) { - return `Lookups can only be performed on selection parameters. "${name}" is a variable parameter.`; - } - function noSameUnitLookup(name) { - return `Cannot define and lookup the "${name}" selection in the same view. ` + `Try moving the lookup into a second, layered view?`; - } - const NEEDS_SAME_SELECTION = 'The same selection must be used to override scale domains in a layered view.'; - const INTERVAL_INITIALIZED_WITH_POS = 'Interval selections should be initialized using "x", "y", "longitude", or "latitude" keys.'; - - // REPEAT - function noSuchRepeatedValue(field) { - return `Unknown repeated value "${field}".`; - } - function columnsNotSupportByRowCol(type) { - return `The "columns" property cannot be used when "${type}" has nested row/column.`; - } - - // CONCAT / REPEAT - const CONCAT_CANNOT_SHARE_AXIS = 'Axes cannot be shared in concatenated or repeated views yet (https://github.com/vega/vega-lite/issues/2415).'; - - // DATA - function unrecognizedParse(p) { - return `Unrecognized parse "${p}".`; - } - function differentParse(field, local, ancestor) { - return `An ancestor parsed field "${field}" as ${ancestor} but a child wants to parse the field as ${local}.`; - } - const ADD_SAME_CHILD_TWICE = 'Attempt to add the same child twice.'; - - // TRANSFORMS - function invalidTransformIgnored(transform) { - return `Ignoring an invalid transform: ${stringify(transform)}.`; - } - const 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.'; - - // ENCODING & FACET - - function customFormatTypeNotAllowed(channel) { - return `Config.customFormatTypes is not true, thus custom format type and format for channel ${channel} are dropped.`; - } - function projectionOverridden(opt) { - const { - parentProjection, - projection - } = opt; - return `Layer's shared projection ${stringify(parentProjection)} is overridden by a child projection ${stringify(projection)}.`; - } - const REPLACE_ANGLE_WITH_THETA = 'Arc marks uses theta channel rather than angle, replacing angle with theta.'; - function offsetNestedInsideContinuousPositionScaleDropped(mainChannel) { - return `${mainChannel}Offset dropped because ${mainChannel} is continuous`; - } - function primitiveChannelDef(channel, type, value) { - return `Channel ${channel} is a ${type}. Converted to {value: ${stringify(value)}}.`; - } - function invalidFieldType(type) { - return `Invalid field type "${type}".`; - } - function invalidFieldTypeForCountAggregate(type, aggregate) { - return `Invalid field type "${type}" for aggregate: "${aggregate}", using "quantitative" instead.`; - } - function invalidAggregate(aggregate) { - return `Invalid aggregation operator "${aggregate}".`; - } - function droppingColor(type, opt) { - const { - fill, - stroke - } = opt; - return `Dropping color ${type} as the plot also has ${fill && stroke ? 'fill and stroke' : fill ? 'fill' : 'stroke'}.`; - } - function relativeBandSizeNotSupported(sizeChannel) { - return `Position range does not support relative band size for ${sizeChannel}.`; - } - function emptyFieldDef(fieldDef, channel) { - return `Dropping ${stringify(fieldDef)} from channel "${channel}" since it does not contain any data field, datum, value, or signal.`; - } - const LINE_WITH_VARYING_SIZE = 'Line marks cannot encode size with a non-groupby field. You may want to use trail marks instead.'; - function incompatibleChannel(channel, markOrFacet, when) { - return `${channel} dropped as it is incompatible with "${markOrFacet}"${''}.`; - } - function invalidEncodingChannel(channel) { - return `${channel}-encoding is dropped as ${channel} is not a valid encoding channel.`; - } - function channelShouldBeDiscrete(channel) { - return `${channel} encoding should be discrete (ordinal / nominal / binned).`; - } - function channelShouldBeDiscreteOrDiscretizing(channel) { - return `${channel} encoding should be discrete (ordinal / nominal / binned) or use a discretizing scale (e.g. threshold).`; - } - function facetChannelDropped(channels) { - return `Facet encoding dropped as ${channels.join(' and ')} ${channels.length > 1 ? 'are' : 'is'} also specified.`; - } - function discreteChannelCannotEncode(channel, type) { - return `Using discrete channel "${channel}" to encode "${type}" field can be misleading as it does not encode ${type === 'ordinal' ? 'order' : 'magnitude'}.`; - } - - // MARK - - function rangeMarkAlignmentCannotBeExpression(align) { - return `The ${align} for range marks cannot be an expression`; - } - function lineWithRange(hasX2, hasY2) { - const channels = hasX2 && hasY2 ? 'x2 and y2' : hasX2 ? 'x2' : 'y2'; - return `Line mark is for continuous lines and thus cannot be used with ${channels}. We will use the rule mark (line segments) instead.`; - } - function orientOverridden(original, actual) { - return `Specified orient "${original}" overridden with "${actual}".`; - } - function cannotUseScalePropertyWithNonColor(prop) { - return `Cannot use the scale property "${prop}" with non-color channel.`; - } - function cannotUseRelativeBandSizeWithNonBandScale(scaleType) { - return `Cannot use the relative band size with ${scaleType} scale.`; - } - function unaggregateDomainHasNoEffectForRawField(fieldDef) { - return `Using unaggregated domain with raw field has no effect (${stringify(fieldDef)}).`; - } - function unaggregateDomainWithNonSharedDomainOp(aggregate) { - return `Unaggregated domain not applicable for "${aggregate}" since it produces values outside the origin domain of the source data.`; - } - function unaggregatedDomainWithLogScale(fieldDef) { - return `Unaggregated domain is currently unsupported for log scale (${stringify(fieldDef)}).`; - } - function cannotApplySizeToNonOrientedMark(mark) { - return `Cannot apply size to non-oriented mark "${mark}".`; - } - function scaleTypeNotWorkWithChannel(channel, scaleType, defaultScaleType) { - return `Channel "${channel}" does not work with "${scaleType}" scale. We are using "${defaultScaleType}" scale instead.`; - } - function scaleTypeNotWorkWithFieldDef(scaleType, defaultScaleType) { - return `FieldDef does not work with "${scaleType}" scale. We are using "${defaultScaleType}" scale instead.`; - } - function scalePropertyNotWorkWithScaleType(scaleType, propName, channel) { - return `${channel}-scale's "${propName}" is dropped as it does not work with ${scaleType} scale.`; - } - function stepDropped(channel) { - return `The step for "${channel}" is dropped because the ${channel === 'width' ? 'x' : 'y'} is continuous.`; - } - function mergeConflictingProperty(property, propertyOf, v1, v2) { - return `Conflicting ${propertyOf.toString()} property "${property.toString()}" (${stringify(v1)} and ${stringify(v2)}). Using ${stringify(v1)}.`; - } - function mergeConflictingDomainProperty(property, propertyOf, v1, v2) { - return `Conflicting ${propertyOf.toString()} property "${property.toString()}" (${stringify(v1)} and ${stringify(v2)}). Using the union of the two domains.`; - } - function independentScaleMeansIndependentGuide(channel) { - return `Setting the scale to be independent for "${channel}" means we also have to set the guide (axis or legend) to be independent.`; - } - function domainSortDropped(sort) { - return `Dropping sort property ${stringify(sort)} as unioned domains only support boolean or op "count", "min", and "max".`; - } - const MORE_THAN_ONE_SORT = 'Domains that should be unioned has conflicting sort properties. Sort will be set to true.'; - const FACETED_INDEPENDENT_DIFFERENT_SOURCES = '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.'; - const FACETED_INDEPENDENT_SAME_FIELDS_DIFFERENT_SOURCES = 'Detected faceted independent scales that union domain of the same fields from different source. We will assume that this is the same field from a different fork of the same data source. However, if this is not the case, the result view size may be incorrect.'; - const FACETED_INDEPENDENT_SAME_SOURCE = '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.'; - - // STACK - function cannotStackRangedMark(channel) { - return `Cannot stack "${channel}" if there is already "${channel}2".`; - } - function stackNonLinearScale(scaleType) { - return `Stack is applied to a non-linear scale (${scaleType}).`; - } - function stackNonSummativeAggregate(aggregate) { - return `Stacking is applied even though the aggregate function is non-summative ("${aggregate}").`; - } - - // TIMEUNIT - function invalidTimeUnit(unitName, value) { - return `Invalid ${unitName}: ${stringify(value)}.`; - } - function droppedDay(d) { - return `Dropping day from datetime ${stringify(d)} as day cannot be combined with other units.`; - } - function errorBarCenterAndExtentAreNotNeeded(center, extent) { - return `${extent ? 'extent ' : ''}${extent && center ? 'and ' : ''}${center ? 'center ' : ''}${extent && center ? 'are ' : 'is '}not needed when data are aggregated.`; - } - function errorBarCenterIsUsedWithWrongExtent(center, extent, mark) { - return `${center} is not usually used with ${extent} for ${mark}.`; - } - function errorBarContinuousAxisHasCustomizedAggregate(aggregate, compositeMark) { - return `Continuous axis should not have customized aggregation function ${aggregate}; ${compositeMark} already agregates the axis.`; - } - function errorBand1DNotSupport(property) { - return `1D error band does not support ${property}.`; - } - - // CHANNEL - function channelRequiredForBinned(channel) { - return `Channel ${channel} is required for "binned" bin.`; - } - function channelShouldNotBeUsedForBinned(channel) { - return `Channel ${channel} should not be used with "binned" bin.`; - } - function domainRequiredForThresholdScale(channel) { - return `Domain for ${channel} is required for threshold scale.`; - } - - /** - * Vega-Lite's singleton logger utility. - */ - - - /** - * Main (default) Vega Logger instance for Vega-Lite. - */ - const main = vega.logger(vega.Warn); - let current = main; - - /** - * Set the singleton logger to be a custom logger. - */ - function set(newLogger) { - current = newLogger; - return current; - } - - /** - * Reset the main logger to use the default Vega Logger. - */ - function reset() { - current = main; - return current; - } - function warn() { - current.warn(...arguments); - } - function debug() { - current.debug(...arguments); - } - - // DateTime definition object - - - /** - * @minimum 1 - * @maximum 12 - * @TJS-type integer - */ - - /** - * @minimum 1 - * @maximum 7 - */ - - /** - * Object for defining datetime in Vega-Lite Filter. - * If both month and quarter are provided, month has higher precedence. - * `day` cannot be combined with other date. - * We accept string for month and day names. - */ - - /** - * Internal Object for defining datetime expressions. - * This is an expression version of DateTime. - * If both month and quarter are provided, month has higher precedence. - * `day` cannot be combined with other date. - */ - - function isDateTime(o) { - if (o && vega.isObject(o)) { - for (const part of TIMEUNIT_PARTS) { - if (part in o) { - return true; - } - } - } - return false; - } - const MONTHS = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december']; - const SHORT_MONTHS = MONTHS.map(m => m.substr(0, 3)); - const DAYS = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; - const SHORT_DAYS = DAYS.map(d => d.substr(0, 3)); - function normalizeQuarter(q) { - if (isNumeric(q)) { - q = +q; - } - if (vega.isNumber(q)) { - if (q > 4) { - warn(invalidTimeUnit('quarter', q)); - } - // We accept 1-based quarter, so need to readjust to 0-based quarter - return q - 1; - } else { - // Invalid quarter - throw new Error(invalidTimeUnit('quarter', q)); - } - } - function normalizeMonth(m) { - if (isNumeric(m)) { - m = +m; - } - if (vega.isNumber(m)) { - // We accept 1-based month, so need to readjust to 0-based month - return m - 1; - } else { - const lowerM = m.toLowerCase(); - const monthIndex = MONTHS.indexOf(lowerM); - if (monthIndex !== -1) { - return monthIndex; // 0 for january, ... - } - const shortM = lowerM.substr(0, 3); - const shortMonthIndex = SHORT_MONTHS.indexOf(shortM); - if (shortMonthIndex !== -1) { - return shortMonthIndex; - } - - // Invalid month - throw new Error(invalidTimeUnit('month', m)); - } - } - function normalizeDay(d) { - if (isNumeric(d)) { - d = +d; - } - if (vega.isNumber(d)) { - // mod so that this can be both 0-based where 0 = sunday - // and 1-based where 7=sunday - return d % 7; - } else { - const lowerD = d.toLowerCase(); - const dayIndex = DAYS.indexOf(lowerD); - if (dayIndex !== -1) { - return dayIndex; // 0 for january, ... - } - const shortD = lowerD.substr(0, 3); - const shortDayIndex = SHORT_DAYS.indexOf(shortD); - if (shortDayIndex !== -1) { - return shortDayIndex; - } - // Invalid day - throw new Error(invalidTimeUnit('day', d)); - } - } - - /** - * @param d the date. - * @param normalize whether to normalize quarter, month, day. This should probably be true if d is a DateTime. - * @returns array of date time parts [year, month, day, hours, minutes, seconds, milliseconds] - */ - function dateTimeParts(d, normalize) { - const parts = []; - if (normalize && d.day !== undefined) { - if (keys(d).length > 1) { - warn(droppedDay(d)); - d = duplicate(d); - delete d.day; - } - } - if (d.year !== undefined) { - parts.push(d.year); - } else { - // Just like Vega's timeunit transform, set default year to 2012, so domain conversion will be compatible with Vega - // Note: 2012 is a leap year (and so the date February 29 is respected) that begins on a Sunday (and so days of the week will order properly at the beginning of the year). - parts.push(2012); - } - if (d.month !== undefined) { - const month = normalize ? normalizeMonth(d.month) : d.month; - parts.push(month); - } else if (d.quarter !== undefined) { - const quarter = normalize ? normalizeQuarter(d.quarter) : d.quarter; - parts.push(vega.isNumber(quarter) ? quarter * 3 : `${quarter}*3`); - } else { - parts.push(0); // months start at zero in JS - } - if (d.date !== undefined) { - parts.push(d.date); - } else if (d.day !== undefined) { - // HACK: Day only works as a standalone unit - // This is only correct because we always set year to 2006 for day - const day = normalize ? normalizeDay(d.day) : d.day; - parts.push(vega.isNumber(day) ? day + 1 : `${day}+1`); - } else { - parts.push(1); // Date starts at 1 in JS - } - - // Note: can't use TimeUnit enum here as importing it will create - // circular dependency problem! - for (const timeUnit of ['hours', 'minutes', 'seconds', 'milliseconds']) { - const unit = d[timeUnit]; - parts.push(typeof unit === 'undefined' ? 0 : unit); - } - return parts; - } - - /** - * Return Vega expression for a date time. - * - * @param d the date time. - * @returns the Vega expression. - */ - function dateTimeToExpr(d) { - const parts = dateTimeParts(d, true); - const string = parts.join(', '); - if (d.utc) { - return `utc(${string})`; - } else { - return `datetime(${string})`; - } - } - - /** - * Return Vega expression for a date time expression. - * - * @param d the internal date time object with expression. - * @returns the Vega expression. - */ - function dateTimeExprToExpr(d) { - const parts = dateTimeParts(d, false); - const string = parts.join(', '); - if (d.utc) { - return `utc(${string})`; - } else { - return `datetime(${string})`; - } - } - - /** - * @param d the date time. - * @returns the timestamp. - */ - function dateTimeToTimestamp(d) { - const parts = dateTimeParts(d, true); - if (d.utc) { - return +new Date(Date.UTC(...parts)); - } else { - return +new Date(...parts); - } - } - - /** Time Unit that only corresponds to only one part of Date objects. */ - const LOCAL_SINGLE_TIMEUNIT_INDEX = { - year: 1, - quarter: 1, - month: 1, - week: 1, - day: 1, - dayofyear: 1, - date: 1, - hours: 1, - minutes: 1, - seconds: 1, - milliseconds: 1 - }; - const TIMEUNIT_PARTS = keys(LOCAL_SINGLE_TIMEUNIT_INDEX); - function isLocalSingleTimeUnit(timeUnit) { - return !!LOCAL_SINGLE_TIMEUNIT_INDEX[timeUnit]; - } - function isBinnedTimeUnit(timeUnit) { - if (vega.isObject(timeUnit)) { - return timeUnit.binned; - } - return isBinnedTimeUnitString(timeUnit); - } - function isBinnedTimeUnitString(timeUnit) { - return timeUnit && timeUnit.startsWith('binned'); - } - function isUTCTimeUnit(t) { - return t.startsWith('utc'); - } - function getLocalTimeUnitFromUTCTimeUnit(t) { - return t.substring(3); - } - - /** - * Time Unit Params for encoding predicate, which can specified if the data is already "binned". - */ - - // matches vega time unit format specifier - - // In order of increasing specificity - const VEGALITE_TIMEFORMAT = { - 'year-month': '%b %Y ', - 'year-month-date': '%b %d, %Y ' - }; - function getTimeUnitParts(timeUnit) { - return TIMEUNIT_PARTS.filter(part => containsTimeUnit(timeUnit, part)); - } - function getSmallestTimeUnitPart(timeUnit) { - const parts = getTimeUnitParts(timeUnit); - return parts[parts.length - 1]; - } - - /** Returns true if fullTimeUnit contains the timeUnit, false otherwise. */ - function containsTimeUnit(fullTimeUnit, timeUnit) { - const index = fullTimeUnit.indexOf(timeUnit); - if (index < 0) { - return false; - } - - // exclude milliseconds - if (index > 0 && timeUnit === 'seconds' && fullTimeUnit.charAt(index - 1) === 'i') { - return false; - } - - // exclude dayofyear - if (fullTimeUnit.length > index + 3 && timeUnit === 'day' && fullTimeUnit.charAt(index + 3) === 'o') { - return false; - } - if (index > 0 && timeUnit === 'year' && fullTimeUnit.charAt(index - 1) === 'f') { - return false; - } - return true; - } - - /** - * Returns Vega expression for a given timeUnit and fieldRef - */ - function fieldExpr(fullTimeUnit, field) { - let { - end - } = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : { - end: false - }; - const fieldRef = accessPathWithDatum(field); - const utc = isUTCTimeUnit(fullTimeUnit) ? 'utc' : ''; - function func(timeUnit) { - if (timeUnit === 'quarter') { - // quarter starting at 0 (0,3,6,9). - return `(${utc}quarter(${fieldRef})-1)`; - } else { - return `${utc}${timeUnit}(${fieldRef})`; - } - } - let lastTimeUnit; - const dateExpr = {}; - for (const part of TIMEUNIT_PARTS) { - if (containsTimeUnit(fullTimeUnit, part)) { - dateExpr[part] = func(part); - lastTimeUnit = part; - } - } - if (end) { - dateExpr[lastTimeUnit] += '+1'; - } - return dateTimeExprToExpr(dateExpr); - } - function timeUnitSpecifierExpression(timeUnit) { - if (!timeUnit) { - return undefined; - } - const timeUnitParts = getTimeUnitParts(timeUnit); - return `timeUnitSpecifier(${stringify(timeUnitParts)}, ${stringify(VEGALITE_TIMEFORMAT)})`; - } - - /** - * Returns the signal expression used for axis labels for a time unit. - */ - function formatExpression(timeUnit, field, isUTCScale) { - if (!timeUnit) { - return undefined; - } - const expr = timeUnitSpecifierExpression(timeUnit); - - // We only use utcFormat for utc scale - // For utc time units, the data is already converted as a part of timeUnit transform. - // Thus, utc time units should use timeFormat to avoid shifting the time twice. - const utc = isUTCScale || isUTCTimeUnit(timeUnit); - return `${utc ? 'utc' : 'time'}Format(${field}, ${expr})`; - } - function normalizeTimeUnit(timeUnit) { - if (!timeUnit) { - return undefined; - } - let params; - if (vega.isString(timeUnit)) { - if (isBinnedTimeUnitString(timeUnit)) { - params = { - unit: timeUnit.substring(6), - binned: true - }; - } else { - params = { - unit: timeUnit - }; - } - } else if (vega.isObject(timeUnit)) { - params = { - ...timeUnit, - ...(timeUnit.unit ? { - unit: timeUnit.unit - } : {}) - }; - } - if (isUTCTimeUnit(params.unit)) { - params.utc = true; - params.unit = getLocalTimeUnitFromUTCTimeUnit(params.unit); - } - return params; - } - function timeUnitToString(tu) { - const { - utc, - ...rest - } = normalizeTimeUnit(tu); - if (rest.unit) { - return (utc ? 'utc' : '') + keys(rest).map(p => varName(`${p === 'unit' ? '' : `_${p}_`}${rest[p]}`)).join(''); - } else { - // when maxbins is specified instead of units - return (utc ? 'utc' : '') + 'timeunit' + keys(rest).map(p => varName(`_${p}_${rest[p]}`)).join(''); - } - } - function durationExpr(timeUnit) { - let wrap = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x => x; - const normalizedTimeUnit = normalizeTimeUnit(timeUnit); - const smallestUnitPart = getSmallestTimeUnitPart(normalizedTimeUnit.unit); - if (smallestUnitPart && smallestUnitPart !== 'day') { - const startDate = { - year: 2001, - // pick a non-leap year - month: 1, - date: 1, - hours: 0, - minutes: 0, - seconds: 0, - milliseconds: 0 - }; - const { - step, - part - } = getDateTimePartAndStep(smallestUnitPart, normalizedTimeUnit.step); - const endDate = { - ...startDate, - [part]: +startDate[part] + step - }; - - // Calculate timestamp duration for the smallest unit listed - return `${wrap(dateTimeToExpr(endDate))} - ${wrap(dateTimeToExpr(startDate))}`; - } - return undefined; - } - const DATE_PARTS = { - year: 1, - month: 1, - date: 1, - hours: 1, - minutes: 1, - seconds: 1, - milliseconds: 1 - }; - function isDatePart(timeUnit) { - return !!DATE_PARTS[timeUnit]; - } - function getDateTimePartAndStep(timeUnit) { - let step = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1; - if (isDatePart(timeUnit)) { - return { - part: timeUnit, - step - }; - } - switch (timeUnit) { - case 'day': - case 'dayofyear': - return { - part: 'date', - step - }; - case 'quarter': - return { - part: 'month', - step: step * 3 - }; - case 'week': - return { - part: 'date', - step: step * 7 - }; - } - } - - function isSelectionPredicate(predicate) { - return predicate?.['param']; - } - function isFieldEqualPredicate(predicate) { - return !!predicate?.field && predicate.equal !== undefined; - } - function isFieldLTPredicate(predicate) { - return !!predicate?.field && predicate.lt !== undefined; - } - function isFieldLTEPredicate(predicate) { - return !!predicate?.field && predicate.lte !== undefined; - } - function isFieldGTPredicate(predicate) { - return !!predicate?.field && predicate.gt !== undefined; - } - function isFieldGTEPredicate(predicate) { - return !!predicate?.field && predicate.gte !== undefined; - } - function isFieldRangePredicate(predicate) { - if (predicate?.field) { - if (vega.isArray(predicate.range) && predicate.range.length === 2) { - return true; - } else if (isSignalRef(predicate.range)) { - return true; - } - } - return false; - } - function isFieldOneOfPredicate(predicate) { - return !!predicate?.field && (vega.isArray(predicate.oneOf) || vega.isArray(predicate.in)) // backward compatibility - ; - } - function isFieldValidPredicate(predicate) { - return !!predicate?.field && predicate.valid !== undefined; - } - function isFieldPredicate(predicate) { - return isFieldOneOfPredicate(predicate) || isFieldEqualPredicate(predicate) || isFieldRangePredicate(predicate) || isFieldLTPredicate(predicate) || isFieldGTPredicate(predicate) || isFieldLTEPredicate(predicate) || isFieldGTEPredicate(predicate); - } - function predicateValueExpr(v, timeUnit) { - return valueExpr(v, { - timeUnit, - wrapTime: true - }); - } - function predicateValuesExpr(vals, timeUnit) { - return vals.map(v => predicateValueExpr(v, timeUnit)); - } - - // This method is used by Voyager. Do not change its behavior without changing Voyager. - function fieldFilterExpression(predicate) { - let useInRange = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; - const { - field - } = predicate; - const normalizedTimeUnit = normalizeTimeUnit(predicate.timeUnit); - const { - unit, - binned - } = normalizedTimeUnit || {}; - const rawFieldExpr = vgField(predicate, { - expr: 'datum' - }); - const fieldExpr$1 = unit ? - // For timeUnit, cast into integer with time() so we can use ===, inrange, indexOf to compare values directly. - // TODO: We calculate timeUnit on the fly here. Consider if we would like to consolidate this with timeUnit pipeline - // TODO: support utc - `time(${!binned ? fieldExpr(unit, field) : rawFieldExpr})` : rawFieldExpr; - if (isFieldEqualPredicate(predicate)) { - return `${fieldExpr$1}===${predicateValueExpr(predicate.equal, unit)}`; - } else if (isFieldLTPredicate(predicate)) { - const upper = predicate.lt; - return `${fieldExpr$1}<${predicateValueExpr(upper, unit)}`; - } else if (isFieldGTPredicate(predicate)) { - const lower = predicate.gt; - return `${fieldExpr$1}>${predicateValueExpr(lower, unit)}`; - } else if (isFieldLTEPredicate(predicate)) { - const upper = predicate.lte; - return `${fieldExpr$1}<=${predicateValueExpr(upper, unit)}`; - } else if (isFieldGTEPredicate(predicate)) { - const lower = predicate.gte; - return `${fieldExpr$1}>=${predicateValueExpr(lower, unit)}`; - } else if (isFieldOneOfPredicate(predicate)) { - return `indexof([${predicateValuesExpr(predicate.oneOf, unit).join(',')}], ${fieldExpr$1}) !== -1`; - } else if (isFieldValidPredicate(predicate)) { - return fieldValidPredicate(fieldExpr$1, predicate.valid); - } else if (isFieldRangePredicate(predicate)) { - const { - range - } = predicate; - const lower = isSignalRef(range) ? { - signal: `${range.signal}[0]` - } : range[0]; - const upper = isSignalRef(range) ? { - signal: `${range.signal}[1]` - } : range[1]; - if (lower !== null && upper !== null && useInRange) { - return 'inrange(' + fieldExpr$1 + ', [' + predicateValueExpr(lower, unit) + ', ' + predicateValueExpr(upper, unit) + '])'; - } - const exprs = []; - if (lower !== null) { - exprs.push(`${fieldExpr$1} >= ${predicateValueExpr(lower, unit)}`); - } - if (upper !== null) { - exprs.push(`${fieldExpr$1} <= ${predicateValueExpr(upper, unit)}`); - } - return exprs.length > 0 ? exprs.join(' && ') : 'true'; - } - - /* istanbul ignore next: it should never reach here */ - throw new Error(`Invalid field predicate: ${stringify(predicate)}`); - } - function fieldValidPredicate(fieldExpr) { - let valid = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; - if (valid) { - return `isValid(${fieldExpr}) && isFinite(+${fieldExpr})`; - } else { - return `!isValid(${fieldExpr}) || !isFinite(+${fieldExpr})`; - } - } - function normalizePredicate$1(f) { - if (isFieldPredicate(f) && f.timeUnit) { - return { - ...f, - timeUnit: normalizeTimeUnit(f.timeUnit) - }; - } - return f; - } - - /** - * Data type based on level of measurement - */ - const Type = { - quantitative: 'quantitative', - ordinal: 'ordinal', - temporal: 'temporal', - nominal: 'nominal', - geojson: 'geojson' - }; - function isContinuous(type) { - return type === 'quantitative' || type === 'temporal'; - } - function isDiscrete$1(type) { - return type === 'ordinal' || type === 'nominal'; - } - const QUANTITATIVE = Type.quantitative; - const ORDINAL = Type.ordinal; - const TEMPORAL = Type.temporal; - const NOMINAL = Type.nominal; - const GEOJSON = Type.geojson; - - /** - * Get full, lowercase type name for a given type. - * @param type - * @return Full type name. - */ - function getFullName(type) { - if (type) { - type = type.toLowerCase(); - switch (type) { - case 'q': - case QUANTITATIVE: - return 'quantitative'; - case 't': - case TEMPORAL: - return 'temporal'; - case 'o': - case ORDINAL: - return 'ordinal'; - case 'n': - case NOMINAL: - return 'nominal'; - case GEOJSON: - return 'geojson'; - } - } - // If we get invalid input, return undefined type. - return undefined; - } - - const ScaleType = { - // Continuous - Quantitative - LINEAR: 'linear', - LOG: 'log', - POW: 'pow', - SQRT: 'sqrt', - SYMLOG: 'symlog', - IDENTITY: 'identity', - SEQUENTIAL: 'sequential', - // Continuous - Time - TIME: 'time', - UTC: 'utc', - // Discretizing scales - QUANTILE: 'quantile', - QUANTIZE: 'quantize', - THRESHOLD: 'threshold', - BIN_ORDINAL: 'bin-ordinal', - // Discrete scales - ORDINAL: 'ordinal', - POINT: 'point', - BAND: 'band' - }; - /** - * Index for scale categories -- only scale of the same categories can be merged together. - * Current implementation is trying to be conservative and avoid merging scale type that might not work together - */ - const SCALE_CATEGORY_INDEX = { - linear: 'numeric', - log: 'numeric', - pow: 'numeric', - sqrt: 'numeric', - symlog: 'numeric', - identity: 'numeric', - sequential: 'numeric', - time: 'time', - utc: 'time', - ordinal: 'ordinal', - 'bin-ordinal': 'bin-ordinal', - // TODO: should bin-ordinal support merging with other - point: 'ordinal-position', - band: 'ordinal-position', - quantile: 'discretizing', - quantize: 'discretizing', - threshold: 'discretizing' - }; - - /** - * Whether the two given scale types can be merged together. - */ - function scaleCompatible(scaleType1, scaleType2) { - const scaleCategory1 = SCALE_CATEGORY_INDEX[scaleType1]; - const scaleCategory2 = SCALE_CATEGORY_INDEX[scaleType2]; - return scaleCategory1 === scaleCategory2 || scaleCategory1 === 'ordinal-position' && scaleCategory2 === 'time' || scaleCategory2 === 'ordinal-position' && scaleCategory1 === 'time'; - } - - /** - * Index for scale precedence -- high score = higher priority for merging. - */ - const SCALE_PRECEDENCE_INDEX = { - // numeric - linear: 0, - log: 1, - pow: 1, - sqrt: 1, - symlog: 1, - identity: 1, - sequential: 1, - // time - time: 0, - utc: 0, - // ordinal-position -- these have higher precedence than continuous scales as they support more types of data - point: 10, - band: 11, - // band has higher precedence as it is better for interaction - // non grouped types - ordinal: 0, - 'bin-ordinal': 0, - quantile: 0, - quantize: 0, - threshold: 0 - }; - - /** - * Return scale categories -- only scale of the same categories can be merged together. - */ - function scaleTypePrecedence(scaleType) { - return SCALE_PRECEDENCE_INDEX[scaleType]; - } - const QUANTITATIVE_SCALES = new Set(['linear', 'log', 'pow', 'sqrt', 'symlog']); - const CONTINUOUS_TO_CONTINUOUS_SCALES = new Set([...QUANTITATIVE_SCALES, 'time', 'utc']); - function isQuantitative(type) { - return QUANTITATIVE_SCALES.has(type); - } - const CONTINUOUS_TO_DISCRETE_SCALES = new Set(['quantile', 'quantize', 'threshold']); - const CONTINUOUS_DOMAIN_SCALES = new Set([...CONTINUOUS_TO_CONTINUOUS_SCALES, ...CONTINUOUS_TO_DISCRETE_SCALES, 'sequential', 'identity']); - const DISCRETE_DOMAIN_SCALES = new Set(['ordinal', 'bin-ordinal', 'point', 'band']); - function hasDiscreteDomain(type) { - return DISCRETE_DOMAIN_SCALES.has(type); - } - function hasContinuousDomain(type) { - return CONTINUOUS_DOMAIN_SCALES.has(type); - } - function isContinuousToContinuous(type) { - return CONTINUOUS_TO_CONTINUOUS_SCALES.has(type); - } - function isContinuousToDiscrete(type) { - return CONTINUOUS_TO_DISCRETE_SCALES.has(type); - } - const defaultScaleConfig = { - pointPadding: 0.5, - barBandPaddingInner: 0.1, - rectBandPaddingInner: 0, - tickBandPaddingInner: 0.25, - bandWithNestedOffsetPaddingInner: 0.2, - bandWithNestedOffsetPaddingOuter: 0.2, - minBandSize: 2, - minFontSize: 8, - maxFontSize: 40, - minOpacity: 0.3, - maxOpacity: 0.8, - // FIXME: revise if these *can* become ratios of width/height step - minSize: 4, - // Point size is area. For square point, 9 = 3 pixel ^ 2, not too small! - - minStrokeWidth: 1, - maxStrokeWidth: 4, - quantileCount: 4, - quantizeCount: 4, - zero: true - }; - function isExtendedScheme(scheme) { - return !vega.isString(scheme) && !!scheme['name']; - } - function isParameterDomain(domain) { - return domain?.['param']; - } - function isDomainUnionWith(domain) { - return domain?.['unionWith']; - } - function isFieldRange(range) { - return vega.isObject(range) && 'field' in range; - } - const SCALE_PROPERTY_INDEX = { - type: 1, - domain: 1, - domainMax: 1, - domainMin: 1, - domainMid: 1, - domainRaw: 1, - align: 1, - range: 1, - rangeMax: 1, - rangeMin: 1, - scheme: 1, - bins: 1, - // Other properties - reverse: 1, - round: 1, - // quantitative / time - clamp: 1, - nice: 1, - // quantitative - base: 1, - exponent: 1, - constant: 1, - interpolate: 1, - zero: 1, - // zero depends on domain - // band/point - padding: 1, - paddingInner: 1, - paddingOuter: 1 - }; - const { - type, - domain: domain$1, - range, - rangeMax, - rangeMin, - scheme, - ...NON_TYPE_DOMAIN_RANGE_VEGA_SCALE_PROPERTY_INDEX - } = SCALE_PROPERTY_INDEX; - const NON_TYPE_DOMAIN_RANGE_VEGA_SCALE_PROPERTIES = keys(NON_TYPE_DOMAIN_RANGE_VEGA_SCALE_PROPERTY_INDEX); - function scaleTypeSupportProperty(scaleType, propName) { - switch (propName) { - case 'type': - case 'domain': - case 'reverse': - case 'range': - return true; - case 'scheme': - case 'interpolate': - return !['point', 'band', 'identity'].includes(scaleType); - case 'bins': - return !['point', 'band', 'identity', 'ordinal'].includes(scaleType); - case 'round': - return isContinuousToContinuous(scaleType) || scaleType === 'band' || scaleType === 'point'; - case 'padding': - case 'rangeMin': - case 'rangeMax': - return isContinuousToContinuous(scaleType) || ['point', 'band'].includes(scaleType); - case 'paddingOuter': - case 'align': - return ['point', 'band'].includes(scaleType); - case 'paddingInner': - return scaleType === 'band'; - case 'domainMax': - case 'domainMid': - case 'domainMin': - case 'domainRaw': - case 'clamp': - return isContinuousToContinuous(scaleType); - case 'nice': - return isContinuousToContinuous(scaleType) || scaleType === 'quantize' || scaleType === 'threshold'; - case 'exponent': - return scaleType === 'pow'; - case 'base': - return scaleType === 'log'; - case 'constant': - return scaleType === 'symlog'; - case 'zero': - return hasContinuousDomain(scaleType) && !contains(['log', - // log scale cannot have zero value - 'time', 'utc', - // zero is not meaningful for time - 'threshold', - // threshold requires custom domain so zero does not matter - 'quantile' // quantile depends on distribution so zero does not matter - ], scaleType); - } - } - - /** - * Returns undefined if the input channel supports the input scale property name - */ - function channelScalePropertyIncompatability(channel, propName) { - switch (propName) { - case 'interpolate': - case 'scheme': - case 'domainMid': - if (!isColorChannel(channel)) { - return cannotUseScalePropertyWithNonColor(propName); - } - return undefined; - case 'align': - case 'type': - case 'bins': - case 'domain': - case 'domainMax': - case 'domainMin': - case 'domainRaw': - case 'range': - case 'base': - case 'exponent': - case 'constant': - case 'nice': - case 'padding': - case 'paddingInner': - case 'paddingOuter': - case 'rangeMax': - case 'rangeMin': - case 'reverse': - case 'round': - case 'clamp': - case 'zero': - return undefined; - // GOOD! - } - } - function scaleTypeSupportDataType(specifiedType, fieldDefType) { - if (contains([ORDINAL, NOMINAL], fieldDefType)) { - return specifiedType === undefined || hasDiscreteDomain(specifiedType); - } else if (fieldDefType === TEMPORAL) { - return contains([ScaleType.TIME, ScaleType.UTC, undefined], specifiedType); - } else if (fieldDefType === QUANTITATIVE) { - return isQuantitative(specifiedType) || isContinuousToDiscrete(specifiedType) || specifiedType === undefined; - } - return true; - } - function channelSupportScaleType(channel, scaleType) { - let hasNestedOffsetScale = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; - if (!isScaleChannel(channel)) { - return false; - } - switch (channel) { - case X: - case Y: - case XOFFSET: - case YOFFSET: - case THETA: - case RADIUS: - if (isContinuousToContinuous(scaleType)) { - return true; - } else if (scaleType === 'band') { - return true; - } else if (scaleType === 'point') { - /* - Point scale can't be use if the position has a nested offset scale - because if there is a nested scale, then it's band. - */ - return !hasNestedOffsetScale; - } - return false; - case SIZE: // TODO: size and opacity can support ordinal with more modification - case STROKEWIDTH: - case OPACITY: - case FILLOPACITY: - case STROKEOPACITY: - case ANGLE: - // Although it generally doesn't make sense to use band with size and opacity, - // it can also work since we use band: 0.5 to get midpoint. - return isContinuousToContinuous(scaleType) || isContinuousToDiscrete(scaleType) || contains(['band', 'point', 'ordinal'], scaleType); - case COLOR: - case FILL: - case STROKE: - return scaleType !== 'band'; - // band does not make sense with color - case STROKEDASH: - case SHAPE: - return scaleType === 'ordinal' || isContinuousToDiscrete(scaleType); - } - } - - /** - * Mixins for Vega-Lite Spec's Mark Definiton (to add mark.invalid) - */ - - /** - * Mixins for Vega-Lite Spec's config.scale - */ - - function isScaleInvalidDataIncludeAsValue(invalidDataMode) { - return vega.isObject(invalidDataMode) && 'value' in invalidDataMode; - } - - /** - * All types of primitive marks. - */ - const Mark = { - arc: 'arc', - area: 'area', - bar: 'bar', - image: 'image', - line: 'line', - point: 'point', - rect: 'rect', - rule: 'rule', - text: 'text', - tick: 'tick', - trail: 'trail', - circle: 'circle', - square: 'square', - geoshape: 'geoshape' - }; - const ARC = Mark.arc; - const AREA = Mark.area; - const BAR = Mark.bar; - const IMAGE = Mark.image; - const LINE = Mark.line; - const POINT = Mark.point; - const RECT = Mark.rect; - const RULE = Mark.rule; - const TEXT = Mark.text; - const TICK = Mark.tick; - const TRAIL = Mark.trail; - const CIRCLE = Mark.circle; - const SQUARE = Mark.square; - const GEOSHAPE = Mark.geoshape; - function isPathMark(m) { - return ['line', 'area', 'trail'].includes(m); - } - function isRectBasedMark(m) { - return ['rect', 'bar', 'image', 'arc' /* arc is rect/interval in polar coordinate */].includes(m); - } - const PRIMITIVE_MARKS = new Set(keys(Mark)); - function isMarkDef(mark) { - return mark['type']; - } - const STROKE_CONFIG = ['stroke', 'strokeWidth', 'strokeDash', 'strokeDashOffset', 'strokeOpacity', 'strokeJoin', 'strokeMiterLimit']; - const FILL_CONFIG = ['fill', 'fillOpacity']; - const FILL_STROKE_CONFIG = [...STROKE_CONFIG, ...FILL_CONFIG]; - const VL_ONLY_MARK_CONFIG_INDEX = { - color: 1, - filled: 1, - invalid: 1, - order: 1, - radius2: 1, - theta2: 1, - timeUnitBandSize: 1, - timeUnitBandPosition: 1 - }; - const VL_ONLY_MARK_CONFIG_PROPERTIES = keys(VL_ONLY_MARK_CONFIG_INDEX); - const VL_ONLY_MARK_SPECIFIC_CONFIG_PROPERTY_INDEX = { - area: ['line', 'point'], - bar: ['binSpacing', 'continuousBandSize', 'discreteBandSize', 'minBandSize'], - rect: ['binSpacing', 'continuousBandSize', 'discreteBandSize', 'minBandSize'], - line: ['point'], - tick: ['bandSize', 'thickness'] - }; - const defaultMarkConfig = { - color: '#4c78a8', - invalid: 'break-paths-show-path-domains', - timeUnitBandSize: 1 - }; - - // TODO: replace with MarkConfigMixins[Mark] once https://github.com/vega/ts-json-schema-generator/issues/344 is fixed - - const MARK_CONFIG_INDEX = { - mark: 1, - arc: 1, - area: 1, - bar: 1, - circle: 1, - image: 1, - line: 1, - point: 1, - rect: 1, - rule: 1, - square: 1, - text: 1, - tick: 1, - trail: 1, - geoshape: 1 - }; - const MARK_CONFIGS = keys(MARK_CONFIG_INDEX); - function isRelativeBandSize(o) { - return o && o['band'] != undefined; - } - const BAR_CORNER_RADIUS_INDEX = { - horizontal: ['cornerRadiusTopRight', 'cornerRadiusBottomRight'], - vertical: ['cornerRadiusTopLeft', 'cornerRadiusTopRight'] - }; - - // Point/Line OverlayMixins are only for area, line, and trail but we don't want to declare multiple types of MarkDef - - const DEFAULT_RECT_BAND_SIZE = 5; - const defaultBarConfig = { - binSpacing: 1, - continuousBandSize: DEFAULT_RECT_BAND_SIZE, - minBandSize: 0.25, - timeUnitBandPosition: 0.5 - }; - const defaultRectConfig = { - binSpacing: 0, - continuousBandSize: DEFAULT_RECT_BAND_SIZE, - minBandSize: 0.25, - timeUnitBandPosition: 0.5 - }; - const defaultTickConfig = { - thickness: 1 - }; - function getMarkType(m) { - return isMarkDef(m) ? m.type : m; - } - - function normalizeInvalidDataMode(mode, _ref) { - let { - isPath - } = _ref; - if (mode === undefined || mode === 'break-paths-show-path-domains') { - return isPath ? 'break-paths-show-domains' : 'filter'; - } else if (mode === null) { - return 'show'; - } - return mode; - } - - function getScaleInvalidDataMode(_ref) { - let { - markDef, - config, - scaleChannel, - scaleType, - isCountAggregate - } = _ref; - if (!scaleType || !hasContinuousDomain(scaleType) || isCountAggregate) { - // - Discrete scales can always display null as another category - // - Count cannot output null values - return 'always-valid'; - } - const invalidMode = normalizeInvalidDataMode(getMarkPropOrConfig('invalid', markDef, config), { - isPath: isPathMark(markDef.type) - }); - const scaleOutputForInvalid = config.scale?.invalid?.[scaleChannel]; - if (scaleOutputForInvalid !== undefined) { - // Regardless of the current invalid mode, if the channel has a default value, we consider the field valid. - return 'show'; - } - return invalidMode; - } - function shouldBreakPath(mode) { - return mode === 'break-paths-filter-domains' || mode === 'break-paths-show-domains'; - } - - function scaledZeroOrMinOrMax(_ref) { - let { - scaleName, - scale, - mode - } = _ref; - const domain = `domain('${scaleName}')`; - if (!scale || !scaleName) { - return undefined; - } - const min = `${domain}[0]`; - const max = `peek(${domain})`; // peek = the last item of the array - - // If there is a scale (and hence its name) - const domainHasZero = scale.domainHasZero(); - // zeroOrMin or zeroOrMax mode - if (domainHasZero === 'definitely') { - return { - scale: scaleName, - value: 0 - }; - } else if (domainHasZero === 'maybe') { - const nonZeroValue = mode === 'zeroOrMin' ? min : max; - return { - signal: `scale('${scaleName}', inrange(0, ${domain}) ? 0 : ${nonZeroValue})` - }; - } else { - // domainHasZero === 'definitely-not' - return { - signal: `scale('${scaleName}', ${mode === 'zeroOrMin' ? min : max})` - }; - } - } - - function getConditionalValueRefForIncludingInvalidValue(_ref) { - let { - scaleChannel, - channelDef, - scale, - scaleName, - markDef, - config - } = _ref; - const scaleType = scale?.get('type'); - const fieldDef = getFieldDef(channelDef); - const isCountAggregate = isCountingAggregateOp(fieldDef?.aggregate); - const invalidDataMode = getScaleInvalidDataMode({ - scaleChannel, - markDef, - config, - scaleType, - isCountAggregate - }); - if (fieldDef && invalidDataMode === 'show') { - const includeAs = config.scale.invalid?.[scaleChannel] ?? 'zero-or-min'; - return { - test: fieldValidPredicate(vgField(fieldDef, { - expr: 'datum' - }), false), - ...refForInvalidValues(includeAs, scale, scaleName) - }; - } - return undefined; - } - function refForInvalidValues(includeAs, scale, scaleName) { - if (isScaleInvalidDataIncludeAsValue(includeAs)) { - const { - value - } = includeAs; - return isSignalRef(value) ? { - signal: value.signal - } : { - value - }; - } - return scaledZeroOrMinOrMax({ - scale, - scaleName, - mode: 'zeroOrMin' - }); - } - - /** - * Utility files for producing Vega ValueRef for marks - */ - - function midPointRefWithPositionInvalidTest(params) { - const { - channel, - channelDef, - markDef, - scale, - scaleName, - config - } = params; - const scaleChannel = getMainRangeChannel(channel); - const mainRef = midPoint(params); - const valueRefForIncludingInvalid = getConditionalValueRefForIncludingInvalidValue({ - scaleChannel, - channelDef, - scale, - scaleName, - markDef, - config - }); - return valueRefForIncludingInvalid !== undefined ? [valueRefForIncludingInvalid, mainRef] : mainRef; - } - function datumDefToExpr(datumDef) { - const { - datum - } = datumDef; - if (isDateTime(datum)) { - return dateTimeToExpr(datum); - } - return `${stringify(datum)}`; - } - function valueRefForFieldOrDatumDef(fieldDef, scaleName, opt, encode) { - const ref = {}; - if (scaleName) { - ref.scale = scaleName; - } - if (isDatumDef(fieldDef)) { - const { - datum - } = fieldDef; - if (isDateTime(datum)) { - ref.signal = dateTimeToExpr(datum); - } else if (isSignalRef(datum)) { - ref.signal = datum.signal; - } else if (isExprRef(datum)) { - ref.signal = datum.expr; - } else { - ref.value = datum; - } - } else { - ref.field = vgField(fieldDef, opt); - } - if (encode) { - const { - offset, - band - } = encode; - if (offset) { - ref.offset = offset; - } - if (band) { - ref.band = band; - } - } - return ref; - } - - /** - * Signal that returns the middle of a bin from start and end field. Should only be used with x and y. - */ - function interpolatedSignalRef(_ref) { - let { - scaleName, - fieldOrDatumDef, - fieldOrDatumDef2, - offset, - startSuffix, - endSuffix = 'end', - bandPosition = 0.5 - } = _ref; - const expr = !isSignalRef(bandPosition) && 0 < bandPosition && bandPosition < 1 ? 'datum' : undefined; - const start = vgField(fieldOrDatumDef, { - expr, - suffix: startSuffix - }); - const end = fieldOrDatumDef2 !== undefined ? vgField(fieldOrDatumDef2, { - expr - }) : vgField(fieldOrDatumDef, { - suffix: endSuffix, - expr - }); - const ref = {}; - if (bandPosition === 0 || bandPosition === 1) { - ref.scale = scaleName; - const field = bandPosition === 0 ? start : end; - ref.field = field; - } else { - const datum = isSignalRef(bandPosition) ? `(1-${bandPosition.signal}) * ${start} + ${bandPosition.signal} * ${end}` : `${1 - bandPosition} * ${start} + ${bandPosition} * ${end}`; - ref.signal = `scale("${scaleName}", ${datum})`; - } - if (offset) { - ref.offset = offset; - } - return ref; - } - function binSizeExpr(_ref2) { - let { - scaleName, - fieldDef - } = _ref2; - const start = vgField(fieldDef, { - expr: 'datum' - }); - const end = vgField(fieldDef, { - expr: 'datum', - suffix: 'end' - }); - return `abs(scale("${scaleName}", ${end}) - scale("${scaleName}", ${start}))`; - } - /** - * @returns {VgValueRef} Value Ref for xc / yc or mid point for other channels. - */ - function midPoint(_ref3) { - let { - channel, - channelDef, - channel2Def, - markDef, - config, - scaleName, - scale, - stack, - offset, - defaultRef, - bandPosition - } = _ref3; - // TODO: datum support - if (channelDef) { - /* istanbul ignore else */ - - if (isFieldOrDatumDef(channelDef)) { - const scaleType = scale?.get('type'); - if (isTypedFieldDef(channelDef)) { - bandPosition ??= getBandPosition({ - fieldDef: channelDef, - fieldDef2: channel2Def, - markDef, - config - }); - const { - bin, - timeUnit, - type - } = channelDef; - if (isBinning(bin) || bandPosition && timeUnit && type === TEMPORAL) { - // Use middle only for x an y to place marks in the center between start and end of the bin range. - // We do not use the mid point for other channels (e.g. size) so that properties of legends and marks match. - if (stack?.impute) { - // For stack, we computed bin_mid so we can impute. - return valueRefForFieldOrDatumDef(channelDef, scaleName, { - binSuffix: 'mid' - }, { - offset - }); - } - if (bandPosition && !hasDiscreteDomain(scaleType)) { - // if band = 0, no need to call interpolation - // For non-stack, we can just calculate bin mid on the fly using signal. - return interpolatedSignalRef({ - scaleName, - fieldOrDatumDef: channelDef, - bandPosition, - offset - }); - } - return valueRefForFieldOrDatumDef(channelDef, scaleName, binRequiresRange(channelDef, channel) ? { - binSuffix: 'range' - } : {}, { - offset - }); - } else if (isBinned(bin)) { - if (isFieldDef(channel2Def)) { - return interpolatedSignalRef({ - scaleName, - fieldOrDatumDef: channelDef, - fieldOrDatumDef2: channel2Def, - bandPosition, - offset - }); - } else { - const channel2 = channel === X ? X2 : Y2; - warn(channelRequiredForBinned(channel2)); - } - } - } - return valueRefForFieldOrDatumDef(channelDef, scaleName, hasDiscreteDomain(scaleType) ? { - binSuffix: 'range' - } : {}, - // no need for bin suffix if there is no scale - { - offset, - // For band, to get mid point, need to offset by half of the band - band: scaleType === 'band' ? bandPosition ?? channelDef.bandPosition ?? 0.5 : undefined - }); - } else if (isValueDef(channelDef)) { - const value = channelDef.value; - const offsetMixins = offset ? { - offset - } : {}; - return { - ...widthHeightValueOrSignalRef(channel, value), - ...offsetMixins - }; - } - - // If channelDef is neither field def or value def, it's a condition-only def. - // In such case, we will use default ref. - } - if (vega.isFunction(defaultRef)) { - defaultRef = defaultRef(); - } - if (defaultRef) { - // for non-position, ref could be undefined. - return { - ...defaultRef, - // only include offset when it is non-zero (zero = no offset) - ...(offset ? { - offset - } : {}) - }; - } - return defaultRef; - } - - /** - * Convert special "width" and "height" values in Vega-Lite into Vega value ref. - */ - function widthHeightValueOrSignalRef(channel, value) { - if (contains(['x', 'x2'], channel) && value === 'width') { - return { - field: { - group: 'width' - } - }; - } else if (contains(['y', 'y2'], channel) && value === 'height') { - return { - field: { - group: 'height' - } - }; - } - return signalOrValueRef(value); - } - - function isCustomFormatType(formatType) { - return formatType && formatType !== 'number' && formatType !== 'time'; - } - function customFormatExpr(formatType, field, format) { - return `${formatType}(${field}${format ? `, ${stringify(format)}` : ''})`; - } - const BIN_RANGE_DELIMITER = ' \u2013 '; - function formatSignalRef(_ref) { - let { - fieldOrDatumDef, - format, - formatType, - expr, - normalizeStack, - config - } = _ref; - if (isCustomFormatType(formatType)) { - return formatCustomType({ - fieldOrDatumDef, - format, - formatType, - expr, - config - }); - } - const field = fieldToFormat(fieldOrDatumDef, expr, normalizeStack); - const type = channelDefType(fieldOrDatumDef); - if (format === undefined && formatType === undefined && config.customFormatTypes) { - if (type === 'quantitative') { - if (normalizeStack && config.normalizedNumberFormatType) return formatCustomType({ - fieldOrDatumDef, - format: config.normalizedNumberFormat, - formatType: config.normalizedNumberFormatType, - expr, - config - }); - if (config.numberFormatType) { - return formatCustomType({ - fieldOrDatumDef, - format: config.numberFormat, - formatType: config.numberFormatType, - expr, - config - }); - } - } - if (type === 'temporal' && config.timeFormatType && isFieldDef(fieldOrDatumDef) && fieldOrDatumDef.timeUnit === undefined) { - return formatCustomType({ - fieldOrDatumDef, - format: config.timeFormat, - formatType: config.timeFormatType, - expr, - config - }); - } - } - if (isFieldOrDatumDefForTimeFormat(fieldOrDatumDef)) { - const signal = timeFormatExpression({ - field, - timeUnit: isFieldDef(fieldOrDatumDef) ? normalizeTimeUnit(fieldOrDatumDef.timeUnit)?.unit : undefined, - format, - formatType: config.timeFormatType, - rawTimeFormat: config.timeFormat, - isUTCScale: isScaleFieldDef(fieldOrDatumDef) && fieldOrDatumDef.scale?.type === ScaleType.UTC - }); - return signal ? { - signal - } : undefined; - } - format = numberFormat({ - type, - specifiedFormat: format, - config, - normalizeStack - }); - if (isFieldDef(fieldOrDatumDef) && isBinning(fieldOrDatumDef.bin)) { - const endField = vgField(fieldOrDatumDef, { - expr, - binSuffix: 'end' - }); - return { - signal: binFormatExpression(field, endField, format, formatType, config) - }; - } else if (format || channelDefType(fieldOrDatumDef) === 'quantitative') { - return { - signal: `${formatExpr(field, format)}` - }; - } else { - return { - signal: `isValid(${field}) ? ${field} : ""+${field}` - }; - } - } - function fieldToFormat(fieldOrDatumDef, expr, normalizeStack) { - if (isFieldDef(fieldOrDatumDef)) { - if (normalizeStack) { - return `${vgField(fieldOrDatumDef, { - expr, - suffix: 'end' - })}-${vgField(fieldOrDatumDef, { - expr, - suffix: 'start' - })}`; - } else { - return vgField(fieldOrDatumDef, { - expr - }); - } - } else { - return datumDefToExpr(fieldOrDatumDef); - } - } - function formatCustomType(_ref2) { - let { - fieldOrDatumDef, - format, - formatType, - expr, - normalizeStack, - config, - field - } = _ref2; - field ??= fieldToFormat(fieldOrDatumDef, expr, normalizeStack); - if (field !== 'datum.value' && - // For axis/legend, we can't correctly know the end of the bin from `datum` - isFieldDef(fieldOrDatumDef) && isBinning(fieldOrDatumDef.bin)) { - const endField = vgField(fieldOrDatumDef, { - expr, - binSuffix: 'end' - }); - return { - signal: binFormatExpression(field, endField, format, formatType, config) - }; - } - return { - signal: customFormatExpr(formatType, field, format) - }; - } - function guideFormat(fieldOrDatumDef, type, format, formatType, config, omitTimeFormatConfig) { - if (vega.isString(formatType) && isCustomFormatType(formatType)) { - return undefined; // handled in encode block - } else if (format === undefined && formatType === undefined && config.customFormatTypes) { - if (channelDefType(fieldOrDatumDef) === 'quantitative') { - if (config.normalizedNumberFormatType && isPositionFieldOrDatumDef(fieldOrDatumDef) && fieldOrDatumDef.stack === 'normalize') { - return undefined; // handled in encode block - } - if (config.numberFormatType) { - return undefined; // handled in encode block - } - } - } - if (isPositionFieldOrDatumDef(fieldOrDatumDef) && fieldOrDatumDef.stack === 'normalize' && config.normalizedNumberFormat) { - return numberFormat({ - type: 'quantitative', - config, - normalizeStack: true - }); - } - if (isFieldOrDatumDefForTimeFormat(fieldOrDatumDef)) { - const timeUnit = isFieldDef(fieldOrDatumDef) ? normalizeTimeUnit(fieldOrDatumDef.timeUnit)?.unit : undefined; - if (timeUnit === undefined && config.customFormatTypes && config.timeFormatType) { - return undefined; // hanlded in encode block - } - return timeFormat({ - specifiedFormat: format, - timeUnit, - config, - omitTimeFormatConfig - }); - } - return numberFormat({ - type, - specifiedFormat: format, - config - }); - } - function guideFormatType(formatType, fieldOrDatumDef, scaleType) { - if (formatType && (isSignalRef(formatType) || formatType === 'number' || formatType === 'time')) { - return formatType; - } - if (isFieldOrDatumDefForTimeFormat(fieldOrDatumDef) && scaleType !== 'time' && scaleType !== 'utc') { - return isFieldDef(fieldOrDatumDef) && normalizeTimeUnit(fieldOrDatumDef?.timeUnit)?.utc ? 'utc' : 'time'; - } - return undefined; - } - - /** - * Returns number format for a fieldDef. - */ - function numberFormat(_ref3) { - let { - type, - specifiedFormat, - config, - normalizeStack - } = _ref3; - // Specified format in axis/legend has higher precedence than fieldDef.format - if (vega.isString(specifiedFormat)) { - return specifiedFormat; - } - if (type === QUANTITATIVE) { - // we only apply the default if the field is quantitative - return normalizeStack ? config.normalizedNumberFormat : config.numberFormat; - } - return undefined; - } - - /** - * Returns time format for a fieldDef for use in guides. - */ - function timeFormat(_ref4) { - let { - specifiedFormat, - timeUnit, - config, - omitTimeFormatConfig - } = _ref4; - if (specifiedFormat) { - return specifiedFormat; - } - if (timeUnit) { - return { - signal: timeUnitSpecifierExpression(timeUnit) - }; - } - return omitTimeFormatConfig ? undefined : config.timeFormat; - } - function formatExpr(field, format) { - return `format(${field}, "${format || ''}")`; - } - function binNumberFormatExpr(field, format, formatType, config) { - if (isCustomFormatType(formatType)) { - return customFormatExpr(formatType, field, format); - } - return formatExpr(field, (vega.isString(format) ? format : undefined) ?? config.numberFormat); - } - function binFormatExpression(startField, endField, format, formatType, config) { - if (format === undefined && formatType === undefined && config.customFormatTypes && config.numberFormatType) { - return binFormatExpression(startField, endField, config.numberFormat, config.numberFormatType, config); - } - const start = binNumberFormatExpr(startField, format, formatType, config); - const end = binNumberFormatExpr(endField, format, formatType, config); - return `${fieldValidPredicate(startField, false)} ? "null" : ${start} + "${BIN_RANGE_DELIMITER}" + ${end}`; - } - - /** - * Returns the time expression used for axis/legend labels or text mark for a temporal field - */ - function timeFormatExpression(_ref5) { - let { - field, - timeUnit, - format, - formatType, - rawTimeFormat, - isUTCScale - } = _ref5; - if (!timeUnit || format) { - // If there is no time unit, or if user explicitly specifies format for axis/legend/text. - if (!timeUnit && formatType) { - return `${formatType}(${field}, '${format}')`; - } - format = vega.isString(format) ? format : rawTimeFormat; // only use provided timeFormat if there is no timeUnit. - return `${isUTCScale ? 'utc' : 'time'}Format(${field}, '${format}')`; - } else { - return formatExpression(timeUnit, field, isUTCScale); - } - } - - /** - * A sort definition for transform - */ - - const DEFAULT_SORT_OP = 'min'; - - /** - * A sort definition for sorting a discrete scale in an encoding field definition. - */ - - const SORT_BY_CHANNEL_INDEX = { - x: 1, - y: 1, - color: 1, - fill: 1, - stroke: 1, - strokeWidth: 1, - size: 1, - shape: 1, - fillOpacity: 1, - strokeOpacity: 1, - opacity: 1, - text: 1 - }; - function isSortByChannel(c) { - return c in SORT_BY_CHANNEL_INDEX; - } - function isSortByEncoding(sort) { - return !!sort?.['encoding']; - } - function isSortField(sort) { - return sort && (sort['op'] === 'count' || !!sort['field']); - } - function isSortArray(sort) { - return sort && vega.isArray(sort); - } - - function isFacetMapping(f) { - return 'row' in f || 'column' in f; - } - - /** - * Facet mapping for encoding macro - */ - - function isFacetFieldDef(channelDef) { - return !!channelDef && 'header' in channelDef; - } - - /** - * Base interface for a facet specification. - */ - - /** - * A facet specification without any shortcut / expansion syntax - */ - - function isFacetSpec(spec) { - return 'facet' in spec; - } - - /** - * Definition object for a constant value (primitive value or gradient definition) of an encoding channel. - */ - - /** - * A ValueDef with Condition where either the condition or the value are optional. - * { - * condition: {field: ...} | {value: ...}, - * value: ..., - * } - */ - - /** - * @minProperties 1 - */ - - function isConditionalParameter(c) { - return c['param']; - } - - /** - * A FieldDef with Condition - * { - * condition: {value: ...}, - * field: ..., - * ... - * } - */ - - /** - * A ValueDef with optional Condition - * { - * condition: {field: ...} | {value: ...}, - * value: ..., - * } - */ - - /** - * Reference to a repeated value. - */ - - function isRepeatRef(field) { - return field && !vega.isString(field) && 'repeat' in field; - } - - /** @@hidden */ - - function toFieldDefBase(fieldDef) { - const { - field, - timeUnit, - bin, - aggregate - } = fieldDef; - return { - ...(timeUnit ? { - timeUnit - } : {}), - ...(bin ? { - bin - } : {}), - ...(aggregate ? { - aggregate - } : {}), - field - }; - } - - /** - * Definition object for a data field, its type and transformation of an encoding channel. - */ - - function isSortableFieldDef(fieldDef) { - return 'sort' in fieldDef; - } - - /** - * A field definition of a secondary channel that shares a scale with another primary channel. For example, `x2`, `xError` and `xError2` share the same scale with `x`. - */ - // x2/y2 shouldn't have bin, but we keep bin property for simplicity of the codebase. - - /** - * Field Def without scale (and without bin: "binned" support). - */ - - // Lat long shouldn't have bin, but we keep bin property for simplicity of the codebase. - - function getBandPosition(_ref) { - let { - fieldDef, - fieldDef2, - markDef: mark, - config - } = _ref; - if (isFieldOrDatumDef(fieldDef) && fieldDef.bandPosition !== undefined) { - return fieldDef.bandPosition; - } - if (isFieldDef(fieldDef)) { - const { - timeUnit, - bin - } = fieldDef; - if (timeUnit && !fieldDef2) { - return getMarkConfig('timeUnitBandPosition', mark, config); - } else if (isBinning(bin)) { - return 0.5; - } - } - return undefined; - } - function getBandSize(_ref2) { - let { - channel, - fieldDef, - fieldDef2, - markDef: mark, - config, - scaleType, - useVlSizeChannel - } = _ref2; - const sizeChannel = getSizeChannel(channel); - const size = getMarkPropOrConfig(useVlSizeChannel ? 'size' : sizeChannel, mark, config, { - vgChannel: sizeChannel - }); - if (size !== undefined) { - return size; - } - if (isFieldDef(fieldDef)) { - const { - timeUnit, - bin - } = fieldDef; - if (timeUnit && !fieldDef2) { - return { - band: getMarkConfig('timeUnitBandSize', mark, config) - }; - } else if (isBinning(bin) && !hasDiscreteDomain(scaleType)) { - return { - band: 1 - }; - } - } - if (isRectBasedMark(mark.type)) { - if (scaleType) { - if (hasDiscreteDomain(scaleType)) { - return config[mark.type]?.discreteBandSize || { - band: 1 - }; - } else { - return config[mark.type]?.continuousBandSize; - } - } - return config[mark.type]?.discreteBandSize; - } - return undefined; - } - function hasBandEnd(fieldDef, fieldDef2, markDef, config) { - if (isBinning(fieldDef.bin) || fieldDef.timeUnit && isTypedFieldDef(fieldDef) && fieldDef.type === 'temporal') { - // Need to check bandPosition because non-rect marks (e.g., point) with timeUnit - // doesn't have to use bandEnd if there is no bandPosition. - return getBandPosition({ - fieldDef, - fieldDef2, - markDef, - config - }) !== undefined; - } - return false; - } - - /** - * Field definition of a mark property, which can contain a legend. - */ - - // Detail - - // Order Path have no scale - - function isOrderOnlyDef(orderDef) { - return orderDef && !!orderDef.sort && !orderDef['field']; - } - function isConditionalDef(channelDef) { - return channelDef && 'condition' in channelDef; - } - - /** - * Return if a channelDef is a ConditionalValueDef with ConditionFieldDef - */ - function hasConditionalFieldDef(channelDef) { - const condition = channelDef?.['condition']; - return !!condition && !vega.isArray(condition) && isFieldDef(condition); - } - function hasConditionalFieldOrDatumDef(channelDef) { - const condition = channelDef?.['condition']; - return !!condition && !vega.isArray(condition) && isFieldOrDatumDef(condition); - } - function hasConditionalValueDef(channelDef) { - const condition = channelDef?.['condition']; - return !!condition && (vega.isArray(condition) || isValueDef(condition)); - } - function isFieldDef(channelDef) { - // TODO: we can't use field in channelDef here as it's somehow failing runtime test - return channelDef && (!!channelDef['field'] || channelDef['aggregate'] === 'count'); - } - function channelDefType(channelDef) { - return channelDef?.['type']; - } - function isDatumDef(channelDef) { - return channelDef && 'datum' in channelDef; - } - function isContinuousFieldOrDatumDef(cd) { - // TODO: make datum support DateTime object - return isTypedFieldDef(cd) && !isDiscrete(cd) || isNumericDataDef(cd); - } - function isUnbinnedQuantitativeFieldOrDatumDef(cd) { - // TODO: make datum support DateTime object - return isTypedFieldDef(cd) && cd.type === 'quantitative' && !cd.bin || isNumericDataDef(cd); - } - function isNumericDataDef(cd) { - return isDatumDef(cd) && vega.isNumber(cd.datum); - } - function isFieldOrDatumDef(channelDef) { - return isFieldDef(channelDef) || isDatumDef(channelDef); - } - function isTypedFieldDef(channelDef) { - return channelDef && ('field' in channelDef || channelDef['aggregate'] === 'count') && 'type' in channelDef; - } - function isValueDef(channelDef) { - return channelDef && 'value' in channelDef && 'value' in channelDef; - } - function isScaleFieldDef(channelDef) { - return channelDef && ('scale' in channelDef || 'sort' in channelDef); - } - function isPositionFieldOrDatumDef(channelDef) { - return channelDef && ('axis' in channelDef || 'stack' in channelDef || 'impute' in channelDef); - } - function isMarkPropFieldOrDatumDef(channelDef) { - return channelDef && 'legend' in channelDef; - } - function isStringFieldOrDatumDef(channelDef) { - return channelDef && ('format' in channelDef || 'formatType' in channelDef); - } - function toStringFieldDef(fieldDef) { - // omit properties that don't exist in string field defs - return omit(fieldDef, ['legend', 'axis', 'header', 'scale']); - } - function isOpFieldDef(fieldDef) { - return 'op' in fieldDef; - } - - /** - * Get a Vega field reference from a Vega-Lite field def. - */ - function vgField(fieldDef) { - let opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - let field = fieldDef.field; - const prefix = opt.prefix; - let suffix = opt.suffix; - let argAccessor = ''; // for accessing argmin/argmax field at the end without getting escaped - - if (isCount(fieldDef)) { - field = internalField('count'); - } else { - let fn; - if (!opt.nofn) { - if (isOpFieldDef(fieldDef)) { - fn = fieldDef.op; - } else { - const { - bin, - aggregate, - timeUnit - } = fieldDef; - if (isBinning(bin)) { - fn = binToString(bin); - suffix = (opt.binSuffix ?? '') + (opt.suffix ?? ''); - } else if (aggregate) { - if (isArgmaxDef(aggregate)) { - argAccessor = `["${field}"]`; - field = `argmax_${aggregate.argmax}`; - } else if (isArgminDef(aggregate)) { - argAccessor = `["${field}"]`; - field = `argmin_${aggregate.argmin}`; - } else { - fn = String(aggregate); - } - } else if (timeUnit && !isBinnedTimeUnit(timeUnit)) { - fn = timeUnitToString(timeUnit); - suffix = (!['range', 'mid'].includes(opt.binSuffix) && opt.binSuffix || '') + (opt.suffix ?? ''); - } - } - } - if (fn) { - field = field ? `${fn}_${field}` : fn; - } - } - if (suffix) { - field = `${field}_${suffix}`; - } - if (prefix) { - field = `${prefix}_${field}`; - } - if (opt.forAs) { - return removePathFromField(field); - } else if (opt.expr) { - // Expression to access flattened field. No need to escape dots. - return flatAccessWithDatum(field, opt.expr) + argAccessor; - } else { - // We flattened all fields so paths should have become dot. - return replacePathInField(field) + argAccessor; - } - } - function isDiscrete(def) { - switch (def.type) { - case 'nominal': - case 'ordinal': - case 'geojson': - return true; - case 'quantitative': - return isFieldDef(def) && !!def.bin; - case 'temporal': - return false; - } - throw new Error(invalidFieldType(def.type)); - } - function isDiscretizing(def) { - return isScaleFieldDef(def) && isContinuousToDiscrete(def.scale?.type); - } - function isCount(fieldDef) { - return fieldDef.aggregate === 'count'; - } - function verbalTitleFormatter(fieldDef, config) { - const { - field, - bin, - timeUnit, - aggregate - } = fieldDef; - if (aggregate === 'count') { - return config.countTitle; - } else if (isBinning(bin)) { - return `${field} (binned)`; - } else if (timeUnit && !isBinnedTimeUnit(timeUnit)) { - const unit = normalizeTimeUnit(timeUnit)?.unit; - if (unit) { - return `${field} (${getTimeUnitParts(unit).join('-')})`; - } - } else if (aggregate) { - if (isArgmaxDef(aggregate)) { - return `${field} for max ${aggregate.argmax}`; - } else if (isArgminDef(aggregate)) { - return `${field} for min ${aggregate.argmin}`; - } else { - return `${titleCase(aggregate)} of ${field}`; - } - } - return field; - } - function functionalTitleFormatter(fieldDef) { - const { - aggregate, - bin, - timeUnit, - field - } = fieldDef; - if (isArgmaxDef(aggregate)) { - return `${field} for argmax(${aggregate.argmax})`; - } else if (isArgminDef(aggregate)) { - return `${field} for argmin(${aggregate.argmin})`; - } - const timeUnitParams = timeUnit && !isBinnedTimeUnit(timeUnit) ? normalizeTimeUnit(timeUnit) : undefined; - const fn = aggregate || timeUnitParams?.unit || timeUnitParams?.maxbins && 'timeunit' || isBinning(bin) && 'bin'; - if (fn) { - return `${fn.toUpperCase()}(${field})`; - } else { - return field; - } - } - const defaultTitleFormatter = (fieldDef, config) => { - switch (config.fieldTitle) { - case 'plain': - return fieldDef.field; - case 'functional': - return functionalTitleFormatter(fieldDef); - default: - return verbalTitleFormatter(fieldDef, config); - } - }; - let titleFormatter = defaultTitleFormatter; - function setTitleFormatter(formatter) { - titleFormatter = formatter; - } - function resetTitleFormatter() { - setTitleFormatter(defaultTitleFormatter); - } - function title(fieldOrDatumDef, config, _ref3) { - let { - allowDisabling, - includeDefault = true - } = _ref3; - const guideTitle = getGuide(fieldOrDatumDef)?.title; - if (!isFieldDef(fieldOrDatumDef)) { - return guideTitle ?? fieldOrDatumDef.title; - } - const fieldDef = fieldOrDatumDef; - const def = includeDefault ? defaultTitle(fieldDef, config) : undefined; - if (allowDisabling) { - return getFirstDefined(guideTitle, fieldDef.title, def); - } else { - return guideTitle ?? fieldDef.title ?? def; - } - } - function getGuide(fieldDef) { - if (isPositionFieldOrDatumDef(fieldDef) && fieldDef.axis) { - return fieldDef.axis; - } else if (isMarkPropFieldOrDatumDef(fieldDef) && fieldDef.legend) { - return fieldDef.legend; - } else if (isFacetFieldDef(fieldDef) && fieldDef.header) { - return fieldDef.header; - } - return undefined; - } - function defaultTitle(fieldDef, config) { - return titleFormatter(fieldDef, config); - } - function getFormatMixins(fieldDef) { - if (isStringFieldOrDatumDef(fieldDef)) { - const { - format, - formatType - } = fieldDef; - return { - format, - formatType - }; - } else { - const guide = getGuide(fieldDef) ?? {}; - const { - format, - formatType - } = guide; - return { - format, - formatType - }; - } - } - function defaultType$2(fieldDef, channel) { - switch (channel) { - case 'latitude': - case 'longitude': - return 'quantitative'; - case 'row': - case 'column': - case 'facet': - case 'shape': - case 'strokeDash': - return 'nominal'; - case 'order': - return 'ordinal'; - } - if (isSortableFieldDef(fieldDef) && vega.isArray(fieldDef.sort)) { - return 'ordinal'; - } - const { - aggregate, - bin, - timeUnit - } = fieldDef; - if (timeUnit) { - return 'temporal'; - } - if (bin || aggregate && !isArgmaxDef(aggregate) && !isArgminDef(aggregate)) { - return 'quantitative'; - } - if (isScaleFieldDef(fieldDef) && fieldDef.scale?.type) { - switch (SCALE_CATEGORY_INDEX[fieldDef.scale.type]) { - case 'numeric': - case 'discretizing': - return 'quantitative'; - case 'time': - return 'temporal'; - } - } - return 'nominal'; - } - - /** - * Returns the fieldDef -- either from the outer channelDef or from the condition of channelDef. - * @param channelDef - */ - - function getFieldDef(channelDef) { - if (isFieldDef(channelDef)) { - return channelDef; - } else if (hasConditionalFieldDef(channelDef)) { - return channelDef.condition; - } - return undefined; - } - function getFieldOrDatumDef(channelDef) { - if (isFieldOrDatumDef(channelDef)) { - return channelDef; - } else if (hasConditionalFieldOrDatumDef(channelDef)) { - return channelDef.condition; - } - return undefined; - } - - /** - * Convert type to full, lowercase type, or augment the fieldDef with a default type if missing. - */ - function initChannelDef(channelDef, channel, config) { - let opt = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; - if (vega.isString(channelDef) || vega.isNumber(channelDef) || vega.isBoolean(channelDef)) { - const primitiveType = vega.isString(channelDef) ? 'string' : vega.isNumber(channelDef) ? 'number' : 'boolean'; - warn(primitiveChannelDef(channel, primitiveType, channelDef)); - return { - value: channelDef - }; - } - - // If a fieldDef contains a field, we need type. - if (isFieldOrDatumDef(channelDef)) { - return initFieldOrDatumDef(channelDef, channel, config, opt); - } else if (hasConditionalFieldOrDatumDef(channelDef)) { - return { - ...channelDef, - // Need to cast as normalizeFieldDef normally return FieldDef, but here we know that it is definitely Condition - condition: initFieldOrDatumDef(channelDef.condition, channel, config, opt) - }; - } - return channelDef; - } - function initFieldOrDatumDef(fd, channel, config, opt) { - if (isStringFieldOrDatumDef(fd)) { - const { - format, - formatType, - ...rest - } = fd; - if (isCustomFormatType(formatType) && !config.customFormatTypes) { - warn(customFormatTypeNotAllowed(channel)); - return initFieldOrDatumDef(rest, channel, config, opt); - } - } else { - const guideType = isPositionFieldOrDatumDef(fd) ? 'axis' : isMarkPropFieldOrDatumDef(fd) ? 'legend' : isFacetFieldDef(fd) ? 'header' : null; - if (guideType && fd[guideType]) { - const { - format, - formatType, - ...newGuide - } = fd[guideType]; - if (isCustomFormatType(formatType) && !config.customFormatTypes) { - warn(customFormatTypeNotAllowed(channel)); - return initFieldOrDatumDef({ - ...fd, - [guideType]: newGuide - }, channel, config, opt); - } - } - } - if (isFieldDef(fd)) { - return initFieldDef(fd, channel, opt); - } - return initDatumDef(fd); - } - function initDatumDef(datumDef) { - let type = datumDef['type']; - if (type) { - return datumDef; - } - const { - datum - } = datumDef; - type = vega.isNumber(datum) ? 'quantitative' : vega.isString(datum) ? 'nominal' : isDateTime(datum) ? 'temporal' : undefined; - return { - ...datumDef, - type - }; - } - function initFieldDef(fd, channel) { - let { - compositeMark = false - } = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - const { - aggregate, - timeUnit, - bin, - field - } = fd; - const fieldDef = { - ...fd - }; - - // Drop invalid aggregate - if (!compositeMark && aggregate && !isAggregateOp(aggregate) && !isArgmaxDef(aggregate) && !isArgminDef(aggregate)) { - warn(invalidAggregate(aggregate)); - delete fieldDef.aggregate; - } - - // Normalize Time Unit - if (timeUnit) { - fieldDef.timeUnit = normalizeTimeUnit(timeUnit); - } - if (field) { - fieldDef.field = `${field}`; - } - - // Normalize bin - if (isBinning(bin)) { - fieldDef.bin = normalizeBin(bin, channel); - } - if (isBinned(bin) && !isXorY(channel)) { - warn(channelShouldNotBeUsedForBinned(channel)); - } - - // Normalize Type - if (isTypedFieldDef(fieldDef)) { - const { - type - } = fieldDef; - const fullType = getFullName(type); - if (type !== fullType) { - // convert short type to full type - fieldDef.type = fullType; - } - if (type !== 'quantitative') { - if (isCountingAggregateOp(aggregate)) { - warn(invalidFieldTypeForCountAggregate(type, aggregate)); - fieldDef.type = 'quantitative'; - } - } - } else if (!isSecondaryRangeChannel(channel)) { - // If type is empty / invalid, then augment with default type - const newType = defaultType$2(fieldDef, channel); - fieldDef['type'] = newType; - } - if (isTypedFieldDef(fieldDef)) { - const { - compatible, - warning - } = channelCompatibility(fieldDef, channel) || {}; - if (compatible === false) { - warn(warning); - } - } - if (isSortableFieldDef(fieldDef) && vega.isString(fieldDef.sort)) { - const { - sort - } = fieldDef; - if (isSortByChannel(sort)) { - return { - ...fieldDef, - sort: { - encoding: sort - } - }; - } - const sub = sort.substr(1); - if (sort.charAt(0) === '-' && isSortByChannel(sub)) { - return { - ...fieldDef, - sort: { - encoding: sub, - order: 'descending' - } - }; - } - } - if (isFacetFieldDef(fieldDef)) { - const { - header - } = fieldDef; - if (header) { - const { - orient, - ...rest - } = header; - if (orient) { - return { - ...fieldDef, - header: { - ...rest, - labelOrient: header.labelOrient || orient, - titleOrient: header.titleOrient || orient - } - }; - } - } - } - return fieldDef; - } - function normalizeBin(bin, channel) { - if (vega.isBoolean(bin)) { - return { - maxbins: autoMaxBins(channel) - }; - } else if (bin === 'binned') { - return { - binned: true - }; - } else if (!bin.maxbins && !bin.step) { - return { - ...bin, - maxbins: autoMaxBins(channel) - }; - } else { - return bin; - } - } - const COMPATIBLE = { - compatible: true - }; - function channelCompatibility(fieldDef, channel) { - const type = fieldDef.type; - if (type === 'geojson' && channel !== 'shape') { - return { - compatible: false, - warning: `Channel ${channel} should not be used with a geojson data.` - }; - } - switch (channel) { - case ROW: - case COLUMN: - case FACET: - if (!isDiscrete(fieldDef)) { - return { - compatible: false, - warning: channelShouldBeDiscrete(channel) - }; - } - return COMPATIBLE; - case X: - case Y: - case XOFFSET: - case YOFFSET: - case COLOR: - case FILL: - case STROKE: - case TEXT$1: - case DETAIL: - case KEY: - case TOOLTIP: - case HREF: - case URL: - case ANGLE: - case THETA: - case RADIUS: - case DESCRIPTION: - return COMPATIBLE; - case LONGITUDE: - case LONGITUDE2: - case LATITUDE: - case LATITUDE2: - if (type !== QUANTITATIVE) { - return { - compatible: false, - warning: `Channel ${channel} should be used with a quantitative field only, not ${fieldDef.type} field.` - }; - } - return COMPATIBLE; - case OPACITY: - case FILLOPACITY: - case STROKEOPACITY: - case STROKEWIDTH: - case SIZE: - case THETA2: - case RADIUS2: - case X2: - case Y2: - if (type === 'nominal' && !fieldDef['sort']) { - return { - compatible: false, - warning: `Channel ${channel} should not be used with an unsorted discrete field.` - }; - } - return COMPATIBLE; - case SHAPE: - case STROKEDASH: - if (!isDiscrete(fieldDef) && !isDiscretizing(fieldDef)) { - return { - compatible: false, - warning: channelShouldBeDiscreteOrDiscretizing(channel) - }; - } - return COMPATIBLE; - case ORDER: - if (fieldDef.type === 'nominal' && !('sort' in fieldDef)) { - return { - compatible: false, - warning: `Channel order is inappropriate for nominal field, which has no inherent order.` - }; - } - return COMPATIBLE; - } - } - - /** - * Check if the field def uses a time format or does not use any format but is temporal - * (this does not cover field defs that are temporal but use a number format). - */ - function isFieldOrDatumDefForTimeFormat(fieldOrDatumDef) { - const { - formatType - } = getFormatMixins(fieldOrDatumDef); - return formatType === 'time' || !formatType && isTimeFieldDef(fieldOrDatumDef); - } - - /** - * Check if field def has type `temporal`. If you want to also cover field defs that use a time format, use `isTimeFormatFieldDef`. - */ - function isTimeFieldDef(def) { - return def && (def['type'] === 'temporal' || isFieldDef(def) && !!def.timeUnit); - } - - /** - * Getting a value associated with a fielddef. - * Convert the value to Vega expression if applicable (for datetime object, or string if the field def is temporal or has timeUnit) - */ - function valueExpr(v, _ref4) { - let { - timeUnit, - type, - wrapTime, - undefinedIfExprNotRequired - } = _ref4; - const unit = timeUnit && normalizeTimeUnit(timeUnit)?.unit; - let isTime = unit || type === 'temporal'; - let expr; - if (isExprRef(v)) { - expr = v.expr; - } else if (isSignalRef(v)) { - expr = v.signal; - } else if (isDateTime(v)) { - isTime = true; - expr = dateTimeToExpr(v); - } else if (vega.isString(v) || vega.isNumber(v)) { - if (isTime) { - expr = `datetime(${stringify(v)})`; - if (isLocalSingleTimeUnit(unit)) { - // for single timeUnit, we will use dateTimeToExpr to convert number/string to match the timeUnit - if (vega.isNumber(v) && v < 10000 || vega.isString(v) && isNaN(Date.parse(v))) { - expr = dateTimeToExpr({ - [unit]: v - }); - } - } - } - } - if (expr) { - return wrapTime && isTime ? `time(${expr})` : expr; - } - // number or boolean or normal string - return undefinedIfExprNotRequired ? undefined : stringify(v); - } - - /** - * Standardize value array -- convert each value to Vega expression if applicable - */ - function valueArray(fieldOrDatumDef, values) { - const { - type - } = fieldOrDatumDef; - return values.map(v => { - const timeUnit = isFieldDef(fieldOrDatumDef) && !isBinnedTimeUnit(fieldOrDatumDef.timeUnit) ? fieldOrDatumDef.timeUnit : undefined; - const expr = valueExpr(v, { - timeUnit, - type, - undefinedIfExprNotRequired: true - }); - // return signal for the expression if we need an expression - if (expr !== undefined) { - return { - signal: expr - }; - } - // otherwise just return the original value - return v; - }); - } - - /** - * Checks whether a fieldDef for a particular channel requires a computed bin range. - */ - function binRequiresRange(fieldDef, channel) { - if (!isBinning(fieldDef.bin)) { - console.warn('Only call this method for binned field defs.'); - return false; - } - - // 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. - // We could check whether the axis or legend exists (not disabled) but that seems overkill. - return isScaleChannel(channel) && ['ordinal', 'nominal'].includes(fieldDef.type); - } - - const CONDITIONAL_AXIS_PROP_INDEX = { - labelAlign: { - part: 'labels', - vgProp: 'align' - }, - labelBaseline: { - part: 'labels', - vgProp: 'baseline' - }, - labelColor: { - part: 'labels', - vgProp: 'fill' - }, - labelFont: { - part: 'labels', - vgProp: 'font' - }, - labelFontSize: { - part: 'labels', - vgProp: 'fontSize' - }, - labelFontStyle: { - part: 'labels', - vgProp: 'fontStyle' - }, - labelFontWeight: { - part: 'labels', - vgProp: 'fontWeight' - }, - labelOpacity: { - part: 'labels', - vgProp: 'opacity' - }, - labelOffset: null, - labelPadding: null, - // There is no fixed vgProp for tickSize, need to use signal. - gridColor: { - part: 'grid', - vgProp: 'stroke' - }, - gridDash: { - part: 'grid', - vgProp: 'strokeDash' - }, - gridDashOffset: { - part: 'grid', - vgProp: 'strokeDashOffset' - }, - gridOpacity: { - part: 'grid', - vgProp: 'opacity' - }, - gridWidth: { - part: 'grid', - vgProp: 'strokeWidth' - }, - tickColor: { - part: 'ticks', - vgProp: 'stroke' - }, - tickDash: { - part: 'ticks', - vgProp: 'strokeDash' - }, - tickDashOffset: { - part: 'ticks', - vgProp: 'strokeDashOffset' - }, - tickOpacity: { - part: 'ticks', - vgProp: 'opacity' - }, - tickSize: null, - // There is no fixed vgProp for tickSize, need to use signal. - tickWidth: { - part: 'ticks', - vgProp: 'strokeWidth' - } - }; - function isConditionalAxisValue(v) { - return v?.condition; - } - - // Vega axis config is the same as Vega axis base. If this is not the case, add specific type. - - const AXIS_PARTS = ['domain', 'grid', 'labels', 'ticks', 'title']; - - /** - * A dictionary listing whether a certain axis property is applicable for only main axes or only grid axes. - */ - const AXIS_PROPERTY_TYPE = { - grid: 'grid', - gridCap: 'grid', - gridColor: 'grid', - gridDash: 'grid', - gridDashOffset: 'grid', - gridOpacity: 'grid', - gridScale: 'grid', - gridWidth: 'grid', - orient: 'main', - bandPosition: 'both', - // Need to be applied to grid axis too, so the grid will align with ticks. - - aria: 'main', - description: 'main', - domain: 'main', - domainCap: 'main', - domainColor: 'main', - domainDash: 'main', - domainDashOffset: 'main', - domainOpacity: 'main', - domainWidth: 'main', - format: 'main', - formatType: 'main', - labelAlign: 'main', - labelAngle: 'main', - labelBaseline: 'main', - labelBound: 'main', - labelColor: 'main', - labelFlush: 'main', - labelFlushOffset: 'main', - labelFont: 'main', - labelFontSize: 'main', - labelFontStyle: 'main', - labelFontWeight: 'main', - labelLimit: 'main', - labelLineHeight: 'main', - labelOffset: 'main', - labelOpacity: 'main', - labelOverlap: 'main', - labelPadding: 'main', - labels: 'main', - labelSeparation: 'main', - maxExtent: 'main', - minExtent: 'main', - offset: 'both', - position: 'main', - tickCap: 'main', - tickColor: 'main', - tickDash: 'main', - tickDashOffset: 'main', - tickMinStep: 'both', - tickOffset: 'both', - // Need to be applied to grid axis too, so the grid will align with ticks. - tickOpacity: 'main', - tickRound: 'both', - // Apply rounding to grid and ticks so they are aligned. - ticks: 'main', - tickSize: 'main', - tickWidth: 'both', - title: 'main', - titleAlign: 'main', - titleAnchor: 'main', - titleAngle: 'main', - titleBaseline: 'main', - titleColor: 'main', - titleFont: 'main', - titleFontSize: 'main', - titleFontStyle: 'main', - titleFontWeight: 'main', - titleLimit: 'main', - titleLineHeight: 'main', - titleOpacity: 'main', - titlePadding: 'main', - titleX: 'main', - titleY: 'main', - encode: 'both', - // we hide this in Vega-Lite - scale: 'both', - tickBand: 'both', - tickCount: 'both', - tickExtra: 'both', - translate: 'both', - values: 'both', - zindex: 'both' // this is actually set afterward, so it doesn't matter - }; - const COMMON_AXIS_PROPERTIES_INDEX = { - orient: 1, - // other things can depend on orient - - aria: 1, - bandPosition: 1, - description: 1, - domain: 1, - domainCap: 1, - domainColor: 1, - domainDash: 1, - domainDashOffset: 1, - domainOpacity: 1, - domainWidth: 1, - format: 1, - formatType: 1, - grid: 1, - gridCap: 1, - gridColor: 1, - gridDash: 1, - gridDashOffset: 1, - gridOpacity: 1, - gridWidth: 1, - labelAlign: 1, - labelAngle: 1, - labelBaseline: 1, - labelBound: 1, - labelColor: 1, - labelFlush: 1, - labelFlushOffset: 1, - labelFont: 1, - labelFontSize: 1, - labelFontStyle: 1, - labelFontWeight: 1, - labelLimit: 1, - labelLineHeight: 1, - labelOffset: 1, - labelOpacity: 1, - labelOverlap: 1, - labelPadding: 1, - labels: 1, - labelSeparation: 1, - maxExtent: 1, - minExtent: 1, - offset: 1, - position: 1, - tickBand: 1, - tickCap: 1, - tickColor: 1, - tickCount: 1, - tickDash: 1, - tickDashOffset: 1, - tickExtra: 1, - tickMinStep: 1, - tickOffset: 1, - tickOpacity: 1, - tickRound: 1, - ticks: 1, - tickSize: 1, - tickWidth: 1, - title: 1, - titleAlign: 1, - titleAnchor: 1, - titleAngle: 1, - titleBaseline: 1, - titleColor: 1, - titleFont: 1, - titleFontSize: 1, - titleFontStyle: 1, - titleFontWeight: 1, - titleLimit: 1, - titleLineHeight: 1, - titleOpacity: 1, - titlePadding: 1, - titleX: 1, - titleY: 1, - translate: 1, - values: 1, - zindex: 1 - }; - const AXIS_PROPERTIES_INDEX = { - ...COMMON_AXIS_PROPERTIES_INDEX, - style: 1, - labelExpr: 1, - encoding: 1 - }; - function isAxisProperty(prop) { - return !!AXIS_PROPERTIES_INDEX[prop]; - } - const AXIS_CONFIGS_INDEX = { - axis: 1, - axisBand: 1, - axisBottom: 1, - axisDiscrete: 1, - axisLeft: 1, - axisPoint: 1, - axisQuantitative: 1, - axisRight: 1, - axisTemporal: 1, - axisTop: 1, - axisX: 1, - axisXBand: 1, - axisXDiscrete: 1, - axisXPoint: 1, - axisXQuantitative: 1, - axisXTemporal: 1, - axisY: 1, - axisYBand: 1, - axisYDiscrete: 1, - axisYPoint: 1, - axisYQuantitative: 1, - axisYTemporal: 1 - }; - const AXIS_CONFIGS = keys(AXIS_CONFIGS_INDEX); - - /** - * Base interface for a unit (single-view) specification. - */ - - /** - * A unit specification without any shortcut/expansion syntax. - */ - - /** - * A unit specification, which can contain either [primitive marks or composite marks](https://vega.github.io/vega-lite/docs/mark.html#types). - */ - - /** - * Unit spec that can have a composite mark and row or column channels (shorthand for a facet spec). - */ - - function isUnitSpec(spec) { - return 'mark' in spec; - } - - // TODO: replace string with Mark - - class CompositeMarkNormalizer { - constructor(name, run) { - this.name = name; - this.run = run; - } - hasMatchingType(spec) { - if (isUnitSpec(spec)) { - return getMarkType(spec.mark) === this.name; - } - return false; - } - } - - function channelHasField(encoding, channel) { - const channelDef = encoding && encoding[channel]; - if (channelDef) { - if (vega.isArray(channelDef)) { - return some(channelDef, fieldDef => !!fieldDef.field); - } else { - return isFieldDef(channelDef) || hasConditionalFieldDef(channelDef); - } - } - return false; - } - function channelHasFieldOrDatum(encoding, channel) { - const channelDef = encoding && encoding[channel]; - if (channelDef) { - if (vega.isArray(channelDef)) { - return some(channelDef, fieldDef => !!fieldDef.field); - } else { - return isFieldDef(channelDef) || isDatumDef(channelDef) || hasConditionalFieldOrDatumDef(channelDef); - } - } - return false; - } - function channelHasNestedOffsetScale(encoding, channel) { - if (isXorY(channel)) { - const fieldDef = encoding[channel]; - if ((isFieldDef(fieldDef) || isDatumDef(fieldDef)) && (isDiscrete$1(fieldDef.type) || isFieldDef(fieldDef) && fieldDef.timeUnit)) { - const offsetChannel = getOffsetScaleChannel(channel); - return channelHasFieldOrDatum(encoding, offsetChannel); - } - } - return false; - } - function isAggregate$1(encoding) { - return some(CHANNELS, channel => { - if (channelHasField(encoding, channel)) { - const channelDef = encoding[channel]; - if (vega.isArray(channelDef)) { - return some(channelDef, fieldDef => !!fieldDef.aggregate); - } else { - const fieldDef = getFieldDef(channelDef); - return fieldDef && !!fieldDef.aggregate; - } - } - return false; - }); - } - function extractTransformsFromEncoding(oldEncoding, config) { - const groupby = []; - const bins = []; - const timeUnits = []; - const aggregate = []; - const encoding = {}; - forEach(oldEncoding, (channelDef, channel) => { - // Extract potential embedded transformations along with remaining properties - if (isFieldDef(channelDef)) { - const { - field, - aggregate: aggOp, - bin, - timeUnit, - ...remaining - } = channelDef; - if (aggOp || timeUnit || bin) { - const guide = getGuide(channelDef); - const isTitleDefined = guide?.title; - let newField = vgField(channelDef, { - forAs: true - }); - const newFieldDef = { - // Only add title if it doesn't exist - ...(isTitleDefined ? [] : { - title: title(channelDef, config, { - allowDisabling: true - }) - }), - ...remaining, - // Always overwrite field - field: newField - }; - if (aggOp) { - let op; - if (isArgmaxDef(aggOp)) { - op = 'argmax'; - newField = vgField({ - op: 'argmax', - field: aggOp.argmax - }, { - forAs: true - }); - newFieldDef.field = `${newField}.${field}`; - } else if (isArgminDef(aggOp)) { - op = 'argmin'; - newField = vgField({ - op: 'argmin', - field: aggOp.argmin - }, { - forAs: true - }); - newFieldDef.field = `${newField}.${field}`; - } else if (aggOp !== 'boxplot' && aggOp !== 'errorbar' && aggOp !== 'errorband') { - op = aggOp; - } - if (op) { - const aggregateEntry = { - op, - as: newField - }; - if (field) { - aggregateEntry.field = field; - } - aggregate.push(aggregateEntry); - } - } else { - groupby.push(newField); - if (isTypedFieldDef(channelDef) && isBinning(bin)) { - bins.push({ - bin, - field, - as: newField - }); - // Add additional groupbys for range and end of bins - groupby.push(vgField(channelDef, { - binSuffix: 'end' - })); - if (binRequiresRange(channelDef, channel)) { - groupby.push(vgField(channelDef, { - binSuffix: 'range' - })); - } - // Create accompanying 'x2' or 'y2' field if channel is 'x' or 'y' respectively - if (isXorY(channel)) { - const secondaryChannel = { - field: `${newField}_end` - }; - encoding[`${channel}2`] = secondaryChannel; - } - newFieldDef.bin = 'binned'; - if (!isSecondaryRangeChannel(channel)) { - newFieldDef['type'] = QUANTITATIVE; - } - } else if (timeUnit && !isBinnedTimeUnit(timeUnit)) { - timeUnits.push({ - timeUnit, - field, - as: newField - }); - - // define the format type for later compilation - const formatType = isTypedFieldDef(channelDef) && channelDef.type !== TEMPORAL && 'time'; - if (formatType) { - if (channel === TEXT$1 || channel === TOOLTIP) { - newFieldDef['formatType'] = formatType; - } else if (isNonPositionScaleChannel(channel)) { - newFieldDef['legend'] = { - formatType, - ...newFieldDef['legend'] - }; - } else if (isXorY(channel)) { - newFieldDef['axis'] = { - formatType, - ...newFieldDef['axis'] - }; - } - } - } - } - - // now the field should refer to post-transformed field instead - encoding[channel] = newFieldDef; - } else { - groupby.push(field); - encoding[channel] = oldEncoding[channel]; - } - } else { - // For value def / signal ref / datum def, just copy - encoding[channel] = oldEncoding[channel]; - } - }); - return { - bins, - timeUnits, - aggregate, - groupby, - encoding - }; - } - function markChannelCompatible(encoding, channel, mark) { - const markSupported = supportMark(channel, mark); - if (!markSupported) { - return false; - } else if (markSupported === 'binned') { - const primaryFieldDef = encoding[channel === X2 ? X : Y]; - - // circle, point, square and tick only support x2/y2 when their corresponding x/y fieldDef - // has "binned" data and thus need x2/y2 to specify the bin-end field. - if (isFieldDef(primaryFieldDef) && isFieldDef(encoding[channel]) && isBinned(primaryFieldDef.bin)) { - return true; - } else { - return false; - } - } - return true; - } - function initEncoding(encoding, mark, filled, config) { - const normalizedEncoding = {}; - for (const key of keys(encoding)) { - if (!isChannel(key)) { - // Drop invalid channel - warn(invalidEncodingChannel(key)); - } - } - for (let channel of UNIT_CHANNELS) { - if (!encoding[channel]) { - continue; - } - const channelDef = encoding[channel]; - if (isXorYOffset(channel)) { - const mainChannel = getMainChannelFromOffsetChannel(channel); - const positionDef = normalizedEncoding[mainChannel]; - if (isFieldDef(positionDef)) { - if (isContinuous(positionDef.type)) { - if (isFieldDef(channelDef) && !positionDef.timeUnit) { - // TODO: nesting continuous field instead continuous field should - // behave like offsetting the data in data domain - warn(offsetNestedInsideContinuousPositionScaleDropped(mainChannel)); - continue; - } - } - } - } - if (channel === 'angle' && mark === 'arc' && !encoding.theta) { - warn(REPLACE_ANGLE_WITH_THETA); - channel = THETA; - } - if (!markChannelCompatible(encoding, channel, mark)) { - // Drop unsupported channel - warn(incompatibleChannel(channel, mark)); - continue; - } - - // Drop line's size if the field is aggregated. - if (channel === SIZE && mark === 'line') { - const fieldDef = getFieldDef(encoding[channel]); - if (fieldDef?.aggregate) { - warn(LINE_WITH_VARYING_SIZE); - continue; - } - } - // Drop color if either fill or stroke is specified - - if (channel === COLOR && (filled ? 'fill' in encoding : 'stroke' in encoding)) { - warn(droppingColor('encoding', { - fill: 'fill' in encoding, - stroke: 'stroke' in encoding - })); - continue; - } - if (channel === DETAIL || channel === ORDER && !vega.isArray(channelDef) && !isValueDef(channelDef) || channel === TOOLTIP && vega.isArray(channelDef)) { - if (channelDef) { - if (channel === ORDER) { - const def = encoding[channel]; - if (isOrderOnlyDef(def)) { - normalizedEncoding[channel] = def; - continue; - } - } - // Array of fieldDefs for detail channel (or production rule) - normalizedEncoding[channel] = vega.array(channelDef).reduce((defs, fieldDef) => { - if (!isFieldDef(fieldDef)) { - warn(emptyFieldDef(fieldDef, channel)); - } else { - defs.push(initFieldDef(fieldDef, channel)); - } - return defs; - }, []); - } - } else { - if (channel === TOOLTIP && channelDef === null) { - // Preserve null so we can use it to disable tooltip - normalizedEncoding[channel] = null; - } else if (!isFieldDef(channelDef) && !isDatumDef(channelDef) && !isValueDef(channelDef) && !isConditionalDef(channelDef) && !isSignalRef(channelDef)) { - warn(emptyFieldDef(channelDef, channel)); - continue; - } - normalizedEncoding[channel] = initChannelDef(channelDef, channel, config); - } - } - return normalizedEncoding; - } - - /** - * For composite marks, we have to call initChannelDef during init so we can infer types earlier. - */ - function normalizeEncoding(encoding, config) { - const normalizedEncoding = {}; - for (const channel of keys(encoding)) { - const newChannelDef = initChannelDef(encoding[channel], channel, config, { - compositeMark: true - }); - normalizedEncoding[channel] = newChannelDef; - } - return normalizedEncoding; - } - function fieldDefs(encoding) { - const arr = []; - for (const channel of keys(encoding)) { - if (channelHasField(encoding, channel)) { - const channelDef = encoding[channel]; - const channelDefArray = vega.array(channelDef); - for (const def of channelDefArray) { - if (isFieldDef(def)) { - arr.push(def); - } else if (hasConditionalFieldDef(def)) { - arr.push(def.condition); - } - } - } - } - return arr; - } - function forEach(mapping, f, thisArg) { - if (!mapping) { - return; - } - for (const channel of keys(mapping)) { - const el = mapping[channel]; - if (vega.isArray(el)) { - for (const channelDef of el) { - f.call(thisArg, channelDef, channel); - } - } else { - f.call(thisArg, el, channel); - } - } - } - function reduce(mapping, f, init, thisArg) { - if (!mapping) { - return init; - } - return keys(mapping).reduce((r, channel) => { - const map = mapping[channel]; - if (vega.isArray(map)) { - return map.reduce((r1, channelDef) => { - return f.call(thisArg, r1, channelDef, channel); - }, r); - } else { - return f.call(thisArg, r, map, channel); - } - }, init); - } - - /** - * Returns list of path grouping fields for the given encoding - */ - function pathGroupingFields(mark, encoding) { - return keys(encoding).reduce((details, channel) => { - switch (channel) { - // x, y, x2, y2, lat, long, lat1, long2, order, tooltip, href, aria label, cursor should not cause lines to group - case X: - case Y: - case HREF: - case DESCRIPTION: - case URL: - case X2: - case Y2: - case XOFFSET: - case YOFFSET: - case THETA: - case THETA2: - case RADIUS: - case RADIUS2: - // falls through - - case LATITUDE: - case LONGITUDE: - case LATITUDE2: - case LONGITUDE2: - // TODO: case 'cursor': - - // text, shape, shouldn't be a part of line/trail/area [falls through] - case TEXT$1: - case SHAPE: - case ANGLE: - // falls through - - // tooltip fields should not be added to group by [falls through] - case TOOLTIP: - return details; - case ORDER: - // order should not group line / trail - if (mark === 'line' || mark === 'trail') { - return details; - } - // but order should group area for stacking (falls through) - - case DETAIL: - case KEY: - { - const channelDef = encoding[channel]; - if (vega.isArray(channelDef) || isFieldDef(channelDef)) { - for (const fieldDef of vega.array(channelDef)) { - if (!fieldDef.aggregate) { - details.push(vgField(fieldDef, {})); - } - } - } - return details; - } - case SIZE: - if (mark === 'trail') { - // For trail, size should not group trail lines. - return details; - } - // For line, size should group lines. - - // falls through - case COLOR: - case FILL: - case STROKE: - case OPACITY: - case FILLOPACITY: - case STROKEOPACITY: - case STROKEDASH: - case STROKEWIDTH: - { - // TODO strokeDashOffset: - // falls through - - const fieldDef = getFieldDef(encoding[channel]); - if (fieldDef && !fieldDef.aggregate) { - details.push(vgField(fieldDef, {})); - } - return details; - } - } - }, []); - } - - // Parts mixins can be any mark type. We could make a more specific type for each part. - - function filterTooltipWithAggregatedField(oldEncoding) { - const { - tooltip, - ...filteredEncoding - } = oldEncoding; - if (!tooltip) { - return { - filteredEncoding - }; - } - let customTooltipWithAggregatedField; - let customTooltipWithoutAggregatedField; - if (vega.isArray(tooltip)) { - for (const t of tooltip) { - if (t.aggregate) { - if (!customTooltipWithAggregatedField) { - customTooltipWithAggregatedField = []; - } - customTooltipWithAggregatedField.push(t); - } else { - if (!customTooltipWithoutAggregatedField) { - customTooltipWithoutAggregatedField = []; - } - customTooltipWithoutAggregatedField.push(t); - } - } - if (customTooltipWithAggregatedField) { - filteredEncoding.tooltip = customTooltipWithAggregatedField; - } - } else { - if (tooltip['aggregate']) { - filteredEncoding.tooltip = tooltip; - } else { - customTooltipWithoutAggregatedField = tooltip; - } - } - if (vega.isArray(customTooltipWithoutAggregatedField) && customTooltipWithoutAggregatedField.length === 1) { - customTooltipWithoutAggregatedField = customTooltipWithoutAggregatedField[0]; - } - return { - customTooltipWithoutAggregatedField, - filteredEncoding - }; - } - function getCompositeMarkTooltip(tooltipSummary, continuousAxisChannelDef, encodingWithoutContinuousAxis) { - let withFieldName = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; - if ('tooltip' in encodingWithoutContinuousAxis) { - return { - tooltip: encodingWithoutContinuousAxis.tooltip - }; - } - const fiveSummaryTooltip = tooltipSummary.map(_ref => { - let { - fieldPrefix, - titlePrefix - } = _ref; - const mainTitle = withFieldName ? ` of ${getTitle(continuousAxisChannelDef)}` : ''; - return { - field: fieldPrefix + continuousAxisChannelDef.field, - type: continuousAxisChannelDef.type, - title: isSignalRef(titlePrefix) ? { - signal: `${titlePrefix}"${escape(mainTitle)}"` - } : titlePrefix + mainTitle - }; - }); - const tooltipFieldDefs = fieldDefs(encodingWithoutContinuousAxis).map(toStringFieldDef); - return { - tooltip: [...fiveSummaryTooltip, - // need to cast because TextFieldDef supports fewer types of bin - ...unique(tooltipFieldDefs, hash)] - }; - } - function getTitle(continuousAxisChannelDef) { - const { - title, - field - } = continuousAxisChannelDef; - return getFirstDefined(title, field); - } - function makeCompositeAggregatePartFactory(compositeMarkDef, continuousAxis, continuousAxisChannelDef, sharedEncoding, compositeMarkConfig) { - const { - scale, - axis - } = continuousAxisChannelDef; - return _ref2 => { - let { - partName, - mark, - positionPrefix, - endPositionPrefix = undefined, - extraEncoding = {} - } = _ref2; - const title = getTitle(continuousAxisChannelDef); - return partLayerMixins(compositeMarkDef, partName, compositeMarkConfig, { - mark, - // TODO better remove this method and just have mark as a parameter of the method - encoding: { - [continuousAxis]: { - field: `${positionPrefix}_${continuousAxisChannelDef.field}`, - type: continuousAxisChannelDef.type, - ...(title !== undefined ? { - title - } : {}), - ...(scale !== undefined ? { - scale - } : {}), - ...(axis !== undefined ? { - axis - } : {}) - }, - ...(vega.isString(endPositionPrefix) ? { - [`${continuousAxis}2`]: { - field: `${endPositionPrefix}_${continuousAxisChannelDef.field}` - } - } : {}), - ...sharedEncoding, - ...extraEncoding - } - }); - }; - } - function partLayerMixins(markDef, part, compositeMarkConfig, partBaseSpec) { - const { - clip, - color, - opacity - } = markDef; - const mark = markDef.type; - if (markDef[part] || markDef[part] === undefined && compositeMarkConfig[part]) { - return [{ - ...partBaseSpec, - mark: { - ...compositeMarkConfig[part], - ...(clip ? { - clip - } : {}), - ...(color ? { - color - } : {}), - ...(opacity ? { - opacity - } : {}), - ...(isMarkDef(partBaseSpec.mark) ? partBaseSpec.mark : { - type: partBaseSpec.mark - }), - style: `${mark}-${String(part)}`, - ...(vega.isBoolean(markDef[part]) ? {} : markDef[part]) - } - }]; - } - return []; - } - function compositeMarkContinuousAxis(spec, orient, compositeMark) { - const { - encoding - } = spec; - const continuousAxis = orient === 'vertical' ? 'y' : 'x'; - const continuousAxisChannelDef = encoding[continuousAxis]; // Safe to cast because if x is not continuous fielddef, the orient would not be horizontal. - const continuousAxisChannelDef2 = encoding[`${continuousAxis}2`]; - const continuousAxisChannelDefError = encoding[`${continuousAxis}Error`]; - const continuousAxisChannelDefError2 = encoding[`${continuousAxis}Error2`]; - return { - continuousAxisChannelDef: filterAggregateFromChannelDef(continuousAxisChannelDef, compositeMark), - continuousAxisChannelDef2: filterAggregateFromChannelDef(continuousAxisChannelDef2, compositeMark), - continuousAxisChannelDefError: filterAggregateFromChannelDef(continuousAxisChannelDefError, compositeMark), - continuousAxisChannelDefError2: filterAggregateFromChannelDef(continuousAxisChannelDefError2, compositeMark), - continuousAxis - }; - } - function filterAggregateFromChannelDef(continuousAxisChannelDef, compositeMark) { - if (continuousAxisChannelDef?.aggregate) { - const { - aggregate, - ...continuousAxisWithoutAggregate - } = continuousAxisChannelDef; - if (aggregate !== compositeMark) { - warn(errorBarContinuousAxisHasCustomizedAggregate(aggregate, compositeMark)); - } - return continuousAxisWithoutAggregate; - } else { - return continuousAxisChannelDef; - } - } - function compositeMarkOrient(spec, compositeMark) { - const { - mark, - encoding - } = spec; - const { - x, - y - } = encoding; - if (isMarkDef(mark) && mark.orient) { - return mark.orient; - } - if (isContinuousFieldOrDatumDef(x)) { - // x is continuous - if (isContinuousFieldOrDatumDef(y)) { - // both x and y are continuous - const xAggregate = isFieldDef(x) && x.aggregate; - const yAggregate = isFieldDef(y) && y.aggregate; - if (!xAggregate && yAggregate === compositeMark) { - return 'vertical'; - } else if (!yAggregate && xAggregate === compositeMark) { - return 'horizontal'; - } else if (xAggregate === compositeMark && yAggregate === compositeMark) { - throw new Error('Both x and y cannot have aggregate'); - } else { - if (isFieldOrDatumDefForTimeFormat(y) && !isFieldOrDatumDefForTimeFormat(x)) { - // y is temporal but x is not - return 'horizontal'; - } - - // default orientation for two continuous - return 'vertical'; - } - } - return 'horizontal'; - } else if (isContinuousFieldOrDatumDef(y)) { - // y is continuous but x is not - return 'vertical'; - } else { - // Neither x nor y is continuous. - throw new Error(`Need a valid continuous axis for ${compositeMark}s`); - } - } - - const BOXPLOT = 'boxplot'; - const BOXPLOT_PARTS = ['box', 'median', 'outliers', 'rule', 'ticks']; - const boxPlotNormalizer = new CompositeMarkNormalizer(BOXPLOT, normalizeBoxPlot); - function getBoxPlotType(extent) { - if (vega.isNumber(extent)) { - return 'tukey'; - } - // Ham: If we ever want to, we could add another extent syntax `{kIQR: number}` for the original [Q1-k*IQR, Q3+k*IQR] whisker and call this boxPlotType = `kIQR`. However, I'm not exposing this for now. - return extent; - } - function normalizeBoxPlot(spec, _ref) { - let { - config - } = _ref; - // Need to initEncoding first so we can infer type - spec = { - ...spec, - encoding: normalizeEncoding(spec.encoding, config) - }; - const { - mark, - encoding: _encoding, - params, - projection: _p, - ...outerSpec - } = spec; - const markDef = isMarkDef(mark) ? mark : { - type: mark - }; - - // TODO(https://github.com/vega/vega-lite/issues/3702): add selection support - if (params) { - warn(selectionNotSupported('boxplot')); - } - const extent = markDef.extent ?? config.boxplot.extent; - const sizeValue = getMarkPropOrConfig('size', markDef, - // TODO: https://github.com/vega/vega-lite/issues/6245 - config); - const invalid = markDef.invalid; - const boxPlotType = getBoxPlotType(extent); - const { - bins, - timeUnits, - transform, - continuousAxisChannelDef, - continuousAxis, - groupby, - aggregate, - encodingWithoutContinuousAxis, - ticksOrient, - boxOrient, - customTooltipWithoutAggregatedField - } = boxParams(spec, extent, config); - const aliasedFieldName = removePathFromField(continuousAxisChannelDef.field); - const { - color, - size, - ...encodingWithoutSizeColorAndContinuousAxis - } = encodingWithoutContinuousAxis; - const makeBoxPlotPart = sharedEncoding => { - return makeCompositeAggregatePartFactory(markDef, continuousAxis, continuousAxisChannelDef, sharedEncoding, config.boxplot); - }; - const makeBoxPlotExtent = makeBoxPlotPart(encodingWithoutSizeColorAndContinuousAxis); - const makeBoxPlotBox = makeBoxPlotPart(encodingWithoutContinuousAxis); - const defaultBoxColor = (vega.isObject(config.boxplot.box) ? config.boxplot.box.color : config.mark.color) || '#4c78a8'; - const makeBoxPlotMidTick = makeBoxPlotPart({ - ...encodingWithoutSizeColorAndContinuousAxis, - ...(size ? { - size - } : {}), - color: { - condition: { - test: `datum['lower_box_${continuousAxisChannelDef.field}'] >= datum['upper_box_${continuousAxisChannelDef.field}']`, - ...(color || { - value: defaultBoxColor - }) - } - } - }); - const fiveSummaryTooltipEncoding = getCompositeMarkTooltip([{ - fieldPrefix: boxPlotType === 'min-max' ? 'upper_whisker_' : 'max_', - titlePrefix: 'Max' - }, { - fieldPrefix: 'upper_box_', - titlePrefix: 'Q3' - }, { - fieldPrefix: 'mid_box_', - titlePrefix: 'Median' - }, { - fieldPrefix: 'lower_box_', - titlePrefix: 'Q1' - }, { - fieldPrefix: boxPlotType === 'min-max' ? 'lower_whisker_' : 'min_', - titlePrefix: 'Min' - }], continuousAxisChannelDef, encodingWithoutContinuousAxis); - - // ## Whisker Layers - - const endTick = { - type: 'tick', - color: 'black', - opacity: 1, - orient: ticksOrient, - invalid, - aria: false - }; - const whiskerTooltipEncoding = boxPlotType === 'min-max' ? fiveSummaryTooltipEncoding // for min-max, show five-summary tooltip for whisker - : - // for tukey / k-IQR, just show upper/lower-whisker - getCompositeMarkTooltip([{ - fieldPrefix: 'upper_whisker_', - titlePrefix: 'Upper Whisker' - }, { - fieldPrefix: 'lower_whisker_', - titlePrefix: 'Lower Whisker' - }], continuousAxisChannelDef, encodingWithoutContinuousAxis); - const whiskerLayers = [...makeBoxPlotExtent({ - partName: 'rule', - mark: { - type: 'rule', - invalid, - aria: false - }, - positionPrefix: 'lower_whisker', - endPositionPrefix: 'lower_box', - extraEncoding: whiskerTooltipEncoding - }), ...makeBoxPlotExtent({ - partName: 'rule', - mark: { - type: 'rule', - invalid, - aria: false - }, - positionPrefix: 'upper_box', - endPositionPrefix: 'upper_whisker', - extraEncoding: whiskerTooltipEncoding - }), ...makeBoxPlotExtent({ - partName: 'ticks', - mark: endTick, - positionPrefix: 'lower_whisker', - extraEncoding: whiskerTooltipEncoding - }), ...makeBoxPlotExtent({ - partName: 'ticks', - mark: endTick, - positionPrefix: 'upper_whisker', - extraEncoding: whiskerTooltipEncoding - })]; - - // ## Box Layers - - // TODO: support hiding certain mark parts - const boxLayers = [...(boxPlotType !== 'tukey' ? whiskerLayers : []), ...makeBoxPlotBox({ - partName: 'box', - mark: { - type: 'bar', - ...(sizeValue ? { - size: sizeValue - } : {}), - orient: boxOrient, - invalid, - ariaRoleDescription: 'box' - }, - positionPrefix: 'lower_box', - endPositionPrefix: 'upper_box', - extraEncoding: fiveSummaryTooltipEncoding - }), ...makeBoxPlotMidTick({ - partName: 'median', - mark: { - type: 'tick', - invalid, - ...(vega.isObject(config.boxplot.median) && config.boxplot.median.color ? { - color: config.boxplot.median.color - } : {}), - ...(sizeValue ? { - size: sizeValue - } : {}), - orient: ticksOrient, - aria: false - }, - positionPrefix: 'mid_box', - extraEncoding: fiveSummaryTooltipEncoding - })]; - if (boxPlotType === 'min-max') { - return { - ...outerSpec, - transform: (outerSpec.transform ?? []).concat(transform), - layer: boxLayers - }; - } - - // Tukey Box Plot - - const lowerBoxExpr = `datum["lower_box_${continuousAxisChannelDef.field}"]`; - const upperBoxExpr = `datum["upper_box_${continuousAxisChannelDef.field}"]`; - const iqrExpr = `(${upperBoxExpr} - ${lowerBoxExpr})`; - const lowerWhiskerExpr = `${lowerBoxExpr} - ${extent} * ${iqrExpr}`; - const upperWhiskerExpr = `${upperBoxExpr} + ${extent} * ${iqrExpr}`; - const fieldExpr = `datum["${continuousAxisChannelDef.field}"]`; - const joinaggregateTransform = { - joinaggregate: boxParamsQuartiles(continuousAxisChannelDef.field), - groupby - }; - const filteredWhiskerSpec = { - transform: [{ - filter: `(${lowerWhiskerExpr} <= ${fieldExpr}) && (${fieldExpr} <= ${upperWhiskerExpr})` - }, { - aggregate: [{ - op: 'min', - field: continuousAxisChannelDef.field, - as: `lower_whisker_${aliasedFieldName}` - }, { - op: 'max', - field: continuousAxisChannelDef.field, - as: `upper_whisker_${aliasedFieldName}` - }, - // preserve lower_box / upper_box - { - op: 'min', - field: `lower_box_${continuousAxisChannelDef.field}`, - as: `lower_box_${aliasedFieldName}` - }, { - op: 'max', - field: `upper_box_${continuousAxisChannelDef.field}`, - as: `upper_box_${aliasedFieldName}` - }, ...aggregate], - groupby - }], - layer: whiskerLayers - }; - const { - tooltip, - ...encodingWithoutSizeColorContinuousAxisAndTooltip - } = encodingWithoutSizeColorAndContinuousAxis; - const { - scale, - axis - } = continuousAxisChannelDef; - const title = getTitle(continuousAxisChannelDef); - const axisWithoutTitle = omit(axis, ['title']); - const outlierLayersMixins = partLayerMixins(markDef, 'outliers', config.boxplot, { - transform: [{ - filter: `(${fieldExpr} < ${lowerWhiskerExpr}) || (${fieldExpr} > ${upperWhiskerExpr})` - }], - mark: 'point', - encoding: { - [continuousAxis]: { - field: continuousAxisChannelDef.field, - type: continuousAxisChannelDef.type, - ...(title !== undefined ? { - title - } : {}), - ...(scale !== undefined ? { - scale - } : {}), - // add axis without title since we already added the title above - ...(isEmpty(axisWithoutTitle) ? {} : { - axis: axisWithoutTitle - }) - }, - ...encodingWithoutSizeColorContinuousAxisAndTooltip, - ...(color ? { - color - } : {}), - ...(customTooltipWithoutAggregatedField ? { - tooltip: customTooltipWithoutAggregatedField - } : {}) - } - })[0]; - let filteredLayersMixins; - const filteredLayersMixinsTransforms = [...bins, ...timeUnits, joinaggregateTransform]; - if (outlierLayersMixins) { - filteredLayersMixins = { - transform: filteredLayersMixinsTransforms, - layer: [outlierLayersMixins, filteredWhiskerSpec] - }; - } else { - filteredLayersMixins = filteredWhiskerSpec; - filteredLayersMixins.transform.unshift(...filteredLayersMixinsTransforms); - } - return { - ...outerSpec, - layer: [filteredLayersMixins, { - // boxplot - transform, - layer: boxLayers - }] - }; - } - function boxParamsQuartiles(continousAxisField) { - const aliasedFieldName = removePathFromField(continousAxisField); - return [{ - op: 'q1', - field: continousAxisField, - as: `lower_box_${aliasedFieldName}` - }, { - op: 'q3', - field: continousAxisField, - as: `upper_box_${aliasedFieldName}` - }]; - } - function boxParams(spec, extent, config) { - const orient = compositeMarkOrient(spec, BOXPLOT); - const { - continuousAxisChannelDef, - continuousAxis - } = compositeMarkContinuousAxis(spec, orient, BOXPLOT); - const continuousFieldName = continuousAxisChannelDef.field; - const aliasedFieldName = removePathFromField(continuousFieldName); - const boxPlotType = getBoxPlotType(extent); - const boxplotSpecificAggregate = [...boxParamsQuartiles(continuousFieldName), { - op: 'median', - field: continuousFieldName, - as: `mid_box_${aliasedFieldName}` - }, { - op: 'min', - field: continuousFieldName, - as: (boxPlotType === 'min-max' ? 'lower_whisker_' : 'min_') + aliasedFieldName - }, { - op: 'max', - field: continuousFieldName, - as: (boxPlotType === 'min-max' ? 'upper_whisker_' : 'max_') + aliasedFieldName - }]; - const postAggregateCalculates = boxPlotType === 'min-max' || boxPlotType === 'tukey' ? [] : [ - // This is for the original k-IQR, which we do not expose - { - calculate: `datum["upper_box_${aliasedFieldName}"] - datum["lower_box_${aliasedFieldName}"]`, - as: `iqr_${aliasedFieldName}` - }, { - calculate: `min(datum["upper_box_${aliasedFieldName}"] + datum["iqr_${aliasedFieldName}"] * ${extent}, datum["max_${aliasedFieldName}"])`, - as: `upper_whisker_${aliasedFieldName}` - }, { - calculate: `max(datum["lower_box_${aliasedFieldName}"] - datum["iqr_${aliasedFieldName}"] * ${extent}, datum["min_${aliasedFieldName}"])`, - as: `lower_whisker_${aliasedFieldName}` - }]; - const { - [continuousAxis]: oldContinuousAxisChannelDef, - ...oldEncodingWithoutContinuousAxis - } = spec.encoding; - const { - customTooltipWithoutAggregatedField, - filteredEncoding - } = filterTooltipWithAggregatedField(oldEncodingWithoutContinuousAxis); - const { - bins, - timeUnits, - aggregate, - groupby, - encoding: encodingWithoutContinuousAxis - } = extractTransformsFromEncoding(filteredEncoding, config); - const ticksOrient = orient === 'vertical' ? 'horizontal' : 'vertical'; - const boxOrient = orient; - const transform = [...bins, ...timeUnits, { - aggregate: [...aggregate, ...boxplotSpecificAggregate], - groupby - }, ...postAggregateCalculates]; - return { - bins, - timeUnits, - transform, - groupby, - aggregate, - continuousAxisChannelDef, - continuousAxis, - encodingWithoutContinuousAxis, - ticksOrient, - boxOrient, - customTooltipWithoutAggregatedField - }; - } - - const ERRORBAR = 'errorbar'; - const ERRORBAR_PARTS = ['ticks', 'rule']; - const errorBarNormalizer = new CompositeMarkNormalizer(ERRORBAR, normalizeErrorBar); - function normalizeErrorBar(spec, _ref) { - let { - config - } = _ref; - // Need to initEncoding first so we can infer type - spec = { - ...spec, - encoding: normalizeEncoding(spec.encoding, config) - }; - const { - transform, - continuousAxisChannelDef, - continuousAxis, - encodingWithoutContinuousAxis, - ticksOrient, - markDef, - outerSpec, - tooltipEncoding - } = errorBarParams(spec, ERRORBAR, config); - delete encodingWithoutContinuousAxis['size']; - const makeErrorBarPart = makeCompositeAggregatePartFactory(markDef, continuousAxis, continuousAxisChannelDef, encodingWithoutContinuousAxis, config.errorbar); - const thickness = markDef.thickness; - const size = markDef.size; - const tick = { - type: 'tick', - orient: ticksOrient, - aria: false, - ...(thickness !== undefined ? { - thickness - } : {}), - ...(size !== undefined ? { - size - } : {}) - }; - const layer = [...makeErrorBarPart({ - partName: 'ticks', - mark: tick, - positionPrefix: 'lower', - extraEncoding: tooltipEncoding - }), ...makeErrorBarPart({ - partName: 'ticks', - mark: tick, - positionPrefix: 'upper', - extraEncoding: tooltipEncoding - }), ...makeErrorBarPart({ - partName: 'rule', - mark: { - type: 'rule', - ariaRoleDescription: 'errorbar', - ...(thickness !== undefined ? { - size: thickness - } : {}) - }, - positionPrefix: 'lower', - endPositionPrefix: 'upper', - extraEncoding: tooltipEncoding - })]; - return { - ...outerSpec, - transform, - ...(layer.length > 1 ? { - layer - } : { - ...layer[0] - }) - }; - } - function errorBarOrientAndInputType(spec, compositeMark) { - const { - encoding - } = spec; - if (errorBarIsInputTypeRaw(encoding)) { - return { - orient: compositeMarkOrient(spec, compositeMark), - inputType: 'raw' - }; - } - const isTypeAggregatedUpperLower = errorBarIsInputTypeAggregatedUpperLower(encoding); - const isTypeAggregatedError = errorBarIsInputTypeAggregatedError(encoding); - const x = encoding.x; - const y = encoding.y; - if (isTypeAggregatedUpperLower) { - // type is aggregated-upper-lower - - if (isTypeAggregatedError) { - throw new Error(`${compositeMark} cannot be both type aggregated-upper-lower and aggregated-error`); - } - const x2 = encoding.x2; - const y2 = encoding.y2; - if (isFieldOrDatumDef(x2) && isFieldOrDatumDef(y2)) { - // having both x, x2 and y, y2 - throw new Error(`${compositeMark} cannot have both x2 and y2`); - } else if (isFieldOrDatumDef(x2)) { - if (isContinuousFieldOrDatumDef(x)) { - // having x, x2 quantitative and field y, y2 are not specified - return { - orient: 'horizontal', - inputType: 'aggregated-upper-lower' - }; - } else { - // having x, x2 that are not both quantitative - throw new Error(`Both x and x2 have to be quantitative in ${compositeMark}`); - } - } else if (isFieldOrDatumDef(y2)) { - // y2 is a FieldDef - if (isContinuousFieldOrDatumDef(y)) { - // having y, y2 quantitative and field x, x2 are not specified - return { - orient: 'vertical', - inputType: 'aggregated-upper-lower' - }; - } else { - // having y, y2 that are not both quantitative - throw new Error(`Both y and y2 have to be quantitative in ${compositeMark}`); - } - } - throw new Error('No ranged axis'); - } else { - // type is aggregated-error - - const xError = encoding.xError; - const xError2 = encoding.xError2; - const yError = encoding.yError; - const yError2 = encoding.yError2; - if (isFieldOrDatumDef(xError2) && !isFieldOrDatumDef(xError)) { - // having xError2 without xError - throw new Error(`${compositeMark} cannot have xError2 without xError`); - } - if (isFieldOrDatumDef(yError2) && !isFieldOrDatumDef(yError)) { - // having yError2 without yError - throw new Error(`${compositeMark} cannot have yError2 without yError`); - } - if (isFieldOrDatumDef(xError) && isFieldOrDatumDef(yError)) { - // having both xError and yError - throw new Error(`${compositeMark} cannot have both xError and yError with both are quantiative`); - } else if (isFieldOrDatumDef(xError)) { - if (isContinuousFieldOrDatumDef(x)) { - // having x and xError that are all quantitative - return { - orient: 'horizontal', - inputType: 'aggregated-error' - }; - } else { - // having x, xError, and xError2 that are not all quantitative - throw new Error('All x, xError, and xError2 (if exist) have to be quantitative'); - } - } else if (isFieldOrDatumDef(yError)) { - if (isContinuousFieldOrDatumDef(y)) { - // having y and yError that are all quantitative - return { - orient: 'vertical', - inputType: 'aggregated-error' - }; - } else { - // having y, yError, and yError2 that are not all quantitative - throw new Error('All y, yError, and yError2 (if exist) have to be quantitative'); - } - } - throw new Error('No ranged axis'); - } - } - function errorBarIsInputTypeRaw(encoding) { - return (isFieldOrDatumDef(encoding.x) || isFieldOrDatumDef(encoding.y)) && !isFieldOrDatumDef(encoding.x2) && !isFieldOrDatumDef(encoding.y2) && !isFieldOrDatumDef(encoding.xError) && !isFieldOrDatumDef(encoding.xError2) && !isFieldOrDatumDef(encoding.yError) && !isFieldOrDatumDef(encoding.yError2); - } - function errorBarIsInputTypeAggregatedUpperLower(encoding) { - return isFieldOrDatumDef(encoding.x2) || isFieldOrDatumDef(encoding.y2); - } - function errorBarIsInputTypeAggregatedError(encoding) { - return isFieldOrDatumDef(encoding.xError) || isFieldOrDatumDef(encoding.xError2) || isFieldOrDatumDef(encoding.yError) || isFieldOrDatumDef(encoding.yError2); - } - function errorBarParams(spec, compositeMark, config) { - // TODO: use selection - const { - mark, - encoding, - params, - projection: _p, - ...outerSpec - } = spec; - const markDef = isMarkDef(mark) ? mark : { - type: mark - }; - - // TODO(https://github.com/vega/vega-lite/issues/3702): add selection support - if (params) { - warn(selectionNotSupported(compositeMark)); - } - const { - orient, - inputType - } = errorBarOrientAndInputType(spec, compositeMark); - const { - continuousAxisChannelDef, - continuousAxisChannelDef2, - continuousAxisChannelDefError, - continuousAxisChannelDefError2, - continuousAxis - } = compositeMarkContinuousAxis(spec, orient, compositeMark); - const { - errorBarSpecificAggregate, - postAggregateCalculates, - tooltipSummary, - tooltipTitleWithFieldName - } = errorBarAggregationAndCalculation(markDef, continuousAxisChannelDef, continuousAxisChannelDef2, continuousAxisChannelDefError, continuousAxisChannelDefError2, inputType, compositeMark, config); - const { - [continuousAxis]: oldContinuousAxisChannelDef, - [continuousAxis === 'x' ? 'x2' : 'y2']: oldContinuousAxisChannelDef2, - [continuousAxis === 'x' ? 'xError' : 'yError']: oldContinuousAxisChannelDefError, - [continuousAxis === 'x' ? 'xError2' : 'yError2']: oldContinuousAxisChannelDefError2, - ...oldEncodingWithoutContinuousAxis - } = encoding; - const { - bins, - timeUnits, - aggregate: oldAggregate, - groupby: oldGroupBy, - encoding: encodingWithoutContinuousAxis - } = extractTransformsFromEncoding(oldEncodingWithoutContinuousAxis, config); - const aggregate = [...oldAggregate, ...errorBarSpecificAggregate]; - const groupby = inputType !== 'raw' ? [] : oldGroupBy; - const tooltipEncoding = getCompositeMarkTooltip(tooltipSummary, continuousAxisChannelDef, encodingWithoutContinuousAxis, tooltipTitleWithFieldName); - return { - transform: [...(outerSpec.transform ?? []), ...bins, ...timeUnits, ...(aggregate.length === 0 ? [] : [{ - aggregate, - groupby - }]), ...postAggregateCalculates], - groupby, - continuousAxisChannelDef, - continuousAxis, - encodingWithoutContinuousAxis, - ticksOrient: orient === 'vertical' ? 'horizontal' : 'vertical', - markDef, - outerSpec, - tooltipEncoding - }; - } - function errorBarAggregationAndCalculation(markDef, continuousAxisChannelDef, continuousAxisChannelDef2, continuousAxisChannelDefError, continuousAxisChannelDefError2, inputType, compositeMark, config) { - let errorBarSpecificAggregate = []; - let postAggregateCalculates = []; - const continuousFieldName = continuousAxisChannelDef.field; - let tooltipSummary; - let tooltipTitleWithFieldName = false; - if (inputType === 'raw') { - const center = markDef.center ? markDef.center : markDef.extent ? markDef.extent === 'iqr' ? 'median' : 'mean' : config.errorbar.center; - const extent = markDef.extent ? markDef.extent : center === 'mean' ? 'stderr' : 'iqr'; - if (center === 'median' !== (extent === 'iqr')) { - warn(errorBarCenterIsUsedWithWrongExtent(center, extent, compositeMark)); - } - if (extent === 'stderr' || extent === 'stdev') { - errorBarSpecificAggregate = [{ - op: extent, - field: continuousFieldName, - as: `extent_${continuousFieldName}` - }, { - op: center, - field: continuousFieldName, - as: `center_${continuousFieldName}` - }]; - postAggregateCalculates = [{ - calculate: `datum["center_${continuousFieldName}"] + datum["extent_${continuousFieldName}"]`, - as: `upper_${continuousFieldName}` - }, { - calculate: `datum["center_${continuousFieldName}"] - datum["extent_${continuousFieldName}"]`, - as: `lower_${continuousFieldName}` - }]; - tooltipSummary = [{ - fieldPrefix: 'center_', - titlePrefix: titleCase(center) - }, { - fieldPrefix: 'upper_', - titlePrefix: getTitlePrefix(center, extent, '+') - }, { - fieldPrefix: 'lower_', - titlePrefix: getTitlePrefix(center, extent, '-') - }]; - tooltipTitleWithFieldName = true; - } else { - let centerOp; - let lowerExtentOp; - let upperExtentOp; - if (extent === 'ci') { - centerOp = 'mean'; - lowerExtentOp = 'ci0'; - upperExtentOp = 'ci1'; - } else { - centerOp = 'median'; - lowerExtentOp = 'q1'; - upperExtentOp = 'q3'; - } - errorBarSpecificAggregate = [{ - op: lowerExtentOp, - field: continuousFieldName, - as: `lower_${continuousFieldName}` - }, { - op: upperExtentOp, - field: continuousFieldName, - as: `upper_${continuousFieldName}` - }, { - op: centerOp, - field: continuousFieldName, - as: `center_${continuousFieldName}` - }]; - tooltipSummary = [{ - fieldPrefix: 'upper_', - titlePrefix: title({ - field: continuousFieldName, - aggregate: upperExtentOp, - type: 'quantitative' - }, config, { - allowDisabling: false - }) - }, { - fieldPrefix: 'lower_', - titlePrefix: title({ - field: continuousFieldName, - aggregate: lowerExtentOp, - type: 'quantitative' - }, config, { - allowDisabling: false - }) - }, { - fieldPrefix: 'center_', - titlePrefix: title({ - field: continuousFieldName, - aggregate: centerOp, - type: 'quantitative' - }, config, { - allowDisabling: false - }) - }]; - } - } else { - if (markDef.center || markDef.extent) { - warn(errorBarCenterAndExtentAreNotNeeded(markDef.center, markDef.extent)); - } - if (inputType === 'aggregated-upper-lower') { - tooltipSummary = []; - postAggregateCalculates = [{ - calculate: `datum["${continuousAxisChannelDef2.field}"]`, - as: `upper_${continuousFieldName}` - }, { - calculate: `datum["${continuousFieldName}"]`, - as: `lower_${continuousFieldName}` - }]; - } else if (inputType === 'aggregated-error') { - tooltipSummary = [{ - fieldPrefix: '', - titlePrefix: continuousFieldName - }]; - postAggregateCalculates = [{ - calculate: `datum["${continuousFieldName}"] + datum["${continuousAxisChannelDefError.field}"]`, - as: `upper_${continuousFieldName}` - }]; - if (continuousAxisChannelDefError2) { - postAggregateCalculates.push({ - calculate: `datum["${continuousFieldName}"] + datum["${continuousAxisChannelDefError2.field}"]`, - as: `lower_${continuousFieldName}` - }); - } else { - postAggregateCalculates.push({ - calculate: `datum["${continuousFieldName}"] - datum["${continuousAxisChannelDefError.field}"]`, - as: `lower_${continuousFieldName}` - }); - } - } - for (const postAggregateCalculate of postAggregateCalculates) { - tooltipSummary.push({ - fieldPrefix: postAggregateCalculate.as.substring(0, 6), - titlePrefix: replaceAll(replaceAll(postAggregateCalculate.calculate, 'datum["', ''), '"]', '') - }); - } - } - return { - postAggregateCalculates, - errorBarSpecificAggregate, - tooltipSummary, - tooltipTitleWithFieldName - }; - } - function getTitlePrefix(center, extent, operation) { - return `${titleCase(center)} ${operation} ${extent}`; - } - - const ERRORBAND = 'errorband'; - const ERRORBAND_PARTS = ['band', 'borders']; - const errorBandNormalizer = new CompositeMarkNormalizer(ERRORBAND, normalizeErrorBand); - function normalizeErrorBand(spec, _ref) { - let { - config - } = _ref; - // Need to initEncoding first so we can infer type - spec = { - ...spec, - encoding: normalizeEncoding(spec.encoding, config) - }; - const { - transform, - continuousAxisChannelDef, - continuousAxis, - encodingWithoutContinuousAxis, - markDef, - outerSpec, - tooltipEncoding - } = errorBarParams(spec, ERRORBAND, config); - const errorBandDef = markDef; - const makeErrorBandPart = makeCompositeAggregatePartFactory(errorBandDef, continuousAxis, continuousAxisChannelDef, encodingWithoutContinuousAxis, config.errorband); - const is2D = spec.encoding.x !== undefined && spec.encoding.y !== undefined; - let bandMark = { - type: is2D ? 'area' : 'rect' - }; - let bordersMark = { - type: is2D ? 'line' : 'rule' - }; - const interpolate = { - ...(errorBandDef.interpolate ? { - interpolate: errorBandDef.interpolate - } : {}), - ...(errorBandDef.tension && errorBandDef.interpolate ? { - tension: errorBandDef.tension - } : {}) - }; - if (is2D) { - bandMark = { - ...bandMark, - ...interpolate, - ariaRoleDescription: 'errorband' - }; - bordersMark = { - ...bordersMark, - ...interpolate, - aria: false - }; - } else if (errorBandDef.interpolate) { - warn(errorBand1DNotSupport('interpolate')); - } else if (errorBandDef.tension) { - warn(errorBand1DNotSupport('tension')); - } - return { - ...outerSpec, - transform, - layer: [...makeErrorBandPart({ - partName: 'band', - mark: bandMark, - positionPrefix: 'lower', - endPositionPrefix: 'upper', - extraEncoding: tooltipEncoding - }), ...makeErrorBandPart({ - partName: 'borders', - mark: bordersMark, - positionPrefix: 'lower', - extraEncoding: tooltipEncoding - }), ...makeErrorBandPart({ - partName: 'borders', - mark: bordersMark, - positionPrefix: 'upper', - extraEncoding: tooltipEncoding - })] - }; - } - - /** - * Registry index for all composite mark's normalizer - */ - const compositeMarkRegistry = {}; - function add(mark, run, parts) { - const normalizer = new CompositeMarkNormalizer(mark, run); - compositeMarkRegistry[mark] = { - normalizer, - parts - }; - } - function getAllCompositeMarks() { - return keys(compositeMarkRegistry); - } - add(BOXPLOT, normalizeBoxPlot, BOXPLOT_PARTS); - add(ERRORBAR, normalizeErrorBar, ERRORBAR_PARTS); - add(ERRORBAND, normalizeErrorBand, ERRORBAND_PARTS); - - const VL_ONLY_LEGEND_CONFIG = ['gradientHorizontalMaxLength', 'gradientHorizontalMinLength', 'gradientVerticalMaxLength', 'gradientVerticalMinLength', 'unselectedOpacity']; - - const HEADER_TITLE_PROPERTIES_MAP = { - titleAlign: 'align', - titleAnchor: 'anchor', - titleAngle: 'angle', - titleBaseline: 'baseline', - titleColor: 'color', - titleFont: 'font', - titleFontSize: 'fontSize', - titleFontStyle: 'fontStyle', - titleFontWeight: 'fontWeight', - titleLimit: 'limit', - titleLineHeight: 'lineHeight', - titleOrient: 'orient', - titlePadding: 'offset' - }; - const HEADER_LABEL_PROPERTIES_MAP = { - labelAlign: 'align', - labelAnchor: 'anchor', - labelAngle: 'angle', - labelBaseline: 'baseline', - labelColor: 'color', - labelFont: 'font', - labelFontSize: 'fontSize', - labelFontStyle: 'fontStyle', - labelFontWeight: 'fontWeight', - labelLimit: 'limit', - labelLineHeight: 'lineHeight', - labelOrient: 'orient', - labelPadding: 'offset' - }; - const HEADER_TITLE_PROPERTIES = keys(HEADER_TITLE_PROPERTIES_MAP); - const HEADER_LABEL_PROPERTIES = keys(HEADER_LABEL_PROPERTIES_MAP); - - /** - * Headers of row / column channels for faceted plots. - */ - - const HEADER_CONFIGS_INDEX = { - header: 1, - headerRow: 1, - headerColumn: 1, - headerFacet: 1 - }; - const HEADER_CONFIGS = keys(HEADER_CONFIGS_INDEX); - - const LEGEND_SCALE_CHANNELS = ['size', 'shape', 'fill', 'stroke', 'strokeDash', 'strokeWidth', 'opacity']; - - /** - * Properties of a legend or boolean flag for determining whether to show it. - */ - - // Change comments to be Vega-Lite specific - - const defaultLegendConfig = { - gradientHorizontalMaxLength: 200, - gradientHorizontalMinLength: 100, - gradientVerticalMaxLength: 200, - gradientVerticalMinLength: 64, - // This is Vega's minimum. - unselectedOpacity: 0.35 - }; - const COMMON_LEGEND_PROPERTY_INDEX = { - aria: 1, - clipHeight: 1, - columnPadding: 1, - columns: 1, - cornerRadius: 1, - description: 1, - direction: 1, - fillColor: 1, - format: 1, - formatType: 1, - gradientLength: 1, - gradientOpacity: 1, - gradientStrokeColor: 1, - gradientStrokeWidth: 1, - gradientThickness: 1, - gridAlign: 1, - labelAlign: 1, - labelBaseline: 1, - labelColor: 1, - labelFont: 1, - labelFontSize: 1, - labelFontStyle: 1, - labelFontWeight: 1, - labelLimit: 1, - labelOffset: 1, - labelOpacity: 1, - labelOverlap: 1, - labelPadding: 1, - labelSeparation: 1, - legendX: 1, - legendY: 1, - offset: 1, - orient: 1, - padding: 1, - rowPadding: 1, - strokeColor: 1, - symbolDash: 1, - symbolDashOffset: 1, - symbolFillColor: 1, - symbolLimit: 1, - symbolOffset: 1, - symbolOpacity: 1, - symbolSize: 1, - symbolStrokeColor: 1, - symbolStrokeWidth: 1, - symbolType: 1, - tickCount: 1, - tickMinStep: 1, - title: 1, - titleAlign: 1, - titleAnchor: 1, - titleBaseline: 1, - titleColor: 1, - titleFont: 1, - titleFontSize: 1, - titleFontStyle: 1, - titleFontWeight: 1, - titleLimit: 1, - titleLineHeight: 1, - titleOpacity: 1, - titleOrient: 1, - titlePadding: 1, - type: 1, - values: 1, - zindex: 1 - }; - - const SELECTION_ID = '_vgsid_'; - - // Similar to BaseMarkConfig but the field documentations are specificly for an interval mark. - - const defaultConfig$1 = { - point: { - on: 'click', - fields: [SELECTION_ID], - toggle: 'event.shiftKey', - resolve: 'global', - clear: 'dblclick' - }, - interval: { - on: '[pointerdown, window:pointerup] > window:pointermove!', - encodings: ['x', 'y'], - translate: '[pointerdown, window:pointerup] > window:pointermove!', - zoom: 'wheel!', - mark: { - fill: '#333', - fillOpacity: 0.125, - stroke: 'white' - }, - resolve: 'global', - clear: 'dblclick' - } - }; - function isLegendBinding(bind) { - return bind === 'legend' || !!bind?.legend; - } - function isLegendStreamBinding(bind) { - return isLegendBinding(bind) && vega.isObject(bind); - } - function isSelectionParameter(param) { - return !!param?.['select']; - } - - function assembleParameterSignals(params) { - const signals = []; - for (const param of params || []) { - // Selection parameters are handled separately via assembleSelectionTopLevelSignals - // and assembleSignals methods registered on the Model. - if (isSelectionParameter(param)) continue; - const { - expr, - bind, - ...rest - } = param; - if (bind && expr) { - // Vega's InitSignal -- apply expr to "init" - const signal = { - ...rest, - bind, - init: expr - }; - signals.push(signal); - } else { - const signal = { - ...rest, - ...(expr ? { - update: expr - } : {}), - ...(bind ? { - bind - } : {}) - }; - signals.push(signal); - } - } - return signals; - } - - /** - * Base layout mixins for V/HConcatSpec, which should not have RowCol generic fo its property. - */ - - /** - * Base interface for a generalized concatenation specification. - */ - - /** - * Base interface for a vertical concatenation specification. - */ - - /** - * Base interface for a horizontal concatenation specification. - */ - - /** A concat spec without any shortcut/expansion syntax */ - - function isAnyConcatSpec(spec) { - return isVConcatSpec(spec) || isHConcatSpec(spec) || isConcatSpec(spec); - } - function isConcatSpec(spec) { - return 'concat' in spec; - } - function isVConcatSpec(spec) { - return 'vconcat' in spec; - } - function isHConcatSpec(spec) { - return 'hconcat' in spec; - } - - /** - * Common properties for all types of specification - */ - - function getStepFor(_ref) { - let { - step, - offsetIsDiscrete - } = _ref; - if (offsetIsDiscrete) { - return step.for ?? 'offset'; - } else { - return 'position'; - } - } - function isStep(size) { - return vega.isObject(size) && size['step'] !== undefined; - } - - // TODO(https://github.com/vega/vega-lite/issues/2503): Make this generic so we can support some form of top-down sizing. - /** - * Common properties for specifying width and height of unit and layer specifications. - */ - - function isFrameMixins(o) { - return o['view'] || o['width'] || o['height']; - } - - /** - * Base layout for FacetSpec and RepeatSpec. - * This is named "GenericComposition" layout as ConcatLayout is a GenericCompositionLayout too - * (but _not_ vice versa). - */ - - const DEFAULT_SPACING = 20; - const COMPOSITION_LAYOUT_INDEX = { - align: 1, - bounds: 1, - center: 1, - columns: 1, - spacing: 1 - }; - const COMPOSITION_LAYOUT_PROPERTIES = keys(COMPOSITION_LAYOUT_INDEX); - function extractCompositionLayout(spec, specType, config) { - const compositionConfig = config[specType]; - const layout = {}; - - // Apply config first - const { - spacing: spacingConfig, - columns - } = compositionConfig; - if (spacingConfig !== undefined) { - layout.spacing = spacingConfig; - } - if (columns !== undefined) { - if (isFacetSpec(spec) && !isFacetMapping(spec.facet) || isConcatSpec(spec)) { - layout.columns = columns; - } - } - if (isVConcatSpec(spec)) { - layout.columns = 1; - } - - // Then copy properties from the spec - for (const prop of COMPOSITION_LAYOUT_PROPERTIES) { - if (spec[prop] !== undefined) { - if (prop === 'spacing') { - const spacing = spec[prop]; - layout[prop] = vega.isNumber(spacing) ? spacing : { - row: spacing.row ?? spacingConfig, - column: spacing.column ?? spacingConfig - }; - } else { - layout[prop] = spec[prop]; - } - } - } - return layout; - } - - function getViewConfigContinuousSize(viewConfig, channel) { - return viewConfig[channel] ?? viewConfig[channel === 'width' ? 'continuousWidth' : 'continuousHeight']; // get width/height for backwards compatibility - } - function getViewConfigDiscreteStep(viewConfig, channel) { - const size = getViewConfigDiscreteSize(viewConfig, channel); - return isStep(size) ? size.step : DEFAULT_STEP; - } - function getViewConfigDiscreteSize(viewConfig, channel) { - const size = viewConfig[channel] ?? viewConfig[channel === 'width' ? 'discreteWidth' : 'discreteHeight']; // get width/height for backwards compatibility - return getFirstDefined(size, { - step: viewConfig.step - }); - } - const DEFAULT_STEP = 20; - const defaultViewConfig = { - continuousWidth: 200, - continuousHeight: 200, - step: DEFAULT_STEP - }; - const defaultConfig = { - background: 'white', - padding: 5, - timeFormat: '%b %d, %Y', - countTitle: 'Count of Records', - view: defaultViewConfig, - mark: defaultMarkConfig, - arc: {}, - area: {}, - bar: defaultBarConfig, - circle: {}, - geoshape: {}, - image: {}, - line: {}, - point: {}, - rect: defaultRectConfig, - rule: { - color: 'black' - }, - // Need this to override default color in mark config - square: {}, - text: { - color: 'black' - }, - // Need this to override default color in mark config - tick: defaultTickConfig, - trail: {}, - boxplot: { - size: 14, - extent: 1.5, - box: {}, - median: { - color: 'white' - }, - outliers: {}, - rule: {}, - ticks: null - }, - errorbar: { - center: 'mean', - rule: true, - ticks: false - }, - errorband: { - band: { - opacity: 0.3 - }, - borders: false - }, - scale: defaultScaleConfig, - projection: {}, - legend: defaultLegendConfig, - header: { - titlePadding: 10, - labelPadding: 10 - }, - headerColumn: {}, - headerRow: {}, - headerFacet: {}, - selection: defaultConfig$1, - style: {}, - title: {}, - facet: { - spacing: DEFAULT_SPACING - }, - concat: { - spacing: DEFAULT_SPACING - }, - normalizedNumberFormat: '.0%' - }; - - // Tableau10 color palette, copied from `vegaScale.scheme('tableau10')` - const tab10 = ['#4c78a8', '#f58518', '#e45756', '#72b7b2', '#54a24b', '#eeca3b', '#b279a2', '#ff9da6', '#9d755d', '#bab0ac']; - const DEFAULT_FONT_SIZE = { - text: 11, - guideLabel: 10, - guideTitle: 11, - groupTitle: 13, - groupSubtitle: 12 - }; - const DEFAULT_COLOR = { - blue: tab10[0], - orange: tab10[1], - red: tab10[2], - teal: tab10[3], - green: tab10[4], - yellow: tab10[5], - purple: tab10[6], - pink: tab10[7], - brown: tab10[8], - gray0: '#000', - gray1: '#111', - gray2: '#222', - gray3: '#333', - gray4: '#444', - gray5: '#555', - gray6: '#666', - gray7: '#777', - gray8: '#888', - gray9: '#999', - gray10: '#aaa', - gray11: '#bbb', - gray12: '#ccc', - gray13: '#ddd', - gray14: '#eee', - gray15: '#fff' - }; - function colorSignalConfig() { - let color = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - return { - signals: [{ - name: 'color', - value: vega.isObject(color) ? { - ...DEFAULT_COLOR, - ...color - } : DEFAULT_COLOR - }], - mark: { - color: { - signal: 'color.blue' - } - }, - rule: { - color: { - signal: 'color.gray0' - } - }, - text: { - color: { - signal: 'color.gray0' - } - }, - style: { - 'guide-label': { - fill: { - signal: 'color.gray0' - } - }, - 'guide-title': { - fill: { - signal: 'color.gray0' - } - }, - 'group-title': { - fill: { - signal: 'color.gray0' - } - }, - 'group-subtitle': { - fill: { - signal: 'color.gray0' - } - }, - cell: { - stroke: { - signal: 'color.gray8' - } - } - }, - axis: { - domainColor: { - signal: 'color.gray13' - }, - gridColor: { - signal: 'color.gray8' - }, - tickColor: { - signal: 'color.gray13' - } - }, - range: { - category: [{ - signal: 'color.blue' - }, { - signal: 'color.orange' - }, { - signal: 'color.red' - }, { - signal: 'color.teal' - }, { - signal: 'color.green' - }, { - signal: 'color.yellow' - }, { - signal: 'color.purple' - }, { - signal: 'color.pink' - }, { - signal: 'color.brown' - }, { - signal: 'color.grey8' - }] - } - }; - } - function fontSizeSignalConfig(fontSize) { - return { - signals: [{ - name: 'fontSize', - value: vega.isObject(fontSize) ? { - ...DEFAULT_FONT_SIZE, - ...fontSize - } : DEFAULT_FONT_SIZE - }], - text: { - fontSize: { - signal: 'fontSize.text' - } - }, - style: { - 'guide-label': { - fontSize: { - signal: 'fontSize.guideLabel' - } - }, - 'guide-title': { - fontSize: { - signal: 'fontSize.guideTitle' - } - }, - 'group-title': { - fontSize: { - signal: 'fontSize.groupTitle' - } - }, - 'group-subtitle': { - fontSize: { - signal: 'fontSize.groupSubtitle' - } - } - } - }; - } - function fontConfig(font) { - return { - text: { - font - }, - style: { - 'guide-label': { - font - }, - 'guide-title': { - font - }, - 'group-title': { - font - }, - 'group-subtitle': { - font - } - } - }; - } - function getAxisConfigInternal(axisConfig) { - const props = keys(axisConfig || {}); - const axisConfigInternal = {}; - for (const prop of props) { - const val = axisConfig[prop]; - axisConfigInternal[prop] = isConditionalAxisValue(val) ? signalOrValueRefWithCondition(val) : signalRefOrValue(val); - } - return axisConfigInternal; - } - function getStyleConfigInternal(styleConfig) { - const props = keys(styleConfig); - const styleConfigInternal = {}; - for (const prop of props) { - // We need to cast to cheat a bit here since styleConfig can be either mark config or axis config - styleConfigInternal[prop] = getAxisConfigInternal(styleConfig[prop]); - } - return styleConfigInternal; - } - const configPropsWithExpr = [...MARK_CONFIGS, ...AXIS_CONFIGS, ...HEADER_CONFIGS, 'background', 'padding', 'legend', 'lineBreak', 'scale', 'style', 'title', 'view']; - - /** - * Merge specified config with default config and config for the `color` flag, - * then replace all expressions with signals - */ - function initConfig() { - let specifiedConfig = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - const { - color, - font, - fontSize, - selection, - ...restConfig - } = specifiedConfig; - const mergedConfig = vega.mergeConfig({}, duplicate(defaultConfig), font ? fontConfig(font) : {}, color ? colorSignalConfig(color) : {}, fontSize ? fontSizeSignalConfig(fontSize) : {}, restConfig || {}); - - // mergeConfig doesn't recurse and overrides object values. - if (selection) { - vega.writeConfig(mergedConfig, 'selection', selection, true); - } - const outputConfig = omit(mergedConfig, configPropsWithExpr); - for (const prop of ['background', 'lineBreak', 'padding']) { - if (mergedConfig[prop]) { - outputConfig[prop] = signalRefOrValue(mergedConfig[prop]); - } - } - for (const markConfigType of MARK_CONFIGS) { - if (mergedConfig[markConfigType]) { - // FIXME: outputConfig[markConfigType] expects that types are replaced recursively but replaceExprRef only replaces one level deep - outputConfig[markConfigType] = replaceExprRef(mergedConfig[markConfigType]); - } - } - for (const axisConfigType of AXIS_CONFIGS) { - if (mergedConfig[axisConfigType]) { - outputConfig[axisConfigType] = getAxisConfigInternal(mergedConfig[axisConfigType]); - } - } - for (const headerConfigType of HEADER_CONFIGS) { - if (mergedConfig[headerConfigType]) { - outputConfig[headerConfigType] = replaceExprRef(mergedConfig[headerConfigType]); - } - } - if (mergedConfig.legend) { - outputConfig.legend = replaceExprRef(mergedConfig.legend); - } - if (mergedConfig.scale) { - const { - invalid, - ...otherScaleConfig - } = mergedConfig.scale; - const newScaleInvalid = replaceExprRef(invalid, { - level: 1 - }); - outputConfig.scale = { - ...replaceExprRef(otherScaleConfig), - ...(keys(newScaleInvalid).length > 0 ? { - invalid: newScaleInvalid - } : {}) - }; - } - if (mergedConfig.style) { - outputConfig.style = getStyleConfigInternal(mergedConfig.style); - } - if (mergedConfig.title) { - outputConfig.title = replaceExprRef(mergedConfig.title); - } - if (mergedConfig.view) { - outputConfig.view = replaceExprRef(mergedConfig.view); - } - return outputConfig; - } - const MARK_STYLES = new Set(['view', ...PRIMITIVE_MARKS]); - const VL_ONLY_CONFIG_PROPERTIES = ['color', 'fontSize', 'background', - // We apply background to the spec directly. - 'padding', 'facet', 'concat', 'numberFormat', 'numberFormatType', 'normalizedNumberFormat', 'normalizedNumberFormatType', 'timeFormat', 'countTitle', 'header', 'axisQuantitative', 'axisTemporal', 'axisDiscrete', 'axisPoint', 'axisXBand', 'axisXPoint', 'axisXDiscrete', 'axisXQuantitative', 'axisXTemporal', 'axisYBand', 'axisYPoint', 'axisYDiscrete', 'axisYQuantitative', 'axisYTemporal', 'scale', 'selection', 'overlay' // FIXME: Redesign and unhide this - ]; - const VL_ONLY_ALL_MARK_SPECIFIC_CONFIG_PROPERTY_INDEX = { - view: ['continuousWidth', 'continuousHeight', 'discreteWidth', 'discreteHeight', 'step'], - ...VL_ONLY_MARK_SPECIFIC_CONFIG_PROPERTY_INDEX - }; - function stripAndRedirectConfig(config) { - config = duplicate(config); - for (const prop of VL_ONLY_CONFIG_PROPERTIES) { - delete config[prop]; - } - if (config.axis) { - // delete condition axis config - for (const prop in config.axis) { - if (isConditionalAxisValue(config.axis[prop])) { - delete config.axis[prop]; - } - } - } - if (config.legend) { - for (const prop of VL_ONLY_LEGEND_CONFIG) { - delete config.legend[prop]; - } - } - - // Remove Vega-Lite only generic mark config - if (config.mark) { - for (const prop of VL_ONLY_MARK_CONFIG_PROPERTIES) { - delete config.mark[prop]; - } - if (config.mark.tooltip && vega.isObject(config.mark.tooltip)) { - delete config.mark.tooltip; - } - } - if (config.params) { - config.signals = (config.signals || []).concat(assembleParameterSignals(config.params)); - delete config.params; - } - for (const markType of MARK_STYLES) { - // Remove Vega-Lite-only mark config - for (const prop of VL_ONLY_MARK_CONFIG_PROPERTIES) { - delete config[markType][prop]; - } - - // Remove Vega-Lite only mark-specific config - const vlOnlyMarkSpecificConfigs = VL_ONLY_ALL_MARK_SPECIFIC_CONFIG_PROPERTY_INDEX[markType]; - if (vlOnlyMarkSpecificConfigs) { - for (const prop of vlOnlyMarkSpecificConfigs) { - delete config[markType][prop]; - } - } - - // Redirect mark config to config.style so that mark config only affect its own mark type - // without affecting other marks that share the same underlying Vega marks. - // For example, config.rect should not affect bar marks. - redirectConfigToStyleConfig(config, markType); - } - for (const m of getAllCompositeMarks()) { - // Clean up the composite mark config as we don't need them in the output specs anymore - delete config[m]; - } - redirectTitleConfig(config); - - // Remove empty config objects. - for (const prop in config) { - if (vega.isObject(config[prop]) && isEmpty(config[prop])) { - delete config[prop]; - } - } - return isEmpty(config) ? undefined : config; - } - - /** - * - * Redirect config.title -- so that title config do not affect header labels, - * which also uses `title` directive to implement. - * - * For subtitle configs in config.title, keep them in config.title as header titles never have subtitles. - */ - function redirectTitleConfig(config) { - const { - titleMarkConfig, - subtitleMarkConfig, - subtitle - } = extractTitleConfig(config.title); - - // set config.style if title/subtitleMarkConfig is not an empty object - if (!isEmpty(titleMarkConfig)) { - config.style['group-title'] = { - ...config.style['group-title'], - ...titleMarkConfig // config.title has higher precedence than config.style.group-title in Vega - }; - } - if (!isEmpty(subtitleMarkConfig)) { - config.style['group-subtitle'] = { - ...config.style['group-subtitle'], - ...subtitleMarkConfig - }; - } - - // subtitle part can stay in config.title since header titles do not use subtitle - if (!isEmpty(subtitle)) { - config.title = subtitle; - } else { - delete config.title; - } - } - function redirectConfigToStyleConfig(config, prop, - // string = composite mark - toProp, compositeMarkPart) { - const propConfig = config[prop]; - if (prop === 'view') { - toProp = 'cell'; // View's default style is "cell" - } - const style = { - ...propConfig, - ...config.style[toProp ?? prop] - }; - - // set config.style if it is not an empty object - if (!isEmpty(style)) { - config.style[toProp ?? prop] = style; - } - { - // For composite mark, so don't delete the whole config yet as we have to do multiple redirections. - delete config[prop]; - } - } - - /** - * Base interface for a layer specification. - */ - - /** - * A full layered plot specification, which may contains `encoding` and `projection` properties that will be applied to underlying unit (single-view) specifications. - */ - - /** - * A layered specification without any shortcut/expansion syntax. - */ - - function isLayerSpec(spec) { - return 'layer' in spec; - } - - /** - * Base interface for a repeat specification. - */ - - function isRepeatSpec(spec) { - return 'repeat' in spec; - } - function isLayerRepeatSpec(spec) { - return !vega.isArray(spec.repeat) && spec.repeat['layer']; - } - - class SpecMapper { - map(spec, params) { - if (isFacetSpec(spec)) { - return this.mapFacet(spec, params); - } else if (isRepeatSpec(spec)) { - return this.mapRepeat(spec, params); - } else if (isHConcatSpec(spec)) { - return this.mapHConcat(spec, params); - } else if (isVConcatSpec(spec)) { - return this.mapVConcat(spec, params); - } else if (isConcatSpec(spec)) { - return this.mapConcat(spec, params); - } else { - return this.mapLayerOrUnit(spec, params); - } - } - mapLayerOrUnit(spec, params) { - if (isLayerSpec(spec)) { - return this.mapLayer(spec, params); - } else if (isUnitSpec(spec)) { - return this.mapUnit(spec, params); - } - throw new Error(invalidSpec(spec)); - } - mapLayer(spec, params) { - return { - ...spec, - layer: spec.layer.map(subspec => this.mapLayerOrUnit(subspec, params)) - }; - } - mapHConcat(spec, params) { - return { - ...spec, - hconcat: spec.hconcat.map(subspec => this.map(subspec, params)) - }; - } - mapVConcat(spec, params) { - return { - ...spec, - vconcat: spec.vconcat.map(subspec => this.map(subspec, params)) - }; - } - mapConcat(spec, params) { - const { - concat, - ...rest - } = spec; - return { - ...rest, - concat: concat.map(subspec => this.map(subspec, params)) - }; - } - mapFacet(spec, params) { - return { - // as any is required here since TS cannot infer that FO may only be FieldName or Field, but not RepeatRef - ...spec, - // TODO: remove "any" once we support all facet listed in https://github.com/vega/vega-lite/issues/2760 - spec: this.map(spec.spec, params) - }; - } - mapRepeat(spec, params) { - return { - ...spec, - // as any is required here since TS cannot infer that the output type satisfies the input type - spec: this.map(spec.spec, params) - }; - } - } - - const STACK_OFFSET_INDEX = { - zero: 1, - center: 1, - normalize: 1 - }; - function isStackOffset(s) { - return s in STACK_OFFSET_INDEX; - } - const STACKABLE_MARKS = new Set([ARC, BAR, AREA, RULE, POINT, CIRCLE, SQUARE, LINE, TEXT, TICK]); - const STACK_BY_DEFAULT_MARKS = new Set([BAR, AREA, ARC]); - function isUnbinnedQuantitative(channelDef) { - return isFieldDef(channelDef) && channelDefType(channelDef) === 'quantitative' && !channelDef.bin; - } - function potentialStackedChannel(encoding, x, _ref) { - let { - orient, - type: mark - } = _ref; - const y = x === 'x' ? 'y' : 'radius'; - const isCartesianBarOrArea = x === 'x' && ['bar', 'area'].includes(mark); - const xDef = encoding[x]; - const yDef = encoding[y]; - if (isFieldDef(xDef) && isFieldDef(yDef)) { - if (isUnbinnedQuantitative(xDef) && isUnbinnedQuantitative(yDef)) { - if (xDef.stack) { - return x; - } else if (yDef.stack) { - return y; - } - const xAggregate = isFieldDef(xDef) && !!xDef.aggregate; - const yAggregate = isFieldDef(yDef) && !!yDef.aggregate; - // if there is no explicit stacking, only apply stack if there is only one aggregate for x or y - if (xAggregate !== yAggregate) { - return xAggregate ? x : y; - } - if (isCartesianBarOrArea) { - if (orient === 'vertical') { - return y; - } else if (orient === 'horizontal') { - return x; - } - } - } else if (isUnbinnedQuantitative(xDef)) { - return x; - } else if (isUnbinnedQuantitative(yDef)) { - return y; - } - } else if (isUnbinnedQuantitative(xDef)) { - if (isCartesianBarOrArea && orient === 'vertical') { - return undefined; - } - return x; - } else if (isUnbinnedQuantitative(yDef)) { - if (isCartesianBarOrArea && orient === 'horizontal') { - return undefined; - } - return y; - } - return undefined; - } - function getDimensionChannel(channel) { - switch (channel) { - case 'x': - return 'y'; - case 'y': - return 'x'; - case 'theta': - return 'radius'; - case 'radius': - return 'theta'; - } - } - function stack(m, encoding) { - const markDef = isMarkDef(m) ? m : { - type: m - }; - const mark = markDef.type; - - // Should have stackable mark - if (!STACKABLE_MARKS.has(mark)) { - return null; - } - - // Run potential stacked twice, one for Cartesian and another for Polar, - // so text marks can be stacked in any of the coordinates. - - // Note: The logic here is not perfectly correct. If we want to support stacked dot plots where each dot is a pie chart with label, we have to change the stack logic here to separate Cartesian stacking for polar stacking. - // However, since we probably never want to do that, let's just note the limitation here. - const fieldChannel = potentialStackedChannel(encoding, 'x', markDef) || potentialStackedChannel(encoding, 'theta', markDef); - if (!fieldChannel) { - return null; - } - const stackedFieldDef = encoding[fieldChannel]; - const stackedField = isFieldDef(stackedFieldDef) ? vgField(stackedFieldDef, {}) : undefined; - const dimensionChannel = getDimensionChannel(fieldChannel); - const groupbyChannels = []; - const groupbyFields = new Set(); - if (encoding[dimensionChannel]) { - const dimensionDef = encoding[dimensionChannel]; - const dimensionField = isFieldDef(dimensionDef) ? vgField(dimensionDef, {}) : undefined; - if (dimensionField && dimensionField !== stackedField) { - // avoid grouping by the stacked field - groupbyChannels.push(dimensionChannel); - groupbyFields.add(dimensionField); - } - } - const dimensionOffsetChannel = dimensionChannel === 'x' ? 'xOffset' : 'yOffset'; - const dimensionOffsetDef = encoding[dimensionOffsetChannel]; - const dimensionOffsetField = isFieldDef(dimensionOffsetDef) ? vgField(dimensionOffsetDef, {}) : undefined; - if (dimensionOffsetField && dimensionOffsetField !== stackedField) { - // avoid grouping by the stacked field - groupbyChannels.push(dimensionOffsetChannel); - groupbyFields.add(dimensionOffsetField); - } - - // If the dimension has offset, don't stack anymore - - // Should have grouping level of detail that is different from the dimension field - const stackBy = NONPOSITION_CHANNELS.reduce((sc, channel) => { - // Ignore tooltip in stackBy (https://github.com/vega/vega-lite/issues/4001) - if (channel !== 'tooltip' && channelHasField(encoding, channel)) { - const channelDef = encoding[channel]; - for (const cDef of vega.array(channelDef)) { - const fieldDef = getFieldDef(cDef); - if (fieldDef.aggregate) { - continue; - } - - // Check whether the channel's field is identical to x/y's field or if the channel is a repeat - const f = vgField(fieldDef, {}); - if ( - // if fielddef is a repeat, just include it in the stack by - !f || - // otherwise, the field must be different from the groupBy fields. - !groupbyFields.has(f)) { - sc.push({ - channel, - fieldDef - }); - } - } - } - return sc; - }, []); - - // Automatically determine offset - let offset; - if (stackedFieldDef.stack !== undefined) { - if (vega.isBoolean(stackedFieldDef.stack)) { - offset = stackedFieldDef.stack ? 'zero' : null; - } else { - offset = stackedFieldDef.stack; - } - } else if (STACK_BY_DEFAULT_MARKS.has(mark)) { - offset = 'zero'; - } - if (!offset || !isStackOffset(offset)) { - return null; - } - if (isAggregate$1(encoding) && stackBy.length === 0) { - return null; - } - - // warn when stacking non-linear - if (stackedFieldDef?.scale?.type && stackedFieldDef?.scale?.type !== ScaleType.LINEAR) { - if (stackedFieldDef?.stack) { - warn(stackNonLinearScale(stackedFieldDef.scale.type)); - } - } - - // Check if it is a ranged mark - if (isFieldOrDatumDef(encoding[getSecondaryRangeChannel(fieldChannel)])) { - if (stackedFieldDef.stack !== undefined) { - warn(cannotStackRangedMark(fieldChannel)); - } - return null; - } - - // Warn if stacking non-summative aggregate - if (isFieldDef(stackedFieldDef) && stackedFieldDef.aggregate && !SUM_OPS.has(stackedFieldDef.aggregate)) { - warn(stackNonSummativeAggregate(stackedFieldDef.aggregate)); - } - return { - groupbyChannels, - groupbyFields, - fieldChannel, - impute: stackedFieldDef.impute === null ? false : isPathMark(mark), - stackBy, - offset - }; - } - - function initMarkdef(originalMarkDef, encoding, config) { - // FIXME: markDef expects that exprRefs are replaced recursively but replaceExprRef only replaces the top level - const markDef = replaceExprRef(originalMarkDef); - - // set orient, which can be overridden by rules as sometimes the specified orient is invalid. - const specifiedOrient = getMarkPropOrConfig('orient', markDef, config); - markDef.orient = orient(markDef.type, encoding, specifiedOrient); - if (specifiedOrient !== undefined && specifiedOrient !== markDef.orient) { - warn(orientOverridden(markDef.orient, specifiedOrient)); - } - if (markDef.type === 'bar' && markDef.orient) { - const cornerRadiusEnd = getMarkPropOrConfig('cornerRadiusEnd', markDef, config); - if (cornerRadiusEnd !== undefined) { - const newProps = markDef.orient === 'horizontal' && encoding.x2 || markDef.orient === 'vertical' && encoding.y2 ? ['cornerRadius'] : BAR_CORNER_RADIUS_INDEX[markDef.orient]; - for (const newProp of newProps) { - markDef[newProp] = cornerRadiusEnd; - } - if (markDef.cornerRadiusEnd !== undefined) { - delete markDef.cornerRadiusEnd; // no need to keep the original cap cornerRadius - } - } - } - - // set opacity and filled if not specified in mark config - const specifiedOpacity = getMarkPropOrConfig('opacity', markDef, config); - const specifiedfillOpacity = getMarkPropOrConfig('fillOpacity', markDef, config); - if (specifiedOpacity === undefined && specifiedfillOpacity === undefined) { - markDef.opacity = opacity(markDef.type, encoding); - } - - // set cursor, which should be pointer if href channel is present unless otherwise specified - const specifiedCursor = getMarkPropOrConfig('cursor', markDef, config); - if (specifiedCursor === undefined) { - markDef.cursor = cursor(markDef, encoding, config); - } - return markDef; - } - function cursor(markDef, encoding, config) { - if (encoding.href || markDef.href || getMarkPropOrConfig('href', markDef, config)) { - return 'pointer'; - } - return markDef.cursor; - } - function opacity(mark, encoding) { - if (contains([POINT, TICK, CIRCLE, SQUARE], mark)) { - // point-based marks - if (!isAggregate$1(encoding)) { - return 0.7; - } - } - return undefined; - } - function defaultFilled(markDef, config, _ref) { - let { - graticule - } = _ref; - if (graticule) { - return false; - } - const filledConfig = getMarkConfig('filled', markDef, config); - const mark = markDef.type; - return getFirstDefined(filledConfig, mark !== POINT && mark !== LINE && mark !== RULE); - } - function orient(mark, encoding, specifiedOrient) { - switch (mark) { - case POINT: - case CIRCLE: - case SQUARE: - case TEXT: - case RECT: - case IMAGE: - // orient is meaningless for these marks. - return undefined; - } - const { - x, - y, - x2, - y2 - } = encoding; - switch (mark) { - case BAR: - if (isFieldDef(x) && (isBinned(x.bin) || isFieldDef(y) && y.aggregate && !x.aggregate)) { - return 'vertical'; - } - if (isFieldDef(y) && (isBinned(y.bin) || isFieldDef(x) && x.aggregate && !y.aggregate)) { - return 'horizontal'; - } - if (y2 || x2) { - // Ranged bar does not always have clear orientation, so we allow overriding - if (specifiedOrient) { - return specifiedOrient; - } - - // If y is range and x is non-range, non-bin Q - if (!x2) { - if (isFieldDef(x) && x.type === QUANTITATIVE && !isBinning(x.bin) || isNumericDataDef(x)) { - if (isFieldDef(y) && isBinned(y.bin)) { - return 'horizontal'; - } - } - return 'vertical'; - } - - // If x is range and y is non-range, non-bin Q - if (!y2) { - if (isFieldDef(y) && y.type === QUANTITATIVE && !isBinning(y.bin) || isNumericDataDef(y)) { - if (isFieldDef(x) && isBinned(x.bin)) { - return 'vertical'; - } - } - return 'horizontal'; - } - } - - // falls through - case RULE: - // return undefined for line segment rule and bar with both axis ranged - // we have to ignore the case that the data are already binned - if (x2 && !(isFieldDef(x) && isBinned(x.bin)) && y2 && !(isFieldDef(y) && isBinned(y.bin))) { - return undefined; - } - - // falls through - case AREA: - // If there are range for both x and y, y (vertical) has higher precedence. - if (y2) { - if (isFieldDef(y) && isBinned(y.bin)) { - return 'horizontal'; - } else { - return 'vertical'; - } - } else if (x2) { - if (isFieldDef(x) && isBinned(x.bin)) { - return 'vertical'; - } else { - return 'horizontal'; - } - } else if (mark === RULE) { - if (x && !y) { - return 'vertical'; - } else if (y && !x) { - return 'horizontal'; - } - } - - // falls through - case LINE: - case TICK: - { - const xIsMeasure = isUnbinnedQuantitativeFieldOrDatumDef(x); - const yIsMeasure = isUnbinnedQuantitativeFieldOrDatumDef(y); - if (specifiedOrient) { - return specifiedOrient; - } else if (xIsMeasure && !yIsMeasure) { - // Tick is opposite to bar, line, area - return mark !== 'tick' ? 'horizontal' : 'vertical'; - } else if (!xIsMeasure && yIsMeasure) { - // Tick is opposite to bar, line, area - return mark !== 'tick' ? 'vertical' : 'horizontal'; - } else if (xIsMeasure && yIsMeasure) { - return 'vertical'; - } else { - const xIsTemporal = isTypedFieldDef(x) && x.type === TEMPORAL; - const yIsTemporal = isTypedFieldDef(y) && y.type === TEMPORAL; - - // x: T, y: N --> vertical tick - if (xIsTemporal && !yIsTemporal) { - return 'vertical'; - } else if (!xIsTemporal && yIsTemporal) { - return 'horizontal'; - } - } - return undefined; - } - } - return 'vertical'; - } - - function dropLineAndPoint(markDef) { - const { - point: _point, - line: _line, - ...mark - } = markDef; - return keys(mark).length > 1 ? mark : mark.type; - } - function dropLineAndPointFromConfig(config) { - for (const mark of ['line', 'area', 'rule', 'trail']) { - if (config[mark]) { - config = { - ...config, - // TODO: remove as any - [mark]: omit(config[mark], ['point', 'line']) - }; - } - } - return config; - } - function getPointOverlay(markDef) { - let markConfig = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - let encoding = arguments.length > 2 ? arguments[2] : undefined; - if (markDef.point === 'transparent') { - return { - opacity: 0 - }; - } else if (markDef.point) { - // truthy : true or object - return vega.isObject(markDef.point) ? markDef.point : {}; - } else if (markDef.point !== undefined) { - // false or null - return null; - } else { - // undefined (not disabled) - if (markConfig.point || encoding.shape) { - // enable point overlay if config[mark].point is truthy or if encoding.shape is provided - return vega.isObject(markConfig.point) ? markConfig.point : {}; - } - // markDef.point is defined as falsy - return undefined; - } - } - function getLineOverlay(markDef) { - let markConfig = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - if (markDef.line) { - // true or object - return markDef.line === true ? {} : markDef.line; - } else if (markDef.line !== undefined) { - // false or null - return null; - } else { - // undefined (not disabled) - if (markConfig.line) { - // enable line overlay if config[mark].line is truthy - return markConfig.line === true ? {} : markConfig.line; - } - // markDef.point is defined as falsy - return undefined; - } - } - class PathOverlayNormalizer { - name = 'path-overlay'; - hasMatchingType(spec, config) { - if (isUnitSpec(spec)) { - const { - mark, - encoding - } = spec; - const markDef = isMarkDef(mark) ? mark : { - type: mark - }; - switch (markDef.type) { - case 'line': - case 'rule': - case 'trail': - return !!getPointOverlay(markDef, config[markDef.type], encoding); - case 'area': - return ( - // false / null are also included as we want to remove the properties - !!getPointOverlay(markDef, config[markDef.type], encoding) || !!getLineOverlay(markDef, config[markDef.type]) - ); - } - } - return false; - } - run(spec, normParams, normalize) { - const { - config - } = normParams; - const { - params, - projection, - mark, - name, - encoding: e, - ...outerSpec - } = spec; - - // Need to call normalizeEncoding because we need the inferred types to correctly determine stack - const encoding = normalizeEncoding(e, config); - const markDef = isMarkDef(mark) ? mark : { - type: mark - }; - const pointOverlay = getPointOverlay(markDef, config[markDef.type], encoding); - const lineOverlay = markDef.type === 'area' && getLineOverlay(markDef, config[markDef.type]); - const layer = [{ - name, - ...(params ? { - params - } : {}), - mark: dropLineAndPoint({ - // TODO: extract this 0.7 to be shared with default opacity for point/tick/... - ...(markDef.type === 'area' && markDef.opacity === undefined && markDef.fillOpacity === undefined ? { - opacity: 0.7 - } : {}), - ...markDef - }), - // drop shape from encoding as this might be used to trigger point overlay - encoding: omit(encoding, ['shape']) - }]; - - // FIXME: determine rules for applying selections. - - // Need to copy stack config to overlayed layer - // FIXME: normalizer shouldn't call `initMarkdef`, a method from an init phase. - const stackProps = stack(initMarkdef(markDef, encoding, config), encoding); - let overlayEncoding = encoding; - if (stackProps) { - const { - fieldChannel: stackFieldChannel, - offset - } = stackProps; - overlayEncoding = { - ...encoding, - [stackFieldChannel]: { - ...encoding[stackFieldChannel], - ...(offset ? { - stack: offset - } : {}) - } - }; - } - - // overlay line layer should be on the edge of area but passing y2/x2 makes - // it as "rule" mark so that it draws unwanted vertical/horizontal lines. - // point overlay also should not have y2/x2 as it does not support. - overlayEncoding = omit(overlayEncoding, ['y2', 'x2']); - if (lineOverlay) { - layer.push({ - ...(projection ? { - projection - } : {}), - mark: { - type: 'line', - ...pick(markDef, ['clip', 'interpolate', 'tension', 'tooltip']), - ...lineOverlay - }, - encoding: overlayEncoding - }); - } - if (pointOverlay) { - layer.push({ - ...(projection ? { - projection - } : {}), - mark: { - type: 'point', - opacity: 1, - filled: true, - ...pick(markDef, ['clip', 'tooltip']), - ...pointOverlay - }, - encoding: overlayEncoding - }); - } - return normalize({ - ...outerSpec, - layer - }, { - ...normParams, - config: dropLineAndPointFromConfig(config) - }); - } - } - - function replaceRepeaterInFacet(facet, repeater) { - if (!repeater) { - return facet; - } - if (isFacetMapping(facet)) { - return replaceRepeaterInMapping(facet, repeater); - } - return replaceRepeaterInFieldDef(facet, repeater); - } - function replaceRepeaterInEncoding(encoding, repeater) { - if (!repeater) { - return encoding; - } - return replaceRepeaterInMapping(encoding, repeater); - } - - /** - * Replaces repeated value and returns if the repeated value is valid. - */ - function replaceRepeatInProp(prop, o, repeater) { - const val = o[prop]; - if (isRepeatRef(val)) { - if (val.repeat in repeater) { - return { - ...o, - [prop]: repeater[val.repeat] - }; - } else { - warn(noSuchRepeatedValue(val.repeat)); - return undefined; - } - } - return o; - } - - /** - * Replace repeater values in a field def with the concrete field name. - */ - - function replaceRepeaterInFieldDef(fieldDef, repeater) { - fieldDef = replaceRepeatInProp('field', fieldDef, repeater); - if (fieldDef === undefined) { - // the field def should be ignored - return undefined; - } else if (fieldDef === null) { - return null; - } - if (isSortableFieldDef(fieldDef) && isSortField(fieldDef.sort)) { - const sort = replaceRepeatInProp('field', fieldDef.sort, repeater); - fieldDef = { - ...fieldDef, - ...(sort ? { - sort - } : {}) - }; - } - return fieldDef; - } - function replaceRepeaterInFieldOrDatumDef(def, repeater) { - if (isFieldDef(def)) { - return replaceRepeaterInFieldDef(def, repeater); - } else { - const datumDef = replaceRepeatInProp('datum', def, repeater); - if (datumDef !== def && !datumDef.type) { - datumDef.type = 'nominal'; - } - return datumDef; - } - } - function replaceRepeaterInChannelDef(channelDef, repeater) { - if (isFieldOrDatumDef(channelDef)) { - const fd = replaceRepeaterInFieldOrDatumDef(channelDef, repeater); - if (fd) { - return fd; - } else if (isConditionalDef(channelDef)) { - return { - condition: channelDef.condition - }; - } - } else { - if (hasConditionalFieldOrDatumDef(channelDef)) { - const fd = replaceRepeaterInFieldOrDatumDef(channelDef.condition, repeater); - if (fd) { - return { - ...channelDef, - condition: fd - }; - } else { - const { - condition, - ...channelDefWithoutCondition - } = channelDef; - return channelDefWithoutCondition; - } - } - return channelDef; - } - return undefined; - } - function replaceRepeaterInMapping(mapping, repeater) { - const out = {}; - for (const channel in mapping) { - if (vega.hasOwnProperty(mapping, channel)) { - const channelDef = mapping[channel]; - if (vega.isArray(channelDef)) { - // array cannot have condition - out[channel] = channelDef // somehow we need to cast it here - .map(cd => replaceRepeaterInChannelDef(cd, repeater)).filter(cd => cd); - } else { - const cd = replaceRepeaterInChannelDef(channelDef, repeater); - if (cd !== undefined) { - out[channel] = cd; - } - } - } - } - return out; - } - - class RuleForRangedLineNormalizer { - name = 'RuleForRangedLine'; - hasMatchingType(spec) { - if (isUnitSpec(spec)) { - const { - encoding, - mark - } = spec; - if (mark === 'line' || isMarkDef(mark) && mark.type === 'line') { - for (const channel of SECONDARY_RANGE_CHANNEL) { - const mainChannel = getMainRangeChannel(channel); - const mainChannelDef = encoding[mainChannel]; - if (encoding[channel]) { - if (isFieldDef(mainChannelDef) && !isBinned(mainChannelDef.bin) || isDatumDef(mainChannelDef)) { - return true; - } - } - } - } - } - return false; - } - run(spec, params, normalize) { - const { - encoding, - mark - } = spec; - warn(lineWithRange(!!encoding.x2, !!encoding.y2)); - return normalize({ - ...spec, - mark: vega.isObject(mark) ? { - ...mark, - type: 'rule' - } : 'rule' - }, params); - } - } - - class CoreNormalizer extends SpecMapper { - nonFacetUnitNormalizers = [boxPlotNormalizer, errorBarNormalizer, errorBandNormalizer, new PathOverlayNormalizer(), new RuleForRangedLineNormalizer()]; - map(spec, params) { - // Special handling for a faceted unit spec as it can return a facet spec, not just a layer or unit spec like a normal unit spec. - if (isUnitSpec(spec)) { - const hasRow = channelHasField(spec.encoding, ROW); - const hasColumn = channelHasField(spec.encoding, COLUMN); - const hasFacet = channelHasField(spec.encoding, FACET); - if (hasRow || hasColumn || hasFacet) { - return this.mapFacetedUnit(spec, params); - } - } - return super.map(spec, params); - } - - // This is for normalizing non-facet unit - mapUnit(spec, params) { - const { - parentEncoding, - parentProjection - } = params; - const encoding = replaceRepeaterInEncoding(spec.encoding, params.repeater); - const specWithReplacedEncoding = { - ...spec, - ...(spec.name ? { - name: [params.repeaterPrefix, spec.name].filter(n => n).join('_') - } : {}), - ...(encoding ? { - encoding - } : {}) - }; - if (parentEncoding || parentProjection) { - return this.mapUnitWithParentEncodingOrProjection(specWithReplacedEncoding, params); - } - const normalizeLayerOrUnit = this.mapLayerOrUnit.bind(this); - for (const unitNormalizer of this.nonFacetUnitNormalizers) { - if (unitNormalizer.hasMatchingType(specWithReplacedEncoding, params.config)) { - return unitNormalizer.run(specWithReplacedEncoding, params, normalizeLayerOrUnit); - } - } - return specWithReplacedEncoding; - } - mapRepeat(spec, params) { - if (isLayerRepeatSpec(spec)) { - return this.mapLayerRepeat(spec, params); - } else { - return this.mapNonLayerRepeat(spec, params); - } - } - mapLayerRepeat(spec, params) { - const { - repeat, - spec: childSpec, - ...rest - } = spec; - const { - row, - column, - layer - } = repeat; - const { - repeater = {}, - repeaterPrefix = '' - } = params; - if (row || column) { - return this.mapRepeat({ - ...spec, - repeat: { - ...(row ? { - row - } : {}), - ...(column ? { - column - } : {}) - }, - spec: { - repeat: { - layer - }, - spec: childSpec - } - }, params); - } else { - return { - ...rest, - layer: layer.map(layerValue => { - const childRepeater = { - ...repeater, - layer: layerValue - }; - const childName = `${(childSpec.name ? `${childSpec.name}_` : '') + repeaterPrefix}child__layer_${varName(layerValue)}`; - const child = this.mapLayerOrUnit(childSpec, { - ...params, - repeater: childRepeater, - repeaterPrefix: childName - }); - child.name = childName; - return child; - }) - }; - } - } - mapNonLayerRepeat(spec, params) { - const { - repeat, - spec: childSpec, - data, - ...remainingProperties - } = spec; - if (!vega.isArray(repeat) && spec.columns) { - // is repeat with row/column - spec = omit(spec, ['columns']); - warn(columnsNotSupportByRowCol('repeat')); - } - const concat = []; - const { - repeater = {}, - repeaterPrefix = '' - } = params; - const row = !vega.isArray(repeat) && repeat.row || [repeater ? repeater.row : null]; - const column = !vega.isArray(repeat) && repeat.column || [repeater ? repeater.column : null]; - const repeatValues = vega.isArray(repeat) && repeat || [repeater ? repeater.repeat : null]; - - // cross product - for (const repeatValue of repeatValues) { - for (const rowValue of row) { - for (const columnValue of column) { - const childRepeater = { - repeat: repeatValue, - row: rowValue, - column: columnValue, - layer: repeater.layer - }; - const childName = (childSpec.name ? `${childSpec.name}_` : '') + repeaterPrefix + 'child__' + (vega.isArray(repeat) ? `${varName(repeatValue)}` : (repeat.row ? `row_${varName(rowValue)}` : '') + (repeat.column ? `column_${varName(columnValue)}` : '')); - const child = this.map(childSpec, { - ...params, - repeater: childRepeater, - repeaterPrefix: childName - }); - child.name = childName; - - // we move data up - concat.push(omit(child, ['data'])); - } - } - } - const columns = vega.isArray(repeat) ? spec.columns : repeat.column ? repeat.column.length : 1; - return { - data: childSpec.data ?? data, - // data from child spec should have precedence - align: 'all', - ...remainingProperties, - columns, - concat - }; - } - mapFacet(spec, params) { - const { - facet - } = spec; - if (isFacetMapping(facet) && spec.columns) { - // is facet with row/column - spec = omit(spec, ['columns']); - warn(columnsNotSupportByRowCol('facet')); - } - return super.mapFacet(spec, params); - } - mapUnitWithParentEncodingOrProjection(spec, params) { - const { - encoding, - projection - } = spec; - const { - parentEncoding, - parentProjection, - config - } = params; - const mergedProjection = mergeProjection({ - parentProjection, - projection - }); - const mergedEncoding = mergeEncoding({ - parentEncoding, - encoding: replaceRepeaterInEncoding(encoding, params.repeater) - }); - return this.mapUnit({ - ...spec, - ...(mergedProjection ? { - projection: mergedProjection - } : {}), - ...(mergedEncoding ? { - encoding: mergedEncoding - } : {}) - }, { - config - }); - } - mapFacetedUnit(spec, normParams) { - // New encoding in the inside spec should not contain row / column - // as row/column should be moved to facet - const { - row, - column, - facet, - ...encoding - } = spec.encoding; - - // Mark and encoding should be moved into the inner spec - const { - mark, - width, - projection, - height, - view, - params, - encoding: _, - ...outerSpec - } = spec; - const { - facetMapping, - layout - } = this.getFacetMappingAndLayout({ - row, - column, - facet - }, normParams); - const newEncoding = replaceRepeaterInEncoding(encoding, normParams.repeater); - return this.mapFacet({ - ...outerSpec, - ...layout, - // row / column has higher precedence than facet - facet: facetMapping, - spec: { - ...(width ? { - width - } : {}), - ...(height ? { - height - } : {}), - ...(view ? { - view - } : {}), - ...(projection ? { - projection - } : {}), - mark, - encoding: newEncoding, - ...(params ? { - params - } : {}) - } - }, normParams); - } - getFacetMappingAndLayout(facets, params) { - const { - row, - column, - facet - } = facets; - if (row || column) { - if (facet) { - warn(facetChannelDropped([...(row ? [ROW] : []), ...(column ? [COLUMN] : [])])); - } - const facetMapping = {}; - const layout = {}; - for (const channel of [ROW, COLUMN]) { - const def = facets[channel]; - if (def) { - const { - align, - center, - spacing, - columns, - ...defWithoutLayout - } = def; - facetMapping[channel] = defWithoutLayout; - for (const prop of ['align', 'center', 'spacing']) { - if (def[prop] !== undefined) { - layout[prop] ??= {}; - layout[prop][channel] = def[prop]; - } - } - } - } - return { - facetMapping, - layout - }; - } else { - const { - align, - center, - spacing, - columns, - ...facetMapping - } = facet; - return { - facetMapping: replaceRepeaterInFacet(facetMapping, params.repeater), - layout: { - ...(align ? { - align - } : {}), - ...(center ? { - center - } : {}), - ...(spacing ? { - spacing - } : {}), - ...(columns ? { - columns - } : {}) - } - }; - } - } - mapLayer(spec, _ref) { - let { - parentEncoding, - parentProjection, - ...otherParams - } = _ref; - // Special handling for extended layer spec - - const { - encoding, - projection, - ...rest - } = spec; - const params = { - ...otherParams, - parentEncoding: mergeEncoding({ - parentEncoding, - encoding, - layer: true - }), - parentProjection: mergeProjection({ - parentProjection, - projection - }) - }; - return super.mapLayer({ - ...rest, - ...(spec.name ? { - name: [params.repeaterPrefix, spec.name].filter(n => n).join('_') - } : {}) - }, params); - } - } - function mergeEncoding(_ref2) { - let { - parentEncoding, - encoding = {}, - layer - } = _ref2; - let merged = {}; - if (parentEncoding) { - const channels = new Set([...keys(parentEncoding), ...keys(encoding)]); - for (const channel of channels) { - const channelDef = encoding[channel]; - const parentChannelDef = parentEncoding[channel]; - if (isFieldOrDatumDef(channelDef)) { - // Field/Datum Def can inherit properties from its parent - // Note that parentChannelDef doesn't have to be a field/datum def if the channelDef is already one. - const mergedChannelDef = { - ...parentChannelDef, - ...channelDef - }; - merged[channel] = mergedChannelDef; - } else if (hasConditionalFieldOrDatumDef(channelDef)) { - merged[channel] = { - ...channelDef, - condition: { - ...parentChannelDef, - ...channelDef.condition - } - }; - } else if (channelDef || channelDef === null) { - merged[channel] = channelDef; - } else if (layer || isValueDef(parentChannelDef) || isSignalRef(parentChannelDef) || isFieldOrDatumDef(parentChannelDef) || vega.isArray(parentChannelDef)) { - merged[channel] = parentChannelDef; - } - } - } else { - merged = encoding; - } - return !merged || isEmpty(merged) ? undefined : merged; - } - function mergeProjection(opt) { - const { - parentProjection, - projection - } = opt; - if (parentProjection && projection) { - warn(projectionOverridden({ - parentProjection, - projection - })); - } - return projection ?? parentProjection; - } - - function isFilter(t) { - return 'filter' in t; - } - function isImputeSequence(t) { - return t?.['stop'] !== undefined; - } - function isLookup(t) { - return 'lookup' in t; - } - function isLookupData(from) { - return 'data' in from; - } - function isLookupSelection(from) { - return 'param' in from; - } - function isPivot(t) { - return 'pivot' in t; - } - function isDensity(t) { - return 'density' in t; - } - function isQuantile(t) { - return 'quantile' in t; - } - function isRegression(t) { - return 'regression' in t; - } - function isLoess(t) { - return 'loess' in t; - } - function isSample(t) { - return 'sample' in t; - } - function isWindow(t) { - return 'window' in t; - } - function isJoinAggregate(t) { - return 'joinaggregate' in t; - } - function isFlatten(t) { - return 'flatten' in t; - } - function isCalculate(t) { - return 'calculate' in t; - } - function isBin(t) { - return 'bin' in t; - } - function isImpute(t) { - return 'impute' in t; - } - function isTimeUnit(t) { - return 'timeUnit' in t; - } - function isAggregate(t) { - return 'aggregate' in t; - } - function isStack(t) { - return 'stack' in t; - } - function isFold(t) { - return 'fold' in t; - } - function isExtent(t) { - return 'extent' in t && !('density' in t) && !('regression' in t); - } - function normalizeTransform(transform) { - return transform.map(t => { - if (isFilter(t)) { - return { - filter: normalizeLogicalComposition(t.filter, normalizePredicate$1) - }; - } - return t; - }); - } - - class SelectionCompatibilityNormalizer extends SpecMapper { - map(spec, normParams) { - normParams.emptySelections ??= {}; - normParams.selectionPredicates ??= {}; - spec = normalizeTransforms(spec, normParams); - return super.map(spec, normParams); - } - mapLayerOrUnit(spec, normParams) { - spec = normalizeTransforms(spec, normParams); - if (spec.encoding) { - const encoding = {}; - for (const [channel, enc] of entries$1(spec.encoding)) { - encoding[channel] = normalizeChannelDef(enc, normParams); - } - spec = { - ...spec, - encoding - }; - } - return super.mapLayerOrUnit(spec, normParams); - } - mapUnit(spec, normParams) { - const { - selection, - ...rest - } = spec; - if (selection) { - return { - ...rest, - params: entries$1(selection).map(_ref => { - let [name, selDef] = _ref; - const { - init: value, - bind, - empty, - ...select - } = selDef; - if (select.type === 'single') { - select.type = 'point'; - select.toggle = false; - } else if (select.type === 'multi') { - select.type = 'point'; - } - - // Propagate emptiness forwards and backwards - normParams.emptySelections[name] = empty !== 'none'; - for (const pred of vals(normParams.selectionPredicates[name] ?? {})) { - pred.empty = empty !== 'none'; - } - return { - name, - value, - select, - bind - }; - }) - }; - } - return spec; - } - } - function normalizeTransforms(spec, normParams) { - const { - transform: tx, - ...rest - } = spec; - if (tx) { - const transform = tx.map(t => { - if (isFilter(t)) { - return { - filter: normalizePredicate(t, normParams) - }; - } else if (isBin(t) && isBinParams(t.bin)) { - return { - ...t, - bin: normalizeBinExtent(t.bin) - }; - } else if (isLookup(t)) { - const { - selection: param, - ...from - } = t.from; - return param ? { - ...t, - from: { - param, - ...from - } - } : t; - } - return t; - }); - return { - ...rest, - transform - }; - } - return spec; - } - function normalizeChannelDef(obj, normParams) { - const enc = duplicate(obj); - if (isFieldDef(enc) && isBinParams(enc.bin)) { - enc.bin = normalizeBinExtent(enc.bin); - } - if (isScaleFieldDef(enc) && enc.scale?.domain?.selection) { - const { - selection: param, - ...domain - } = enc.scale.domain; - enc.scale.domain = { - ...domain, - ...(param ? { - param - } : {}) - }; - } - if (isConditionalDef(enc)) { - if (vega.isArray(enc.condition)) { - enc.condition = enc.condition.map(c => { - const { - selection, - param, - test, - ...cond - } = c; - return param ? c : { - ...cond, - test: normalizePredicate(c, normParams) - }; - }); - } else { - const { - selection, - param, - test, - ...cond - } = normalizeChannelDef(enc.condition, normParams); - enc.condition = param ? enc.condition : { - ...cond, - test: normalizePredicate(enc.condition, normParams) - }; - } - } - return enc; - } - function normalizeBinExtent(bin) { - const ext = bin.extent; - if (ext?.selection) { - const { - selection: param, - ...rest - } = ext; - return { - ...bin, - extent: { - ...rest, - param - } - }; - } - return bin; - } - function normalizePredicate(op, normParams) { - // Normalize old compositions of selection names (e.g., selection: {and: ["one", "two"]}) - const normalizeSelectionComposition = o => { - return normalizeLogicalComposition(o, param => { - const empty = normParams.emptySelections[param] ?? true; - const pred = { - param, - empty - }; - normParams.selectionPredicates[param] ??= []; - normParams.selectionPredicates[param].push(pred); - return pred; - }); - }; - return op.selection ? normalizeSelectionComposition(op.selection) : normalizeLogicalComposition(op.test || op.filter, o => o.selection ? normalizeSelectionComposition(o.selection) : o); - } - - class TopLevelSelectionsNormalizer extends SpecMapper { - map(spec, normParams) { - const selections = normParams.selections ?? []; - if (spec.params && !isUnitSpec(spec)) { - const params = []; - for (const param of spec.params) { - if (isSelectionParameter(param)) { - selections.push(param); - } else { - params.push(param); - } - } - spec.params = params; - } - normParams.selections = selections; - return super.map(spec, normParams); - } - mapUnit(spec, normParams) { - const selections = normParams.selections; - if (!selections || !selections.length) return spec; - const path = (normParams.path ?? []).concat(spec.name); - const params = []; - for (const selection of selections) { - // By default, apply selections to all unit views. - if (!selection.views || !selection.views.length) { - params.push(selection); - } else { - for (const view of selection.views) { - // view is either a specific unit name, or a partial path through the spec tree. - if (vega.isString(view) && (view === spec.name || path.includes(view)) || vega.isArray(view) && - // logic for backwards compatibility with view paths before we had unique names - // @ts-ignore - view.map(v => path.indexOf(v)).every((v, i, arr) => v !== -1 && (i === 0 || v > arr[i - 1]))) { - params.push(selection); - } - } - } - } - if (params.length) spec.params = params; - return spec; - } - } - for (const method of ['mapFacet', 'mapRepeat', 'mapHConcat', 'mapVConcat', 'mapLayer']) { - const proto = TopLevelSelectionsNormalizer.prototype[method]; - TopLevelSelectionsNormalizer.prototype[method] = function (spec, params) { - return proto.call(this, spec, addSpecNameToParams(spec, params)); - }; - } - function addSpecNameToParams(spec, params) { - return spec.name ? { - ...params, - path: (params.path ?? []).concat(spec.name) - } : params; - } - - function normalize(spec, config) { - if (config === undefined) { - config = initConfig(spec.config); - } - const normalizedSpec = normalizeGenericSpec(spec, config); - const { - width, - height - } = spec; - const autosize = normalizeAutoSize(normalizedSpec, { - width, - height, - autosize: spec.autosize - }, config); - return { - ...normalizedSpec, - ...(autosize ? { - autosize - } : {}) - }; - } - const coreNormalizer = new CoreNormalizer(); - const selectionCompatNormalizer = new SelectionCompatibilityNormalizer(); - const topLevelSelectionNormalizer = new TopLevelSelectionsNormalizer(); - - /** - * Decompose extended unit specs into composition of pure unit specs. - * And push top-level selection definitions down to unit specs. - */ - function normalizeGenericSpec(spec) { - let config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - const normParams = { - config - }; - return topLevelSelectionNormalizer.map(coreNormalizer.map(selectionCompatNormalizer.map(spec, normParams), normParams), normParams); - } - function _normalizeAutoSize(autosize) { - return vega.isString(autosize) ? { - type: autosize - } : autosize ?? {}; - } - - /** - * Normalize autosize and deal with width or height == "container". - */ - function normalizeAutoSize(spec, sizeInfo, config) { - let { - width, - height - } = sizeInfo; - const isFitCompatible = isUnitSpec(spec) || isLayerSpec(spec); - const autosizeDefault = {}; - if (!isFitCompatible) { - // If spec is not compatible with autosize == "fit", discard width/height == container - if (width == 'container') { - warn(containerSizeNonSingle('width')); - width = undefined; - } - if (height == 'container') { - warn(containerSizeNonSingle('height')); - height = undefined; - } - } else { - // Default autosize parameters to fit when width/height is "container" - if (width == 'container' && height == 'container') { - autosizeDefault.type = 'fit'; - autosizeDefault.contains = 'padding'; - } else if (width == 'container') { - autosizeDefault.type = 'fit-x'; - autosizeDefault.contains = 'padding'; - } else if (height == 'container') { - autosizeDefault.type = 'fit-y'; - autosizeDefault.contains = 'padding'; - } - } - const autosize = { - type: 'pad', - ...autosizeDefault, - ...(config ? _normalizeAutoSize(config.autosize) : {}), - ..._normalizeAutoSize(spec.autosize) - }; - if (autosize.type === 'fit' && !isFitCompatible) { - warn(FIT_NON_SINGLE); - autosize.type = 'pad'; - } - if (width == 'container' && !(autosize.type == 'fit' || autosize.type == 'fit-x')) { - warn(containerSizeNotCompatibleWithAutosize('width')); - } - if (height == 'container' && !(autosize.type == 'fit' || autosize.type == 'fit-y')) { - warn(containerSizeNotCompatibleWithAutosize('height')); - } - - // Delete autosize property if it's Vega's default - if (deepEqual(autosize, { - type: 'pad' - })) { - return undefined; - } - return autosize; - } - - /** - * @minimum 0 - */ - - /** - * Shared properties between Top-Level specs and Config - */ - - function isFitType(autoSizeType) { - return autoSizeType === 'fit' || autoSizeType === 'fit-x' || autoSizeType === 'fit-y'; - } - function getFitType(sizeType) { - return sizeType ? `fit-${getPositionScaleChannel(sizeType)}` : 'fit'; - } - const TOP_LEVEL_PROPERTIES = ['background', 'padding' - // We do not include "autosize" here as it is supported by only unit and layer specs and thus need to be normalized - ]; - function extractTopLevelProperties(t, includeParams) { - const o = {}; - for (const p of TOP_LEVEL_PROPERTIES) { - if (t && t[p] !== undefined) { - o[p] = signalRefOrValue(t[p]); - } - } - if (includeParams) { - o.params = t.params; - } - return o; - } - - /** - * Generic class for storing properties that are explicitly specified - * and implicitly determined by the compiler. - * This is important for scale/axis/legend merging as - * we want to prioritize properties that users explicitly specified. - */ - // eslint-disable-next-line @typescript-eslint/ban-types - class Split { - constructor() { - let explicit = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - let implicit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - this.explicit = explicit; - this.implicit = implicit; - } - clone() { - return new Split(duplicate(this.explicit), duplicate(this.implicit)); - } - combine() { - return { - ...this.explicit, - // Explicit properties comes first - ...this.implicit - }; - } - get(key) { - // Explicit has higher precedence - return getFirstDefined(this.explicit[key], this.implicit[key]); - } - getWithExplicit(key) { - // Explicit has higher precedence - if (this.explicit[key] !== undefined) { - return { - explicit: true, - value: this.explicit[key] - }; - } else if (this.implicit[key] !== undefined) { - return { - explicit: false, - value: this.implicit[key] - }; - } - return { - explicit: false, - value: undefined - }; - } - setWithExplicit(key, _ref) { - let { - value, - explicit - } = _ref; - if (value !== undefined) { - this.set(key, value, explicit); - } - } - set(key, value, explicit) { - delete this[explicit ? 'implicit' : 'explicit'][key]; - this[explicit ? 'explicit' : 'implicit'][key] = value; - return this; - } - copyKeyFromSplit(key, _ref2) { - let { - explicit, - implicit - } = _ref2; - // Explicit has higher precedence - if (explicit[key] !== undefined) { - this.set(key, explicit[key], true); - } else if (implicit[key] !== undefined) { - this.set(key, implicit[key], false); - } - } - copyKeyFromObject(key, s) { - // Explicit has higher precedence - if (s[key] !== undefined) { - this.set(key, s[key], true); - } - } - - /** - * Merge split object into this split object. Properties from the other split - * overwrite properties from this split. - */ - copyAll(other) { - for (const key of keys(other.combine())) { - const val = other.getWithExplicit(key); - this.setWithExplicit(key, val); - } - } - } - function makeExplicit(value) { - return { - explicit: true, - value - }; - } - function makeImplicit(value) { - return { - explicit: false, - value - }; - } - function tieBreakByComparing(compare) { - return (v1, v2, property, propertyOf) => { - const diff = compare(v1.value, v2.value); - if (diff > 0) { - return v1; - } else if (diff < 0) { - return v2; - } - return defaultTieBreaker(v1, v2, property, propertyOf); - }; - } - function defaultTieBreaker(v1, v2, property, propertyOf) { - if (v1.explicit && v2.explicit) { - warn(mergeConflictingProperty(property, propertyOf, v1.value, v2.value)); - } - // If equal score, prefer v1. - return v1; - } - function mergeValuesWithExplicit(v1, v2, property, propertyOf) { - let tieBreaker = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : defaultTieBreaker; - if (v1 === undefined || v1.value === undefined) { - // For first run - return v2; - } - if (v1.explicit && !v2.explicit) { - return v1; - } else if (v2.explicit && !v1.explicit) { - return v2; - } else if (deepEqual(v1.value, v2.value)) { - return v1; - } else { - return tieBreaker(v1, v2, property, propertyOf); - } - } - - /** - * Class to track interesting properties (see https://15721.courses.cs.cmu.edu/spring2016/papers/graefe-ieee1995.pdf) - * about how fields have been parsed or whether they have been derived in a transform. We use this to not parse the - * same field again (or differently). - */ - class AncestorParse extends Split { - constructor() { - let explicit = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - let implicit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - let parseNothing = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; - super(explicit, implicit); - this.explicit = explicit; - this.implicit = implicit; - this.parseNothing = parseNothing; - } - clone() { - const clone = super.clone(); - clone.parseNothing = this.parseNothing; - return clone; - } - } - - /* - * Constants and utilities for data. - */ - - // eslint-disable-next-line @typescript-eslint/ban-types - - function isUrlData(data) { - return 'url' in data; - } - function isInlineData(data) { - return 'values' in data; - } - function isNamedData(data) { - return 'name' in data && !isUrlData(data) && !isInlineData(data) && !isGenerator(data); - } - function isGenerator(data) { - return data && (isSequenceGenerator(data) || isSphereGenerator(data) || isGraticuleGenerator(data)); - } - function isSequenceGenerator(data) { - return 'sequence' in data; - } - function isSphereGenerator(data) { - return 'sphere' in data; - } - function isGraticuleGenerator(data) { - return 'graticule' in data; - } - let DataSourceType = /*#__PURE__*/function (DataSourceType) { - DataSourceType[DataSourceType["Raw"] = 0] = "Raw"; - DataSourceType[DataSourceType["Main"] = 1] = "Main"; - DataSourceType[DataSourceType["Row"] = 2] = "Row"; - DataSourceType[DataSourceType["Column"] = 3] = "Column"; - DataSourceType[DataSourceType["Lookup"] = 4] = "Lookup"; - DataSourceType[DataSourceType["PreFilterInvalid"] = 5] = "PreFilterInvalid"; - DataSourceType[DataSourceType["PostFilterInvalid"] = 6] = "PostFilterInvalid"; - return DataSourceType; - }({}); - - function getDataSourcesForHandlingInvalidValues(_ref) { - let { - invalid, - isPath - } = _ref; - const normalizedInvalid = normalizeInvalidDataMode(invalid, { - isPath - }); - switch (normalizedInvalid) { - case 'filter': - // Both marks and scales use post-filter data - return { - marks: 'exclude-invalid-values', - scales: 'exclude-invalid-values' - }; - case 'break-paths-show-domains': - return { - // Path-based marks use pre-filter data so we know to skip these invalid points in the path. - // For non-path based marks, we skip by not showing them at all. - marks: isPath ? 'include-invalid-values' : 'exclude-invalid-values', - scales: 'include-invalid-values' - }; - case 'break-paths-filter-domains': - // For path marks, the marks will use unfiltered data (and skip points). But we need a separate data sources to feed the domain. - // For non-path marks, we can use the filtered data for both marks and scales. - return { - marks: isPath ? 'include-invalid-values' : 'exclude-invalid-values', - // Unlike 'break-paths-show-domains', 'break-paths-filter-domains' uses post-filter data to feed scale. - scales: 'exclude-invalid-values' - }; - case 'show': - return { - marks: 'include-invalid-values', - scales: 'include-invalid-values' - }; - } - } - function getScaleDataSourceForHandlingInvalidValues(props) { - const { - marks, - scales - } = getDataSourcesForHandlingInvalidValues(props); - if (marks === scales) { - // If both marks and scales use the same data, there is only the main data source. - return DataSourceType.Main; - } - // If marks and scales use differetnt data, return the pre/post-filter data source accordingly. - return scales === 'include-invalid-values' ? DataSourceType.PreFilterInvalid : DataSourceType.PostFilterInvalid; - } - - function assembleProjection(proj) { - const { - signals, - hasLegend, - index, - ...rest - } = proj; - rest.field = replacePathInField(rest.field); - return rest; - } - function assembleInit(init) { - let isExpr = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; - let wrap = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : vega.identity; - if (vega.isArray(init)) { - const assembled = init.map(v => assembleInit(v, isExpr, wrap)); - return isExpr ? `[${assembled.join(', ')}]` : assembled; - } else if (isDateTime(init)) { - if (isExpr) { - return wrap(dateTimeToExpr(init)); - } else { - return wrap(dateTimeToTimestamp(init)); - } - } - return isExpr ? wrap(stringify(init)) : init; - } - function assembleUnitSelectionSignals(model, signals) { - for (const selCmpt of vals(model.component.selection ?? {})) { - const name = selCmpt.name; - let modifyExpr = `${name}${TUPLE}, ${selCmpt.resolve === 'global' ? 'true' : `{unit: ${unitName(model)}}`}`; - for (const c of selectionCompilers) { - if (!c.defined(selCmpt)) continue; - if (c.signals) signals = c.signals(model, selCmpt, signals); - if (c.modifyExpr) modifyExpr = c.modifyExpr(model, selCmpt, modifyExpr); - } - signals.push({ - name: name + MODIFY, - on: [{ - events: { - signal: selCmpt.name + TUPLE - }, - update: `modify(${vega.stringValue(selCmpt.name + STORE)}, ${modifyExpr})` - }] - }); - } - return cleanupEmptyOnArray(signals); - } - function assembleFacetSignals(model, signals) { - if (model.component.selection && keys(model.component.selection).length) { - const name = vega.stringValue(model.getName('cell')); - signals.unshift({ - name: 'facet', - value: {}, - on: [{ - events: vega.parseSelector('pointermove', 'scope'), - update: `isTuple(facet) ? facet : group(${name}).datum` - }] - }); - } - return cleanupEmptyOnArray(signals); - } - function assembleTopLevelSignals(model, signals) { - let hasSelections = false; - for (const selCmpt of vals(model.component.selection ?? {})) { - const name = selCmpt.name; - const store = vega.stringValue(name + STORE); - const hasSg = signals.filter(s => s.name === name); - if (hasSg.length === 0) { - const resolve = selCmpt.resolve === 'global' ? 'union' : selCmpt.resolve; - const isPoint = selCmpt.type === 'point' ? ', true, true)' : ')'; - signals.push({ - name: selCmpt.name, - update: `${VL_SELECTION_RESOLVE}(${store}, ${vega.stringValue(resolve)}${isPoint}` - }); - } - hasSelections = true; - for (const c of selectionCompilers) { - if (c.defined(selCmpt) && c.topLevelSignals) { - signals = c.topLevelSignals(model, selCmpt, signals); - } - } - } - if (hasSelections) { - const hasUnit = signals.filter(s => s.name === 'unit'); - if (hasUnit.length === 0) { - signals.unshift({ - name: 'unit', - value: {}, - on: [{ - events: 'pointermove', - update: 'isTuple(group()) ? group() : unit' - }] - }); - } - } - return cleanupEmptyOnArray(signals); - } - function assembleUnitSelectionData(model, data) { - const dataCopy = [...data]; - const unit = unitName(model, { - escape: false - }); - for (const selCmpt of vals(model.component.selection ?? {})) { - const store = { - name: selCmpt.name + STORE - }; - if (selCmpt.project.hasSelectionId) { - store.transform = [{ - type: 'collect', - sort: { - field: SELECTION_ID - } - }]; - } - if (selCmpt.init) { - const fields = selCmpt.project.items.map(assembleProjection); - store.values = selCmpt.project.hasSelectionId ? selCmpt.init.map(i => ({ - unit, - [SELECTION_ID]: assembleInit(i, false)[0] - })) : selCmpt.init.map(i => ({ - unit, - fields, - values: assembleInit(i, false) - })); - } - const contains = dataCopy.filter(d => d.name === selCmpt.name + STORE); - if (!contains.length) { - dataCopy.push(store); - } - } - return dataCopy; - } - function assembleUnitSelectionMarks(model, marks) { - for (const selCmpt of vals(model.component.selection ?? {})) { - for (const c of selectionCompilers) { - if (c.defined(selCmpt) && c.marks) { - marks = c.marks(model, selCmpt, marks); - } - } - } - return marks; - } - function assembleLayerSelectionMarks(model, marks) { - for (const child of model.children) { - if (isUnitModel(child)) { - marks = assembleUnitSelectionMarks(child, marks); - } - } - return marks; - } - function assembleSelectionScaleDomain(model, extent, scaleCmpt, domain) { - const parsedExtent = parseSelectionExtent(model, extent.param, extent); - return { - signal: hasContinuousDomain(scaleCmpt.get('type')) && vega.isArray(domain) && domain[0] > domain[1] ? `isValid(${parsedExtent}) && reverse(${parsedExtent})` : parsedExtent - }; - } - function cleanupEmptyOnArray(signals) { - return signals.map(s => { - if (s.on && !s.on.length) delete s.on; - return s; - }); - } - - /** - * A node in the dataflow tree. - */ - class DataFlowNode { - _children = []; - _parent = null; - constructor(parent, debugName) { - this.debugName = debugName; - if (parent) { - this.parent = parent; - } - } - - /** - * Clone this node with a deep copy but don't clone links to children or parents. - */ - clone() { - throw new Error('Cannot clone node'); - } - - /** - * Return a hash of the node. - */ - - /** - * Set of fields that this node depends on. - */ - - /** - * Set of fields that are being created by this node. - */ - - get parent() { - return this._parent; - } - - /** - * Set the parent of the node and also add this node to the parent's children. - */ - set parent(parent) { - this._parent = parent; - if (parent) { - parent.addChild(this); - } - } - get children() { - return this._children; - } - numChildren() { - return this._children.length; - } - addChild(child, loc) { - // do not add the same child twice - if (this._children.includes(child)) { - warn(ADD_SAME_CHILD_TWICE); - return; - } - if (loc !== undefined) { - this._children.splice(loc, 0, child); - } else { - this._children.push(child); - } - } - removeChild(oldChild) { - const loc = this._children.indexOf(oldChild); - this._children.splice(loc, 1); - return loc; - } - - /** - * Remove node from the dataflow. - */ - remove() { - let loc = this._parent.removeChild(this); - for (const child of this._children) { - // do not use the set method because we want to insert at a particular location - child._parent = this._parent; - this._parent.addChild(child, loc++); - } - } - - /** - * Insert another node as a parent of this node. - */ - insertAsParentOf(other) { - const parent = other.parent; - parent.removeChild(this); - this.parent = parent; - other.parent = this; - } - swapWithParent() { - const parent = this._parent; - const newParent = parent.parent; - - // reconnect the children - for (const child of this._children) { - child.parent = parent; - } - - // remove old links - this._children = []; // equivalent to removing every child link one by one - parent.removeChild(this); - const loc = parent.parent.removeChild(parent); - - // swap two nodes but maintain order in children - this._parent = newParent; - newParent.addChild(this, loc); - parent.parent = this; - } - } - class OutputNode extends DataFlowNode { - clone() { - const cloneObj = new this.constructor(); - cloneObj.debugName = `clone_${this.debugName}`; - cloneObj._source = this._source; - cloneObj._name = `clone_${this._name}`; - cloneObj.type = this.type; - cloneObj.refCounts = this.refCounts; - cloneObj.refCounts[cloneObj._name] = 0; - return cloneObj; - } - - /** - * @param source The name of the source. Will change in assemble. - * @param type The type of the output node. - * @param refCounts A global ref counter map. - */ - constructor(parent, source, type, refCounts) { - super(parent, source); - this.type = type; - this.refCounts = refCounts; - this._source = this._name = source; - if (this.refCounts && !(this._name in this.refCounts)) { - this.refCounts[this._name] = 0; - } - } - dependentFields() { - return new Set(); - } - producedFields() { - return new Set(); - } - hash() { - if (this._hash === undefined) { - this._hash = `Output ${uniqueId()}`; - } - return this._hash; - } - - /** - * Request the datasource name and increase the ref counter. - * - * During the parsing phase, this will return the simple name such as 'main' or 'raw'. - * It is crucial to request the name from an output node to mark it as a required node. - * If nobody ever requests the name, this datasource will not be instantiated in the assemble phase. - * - * In the assemble phase, this will return the correct name. - */ - getSource() { - this.refCounts[this._name]++; - return this._source; - } - isRequired() { - return !!this.refCounts[this._name]; - } - setSource(source) { - this._source = source; - } - } - - function isTimeUnitTransformComponent(timeUnitComponent) { - return timeUnitComponent.as !== undefined; - } - function offsetAs(field) { - return `${field}_end`; - } - class TimeUnitNode extends DataFlowNode { - clone() { - return new TimeUnitNode(null, duplicate(this.timeUnits)); - } - constructor(parent, timeUnits) { - super(parent); - this.timeUnits = timeUnits; - } - static makeFromEncoding(parent, model) { - const formula = model.reduceFieldDef((timeUnitComponent, fieldDef, channel) => { - const { - field, - timeUnit - } = fieldDef; - if (timeUnit) { - let component; - if (isBinnedTimeUnit(timeUnit)) { - // For binned time unit, only produce end if the mark is a rect-based mark (rect, bar, image, arc), which needs "range". - - if (isUnitModel(model)) { - const { - mark, - markDef, - config - } = model; - const bandPosition = getBandPosition({ - fieldDef, - markDef, - config - }); - if (isRectBasedMark(mark) || !!bandPosition) { - component = { - timeUnit: normalizeTimeUnit(timeUnit), - field - }; - } - } - } else { - component = { - as: vgField(fieldDef, { - forAs: true - }), - field, - timeUnit - }; - } - if (isUnitModel(model)) { - const { - mark, - markDef, - config - } = model; - const bandPosition = getBandPosition({ - fieldDef, - markDef, - config - }); - if (isRectBasedMark(mark) && isXorY(channel) && bandPosition !== 0.5) { - component.rectBandPosition = bandPosition; - } - } - if (component) { - timeUnitComponent[hash(component)] = component; - } - } - return timeUnitComponent; - }, {}); - if (isEmpty(formula)) { - return null; - } - return new TimeUnitNode(parent, formula); - } - static makeFromTransform(parent, t) { - const { - timeUnit, - ...other - } = { - ...t - }; - const normalizedTimeUnit = normalizeTimeUnit(timeUnit); - const component = { - ...other, - timeUnit: normalizedTimeUnit - }; - return new TimeUnitNode(parent, { - [hash(component)]: component - }); - } - - /** - * Merge together TimeUnitNodes assigning the children of `other` to `this` - * and removing `other`. - */ - merge(other) { - this.timeUnits = { - ...this.timeUnits - }; - - // if the same hash happen twice, merge - for (const key in other.timeUnits) { - if (!this.timeUnits[key]) { - // copy if it's not a duplicate - this.timeUnits[key] = other.timeUnits[key]; - } - } - for (const child of other.children) { - other.removeChild(child); - child.parent = this; - } - other.remove(); - } - - /** - * Remove time units coming from the other node. - */ - removeFormulas(fields) { - const newFormula = {}; - for (const [key, timeUnitComponent] of entries$1(this.timeUnits)) { - const fieldAs = isTimeUnitTransformComponent(timeUnitComponent) ? timeUnitComponent.as : `${timeUnitComponent.field}_end`; - if (!fields.has(fieldAs)) { - newFormula[key] = timeUnitComponent; - } - } - this.timeUnits = newFormula; - } - producedFields() { - return new Set(vals(this.timeUnits).map(f => { - return isTimeUnitTransformComponent(f) ? f.as : offsetAs(f.field); - })); - } - dependentFields() { - return new Set(vals(this.timeUnits).map(f => f.field)); - } - hash() { - return `TimeUnit ${hash(this.timeUnits)}`; - } - assemble() { - const transforms = []; - for (const f of vals(this.timeUnits)) { - const { - rectBandPosition - } = f; - const normalizedTimeUnit = normalizeTimeUnit(f.timeUnit); - if (isTimeUnitTransformComponent(f)) { - const { - field, - as - } = f; - const { - unit, - utc, - ...params - } = normalizedTimeUnit; - const startEnd = [as, `${as}_end`]; - transforms.push({ - field: replacePathInField(field), - type: 'timeunit', - ...(unit ? { - units: getTimeUnitParts(unit) - } : {}), - ...(utc ? { - timezone: 'utc' - } : {}), - ...params, - as: startEnd - }); - transforms.push(...offsetedRectFormulas(startEnd, rectBandPosition, normalizedTimeUnit)); - } else if (f) { - const { - field: escapedField - } = f; - // since this is a expression, we want the unescaped field name - const field = escapedField.replaceAll('\\.', '.'); - const expr = offsetExpr({ - timeUnit: normalizedTimeUnit, - field - }); - const endAs = offsetAs(field); - transforms.push({ - type: 'formula', - expr, - as: endAs - }); - transforms.push(...offsetedRectFormulas([field, endAs], rectBandPosition, normalizedTimeUnit)); - } - } - return transforms; - } - } - const OFFSETTED_RECT_START_SUFFIX = 'offsetted_rect_start'; - const OFFSETTED_RECT_END_SUFFIX = 'offsetted_rect_end'; - function offsetExpr(_ref) { - let { - timeUnit, - field, - reverse - } = _ref; - const { - unit, - utc - } = timeUnit; - const smallestUnit = getSmallestTimeUnitPart(unit); - const { - part, - step - } = getDateTimePartAndStep(smallestUnit, timeUnit.step); - const offsetFn = utc ? 'utcOffset' : 'timeOffset'; - const expr = `${offsetFn}('${part}', datum['${field}'], ${reverse ? -step : step})`; - return expr; - } - function offsetedRectFormulas(_ref2, rectBandPosition, timeUnit) { - let [startField, endField] = _ref2; - if (rectBandPosition !== undefined && rectBandPosition !== 0.5) { - const startExpr = `datum['${startField}']`; - const endExpr = `datum['${endField}']`; - return [{ - type: 'formula', - expr: interpolateExpr([offsetExpr({ - timeUnit, - field: startField, - reverse: true - }), startExpr], rectBandPosition + 0.5), - as: `${startField}_${OFFSETTED_RECT_START_SUFFIX}` - }, { - type: 'formula', - expr: interpolateExpr([startExpr, endExpr], rectBandPosition + 0.5), - as: `${startField}_${OFFSETTED_RECT_END_SUFFIX}` - }]; - } - return []; - } - function interpolateExpr(_ref3, fraction) { - let [start, end] = _ref3; - return `${1 - fraction} * ${start} + ${fraction} * ${end}`; - } - - const TUPLE_FIELDS = '_tuple_fields'; - - /** - * Whether the selection tuples hold enumerated or ranged values for a field. - */ - - class SelectionProjectionComponent { - constructor() { - for (var _len = arguments.length, items = new Array(_len), _key = 0; _key < _len; _key++) { - items[_key] = arguments[_key]; - } - this.items = items; - this.hasChannel = {}; - this.hasField = {}; - this.hasSelectionId = false; - } - } - const project = { - defined: () => { - return true; // This transform handles its own defaults, so always run parse. - }, - parse: (model, selCmpt, selDef) => { - const name = selCmpt.name; - const proj = selCmpt.project ??= new SelectionProjectionComponent(); - const parsed = {}; - const timeUnits = {}; - const signals = new Set(); - const signalName = (p, range) => { - const suffix = range === 'visual' ? p.channel : p.field; - let sg = varName(`${name}_${suffix}`); - for (let counter = 1; signals.has(sg); counter++) { - sg = varName(`${name}_${suffix}_${counter}`); - } - signals.add(sg); - return { - [range]: sg - }; - }; - const type = selCmpt.type; - const cfg = model.config.selection[type]; - const init = selDef.value !== undefined ? vega.array(selDef.value) : null; - - // If no explicit projection (either fields or encodings) is specified, set some defaults. - // If an initial value is set, try to infer projections. - let { - fields, - encodings - } = vega.isObject(selDef.select) ? selDef.select : {}; - if (!fields && !encodings && init) { - for (const initVal of init) { - // initVal may be a scalar value to smoothen varParam -> pointSelection gradient. - if (!vega.isObject(initVal)) { - continue; - } - for (const key of keys(initVal)) { - if (isSingleDefUnitChannel(key)) { - (encodings || (encodings = [])).push(key); - } else { - if (type === 'interval') { - warn(INTERVAL_INITIALIZED_WITH_POS); - encodings = cfg.encodings; - } else { - (fields ??= []).push(key); - } - } - } - } - } - - // If no initial value is specified, use the default configuration. - // We break this out as a separate if block (instead of an else condition) - // to account for unprojected point selections that have scalar initial values - if (!fields && !encodings) { - encodings = cfg.encodings; - if ('fields' in cfg) { - fields = cfg.fields; - } - } - for (const channel of encodings ?? []) { - const fieldDef = model.fieldDef(channel); - if (fieldDef) { - let field = fieldDef.field; - if (fieldDef.aggregate) { - warn(cannotProjectAggregate(channel, fieldDef.aggregate)); - continue; - } else if (!field) { - warn(cannotProjectOnChannelWithoutField(channel)); - continue; - } - if (fieldDef.timeUnit && !isBinnedTimeUnit(fieldDef.timeUnit)) { - field = model.vgField(channel); - // Construct TimeUnitComponents which will be combined into a - // TimeUnitNode. This node may need to be inserted into the - // dataflow if the selection is used across views that do not - // have these time units defined. - const component = { - timeUnit: fieldDef.timeUnit, - as: field, - field: fieldDef.field - }; - timeUnits[hash(component)] = component; - } - - // Prevent duplicate projections on the same field. - // TODO: what if the same field is bound to multiple channels (e.g., SPLOM diag). - if (!parsed[field]) { - // Determine whether the tuple will store enumerated or ranged values. - // Interval selections store ranges for continuous scales, and enumerations otherwise. - // Single/multi selections store ranges for binned fields, and enumerations otherwise. - const tplType = type === 'interval' && isScaleChannel(channel) && hasContinuousDomain(model.getScaleComponent(channel).get('type')) ? 'R' : fieldDef.bin ? 'R-RE' : 'E'; - const p = { - field, - channel, - type: tplType, - index: proj.items.length - }; - p.signals = { - ...signalName(p, 'data'), - ...signalName(p, 'visual') - }; - proj.items.push(parsed[field] = p); - proj.hasField[field] = parsed[field]; - proj.hasSelectionId = proj.hasSelectionId || field === SELECTION_ID; - if (isGeoPositionChannel(channel)) { - p.geoChannel = channel; - p.channel = getPositionChannelFromLatLong(channel); - proj.hasChannel[p.channel] = parsed[field]; - } else { - proj.hasChannel[channel] = parsed[field]; - } - } - } else { - warn(cannotProjectOnChannelWithoutField(channel)); - } - } - for (const field of fields ?? []) { - if (proj.hasField[field]) continue; - const p = { - type: 'E', - field, - index: proj.items.length - }; - p.signals = { - ...signalName(p, 'data') - }; - proj.items.push(p); - proj.hasField[field] = p; - proj.hasSelectionId = proj.hasSelectionId || field === SELECTION_ID; - } - if (init) { - selCmpt.init = init.map(v => { - // Selections can be initialized either with a full object that maps projections to values - // or scalar values to smoothen the abstraction gradient from variable params to point selections. - return proj.items.map(p => vega.isObject(v) ? v[p.geoChannel || p.channel] !== undefined ? v[p.geoChannel || p.channel] : v[p.field] : v); - }); - } - if (!isEmpty(timeUnits)) { - proj.timeUnit = new TimeUnitNode(null, timeUnits); - } - }, - signals: (model, selCmpt, allSignals) => { - const name = selCmpt.name + TUPLE_FIELDS; - const hasSignal = allSignals.filter(s => s.name === name); - return hasSignal.length > 0 || selCmpt.project.hasSelectionId ? allSignals : allSignals.concat({ - name, - value: selCmpt.project.items.map(assembleProjection) - }); - } - }; - - const scaleBindings = { - defined: selCmpt => { - return selCmpt.type === 'interval' && selCmpt.resolve === 'global' && selCmpt.bind && selCmpt.bind === 'scales'; - }, - parse: (model, selCmpt) => { - const bound = selCmpt.scales = []; - for (const proj of selCmpt.project.items) { - const channel = proj.channel; - if (!isScaleChannel(channel)) { - continue; - } - const scale = model.getScaleComponent(channel); - const scaleType = scale ? scale.get('type') : undefined; - if (scaleType == 'sequential') { - warn(SEQUENTIAL_SCALE_DEPRECATED); - } - if (!scale || !hasContinuousDomain(scaleType)) { - warn(SCALE_BINDINGS_CONTINUOUS); - continue; - } - scale.set('selectionExtent', { - param: selCmpt.name, - field: proj.field - }, true); - bound.push(proj); - } - }, - topLevelSignals: (model, selCmpt, signals) => { - const bound = selCmpt.scales.filter(proj => signals.filter(s => s.name === proj.signals.data).length === 0); - - // Top-level signals are only needed for multiview displays and if this - // view's top-level signals haven't already been generated. - if (!model.parent || isTopLevelLayer(model) || bound.length === 0) { - return signals; - } - - // vlSelectionResolve does not account for the behavior of bound scales in - // multiview displays. Each unit view adds a tuple to the store, but the - // state of the selection is the unit selection most recently updated. This - // state is captured by the top-level signals that we insert and "push - // outer" to from within the units. We need to reassemble this state into - // the top-level named signal, except no single selCmpt has a global view. - const namedSg = signals.filter(s => s.name === selCmpt.name)[0]; - let update = namedSg.update; - if (update.indexOf(VL_SELECTION_RESOLVE) >= 0) { - namedSg.update = `{${bound.map(proj => `${vega.stringValue(replacePathInField(proj.field))}: ${proj.signals.data}`).join(', ')}}`; - } else { - for (const proj of bound) { - const mapping = `${vega.stringValue(replacePathInField(proj.field))}: ${proj.signals.data}`; - if (!update.includes(mapping)) { - update = `${update.substring(0, update.length - 1)}, ${mapping}}`; - } - } - namedSg.update = update; - } - return signals.concat(bound.map(proj => ({ - name: proj.signals.data - }))); - }, - signals: (model, selCmpt, signals) => { - // Nested signals need only push to top-level signals with multiview displays. - if (model.parent && !isTopLevelLayer(model)) { - for (const proj of selCmpt.scales) { - const signal = signals.find(s => s.name === proj.signals.data); - signal.push = 'outer'; - delete signal.value; - delete signal.update; - } - } - return signals; - } - }; - function domain(model, channel) { - const scale = vega.stringValue(model.scaleName(channel)); - return `domain(${scale})`; - } - function isTopLevelLayer(model) { - return model.parent && isLayerModel(model.parent) && (!model.parent.parent ?? isTopLevelLayer(model.parent.parent)); - } - - const BRUSH = '_brush'; - const SCALE_TRIGGER = '_scale_trigger'; - const GEO_INIT_TICK = 'geo_interval_init_tick'; // Workaround for https://github.com/vega/vega/issues/3481 - const INIT = '_init'; - const CENTER = '_center'; - - // Separate type because the "fields" property is only used internally and we don't want to leak it to the schema. - - const interval = { - defined: selCmpt => selCmpt.type === 'interval', - parse: (model, selCmpt, selDef) => { - if (model.hasProjection) { - const def = { - ...(vega.isObject(selDef.select) ? selDef.select : {}) - }; - def.fields = [SELECTION_ID]; - if (!def.encodings) { - // Remap default x/y projection - def.encodings = selDef.value ? keys(selDef.value) : [LONGITUDE, LATITUDE]; - } - selDef.select = { - type: 'interval', - ...def - }; - } - if (selCmpt.translate && !scaleBindings.defined(selCmpt)) { - const filterExpr = `!event.item || event.item.mark.name !== ${vega.stringValue(selCmpt.name + BRUSH)}`; - for (const evt of selCmpt.events) { - if (!evt.between) { - warn(`${evt} is not an ordered event stream for interval selections.`); - continue; - } - const filters = vega.array(evt.between[0].filter ??= []); - if (filters.indexOf(filterExpr) < 0) { - filters.push(filterExpr); - } - } - } - }, - signals: (model, selCmpt, signals) => { - const name = selCmpt.name; - const tupleSg = name + TUPLE; - const channels = vals(selCmpt.project.hasChannel).filter(p => p.channel === X || p.channel === Y); - const init = selCmpt.init ? selCmpt.init[0] : null; - signals.push(...channels.reduce((arr, proj) => arr.concat(channelSignals(model, selCmpt, proj, init && init[proj.index])), [])); - if (!model.hasProjection) { - // Proxy scale reactions to ensure that an infinite loop doesn't occur - // when an interval selection filter touches the scale. - if (!scaleBindings.defined(selCmpt)) { - const triggerSg = name + SCALE_TRIGGER; - const scaleTriggers = channels.map(proj => { - const channel = proj.channel; - const { - data: dname, - visual: vname - } = proj.signals; - const scaleName = vega.stringValue(model.scaleName(channel)); - const scaleType = model.getScaleComponent(channel).get('type'); - const toNum = hasContinuousDomain(scaleType) ? '+' : ''; - return `(!isArray(${dname}) || ` + `(${toNum}invert(${scaleName}, ${vname})[0] === ${toNum}${dname}[0] && ` + `${toNum}invert(${scaleName}, ${vname})[1] === ${toNum}${dname}[1]))`; - }); - if (scaleTriggers.length) { - signals.push({ - name: triggerSg, - value: {}, - on: [{ - events: channels.map(proj => ({ - scale: model.scaleName(proj.channel) - })), - update: scaleTriggers.join(' && ') + ` ? ${triggerSg} : {}` - }] - }); - } - } - - // Only add an interval to the store if it has valid data extents. Data extents - // are set to null if pixel extents are equal to account for intervals over - // ordinal/nominal domains which, when inverted, will still produce a valid datum. - const dataSignals = channels.map(proj => proj.signals.data); - const update = `unit: ${unitName(model)}, fields: ${name + TUPLE_FIELDS}, values`; - return signals.concat({ - name: tupleSg, - ...(init ? { - init: `{${update}: ${assembleInit(init)}}` - } : {}), - ...(dataSignals.length ? { - on: [{ - events: [{ - signal: dataSignals.join(' || ') - }], - // Prevents double invocation, see https://github.com/vega/vega/issues/1672. - update: `${dataSignals.join(' && ')} ? {${update}: [${dataSignals}]} : null` - }] - } : {}) - }); - } else { - const projection = vega.stringValue(model.projectionName()); - const centerSg = model.projectionName() + CENTER; - const { - x, - y - } = selCmpt.project.hasChannel; - const xvname = x && x.signals.visual; - const yvname = y && y.signals.visual; - const xinit = x ? init && init[x.index] : `${centerSg}[0]`; - const yinit = y ? init && init[y.index] : `${centerSg}[1]`; - const sizeSg = layout => model.getSizeSignalRef(layout).signal; - const bbox = `[` + `[${xvname ? xvname + '[0]' : '0'}, ${yvname ? yvname + '[0]' : '0'}],` + `[${xvname ? xvname + '[1]' : sizeSg('width')}, ` + `${yvname ? yvname + '[1]' : sizeSg('height')}]` + `]`; - if (init) { - signals.unshift({ - name: name + INIT, - init: `[scale(${projection}, [${x ? xinit[0] : xinit}, ${y ? yinit[0] : yinit}]), ` + `scale(${projection}, [${x ? xinit[1] : xinit}, ${y ? yinit[1] : yinit}])]` - }); - if (!x || !y) { - // If initializing a uni-dimensional brush, use the center of the view to determine the other coord - const hasCenterSg = signals.find(s => s.name === centerSg); - if (!hasCenterSg) { - signals.unshift({ - name: centerSg, - update: `invert(${projection}, [${sizeSg('width')}/2, ${sizeSg('height')}/2])` - }); - } - } - } - const intersect = `intersect(${bbox}, {markname: ${vega.stringValue(model.getName('marks'))}}, unit.mark)`; - const base = `{unit: ${unitName(model)}}`; - const update = `vlSelectionTuples(${intersect}, ${base})`; - const visualSignals = channels.map(proj => proj.signals.visual); - return signals.concat({ - name: tupleSg, - on: [{ - events: [...(visualSignals.length ? [{ - signal: visualSignals.join(' || ') - }] : []), ...(init ? [{ - signal: GEO_INIT_TICK - }] : [])], - update - }] - }); - } - }, - topLevelSignals: (model, selCmpt, signals) => { - if (isUnitModel(model) && model.hasProjection && selCmpt.init) { - // Workaround for https://github.com/vega/vega/issues/3481 - // The scenegraph isn't populated on the first pulse. So we use a timer signal - // to re-pulse the dataflow as soon as possible. We return an object to ensure - // this only occurs once. - const hasTick = signals.filter(s => s.name === GEO_INIT_TICK); - if (!hasTick.length) { - signals.unshift({ - name: GEO_INIT_TICK, - value: null, - on: [{ - events: 'timer{1}', - update: `${GEO_INIT_TICK} === null ? {} : ${GEO_INIT_TICK}` - }] - }); - } - } - return signals; - }, - marks: (model, selCmpt, marks) => { - const name = selCmpt.name; - const { - x, - y - } = selCmpt.project.hasChannel; - const xvname = x?.signals.visual; - const yvname = y?.signals.visual; - const store = `data(${vega.stringValue(selCmpt.name + STORE)})`; - - // Do not add a brush if we're binding to scales - // or we don't have a valid interval projection - if (scaleBindings.defined(selCmpt) || !x && !y) { - return marks; - } - const update = { - x: x !== undefined ? { - signal: `${xvname}[0]` - } : { - value: 0 - }, - y: y !== undefined ? { - signal: `${yvname}[0]` - } : { - value: 0 - }, - x2: x !== undefined ? { - signal: `${xvname}[1]` - } : { - field: { - group: 'width' - } - }, - y2: y !== undefined ? { - signal: `${yvname}[1]` - } : { - field: { - group: 'height' - } - } - }; - - // If the selection is resolved to global, only a single interval is in - // the store. Wrap brush mark's encodings with a production rule to test - // this based on the `unit` property. Hide the brush mark if it corresponds - // to a unit different from the one in the store. - if (selCmpt.resolve === 'global') { - for (const key of keys(update)) { - update[key] = [{ - test: `${store}.length && ${store}[0].unit === ${unitName(model)}`, - ...update[key] - }, { - value: 0 - }]; - } - } - - // Two brush marks ensure that fill colors and other aesthetic choices do - // not interefere with the core marks, but that the brushed region can still - // be interacted with (e.g., dragging it around). - const { - fill, - fillOpacity, - cursor, - ...stroke - } = selCmpt.mark; - const vgStroke = keys(stroke).reduce((def, k) => { - def[k] = [{ - test: [x !== undefined && `${xvname}[0] !== ${xvname}[1]`, y !== undefined && `${yvname}[0] !== ${yvname}[1]`].filter(t => t).join(' && '), - value: stroke[k] - }, { - value: null - }]; - return def; - }, {}); - - // Set cursor to move unless the brush cannot be translated - const vgCursor = cursor ?? (selCmpt.translate ? 'move' : null); - return [{ - name: `${name + BRUSH}_bg`, - type: 'rect', - clip: true, - encode: { - enter: { - fill: { - value: fill - }, - fillOpacity: { - value: fillOpacity - } - }, - update - } - }, ...marks, { - name: name + BRUSH, - type: 'rect', - clip: true, - encode: { - enter: { - ...(vgCursor ? { - cursor: { - value: vgCursor - } - } : {}), - fill: { - value: 'transparent' - } - }, - update: { - ...update, - ...vgStroke - } - } - }]; - } - }; - - /** - * Returns the visual and data signals for an interval selection. - */ - function channelSignals(model, selCmpt, proj, init) { - const scaledInterval = !model.hasProjection; - const channel = proj.channel; - const vname = proj.signals.visual; - const scaleName = vega.stringValue(scaledInterval ? model.scaleName(channel) : model.projectionName()); - const scaled = str => `scale(${scaleName}, ${str})`; - const size = model.getSizeSignalRef(channel === X ? 'width' : 'height').signal; - const coord = `${channel}(unit)`; - const von = selCmpt.events.reduce((def, evt) => { - return [...def, { - events: evt.between[0], - update: `[${coord}, ${coord}]` - }, - // Brush Start - { - events: evt, - update: `[${vname}[0], clamp(${coord}, 0, ${size})]` - } // Brush End - ]; - }, []); - if (scaledInterval) { - const dname = proj.signals.data; - const hasScales = scaleBindings.defined(selCmpt); - const scale = model.getScaleComponent(channel); - const scaleType = scale ? scale.get('type') : undefined; - const vinit = init ? { - init: assembleInit(init, true, scaled) - } : { - value: [] - }; - - // React to pan/zooms of continuous scales. Non-continuous scales - // (band, point) cannot be pan/zoomed and any other changes - // to their domains (e.g., filtering) should clear the brushes. - von.push({ - events: { - signal: selCmpt.name + SCALE_TRIGGER - }, - update: hasContinuousDomain(scaleType) ? `[${scaled(`${dname}[0]`)}, ${scaled(`${dname}[1]`)}]` : `[0, 0]` - }); - return hasScales ? [{ - name: dname, - on: [] - }] : [{ - name: vname, - ...vinit, - on: von - }, { - name: dname, - ...(init ? { - init: assembleInit(init) - } : {}), - // Cannot be `value` as `init` may require datetime exprs. - on: [{ - events: { - signal: vname - }, - update: `${vname}[0] === ${vname}[1] ? null : invert(${scaleName}, ${vname})` - }] - }]; - } else { - const initIdx = channel === X ? 0 : 1; - const initSg = selCmpt.name + INIT; - const vinit = init ? { - init: `[${initSg}[0][${initIdx}], ${initSg}[1][${initIdx}]]` - } : { - value: [] - }; - return [{ - name: vname, - ...vinit, - on: von - }]; - } - } - - const point$1 = { - defined: selCmpt => selCmpt.type === 'point', - signals: (model, selCmpt, signals) => { - const name = selCmpt.name; - const fieldsSg = name + TUPLE_FIELDS; - const project = selCmpt.project; - const datum = '(item().isVoronoi ? datum.datum : datum)'; - - // Only add a discrete selection to the store if a datum is present _and_ - // the interaction isn't occurring on a group mark. This guards against - // polluting interactive state with invalid values in faceted displays - // as the group marks are also data-driven. We force the update to account - // for constant null states but varying toggles (e.g., shift-click in - // whitespace followed by a click in whitespace; the store should only - // be cleared on the second click). - const brushes = vals(model.component.selection ?? {}).reduce((acc, cmpt) => { - return cmpt.type === 'interval' ? acc.concat(cmpt.name + BRUSH) : acc; - }, []).map(b => `indexof(item().mark.name, '${b}') < 0`).join(' && '); - const test = `datum && item().mark.marktype !== 'group' && indexof(item().mark.role, 'legend') < 0${brushes ? ` && ${brushes}` : ''}`; - let update = `unit: ${unitName(model)}, `; - if (selCmpt.project.hasSelectionId) { - update += `${SELECTION_ID}: ${datum}[${vega.stringValue(SELECTION_ID)}]`; - } else { - const values = project.items.map(p => { - const fieldDef = model.fieldDef(p.channel); - // Binned fields should capture extents, for a range test against the raw field. - return fieldDef?.bin ? `[${datum}[${vega.stringValue(model.vgField(p.channel, {}))}], ` + `${datum}[${vega.stringValue(model.vgField(p.channel, { - binSuffix: 'end' - }))}]]` : `${datum}[${vega.stringValue(p.field)}]`; - }).join(', '); - update += `fields: ${fieldsSg}, values: [${values}]`; - } - const events = selCmpt.events; - return signals.concat([{ - name: name + TUPLE, - on: events ? [{ - events, - update: `${test} ? {${update}} : null`, - force: true - }] : [] - }]); - } - }; - - /** - * Return a VgEncodeEntry that includes a Vega production rule for a scale channel's encoding or guide encoding, which includes: - * (1) the conditional rules (if provided as part of channelDef) - * (2) invalidValueRef for handling invalid values (if provided as a parameter of this method) - * (3) main reference for the encoded data. - */ - function wrapCondition(_ref) { - let { - model, - channelDef, - vgChannel, - invalidValueRef, - mainRefFn - } = _ref; - const condition = isConditionalDef(channelDef) && channelDef.condition; - let valueRefs = []; - if (condition) { - const conditions = vega.array(condition); - valueRefs = conditions.map(c => { - const conditionValueRef = mainRefFn(c); - if (isConditionalParameter(c)) { - const { - param, - empty - } = c; - const test = parseSelectionPredicate(model, { - param, - empty - }); - return { - test, - ...conditionValueRef - }; - } else { - const test = expression(model, c.test); // FIXME: remove casting once TS is no longer dumb about it - return { - test, - ...conditionValueRef - }; - } - }); - } - if (invalidValueRef !== undefined) { - valueRefs.push(invalidValueRef); - } - const mainValueRef = mainRefFn(channelDef); - if (mainValueRef !== undefined) { - valueRefs.push(mainValueRef); - } - if (valueRefs.length > 1 || valueRefs.length === 1 && Boolean(valueRefs[0].test) // We must use array form valueRefs if test exists, otherwise Vega won't execute the test. - ) { - return { - [vgChannel]: valueRefs - }; - } else if (valueRefs.length === 1) { - return { - [vgChannel]: valueRefs[0] - }; - } - return {}; - } - - function text$1(model) { - let channel = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'text'; - const channelDef = model.encoding[channel]; - return wrapCondition({ - model, - channelDef, - vgChannel: channel, - mainRefFn: cDef => textRef(cDef, model.config), - invalidValueRef: undefined // text encoding doesn't have continuous scales and thus can't have invalid values - }); - } - function textRef(channelDef, config) { - let expr = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'datum'; - // text - if (channelDef) { - if (isValueDef(channelDef)) { - return signalOrValueRef(channelDef.value); - } - if (isFieldOrDatumDef(channelDef)) { - const { - format, - formatType - } = getFormatMixins(channelDef); - return formatSignalRef({ - fieldOrDatumDef: channelDef, - format, - formatType, - expr, - config - }); - } - } - return undefined; - } - - function tooltip(model) { - let opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - const { - encoding, - markDef, - config, - stack - } = model; - const channelDef = encoding.tooltip; - if (vega.isArray(channelDef)) { - return { - tooltip: tooltipRefForEncoding({ - tooltip: channelDef - }, stack, config, opt) - }; - } else { - const datum = opt.reactiveGeom ? 'datum.datum' : 'datum'; - const mainRefFn = cDef => { - // use valueRef based on channelDef first - const tooltipRefFromChannelDef = textRef(cDef, config, datum); - if (tooltipRefFromChannelDef) { - return tooltipRefFromChannelDef; - } - if (cDef === null) { - // Allow using encoding.tooltip = null to disable tooltip - return undefined; - } - let markTooltip = getMarkPropOrConfig('tooltip', markDef, config); - if (markTooltip === true) { - markTooltip = { - content: 'encoding' - }; - } - if (vega.isString(markTooltip)) { - return { - value: markTooltip - }; - } else if (vega.isObject(markTooltip)) { - // `tooltip` is `{fields: 'encodings' | 'fields'}` - if (isSignalRef(markTooltip)) { - return markTooltip; - } else if (markTooltip.content === 'encoding') { - return tooltipRefForEncoding(encoding, stack, config, opt); - } else { - return { - signal: datum - }; - } - } - return undefined; - }; - return wrapCondition({ - model, - channelDef, - vgChannel: 'tooltip', - mainRefFn, - invalidValueRef: undefined // tooltip encoding doesn't have continuous scales and thus can't have invalid values - }); - } - } - function tooltipData(encoding, stack, config) { - let { - reactiveGeom - } = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; - const formatConfig = { - ...config, - ...config.tooltipFormat - }; - const toSkip = {}; - const expr = reactiveGeom ? 'datum.datum' : 'datum'; - const tuples = []; - function add(fDef, channel) { - const mainChannel = getMainRangeChannel(channel); - const fieldDef = isTypedFieldDef(fDef) ? fDef : { - ...fDef, - type: encoding[mainChannel].type // for secondary field def, copy type from main channel - }; - const title = fieldDef.title || defaultTitle(fieldDef, formatConfig); - const key = vega.array(title).join(', ').replaceAll(/"/g, '\\"'); - let value; - if (isXorY(channel)) { - const channel2 = channel === 'x' ? 'x2' : 'y2'; - const fieldDef2 = getFieldDef(encoding[channel2]); - if (isBinned(fieldDef.bin) && fieldDef2) { - const startField = vgField(fieldDef, { - expr - }); - const endField = vgField(fieldDef2, { - expr - }); - const { - format, - formatType - } = getFormatMixins(fieldDef); - value = binFormatExpression(startField, endField, format, formatType, formatConfig); - toSkip[channel2] = true; - } - } - if ((isXorY(channel) || channel === THETA || channel === RADIUS) && stack && stack.fieldChannel === channel && stack.offset === 'normalize') { - const { - format, - formatType - } = getFormatMixins(fieldDef); - value = formatSignalRef({ - fieldOrDatumDef: fieldDef, - format, - formatType, - expr, - config: formatConfig, - normalizeStack: true - }).signal; - } - value ??= textRef(fieldDef, formatConfig, expr).signal; - tuples.push({ - channel, - key, - value - }); - } - forEach(encoding, (channelDef, channel) => { - if (isFieldDef(channelDef)) { - add(channelDef, channel); - } else if (hasConditionalFieldDef(channelDef)) { - add(channelDef.condition, channel); - } - }); - const out = {}; - for (const { - channel, - key, - value - } of tuples) { - if (!toSkip[channel] && !out[key]) { - out[key] = value; - } - } - return out; - } - function tooltipRefForEncoding(encoding, stack, config) { - let { - reactiveGeom - } = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; - const data = tooltipData(encoding, stack, config, { - reactiveGeom - }); - const keyValues = entries$1(data).map(_ref => { - let [key, value] = _ref; - return `"${key}": ${value}`; - }); - return keyValues.length > 0 ? { - signal: `{${keyValues.join(', ')}}` - } : undefined; - } - - function aria(model) { - const { - markDef, - config - } = model; - const enableAria = getMarkPropOrConfig('aria', markDef, config); - - // We can ignore other aria properties if ariaHidden is true. - if (enableAria === false) { - // getMarkGroups sets aria to false already so we don't have to set it in the encode block - return {}; - } - return { - ...(enableAria ? { - aria: enableAria - } : {}), - ...ariaRoleDescription(model), - ...description(model) - }; - } - function ariaRoleDescription(model) { - const { - mark, - markDef, - config - } = model; - if (config.aria === false) { - return {}; - } - const ariaRoleDesc = getMarkPropOrConfig('ariaRoleDescription', markDef, config); - if (ariaRoleDesc != null) { - return { - ariaRoleDescription: { - value: ariaRoleDesc - } - }; - } - return mark in VG_MARK_INDEX ? {} : { - ariaRoleDescription: { - value: mark - } - }; - } - function description(model) { - const { - encoding, - markDef, - config, - stack - } = model; - const channelDef = encoding.description; - if (channelDef) { - return wrapCondition({ - model, - channelDef, - vgChannel: 'description', - mainRefFn: cDef => textRef(cDef, model.config), - invalidValueRef: undefined // aria encoding doesn't have continuous scales and thus can't have invalid values - }); - } - - // Use default from mark def or config if defined. - // Functions in encode usually just return undefined but since we are defining a default below, we need to check the default here. - const descriptionValue = getMarkPropOrConfig('description', markDef, config); - if (descriptionValue != null) { - return { - description: signalOrValueRef(descriptionValue) - }; - } - if (config.aria === false) { - return {}; - } - const data = tooltipData(encoding, stack, config); - if (isEmpty(data)) { - return undefined; - } - return { - description: { - signal: entries$1(data).map((_ref, index) => { - let [key, value] = _ref; - return `"${index > 0 ? '; ' : ''}${key}: " + (${value})`; - }).join(' + ') - } - }; - } - - /** - * Return encode for non-positional channels with scales. (Text doesn't have scale.) - */ - function nonPosition(channel, model) { - let opt = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - const { - markDef, - encoding, - config - } = model; - const { - vgChannel - } = opt; - let { - defaultRef, - defaultValue - } = opt; - if (defaultRef === undefined) { - // prettier-ignore - defaultValue ??= getMarkPropOrConfig(channel, markDef, config, { - vgChannel, - ignoreVgConfig: true - }); - if (defaultValue !== undefined) { - defaultRef = signalOrValueRef(defaultValue); - } - } - const channelDef = encoding[channel]; - const commonProps = { - markDef, - config, - scaleName: model.scaleName(channel), - scale: model.getScaleComponent(channel) - }; - const invalidValueRef = getConditionalValueRefForIncludingInvalidValue({ - ...commonProps, - scaleChannel: channel, - channelDef - }); - const mainRefFn = cDef => { - return midPoint({ - ...commonProps, - channel, - channelDef: cDef, - stack: null, - // No need to provide stack for non-position as it does not affect mid point - defaultRef - }); - }; - return wrapCondition({ - model, - channelDef, - vgChannel: vgChannel ?? channel, - invalidValueRef, - mainRefFn - }); - } - - function color(model) { - let opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { - filled: undefined - }; - const { - markDef, - encoding, - config - } = model; - const { - type: markType - } = markDef; - - // Allow filled to be overridden (for trail's "filled") - const filled = opt.filled ?? getMarkPropOrConfig('filled', markDef, config); - const transparentIfNeeded = contains(['bar', 'point', 'circle', 'square', 'geoshape'], markType) ? 'transparent' : undefined; - const defaultFill = getMarkPropOrConfig(filled === true ? 'color' : undefined, markDef, config, { - vgChannel: 'fill' - }) ?? - // need to add this manually as getMarkConfig normally drops config.mark[channel] if vgChannel is specified - config.mark[filled === true && 'color'] ?? - // If there is no fill, always fill symbols, bar, geoshape - // with transparent fills https://github.com/vega/vega-lite/issues/1316 - transparentIfNeeded; - const defaultStroke = getMarkPropOrConfig(filled === false ? 'color' : undefined, markDef, config, { - vgChannel: 'stroke' - }) ?? - // need to add this manually as getMarkConfig normally drops config.mark[channel] if vgChannel is specified - config.mark[filled === false && 'color']; - const colorVgChannel = filled ? 'fill' : 'stroke'; - const fillStrokeMarkDefAndConfig = { - ...(defaultFill ? { - fill: signalOrValueRef(defaultFill) - } : {}), - ...(defaultStroke ? { - stroke: signalOrValueRef(defaultStroke) - } : {}) - }; - if (markDef.color && (filled ? markDef.fill : markDef.stroke)) { - warn(droppingColor('property', { - fill: 'fill' in markDef, - stroke: 'stroke' in markDef - })); - } - return { - ...fillStrokeMarkDefAndConfig, - ...nonPosition('color', model, { - vgChannel: colorVgChannel, - defaultValue: filled ? defaultFill : defaultStroke - }), - ...nonPosition('fill', model, { - // if there is encoding.fill, include default fill just in case we have conditional-only fill encoding - defaultValue: encoding.fill ? defaultFill : undefined - }), - ...nonPosition('stroke', model, { - // if there is encoding.stroke, include default fill just in case we have conditional-only stroke encoding - defaultValue: encoding.stroke ? defaultStroke : undefined - }) - }; - } - - function zindex(model) { - const { - encoding, - mark - } = model; - const order = encoding.order; - if (!isPathMark(mark) && isValueDef(order)) { - return wrapCondition({ - model, - channelDef: order, - vgChannel: 'zindex', - mainRefFn: cd => signalOrValueRef(cd.value), - invalidValueRef: undefined // zindex encoding doesn't have continuous scales and thus can't have invalid values - }); - } - return {}; - } - - /** - * Utility files for producing Vega ValueRef for marks - */ - - function positionOffset(_ref) { - let { - channel: baseChannel, - markDef, - encoding = {}, - model, - bandPosition - } = _ref; - const channel = `${baseChannel}Offset`; // Need to cast as the type can't be inferred automatically - - const defaultValue = markDef[channel]; - const channelDef = encoding[channel]; - if ((channel === 'xOffset' || channel === 'yOffset') && channelDef) { - const ref = midPoint({ - channel: channel, - channelDef, - markDef, - config: model?.config, - scaleName: model.scaleName(channel), - scale: model.getScaleComponent(channel), - stack: null, - defaultRef: signalOrValueRef(defaultValue), - bandPosition - }); - return { - offsetType: 'encoding', - offset: ref - }; - } - const markDefOffsetValue = markDef[channel]; - if (markDefOffsetValue) { - return { - offsetType: 'visual', - offset: markDefOffsetValue - }; - } - return {}; - } - - /** - * Return encode for point (non-band) position channels. - */ - function pointPosition(channel, model, _ref) { - let { - defaultPos, - vgChannel - } = _ref; - const { - encoding, - markDef, - config, - stack - } = model; - const channelDef = encoding[channel]; - const channel2Def = encoding[getSecondaryRangeChannel(channel)]; - const scaleName = model.scaleName(channel); - const scale = model.getScaleComponent(channel); - const { - offset, - offsetType - } = positionOffset({ - channel, - markDef, - encoding, - model, - bandPosition: 0.5 - }); - - // Get default position or position from mark def - const defaultRef = pointPositionDefaultRef({ - model, - defaultPos, - channel, - scaleName, - scale - }); - const valueRef = !channelDef && isXorY(channel) && (encoding.latitude || encoding.longitude) ? - // use geopoint output if there are lat/long and there is no point position overriding lat/long. - { - field: model.getName(channel) - } : positionRef({ - channel, - channelDef, - channel2Def, - markDef, - config, - scaleName, - scale, - stack, - offset, - defaultRef, - bandPosition: offsetType === 'encoding' ? 0 : undefined - }); - return valueRef ? { - [vgChannel || channel]: valueRef - } : undefined; - } - - // TODO: we need to find a way to refactor these so that scaleName is a part of scale - // but that's complicated. For now, this is a huge step moving forward. - - /** - * @return Vega ValueRef for normal x- or y-position without projection - */ - function positionRef(params) { - const { - channel, - channelDef, - scaleName, - stack, - offset, - markDef - } = params; - - // This isn't a part of midPoint because we use midPoint for non-position too - if (isFieldOrDatumDef(channelDef) && stack && channel === stack.fieldChannel) { - if (isFieldDef(channelDef)) { - let bandPosition = channelDef.bandPosition; - if (bandPosition === undefined && markDef.type === 'text' && (channel === 'radius' || channel === 'theta')) { - // theta and radius of text mark should use bandPosition = 0.5 by default - // so that labels for arc marks are centered automatically - bandPosition = 0.5; - } - if (bandPosition !== undefined) { - return interpolatedSignalRef({ - scaleName, - fieldOrDatumDef: channelDef, - // positionRef always have type - startSuffix: 'start', - bandPosition, - offset - }); - } - } - // x or y use stack_end so that stacked line's point mark use stack_end too. - return valueRefForFieldOrDatumDef(channelDef, scaleName, { - suffix: 'end' - }, { - offset - }); - } - return midPointRefWithPositionInvalidTest(params); - } - function pointPositionDefaultRef(_ref2) { - let { - model, - defaultPos, - channel, - scaleName, - scale - } = _ref2; - const { - markDef, - config - } = model; - return () => { - const mainChannel = getMainRangeChannel(channel); - const vgChannel = getVgPositionChannel(channel); - const definedValueOrConfig = getMarkPropOrConfig(channel, markDef, config, { - vgChannel - }); - if (definedValueOrConfig !== undefined) { - return widthHeightValueOrSignalRef(channel, definedValueOrConfig); - } - switch (defaultPos) { - case 'zeroOrMin': - return zeroOrMinOrMaxPosition({ - scaleName, - scale, - mode: 'zeroOrMin', - mainChannel, - config - }); - case 'zeroOrMax': - return zeroOrMinOrMaxPosition({ - scaleName, - scale, - mode: { - zeroOrMax: { - widthSignal: model.width.signal, - heightSignal: model.height.signal - } - }, - mainChannel, - config - }); - case 'mid': - { - const sizeRef = model[getSizeChannel(channel)]; - return { - ...sizeRef, - mult: 0.5 - }; - } - } - // defaultPos === null - return undefined; - }; - } - function zeroOrMinOrMaxPosition(_ref3) { - let { - mainChannel, - config, - ...otherProps - } = _ref3; - const scaledValueRef = scaledZeroOrMinOrMax(otherProps); - const { - mode - } = otherProps; - if (scaledValueRef) { - return scaledValueRef; - } - switch (mainChannel) { - case 'radius': - { - if (mode === 'zeroOrMin') { - return { - value: 0 - }; // min value - } - const { - widthSignal, - heightSignal - } = mode.zeroOrMax; - // max of radius is min(width, height) / 2 - return { - signal: `min(${widthSignal},${heightSignal})/2` - }; - } - case 'theta': - return mode === 'zeroOrMin' ? { - value: 0 - } : { - signal: '2*PI' - }; - case 'x': - return mode === 'zeroOrMin' ? { - value: 0 - } : { - field: { - group: 'width' - } - }; - case 'y': - return mode === 'zeroOrMin' ? { - field: { - group: 'height' - } - } : { - value: 0 - }; - } - } - - const ALIGNED_X_CHANNEL = { - left: 'x', - center: 'xc', - right: 'x2' - }; - const BASELINED_Y_CHANNEL = { - top: 'y', - middle: 'yc', - bottom: 'y2' - }; - function vgAlignedPositionChannel(channel, markDef, config) { - let defaultAlign = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'middle'; - if (channel === 'radius' || channel === 'theta') { - return getVgPositionChannel(channel); - } - const alignChannel = channel === 'x' ? 'align' : 'baseline'; - const align = getMarkPropOrConfig(alignChannel, markDef, config); - let alignExcludingSignal; - if (isSignalRef(align)) { - warn(rangeMarkAlignmentCannotBeExpression(alignChannel)); - alignExcludingSignal = undefined; - } else { - alignExcludingSignal = align; - } - if (channel === 'x') { - return ALIGNED_X_CHANNEL[alignExcludingSignal || (defaultAlign === 'top' ? 'left' : 'center')]; - } else { - return BASELINED_Y_CHANNEL[alignExcludingSignal || defaultAlign]; - } - } - - /** - * Utility for area/rule position, which can be either point or range. - * (One of the axes should be point and the other should be range.) - */ - function pointOrRangePosition(channel, model, _ref) { - let { - defaultPos, - defaultPos2, - range - } = _ref; - if (range) { - return rangePosition(channel, model, { - defaultPos, - defaultPos2 - }); - } - return pointPosition(channel, model, { - defaultPos - }); - } - function rangePosition(channel, model, _ref2) { - let { - defaultPos, - defaultPos2 - } = _ref2; - const { - markDef, - config - } = model; - const channel2 = getSecondaryRangeChannel(channel); - const sizeChannel = getSizeChannel(channel); - const pos2Mixins = pointPosition2OrSize(model, defaultPos2, channel2); - const vgChannel = pos2Mixins[sizeChannel] ? - // If there is width/height, we need to position the marks based on the alignment. - vgAlignedPositionChannel(channel, markDef, config) : - // Otherwise, make sure to apply to the right Vg Channel (for arc mark) - getVgPositionChannel(channel); - return { - ...pointPosition(channel, model, { - defaultPos, - vgChannel - }), - ...pos2Mixins - }; - } - - /** - * Return encode for x2, y2. - * If channel is not specified, return one channel based on orientation. - */ - function pointPosition2OrSize(model, defaultPos, channel) { - const { - encoding, - mark, - markDef, - stack, - config - } = model; - const baseChannel = getMainRangeChannel(channel); - const sizeChannel = getSizeChannel(channel); - const vgChannel = getVgPositionChannel(channel); - const channelDef = encoding[baseChannel]; - const scaleName = model.scaleName(baseChannel); - const scale = model.getScaleComponent(baseChannel); - const { - offset - } = channel in encoding || channel in markDef ? positionOffset({ - channel, - markDef, - encoding, - model - }) : positionOffset({ - channel: baseChannel, - markDef, - encoding, - model - }); - if (!channelDef && (channel === 'x2' || channel === 'y2') && (encoding.latitude || encoding.longitude)) { - const vgSizeChannel = getSizeChannel(channel); - const size = model.markDef[vgSizeChannel]; - if (size != null) { - return { - [vgSizeChannel]: { - value: size - } - }; - } else { - return { - [vgChannel]: { - field: model.getName(channel) - } - }; - } - } - const valueRef = position2Ref({ - channel, - channelDef, - channel2Def: encoding[channel], - markDef, - config, - scaleName, - scale, - stack, - offset, - defaultRef: undefined - }); - if (valueRef !== undefined) { - return { - [vgChannel]: valueRef - }; - } - - // TODO: check width/height encoding here once we add them - - // no x2/y2 encoding, then try to read x2/y2 or width/height based on precedence: - // markDef > config.style > mark-specific config (config[mark]) > general mark config (config.mark) - - return position2orSize(channel, markDef) || position2orSize(channel, { - [channel]: getMarkStyleConfig(channel, markDef, config.style), - [sizeChannel]: getMarkStyleConfig(sizeChannel, markDef, config.style) - }) || position2orSize(channel, config[mark]) || position2orSize(channel, config.mark) || { - [vgChannel]: pointPositionDefaultRef({ - model, - defaultPos, - channel, - scaleName, - scale - })() - }; - } - function position2Ref(_ref3) { - let { - channel, - channelDef, - channel2Def, - markDef, - config, - scaleName, - scale, - stack, - offset, - defaultRef - } = _ref3; - if (isFieldOrDatumDef(channelDef) && stack && - // If fieldChannel is X and channel is X2 (or Y and Y2) - channel.charAt(0) === stack.fieldChannel.charAt(0)) { - return valueRefForFieldOrDatumDef(channelDef, scaleName, { - suffix: 'start' - }, { - offset - }); - } - return midPointRefWithPositionInvalidTest({ - channel, - channelDef: channel2Def, - scaleName, - scale, - stack, - markDef, - config, - offset, - defaultRef - }); - } - function position2orSize(channel, markDef) { - const sizeChannel = getSizeChannel(channel); - const vgChannel = getVgPositionChannel(channel); - if (markDef[vgChannel] !== undefined) { - return { - [vgChannel]: widthHeightValueOrSignalRef(channel, markDef[vgChannel]) - }; - } else if (markDef[channel] !== undefined) { - return { - [vgChannel]: widthHeightValueOrSignalRef(channel, markDef[channel]) - }; - } else if (markDef[sizeChannel]) { - const dimensionSize = markDef[sizeChannel]; - if (isRelativeBandSize(dimensionSize)) { - warn(relativeBandSizeNotSupported(sizeChannel)); - } else { - return { - [sizeChannel]: widthHeightValueOrSignalRef(channel, dimensionSize) - }; - } - } - return undefined; - } - - function rectPosition(model, channel) { - const { - config, - encoding, - markDef - } = model; - const mark = markDef.type; - const channel2 = getSecondaryRangeChannel(channel); - const sizeChannel = getSizeChannel(channel); - const channelDef = encoding[channel]; - const channelDef2 = encoding[channel2]; - const scale = model.getScaleComponent(channel); - const scaleType = scale ? scale.get('type') : undefined; - const orient = markDef.orient; - const hasSizeDef = encoding[sizeChannel] ?? encoding.size ?? getMarkPropOrConfig('size', markDef, config, { - vgChannel: sizeChannel - }); - const offsetScaleChannel = getOffsetChannel(channel); - const isBarBand = mark === 'bar' && (channel === 'x' ? orient === 'vertical' : orient === 'horizontal'); - - // x, x2, and width -- we must specify two of these in all conditions - if (isFieldDef(channelDef) && (isBinning(channelDef.bin) || isBinned(channelDef.bin) || channelDef.timeUnit && !channelDef2) && !(hasSizeDef && !isRelativeBandSize(hasSizeDef)) && !encoding[offsetScaleChannel] && !hasDiscreteDomain(scaleType)) { - return rectBinPosition({ - fieldDef: channelDef, - fieldDef2: channelDef2, - channel, - model - }); - } else if ((isFieldOrDatumDef(channelDef) && hasDiscreteDomain(scaleType) || isBarBand) && !channelDef2) { - return positionAndSize(channelDef, channel, model); - } else { - return rangePosition(channel, model, { - defaultPos: 'zeroOrMax', - defaultPos2: 'zeroOrMin' - }); - } - } - function defaultSizeRef(sizeChannel, scaleName, scale, config, bandSize, hasFieldDef, mark) { - if (isRelativeBandSize(bandSize)) { - if (scale) { - const scaleType = scale.get('type'); - if (scaleType === 'band') { - let bandWidth = `bandwidth('${scaleName}')`; - if (bandSize.band !== 1) { - bandWidth = `${bandSize.band} * ${bandWidth}`; - } - const minBandSize = getMarkConfig('minBandSize', { - type: mark - }, config); - return { - signal: minBandSize ? `max(${signalOrStringValue(minBandSize)}, ${bandWidth})` : bandWidth - }; - } else if (bandSize.band !== 1) { - warn(cannotUseRelativeBandSizeWithNonBandScale(scaleType)); - bandSize = undefined; - } - } else { - return { - mult: bandSize.band, - field: { - group: sizeChannel - } - }; - } - } else if (isSignalRef(bandSize)) { - return bandSize; - } else if (bandSize) { - return { - value: bandSize - }; - } - - // no valid band size - if (scale) { - const scaleRange = scale.get('range'); - if (isVgRangeStep(scaleRange) && vega.isNumber(scaleRange.step)) { - return { - value: scaleRange.step - 2 - }; - } - } - if (!hasFieldDef) { - const { - bandPaddingInner, - barBandPaddingInner, - rectBandPaddingInner - } = config.scale; - const padding = getFirstDefined(bandPaddingInner, mark === 'bar' ? barBandPaddingInner : rectBandPaddingInner); // this part is like paddingInner in scale.ts - if (isSignalRef(padding)) { - return { - signal: `(1 - (${padding.signal})) * ${sizeChannel}` - }; - } else if (vega.isNumber(padding)) { - return { - signal: `${1 - padding} * ${sizeChannel}` - }; - } - } - const defaultStep = getViewConfigDiscreteStep(config.view, sizeChannel); - return { - value: defaultStep - 2 - }; - } - - /** - * Output position encoding and its size encoding for continuous, point, and band scales. - */ - function positionAndSize(fieldDef, channel, model) { - const { - markDef, - encoding, - config, - stack - } = model; - const orient = markDef.orient; - const scaleName = model.scaleName(channel); - const scale = model.getScaleComponent(channel); - const vgSizeChannel = getSizeChannel(channel); - const channel2 = getSecondaryRangeChannel(channel); - const offsetScaleChannel = getOffsetChannel(channel); - const offsetScaleName = model.scaleName(offsetScaleChannel); - const offsetScale = model.getScaleComponent(getOffsetScaleChannel(channel)); - - // use "size" channel for bars, if there is orient and the channel matches the right orientation - const useVlSizeChannel = orient === 'horizontal' && channel === 'y' || orient === 'vertical' && channel === 'x'; - - // Use size encoding / mark property / config if it exists - let sizeMixins; - if (encoding.size || markDef.size) { - if (useVlSizeChannel) { - sizeMixins = nonPosition('size', model, { - vgChannel: vgSizeChannel, - defaultRef: signalOrValueRef(markDef.size) - }); - } else { - warn(cannotApplySizeToNonOrientedMark(markDef.type)); - } - } - const hasSizeFromMarkOrEncoding = !!sizeMixins; - - // Otherwise, apply default value - const bandSize = getBandSize({ - channel, - fieldDef, - markDef, - config, - scaleType: (scale || offsetScale)?.get('type'), - useVlSizeChannel - }); - sizeMixins = sizeMixins || { - [vgSizeChannel]: defaultSizeRef(vgSizeChannel, offsetScaleName || scaleName, offsetScale || scale, config, bandSize, !!fieldDef, markDef.type) - }; - - /* - Band scales with size value and all point scales, use xc/yc + band=0.5 - Otherwise (band scales that has size based on a band ref), use x/y with position band = (1 - size_band) / 2. - In this case, size_band is the band specified in the x/y-encoding. - By default band is 1, so `(1 - band) / 2` = 0. - If band is 0.6, the the x/y position in such case should be `(1 - band) / 2` = 0.2 - */ - - const defaultBandAlign = (scale || offsetScale)?.get('type') === 'band' && isRelativeBandSize(bandSize) && !hasSizeFromMarkOrEncoding ? 'top' : 'middle'; - const vgChannel = vgAlignedPositionChannel(channel, markDef, config, defaultBandAlign); - const center = vgChannel === 'xc' || vgChannel === 'yc'; - const { - offset, - offsetType - } = positionOffset({ - channel, - markDef, - encoding, - model, - bandPosition: center ? 0.5 : 0 - }); - const posRef = midPointRefWithPositionInvalidTest({ - channel, - channelDef: fieldDef, - markDef, - config, - scaleName, - scale, - stack, - offset, - defaultRef: pointPositionDefaultRef({ - model, - defaultPos: 'mid', - channel, - scaleName, - scale - }), - bandPosition: center ? offsetType === 'encoding' ? 0 : 0.5 : isSignalRef(bandSize) ? { - signal: `(1-${bandSize})/2` - } : isRelativeBandSize(bandSize) ? (1 - bandSize.band) / 2 : 0 - }); - if (vgSizeChannel) { - return { - [vgChannel]: posRef, - ...sizeMixins - }; - } else { - // otherwise, we must simulate size by setting position2 = position + size - // (for theta/radius since Vega doesn't have thetaWidth/radiusWidth) - const vgChannel2 = getVgPositionChannel(channel2); - const sizeRef = sizeMixins[vgSizeChannel]; - const sizeOffset = offset ? { - ...sizeRef, - offset - } : sizeRef; - return { - [vgChannel]: posRef, - // posRef might be an array that wraps position invalid test - [vgChannel2]: vega.isArray(posRef) ? [posRef[0], { - ...posRef[1], - offset: sizeOffset - }] : { - ...posRef, - offset: sizeOffset - } - }; - } - } - function getBinSpacing(channel, spacing, reverse, axisTranslate, offset, minBandSize, bandSizeExpr) { - if (isPolarPositionChannel(channel)) { - return 0; - } - const isEnd = channel === 'x' || channel === 'y2'; - const spacingOffset = isEnd ? -spacing / 2 : spacing / 2; - if (isSignalRef(reverse) || isSignalRef(offset) || isSignalRef(axisTranslate) || minBandSize) { - const reverseExpr = signalOrStringValue(reverse); - const offsetExpr = signalOrStringValue(offset); - const axisTranslateExpr = signalOrStringValue(axisTranslate); - const minBandSizeExpr = signalOrStringValue(minBandSize); - const sign = isEnd ? '' : '-'; - const spacingAndSizeOffset = minBandSize ? `(${bandSizeExpr} < ${minBandSizeExpr} ? ${sign}0.5 * (${minBandSizeExpr} - (${bandSizeExpr})) : ${spacingOffset})` : spacingOffset; - const t = axisTranslateExpr ? `${axisTranslateExpr} + ` : ''; - const r = reverseExpr ? `(${reverseExpr} ? -1 : 1) * ` : ''; - const o = offsetExpr ? `(${offsetExpr} + ${spacingAndSizeOffset})` : spacingAndSizeOffset; - return { - signal: t + r + o - }; - } else { - offset = offset || 0; - return axisTranslate + (reverse ? -offset - spacingOffset : +offset + spacingOffset); - } - } - function rectBinPosition(_ref) { - let { - fieldDef, - fieldDef2, - channel, - model - } = _ref; - const { - config, - markDef, - encoding - } = model; - const scale = model.getScaleComponent(channel); - const scaleName = model.scaleName(channel); - const scaleType = scale ? scale.get('type') : undefined; - const reverse = scale.get('reverse'); - const bandSize = getBandSize({ - channel, - fieldDef, - markDef, - config, - scaleType - }); - const axis = model.component.axes[channel]?.[0]; - const axisTranslate = axis?.get('translate') ?? 0.5; // vega default is 0.5 - - const spacing = isXorY(channel) ? getMarkPropOrConfig('binSpacing', markDef, config) ?? 0 : 0; - const channel2 = getSecondaryRangeChannel(channel); - const vgChannel = getVgPositionChannel(channel); - const vgChannel2 = getVgPositionChannel(channel2); - const minBandSize = getMarkConfig('minBandSize', markDef, config); - const { - offset - } = positionOffset({ - channel, - markDef, - encoding, - model, - bandPosition: 0 - }); - const { - offset: offset2 - } = positionOffset({ - channel: channel2, - markDef, - encoding, - model, - bandPosition: 0 - }); - const bandSizeExpr = binSizeExpr({ - fieldDef, - scaleName - }); - const binSpacingOffset = getBinSpacing(channel, spacing, reverse, axisTranslate, offset, minBandSize, bandSizeExpr); - const binSpacingOffset2 = getBinSpacing(channel2, spacing, reverse, axisTranslate, offset2 ?? offset, minBandSize, bandSizeExpr); - const bandPositionForBandSize = isSignalRef(bandSize) ? { - signal: `(1-${bandSize.signal})/2` - } : isRelativeBandSize(bandSize) ? (1 - bandSize.band) / 2 : 0.5; - const bandPosition = getBandPosition({ - fieldDef, - fieldDef2, - markDef, - config - }); - if (isBinning(fieldDef.bin) || fieldDef.timeUnit) { - const useRectOffsetField = fieldDef.timeUnit && bandPosition !== 0.5; - return { - [vgChannel2]: rectBinRef({ - fieldDef, - scaleName, - bandPosition: bandPositionForBandSize, - offset: binSpacingOffset2, - useRectOffsetField - }), - [vgChannel]: rectBinRef({ - fieldDef, - scaleName, - bandPosition: isSignalRef(bandPositionForBandSize) ? { - signal: `1-${bandPositionForBandSize.signal}` - } : 1 - bandPositionForBandSize, - offset: binSpacingOffset, - useRectOffsetField - }) - }; - } else if (isBinned(fieldDef.bin)) { - const startRef = valueRefForFieldOrDatumDef(fieldDef, scaleName, {}, { - offset: binSpacingOffset2 - }); - if (isFieldDef(fieldDef2)) { - return { - [vgChannel2]: startRef, - [vgChannel]: valueRefForFieldOrDatumDef(fieldDef2, scaleName, {}, { - offset: binSpacingOffset - }) - }; - } else if (isBinParams(fieldDef.bin) && fieldDef.bin.step) { - return { - [vgChannel2]: startRef, - [vgChannel]: { - signal: `scale("${scaleName}", ${vgField(fieldDef, { - expr: 'datum' - })} + ${fieldDef.bin.step})`, - offset: binSpacingOffset - } - }; - } - } - warn(channelRequiredForBinned(channel2)); - return undefined; - } - - /** - * Value Ref for binned fields - */ - function rectBinRef(_ref2) { - let { - fieldDef, - scaleName, - bandPosition, - offset, - useRectOffsetField - } = _ref2; - return interpolatedSignalRef({ - scaleName, - fieldOrDatumDef: fieldDef, - bandPosition, - offset, - ...(useRectOffsetField ? { - startSuffix: OFFSETTED_RECT_START_SUFFIX, - endSuffix: OFFSETTED_RECT_END_SUFFIX - } : {}) - }); - } - - const ALWAYS_IGNORE = new Set(['aria', 'width', 'height']); - function baseEncodeEntry(model, ignore) { - const { - fill = undefined, - stroke = undefined - } = ignore.color === 'include' ? color(model) : {}; - return { - ...markDefProperties(model.markDef, ignore), - ...colorRef('fill', fill), - ...colorRef('stroke', stroke), - ...nonPosition('opacity', model), - ...nonPosition('fillOpacity', model), - ...nonPosition('strokeOpacity', model), - ...nonPosition('strokeWidth', model), - ...nonPosition('strokeDash', model), - ...zindex(model), - ...tooltip(model), - ...text$1(model, 'href'), - ...aria(model) - }; - } - function colorRef(channel, valueRef) { - return valueRef ? { - [channel]: valueRef - } : {}; - } - function markDefProperties(mark, ignore) { - return VG_MARK_CONFIGS.reduce((m, prop) => { - if (!ALWAYS_IGNORE.has(prop) && mark[prop] !== undefined && ignore[prop] !== 'ignore') { - m[prop] = signalOrValueRef(mark[prop]); - } - return m; - }, {}); - } - - /** - * Create Vega's "defined" encoding to break paths in a path mark for invalid values. - */ - function defined(model) { - const { - config, - markDef - } = model; - - // For each channel (x/y), add fields to break path to a set first. - const fieldsToBreakPath = new Set(); - model.forEachFieldDef((fieldDef, channel) => { - let scaleType; - if (!isScaleChannel(channel) || !(scaleType = model.getScaleType(channel))) { - // Skip if the channel is not a scale channel or does not have a scale - return; - } - const isCountAggregate = isCountingAggregateOp(fieldDef.aggregate); - const invalidDataMode = getScaleInvalidDataMode({ - scaleChannel: channel, - markDef, - config, - scaleType, - isCountAggregate - }); - if (shouldBreakPath(invalidDataMode)) { - const field = model.vgField(channel, { - expr: 'datum', - binSuffix: model.stack?.impute ? 'mid' : undefined - }); - if (field) { - fieldsToBreakPath.add(field); - } - } - }); - - // If the set is not empty, return a defined signal. - if (fieldsToBreakPath.size > 0) { - const signal = [...fieldsToBreakPath].map(field => fieldValidPredicate(field, true)).join(' && '); - return { - defined: { - signal - } - }; - } - return undefined; - } - function valueIfDefined(prop, value) { - if (value !== undefined) { - return { - [prop]: signalOrValueRef(value) - }; - } - return undefined; - } - - const VORONOI = 'voronoi'; - const nearest = { - defined: selCmpt => { - return selCmpt.type === 'point' && selCmpt.nearest; - }, - parse: (model, selCmpt) => { - // Scope selection events to the voronoi mark to prevent capturing - // events that occur on the group mark (https://github.com/vega/vega/issues/2112). - if (selCmpt.events) { - for (const s of selCmpt.events) { - s.markname = model.getName(VORONOI); - } - } - }, - marks: (model, selCmpt, marks) => { - const { - x, - y - } = selCmpt.project.hasChannel; - const markType = model.mark; - if (isPathMark(markType)) { - warn(nearestNotSupportForContinuous(markType)); - return marks; - } - const cellDef = { - name: model.getName(VORONOI), - type: 'path', - interactive: true, - from: { - data: model.getName('marks') - }, - encode: { - update: { - fill: { - value: 'transparent' - }, - strokeWidth: { - value: 0.35 - }, - stroke: { - value: 'transparent' - }, - isVoronoi: { - value: true - }, - ...tooltip(model, { - reactiveGeom: true - }) - } - }, - transform: [{ - type: 'voronoi', - x: { - expr: x || !y ? 'datum.datum.x || 0' : '0' - }, - y: { - expr: y || !x ? 'datum.datum.y || 0' : '0' - }, - size: [model.getSizeSignalRef('width'), model.getSizeSignalRef('height')] - }] - }; - let index = 0; - let exists = false; - marks.forEach((mark, i) => { - const name = mark.name ?? ''; - if (name === model.component.mark[0].name) { - index = i; - } else if (name.indexOf(VORONOI) >= 0) { - exists = true; - } - }); - if (!exists) { - marks.splice(index + 1, 0, cellDef); - } - return marks; - } - }; - - const inputBindings = { - defined: selCmpt => { - return selCmpt.type === 'point' && selCmpt.resolve === 'global' && selCmpt.bind && selCmpt.bind !== 'scales' && !isLegendBinding(selCmpt.bind); - }, - parse: (model, selCmpt, selDef) => disableDirectManipulation(selCmpt, selDef), - topLevelSignals: (model, selCmpt, signals) => { - const name = selCmpt.name; - const proj = selCmpt.project; - const bind = selCmpt.bind; - const init = selCmpt.init && selCmpt.init[0]; // Can only exist on single selections (one initial value). - const datum = nearest.defined(selCmpt) ? '(item().isVoronoi ? datum.datum : datum)' : 'datum'; - proj.items.forEach((p, i) => { - const sgname = varName(`${name}_${p.field}`); - const hasSignal = signals.filter(s => s.name === sgname); - if (!hasSignal.length) { - signals.unshift({ - name: sgname, - ...(init ? { - init: assembleInit(init[i]) - } : { - value: null - }), - on: selCmpt.events ? [{ - events: selCmpt.events, - update: `datum && item().mark.marktype !== 'group' ? ${datum}[${vega.stringValue(p.field)}] : null` - }] : [], - bind: bind[p.field] ?? bind[p.channel] ?? bind - }); - } - }); - return signals; - }, - signals: (model, selCmpt, signals) => { - const name = selCmpt.name; - const proj = selCmpt.project; - const signal = signals.filter(s => s.name === name + TUPLE)[0]; - const fields = name + TUPLE_FIELDS; - const values = proj.items.map(p => varName(`${name}_${p.field}`)); - const valid = values.map(v => `${v} !== null`).join(' && '); - if (values.length) { - signal.update = `${valid} ? {fields: ${fields}, values: [${values.join(', ')}]} : null`; - } - delete signal.value; - delete signal.on; - return signals; - } - }; - - const TOGGLE = '_toggle'; - const toggle = { - defined: selCmpt => { - return selCmpt.type === 'point' && !!selCmpt.toggle; - }, - signals: (model, selCmpt, signals) => { - return signals.concat({ - name: selCmpt.name + TOGGLE, - value: false, - on: [{ - events: selCmpt.events, - update: selCmpt.toggle - }] - }); - }, - modifyExpr: (model, selCmpt) => { - const tpl = selCmpt.name + TUPLE; - const signal = selCmpt.name + TOGGLE; - return `${signal} ? null : ${tpl}, ` + (selCmpt.resolve === 'global' ? `${signal} ? null : true, ` : `${signal} ? null : {unit: ${unitName(model)}}, `) + `${signal} ? ${tpl} : null`; - } - }; - - const clear = { - defined: selCmpt => { - return selCmpt.clear !== undefined && selCmpt.clear !== false; - }, - parse: (model, selCmpt) => { - if (selCmpt.clear) { - selCmpt.clear = vega.isString(selCmpt.clear) ? vega.parseSelector(selCmpt.clear, 'view') : selCmpt.clear; - } - }, - topLevelSignals: (model, selCmpt, signals) => { - if (inputBindings.defined(selCmpt)) { - for (const proj of selCmpt.project.items) { - const idx = signals.findIndex(n => n.name === varName(`${selCmpt.name}_${proj.field}`)); - if (idx !== -1) { - signals[idx].on.push({ - events: selCmpt.clear, - update: 'null' - }); - } - } - } - return signals; - }, - signals: (model, selCmpt, signals) => { - function addClear(idx, update) { - if (idx !== -1 && signals[idx].on) { - signals[idx].on.push({ - events: selCmpt.clear, - update - }); - } - } - - // Be as minimalist as possible when adding clear triggers to minimize dataflow execution. - if (selCmpt.type === 'interval') { - for (const proj of selCmpt.project.items) { - const vIdx = signals.findIndex(n => n.name === proj.signals.visual); - addClear(vIdx, '[0, 0]'); - if (vIdx === -1) { - const dIdx = signals.findIndex(n => n.name === proj.signals.data); - addClear(dIdx, 'null'); - } - } - } else { - let tIdx = signals.findIndex(n => n.name === selCmpt.name + TUPLE); - addClear(tIdx, 'null'); - if (toggle.defined(selCmpt)) { - tIdx = signals.findIndex(n => n.name === selCmpt.name + TOGGLE); - addClear(tIdx, 'false'); - } - } - return signals; - } - }; - - const legendBindings = { - defined: selCmpt => { - const spec = selCmpt.resolve === 'global' && selCmpt.bind && isLegendBinding(selCmpt.bind); - const projLen = selCmpt.project.items.length === 1 && selCmpt.project.items[0].field !== SELECTION_ID; - if (spec && !projLen) { - warn(LEGEND_BINDINGS_MUST_HAVE_PROJECTION); - } - return spec && projLen; - }, - parse: (model, selCmpt, selDef) => { - // Allow legend items to be toggleable by default even though direct manipulation is disabled. - const selDef_ = duplicate(selDef); - selDef_.select = vega.isString(selDef_.select) ? { - type: selDef_.select, - toggle: selCmpt.toggle - } : { - ...selDef_.select, - toggle: selCmpt.toggle - }; - disableDirectManipulation(selCmpt, selDef_); - if (vega.isObject(selDef.select) && (selDef.select.on || selDef.select.clear)) { - const legendFilter = 'event.item && indexof(event.item.mark.role, "legend") < 0'; - for (const evt of selCmpt.events) { - evt.filter = vega.array(evt.filter ?? []); - if (!evt.filter.includes(legendFilter)) { - evt.filter.push(legendFilter); - } - } - } - const evt = isLegendStreamBinding(selCmpt.bind) ? selCmpt.bind.legend : 'click'; - const stream = vega.isString(evt) ? vega.parseSelector(evt, 'view') : vega.array(evt); - selCmpt.bind = { - legend: { - merge: stream - } - }; - }, - topLevelSignals: (model, selCmpt, signals) => { - const selName = selCmpt.name; - const stream = isLegendStreamBinding(selCmpt.bind) && selCmpt.bind.legend; - const markName = name => s => { - const ds = duplicate(s); - ds.markname = name; - return ds; - }; - for (const proj of selCmpt.project.items) { - if (!proj.hasLegend) continue; - const prefix = `${varName(proj.field)}_legend`; - const sgName = `${selName}_${prefix}`; - const hasSignal = signals.filter(s => s.name === sgName); - if (hasSignal.length === 0) { - const events = stream.merge.map(markName(`${prefix}_symbols`)).concat(stream.merge.map(markName(`${prefix}_labels`))).concat(stream.merge.map(markName(`${prefix}_entries`))); - signals.unshift({ - name: sgName, - ...(!selCmpt.init ? { - value: null - } : {}), - on: [ - // Legend entries do not store values, so we need to walk the scenegraph to the symbol datum. - { - events, - update: 'isDefined(datum.value) ? datum.value : item().items[0].items[0].datum.value', - force: true - }, { - events: stream.merge, - update: `!event.item || !datum ? null : ${sgName}`, - force: true - }] - }); - } - } - return signals; - }, - signals: (model, selCmpt, signals) => { - const name = selCmpt.name; - const proj = selCmpt.project; - const tuple = signals.find(s => s.name === name + TUPLE); - const fields = name + TUPLE_FIELDS; - const values = proj.items.filter(p => p.hasLegend).map(p => varName(`${name}_${varName(p.field)}_legend`)); - const valid = values.map(v => `${v} !== null`).join(' && '); - const update = `${valid} ? {fields: ${fields}, values: [${values.join(', ')}]} : null`; - if (selCmpt.events && values.length > 0) { - tuple.on.push({ - events: values.map(signal => ({ - signal - })), - update - }); - } else if (values.length > 0) { - tuple.update = update; - delete tuple.value; - delete tuple.on; - } - const toggle = signals.find(s => s.name === name + TOGGLE); - const events = isLegendStreamBinding(selCmpt.bind) && selCmpt.bind.legend; - if (toggle) { - if (!selCmpt.events) toggle.on[0].events = events;else toggle.on.push({ - ...toggle.on[0], - events - }); - } - return signals; - } - }; - function parseInteractiveLegend(model, channel, legendCmpt) { - const field = model.fieldDef(channel)?.field; - for (const selCmpt of vals(model.component.selection ?? {})) { - const proj = selCmpt.project.hasField[field] ?? selCmpt.project.hasChannel[channel]; - if (proj && legendBindings.defined(selCmpt)) { - const legendSelections = legendCmpt.get('selections') ?? []; - legendSelections.push(selCmpt.name); - legendCmpt.set('selections', legendSelections, false); - proj.hasLegend = true; - } - } - } - - const ANCHOR$1 = '_translate_anchor'; - const DELTA$1 = '_translate_delta'; - const translate = { - defined: selCmpt => { - return selCmpt.type === 'interval' && selCmpt.translate; - }, - signals: (model, selCmpt, signals) => { - const name = selCmpt.name; - const boundScales = scaleBindings.defined(selCmpt); - const anchor = name + ANCHOR$1; - const { - x, - y - } = selCmpt.project.hasChannel; - let events = vega.parseSelector(selCmpt.translate, 'scope'); - if (!boundScales) { - events = events.map(e => (e.between[0].markname = name + BRUSH, e)); - } - signals.push({ - name: anchor, - value: {}, - on: [{ - events: events.map(e => e.between[0]), - update: '{x: x(unit), y: y(unit)' + (x !== undefined ? `, extent_x: ${boundScales ? domain(model, X) : `slice(${x.signals.visual})`}` : '') + (y !== undefined ? `, extent_y: ${boundScales ? domain(model, Y) : `slice(${y.signals.visual})`}` : '') + '}' - }] - }, { - name: name + DELTA$1, - value: {}, - on: [{ - events, - update: `{x: ${anchor}.x - x(unit), y: ${anchor}.y - y(unit)}` - }] - }); - if (x !== undefined) { - onDelta$1(model, selCmpt, x, 'width', signals); - } - if (y !== undefined) { - onDelta$1(model, selCmpt, y, 'height', signals); - } - return signals; - } - }; - function onDelta$1(model, selCmpt, proj, size, signals) { - const name = selCmpt.name; - const anchor = name + ANCHOR$1; - const delta = name + DELTA$1; - const channel = proj.channel; - const boundScales = scaleBindings.defined(selCmpt); - const signal = signals.filter(s => s.name === proj.signals[boundScales ? 'data' : 'visual'])[0]; - const sizeSg = model.getSizeSignalRef(size).signal; - const scaleCmpt = model.getScaleComponent(channel); - const scaleType = scaleCmpt && scaleCmpt.get('type'); - const reversed = scaleCmpt && scaleCmpt.get('reverse'); // scale parsing sets this flag for fieldDef.sort - const sign = !boundScales ? '' : channel === X ? reversed ? '' : '-' : reversed ? '-' : ''; - const extent = `${anchor}.extent_${channel}`; - const offset = `${sign}${delta}.${channel} / ${boundScales ? `${sizeSg}` : `span(${extent})`}`; - const panFn = !boundScales || !scaleCmpt ? 'panLinear' : scaleType === 'log' ? 'panLog' : scaleType === 'symlog' ? 'panSymlog' : scaleType === 'pow' ? 'panPow' : 'panLinear'; - const arg = !boundScales ? '' : scaleType === 'pow' ? `, ${scaleCmpt.get('exponent') ?? 1}` : scaleType === 'symlog' ? `, ${scaleCmpt.get('constant') ?? 1}` : ''; - const update = `${panFn}(${extent}, ${offset}${arg})`; - signal.on.push({ - events: { - signal: delta - }, - update: boundScales ? update : `clampRange(${update}, 0, ${sizeSg})` - }); - } - - const ANCHOR = '_zoom_anchor'; - const DELTA = '_zoom_delta'; - const zoom = { - defined: selCmpt => { - return selCmpt.type === 'interval' && selCmpt.zoom; - }, - signals: (model, selCmpt, signals) => { - const name = selCmpt.name; - const boundScales = scaleBindings.defined(selCmpt); - const delta = name + DELTA; - const { - x, - y - } = selCmpt.project.hasChannel; - const sx = vega.stringValue(model.scaleName(X)); - const sy = vega.stringValue(model.scaleName(Y)); - let events = vega.parseSelector(selCmpt.zoom, 'scope'); - if (!boundScales) { - events = events.map(e => (e.markname = name + BRUSH, e)); - } - signals.push({ - name: name + ANCHOR, - on: [{ - events, - update: !boundScales ? `{x: x(unit), y: y(unit)}` : '{' + [sx ? `x: invert(${sx}, x(unit))` : '', sy ? `y: invert(${sy}, y(unit))` : ''].filter(expr => expr).join(', ') + '}' - }] - }, { - name: delta, - on: [{ - events, - force: true, - update: 'pow(1.001, event.deltaY * pow(16, event.deltaMode))' - }] - }); - if (x !== undefined) { - onDelta(model, selCmpt, x, 'width', signals); - } - if (y !== undefined) { - onDelta(model, selCmpt, y, 'height', signals); - } - return signals; - } - }; - function onDelta(model, selCmpt, proj, size, signals) { - const name = selCmpt.name; - const channel = proj.channel; - const boundScales = scaleBindings.defined(selCmpt); - const signal = signals.filter(s => s.name === proj.signals[boundScales ? 'data' : 'visual'])[0]; - const sizeSg = model.getSizeSignalRef(size).signal; - const scaleCmpt = model.getScaleComponent(channel); - const scaleType = scaleCmpt && scaleCmpt.get('type'); - const base = boundScales ? domain(model, channel) : signal.name; - const delta = name + DELTA; - const anchor = `${name}${ANCHOR}.${channel}`; - const zoomFn = !boundScales || !scaleCmpt ? 'zoomLinear' : scaleType === 'log' ? 'zoomLog' : scaleType === 'symlog' ? 'zoomSymlog' : scaleType === 'pow' ? 'zoomPow' : 'zoomLinear'; - const arg = !boundScales ? '' : scaleType === 'pow' ? `, ${scaleCmpt.get('exponent') ?? 1}` : scaleType === 'symlog' ? `, ${scaleCmpt.get('constant') ?? 1}` : ''; - const update = `${zoomFn}(${base}, ${anchor}, ${delta}${arg})`; - signal.on.push({ - events: { - signal: delta - }, - update: boundScales ? update : `clampRange(${update}, 0, ${sizeSg})` - }); - } - - const STORE = '_store'; - const TUPLE = '_tuple'; - const MODIFY = '_modify'; - const VL_SELECTION_RESOLVE = 'vlSelectionResolve'; - // Order matters for parsing and assembly. - const selectionCompilers = [point$1, interval, project, toggle, - // Bindings may disable direct manipulation. - inputBindings, scaleBindings, legendBindings, clear, translate, zoom, nearest]; - function getFacetModel(model) { - let parent = model.parent; - while (parent) { - if (isFacetModel(parent)) break; - parent = parent.parent; - } - return parent; - } - function unitName(model) { - let { - escape - } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { - escape: true - }; - let name = escape ? vega.stringValue(model.name) : model.name; - const facetModel = getFacetModel(model); - if (facetModel) { - const { - facet - } = facetModel; - for (const channel of FACET_CHANNELS) { - if (facet[channel]) { - name += ` + '__facet_${channel}_' + (facet[${vega.stringValue(facetModel.vgField(channel))}])`; - } - } - } - return name; - } - function requiresSelectionId(model) { - return vals(model.component.selection ?? {}).reduce((identifier, selCmpt) => { - return identifier || selCmpt.project.hasSelectionId; - }, false); - } - - // Binding a point selection to query widgets or legends disables default direct manipulation interaction. - // A user can choose to re-enable it by explicitly specifying triggering input events. - function disableDirectManipulation(selCmpt, selDef) { - if (vega.isString(selDef.select) || !selDef.select.on) delete selCmpt.events; - if (vega.isString(selDef.select) || !selDef.select.clear) delete selCmpt.clear; - if (vega.isString(selDef.select) || !selDef.select.toggle) delete selCmpt.toggle; - } - - function getName(node) { - const name = []; - if (node.type === 'Identifier') { - return [node.name]; - } - if (node.type === 'Literal') { - return [node.value]; - } - if (node.type === 'MemberExpression') { - name.push(...getName(node.object)); - name.push(...getName(node.property)); - } - return name; - } - function startsWithDatum(node) { - if (node.object.type === 'MemberExpression') { - return startsWithDatum(node.object); - } - return node.object.name === 'datum'; - } - function getDependentFields(expression) { - const ast = vega.parseExpression(expression); - const dependents = new Set(); - // visit is missing in types https://github.com/vega/vega/issues/3298 - ast.visit(node => { - if (node.type === 'MemberExpression' && startsWithDatum(node)) { - dependents.add(getName(node).slice(1).join('.')); - } - }); - return dependents; - } - - class FilterNode extends DataFlowNode { - clone() { - return new FilterNode(null, this.model, duplicate(this.filter)); - } - constructor(parent, model, filter) { - super(parent); - - // TODO: refactor this to not take a node and - // then add a static function makeFromOperand and make the constructor take only an expression - this.model = model; - this.filter = filter; - this.expr = expression(this.model, this.filter, this); - this._dependentFields = getDependentFields(this.expr); - } - dependentFields() { - return this._dependentFields; - } - producedFields() { - return new Set(); // filter does not produce any new fields - } - assemble() { - return { - type: 'filter', - expr: this.expr - }; - } - hash() { - return `Filter ${this.expr}`; - } - } - - function parseUnitSelection(model, selDefs) { - const selCmpts = {}; - const selectionConfig = model.config.selection; - if (!selDefs || !selDefs.length) return selCmpts; - for (const def of selDefs) { - const name = varName(def.name); - const selDef = def.select; - const type = vega.isString(selDef) ? selDef : selDef.type; - const defaults = vega.isObject(selDef) ? duplicate(selDef) : { - type - }; - - // Set default values from config if a property hasn't been specified, - // or if it is true. E.g., "translate": true should use the default - // event handlers for translate. However, true may be a valid value for - // a property (e.g., "nearest": true). - const cfg = selectionConfig[type]; - for (const key in cfg) { - // Project transform applies its defaults. - if (key === 'fields' || key === 'encodings') { - continue; - } - if (key === 'mark') { - defaults[key] = { - ...cfg[key], - ...defaults[key] - }; - } - if (defaults[key] === undefined || defaults[key] === true) { - defaults[key] = duplicate(cfg[key] ?? defaults[key]); - } - } - const selCmpt = selCmpts[name] = { - ...defaults, - name, - type, - init: def.value, - bind: def.bind, - events: vega.isString(defaults.on) ? vega.parseSelector(defaults.on, 'scope') : vega.array(duplicate(defaults.on)) - }; - const def_ = duplicate(def); // defensive copy to prevent compilers from causing side effects - for (const c of selectionCompilers) { - if (c.defined(selCmpt) && c.parse) { - c.parse(model, selCmpt, def_); - } - } - } - return selCmpts; - } - function parseSelectionPredicate(model, pred, dfnode) { - let datum = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'datum'; - const name = vega.isString(pred) ? pred : pred.param; - const vname = varName(name); - const store = vega.stringValue(vname + STORE); - let selCmpt; - try { - selCmpt = model.getSelectionComponent(vname, name); - } catch (e) { - // If a selection isn't found, treat as a variable parameter and coerce to boolean. - return `!!${vname}`; - } - if (selCmpt.project.timeUnit) { - const child = dfnode ?? model.component.data.raw; - const tunode = selCmpt.project.timeUnit.clone(); - if (child.parent) { - tunode.insertAsParentOf(child); - } else { - child.parent = tunode; - } - } - const fn = selCmpt.project.hasSelectionId ? 'vlSelectionIdTest(' : 'vlSelectionTest('; - const resolve = selCmpt.resolve === 'global' ? ')' : `, ${vega.stringValue(selCmpt.resolve)})`; - const test = `${fn}${store}, ${datum}${resolve}`; - const length = `length(data(${store}))`; - return pred.empty === false ? `${length} && ${test}` : `!${length} || ${test}`; - } - function parseSelectionExtent(model, name, extent) { - const vname = varName(name); - const encoding = extent['encoding']; - let field = extent['field']; - let selCmpt; - try { - selCmpt = model.getSelectionComponent(vname, name); - } catch (e) { - // If a selection isn't found, treat it as a variable parameter. - return vname; - } - if (!encoding && !field) { - field = selCmpt.project.items[0].field; - if (selCmpt.project.items.length > 1) { - warn('A "field" or "encoding" must be specified when using a selection as a scale domain. ' + `Using "field": ${vega.stringValue(field)}.`); - } - } else if (encoding && !field) { - const encodings = selCmpt.project.items.filter(p => p.channel === encoding); - if (!encodings.length || encodings.length > 1) { - field = selCmpt.project.items[0].field; - warn((!encodings.length ? 'No ' : 'Multiple ') + `matching ${vega.stringValue(encoding)} encoding found for selection ${vega.stringValue(extent.param)}. ` + `Using "field": ${vega.stringValue(field)}.`); - } else { - field = encodings[0].field; - } - } - return `${selCmpt.name}[${vega.stringValue(replacePathInField(field))}]`; - } - function materializeSelections(model, main) { - for (const [selection, selCmpt] of entries$1(model.component.selection ?? {})) { - const lookupName = model.getName(`lookup_${selection}`); - model.component.data.outputNodes[lookupName] = selCmpt.materialized = new OutputNode(new FilterNode(main, model, { - param: selection - }), lookupName, DataSourceType.Lookup, model.component.data.outputNodeRefCounts); - } - } - - /** - * Converts a predicate into an expression. - */ - // model is only used for selection filters. - function expression(model, filterOp, node) { - return logicalExpr(filterOp, predicate => { - if (vega.isString(predicate)) { - return predicate; - } else if (isSelectionPredicate(predicate)) { - return parseSelectionPredicate(model, predicate, node); - } else { - // Filter Object - return fieldFilterExpression(predicate); - } - }); - } - - function assembleTitle(title, config) { - if (!title) { - return undefined; - } - if (vega.isArray(title) && !isText(title)) { - return title.map(fieldDef => defaultTitle(fieldDef, config)).join(', '); - } - return title; - } - function setAxisEncode(axis, part, vgProp, vgRef) { - axis.encode ??= {}; - axis.encode[part] ??= {}; - axis.encode[part].update ??= {}; - // TODO: remove as any after https://github.com/prisma/nexus-prisma/issues/291 - axis.encode[part].update[vgProp] = vgRef; - } - function assembleAxis(axisCmpt, kind, config) { - let opt = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : { - header: false - }; - const { - disable, - orient, - scale, - labelExpr, - title, - zindex, - ...axis - } = axisCmpt.combine(); - if (disable) { - return undefined; - } - for (const prop in axis) { - const propType = AXIS_PROPERTY_TYPE[prop]; - const propValue = axis[prop]; - if (propType && propType !== kind && propType !== 'both') { - // Remove properties that are not valid for this kind of axis - delete axis[prop]; - } else if (isConditionalAxisValue(propValue)) { - // deal with conditional axis value - - const { - condition, - ...valueOrSignalRef - } = propValue; - const conditions = vega.array(condition); - const propIndex = CONDITIONAL_AXIS_PROP_INDEX[prop]; - if (propIndex) { - const { - vgProp, - part - } = propIndex; - // If there is a corresponding Vega property for the channel, - // use Vega's custom axis encoding and delete the original axis property to avoid conflicts - - const vgRef = [...conditions.map(c => { - const { - test, - ...valueOrSignalCRef - } = c; - return { - test: expression(null, test), - ...valueOrSignalCRef - }; - }), valueOrSignalRef]; - setAxisEncode(axis, part, vgProp, vgRef); - delete axis[prop]; - } else if (propIndex === null) { - // If propIndex is null, this means we support conditional axis property by converting the condition to signal instead. - const signalRef = { - signal: conditions.map(c => { - const { - test, - ...valueOrSignalCRef - } = c; - return `${expression(null, test)} ? ${exprFromValueRefOrSignalRef(valueOrSignalCRef)} : `; - }).join('') + exprFromValueRefOrSignalRef(valueOrSignalRef) - }; - axis[prop] = signalRef; - } - } else if (isSignalRef(propValue)) { - const propIndex = CONDITIONAL_AXIS_PROP_INDEX[prop]; - if (propIndex) { - const { - vgProp, - part - } = propIndex; - setAxisEncode(axis, part, vgProp, propValue); - delete axis[prop]; - } // else do nothing since the property already supports signal - } - - // Do not pass labelAlign/Baseline = null to Vega since it won't pass the schema - // Note that we need to use null so the default labelAlign is preserved. - if (contains(['labelAlign', 'labelBaseline'], prop) && axis[prop] === null) { - delete axis[prop]; - } - } - if (kind === 'grid') { - if (!axis.grid) { - return undefined; - } - - // Remove unnecessary encode block - if (axis.encode) { - // Only need to keep encode block for grid - const { - grid - } = axis.encode; - axis.encode = { - ...(grid ? { - grid - } : {}) - }; - if (isEmpty(axis.encode)) { - delete axis.encode; - } - } - return { - scale, - orient, - ...axis, - domain: false, - labels: false, - aria: false, - // always hide grid axis - - // Always set min/maxExtent to 0 to ensure that `config.axis*.minExtent` and `config.axis*.maxExtent` - // would not affect gridAxis - maxExtent: 0, - minExtent: 0, - ticks: false, - zindex: getFirstDefined(zindex, 0) // put grid behind marks by default - }; - } else { - // kind === 'main' - - if (!opt.header && axisCmpt.mainExtracted) { - // if mainExtracted has been extracted to a separate facet - return undefined; - } - if (labelExpr !== undefined) { - let expr = labelExpr; - if (axis.encode?.labels?.update && isSignalRef(axis.encode.labels.update.text)) { - expr = replaceAll(labelExpr, 'datum.label', axis.encode.labels.update.text.signal); - } - setAxisEncode(axis, 'labels', 'text', { - signal: expr - }); - } - if (axis.labelAlign === null) { - delete axis.labelAlign; - } - - // Remove unnecessary encode block - if (axis.encode) { - for (const part of AXIS_PARTS) { - if (!axisCmpt.hasAxisPart(part)) { - delete axis.encode[part]; - } - } - if (isEmpty(axis.encode)) { - delete axis.encode; - } - } - const titleString = assembleTitle(title, config); - return { - scale, - orient, - grid: false, - ...(titleString ? { - title: titleString - } : {}), - ...axis, - ...(config.aria === false ? { - aria: false - } : {}), - zindex: getFirstDefined(zindex, 0) // put axis line above marks by default - }; - } - } - - /** - * Add axis signals so grid line works correctly - * (Fix https://github.com/vega/vega-lite/issues/4226) - */ - function assembleAxisSignals(model) { - const { - axes - } = model.component; - const signals = []; - for (const channel of POSITION_SCALE_CHANNELS) { - if (axes[channel]) { - for (const axis of axes[channel]) { - if (!axis.get('disable') && !axis.get('gridScale')) { - // If there is x-axis but no y-scale for gridScale, need to set height/width so x-axis can draw the grid with the right height. Same for y-axis and width. - - const sizeType = channel === 'x' ? 'height' : 'width'; - const update = model.getSizeSignalRef(sizeType).signal; - if (sizeType !== update) { - signals.push({ - name: sizeType, - update - }); - } - } - } - } - } - return signals; - } - function assembleAxes(axisComponents, config) { - const { - x = [], - y = [] - } = axisComponents; - return [...x.map(a => assembleAxis(a, 'grid', config)), ...y.map(a => assembleAxis(a, 'grid', config)), ...x.map(a => assembleAxis(a, 'main', config)), ...y.map(a => assembleAxis(a, 'main', config))].filter(a => a); // filter undefined - } - - function getAxisConfigFromConfigTypes(configTypes, config, channel, orient) { - // TODO: add special casing to add conditional value based on orient signal - return Object.assign.apply(null, [{}, ...configTypes.map(configType => { - if (configType === 'axisOrient') { - const orient1 = channel === 'x' ? 'bottom' : 'left'; - const orientConfig1 = config[channel === 'x' ? 'axisBottom' : 'axisLeft'] || {}; - const orientConfig2 = config[channel === 'x' ? 'axisTop' : 'axisRight'] || {}; - const props = new Set([...keys(orientConfig1), ...keys(orientConfig2)]); - const conditionalOrientAxisConfig = {}; - for (const prop of props.values()) { - conditionalOrientAxisConfig[prop] = { - // orient is surely signal in this case - signal: `${orient['signal']} === "${orient1}" ? ${signalOrStringValue(orientConfig1[prop])} : ${signalOrStringValue(orientConfig2[prop])}` - }; - } - return conditionalOrientAxisConfig; - } - return config[configType]; - })]); - } - function getAxisConfigs(channel, scaleType, orient, config) { - const typeBasedConfigTypes = scaleType === 'band' ? ['axisDiscrete', 'axisBand'] : scaleType === 'point' ? ['axisDiscrete', 'axisPoint'] : isQuantitative(scaleType) ? ['axisQuantitative'] : scaleType === 'time' || scaleType === 'utc' ? ['axisTemporal'] : []; - const axisChannel = channel === 'x' ? 'axisX' : 'axisY'; - const axisOrient = isSignalRef(orient) ? 'axisOrient' : `axis${titleCase(orient)}`; // axisTop, axisBottom, ... - - const vlOnlyConfigTypes = [ - // technically Vega does have axisBand, but if we make another separation here, - // it will further introduce complexity in the code - ...typeBasedConfigTypes, ...typeBasedConfigTypes.map(c => axisChannel + c.substr(4))]; - const vgConfigTypes = ['axis', axisOrient, axisChannel]; - return { - vlOnlyAxisConfig: getAxisConfigFromConfigTypes(vlOnlyConfigTypes, config, channel, orient), - vgAxisConfig: getAxisConfigFromConfigTypes(vgConfigTypes, config, channel, orient), - axisConfigStyle: getAxisConfigStyle([...vgConfigTypes, ...vlOnlyConfigTypes], config) - }; - } - function getAxisConfigStyle(axisConfigTypes, config) { - const toMerge = [{}]; - for (const configType of axisConfigTypes) { - // TODO: add special casing to add conditional value based on orient signal - let style = config[configType]?.style; - if (style) { - style = vega.array(style); - for (const s of style) { - toMerge.push(config.style[s]); - } - } - } - return Object.assign.apply(null, toMerge); - } - function getAxisConfig(property, styleConfigIndex, style) { - let axisConfigs = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; - const styleConfig = getStyleConfig(property, style, styleConfigIndex); - if (styleConfig !== undefined) { - return { - configFrom: 'style', - configValue: styleConfig - }; - } - for (const configFrom of ['vlOnlyAxisConfig', 'vgAxisConfig', 'axisConfigStyle']) { - if (axisConfigs[configFrom]?.[property] !== undefined) { - return { - configFrom, - configValue: axisConfigs[configFrom][property] - }; - } - } - return {}; - } - - const axisRules = { - scale: _ref => { - let { - model, - channel - } = _ref; - return model.scaleName(channel); - }, - format: _ref2 => { - let { - format - } = _ref2; - return format; - }, - // we already calculate this in parse - - formatType: _ref3 => { - let { - formatType - } = _ref3; - return formatType; - }, - // we already calculate this in parse - - grid: _ref4 => { - let { - fieldOrDatumDef, - axis, - scaleType - } = _ref4; - return axis.grid ?? defaultGrid(scaleType, fieldOrDatumDef); - }, - gridScale: _ref5 => { - let { - model, - channel - } = _ref5; - return gridScale(model, channel); - }, - labelAlign: _ref6 => { - let { - axis, - labelAngle, - orient, - channel - } = _ref6; - return axis.labelAlign || defaultLabelAlign(labelAngle, orient, channel); - }, - labelAngle: _ref7 => { - let { - labelAngle - } = _ref7; - return labelAngle; - }, - // we already calculate this in parse - - labelBaseline: _ref8 => { - let { - axis, - labelAngle, - orient, - channel - } = _ref8; - return axis.labelBaseline || defaultLabelBaseline(labelAngle, orient, channel); - }, - labelFlush: _ref9 => { - let { - axis, - fieldOrDatumDef, - channel - } = _ref9; - return axis.labelFlush ?? defaultLabelFlush(fieldOrDatumDef.type, channel); - }, - labelOverlap: _ref10 => { - let { - axis, - fieldOrDatumDef, - scaleType - } = _ref10; - return axis.labelOverlap ?? defaultLabelOverlap$1(fieldOrDatumDef.type, scaleType, isFieldDef(fieldOrDatumDef) && !!fieldOrDatumDef.timeUnit, isFieldDef(fieldOrDatumDef) ? fieldOrDatumDef.sort : undefined); - }, - // we already calculate orient in parse - orient: _ref11 => { - let { - orient - } = _ref11; - return orient; - }, - // Need to cast until Vega supports signal - - tickCount: _ref12 => { - let { - channel, - model, - axis, - fieldOrDatumDef, - scaleType - } = _ref12; - const sizeType = channel === 'x' ? 'width' : channel === 'y' ? 'height' : undefined; - const size = sizeType ? model.getSizeSignalRef(sizeType) : undefined; - return axis.tickCount ?? defaultTickCount({ - fieldOrDatumDef, - scaleType, - size, - values: axis.values - }); - }, - tickMinStep: defaultTickMinStep, - title: _ref13 => { - let { - axis, - model, - channel - } = _ref13; - if (axis.title !== undefined) { - return axis.title; - } - const fieldDefTitle = getFieldDefTitle(model, channel); - if (fieldDefTitle !== undefined) { - return fieldDefTitle; - } - const fieldDef = model.typedFieldDef(channel); - const channel2 = channel === 'x' ? 'x2' : 'y2'; - const fieldDef2 = model.fieldDef(channel2); - - // If title not specified, store base parts of fieldDef (and fieldDef2 if exists) - return mergeTitleFieldDefs(fieldDef ? [toFieldDefBase(fieldDef)] : [], isFieldDef(fieldDef2) ? [toFieldDefBase(fieldDef2)] : []); - }, - values: _ref14 => { - let { - axis, - fieldOrDatumDef - } = _ref14; - return values$1(axis, fieldOrDatumDef); - }, - zindex: _ref15 => { - let { - axis, - fieldOrDatumDef, - mark - } = _ref15; - return axis.zindex ?? defaultZindex(mark, fieldOrDatumDef); - } - }; - - // TODO: we need to refactor this method after we take care of config refactoring - /** - * Default rules for whether to show a grid should be shown for a channel. - * If `grid` is unspecified, the default value is `true` for ordinal scales that are not binned - */ - - function defaultGrid(scaleType, fieldDef) { - return !hasDiscreteDomain(scaleType) && isFieldDef(fieldDef) && !isBinning(fieldDef?.bin) && !isBinned(fieldDef?.bin); - } - function gridScale(model, channel) { - const gridChannel = channel === 'x' ? 'y' : 'x'; - if (model.getScaleComponent(gridChannel)) { - return model.scaleName(gridChannel); - } - return undefined; - } - function getLabelAngle(fieldOrDatumDef, axis, channel, styleConfig, axisConfigs) { - const labelAngle = axis?.labelAngle; - // try axis value - if (labelAngle !== undefined) { - return isSignalRef(labelAngle) ? labelAngle : normalizeAngle(labelAngle); - } else { - // try axis config value - const { - configValue: angle - } = getAxisConfig('labelAngle', styleConfig, axis?.style, axisConfigs); - if (angle !== undefined) { - return normalizeAngle(angle); - } else { - // get default value - if (channel === X && contains([NOMINAL, ORDINAL], fieldOrDatumDef.type) && !(isFieldDef(fieldOrDatumDef) && fieldOrDatumDef.timeUnit)) { - return 270; - } - // no default - return undefined; - } - } - } - function normalizeAngleExpr(angle) { - return `(((${angle.signal} % 360) + 360) % 360)`; - } - function defaultLabelBaseline(angle, orient, channel, alwaysIncludeMiddle) { - if (angle !== undefined) { - if (channel === 'x') { - if (isSignalRef(angle)) { - const a = normalizeAngleExpr(angle); - const orientIsTop = isSignalRef(orient) ? `(${orient.signal} === "top")` : orient === 'top'; - return { - signal: `(45 < ${a} && ${a} < 135) || (225 < ${a} && ${a} < 315) ? "middle" :` + `(${a} <= 45 || 315 <= ${a}) === ${orientIsTop} ? "bottom" : "top"` - }; - } - if (45 < angle && angle < 135 || 225 < angle && angle < 315) { - return 'middle'; - } - if (isSignalRef(orient)) { - const op = angle <= 45 || 315 <= angle ? '===' : '!=='; - return { - signal: `${orient.signal} ${op} "top" ? "bottom" : "top"` - }; - } - return (angle <= 45 || 315 <= angle) === (orient === 'top') ? 'bottom' : 'top'; - } else { - if (isSignalRef(angle)) { - const a = normalizeAngleExpr(angle); - const orientIsLeft = isSignalRef(orient) ? `(${orient.signal} === "left")` : orient === 'left'; - const middle = alwaysIncludeMiddle ? '"middle"' : 'null'; - return { - signal: `${a} <= 45 || 315 <= ${a} || (135 <= ${a} && ${a} <= 225) ? ${middle} : (45 <= ${a} && ${a} <= 135) === ${orientIsLeft} ? "top" : "bottom"` - }; - } - if (angle <= 45 || 315 <= angle || 135 <= angle && angle <= 225) { - return alwaysIncludeMiddle ? 'middle' : null; - } - if (isSignalRef(orient)) { - const op = 45 <= angle && angle <= 135 ? '===' : '!=='; - return { - signal: `${orient.signal} ${op} "left" ? "top" : "bottom"` - }; - } - return (45 <= angle && angle <= 135) === (orient === 'left') ? 'top' : 'bottom'; - } - } - return undefined; - } - function defaultLabelAlign(angle, orient, channel) { - if (angle === undefined) { - return undefined; - } - const isX = channel === 'x'; - const startAngle = isX ? 0 : 90; - const mainOrient = isX ? 'bottom' : 'left'; - if (isSignalRef(angle)) { - const a = normalizeAngleExpr(angle); - const orientIsMain = isSignalRef(orient) ? `(${orient.signal} === "${mainOrient}")` : orient === mainOrient; - return { - signal: `(${startAngle ? `(${a} + 90)` : a} % 180 === 0) ? ${isX ? null : '"center"'} :` + `(${startAngle} < ${a} && ${a} < ${180 + startAngle}) === ${orientIsMain} ? "left" : "right"` - }; - } - if ((angle + startAngle) % 180 === 0) { - // For bottom, use default label align so label flush still works - return isX ? null : 'center'; - } - if (isSignalRef(orient)) { - const op = startAngle < angle && angle < 180 + startAngle ? '===' : '!=='; - const orientIsMain = `${orient.signal} ${op} "${mainOrient}"`; - return { - signal: `${orientIsMain} ? "left" : "right"` - }; - } - if ((startAngle < angle && angle < 180 + startAngle) === (orient === mainOrient)) { - return 'left'; - } - return 'right'; - } - function defaultLabelFlush(type, channel) { - if (channel === 'x' && contains(['quantitative', 'temporal'], type)) { - return true; - } - return undefined; - } - function defaultLabelOverlap$1(type, scaleType, hasTimeUnit, sort) { - // do not prevent overlap for nominal data because there is no way to infer what the missing labels are - if (hasTimeUnit && !vega.isObject(sort) || type !== 'nominal' && type !== 'ordinal') { - if (scaleType === 'log' || scaleType === 'symlog') { - return 'greedy'; - } - return true; - } - return undefined; - } - function defaultOrient(channel) { - return channel === 'x' ? 'bottom' : 'left'; - } - function defaultTickCount(_ref16) { - let { - fieldOrDatumDef, - scaleType, - size, - values: vals - } = _ref16; - if (!vals && !hasDiscreteDomain(scaleType) && scaleType !== 'log') { - if (isFieldDef(fieldOrDatumDef)) { - if (isBinning(fieldOrDatumDef.bin)) { - // for binned data, we don't want more ticks than maxbins - return { - signal: `ceil(${size.signal}/10)` - }; - } - if (fieldOrDatumDef.timeUnit && contains(['month', 'hours', 'day', 'quarter'], normalizeTimeUnit(fieldOrDatumDef.timeUnit)?.unit)) { - return undefined; - } - } - return { - signal: `ceil(${size.signal}/40)` - }; - } - return undefined; - } - function defaultTickMinStep(_ref17) { - let { - format, - fieldOrDatumDef - } = _ref17; - if (format === 'd') { - return 1; - } - if (isFieldDef(fieldOrDatumDef)) { - const { - timeUnit - } = fieldOrDatumDef; - if (timeUnit) { - const signal = durationExpr(timeUnit); - if (signal) { - return { - signal - }; - } - } - } - return undefined; - } - function getFieldDefTitle(model, channel) { - const channel2 = channel === 'x' ? 'x2' : 'y2'; - const fieldDef = model.fieldDef(channel); - const fieldDef2 = model.fieldDef(channel2); - const title1 = fieldDef ? fieldDef.title : undefined; - const title2 = fieldDef2 ? fieldDef2.title : undefined; - if (title1 && title2) { - return mergeTitle(title1, title2); - } else if (title1) { - return title1; - } else if (title2) { - return title2; - } else if (title1 !== undefined) { - // falsy value to disable config - return title1; - } else if (title2 !== undefined) { - // falsy value to disable config - return title2; - } - return undefined; - } - function values$1(axis, fieldOrDatumDef) { - const vals = axis.values; - if (vega.isArray(vals)) { - return valueArray(fieldOrDatumDef, vals); - } else if (isSignalRef(vals)) { - return vals; - } - return undefined; - } - function defaultZindex(mark, fieldDef) { - if (mark === 'rect' && isDiscrete(fieldDef)) { - return 1; - } - return 0; - } - - class CalculateNode extends DataFlowNode { - clone() { - return new CalculateNode(null, duplicate(this.transform)); - } - constructor(parent, transform) { - super(parent); - this.transform = transform; - this._dependentFields = getDependentFields(this.transform.calculate); - } - static parseAllForSortIndex(parent, model) { - // get all the encoding with sort fields from model - model.forEachFieldDef((fieldDef, channel) => { - if (!isScaleFieldDef(fieldDef)) { - return; - } - if (isSortArray(fieldDef.sort)) { - const { - field, - timeUnit - } = fieldDef; - const sort = fieldDef.sort; - // generate `datum["a"] === val0 ? 0 : datum["a"] === val1 ? 1 : ... : n` via FieldEqualPredicate - const calculate = sort.map((sortValue, i) => { - return `${fieldFilterExpression({ - field, - timeUnit, - equal: sortValue - })} ? ${i} : `; - }).join('') + sort.length; - parent = new CalculateNode(parent, { - calculate, - as: sortArrayIndexField(fieldDef, channel, { - forAs: true - }) - }); - } - }); - return parent; - } - producedFields() { - return new Set([this.transform.as]); - } - dependentFields() { - return this._dependentFields; - } - assemble() { - return { - type: 'formula', - expr: this.transform.calculate, - as: this.transform.as - }; - } - hash() { - return `Calculate ${hash(this.transform)}`; - } - } - function sortArrayIndexField(fieldDef, channel, opt) { - return vgField(fieldDef, { - prefix: channel, - suffix: 'sort_index', - ...opt - }); - } - - /** - * Get header channel, which can be different from facet channel when orient is specified or when the facet channel is facet. - */ - function getHeaderChannel(channel, orient) { - if (contains(['top', 'bottom'], orient)) { - return 'column'; - } else if (contains(['left', 'right'], orient)) { - return 'row'; - } - return channel === 'row' ? 'row' : 'column'; - } - function getHeaderProperty(prop, header, config, channel) { - const headerSpecificConfig = channel === 'row' ? config.headerRow : channel === 'column' ? config.headerColumn : config.headerFacet; - return getFirstDefined((header || {})[prop], headerSpecificConfig[prop], config.header[prop]); - } - function getHeaderProperties(properties, header, config, channel) { - const props = {}; - for (const prop of properties) { - const value = getHeaderProperty(prop, header || {}, config, channel); - if (value !== undefined) { - props[prop] = value; - } - } - return props; - } - - /** - * Utility for generating row / column headers - */ - - const HEADER_CHANNELS = ['row', 'column']; - const HEADER_TYPES = ['header', 'footer']; - - /** - * A component that represents all header, footers and title of a Vega group with layout directive. - */ - - /** - * A component that represents one group of row/column-header/footer. - */ - - /** - * Utility for generating row / column headers - */ - - - // TODO: rename to assembleHeaderTitleGroup - function assembleTitleGroup(model, channel) { - const title = model.component.layoutHeaders[channel].title; - const config = model.config ? model.config : undefined; - const facetFieldDef = model.component.layoutHeaders[channel].facetFieldDef ? model.component.layoutHeaders[channel].facetFieldDef : undefined; - const { - titleAnchor, - titleAngle: ta, - titleOrient - } = getHeaderProperties(['titleAnchor', 'titleAngle', 'titleOrient'], facetFieldDef.header, config, channel); - const headerChannel = getHeaderChannel(channel, titleOrient); - const titleAngle = normalizeAngle(ta); - return { - name: `${channel}-title`, - type: 'group', - role: `${headerChannel}-title`, - title: { - text: title, - ...(channel === 'row' ? { - orient: 'left' - } : {}), - style: 'guide-title', - ...defaultHeaderGuideBaseline(titleAngle, headerChannel), - ...defaultHeaderGuideAlign(headerChannel, titleAngle, titleAnchor), - ...assembleHeaderProperties(config, facetFieldDef, channel, HEADER_TITLE_PROPERTIES, HEADER_TITLE_PROPERTIES_MAP) - } - }; - } - function defaultHeaderGuideAlign(headerChannel, angle) { - let anchor = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'middle'; - switch (anchor) { - case 'start': - return { - align: 'left' - }; - case 'end': - return { - align: 'right' - }; - } - const align = defaultLabelAlign(angle, headerChannel === 'row' ? 'left' : 'top', headerChannel === 'row' ? 'y' : 'x'); - return align ? { - align - } : {}; - } - function defaultHeaderGuideBaseline(angle, channel) { - const baseline = defaultLabelBaseline(angle, channel === 'row' ? 'left' : 'top', channel === 'row' ? 'y' : 'x', true); - return baseline ? { - baseline - } : {}; - } - function assembleHeaderGroups(model, channel) { - const layoutHeader = model.component.layoutHeaders[channel]; - const groups = []; - for (const headerType of HEADER_TYPES) { - if (layoutHeader[headerType]) { - for (const headerComponent of layoutHeader[headerType]) { - const group = assembleHeaderGroup(model, channel, headerType, layoutHeader, headerComponent); - if (group != null) { - groups.push(group); - } - } - } - } - return groups; - } - function getSort$1(facetFieldDef, channel) { - const { - sort - } = facetFieldDef; - if (isSortField(sort)) { - return { - field: vgField(sort, { - expr: 'datum' - }), - order: sort.order ?? 'ascending' - }; - } else if (vega.isArray(sort)) { - return { - field: sortArrayIndexField(facetFieldDef, channel, { - expr: 'datum' - }), - order: 'ascending' - }; - } else { - return { - field: vgField(facetFieldDef, { - expr: 'datum' - }), - order: sort ?? 'ascending' - }; - } - } - function assembleLabelTitle(facetFieldDef, channel, config) { - const { - format, - formatType, - labelAngle, - labelAnchor, - labelOrient, - labelExpr - } = getHeaderProperties(['format', 'formatType', 'labelAngle', 'labelAnchor', 'labelOrient', 'labelExpr'], facetFieldDef.header, config, channel); - const titleTextExpr = formatSignalRef({ - fieldOrDatumDef: facetFieldDef, - format, - formatType, - expr: 'parent', - config - }).signal; - const headerChannel = getHeaderChannel(channel, labelOrient); - return { - text: { - signal: labelExpr ? replaceAll(replaceAll(labelExpr, 'datum.label', titleTextExpr), 'datum.value', vgField(facetFieldDef, { - expr: 'parent' - })) : titleTextExpr - }, - ...(channel === 'row' ? { - orient: 'left' - } : {}), - style: 'guide-label', - frame: 'group', - ...defaultHeaderGuideBaseline(labelAngle, headerChannel), - ...defaultHeaderGuideAlign(headerChannel, labelAngle, labelAnchor), - ...assembleHeaderProperties(config, facetFieldDef, channel, HEADER_LABEL_PROPERTIES, HEADER_LABEL_PROPERTIES_MAP) - }; - } - function assembleHeaderGroup(model, channel, headerType, layoutHeader, headerComponent) { - if (headerComponent) { - let title = null; - const { - facetFieldDef - } = layoutHeader; - const config = model.config ? model.config : undefined; - if (facetFieldDef && headerComponent.labels) { - const { - labelOrient - } = getHeaderProperties(['labelOrient'], facetFieldDef.header, config, channel); - - // Include label title in the header if orient aligns with the channel - if (channel === 'row' && !contains(['top', 'bottom'], labelOrient) || channel === 'column' && !contains(['left', 'right'], labelOrient)) { - title = assembleLabelTitle(facetFieldDef, channel, config); - } - } - const isFacetWithoutRowCol = isFacetModel(model) && !isFacetMapping(model.facet); - const axes = headerComponent.axes; - const hasAxes = axes?.length > 0; - if (title || hasAxes) { - const sizeChannel = channel === 'row' ? 'height' : 'width'; - return { - name: model.getName(`${channel}_${headerType}`), - type: 'group', - role: `${channel}-${headerType}`, - ...(layoutHeader.facetFieldDef ? { - from: { - data: model.getName(`${channel}_domain`) - }, - sort: getSort$1(facetFieldDef, channel) - } : {}), - ...(hasAxes && isFacetWithoutRowCol ? { - from: { - data: model.getName(`facet_domain_${channel}`) - } - } : {}), - ...(title ? { - title - } : {}), - ...(headerComponent.sizeSignal ? { - encode: { - update: { - [sizeChannel]: headerComponent.sizeSignal - } - } - } : {}), - ...(hasAxes ? { - axes - } : {}) - }; - } - } - return null; - } - const LAYOUT_TITLE_BAND = { - column: { - start: 0, - end: 1 - }, - row: { - start: 1, - end: 0 - } - }; - function getLayoutTitleBand(titleAnchor, headerChannel) { - return LAYOUT_TITLE_BAND[headerChannel][titleAnchor]; - } - function assembleLayoutTitleBand(headerComponentIndex, config) { - const titleBand = {}; - for (const channel of FACET_CHANNELS) { - const headerComponent = headerComponentIndex[channel]; - if (headerComponent?.facetFieldDef) { - const { - titleAnchor, - titleOrient - } = getHeaderProperties(['titleAnchor', 'titleOrient'], headerComponent.facetFieldDef.header, config, channel); - const headerChannel = getHeaderChannel(channel, titleOrient); - const band = getLayoutTitleBand(titleAnchor, headerChannel); - if (band !== undefined) { - titleBand[headerChannel] = band; - } - } - } - return isEmpty(titleBand) ? undefined : titleBand; - } - function assembleHeaderProperties(config, facetFieldDef, channel, properties, propertiesMap) { - const props = {}; - for (const prop of properties) { - if (!propertiesMap[prop]) { - continue; - } - const value = getHeaderProperty(prop, facetFieldDef?.header, config, channel); - if (value !== undefined) { - props[propertiesMap[prop]] = value; - } - } - return props; - } - - function assembleLayoutSignals(model) { - return [...sizeSignals(model, 'width'), ...sizeSignals(model, 'height'), ...sizeSignals(model, 'childWidth'), ...sizeSignals(model, 'childHeight')]; - } - function sizeSignals(model, sizeType) { - const channel = sizeType === 'width' ? 'x' : 'y'; - const size = model.component.layoutSize.get(sizeType); - if (!size || size === 'merged') { - return []; - } - - // Read size signal name from name map, just in case it is the top-level size signal that got renamed. - const name = model.getSizeSignalRef(sizeType).signal; - if (size === 'step') { - const scaleComponent = model.getScaleComponent(channel); - if (scaleComponent) { - const type = scaleComponent.get('type'); - const range = scaleComponent.get('range'); - if (hasDiscreteDomain(type) && isVgRangeStep(range)) { - const scaleName = model.scaleName(channel); - if (isFacetModel(model.parent)) { - // If parent is facet and this is an independent scale, return only signal signal - // as the width/height will be calculated using the cardinality from - // facet's aggregate rather than reading from scale domain - const parentResolve = model.parent.component.resolve; - if (parentResolve.scale[channel] === 'independent') { - return [stepSignal(scaleName, range)]; - } - } - return [stepSignal(scaleName, range), { - name, - update: sizeExpr(scaleName, scaleComponent, `domain('${scaleName}').length`) - }]; - } - } - /* istanbul ignore next: Condition should not happen -- only for warning in development. */ - throw new Error('layout size is step although width/height is not step.'); - } else if (size == 'container') { - const isWidth = name.endsWith('width'); - const expr = isWidth ? 'containerSize()[0]' : 'containerSize()[1]'; - const defaultValue = getViewConfigContinuousSize(model.config.view, isWidth ? 'width' : 'height'); - const safeExpr = `isFinite(${expr}) ? ${expr} : ${defaultValue}`; - return [{ - name, - init: safeExpr, - on: [{ - update: safeExpr, - events: 'window:resize' - }] - }]; - } else { - return [{ - name, - value: size - }]; - } - } - function stepSignal(scaleName, range) { - const name = `${scaleName}_step`; - if (isSignalRef(range.step)) { - return { - name, - update: range.step.signal - }; - } else { - return { - name, - value: range.step - }; - } - } - function sizeExpr(scaleName, scaleComponent, cardinality) { - const type = scaleComponent.get('type'); - const padding = scaleComponent.get('padding'); - const paddingOuter = getFirstDefined(scaleComponent.get('paddingOuter'), padding); - let paddingInner = scaleComponent.get('paddingInner'); - paddingInner = type === 'band' ? - // only band has real paddingInner - paddingInner !== undefined ? paddingInner : padding : - // For point, as calculated in https://github.com/vega/vega-scale/blob/master/src/band.js#L128, - // it's equivalent to have paddingInner = 1 since there is only n-1 steps between n points. - 1; - return `bandspace(${cardinality}, ${signalOrStringValue(paddingInner)}, ${signalOrStringValue(paddingOuter)}) * ${scaleName}_step`; - } - - function getSizeTypeFromLayoutSizeType(layoutSizeType) { - return layoutSizeType === 'childWidth' ? 'width' : layoutSizeType === 'childHeight' ? 'height' : layoutSizeType; - } - - function guideEncodeEntry(encoding, model) { - return keys(encoding).reduce((encode, channel) => { - return { - ...encode, - ...wrapCondition({ - model, - channelDef: encoding[channel], - vgChannel: channel, - mainRefFn: def => signalOrValueRef(def.value), - invalidValueRef: undefined // guide encoding won't show invalid values for the scale - }) - }; - }, {}); - } - - function defaultScaleResolve(channel, model) { - if (isFacetModel(model)) { - return channel === 'theta' ? 'independent' : 'shared'; - } else if (isLayerModel(model)) { - return 'shared'; - } else if (isConcatModel(model)) { - return isXorY(channel) || channel === 'theta' || channel === 'radius' ? 'independent' : 'shared'; - } - /* istanbul ignore next: should never reach here. */ - throw new Error('invalid model type for resolve'); - } - function parseGuideResolve(resolve, channel) { - const channelScaleResolve = resolve.scale[channel]; - const guide = isXorY(channel) ? 'axis' : 'legend'; - if (channelScaleResolve === 'independent') { - if (resolve[guide][channel] === 'shared') { - warn(independentScaleMeansIndependentGuide(channel)); - } - return 'independent'; - } - return resolve[guide][channel] || 'shared'; - } - - const LEGEND_COMPONENT_PROPERTY_INDEX = { - ...COMMON_LEGEND_PROPERTY_INDEX, - disable: 1, - labelExpr: 1, - selections: 1, - // channel scales - opacity: 1, - shape: 1, - stroke: 1, - fill: 1, - size: 1, - strokeWidth: 1, - strokeDash: 1, - // encode - encode: 1 - }; - const LEGEND_COMPONENT_PROPERTIES = keys(LEGEND_COMPONENT_PROPERTY_INDEX); - class LegendComponent extends Split {} - - const legendEncodeRules = { - symbols, - gradient, - labels: labels$1, - entries - }; - function symbols(symbolsSpec, _ref) { - let { - fieldOrDatumDef, - model, - channel, - legendCmpt, - legendType - } = _ref; - if (legendType !== 'symbol') { - return undefined; - } - const { - markDef, - encoding, - config, - mark - } = model; - const filled = markDef.filled && mark !== 'trail'; - let out = { - ...applyMarkConfig({}, model, FILL_STROKE_CONFIG), - ...color(model, { - filled - }) - }; // FIXME: remove this when VgEncodeEntry is compatible with SymbolEncodeEntry - - const symbolOpacity = legendCmpt.get('symbolOpacity') ?? config.legend.symbolOpacity; - const symbolFillColor = legendCmpt.get('symbolFillColor') ?? config.legend.symbolFillColor; - const symbolStrokeColor = legendCmpt.get('symbolStrokeColor') ?? config.legend.symbolStrokeColor; - const opacity = symbolOpacity === undefined ? getMaxValue(encoding.opacity) ?? markDef.opacity : undefined; - if (out.fill) { - // for fill legend, we don't want any fill in symbol - if (channel === 'fill' || filled && channel === COLOR) { - delete out.fill; - } else { - if (out.fill['field']) { - // For others, set fill to some opaque value (or nothing if a color is already set) - if (symbolFillColor) { - delete out.fill; - } else { - out.fill = signalOrValueRef(config.legend.symbolBaseFillColor ?? 'black'); - out.fillOpacity = signalOrValueRef(opacity ?? 1); - } - } else if (vega.isArray(out.fill)) { - const fill = getFirstConditionValue(encoding.fill ?? encoding.color) ?? markDef.fill ?? (filled && markDef.color); - if (fill) { - out.fill = signalOrValueRef(fill); - } - } - } - } - if (out.stroke) { - if (channel === 'stroke' || !filled && channel === COLOR) { - delete out.stroke; - } else { - if (out.stroke['field'] || symbolStrokeColor) { - // For others, remove stroke field - delete out.stroke; - } else if (vega.isArray(out.stroke)) { - const stroke = getFirstDefined(getFirstConditionValue(encoding.stroke || encoding.color), markDef.stroke, filled ? markDef.color : undefined); - if (stroke) { - out.stroke = { - value: stroke - }; - } - } - } - } - if (channel !== OPACITY) { - const condition = isFieldDef(fieldOrDatumDef) && selectedCondition(model, legendCmpt, fieldOrDatumDef); - if (condition) { - out.opacity = [{ - test: condition, - ...signalOrValueRef(opacity ?? 1) - }, signalOrValueRef(config.legend.unselectedOpacity)]; - } else if (opacity) { - out.opacity = signalOrValueRef(opacity); - } - } - out = { - ...out, - ...symbolsSpec - }; - return isEmpty(out) ? undefined : out; - } - function gradient(gradientSpec, _ref2) { - let { - model, - legendType, - legendCmpt - } = _ref2; - if (legendType !== 'gradient') { - return undefined; - } - const { - config, - markDef, - encoding - } = model; - let out = {}; - const gradientOpacity = legendCmpt.get('gradientOpacity') ?? config.legend.gradientOpacity; - const opacity = gradientOpacity === undefined ? getMaxValue(encoding.opacity) || markDef.opacity : undefined; - if (opacity) { - // only apply opacity if it is neither zero or undefined - out.opacity = signalOrValueRef(opacity); - } - out = { - ...out, - ...gradientSpec - }; - return isEmpty(out) ? undefined : out; - } - function labels$1(specifiedlabelsSpec, _ref3) { - let { - fieldOrDatumDef, - model, - channel, - legendCmpt - } = _ref3; - const legend = model.legend(channel) || {}; - const config = model.config; - const condition = isFieldDef(fieldOrDatumDef) ? selectedCondition(model, legendCmpt, fieldOrDatumDef) : undefined; - const opacity = condition ? [{ - test: condition, - value: 1 - }, { - value: config.legend.unselectedOpacity - }] : undefined; - const { - format, - formatType - } = legend; - let text = undefined; - if (isCustomFormatType(formatType)) { - text = formatCustomType({ - fieldOrDatumDef, - field: 'datum.value', - format, - formatType, - config - }); - } else if (format === undefined && formatType === undefined && config.customFormatTypes) { - if (fieldOrDatumDef.type === 'quantitative' && config.numberFormatType) { - text = formatCustomType({ - fieldOrDatumDef, - field: 'datum.value', - format: config.numberFormat, - formatType: config.numberFormatType, - config - }); - } else if (fieldOrDatumDef.type === 'temporal' && config.timeFormatType && isFieldDef(fieldOrDatumDef) && fieldOrDatumDef.timeUnit === undefined) { - text = formatCustomType({ - fieldOrDatumDef, - field: 'datum.value', - format: config.timeFormat, - formatType: config.timeFormatType, - config - }); - } - } - const labelsSpec = { - ...(opacity ? { - opacity - } : {}), - ...(text ? { - text - } : {}), - ...specifiedlabelsSpec - }; - return isEmpty(labelsSpec) ? undefined : labelsSpec; - } - function entries(entriesSpec, _ref4) { - let { - legendCmpt - } = _ref4; - const selections = legendCmpt.get('selections'); - return selections?.length ? { - ...entriesSpec, - fill: { - value: 'transparent' - } - } : entriesSpec; - } - function getMaxValue(channelDef) { - return getConditionValue(channelDef, (v, conditionalDef) => Math.max(v, conditionalDef.value)); - } - function getFirstConditionValue(channelDef) { - return getConditionValue(channelDef, (v, conditionalDef) => { - return getFirstDefined(v, conditionalDef.value); - }); - } - function getConditionValue(channelDef, reducer) { - if (hasConditionalValueDef(channelDef)) { - return vega.array(channelDef.condition).reduce(reducer, channelDef.value); - } else if (isValueDef(channelDef)) { - return channelDef.value; - } - return undefined; - } - function selectedCondition(model, legendCmpt, fieldDef) { - const selections = legendCmpt.get('selections'); - if (!selections?.length) return undefined; - const field = vega.stringValue(fieldDef.field); - return selections.map(name => { - const store = vega.stringValue(varName(name) + STORE); - return `(!length(data(${store})) || (${name}[${field}] && indexof(${name}[${field}], datum.value) >= 0))`; - }).join(' || '); - } - - const legendRules = { - direction: _ref => { - let { - direction - } = _ref; - return direction; - }, - format: _ref2 => { - let { - fieldOrDatumDef, - legend, - config - } = _ref2; - const { - format, - formatType - } = legend; - return guideFormat(fieldOrDatumDef, fieldOrDatumDef.type, format, formatType, config, false); - }, - formatType: _ref3 => { - let { - legend, - fieldOrDatumDef, - scaleType - } = _ref3; - const { - formatType - } = legend; - return guideFormatType(formatType, fieldOrDatumDef, scaleType); - }, - gradientLength: params => { - const { - legend, - legendConfig - } = params; - return legend.gradientLength ?? legendConfig.gradientLength ?? defaultGradientLength(params); - }, - labelOverlap: _ref4 => { - let { - legend, - legendConfig, - scaleType - } = _ref4; - return legend.labelOverlap ?? legendConfig.labelOverlap ?? defaultLabelOverlap(scaleType); - }, - symbolType: _ref5 => { - let { - legend, - markDef, - channel, - encoding - } = _ref5; - return legend.symbolType ?? defaultSymbolType(markDef.type, channel, encoding.shape, markDef.shape); - }, - title: _ref6 => { - let { - fieldOrDatumDef, - config - } = _ref6; - return title(fieldOrDatumDef, config, { - allowDisabling: true - }); - }, - type: _ref7 => { - let { - legendType, - scaleType, - channel - } = _ref7; - if (isColorChannel(channel) && isContinuousToContinuous(scaleType)) { - if (legendType === 'gradient') { - return undefined; - } - } else if (legendType === 'symbol') { - return undefined; - } - return legendType; - }, - // depended by other property, let's define upfront - - values: _ref8 => { - let { - fieldOrDatumDef, - legend - } = _ref8; - return values(legend, fieldOrDatumDef); - } - }; - function values(legend, fieldOrDatumDef) { - const vals = legend.values; - if (vega.isArray(vals)) { - return valueArray(fieldOrDatumDef, vals); - } else if (isSignalRef(vals)) { - return vals; - } - return undefined; - } - function defaultSymbolType(mark, channel, shapeChannelDef, markShape) { - if (channel !== 'shape') { - // use the value from the shape encoding or the mark config if they exist - const shape = getFirstConditionValue(shapeChannelDef) ?? markShape; - if (shape) { - return shape; - } - } - switch (mark) { - case 'bar': - case 'rect': - case 'image': - case 'square': - return 'square'; - case 'line': - case 'trail': - case 'rule': - return 'stroke'; - case 'arc': - case 'point': - case 'circle': - case 'tick': - case 'geoshape': - case 'area': - case 'text': - return 'circle'; - } - } - function getLegendType(params) { - const { - legend - } = params; - return getFirstDefined(legend.type, defaultType$1(params)); - } - function defaultType$1(_ref9) { - let { - channel, - timeUnit, - scaleType - } = _ref9; - // Following the logic in https://github.com/vega/vega-parser/blob/master/src/parsers/legend.js - - if (isColorChannel(channel)) { - if (contains(['quarter', 'month', 'day'], timeUnit)) { - return 'symbol'; - } - if (isContinuousToContinuous(scaleType)) { - return 'gradient'; - } - } - return 'symbol'; - } - function getDirection(_ref10) { - let { - legendConfig, - legendType, - orient, - legend - } = _ref10; - return legend.direction ?? legendConfig[legendType ? 'gradientDirection' : 'symbolDirection'] ?? defaultDirection(orient, legendType); - } - function defaultDirection(orient, legendType) { - switch (orient) { - case 'top': - case 'bottom': - return 'horizontal'; - case 'left': - case 'right': - case 'none': - case undefined: - // undefined = "right" in Vega - return undefined; - // vertical is Vega's default - default: - // top-left / ... - // For inner legend, uses compact layout like Tableau - return legendType === 'gradient' ? 'horizontal' : undefined; - } - } - function defaultGradientLength(_ref11) { - let { - legendConfig, - model, - direction, - orient, - scaleType - } = _ref11; - const { - gradientHorizontalMaxLength, - gradientHorizontalMinLength, - gradientVerticalMaxLength, - gradientVerticalMinLength - } = legendConfig; - if (isContinuousToContinuous(scaleType)) { - if (direction === 'horizontal') { - if (orient === 'top' || orient === 'bottom') { - return gradientLengthSignal(model, 'width', gradientHorizontalMinLength, gradientHorizontalMaxLength); - } else { - return gradientHorizontalMinLength; - } - } else { - // vertical / undefined (Vega uses vertical by default) - return gradientLengthSignal(model, 'height', gradientVerticalMinLength, gradientVerticalMaxLength); - } - } - return undefined; - } - function gradientLengthSignal(model, sizeType, min, max) { - const sizeSignal = model.getSizeSignalRef(sizeType).signal; - return { - signal: `clamp(${sizeSignal}, ${min}, ${max})` - }; - } - function defaultLabelOverlap(scaleType) { - if (contains(['quantile', 'threshold', 'log', 'symlog'], scaleType)) { - return 'greedy'; - } - return undefined; - } - - function parseLegend(model) { - const legendComponent = isUnitModel(model) ? parseUnitLegend(model) : parseNonUnitLegend(model); - model.component.legends = legendComponent; - return legendComponent; - } - function parseUnitLegend(model) { - const { - encoding - } = model; - const legendComponent = {}; - for (const channel of [COLOR, ...LEGEND_SCALE_CHANNELS]) { - const def = getFieldOrDatumDef(encoding[channel]); - if (!def || !model.getScaleComponent(channel)) { - continue; - } - if (channel === SHAPE && isFieldDef(def) && def.type === GEOJSON) { - continue; - } - legendComponent[channel] = parseLegendForChannel(model, channel); - } - return legendComponent; - } - function getLegendDefWithScale(model, channel) { - const scale = model.scaleName(channel); - if (model.mark === 'trail') { - if (channel === 'color') { - // trail is a filled mark, but its default symbolType ("stroke") should use "stroke" - return { - stroke: scale - }; - } else if (channel === 'size') { - return { - strokeWidth: scale - }; - } - } - if (channel === 'color') { - return model.markDef.filled ? { - fill: scale - } : { - stroke: scale - }; - } - return { - [channel]: scale - }; - } - - // eslint-disable-next-line @typescript-eslint/ban-types - function isExplicit$1(value, property, legend, fieldDef) { - switch (property) { - case 'disable': - return legend !== undefined; - // if axis is specified or null/false, then its enable/disable state is explicit - case 'values': - // specified legend.values is already respected, but may get transformed. - return !!legend?.values; - case 'title': - // title can be explicit if fieldDef.title is set - if (property === 'title' && value === fieldDef?.title) { - return true; - } - } - // Otherwise, things are explicit if the returned value matches the specified property - return value === (legend || {})[property]; - } - function parseLegendForChannel(model, channel) { - let legend = model.legend(channel); - const { - markDef, - encoding, - config - } = model; - const legendConfig = config.legend; - const legendCmpt = new LegendComponent({}, getLegendDefWithScale(model, channel)); - parseInteractiveLegend(model, channel, legendCmpt); - const disable = legend !== undefined ? !legend : legendConfig.disable; - legendCmpt.set('disable', disable, legend !== undefined); - if (disable) { - return legendCmpt; - } - legend = legend || {}; - const scaleType = model.getScaleComponent(channel).get('type'); - const fieldOrDatumDef = getFieldOrDatumDef(encoding[channel]); - const timeUnit = isFieldDef(fieldOrDatumDef) ? normalizeTimeUnit(fieldOrDatumDef.timeUnit)?.unit : undefined; - const orient = legend.orient || config.legend.orient || 'right'; - const legendType = getLegendType({ - legend, - channel, - timeUnit, - scaleType - }); - const direction = getDirection({ - legend, - legendType, - orient, - legendConfig - }); - const ruleParams = { - legend, - channel, - model, - markDef, - encoding, - fieldOrDatumDef, - legendConfig, - config, - scaleType, - orient, - legendType, - direction - }; - for (const property of LEGEND_COMPONENT_PROPERTIES) { - if (legendType === 'gradient' && property.startsWith('symbol') || legendType === 'symbol' && property.startsWith('gradient')) { - continue; - } - const value = property in legendRules ? legendRules[property](ruleParams) : legend[property]; - if (value !== undefined) { - const explicit = isExplicit$1(value, property, legend, model.fieldDef(channel)); - if (explicit || config.legend[property] === undefined) { - legendCmpt.set(property, value, explicit); - } - } - } - const legendEncoding = legend?.encoding ?? {}; - const selections = legendCmpt.get('selections'); - const legendEncode = {}; - const legendEncodeParams = { - fieldOrDatumDef, - model, - channel, - legendCmpt, - legendType - }; - for (const part of ['labels', 'legend', 'title', 'symbols', 'gradient', 'entries']) { - const legendEncodingPart = guideEncodeEntry(legendEncoding[part] ?? {}, model); - const value = part in legendEncodeRules ? legendEncodeRules[part](legendEncodingPart, legendEncodeParams) // apply rule - : legendEncodingPart; // no rule -- just default values - - if (value !== undefined && !isEmpty(value)) { - legendEncode[part] = { - ...(selections?.length && isFieldDef(fieldOrDatumDef) ? { - name: `${varName(fieldOrDatumDef.field)}_legend_${part}` - } : {}), - ...(selections?.length ? { - interactive: !!selections - } : {}), - update: value - }; - } - } - if (!isEmpty(legendEncode)) { - legendCmpt.set('encode', legendEncode, !!legend?.encoding); - } - return legendCmpt; - } - function parseNonUnitLegend(model) { - const { - legends, - resolve - } = model.component; - for (const child of model.children) { - parseLegend(child); - for (const channel of keys(child.component.legends)) { - resolve.legend[channel] = parseGuideResolve(model.component.resolve, channel); - if (resolve.legend[channel] === 'shared') { - // If the resolve says shared (and has not been overridden) - // We will try to merge and see if there is a conflict - - legends[channel] = mergeLegendComponent(legends[channel], child.component.legends[channel]); - if (!legends[channel]) { - // If merge returns nothing, there is a conflict so we cannot make the legend shared. - // Thus, mark legend as independent and remove the legend component. - resolve.legend[channel] = 'independent'; - delete legends[channel]; - } - } - } - } - for (const channel of keys(legends)) { - for (const child of model.children) { - if (!child.component.legends[channel]) { - // skip if the child does not have a particular legend - continue; - } - if (resolve.legend[channel] === 'shared') { - // After merging shared legend, make sure to remove legend from child - delete child.component.legends[channel]; - } - } - } - return legends; - } - function mergeLegendComponent(mergedLegend, childLegend) { - if (!mergedLegend) { - return childLegend.clone(); - } - const mergedOrient = mergedLegend.getWithExplicit('orient'); - const childOrient = childLegend.getWithExplicit('orient'); - if (mergedOrient.explicit && childOrient.explicit && mergedOrient.value !== childOrient.value) { - // TODO: throw warning if resolve is explicit (We don't have info about explicit/implicit resolve yet.) - // Cannot merge due to inconsistent orient - return undefined; - } - let typeMerged = false; - // Otherwise, let's merge - for (const prop of LEGEND_COMPONENT_PROPERTIES) { - const mergedValueWithExplicit = mergeValuesWithExplicit(mergedLegend.getWithExplicit(prop), childLegend.getWithExplicit(prop), prop, 'legend', - // Tie breaker function - (v1, v2) => { - switch (prop) { - case 'symbolType': - return mergeSymbolType(v1, v2); - case 'title': - return mergeTitleComponent(v1, v2); - case 'type': - // There are only two types. If we have different types, then prefer symbol over gradient. - typeMerged = true; - return makeImplicit('symbol'); - } - return defaultTieBreaker(v1, v2, prop, 'legend'); - }); - mergedLegend.setWithExplicit(prop, mergedValueWithExplicit); - } - if (typeMerged) { - if (mergedLegend.implicit?.encode?.gradient) { - deleteNestedProperty(mergedLegend.implicit, ['encode', 'gradient']); - } - if (mergedLegend.explicit?.encode?.gradient) { - deleteNestedProperty(mergedLegend.explicit, ['encode', 'gradient']); - } - } - return mergedLegend; - } - function mergeSymbolType(st1, st2) { - if (st2.value === 'circle') { - // prefer "circle" over "stroke" - return st2; - } - return st1; - } - - function setLegendEncode(legend, part, vgProp, vgRef) { - legend.encode ??= {}; - legend.encode[part] ??= {}; - legend.encode[part].update ??= {}; - // TODO: remove as any after https://github.com/prisma/nexus-prisma/issues/291 - legend.encode[part].update[vgProp] = vgRef; - } - function assembleLegends(model) { - const legendComponentIndex = model.component.legends; - const legendByDomain = {}; - for (const channel of keys(legendComponentIndex)) { - const scaleComponent = model.getScaleComponent(channel); - const domainHash = stringify(scaleComponent.get('domains')); - if (legendByDomain[domainHash]) { - for (const mergedLegendComponent of legendByDomain[domainHash]) { - const merged = mergeLegendComponent(mergedLegendComponent, legendComponentIndex[channel]); - if (!merged) { - // If cannot merge, need to add this legend separately - legendByDomain[domainHash].push(legendComponentIndex[channel]); - } - } - } else { - legendByDomain[domainHash] = [legendComponentIndex[channel].clone()]; - } - } - const legends = vals(legendByDomain).flat().map(l => assembleLegend(l, model.config)).filter(l => l !== undefined); - return legends; - } - function assembleLegend(legendCmpt, config) { - const { - disable, - labelExpr, - selections, - ...legend - } = legendCmpt.combine(); - if (disable) { - return undefined; - } - if (config.aria === false && legend.aria == undefined) { - legend.aria = false; - } - if (legend.encode?.symbols) { - const out = legend.encode.symbols.update; - if (out.fill && out.fill['value'] !== 'transparent' && !out.stroke && !legend.stroke) { - // For non color channel's legend, we need to override symbol stroke config from Vega config if stroke channel is not used. - out.stroke = { - value: 'transparent' - }; - } - - // Remove properties that the legend is encoding. - for (const property of LEGEND_SCALE_CHANNELS) { - if (legend[property]) { - delete out[property]; - } - } - } - if (!legend.title) { - // title schema doesn't include null, '' - delete legend.title; - } - if (labelExpr !== undefined) { - let expr = labelExpr; - if (legend.encode?.labels?.update && isSignalRef(legend.encode.labels.update.text)) { - expr = replaceAll(labelExpr, 'datum.label', legend.encode.labels.update.text.signal); - } - setLegendEncode(legend, 'labels', 'text', { - signal: expr - }); - } - return legend; - } - - function assembleProjections(model) { - if (isLayerModel(model) || isConcatModel(model)) { - return assembleProjectionsForModelAndChildren(model); - } else { - return assembleProjectionForModel(model); - } - } - function assembleProjectionsForModelAndChildren(model) { - return model.children.reduce((projections, child) => { - return projections.concat(child.assembleProjections()); - }, assembleProjectionForModel(model)); - } - function assembleProjectionForModel(model) { - const component = model.component.projection; - if (!component || component.merged) { - return []; - } - const projection = component.combine(); - const { - name - } = projection; // we need to extract name so that it is always present in the output and pass TS type validation - - if (!component.data) { - // generate custom projection, no automatic fitting - return [{ - name, - // translate to center by default - translate: { - signal: '[width / 2, height / 2]' - }, - // parameters, overwrite default translate if specified - ...projection - }]; - } else { - // generate projection that uses extent fitting - const size = { - signal: `[${component.size.map(ref => ref.signal).join(', ')}]` - }; - const fits = component.data.reduce((sources, data) => { - const source = isSignalRef(data) ? data.signal : `data('${model.lookupDataSource(data)}')`; - if (!contains(sources, source)) { - // build a unique list of sources - sources.push(source); - } - return sources; - }, []); - if (fits.length <= 0) { - throw new Error("Projection's fit didn't find any data sources"); - } - return [{ - name, - size, - fit: { - signal: fits.length > 1 ? `[${fits.join(', ')}]` : fits[0] - }, - ...projection - }]; - } - } - - /** - * Any property of Projection can be in config - */ - - const PROJECTION_PROPERTIES = ['type', 'clipAngle', 'clipExtent', 'center', 'rotate', 'precision', 'reflectX', 'reflectY', 'coefficient', 'distance', 'fraction', 'lobes', 'parallel', 'radius', 'ratio', 'spacing', 'tilt']; - - class ProjectionComponent extends Split { - merged = false; - constructor(name, specifiedProjection, size, data) { - super({ - ...specifiedProjection - }, - // all explicit properties of projection - { - name - } // name as initial implicit property - ); - this.specifiedProjection = specifiedProjection; - this.size = size; - this.data = data; - } - - /** - * Whether the projection parameters should fit provided data. - */ - get isFit() { - return !!this.data; - } - } - - function parseProjection(model) { - model.component.projection = isUnitModel(model) ? parseUnitProjection(model) : parseNonUnitProjections(model); - } - function parseUnitProjection(model) { - if (model.hasProjection) { - const proj = replaceExprRef(model.specifiedProjection); - const fit = !(proj && (proj.scale != null || proj.translate != null)); - const size = fit ? [model.getSizeSignalRef('width'), model.getSizeSignalRef('height')] : undefined; - const data = fit ? gatherFitData(model) : undefined; - const projComp = new ProjectionComponent(model.projectionName(true), { - ...replaceExprRef(model.config.projection), - ...proj - }, size, data); - if (!projComp.get('type')) { - projComp.set('type', 'equalEarth', false); - } - return projComp; - } - return undefined; - } - function gatherFitData(model) { - const data = []; - const { - encoding - } = model; - for (const posssiblePair of [[LONGITUDE, LATITUDE], [LONGITUDE2, LATITUDE2]]) { - if (getFieldOrDatumDef(encoding[posssiblePair[0]]) || getFieldOrDatumDef(encoding[posssiblePair[1]])) { - data.push({ - signal: model.getName(`geojson_${data.length}`) - }); - } - } - if (model.channelHasField(SHAPE) && model.typedFieldDef(SHAPE).type === GEOJSON) { - data.push({ - signal: model.getName(`geojson_${data.length}`) - }); - } - if (data.length === 0) { - // main source is geojson, so we can just use that - data.push(model.requestDataName(DataSourceType.Main)); - } - return data; - } - function mergeIfNoConflict(first, second) { - const allPropertiesShared = every(PROJECTION_PROPERTIES, prop => { - // neither has the property - if (!vega.hasOwnProperty(first.explicit, prop) && !vega.hasOwnProperty(second.explicit, prop)) { - return true; - } - // both have property and an equal value for property - if (vega.hasOwnProperty(first.explicit, prop) && vega.hasOwnProperty(second.explicit, prop) && - // some properties might be signals or objects and require hashing for comparison - deepEqual(first.get(prop), second.get(prop))) { - return true; - } - return false; - }); - const size = deepEqual(first.size, second.size); - if (size) { - if (allPropertiesShared) { - return first; - } else if (deepEqual(first.explicit, {})) { - return second; - } else if (deepEqual(second.explicit, {})) { - return first; - } - } - - // if all properties don't match, let each unit spec have its own projection - return null; - } - function parseNonUnitProjections(model) { - if (model.children.length === 0) { - return undefined; - } - let nonUnitProjection; - - // parse all children first - for (const child of model.children) { - parseProjection(child); - } - - // analyze parsed projections, attempt to merge - const mergable = every(model.children, child => { - const projection = child.component.projection; - if (!projection) { - // child layer does not use a projection - return true; - } else if (!nonUnitProjection) { - // cached 'projection' is null, cache this one - nonUnitProjection = projection; - return true; - } else { - const merge = mergeIfNoConflict(nonUnitProjection, projection); - if (merge) { - nonUnitProjection = merge; - } - return !!merge; - } - }); - - // if cached one and all other children share the same projection, - if (nonUnitProjection && mergable) { - // so we can elevate it to the layer level - const name = model.projectionName(true); - const modelProjection = new ProjectionComponent(name, nonUnitProjection.specifiedProjection, nonUnitProjection.size, duplicate(nonUnitProjection.data)); - - // rename and assign all others as merged - for (const child of model.children) { - const projection = child.component.projection; - if (projection) { - if (projection.isFit) { - modelProjection.data.push(...child.component.projection.data); - } - child.renameProjection(projection.get('name'), name); - projection.merged = true; - } - } - return modelProjection; - } - return undefined; - } - - function rangeFormula(model, fieldDef, channel, config) { - if (binRequiresRange(fieldDef, channel)) { - // read format from axis or legend, if there is no format then use config.numberFormat - - const guide = isUnitModel(model) ? model.axis(channel) ?? model.legend(channel) ?? {} : {}; - const startField = vgField(fieldDef, { - expr: 'datum' - }); - const endField = vgField(fieldDef, { - expr: 'datum', - binSuffix: 'end' - }); - return { - formulaAs: vgField(fieldDef, { - binSuffix: 'range', - forAs: true - }), - formula: binFormatExpression(startField, endField, guide.format, guide.formatType, config) - }; - } - return {}; - } - function binKey(bin, field) { - return `${binToString(bin)}_${field}`; - } - function getSignalsFromModel(model, key) { - return { - signal: model.getName(`${key}_bins`), - extentSignal: model.getName(`${key}_extent`) - }; - } - function getBinSignalName(model, field, bin) { - const normalizedBin = normalizeBin(bin, undefined) ?? {}; - const key = binKey(normalizedBin, field); - return model.getName(`${key}_bins`); - } - function isBinTransform(t) { - return 'as' in t; - } - function createBinComponent(t, bin, model) { - let as; - let span; - if (isBinTransform(t)) { - as = vega.isString(t.as) ? [t.as, `${t.as}_end`] : [t.as[0], t.as[1]]; - } else { - as = [vgField(t, { - forAs: true - }), vgField(t, { - binSuffix: 'end', - forAs: true - })]; - } - const normalizedBin = { - ...normalizeBin(bin, undefined) - }; - const key = binKey(normalizedBin, t.field); - const { - signal, - extentSignal - } = getSignalsFromModel(model, key); - if (isParameterExtent(normalizedBin.extent)) { - const ext = normalizedBin.extent; - span = parseSelectionExtent(model, ext.param, ext); - delete normalizedBin.extent; // Vega-Lite selection extent map to Vega's span property. - } - const binComponent = { - bin: normalizedBin, - field: t.field, - as: [as], - ...(signal ? { - signal - } : {}), - ...(extentSignal ? { - extentSignal - } : {}), - ...(span ? { - span - } : {}) - }; - return { - key, - binComponent - }; - } - class BinNode extends DataFlowNode { - clone() { - return new BinNode(null, duplicate(this.bins)); - } - constructor(parent, bins) { - super(parent); - this.bins = bins; - } - static makeFromEncoding(parent, model) { - const bins = model.reduceFieldDef((binComponentIndex, fieldDef, channel) => { - if (isTypedFieldDef(fieldDef) && isBinning(fieldDef.bin)) { - const { - key, - binComponent - } = createBinComponent(fieldDef, fieldDef.bin, model); - binComponentIndex[key] = { - ...binComponent, - ...binComponentIndex[key], - ...rangeFormula(model, fieldDef, channel, model.config) - }; - } - return binComponentIndex; - }, {}); - if (isEmpty(bins)) { - return null; - } - return new BinNode(parent, bins); - } - - /** - * Creates a bin node from BinTransform. - * The optional parameter should provide - */ - static makeFromTransform(parent, t, model) { - const { - key, - binComponent - } = createBinComponent(t, t.bin, model); - return new BinNode(parent, { - [key]: binComponent - }); - } - - /** - * Merge bin nodes. This method either integrates the bin config from the other node - * or if this node already has a bin config, renames the corresponding signal in the model. - */ - merge(other, renameSignal) { - for (const key of keys(other.bins)) { - if (key in this.bins) { - renameSignal(other.bins[key].signal, this.bins[key].signal); - // Ensure that we don't have duplicate names for signal pairs - this.bins[key].as = unique([...this.bins[key].as, ...other.bins[key].as], hash); - } else { - this.bins[key] = other.bins[key]; - } - } - for (const child of other.children) { - other.removeChild(child); - child.parent = this; - } - other.remove(); - } - producedFields() { - return new Set(vals(this.bins).map(c => c.as).flat(2)); - } - dependentFields() { - return new Set(vals(this.bins).map(c => c.field)); - } - hash() { - return `Bin ${hash(this.bins)}`; - } - assemble() { - return vals(this.bins).flatMap(bin => { - const transform = []; - const [binAs, ...remainingAs] = bin.as; - const { - extent, - ...params - } = bin.bin; - const binTrans = { - type: 'bin', - field: replacePathInField(bin.field), - as: binAs, - signal: bin.signal, - ...(!isParameterExtent(extent) ? { - extent - } : { - extent: null - }), - ...(bin.span ? { - span: { - signal: `span(${bin.span})` - } - } : {}), - ...params - }; - if (!extent && bin.extentSignal) { - transform.push({ - type: 'extent', - field: replacePathInField(bin.field), - signal: bin.extentSignal - }); - binTrans.extent = { - signal: bin.extentSignal - }; - } - transform.push(binTrans); - for (const as of remainingAs) { - for (let i = 0; i < 2; i++) { - transform.push({ - type: 'formula', - expr: vgField({ - field: binAs[i] - }, { - expr: 'datum' - }), - as: as[i] - }); - } - } - if (bin.formula) { - transform.push({ - type: 'formula', - expr: bin.formula, - as: bin.formulaAs - }); - } - return transform; - }); - } - } - - function addDimension(dims, channel, fieldDef, model) { - const channelDef2 = isUnitModel(model) ? model.encoding[getSecondaryRangeChannel(channel)] : undefined; - if (isTypedFieldDef(fieldDef) && isUnitModel(model) && hasBandEnd(fieldDef, channelDef2, model.markDef, model.config)) { - dims.add(vgField(fieldDef, {})); - dims.add(vgField(fieldDef, { - suffix: 'end' - })); - const { - mark, - markDef, - config - } = model; - const bandPosition = getBandPosition({ - fieldDef, - markDef, - config - }); - if (isRectBasedMark(mark) && bandPosition !== 0.5 && isXorY(channel)) { - dims.add(vgField(fieldDef, { - suffix: OFFSETTED_RECT_START_SUFFIX - })); - dims.add(vgField(fieldDef, { - suffix: OFFSETTED_RECT_END_SUFFIX - })); - } - if (fieldDef.bin && binRequiresRange(fieldDef, channel)) { - dims.add(vgField(fieldDef, { - binSuffix: 'range' - })); - } - } else if (isGeoPositionChannel(channel)) { - const posChannel = getPositionChannelFromLatLong(channel); - dims.add(model.getName(posChannel)); - } else { - dims.add(vgField(fieldDef)); - } - if (isScaleFieldDef(fieldDef) && isFieldRange(fieldDef.scale?.range)) { - dims.add(fieldDef.scale.range.field); - } - return dims; - } - function mergeMeasures(parentMeasures, childMeasures) { - for (const field of keys(childMeasures)) { - // when we merge a measure, we either have to add an aggregation operator or even a new field - const ops = childMeasures[field]; - for (const op of keys(ops)) { - if (field in parentMeasures) { - // add operator to existing measure field - parentMeasures[field][op] = new Set([...(parentMeasures[field][op] ?? []), ...ops[op]]); - } else { - parentMeasures[field] = { - [op]: ops[op] - }; - } - } - } - } - class AggregateNode extends DataFlowNode { - clone() { - return new AggregateNode(null, new Set(this.dimensions), duplicate(this.measures)); - } - - /** - * @param dimensions string set for dimensions - * @param measures dictionary mapping field name => dict of aggregation functions and names to use - */ - constructor(parent, dimensions, measures) { - super(parent); - this.dimensions = dimensions; - this.measures = measures; - } - get groupBy() { - return this.dimensions; - } - static makeFromEncoding(parent, model) { - let isAggregate = false; - model.forEachFieldDef(fd => { - if (fd.aggregate) { - isAggregate = true; - } - }); - const meas = {}; - const dims = new Set(); - if (!isAggregate) { - // no need to create this node if the model has no aggregation - return null; - } - model.forEachFieldDef((fieldDef, channel) => { - const { - aggregate, - field - } = fieldDef; - if (aggregate) { - if (aggregate === 'count') { - meas['*'] ??= {}; - meas['*']['count'] = new Set([vgField(fieldDef, { - forAs: true - })]); - } else { - if (isArgminDef(aggregate) || isArgmaxDef(aggregate)) { - const op = isArgminDef(aggregate) ? 'argmin' : 'argmax'; - const argField = aggregate[op]; - meas[argField] ??= {}; - meas[argField][op] = new Set([vgField({ - op, - field: argField - }, { - forAs: true - })]); - } else { - meas[field] ??= {}; - meas[field][aggregate] = new Set([vgField(fieldDef, { - forAs: true - })]); - } - - // For scale channel with domain === 'unaggregated', add min/max so we can use their union as unaggregated domain - if (isScaleChannel(channel) && model.scaleDomain(channel) === 'unaggregated') { - meas[field] ??= {}; - meas[field]['min'] = new Set([vgField({ - field, - aggregate: 'min' - }, { - forAs: true - })]); - meas[field]['max'] = new Set([vgField({ - field, - aggregate: 'max' - }, { - forAs: true - })]); - } - } - } else { - addDimension(dims, channel, fieldDef, model); - } - }); - if (dims.size + keys(meas).length === 0) { - return null; - } - return new AggregateNode(parent, dims, meas); - } - static makeFromTransform(parent, t) { - const dims = new Set(); - const meas = {}; - for (const s of t.aggregate) { - const { - op, - field, - as - } = s; - if (op) { - if (op === 'count') { - meas['*'] ??= {}; - meas['*']['count'] = new Set([as ? as : vgField(s, { - forAs: true - })]); - } else { - meas[field] ??= {}; - meas[field][op] ??= new Set(); - meas[field][op].add(as ? as : vgField(s, { - forAs: true - })); - } - } - } - for (const s of t.groupby ?? []) { - dims.add(s); - } - if (dims.size + keys(meas).length === 0) { - return null; - } - return new AggregateNode(parent, dims, meas); - } - merge(other) { - if (setEqual(this.dimensions, other.dimensions)) { - mergeMeasures(this.measures, other.measures); - return true; - } - debug('different dimensions, cannot merge'); - return false; - } - addDimensions(fields) { - fields.forEach(this.dimensions.add, this.dimensions); - } - dependentFields() { - return new Set([...this.dimensions, ...keys(this.measures)]); - } - producedFields() { - const out = new Set(); - for (const field of keys(this.measures)) { - for (const op of keys(this.measures[field])) { - const m = this.measures[field][op]; - if (m.size === 0) { - out.add(`${op}_${field}`); - } else { - m.forEach(out.add, out); - } - } - } - return out; - } - hash() { - return `Aggregate ${hash({ - dimensions: this.dimensions, - measures: this.measures - })}`; - } - assemble() { - const ops = []; - const fields = []; - const as = []; - for (const field of keys(this.measures)) { - for (const op of keys(this.measures[field])) { - for (const alias of this.measures[field][op]) { - as.push(alias); - ops.push(op); - fields.push(field === '*' ? null : replacePathInField(field)); - } - } - } - const result = { - type: 'aggregate', - groupby: [...this.dimensions].map(replacePathInField), - ops, - fields, - as - }; - return result; - } - } - - /** - * A node that helps us track what fields we are faceting by. - */ - class FacetNode extends DataFlowNode { - /** - * @param model The facet model. - * @param name The name that this facet source will have. - * @param data The source data for this facet data. - */ - constructor(parent, model, name, data) { - super(parent); - this.model = model; - this.name = name; - this.data = data; - for (const channel of FACET_CHANNELS) { - const fieldDef = model.facet[channel]; - if (fieldDef) { - const { - bin, - sort - } = fieldDef; - this[channel] = { - name: model.getName(`${channel}_domain`), - fields: [vgField(fieldDef), ...(isBinning(bin) ? [vgField(fieldDef, { - binSuffix: 'end' - })] : [])], - ...(isSortField(sort) ? { - sortField: sort - } : vega.isArray(sort) ? { - sortIndexField: sortArrayIndexField(fieldDef, channel) - } : {}) - }; - } - } - this.childModel = model.child; - } - hash() { - let out = `Facet`; - for (const channel of FACET_CHANNELS) { - if (this[channel]) { - out += ` ${channel.charAt(0)}:${hash(this[channel])}`; - } - } - return out; - } - get fields() { - const f = []; - for (const channel of FACET_CHANNELS) { - if (this[channel]?.fields) { - f.push(...this[channel].fields); - } - } - return f; - } - dependentFields() { - const depFields = new Set(this.fields); - for (const channel of FACET_CHANNELS) { - if (this[channel]) { - if (this[channel].sortField) { - depFields.add(this[channel].sortField.field); - } - if (this[channel].sortIndexField) { - depFields.add(this[channel].sortIndexField); - } - } - } - return depFields; - } - producedFields() { - return new Set(); // facet does not produce any new fields - } - - /** - * The name to reference this source is its name. - */ - getSource() { - return this.name; - } - getChildIndependentFieldsWithStep() { - const childIndependentFieldsWithStep = {}; - for (const channel of POSITION_SCALE_CHANNELS) { - const childScaleComponent = this.childModel.component.scales[channel]; - if (childScaleComponent && !childScaleComponent.merged) { - // independent scale - const type = childScaleComponent.get('type'); - const range = childScaleComponent.get('range'); - if (hasDiscreteDomain(type) && isVgRangeStep(range)) { - const domain = assembleDomain(this.childModel, channel); - const field = getFieldFromDomain(domain); - if (field) { - childIndependentFieldsWithStep[channel] = field; - } else { - warn(unknownField(channel)); - } - } - } - } - return childIndependentFieldsWithStep; - } - assembleRowColumnHeaderData(channel, crossedDataName, childIndependentFieldsWithStep) { - const childChannel = { - row: 'y', - column: 'x', - facet: undefined - }[channel]; - const fields = []; - const ops = []; - const as = []; - if (childChannel && childIndependentFieldsWithStep && childIndependentFieldsWithStep[childChannel]) { - if (crossedDataName) { - // If there is a crossed data, calculate max - fields.push(`distinct_${childIndependentFieldsWithStep[childChannel]}`); - ops.push('max'); - } else { - // If there is no crossed data, just calculate distinct - fields.push(childIndependentFieldsWithStep[childChannel]); - ops.push('distinct'); - } - // Although it is technically a max, just name it distinct so it's easier to refer to it - as.push(`distinct_${childIndependentFieldsWithStep[childChannel]}`); - } - const { - sortField, - sortIndexField - } = this[channel]; - if (sortField) { - const { - op = DEFAULT_SORT_OP, - field - } = sortField; - fields.push(field); - ops.push(op); - as.push(vgField(sortField, { - forAs: true - })); - } else if (sortIndexField) { - fields.push(sortIndexField); - ops.push('max'); - as.push(sortIndexField); - } - return { - name: this[channel].name, - // Use data from the crossed one if it exist - source: crossedDataName ?? this.data, - transform: [{ - type: 'aggregate', - groupby: this[channel].fields, - ...(fields.length ? { - fields, - ops, - as - } : {}) - }] - }; - } - assembleFacetHeaderData(childIndependentFieldsWithStep) { - const { - columns - } = this.model.layout; - const { - layoutHeaders - } = this.model.component; - const data = []; - const hasSharedAxis = {}; - for (const headerChannel of HEADER_CHANNELS) { - for (const headerType of HEADER_TYPES) { - const headers = (layoutHeaders[headerChannel] && layoutHeaders[headerChannel][headerType]) ?? []; - for (const header of headers) { - if (header.axes?.length > 0) { - hasSharedAxis[headerChannel] = true; - break; - } - } - } - if (hasSharedAxis[headerChannel]) { - const cardinality = `length(data("${this.facet.name}"))`; - const stop = headerChannel === 'row' ? columns ? { - signal: `ceil(${cardinality} / ${columns})` - } : 1 : columns ? { - signal: `min(${cardinality}, ${columns})` - } : { - signal: cardinality - }; - data.push({ - name: `${this.facet.name}_${headerChannel}`, - transform: [{ - type: 'sequence', - start: 0, - stop - }] - }); - } - } - const { - row, - column - } = hasSharedAxis; - if (row || column) { - data.unshift(this.assembleRowColumnHeaderData('facet', null, childIndependentFieldsWithStep)); - } - return data; - } - assemble() { - const data = []; - let crossedDataName = null; - const childIndependentFieldsWithStep = this.getChildIndependentFieldsWithStep(); - const { - column, - row, - facet - } = this; - if (column && row && (childIndependentFieldsWithStep.x || childIndependentFieldsWithStep.y)) { - // Need to create a cross dataset to correctly calculate cardinality - crossedDataName = `cross_${this.column.name}_${this.row.name}`; - const fields = [].concat(childIndependentFieldsWithStep.x ?? [], childIndependentFieldsWithStep.y ?? []); - const ops = fields.map(() => 'distinct'); - data.push({ - name: crossedDataName, - source: this.data, - transform: [{ - type: 'aggregate', - groupby: this.fields, - fields, - ops - }] - }); - } - for (const channel of [COLUMN, ROW]) { - if (this[channel]) { - data.push(this.assembleRowColumnHeaderData(channel, crossedDataName, childIndependentFieldsWithStep)); - } - } - if (facet) { - const facetData = this.assembleFacetHeaderData(childIndependentFieldsWithStep); - if (facetData) { - data.push(...facetData); - } - } - return data; - } - } - - /** - * Remove quotes from a string. - */ - function unquote(pattern) { - if (pattern.startsWith("'") && pattern.endsWith("'") || pattern.startsWith('"') && pattern.endsWith('"')) { - return pattern.slice(1, -1); - } - return pattern; - } - - /** - * @param field The field. - * @param parse What to parse the field as. - */ - function parseExpression(field, parse) { - const f = accessPathWithDatum(field); - if (parse === 'number') { - return `toNumber(${f})`; - } else if (parse === 'boolean') { - return `toBoolean(${f})`; - } else if (parse === 'string') { - return `toString(${f})`; - } else if (parse === 'date') { - return `toDate(${f})`; - } else if (parse === 'flatten') { - return f; - } else if (parse.startsWith('date:')) { - const specifier = unquote(parse.slice(5, parse.length)); - return `timeParse(${f},'${specifier}')`; - } else if (parse.startsWith('utc:')) { - const specifier = unquote(parse.slice(4, parse.length)); - return `utcParse(${f},'${specifier}')`; - } else { - warn(unrecognizedParse(parse)); - return null; - } - } - function getImplicitFromFilterTransform(transform) { - const implicit = {}; - forEachLeaf(transform.filter, filter => { - if (isFieldPredicate(filter)) { - // Automatically add a parse node for filters with filter objects - let val = null; - - // For EqualFilter, just use the equal property. - // For RangeFilter and OneOfFilter, all array members should have - // the same type, so we only use the first one. - if (isFieldEqualPredicate(filter)) { - val = signalRefOrValue(filter.equal); - } else if (isFieldLTEPredicate(filter)) { - val = signalRefOrValue(filter.lte); - } else if (isFieldLTPredicate(filter)) { - val = signalRefOrValue(filter.lt); - } else if (isFieldGTPredicate(filter)) { - val = signalRefOrValue(filter.gt); - } else if (isFieldGTEPredicate(filter)) { - val = signalRefOrValue(filter.gte); - } else if (isFieldRangePredicate(filter)) { - val = filter.range[0]; - } else if (isFieldOneOfPredicate(filter)) { - val = (filter.oneOf ?? filter['in'])[0]; - } // else -- for filter expression, we can't infer anything - - if (val) { - if (isDateTime(val)) { - implicit[filter.field] = 'date'; - } else if (vega.isNumber(val)) { - implicit[filter.field] = 'number'; - } else if (vega.isString(val)) { - implicit[filter.field] = 'string'; - } - } - if (filter.timeUnit) { - implicit[filter.field] = 'date'; - } - } - }); - return implicit; - } - - /** - * Creates a parse node for implicit parsing from a model and updates ancestorParse. - */ - function getImplicitFromEncoding(model) { - const implicit = {}; - function add(fieldDef) { - if (isFieldOrDatumDefForTimeFormat(fieldDef)) { - implicit[fieldDef.field] = 'date'; - } else if (fieldDef.type === 'quantitative' && isMinMaxOp(fieldDef.aggregate) // we need to parse numbers to support correct min and max - ) { - implicit[fieldDef.field] = 'number'; - } else if (accessPathDepth(fieldDef.field) > 1) { - // For non-date/non-number (strings and booleans), derive a flattened field for a referenced nested field. - // (Parsing numbers / dates already flattens numeric and temporal fields.) - if (!(fieldDef.field in implicit)) { - implicit[fieldDef.field] = 'flatten'; - } - } else if (isScaleFieldDef(fieldDef) && isSortField(fieldDef.sort) && accessPathDepth(fieldDef.sort.field) > 1) { - // Flatten fields that we sort by but that are not otherwise flattened. - if (!(fieldDef.sort.field in implicit)) { - implicit[fieldDef.sort.field] = 'flatten'; - } - } - } - if (isUnitModel(model) || isFacetModel(model)) { - // Parse encoded fields - model.forEachFieldDef((fieldDef, channel) => { - if (isTypedFieldDef(fieldDef)) { - add(fieldDef); - } else { - const mainChannel = getMainRangeChannel(channel); - const mainFieldDef = model.fieldDef(mainChannel); - add({ - ...fieldDef, - type: mainFieldDef.type - }); - } - }); - } - - // Parse quantitative dimension fields of path marks as numbers so that we sort them correctly. - if (isUnitModel(model)) { - const { - mark, - markDef, - encoding - } = model; - if (isPathMark(mark) && - // No need to sort by dimension if we have a connected scatterplot (order channel is present) - !model.encoding.order) { - const dimensionChannel = markDef.orient === 'horizontal' ? 'y' : 'x'; - const dimensionChannelDef = encoding[dimensionChannel]; - if (isFieldDef(dimensionChannelDef) && dimensionChannelDef.type === 'quantitative' && !(dimensionChannelDef.field in implicit)) { - implicit[dimensionChannelDef.field] = 'number'; - } - } - } - return implicit; - } - - /** - * Creates a parse node for implicit parsing from a model and updates ancestorParse. - */ - function getImplicitFromSelection(model) { - const implicit = {}; - if (isUnitModel(model) && model.component.selection) { - for (const name of keys(model.component.selection)) { - const selCmpt = model.component.selection[name]; - for (const proj of selCmpt.project.items) { - if (!proj.channel && accessPathDepth(proj.field) > 1) { - implicit[proj.field] = 'flatten'; - } - } - } - } - return implicit; - } - class ParseNode extends DataFlowNode { - clone() { - return new ParseNode(null, duplicate(this._parse)); - } - constructor(parent, parse) { - super(parent); - this._parse = parse; - } - hash() { - return `Parse ${hash(this._parse)}`; - } - - /** - * Creates a parse node from a data.format.parse and updates ancestorParse. - */ - static makeExplicit(parent, model, ancestorParse) { - // Custom parse - let explicit = {}; - const data = model.data; - if (!isGenerator(data) && data?.format?.parse) { - explicit = data.format.parse; - } - return this.makeWithAncestors(parent, explicit, {}, ancestorParse); - } - - /** - * Creates a parse node from "explicit" parse and "implicit" parse and updates ancestorParse. - */ - static makeWithAncestors(parent, explicit, implicit, ancestorParse) { - // 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. - for (const field of keys(implicit)) { - const parsedAs = ancestorParse.getWithExplicit(field); - if (parsedAs.value !== undefined) { - // We always ignore derived fields even if they are implicitly defined because we expect users to create the right types. - if (parsedAs.explicit || parsedAs.value === implicit[field] || parsedAs.value === 'derived' || implicit[field] === 'flatten') { - delete implicit[field]; - } else { - warn(differentParse(field, implicit[field], parsedAs.value)); - } - } - } - for (const field of keys(explicit)) { - const parsedAs = ancestorParse.get(field); - if (parsedAs !== undefined) { - // Don't parse a field again if it has been parsed with the same type already. - if (parsedAs === explicit[field]) { - delete explicit[field]; - } else { - warn(differentParse(field, explicit[field], parsedAs)); - } - } - } - const parse = new Split(explicit, implicit); - - // add the format parse from this model so that children don't parse the same field again - ancestorParse.copyAll(parse); - - // copy only non-null parses - const p = {}; - for (const key of keys(parse.combine())) { - const val = parse.get(key); - if (val !== null) { - p[key] = val; - } - } - if (keys(p).length === 0 || ancestorParse.parseNothing) { - return null; - } - return new ParseNode(parent, p); - } - get parse() { - return this._parse; - } - merge(other) { - this._parse = { - ...this._parse, - ...other.parse - }; - other.remove(); - } - - /** - * Assemble an object for Vega's format.parse property. - */ - assembleFormatParse() { - const formatParse = {}; - for (const field of keys(this._parse)) { - const p = this._parse[field]; - if (accessPathDepth(field) === 1) { - formatParse[field] = p; - } - } - return formatParse; - } - - // format parse depends and produces all fields in its parse - producedFields() { - return new Set(keys(this._parse)); - } - dependentFields() { - return new Set(keys(this._parse)); - } - assembleTransforms() { - let onlyNested = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; - return keys(this._parse).filter(field => onlyNested ? accessPathDepth(field) > 1 : true).map(field => { - const expr = parseExpression(field, this._parse[field]); - if (!expr) { - return null; - } - const formula = { - type: 'formula', - expr, - as: removePathFromField(field) // Vega output is always flattened - }; - return formula; - }).filter(t => t !== null); - } - } - - class IdentifierNode extends DataFlowNode { - clone() { - return new IdentifierNode(null); - } - constructor(parent) { - super(parent); - } - dependentFields() { - return new Set(); - } - producedFields() { - return new Set([SELECTION_ID]); - } - hash() { - return 'Identifier'; - } - assemble() { - return { - type: 'identifier', - as: SELECTION_ID - }; - } - } - - class GraticuleNode extends DataFlowNode { - clone() { - return new GraticuleNode(null, this.params); - } - constructor(parent, params) { - super(parent); - this.params = params; - } - dependentFields() { - return new Set(); - } - producedFields() { - return undefined; // there should never be a node before graticule - } - hash() { - return `Graticule ${hash(this.params)}`; - } - assemble() { - return { - type: 'graticule', - ...(this.params === true ? {} : this.params) - }; - } - } - - class SequenceNode extends DataFlowNode { - clone() { - return new SequenceNode(null, this.params); - } - constructor(parent, params) { - super(parent); - this.params = params; - } - dependentFields() { - return new Set(); - } - producedFields() { - return new Set([this.params.as ?? 'data']); - } - hash() { - return `Hash ${hash(this.params)}`; - } - assemble() { - return { - type: 'sequence', - ...this.params - }; - } - } - - class SourceNode extends DataFlowNode { - constructor(data) { - super(null); // source cannot have parent - - data ??= { - name: 'source' - }; - let format; - if (!isGenerator(data)) { - format = data.format ? { - ...omit(data.format, ['parse']) - } : {}; - } - if (isInlineData(data)) { - this._data = { - values: data.values - }; - } else if (isUrlData(data)) { - this._data = { - url: data.url - }; - if (!format.type) { - // Extract extension from URL using snippet from - // http://stackoverflow.com/questions/680929/how-to-extract-extension-from-filename-string-in-javascript - let defaultExtension = /(?:\.([^.]+))?$/.exec(data.url)[1]; - if (!contains(['json', 'csv', 'tsv', 'dsv', 'topojson'], defaultExtension)) { - defaultExtension = 'json'; - } - - // defaultExtension has type string but we ensure that it is DataFormatType above - format.type = defaultExtension; - } - } else if (isSphereGenerator(data)) { - // hardwire GeoJSON sphere data into output specification - this._data = { - values: [{ - type: 'Sphere' - }] - }; - } else if (isNamedData(data) || isGenerator(data)) { - this._data = {}; - } - - // set flag to check if generator - this._generator = isGenerator(data); - - // any dataset can be named - if (data.name) { - this._name = data.name; - } - if (format && !isEmpty(format)) { - this._data.format = format; - } - } - dependentFields() { - return new Set(); - } - producedFields() { - return undefined; // we don't know what this source produces - } - get data() { - return this._data; - } - hasName() { - return !!this._name; - } - get isGenerator() { - return this._generator; - } - get dataName() { - return this._name; - } - set dataName(name) { - this._name = name; - } - set parent(parent) { - throw new Error('Source nodes have to be roots.'); - } - remove() { - throw new Error('Source nodes are roots and cannot be removed.'); - } - hash() { - throw new Error('Cannot hash sources'); - } - assemble() { - return { - name: this._name, - ...this._data, - transform: [] - }; - } - } - - /** - * Whether this dataflow node is the source of the dataflow that produces data i.e. a source or a generator. - */ - function isDataSourceNode(node) { - return node instanceof SourceNode || node instanceof GraticuleNode || node instanceof SequenceNode; - } - - /** - * Abstract base class for Dataflow optimizers. - * Contains only mutation handling logic. Subclasses need to implement iteration logic. - */ - class Optimizer { - #modified; - constructor() { - this.#modified = false; - } - - // Once true, #modified is never set to false - setModified() { - this.#modified = true; - } - get modifiedFlag() { - return this.#modified; - } - - /** - * Run the optimization for the tree with the provided root. - */ - } - - /** - * Starts from a node and runs the optimization function (the "run" method) upwards to the root, - * depending on the continue and modified flag values returned by the optimization function. - */ - class BottomUpOptimizer extends Optimizer { - /** - * Run the optimizer at the node. This method should not change the parent of the passed in node (it should only affect children). - */ - - /** - * Compute a map of node depths that we can use to determine a topological sort order. - */ - getNodeDepths(node, depth, depths) { - depths.set(node, depth); - for (const child of node.children) { - this.getNodeDepths(child, depth + 1, depths); - } - return depths; - } - - /** - * Run the optimizer on all nodes starting from the leaves. - */ - optimize(node) { - const depths = this.getNodeDepths(node, 0, new Map()); - const topologicalSort = [...depths.entries()].sort((a, b) => b[1] - a[1]); - for (const tuple of topologicalSort) { - this.run(tuple[0]); - } - return this.modifiedFlag; - } - } - - /** - * The optimizer function (the "run" method), is invoked on the given node and then continues recursively. - */ - class TopDownOptimizer extends Optimizer { - /** - * Run the optimizer at the node. - */ - - /** - * Run the optimizer depth first on all nodes starting from the roots. - */ - optimize(node) { - this.run(node); - for (const child of node.children) { - this.optimize(child); - } - return this.modifiedFlag; - } - } - - /** - * Merge identical nodes at forks by comparing hashes. - * - * Does not need to iterate from leaves so we implement this with recursion as it's a bit simpler. - */ - class MergeIdenticalNodes extends TopDownOptimizer { - mergeNodes(parent, nodes) { - const mergedNode = nodes.shift(); - for (const node of nodes) { - parent.removeChild(node); - node.parent = mergedNode; - node.remove(); - } - } - run(node) { - const hashes = node.children.map(x => x.hash()); - const buckets = {}; - for (let i = 0; i < hashes.length; i++) { - if (buckets[hashes[i]] === undefined) { - buckets[hashes[i]] = [node.children[i]]; - } else { - buckets[hashes[i]].push(node.children[i]); - } - } - for (const k of keys(buckets)) { - if (buckets[k].length > 1) { - this.setModified(); - this.mergeNodes(node, buckets[k]); - } - } - } - } - - /** - * Optimizer that removes identifier nodes that are not needed for selections. - */ - class RemoveUnnecessaryIdentifierNodes extends TopDownOptimizer { - constructor(model) { - super(); - this.requiresSelectionId = model && requiresSelectionId(model); - } - run(node) { - if (node instanceof IdentifierNode) { - // Only preserve IdentifierNodes if we have default discrete selections - // in our model tree, and if the nodes come after tuple producing nodes. - if (!(this.requiresSelectionId && (isDataSourceNode(node.parent) || node.parent instanceof AggregateNode || node.parent instanceof ParseNode))) { - this.setModified(); - node.remove(); - } - } - } - } - - /** - * Removes duplicate time unit nodes (as determined by the name of the output field) that may be generated due to - * selections projected over time units. Only keeps the first time unit in any branch. - * - * This optimizer is a custom top down optimizer that keep track of produced fields in a branch. - */ - class RemoveDuplicateTimeUnits extends Optimizer { - optimize(node) { - this.run(node, new Set()); - return this.modifiedFlag; - } - run(node, timeUnitFields) { - let producedFields = new Set(); - if (node instanceof TimeUnitNode) { - producedFields = node.producedFields(); - if (hasIntersection(producedFields, timeUnitFields)) { - this.setModified(); - node.removeFormulas(timeUnitFields); - if (node.producedFields.length === 0) { - node.remove(); - } - } - } - for (const child of node.children) { - this.run(child, new Set([...timeUnitFields, ...producedFields])); - } - } - } - - /** - * Remove output nodes that are not required. - */ - class RemoveUnnecessaryOutputNodes extends TopDownOptimizer { - constructor() { - super(); - } - run(node) { - if (node instanceof OutputNode && !node.isRequired()) { - this.setModified(); - node.remove(); - } - } - } - - /** - * Move parse nodes up to forks and merges them if possible. - */ - class MoveParseUp extends BottomUpOptimizer { - run(node) { - if (isDataSourceNode(node)) { - return; - } - if (node.numChildren() > 1) { - // Don't move parse further up but continue with parent. - return; - } - for (const child of node.children) { - if (child instanceof ParseNode) { - if (node instanceof ParseNode) { - this.setModified(); - node.merge(child); - } else { - // Don't swap with nodes that produce something that the parse node depends on (e.g. lookup). - if (fieldIntersection(node.producedFields(), child.dependentFields())) { - continue; - } - this.setModified(); - child.swapWithParent(); - } - } - } - return; - } - } - - /** - * Inserts an intermediate ParseNode containing all non-conflicting parse fields and removes the empty ParseNodes. - * - * We assume that dependent paths that do not have a parse node can be just merged. - */ - class MergeParse extends BottomUpOptimizer { - run(node) { - const originalChildren = [...node.children]; - const parseChildren = node.children.filter(child => child instanceof ParseNode); - if (node.numChildren() > 1 && parseChildren.length >= 1) { - const commonParse = {}; - const conflictingParse = new Set(); - for (const parseNode of parseChildren) { - const parse = parseNode.parse; - for (const k of keys(parse)) { - if (!(k in commonParse)) { - commonParse[k] = parse[k]; - } else if (commonParse[k] !== parse[k]) { - conflictingParse.add(k); - } - } - } - for (const field of conflictingParse) { - delete commonParse[field]; - } - if (!isEmpty(commonParse)) { - this.setModified(); - const mergedParseNode = new ParseNode(node, commonParse); - for (const childNode of originalChildren) { - if (childNode instanceof ParseNode) { - for (const key of keys(commonParse)) { - delete childNode.parse[key]; - } - } - node.removeChild(childNode); - childNode.parent = mergedParseNode; - - // remove empty parse nodes - if (childNode instanceof ParseNode && keys(childNode.parse).length === 0) { - childNode.remove(); - } - } - } - } - } - } - - /** - * Repeatedly remove leaf nodes that are not output or facet nodes. - * The reason is that we don't need subtrees that don't have any output nodes. - * Facet nodes are needed for the row or column domains. - */ - class RemoveUnusedSubtrees extends BottomUpOptimizer { - run(node) { - if (node instanceof OutputNode || node.numChildren() > 0 || node instanceof FacetNode) ; else if (node instanceof SourceNode) ; else { - this.setModified(); - node.remove(); - } - } - } - - /** - * Merge adjacent time unit nodes. - */ - class MergeTimeUnits extends BottomUpOptimizer { - run(node) { - const timeUnitChildren = node.children.filter(x => x instanceof TimeUnitNode); - const combination = timeUnitChildren.pop(); - for (const timeUnit of timeUnitChildren) { - this.setModified(); - combination.merge(timeUnit); - } - } - } - class MergeAggregates extends BottomUpOptimizer { - run(node) { - const aggChildren = node.children.filter(child => child instanceof AggregateNode); - - // Object which we'll use to map the fields which an aggregate is grouped by to - // the set of aggregates with that grouping. This is useful as only aggregates - // with the same group by can be merged - const groupedAggregates = {}; - - // Build groupedAggregates - for (const agg of aggChildren) { - const groupBys = hash(agg.groupBy); - if (!(groupBys in groupedAggregates)) { - groupedAggregates[groupBys] = []; - } - groupedAggregates[groupBys].push(agg); - } - - // Merge aggregateNodes with same key in groupedAggregates - for (const group of keys(groupedAggregates)) { - const mergeableAggs = groupedAggregates[group]; - if (mergeableAggs.length > 1) { - const mergedAggs = mergeableAggs.pop(); - for (const agg of mergeableAggs) { - if (mergedAggs.merge(agg)) { - node.removeChild(agg); - agg.parent = mergedAggs; - agg.remove(); - this.setModified(); - } - } - } - } - } - } - - /** - * Merge bin nodes and move them up through forks. Stop at filters, parse, identifier as we want them to stay before the bin node. - */ - class MergeBins extends BottomUpOptimizer { - constructor(model) { - super(); - this.model = model; - } - run(node) { - const moveBinsUp = !(isDataSourceNode(node) || node instanceof FilterNode || node instanceof ParseNode || node instanceof IdentifierNode); - const promotableBins = []; - const remainingBins = []; - for (const child of node.children) { - if (child instanceof BinNode) { - if (moveBinsUp && !fieldIntersection(node.producedFields(), child.dependentFields())) { - promotableBins.push(child); - } else { - remainingBins.push(child); - } - } - } - if (promotableBins.length > 0) { - const promotedBin = promotableBins.pop(); - for (const bin of promotableBins) { - promotedBin.merge(bin, this.model.renameSignal.bind(this.model)); - } - this.setModified(); - if (node instanceof BinNode) { - node.merge(promotedBin, this.model.renameSignal.bind(this.model)); - } else { - promotedBin.swapWithParent(); - } - } - if (remainingBins.length > 1) { - const remainingBin = remainingBins.pop(); - for (const bin of remainingBins) { - remainingBin.merge(bin, this.model.renameSignal.bind(this.model)); - } - this.setModified(); - } - } - } - - /** - * This optimizer takes output nodes that are at a fork and moves them before the fork. - * - * The algorithm iterates over the children and tries to find the last output node in a chain of output nodes. - * It then moves all output nodes before that main output node. All other children (and the children of the output nodes) - * are inserted after the main output node. - */ - class MergeOutputs extends BottomUpOptimizer { - run(node) { - const children = [...node.children]; - const hasOutputChild = some(children, child => child instanceof OutputNode); - if (!hasOutputChild || node.numChildren() <= 1) { - return; - } - const otherChildren = []; - - // The output node we will connect all other nodes to. - // Output nodes will be added before the new node, other nodes after. - let mainOutput; - for (const child of children) { - if (child instanceof OutputNode) { - let lastOutput = child; - while (lastOutput.numChildren() === 1) { - const [theChild] = lastOutput.children; - if (theChild instanceof OutputNode) { - lastOutput = theChild; - } else { - break; - } - } - otherChildren.push(...lastOutput.children); - if (mainOutput) { - // Move the output nodes before the mainOutput. We do this by setting - // the parent of the first not to the parent of the main output and - // the main output's parent to the last output. - - // note: the child is the first output - node.removeChild(child); - child.parent = mainOutput.parent; - mainOutput.parent.removeChild(mainOutput); - mainOutput.parent = lastOutput; - this.setModified(); - } else { - mainOutput = lastOutput; - } - } else { - otherChildren.push(child); - } - } - if (otherChildren.length) { - this.setModified(); - for (const child of otherChildren) { - child.parent.removeChild(child); - child.parent = mainOutput; - } - } - } - } - - /** - * A class for the join aggregate transform nodes. - */ - class JoinAggregateTransformNode extends DataFlowNode { - clone() { - return new JoinAggregateTransformNode(null, duplicate(this.transform)); - } - constructor(parent, transform) { - super(parent); - this.transform = transform; - } - addDimensions(fields) { - this.transform.groupby = unique(this.transform.groupby.concat(fields), d => d); - } - dependentFields() { - const out = new Set(); - if (this.transform.groupby) { - this.transform.groupby.forEach(out.add, out); - } - this.transform.joinaggregate.map(w => w.field).filter(f => f !== undefined).forEach(out.add, out); - return out; - } - producedFields() { - return new Set(this.transform.joinaggregate.map(this.getDefaultName)); - } - getDefaultName(joinAggregateFieldDef) { - return joinAggregateFieldDef.as ?? vgField(joinAggregateFieldDef); - } - hash() { - return `JoinAggregateTransform ${hash(this.transform)}`; - } - assemble() { - const fields = []; - const ops = []; - const as = []; - for (const joinaggregate of this.transform.joinaggregate) { - ops.push(joinaggregate.op); - as.push(this.getDefaultName(joinaggregate)); - fields.push(joinaggregate.field === undefined ? null : joinaggregate.field); - } - const groupby = this.transform.groupby; - return { - type: 'joinaggregate', - as, - ops, - fields, - ...(groupby !== undefined ? { - groupby - } : {}) - }; - } - } - - class FilterInvalidNode extends DataFlowNode { - clone() { - return new FilterInvalidNode(null, { - ...this.filter - }); - } - constructor(parent, filter) { - super(parent); - this.filter = filter; - } - static make(parent, model, dataSourcesForHandlingInvalidValues) { - const { - config, - markDef - } = model; - const { - marks, - scales - } = dataSourcesForHandlingInvalidValues; - if (marks === 'include-invalid-values' && scales === 'include-invalid-values') { - // If neither marks nor scale domains need data source to filter null values, then don't add the filter. - return null; - } - const filter = model.reduceFieldDef((aggregator, fieldDef, channel) => { - const scaleComponent = isScaleChannel(channel) && model.getScaleComponent(channel); - if (scaleComponent) { - const scaleType = scaleComponent.get('type'); - const { - aggregate - } = fieldDef; - const invalidDataMode = getScaleInvalidDataMode({ - scaleChannel: channel, - markDef, - config, - scaleType, - isCountAggregate: isCountingAggregateOp(aggregate) - }); - - // If the invalid data mode is include or always-valid, we don't need to filter invalid values as the scale can handle invalid values. - if (invalidDataMode !== 'show' && invalidDataMode !== 'always-valid') { - aggregator[fieldDef.field] = fieldDef; // we know that the fieldDef is a typed field def - } - } - return aggregator; - }, {}); - if (!keys(filter).length) { - return null; - } - return new FilterInvalidNode(parent, filter); - } - dependentFields() { - return new Set(keys(this.filter)); - } - producedFields() { - return new Set(); // filter does not produce any new fields - } - hash() { - return `FilterInvalid ${hash(this.filter)}`; - } - - /** - * Create the VgTransforms for each of the filtered fields. - */ - assemble() { - const filters = keys(this.filter).reduce((vegaFilters, field) => { - const fieldDef = this.filter[field]; - const ref = vgField(fieldDef, { - expr: 'datum' - }); - if (fieldDef !== null) { - if (fieldDef.type === 'temporal') { - vegaFilters.push(`(isDate(${ref}) || (${isValidFiniteNumberExpr(ref)}))`); - } else if (fieldDef.type === 'quantitative') { - vegaFilters.push(isValidFiniteNumberExpr(ref)); - } else ; - } - return vegaFilters; - }, []); - return filters.length > 0 ? { - type: 'filter', - expr: filters.join(' && ') - } : null; - } - } - function isValidFiniteNumberExpr(ref) { - return `isValid(${ref}) && isFinite(+${ref})`; - } - - function getStackByFields(model) { - return model.stack.stackBy.reduce((fields, by) => { - const fieldDef = by.fieldDef; - const _field = vgField(fieldDef); - if (_field) { - fields.push(_field); - } - return fields; - }, []); - } - function isValidAsArray(as) { - return vega.isArray(as) && as.every(s => vega.isString(s)) && as.length > 1; - } - class StackNode extends DataFlowNode { - clone() { - return new StackNode(null, duplicate(this._stack)); - } - constructor(parent, stack) { - super(parent); - this._stack = stack; - } - static makeFromTransform(parent, stackTransform) { - const { - stack, - groupby, - as, - offset = 'zero' - } = stackTransform; - const sortFields = []; - const sortOrder = []; - if (stackTransform.sort !== undefined) { - for (const sortField of stackTransform.sort) { - sortFields.push(sortField.field); - sortOrder.push(getFirstDefined(sortField.order, 'ascending')); - } - } - const sort = { - field: sortFields, - order: sortOrder - }; - let normalizedAs; - if (isValidAsArray(as)) { - normalizedAs = as; - } else if (vega.isString(as)) { - normalizedAs = [as, `${as}_end`]; - } else { - normalizedAs = [`${stackTransform.stack}_start`, `${stackTransform.stack}_end`]; - } - return new StackNode(parent, { - dimensionFieldDefs: [], - stackField: stack, - groupby, - offset, - sort, - facetby: [], - as: normalizedAs - }); - } - static makeFromEncoding(parent, model) { - const stackProperties = model.stack; - const { - encoding - } = model; - if (!stackProperties) { - return null; - } - const { - groupbyChannels, - fieldChannel, - offset, - impute - } = stackProperties; - const dimensionFieldDefs = groupbyChannels.map(groupbyChannel => { - const cDef = encoding[groupbyChannel]; - return getFieldDef(cDef); - }).filter(def => !!def); - const stackby = getStackByFields(model); - const orderDef = model.encoding.order; - let sort; - if (vega.isArray(orderDef) || isFieldDef(orderDef)) { - sort = sortParams(orderDef); - } else { - const sortOrder = isOrderOnlyDef(orderDef) ? orderDef.sort : fieldChannel === 'y' ? 'descending' : 'ascending'; - // default = descending by stackFields - // FIXME is the default here correct for binned fields? - sort = stackby.reduce((s, field) => { - if (!s.field.includes(field)) { - s.field.push(field); - s.order.push(sortOrder); - } - return s; - }, { - field: [], - order: [] - }); - } - return new StackNode(parent, { - dimensionFieldDefs, - stackField: model.vgField(fieldChannel), - facetby: [], - stackby, - sort, - offset, - impute, - as: [model.vgField(fieldChannel, { - suffix: 'start', - forAs: true - }), model.vgField(fieldChannel, { - suffix: 'end', - forAs: true - })] - }); - } - get stack() { - return this._stack; - } - addDimensions(fields) { - this._stack.facetby.push(...fields); - } - dependentFields() { - const out = new Set(); - out.add(this._stack.stackField); - this.getGroupbyFields().forEach(out.add, out); - this._stack.facetby.forEach(out.add, out); - this._stack.sort.field.forEach(out.add, out); - return out; - } - producedFields() { - return new Set(this._stack.as); - } - hash() { - return `Stack ${hash(this._stack)}`; - } - getGroupbyFields() { - const { - dimensionFieldDefs, - impute, - groupby - } = this._stack; - if (dimensionFieldDefs.length > 0) { - return dimensionFieldDefs.map(dimensionFieldDef => { - if (dimensionFieldDef.bin) { - if (impute) { - // For binned group by field with impute, we calculate bin_mid - // as we cannot impute two fields simultaneously - return [vgField(dimensionFieldDef, { - binSuffix: 'mid' - })]; - } - return [ - // For binned group by field without impute, we need both bin (start) and bin_end - vgField(dimensionFieldDef, {}), vgField(dimensionFieldDef, { - binSuffix: 'end' - })]; - } - return [vgField(dimensionFieldDef)]; - }).flat(); - } - return groupby ?? []; - } - assemble() { - const transform = []; - const { - facetby, - dimensionFieldDefs, - stackField: field, - stackby, - sort, - offset, - impute, - as - } = this._stack; - - // Impute - if (impute) { - for (const dimensionFieldDef of dimensionFieldDefs) { - const { - bandPosition = 0.5, - bin - } = dimensionFieldDef; - if (bin) { - // As we can only impute one field at a time, we need to calculate - // mid point for a binned field - - const binStart = vgField(dimensionFieldDef, { - expr: 'datum' - }); - const binEnd = vgField(dimensionFieldDef, { - expr: 'datum', - binSuffix: 'end' - }); - transform.push({ - type: 'formula', - expr: `${isValidFiniteNumberExpr(binStart)} ? ${bandPosition}*${binStart}+${1 - bandPosition}*${binEnd} : ${binStart}`, - as: vgField(dimensionFieldDef, { - binSuffix: 'mid', - forAs: true - }) - }); - } - transform.push({ - type: 'impute', - field, - groupby: [...stackby, ...facetby], - key: vgField(dimensionFieldDef, { - binSuffix: 'mid' - }), - method: 'value', - value: 0 - }); - } - } - - // Stack - transform.push({ - type: 'stack', - groupby: [...this.getGroupbyFields(), ...facetby], - field, - sort, - as, - offset - }); - return transform; - } - } - - /** - * A class for the window transform nodes - */ - class WindowTransformNode extends DataFlowNode { - clone() { - return new WindowTransformNode(null, duplicate(this.transform)); - } - constructor(parent, transform) { - super(parent); - this.transform = transform; - } - addDimensions(fields) { - this.transform.groupby = unique(this.transform.groupby.concat(fields), d => d); - } - dependentFields() { - const out = new Set(); - (this.transform.groupby ?? []).forEach(out.add, out); - (this.transform.sort ?? []).forEach(m => out.add(m.field)); - this.transform.window.map(w => w.field).filter(f => f !== undefined).forEach(out.add, out); - return out; - } - producedFields() { - return new Set(this.transform.window.map(this.getDefaultName)); - } - getDefaultName(windowFieldDef) { - return windowFieldDef.as ?? vgField(windowFieldDef); - } - hash() { - return `WindowTransform ${hash(this.transform)}`; - } - assemble() { - const fields = []; - const ops = []; - const as = []; - const params = []; - for (const window of this.transform.window) { - ops.push(window.op); - as.push(this.getDefaultName(window)); - params.push(window.param === undefined ? null : window.param); - fields.push(window.field === undefined ? null : window.field); - } - const frame = this.transform.frame; - const groupby = this.transform.groupby; - if (frame && frame[0] === null && frame[1] === null && ops.every(o => isAggregateOp(o))) { - // when the window does not rely on any particular window ops or frame, switch to a simpler and more efficient joinaggregate - return { - type: 'joinaggregate', - as, - ops: ops, - fields, - ...(groupby !== undefined ? { - groupby - } : {}) - }; - } - const sortFields = []; - const sortOrder = []; - if (this.transform.sort !== undefined) { - for (const sortField of this.transform.sort) { - sortFields.push(sortField.field); - sortOrder.push(sortField.order ?? 'ascending'); - } - } - const sort = { - field: sortFields, - order: sortOrder - }; - const ignorePeers = this.transform.ignorePeers; - return { - type: 'window', - params, - as, - ops, - fields, - sort, - ...(ignorePeers !== undefined ? { - ignorePeers - } : {}), - ...(groupby !== undefined ? { - groupby - } : {}), - ...(frame !== undefined ? { - frame - } : {}) - }; - } - } - - /** - * Clones the subtree and ignores output nodes except for the leaves, which are renamed. - */ - function cloneSubtree(facet) { - function clone(node) { - if (!(node instanceof FacetNode)) { - const copy = node.clone(); - if (copy instanceof OutputNode) { - const newName = FACET_SCALE_PREFIX + copy.getSource(); - copy.setSource(newName); - facet.model.component.data.outputNodes[newName] = copy; - } else if (copy instanceof AggregateNode || copy instanceof StackNode || copy instanceof WindowTransformNode || copy instanceof JoinAggregateTransformNode) { - copy.addDimensions(facet.fields); - } - for (const n of node.children.flatMap(clone)) { - n.parent = copy; - } - return [copy]; - } - return node.children.flatMap(clone); - } - return clone; - } - - /** - * Move facet nodes down to the next fork or output node. Also pull the main output with the facet node. - * After moving down the facet node, make a copy of the subtree and make it a child of the main output. - */ - function moveFacetDown(node) { - if (node instanceof FacetNode) { - if (node.numChildren() === 1 && !(node.children[0] instanceof OutputNode)) { - // move down until we hit a fork or output node - const child = node.children[0]; - if (child instanceof AggregateNode || child instanceof StackNode || child instanceof WindowTransformNode || child instanceof JoinAggregateTransformNode) { - child.addDimensions(node.fields); - } - child.swapWithParent(); - moveFacetDown(node); - } else { - // move main to facet - - const facetMain = node.model.component.data.main; - moveMainDownToFacet(facetMain); - - // replicate the subtree and place it before the facet's main node - const cloner = cloneSubtree(node); - const copy = node.children.map(cloner).flat(); - for (const c of copy) { - c.parent = facetMain; - } - } - } else { - node.children.map(moveFacetDown); - } - } - function moveMainDownToFacet(node) { - if (node instanceof OutputNode && node.type === DataSourceType.Main) { - if (node.numChildren() === 1) { - const child = node.children[0]; - if (!(child instanceof FacetNode)) { - child.swapWithParent(); - moveMainDownToFacet(node); - } - } - } - } - - const FACET_SCALE_PREFIX = 'scale_'; - const MAX_OPTIMIZATION_RUNS = 5; - - /** - * Iterates over a dataflow graph and checks whether all links are consistent. - */ - function checkLinks(nodes) { - for (const node of nodes) { - for (const child of node.children) { - if (child.parent !== node) { - // log.error('Dataflow graph is inconsistent.', node, child); - return false; - } - } - if (!checkLinks(node.children)) { - return false; - } - } - return true; - } - - /** - * Run the specified optimizer on the provided nodes. - * - * @param optimizer The optimizer instance to run. - * @param nodes A set of nodes to optimize. - */ - function runOptimizer(optimizer, nodes) { - let modified = false; - for (const node of nodes) { - modified = optimizer.optimize(node) || modified; - } - return modified; - } - function optimizationDataflowHelper(dataComponent, model, firstPass) { - let roots = dataComponent.sources; - let modified = false; - modified = runOptimizer(new RemoveUnnecessaryOutputNodes(), roots) || modified; - modified = runOptimizer(new RemoveUnnecessaryIdentifierNodes(model), roots) || modified; - - // remove source nodes that don't have any children because they also don't have output nodes - roots = roots.filter(r => r.numChildren() > 0); - modified = runOptimizer(new RemoveUnusedSubtrees(), roots) || modified; - roots = roots.filter(r => r.numChildren() > 0); - if (!firstPass) { - // Only run these optimizations after the optimizer has moved down the facet node. - // With this change, we can be more aggressive in the optimizations. - modified = runOptimizer(new MoveParseUp(), roots) || modified; - modified = runOptimizer(new MergeBins(model), roots) || modified; - modified = runOptimizer(new RemoveDuplicateTimeUnits(), roots) || modified; - modified = runOptimizer(new MergeParse(), roots) || modified; - modified = runOptimizer(new MergeAggregates(), roots) || modified; - modified = runOptimizer(new MergeTimeUnits(), roots) || modified; - modified = runOptimizer(new MergeIdenticalNodes(), roots) || modified; - modified = runOptimizer(new MergeOutputs(), roots) || modified; - } - dataComponent.sources = roots; - return modified; - } - - /** - * Optimizes the dataflow of the passed in data component. - */ - function optimizeDataflow(data, model) { - // check before optimizations - checkLinks(data.sources); - let firstPassCounter = 0; - let secondPassCounter = 0; - for (let i = 0; i < MAX_OPTIMIZATION_RUNS; i++) { - if (!optimizationDataflowHelper(data, model, true)) { - break; - } - firstPassCounter++; - } - - // move facets down and make a copy of the subtree so that we can have scales at the top level - data.sources.map(moveFacetDown); - for (let i = 0; i < MAX_OPTIMIZATION_RUNS; i++) { - if (!optimizationDataflowHelper(data, model, false)) { - break; - } - secondPassCounter++; - } - - // check after optimizations - checkLinks(data.sources); - if (Math.max(firstPassCounter, secondPassCounter) === MAX_OPTIMIZATION_RUNS) { - warn(`Maximum optimization runs(${MAX_OPTIMIZATION_RUNS}) reached.`); - } - } - - /** - * A class that behaves like a SignalRef but lazily generates the signal. - * The provided generator function should use `Model.getSignalName` to use the correct signal name. - */ - class SignalRefWrapper { - constructor(exprGenerator) { - Object.defineProperty(this, 'signal', { - enumerable: true, - get: exprGenerator - }); - } - // for ts - - static fromName(rename, signalName) { - return new SignalRefWrapper(() => rename(signalName)); - } - } - - function parseScaleDomain(model) { - if (isUnitModel(model)) { - parseUnitScaleDomain(model); - } else { - parseNonUnitScaleDomain(model); - } - } - function parseUnitScaleDomain(model) { - const localScaleComponents = model.component.scales; - for (const channel of keys(localScaleComponents)) { - const domains = parseDomainForChannel(model, channel); - const localScaleCmpt = localScaleComponents[channel]; - localScaleCmpt.setWithExplicit('domains', domains); - parseSelectionDomain(model, channel); - if (model.component.data.isFaceted) { - // get resolve from closest facet parent as this decides whether we need to refer to cloned subtree or not - let facetParent = model; - while (!isFacetModel(facetParent) && facetParent.parent) { - facetParent = facetParent.parent; - } - const resolve = facetParent.component.resolve.scale[channel]; - if (resolve === 'shared') { - for (const domain of domains.value) { - // Replace the scale domain with data output from a cloned subtree after the facet. - if (isDataRefDomain(domain)) { - // use data from cloned subtree (which is the same as data but with a prefix added once) - domain.data = FACET_SCALE_PREFIX + domain.data.replace(FACET_SCALE_PREFIX, ''); - } - } - } - } - } - } - function parseNonUnitScaleDomain(model) { - for (const child of model.children) { - parseScaleDomain(child); - } - const localScaleComponents = model.component.scales; - for (const channel of keys(localScaleComponents)) { - let domains; - let selectionExtent = null; - for (const child of model.children) { - const childComponent = child.component.scales[channel]; - if (childComponent) { - if (domains === undefined) { - domains = childComponent.getWithExplicit('domains'); - } else { - domains = mergeValuesWithExplicit(domains, childComponent.getWithExplicit('domains'), 'domains', 'scale', domainsTieBreaker); - } - const se = childComponent.get('selectionExtent'); - if (selectionExtent && se && selectionExtent.param !== se.param) { - warn(NEEDS_SAME_SELECTION); - } - selectionExtent = se; - } - } - localScaleComponents[channel].setWithExplicit('domains', domains); - if (selectionExtent) { - localScaleComponents[channel].set('selectionExtent', selectionExtent, true); - } - } - } - - /** - * Remove unaggregated domain if it is not applicable - * Add unaggregated domain if domain is not specified and config.scale.useUnaggregatedDomain is true. - */ - function normalizeUnaggregatedDomain(domain, fieldDef, scaleType, scaleConfig) { - if (domain === 'unaggregated') { - const { - valid, - reason - } = canUseUnaggregatedDomain(fieldDef, scaleType); - if (!valid) { - warn(reason); - return undefined; - } - } else if (domain === undefined && scaleConfig.useUnaggregatedDomain) { - // Apply config if domain is not specified. - const { - valid - } = canUseUnaggregatedDomain(fieldDef, scaleType); - if (valid) { - return 'unaggregated'; - } - } - return domain; - } - function parseDomainForChannel(model, channel) { - const scaleType = model.getScaleComponent(channel).get('type'); - const { - encoding - } = model; - const domain = normalizeUnaggregatedDomain(model.scaleDomain(channel), model.typedFieldDef(channel), scaleType, model.config.scale); - if (domain !== model.scaleDomain(channel)) { - model.specifiedScales[channel] = { - ...model.specifiedScales[channel], - domain - }; - } - - // If channel is either X or Y then union them with X2 & Y2 if they exist - if (channel === 'x' && getFieldOrDatumDef(encoding.x2)) { - if (getFieldOrDatumDef(encoding.x)) { - return mergeValuesWithExplicit(parseSingleChannelDomain(scaleType, domain, model, 'x'), parseSingleChannelDomain(scaleType, domain, model, 'x2'), 'domain', 'scale', domainsTieBreaker); - } else { - return parseSingleChannelDomain(scaleType, domain, model, 'x2'); - } - } else if (channel === 'y' && getFieldOrDatumDef(encoding.y2)) { - if (getFieldOrDatumDef(encoding.y)) { - return mergeValuesWithExplicit(parseSingleChannelDomain(scaleType, domain, model, 'y'), parseSingleChannelDomain(scaleType, domain, model, 'y2'), 'domain', 'scale', domainsTieBreaker); - } else { - return parseSingleChannelDomain(scaleType, domain, model, 'y2'); - } - } - return parseSingleChannelDomain(scaleType, domain, model, channel); - } - function mapDomainToDataSignal(domain, type, timeUnit) { - return domain.map(v => { - const data = valueExpr(v, { - timeUnit, - type - }); - return { - signal: `{data: ${data}}` - }; - }); - } - function convertDomainIfItIsDateTime(domain, type, timeUnit) { - // explicit value - const normalizedTimeUnit = normalizeTimeUnit(timeUnit)?.unit; - if (type === 'temporal' || normalizedTimeUnit) { - return mapDomainToDataSignal(domain, type, normalizedTimeUnit); - } - return [domain]; // Date time won't make sense - } - function parseSingleChannelDomain(scaleType, domain, model, channel) { - const { - encoding, - markDef, - mark, - config, - stack - } = model; - const fieldOrDatumDef = getFieldOrDatumDef(encoding[channel]); - const { - type - } = fieldOrDatumDef; - const timeUnit = fieldOrDatumDef['timeUnit']; - const dataSourceTypeForScaleDomain = getScaleDataSourceForHandlingInvalidValues({ - invalid: getMarkConfig('invalid', markDef, config), - isPath: isPathMark(mark) - }); - if (isDomainUnionWith(domain)) { - const defaultDomain = parseSingleChannelDomain(scaleType, undefined, model, channel); - const unionWith = convertDomainIfItIsDateTime(domain.unionWith, type, timeUnit); - return makeExplicit([...unionWith, ...defaultDomain.value]); - } else if (isSignalRef(domain)) { - return makeExplicit([domain]); - } else if (domain && domain !== 'unaggregated' && !isParameterDomain(domain)) { - return makeExplicit(convertDomainIfItIsDateTime(domain, type, timeUnit)); - } - if (stack && channel === stack.fieldChannel) { - if (stack.offset === 'normalize') { - return makeImplicit([[0, 1]]); - } - const data = model.requestDataName(dataSourceTypeForScaleDomain); - return makeImplicit([{ - data, - field: model.vgField(channel, { - suffix: 'start' - }) - }, { - data, - field: model.vgField(channel, { - suffix: 'end' - }) - }]); - } - const sort = isScaleChannel(channel) && isFieldDef(fieldOrDatumDef) ? domainSort(model, channel, scaleType) : undefined; - if (isDatumDef(fieldOrDatumDef)) { - const d = convertDomainIfItIsDateTime([fieldOrDatumDef.datum], type, timeUnit); - return makeImplicit(d); - } - const fieldDef = fieldOrDatumDef; // now we can be sure it's a fieldDef - if (domain === 'unaggregated') { - const { - field - } = fieldOrDatumDef; - return makeImplicit([{ - data: model.requestDataName(dataSourceTypeForScaleDomain), - field: vgField({ - field, - aggregate: 'min' - }) - }, { - data: model.requestDataName(dataSourceTypeForScaleDomain), - field: vgField({ - field, - aggregate: 'max' - }) - }]); - } else if (isBinning(fieldDef.bin)) { - if (hasDiscreteDomain(scaleType)) { - if (scaleType === 'bin-ordinal') { - // we can omit the domain as it is inferred from the `bins` property - return makeImplicit([]); - } - - // ordinal bin scale takes domain from bin_range, ordered by bin start - // This is useful for both axis-based scale (x/y) and legend-based scale (other channels). - return makeImplicit([{ - // If sort by aggregation of a specified sort field, we need to use RAW table, - // so we can aggregate values for the scale independently from the main aggregation. - data: isBoolean(sort) ? model.requestDataName(dataSourceTypeForScaleDomain) : model.requestDataName(DataSourceType.Raw), - // Use range if we added it and the scale does not support computing a range as a signal. - field: model.vgField(channel, binRequiresRange(fieldDef, channel) ? { - binSuffix: 'range' - } : {}), - // we have to use a sort object if sort = true to make the sort correct by bin start - sort: sort === true || !vega.isObject(sort) ? { - field: model.vgField(channel, {}), - op: 'min' // min or max doesn't matter since we sort by the start of the bin range - } : sort - }]); - } else { - // continuous scales - const { - bin - } = fieldDef; - if (isBinning(bin)) { - const binSignal = getBinSignalName(model, fieldDef.field, bin); - return makeImplicit([new SignalRefWrapper(() => { - const signal = model.getSignalName(binSignal); - return `[${signal}.start, ${signal}.stop]`; - })]); - } else { - return makeImplicit([{ - data: model.requestDataName(dataSourceTypeForScaleDomain), - field: model.vgField(channel, {}) - }]); - } - } - } else if (fieldDef.timeUnit && contains(['time', 'utc'], scaleType)) { - const fieldDef2 = encoding[getSecondaryRangeChannel(channel)]; - if (hasBandEnd(fieldDef, fieldDef2, markDef, config)) { - const data = model.requestDataName(dataSourceTypeForScaleDomain); - const bandPosition = getBandPosition({ - fieldDef, - fieldDef2, - markDef, - config - }); - const isRectWithOffset = isRectBasedMark(mark) && bandPosition !== 0.5 && isXorY(channel); - return makeImplicit([{ - data, - field: model.vgField(channel, isRectWithOffset ? { - suffix: OFFSETTED_RECT_START_SUFFIX - } : {}) - }, { - data, - field: model.vgField(channel, { - suffix: isRectWithOffset ? OFFSETTED_RECT_END_SUFFIX : 'end' - }) - }]); - } - } - if (sort) { - return makeImplicit([{ - // If sort by aggregation of a specified sort field, we need to use RAW table, - // so we can aggregate values for the scale independently from the main aggregation. - data: isBoolean(sort) ? model.requestDataName(dataSourceTypeForScaleDomain) : model.requestDataName(DataSourceType.Raw), - field: model.vgField(channel), - sort - }]); - } else { - return makeImplicit([{ - data: model.requestDataName(dataSourceTypeForScaleDomain), - field: model.vgField(channel) - }]); - } - } - function normalizeSortField(sort, isStackedMeasure) { - const { - op, - field, - order - } = sort; - return { - // Apply default op - op: op ?? (isStackedMeasure ? 'sum' : DEFAULT_SORT_OP), - // flatten nested fields - ...(field ? { - field: replacePathInField(field) - } : {}), - ...(order ? { - order - } : {}) - }; - } - function parseSelectionDomain(model, channel) { - const scale = model.component.scales[channel]; - const spec = model.specifiedScales[channel].domain; - const bin = model.fieldDef(channel)?.bin; - const domain = isParameterDomain(spec) && spec; - const extent = isBinParams(bin) && isParameterExtent(bin.extent) && bin.extent; - if (domain || extent) { - // As scale parsing occurs before selection parsing, we cannot set - // domainRaw directly. So instead, we store the selectionExtent on - // the scale component, and then add domainRaw during scale assembly. - scale.set('selectionExtent', domain ?? extent, true); - } - } - function domainSort(model, channel, scaleType) { - if (!hasDiscreteDomain(scaleType)) { - return undefined; - } - - // save to cast as the only exception is the geojson type for shape, which would not generate a scale - const fieldDef = model.fieldDef(channel); - const sort = fieldDef.sort; - - // if the sort is specified with array, use the derived sort index field - if (isSortArray(sort)) { - return { - op: 'min', - field: sortArrayIndexField(fieldDef, channel), - order: 'ascending' - }; - } - const { - stack - } = model; - const stackDimensions = stack ? new Set([...stack.groupbyFields, ...stack.stackBy.map(s => s.fieldDef.field)]) : undefined; - - // Sorted based on an aggregate calculation over a specified sort field (only for ordinal scale) - if (isSortField(sort)) { - const isStackedMeasure = stack && !stackDimensions.has(sort.field); - return normalizeSortField(sort, isStackedMeasure); - } else if (isSortByEncoding(sort)) { - const { - encoding, - order - } = sort; - const fieldDefToSortBy = model.fieldDef(encoding); - const { - aggregate, - field - } = fieldDefToSortBy; - const isStackedMeasure = stack && !stackDimensions.has(field); - if (isArgminDef(aggregate) || isArgmaxDef(aggregate)) { - return normalizeSortField({ - field: vgField(fieldDefToSortBy), - order - }, isStackedMeasure); - } else if (isAggregateOp(aggregate) || !aggregate) { - return normalizeSortField({ - op: aggregate, - // can't be argmin/argmax since we don't support them in encoding field def - field, - order - }, isStackedMeasure); - } - } else if (sort === 'descending') { - return { - op: 'min', - field: model.vgField(channel), - order: 'descending' - }; - } else if (contains(['ascending', undefined /* default =ascending*/], sort)) { - return true; - } - - // sort == null - return undefined; - } - - /** - * Determine if a scale can use unaggregated domain. - * @return {Boolean} Returns true if all of the following conditions apply: - * 1. `scale.domain` is `unaggregated` - * 2. Aggregation function is not `count` or `sum` - * 3. The scale is quantitative or time scale. - */ - function canUseUnaggregatedDomain(fieldDef, scaleType) { - const { - aggregate, - type - } = fieldDef; - if (!aggregate) { - return { - valid: false, - reason: unaggregateDomainHasNoEffectForRawField(fieldDef) - }; - } - if (vega.isString(aggregate) && !SHARED_DOMAIN_OPS.has(aggregate)) { - return { - valid: false, - reason: unaggregateDomainWithNonSharedDomainOp(aggregate) - }; - } - if (type === 'quantitative') { - if (scaleType === 'log') { - return { - valid: false, - reason: unaggregatedDomainWithLogScale(fieldDef) - }; - } - } - return { - valid: true - }; - } - - /** - * Tie breaker for mergeValuesWithExplicit for domains. We concat the specified values. - */ - function domainsTieBreaker(v1, v2, property, propertyOf) { - if (v1.explicit && v2.explicit) { - warn(mergeConflictingDomainProperty(property, propertyOf, v1.value, v2.value)); - } - // If equal score, concat the domains so that we union them later. - return { - explicit: v1.explicit, - value: [...v1.value, ...v2.value] - }; - } - - /** - * Converts an array of domains to a single Vega scale domain. - */ - function mergeDomains(domains) { - const uniqueDomains = unique(domains.map(domain => { - // ignore sort property when computing the unique domains - if (isDataRefDomain(domain)) { - const { - sort: _s, - ...domainWithoutSort - } = domain; - return domainWithoutSort; - } - return domain; - }), hash); - const sorts = unique(domains.map(d => { - if (isDataRefDomain(d)) { - const s = d.sort; - if (s !== undefined && !isBoolean(s)) { - if ('op' in s && s.op === 'count') { - // let's make sure that if op is count, we don't use a field - delete s.field; - } - if (s.order === 'ascending') { - // drop order: ascending as it is the default - delete s.order; - } - } - return s; - } - return undefined; - }).filter(s => s !== undefined), hash); - if (uniqueDomains.length === 0) { - return undefined; - } else if (uniqueDomains.length === 1) { - const domain = domains[0]; - if (isDataRefDomain(domain) && sorts.length > 0) { - let sort = sorts[0]; - if (sorts.length > 1) { - warn(MORE_THAN_ONE_SORT); - // Get sorts with non-default ops - const filteredSorts = sorts.filter(s => vega.isObject(s) && 'op' in s && s.op !== 'min'); - if (sorts.every(s => vega.isObject(s) && 'op' in s) && filteredSorts.length === 1) { - sort = filteredSorts[0]; - } else { - sort = true; - } - } else { - // Simplify domain sort by removing field and op when the field is the same as the domain field. - if (vega.isObject(sort) && 'field' in sort) { - const sortField = sort.field; - if (domain.field === sortField) { - sort = sort.order ? { - order: sort.order - } : true; - } - } - } - return { - ...domain, - sort - }; - } - return domain; - } - - // only keep sort properties that work with unioned domains - const unionDomainSorts = unique(sorts.map(s => { - if (isBoolean(s) || !('op' in s) || vega.isString(s.op) && s.op in MULTIDOMAIN_SORT_OP_INDEX) { - return s; - } - warn(domainSortDropped(s)); - return true; - }), hash); - let sort; - if (unionDomainSorts.length === 1) { - sort = unionDomainSorts[0]; - } else if (unionDomainSorts.length > 1) { - warn(MORE_THAN_ONE_SORT); - sort = true; - } - const allData = unique(domains.map(d => { - if (isDataRefDomain(d)) { - return d.data; - } - return null; - }), x => x); - if (allData.length === 1 && allData[0] !== null) { - // create a union domain of different fields with a single data source - const domain = { - data: allData[0], - fields: uniqueDomains.map(d => d.field), - ...(sort ? { - sort - } : {}) - }; - return domain; - } - return { - fields: uniqueDomains, - ...(sort ? { - sort - } : {}) - }; - } - - /** - * Return a field if a scale uses a single field. - * Return `undefined` otherwise. - */ - function getFieldFromDomain(domain) { - if (isDataRefDomain(domain) && vega.isString(domain.field)) { - return domain.field; - } else if (isDataRefUnionedDomain(domain)) { - let field; - for (const nonUnionDomain of domain.fields) { - if (isDataRefDomain(nonUnionDomain) && vega.isString(nonUnionDomain.field)) { - if (!field) { - field = nonUnionDomain.field; - } else if (field !== nonUnionDomain.field) { - warn(FACETED_INDEPENDENT_DIFFERENT_SOURCES); - return field; - } - } - } - warn(FACETED_INDEPENDENT_SAME_FIELDS_DIFFERENT_SOURCES); - return field; - } else if (isFieldRefUnionDomain(domain)) { - warn(FACETED_INDEPENDENT_SAME_SOURCE); - const field = domain.fields[0]; - return vega.isString(field) ? field : undefined; - } - return undefined; - } - function assembleDomain(model, channel) { - const scaleComponent = model.component.scales[channel]; - const domains = scaleComponent.get('domains').map(domain => { - // Correct references to data as the original domain's data was determined - // in parseScale, which happens before parseData. Thus the original data - // reference can be incorrect. - if (isDataRefDomain(domain)) { - domain.data = model.lookupDataSource(domain.data); - } - return domain; - }); - - // domains is an array that has to be merged into a single vega domain - return mergeDomains(domains); - } - - function assembleScales(model) { - if (isLayerModel(model) || isConcatModel(model)) { - // For concat and layer, include scales of children too - return model.children.reduce((scales, child) => { - return scales.concat(assembleScales(child)); - }, assembleScalesForModel(model)); - } else { - // For facet, child scales would not be included in the parent's scope. - // For unit, there is no child. - return assembleScalesForModel(model); - } - } - function assembleScalesForModel(model) { - return keys(model.component.scales).reduce((scales, channel) => { - const scaleComponent = model.component.scales[channel]; - if (scaleComponent.merged) { - // Skipped merged scales - return scales; - } - const scale = scaleComponent.combine(); - const { - name, - type, - selectionExtent, - domains: _d, - range: _r, - reverse, - ...otherScaleProps - } = scale; - const range = assembleScaleRange(scale.range, name, channel, model); - const domain = assembleDomain(model, channel); - const domainRaw = selectionExtent ? assembleSelectionScaleDomain(model, selectionExtent, scaleComponent, domain) : null; - scales.push({ - name, - type, - ...(domain ? { - domain - } : {}), - ...(domainRaw ? { - domainRaw - } : {}), - range, - ...(reverse !== undefined ? { - reverse: reverse - } : {}), - ...otherScaleProps - }); - return scales; - }, []); - } - function assembleScaleRange(scaleRange, scaleName, channel, model) { - // add signals to x/y range - if (isXorY(channel)) { - if (isVgRangeStep(scaleRange)) { - // For width/height step, use a signal created in layout assemble instead of a constant step. - return { - step: { - signal: `${scaleName}_step` - } - }; - } - } else if (vega.isObject(scaleRange) && isDataRefDomain(scaleRange)) { - return { - ...scaleRange, - data: model.lookupDataSource(scaleRange.data) - }; - } - return scaleRange; - } - - /** - * All VgDomain property except domain. - * (We exclude domain as we have a special "domains" array that allow us merge them all at once in assemble.) - */ - - class ScaleComponent extends Split { - merged = false; - constructor(name, typeWithExplicit) { - super({}, - // no initial explicit property - { - name - } // name as initial implicit property - ); - this.setWithExplicit('type', typeWithExplicit); - } - - /** - * Whether the scale definitely includes or not include zero in the domain - */ - domainHasZero() { - const scaleType = this.get('type'); - if (contains([ScaleType.LOG, ScaleType.TIME, ScaleType.UTC], scaleType)) { - // Log scales cannot have zero. - // Zero in time scale is arbitrary, and does not affect ratio. - // (Time is an interval level of measurement, not ratio). - // See https://en.wikipedia.org/wiki/Level_of_measurement for more info. - return 'definitely-not'; - } - const scaleZero = this.get('zero'); - if (scaleZero === true || - // If zero is undefined, linear/sqrt/pow scales have zero by default. - scaleZero === undefined && contains([ScaleType.LINEAR, ScaleType.SQRT, ScaleType.POW], scaleType)) { - return 'definitely'; - } - const domains = this.get('domains'); - if (domains.length > 0) { - let hasExplicitDomainWithZero = false; - let hasExplicitDomainWithoutZero = false; - let hasDomainBasedOnField = false; - for (const d of domains) { - if (vega.isArray(d)) { - const first = d[0]; - const last = d[d.length - 1]; - if (vega.isNumber(first) && vega.isNumber(last)) { - if (first <= 0 && last >= 0) { - hasExplicitDomainWithZero = true; - continue; - } else { - hasExplicitDomainWithoutZero = true; - continue; - } - } - } - hasDomainBasedOnField = true; - } - if (hasExplicitDomainWithZero) { - return 'definitely'; - } else if (hasExplicitDomainWithoutZero && !hasDomainBasedOnField) { - return 'definitely-not'; - } - } - return 'maybe'; - } - } - - const RANGE_PROPERTIES = ['range', 'scheme']; - function parseUnitScaleRange(model) { - const localScaleComponents = model.component.scales; - - // use SCALE_CHANNELS instead of scales[channel] to ensure that x, y come first! - for (const channel of SCALE_CHANNELS) { - const localScaleCmpt = localScaleComponents[channel]; - if (!localScaleCmpt) { - continue; - } - const rangeWithExplicit = parseRangeForChannel(channel, model); - localScaleCmpt.setWithExplicit('range', rangeWithExplicit); - } - } - function getBinStepSignal(model, channel) { - const fieldDef = model.fieldDef(channel); - if (fieldDef?.bin) { - const { - bin, - field - } = fieldDef; - const sizeType = getSizeChannel(channel); - const sizeSignal = model.getName(sizeType); - if (vega.isObject(bin) && bin.binned && bin.step !== undefined) { - return new SignalRefWrapper(() => { - const scaleName = model.scaleName(channel); - const binCount = `(domain("${scaleName}")[1] - domain("${scaleName}")[0]) / ${bin.step}`; - return `${model.getSignalName(sizeSignal)} / (${binCount})`; - }); - } else if (isBinning(bin)) { - const binSignal = getBinSignalName(model, field, bin); - - // TODO: extract this to be range step signal - return new SignalRefWrapper(() => { - const updatedName = model.getSignalName(binSignal); - const binCount = `(${updatedName}.stop - ${updatedName}.start) / ${updatedName}.step`; - return `${model.getSignalName(sizeSignal)} / (${binCount})`; - }); - } - } - return undefined; - } - - /** - * Return mixins that includes one of the Vega range types (explicit range, range.step, range.scheme). - */ - function parseRangeForChannel(channel, model) { - const specifiedScale = model.specifiedScales[channel]; - const { - size - } = model; - const mergedScaleCmpt = model.getScaleComponent(channel); - const scaleType = mergedScaleCmpt.get('type'); - - // Check if any of the range properties is specified. - // If so, check if it is compatible and make sure that we only output one of the properties - for (const property of RANGE_PROPERTIES) { - if (specifiedScale[property] !== undefined) { - const supportedByScaleType = scaleTypeSupportProperty(scaleType, property); - const channelIncompatability = channelScalePropertyIncompatability(channel, property); - if (!supportedByScaleType) { - warn(scalePropertyNotWorkWithScaleType(scaleType, property, channel)); - } else if (channelIncompatability) { - // channel - warn(channelIncompatability); - } else { - switch (property) { - case 'range': - { - const range = specifiedScale.range; - if (vega.isArray(range)) { - if (isXorY(channel)) { - return makeExplicit(range.map(v => { - if (v === 'width' || v === 'height') { - // get signal for width/height - - // Just like default range logic below, we use SignalRefWrapper to account for potential merges and renames. - - const sizeSignal = model.getName(v); - const getSignalName = model.getSignalName.bind(model); - return SignalRefWrapper.fromName(getSignalName, sizeSignal); - } - return v; - })); - } - } else if (vega.isObject(range)) { - return makeExplicit({ - data: model.requestDataName(DataSourceType.Main), - field: range.field, - sort: { - op: 'min', - field: model.vgField(channel) - } - }); - } - return makeExplicit(range); - } - case 'scheme': - return makeExplicit(parseScheme(specifiedScale[property])); - } - } - } - } - const sizeChannel = channel === X || channel === 'xOffset' ? 'width' : 'height'; - const sizeValue = size[sizeChannel]; - if (isStep(sizeValue)) { - if (isXorY(channel)) { - if (hasDiscreteDomain(scaleType)) { - const step = getPositionStep(sizeValue, model, channel); - // Need to be explicit so layer with step wins over layer without step - if (step) { - return makeExplicit({ - step - }); - } - } else { - warn(stepDropped(sizeChannel)); - } - } else if (isXorYOffset(channel)) { - const positionChannel = channel === XOFFSET ? 'x' : 'y'; - const positionScaleCmpt = model.getScaleComponent(positionChannel); - const positionScaleType = positionScaleCmpt.get('type'); - if (positionScaleType === 'band') { - const step = getOffsetStep(sizeValue, scaleType); - if (step) { - return makeExplicit(step); - } - } - } - } - const { - rangeMin, - rangeMax - } = specifiedScale; - const d = defaultRange(channel, model); - if ((rangeMin !== undefined || rangeMax !== undefined) && - // it's ok to check just rangeMin's compatibility since rangeMin/rangeMax are the same - scaleTypeSupportProperty(scaleType, 'rangeMin') && vega.isArray(d) && d.length === 2) { - return makeExplicit([rangeMin ?? d[0], rangeMax ?? d[1]]); - } - return makeImplicit(d); - } - function parseScheme(scheme) { - if (isExtendedScheme(scheme)) { - return { - scheme: scheme.name, - ...omit(scheme, ['name']) - }; - } - return { - scheme - }; - } - function fullWidthOrHeightRange(channel, model, scaleType) { - let { - center - } = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; - // If step is null, use zero to width or height. - // Note that we use SignalRefWrapper to account for potential merges and renames. - const sizeType = getSizeChannel(channel); - const sizeSignal = model.getName(sizeType); - const getSignalName = model.getSignalName.bind(model); - if (channel === Y && hasContinuousDomain(scaleType)) { - // For y continuous scale, we have to start from the height as the bottom part has the max value. - return center ? [SignalRefWrapper.fromName(name => `${getSignalName(name)}/2`, sizeSignal), SignalRefWrapper.fromName(name => `-${getSignalName(name)}/2`, sizeSignal)] : [SignalRefWrapper.fromName(getSignalName, sizeSignal), 0]; - } else { - return center ? [SignalRefWrapper.fromName(name => `-${getSignalName(name)}/2`, sizeSignal), SignalRefWrapper.fromName(name => `${getSignalName(name)}/2`, sizeSignal)] : [0, SignalRefWrapper.fromName(getSignalName, sizeSignal)]; - } - } - function defaultRange(channel, model) { - const { - size, - config, - mark, - encoding - } = model; - const { - type - } = getFieldOrDatumDef(encoding[channel]); - const mergedScaleCmpt = model.getScaleComponent(channel); - const scaleType = mergedScaleCmpt.get('type'); - const { - domain, - domainMid - } = model.specifiedScales[channel]; - switch (channel) { - case X: - case Y: - { - // If there is no explicit width/height for discrete x/y scales - if (contains(['point', 'band'], scaleType)) { - const positionSize = getDiscretePositionSize(channel, size, config.view); - if (isStep(positionSize)) { - const step = getPositionStep(positionSize, model, channel); - return { - step - }; - } - } - return fullWidthOrHeightRange(channel, model, scaleType); - } - case XOFFSET: - case YOFFSET: - return getOffsetRange(channel, model, scaleType); - case SIZE: - { - // TODO: support custom rangeMin, rangeMax - const rangeMin = sizeRangeMin(mark, config); - const rangeMax = sizeRangeMax(mark, size, model, config); - if (isContinuousToDiscrete(scaleType)) { - return interpolateRange(rangeMin, rangeMax, defaultContinuousToDiscreteCount(scaleType, config, domain, channel)); - } else { - return [rangeMin, rangeMax]; - } - } - case THETA: - return [0, Math.PI * 2]; - case ANGLE: - // TODO: add config.scale.min/maxAngleDegree (for point and text) and config.scale.min/maxAngleRadian (for arc) once we add arc marks. - // (It's weird to add just config.scale.min/maxAngleDegree for now) - return [0, 360]; - case RADIUS: - { - // max radius = half od min(width,height) - - return [0, new SignalRefWrapper(() => { - const w = model.getSignalName(isFacetModel(model.parent) ? 'child_width' : 'width'); - const h = model.getSignalName(isFacetModel(model.parent) ? 'child_height' : 'height'); - return `min(${w},${h})/2`; - })]; - } - case STROKEWIDTH: - // TODO: support custom rangeMin, rangeMax - return [config.scale.minStrokeWidth, config.scale.maxStrokeWidth]; - case STROKEDASH: - return [ - // TODO: add this to Vega's config.range? - [1, 0], [4, 2], [2, 1], [1, 1], [1, 2, 4, 2]]; - case SHAPE: - return 'symbol'; - case COLOR: - case FILL: - case STROKE: - if (scaleType === 'ordinal') { - // Only nominal data uses ordinal scale by default - return type === 'nominal' ? 'category' : 'ordinal'; - } else { - if (domainMid !== undefined) { - return 'diverging'; - } else { - return mark === 'rect' || mark === 'geoshape' ? 'heatmap' : 'ramp'; - } - } - case OPACITY: - case FILLOPACITY: - case STROKEOPACITY: - // TODO: support custom rangeMin, rangeMax - return [config.scale.minOpacity, config.scale.maxOpacity]; - } - } - function getPositionStep(step, model, channel) { - const { - encoding - } = model; - const mergedScaleCmpt = model.getScaleComponent(channel); - const offsetChannel = getOffsetScaleChannel(channel); - const offsetDef = encoding[offsetChannel]; - const stepFor = getStepFor({ - step, - offsetIsDiscrete: isFieldOrDatumDef(offsetDef) && isDiscrete$1(offsetDef.type) - }); - if (stepFor === 'offset' && channelHasFieldOrDatum(encoding, offsetChannel)) { - const offsetScaleCmpt = model.getScaleComponent(offsetChannel); - const offsetScaleName = model.scaleName(offsetChannel); - let stepCount = `domain('${offsetScaleName}').length`; - if (offsetScaleCmpt.get('type') === 'band') { - const offsetPaddingInner = offsetScaleCmpt.get('paddingInner') ?? offsetScaleCmpt.get('padding') ?? 0; - const offsetPaddingOuter = offsetScaleCmpt.get('paddingOuter') ?? offsetScaleCmpt.get('padding') ?? 0; - stepCount = `bandspace(${stepCount}, ${offsetPaddingInner}, ${offsetPaddingOuter})`; - } - const paddingInner = mergedScaleCmpt.get('paddingInner') ?? mergedScaleCmpt.get('padding'); - return { - signal: `${step.step} * ${stepCount} / (1-${exprFromSignalRefOrValue(paddingInner)})` - }; - } else { - return step.step; - } - } - function getOffsetStep(step, offsetScaleType) { - const stepFor = getStepFor({ - step, - offsetIsDiscrete: hasDiscreteDomain(offsetScaleType) - }); - if (stepFor === 'offset') { - return { - step: step.step - }; - } - return undefined; - } - function getOffsetRange(channel, model, offsetScaleType) { - const positionChannel = channel === XOFFSET ? 'x' : 'y'; - const positionScaleCmpt = model.getScaleComponent(positionChannel); - if (!positionScaleCmpt) { - return fullWidthOrHeightRange(positionChannel, model, offsetScaleType, { - center: true - }); - } - const positionScaleType = positionScaleCmpt.get('type'); - const positionScaleName = model.scaleName(positionChannel); - const { - markDef, - config - } = model; - if (positionScaleType === 'band') { - const size = getDiscretePositionSize(positionChannel, model.size, model.config.view); - if (isStep(size)) { - // step is for offset - const step = getOffsetStep(size, offsetScaleType); - if (step) { - return step; - } - } - // otherwise use the position - return [0, { - signal: `bandwidth('${positionScaleName}')` - }]; - } else { - // continuous scale - const positionDef = model.encoding[positionChannel]; - if (isFieldDef(positionDef) && positionDef.timeUnit) { - const duration = durationExpr(positionDef.timeUnit, expr => `scale('${positionScaleName}', ${expr})`); - const padding = model.config.scale.bandWithNestedOffsetPaddingInner; - const bandPositionOffset = getBandPosition({ - fieldDef: positionDef, - markDef, - config - }) - 0.5; - const bandPositionOffsetExpr = bandPositionOffset !== 0 ? ` + ${bandPositionOffset}` : ''; - if (padding) { - const startRatio = isSignalRef(padding) ? `${padding.signal}/2` + bandPositionOffsetExpr : `${padding / 2 + bandPositionOffset}`; - const endRatio = isSignalRef(padding) ? `(1 - ${padding.signal}/2)` + bandPositionOffsetExpr : `${1 - padding / 2 + bandPositionOffset}`; - return [{ - signal: `${startRatio} * (${duration})` - }, { - signal: `${endRatio} * (${duration})` - }]; - } - return [0, { - signal: duration - }]; - } - return never(`Cannot use ${channel} scale if ${positionChannel} scale is not discrete.`); - } - } - function getDiscretePositionSize(channel, size, viewConfig) { - const sizeChannel = channel === X ? 'width' : 'height'; - const sizeValue = size[sizeChannel]; - if (sizeValue) { - return sizeValue; - } - return getViewConfigDiscreteSize(viewConfig, sizeChannel); - } - function defaultContinuousToDiscreteCount(scaleType, config, domain, channel) { - switch (scaleType) { - case 'quantile': - return config.scale.quantileCount; - case 'quantize': - return config.scale.quantizeCount; - case 'threshold': - if (domain !== undefined && vega.isArray(domain)) { - return domain.length + 1; - } else { - warn(domainRequiredForThresholdScale(channel)); - // default threshold boundaries for threshold scale since domain has cardinality of 2 - return 3; - } - } - } - - /** - * Returns the linear interpolation of the range according to the cardinality - * - * @param rangeMin start of the range - * @param rangeMax end of the range - * @param cardinality number of values in the output range - */ - function interpolateRange(rangeMin, rangeMax, cardinality) { - // always return a signal since it's better to compute the sequence in Vega later - const f = () => { - const rMax = signalOrStringValue(rangeMax); - const rMin = signalOrStringValue(rangeMin); - const step = `(${rMax} - ${rMin}) / (${cardinality} - 1)`; - return `sequence(${rMin}, ${rMax} + ${step}, ${step})`; - }; - if (isSignalRef(rangeMax)) { - return new SignalRefWrapper(f); - } else { - return { - signal: f() - }; - } - } - function sizeRangeMin(mark, config) { - switch (mark) { - case 'bar': - case 'tick': - return config.scale.minBandSize; - case 'line': - case 'trail': - case 'rule': - return config.scale.minStrokeWidth; - case 'text': - return config.scale.minFontSize; - case 'point': - case 'square': - case 'circle': - return config.scale.minSize; - } - /* istanbul ignore next: should never reach here */ - // sizeRangeMin not implemented for the mark - throw new Error(incompatibleChannel('size', mark)); - } - const MAX_SIZE_RANGE_STEP_RATIO = 0.95; - function sizeRangeMax(mark, size, model, config) { - const xyStepSignals = { - x: getBinStepSignal(model, 'x'), - y: getBinStepSignal(model, 'y') - }; - switch (mark) { - case 'bar': - case 'tick': - { - if (config.scale.maxBandSize !== undefined) { - return config.scale.maxBandSize; - } - const min = minXYStep(size, xyStepSignals, config.view); - if (vega.isNumber(min)) { - return min - 1; - } else { - return new SignalRefWrapper(() => `${min.signal} - 1`); - } - } - case 'line': - case 'trail': - case 'rule': - return config.scale.maxStrokeWidth; - case 'text': - return config.scale.maxFontSize; - case 'point': - case 'square': - case 'circle': - { - if (config.scale.maxSize) { - return config.scale.maxSize; - } - const pointStep = minXYStep(size, xyStepSignals, config.view); - if (vega.isNumber(pointStep)) { - return Math.pow(MAX_SIZE_RANGE_STEP_RATIO * pointStep, 2); - } else { - return new SignalRefWrapper(() => `pow(${MAX_SIZE_RANGE_STEP_RATIO} * ${pointStep.signal}, 2)`); - } - } - } - /* istanbul ignore next: should never reach here */ - // sizeRangeMax not implemented for the mark - throw new Error(incompatibleChannel('size', mark)); - } - - /** - * @returns {number} Range step of x or y or minimum between the two if both are ordinal scale. - */ - function minXYStep(size, xyStepSignals, viewConfig) { - const widthStep = isStep(size.width) ? size.width.step : getViewConfigDiscreteStep(viewConfig, 'width'); - const heightStep = isStep(size.height) ? size.height.step : getViewConfigDiscreteStep(viewConfig, 'height'); - if (xyStepSignals.x || xyStepSignals.y) { - return new SignalRefWrapper(() => { - const exprs = [xyStepSignals.x ? xyStepSignals.x.signal : widthStep, xyStepSignals.y ? xyStepSignals.y.signal : heightStep]; - return `min(${exprs.join(', ')})`; - }); - } - return Math.min(widthStep, heightStep); - } - - function parseScaleProperty(model, property) { - if (isUnitModel(model)) { - parseUnitScaleProperty(model, property); - } else { - parseNonUnitScaleProperty(model, property); - } - } - function parseUnitScaleProperty(model, property) { - const localScaleComponents = model.component.scales; - const { - config, - encoding, - markDef, - specifiedScales - } = model; - for (const channel of keys(localScaleComponents)) { - const specifiedScale = specifiedScales[channel]; - const localScaleCmpt = localScaleComponents[channel]; - const mergedScaleCmpt = model.getScaleComponent(channel); - const fieldOrDatumDef = getFieldOrDatumDef(encoding[channel]); - const specifiedValue = specifiedScale[property]; - const scaleType = mergedScaleCmpt.get('type'); - const scalePadding = mergedScaleCmpt.get('padding'); - const scalePaddingInner = mergedScaleCmpt.get('paddingInner'); - const supportedByScaleType = scaleTypeSupportProperty(scaleType, property); - const channelIncompatability = channelScalePropertyIncompatability(channel, property); - if (specifiedValue !== undefined) { - // If there is a specified value, check if it is compatible with scale type and channel - if (!supportedByScaleType) { - warn(scalePropertyNotWorkWithScaleType(scaleType, property, channel)); - } else if (channelIncompatability) { - // channel - warn(channelIncompatability); - } - } - if (supportedByScaleType && channelIncompatability === undefined) { - if (specifiedValue !== undefined) { - const timeUnit = fieldOrDatumDef['timeUnit']; - const type = fieldOrDatumDef.type; - switch (property) { - // domainMax/Min to signal if the value is a datetime object - case 'domainMax': - case 'domainMin': - if (isDateTime(specifiedScale[property]) || type === 'temporal' || timeUnit) { - localScaleCmpt.set(property, { - signal: valueExpr(specifiedScale[property], { - type, - timeUnit - }) - }, true); - } else { - localScaleCmpt.set(property, specifiedScale[property], true); - } - break; - default: - localScaleCmpt.copyKeyFromObject(property, specifiedScale); - } - } else { - const value = property in scaleRules ? scaleRules[property]({ - model, - channel, - fieldOrDatumDef, - scaleType, - scalePadding, - scalePaddingInner, - domain: specifiedScale.domain, - domainMin: specifiedScale.domainMin, - domainMax: specifiedScale.domainMax, - markDef, - config, - hasNestedOffsetScale: channelHasNestedOffsetScale(encoding, channel), - hasSecondaryRangeChannel: !!encoding[getSecondaryRangeChannel(channel)] - }) : config.scale[property]; - if (value !== undefined) { - localScaleCmpt.set(property, value, false); - } - } - } - } - } - const scaleRules = { - bins: _ref => { - let { - model, - fieldOrDatumDef - } = _ref; - return isFieldDef(fieldOrDatumDef) ? bins(model, fieldOrDatumDef) : undefined; - }, - interpolate: _ref2 => { - let { - channel, - fieldOrDatumDef - } = _ref2; - return interpolate(channel, fieldOrDatumDef.type); - }, - nice: _ref3 => { - let { - scaleType, - channel, - domain, - domainMin, - domainMax, - fieldOrDatumDef - } = _ref3; - return nice(scaleType, channel, domain, domainMin, domainMax, fieldOrDatumDef); - }, - padding: _ref4 => { - let { - channel, - scaleType, - fieldOrDatumDef, - markDef, - config - } = _ref4; - return padding(channel, scaleType, config.scale, fieldOrDatumDef, markDef, config.bar); - }, - paddingInner: _ref5 => { - let { - scalePadding, - channel, - markDef, - scaleType, - config, - hasNestedOffsetScale - } = _ref5; - return paddingInner(scalePadding, channel, markDef.type, scaleType, config.scale, hasNestedOffsetScale); - }, - paddingOuter: _ref6 => { - let { - scalePadding, - channel, - scaleType, - scalePaddingInner, - config, - hasNestedOffsetScale - } = _ref6; - return paddingOuter(scalePadding, channel, scaleType, scalePaddingInner, config.scale, hasNestedOffsetScale); - }, - reverse: _ref7 => { - let { - fieldOrDatumDef, - scaleType, - channel, - config - } = _ref7; - const sort = isFieldDef(fieldOrDatumDef) ? fieldOrDatumDef.sort : undefined; - return reverse(scaleType, sort, channel, config.scale); - }, - zero: _ref8 => { - let { - channel, - fieldOrDatumDef, - domain, - markDef, - scaleType, - config, - hasSecondaryRangeChannel - } = _ref8; - return zero(channel, fieldOrDatumDef, domain, markDef, scaleType, config.scale, hasSecondaryRangeChannel); - } - }; - - // This method is here rather than in range.ts to avoid circular dependency. - function parseScaleRange(model) { - if (isUnitModel(model)) { - parseUnitScaleRange(model); - } else { - parseNonUnitScaleProperty(model, 'range'); - } - } - function parseNonUnitScaleProperty(model, property) { - const localScaleComponents = model.component.scales; - for (const child of model.children) { - if (property === 'range') { - parseScaleRange(child); - } else { - parseScaleProperty(child, property); - } - } - for (const channel of keys(localScaleComponents)) { - let valueWithExplicit; - for (const child of model.children) { - const childComponent = child.component.scales[channel]; - if (childComponent) { - const childValueWithExplicit = childComponent.getWithExplicit(property); - valueWithExplicit = mergeValuesWithExplicit(valueWithExplicit, childValueWithExplicit, property, 'scale', tieBreakByComparing((v1, v2) => { - switch (property) { - case 'range': - // For step, prefer larger step - if (v1.step && v2.step) { - return v1.step - v2.step; - } - return 0; - // TODO: precedence rule for other properties - } - return 0; - })); - } - } - localScaleComponents[channel].setWithExplicit(property, valueWithExplicit); - } - } - function bins(model, fieldDef) { - const bin = fieldDef.bin; - if (isBinning(bin)) { - const binSignal = getBinSignalName(model, fieldDef.field, bin); - return new SignalRefWrapper(() => { - return model.getSignalName(binSignal); - }); - } else if (isBinned(bin) && isBinParams(bin) && bin.step !== undefined) { - // start and stop will be determined from the scale domain - return { - step: bin.step - }; - } - return undefined; - } - function interpolate(channel, type) { - if (contains([COLOR, FILL, STROKE], channel) && type !== 'nominal') { - return 'hcl'; - } - return undefined; - } - function nice(scaleType, channel, specifiedDomain, domainMin, domainMax, fieldOrDatumDef) { - if (getFieldDef(fieldOrDatumDef)?.bin || vega.isArray(specifiedDomain) || domainMax != null || domainMin != null || contains([ScaleType.TIME, ScaleType.UTC], scaleType)) { - return undefined; - } - return isXorY(channel) ? true : undefined; - } - function padding(channel, scaleType, scaleConfig, fieldOrDatumDef, markDef, barConfig) { - if (isXorY(channel)) { - if (isContinuousToContinuous(scaleType)) { - if (scaleConfig.continuousPadding !== undefined) { - return scaleConfig.continuousPadding; - } - const { - type, - orient - } = markDef; - if (type === 'bar' && !(isFieldDef(fieldOrDatumDef) && (fieldOrDatumDef.bin || fieldOrDatumDef.timeUnit))) { - if (orient === 'vertical' && channel === 'x' || orient === 'horizontal' && channel === 'y') { - return barConfig.continuousBandSize; - } - } - } - if (scaleType === ScaleType.POINT) { - return scaleConfig.pointPadding; - } - } - return undefined; - } - function paddingInner(paddingValue, channel, mark, scaleType, scaleConfig) { - let hasNestedOffsetScale = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false; - if (paddingValue !== undefined) { - // If user has already manually specified "padding", no need to add default paddingInner. - return undefined; - } - if (isXorY(channel)) { - // Padding is only set for X and Y by default. - // Basically it doesn't make sense to add padding for color and size. - - // paddingOuter would only be called if it's a band scale, just return the default for bandScale. - const { - bandPaddingInner, - barBandPaddingInner, - rectBandPaddingInner, - tickBandPaddingInner, - bandWithNestedOffsetPaddingInner - } = scaleConfig; - if (hasNestedOffsetScale) { - return bandWithNestedOffsetPaddingInner; - } - return getFirstDefined(bandPaddingInner, mark === 'bar' ? barBandPaddingInner : mark === 'tick' ? tickBandPaddingInner : rectBandPaddingInner); - } else if (isXorYOffset(channel)) { - if (scaleType === ScaleType.BAND) { - return scaleConfig.offsetBandPaddingInner; - } - } - return undefined; - } - function paddingOuter(paddingValue, channel, scaleType, paddingInnerValue, scaleConfig) { - let hasNestedOffsetScale = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false; - if (paddingValue !== undefined) { - // If user has already manually specified "padding", no need to add default paddingOuter. - return undefined; - } - if (isXorY(channel)) { - const { - bandPaddingOuter, - bandWithNestedOffsetPaddingOuter - } = scaleConfig; - if (hasNestedOffsetScale) { - return bandWithNestedOffsetPaddingOuter; - } - // Padding is only set for X and Y by default. - // Basically it doesn't make sense to add padding for color and size. - if (scaleType === ScaleType.BAND) { - return getFirstDefined(bandPaddingOuter, - /* By default, paddingOuter is paddingInner / 2. The reason is that - size (width/height) = step * (cardinality - paddingInner + 2 * paddingOuter). - and we want the width/height to be integer by default. - Note that step (by default) and cardinality are integers.) */ - isSignalRef(paddingInnerValue) ? { - signal: `${paddingInnerValue.signal}/2` - } : paddingInnerValue / 2); - } - } else if (isXorYOffset(channel)) { - if (scaleType === ScaleType.POINT) { - return 0.5; // so the point positions align with centers of band scales. - } else if (scaleType === ScaleType.BAND) { - return scaleConfig.offsetBandPaddingOuter; - } - } - return undefined; - } - function reverse(scaleType, sort, channel, scaleConfig) { - if (channel === 'x' && scaleConfig.xReverse !== undefined) { - if (hasContinuousDomain(scaleType) && sort === 'descending') { - if (isSignalRef(scaleConfig.xReverse)) { - return { - signal: `!${scaleConfig.xReverse.signal}` - }; - } else { - return !scaleConfig.xReverse; - } - } - return scaleConfig.xReverse; - } - if (hasContinuousDomain(scaleType) && sort === 'descending') { - // For continuous domain scales, Vega does not support domain sort. - // Thus, we reverse range instead if sort is descending - return true; - } - return undefined; - } - function zero(channel, fieldDef, specifiedDomain, markDef, scaleType, scaleConfig, hasSecondaryRangeChannel) { - // If users explicitly provide a domain, we should not augment zero as that will be unexpected. - const hasCustomDomain = !!specifiedDomain && specifiedDomain !== 'unaggregated'; - if (hasCustomDomain) { - if (hasContinuousDomain(scaleType)) { - if (vega.isArray(specifiedDomain)) { - const first = specifiedDomain[0]; - const last = specifiedDomain[specifiedDomain.length - 1]; - if (vega.isNumber(first) && first <= 0 && vega.isNumber(last) && last >= 0) { - // if the domain includes zero, make zero remain true - return true; - } - } - return false; - } - } - - // If there is no custom domain, return configZero value (=`true` as default) only for the following cases: - - // 1) using quantitative field with size - // While this can be either ratio or interval fields, our assumption is that - // ratio are more common. However, if the scaleType is discretizing scale, we want to return - // false so that range doesn't start at zero - if (channel === 'size' && fieldDef.type === 'quantitative' && !isContinuousToDiscrete(scaleType)) { - return true; - } - - // 2) non-binned, quantitative x-scale or y-scale - // (For binning, we should not include zero by default because binning are calculated without zero.) - // (For area/bar charts with ratio scale chart, we should always include zero.) - if (!(isFieldDef(fieldDef) && fieldDef.bin) && contains([...POSITION_SCALE_CHANNELS, ...POLAR_POSITION_SCALE_CHANNELS], channel)) { - const { - orient, - type - } = markDef; - if (contains(['bar', 'area', 'line', 'trail'], type)) { - if (orient === 'horizontal' && channel === 'y' || orient === 'vertical' && channel === 'x') { - return false; - } - } - if (contains(['bar', 'area'], type) && !hasSecondaryRangeChannel) { - return true; - } - return scaleConfig?.zero; - } - return false; - } - - /** - * Determine if there is a specified scale type and if it is appropriate, - * or determine default type if type is unspecified or inappropriate. - */ - // NOTE: CompassQL uses this method. - function scaleType(specifiedScale, channel, fieldDef, mark) { - let hasNestedOffsetScale = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; - const defaultScaleType = defaultType(channel, fieldDef, mark, hasNestedOffsetScale); - const { - type - } = specifiedScale; - if (!isScaleChannel(channel)) { - // There is no scale for these channels - return null; - } - if (type !== undefined) { - // Check if explicitly specified scale type is supported by the channel - if (!channelSupportScaleType(channel, type)) { - warn(scaleTypeNotWorkWithChannel(channel, type, defaultScaleType)); - return defaultScaleType; - } - - // Check if explicitly specified scale type is supported by the data type - if (isFieldDef(fieldDef) && !scaleTypeSupportDataType(type, fieldDef.type)) { - warn(scaleTypeNotWorkWithFieldDef(type, defaultScaleType)); - return defaultScaleType; - } - return type; - } - return defaultScaleType; - } - - /** - * Determine appropriate default scale type. - */ - // NOTE: Voyager uses this method. - function defaultType(channel, fieldDef, mark, hasNestedOffsetScale) { - switch (fieldDef.type) { - case 'nominal': - case 'ordinal': - { - if (isColorChannel(channel) || rangeType(channel) === 'discrete') { - if (channel === 'shape' && fieldDef.type === 'ordinal') { - warn(discreteChannelCannotEncode(channel, 'ordinal')); - } - return 'ordinal'; - } - if (isXorY(channel) || isXorYOffset(channel)) { - if (contains(['rect', 'bar', 'image', 'rule', 'tick'], mark.type)) { - // The rect/bar/tick mark should fit into a band. - // For rule, using band scale to make rule align with axis ticks better https://github.com/vega/vega-lite/issues/3429 - return 'band'; - } - if (hasNestedOffsetScale) { - // If there is a nested offset scale, then there is a "band" for the span of the nested scale. - return 'band'; - } - } else if (mark.type === 'arc' && channel in POLAR_POSITION_SCALE_CHANNEL_INDEX) { - return 'band'; - } - const dimensionSize = mark[getSizeChannel(channel)]; - if (isRelativeBandSize(dimensionSize)) { - return 'band'; - } - if (isPositionFieldOrDatumDef(fieldDef) && fieldDef.axis?.tickBand) { - return 'band'; - } - // Otherwise, use ordinal point scale so we can easily get center positions of the marks. - return 'point'; - } - case 'temporal': - if (isColorChannel(channel)) { - return 'time'; - } else if (rangeType(channel) === 'discrete') { - warn(discreteChannelCannotEncode(channel, 'temporal')); - // TODO: consider using quantize (equivalent to binning) once we have it - return 'ordinal'; - } else if (isFieldDef(fieldDef) && fieldDef.timeUnit && normalizeTimeUnit(fieldDef.timeUnit).utc) { - return 'utc'; - } - return 'time'; - case 'quantitative': - if (isColorChannel(channel)) { - if (isFieldDef(fieldDef) && isBinning(fieldDef.bin)) { - return 'bin-ordinal'; - } - return 'linear'; - } else if (rangeType(channel) === 'discrete') { - warn(discreteChannelCannotEncode(channel, 'quantitative')); - // TODO: consider using quantize (equivalent to binning) once we have it - return 'ordinal'; - } - return 'linear'; - case 'geojson': - return undefined; - } - - /* istanbul ignore next: should never reach this */ - throw new Error(invalidFieldType(fieldDef.type)); - } - - function parseScales(model) { - let { - ignoreRange - } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - parseScaleCore(model); - parseScaleDomain(model); - for (const prop of NON_TYPE_DOMAIN_RANGE_VEGA_SCALE_PROPERTIES) { - parseScaleProperty(model, prop); - } - if (!ignoreRange) { - // range depends on zero - parseScaleRange(model); - } - } - function parseScaleCore(model) { - if (isUnitModel(model)) { - model.component.scales = parseUnitScaleCore(model); - } else { - model.component.scales = parseNonUnitScaleCore(model); - } - } - - /** - * Parse scales for all channels of a model. - */ - function parseUnitScaleCore(model) { - const { - encoding, - mark, - markDef - } = model; - const scaleComponents = {}; - for (const channel of SCALE_CHANNELS) { - const fieldOrDatumDef = getFieldOrDatumDef(encoding[channel]); // must be typed def to have scale - - // Don't generate scale for shape of geoshape - if (fieldOrDatumDef && mark === GEOSHAPE && channel === SHAPE && fieldOrDatumDef.type === GEOJSON) { - continue; - } - let specifiedScale = fieldOrDatumDef && fieldOrDatumDef['scale']; - if (fieldOrDatumDef && specifiedScale !== null && specifiedScale !== false) { - specifiedScale ??= {}; - const hasNestedOffsetScale = channelHasNestedOffsetScale(encoding, channel); - const sType = scaleType(specifiedScale, channel, fieldOrDatumDef, markDef, hasNestedOffsetScale); - scaleComponents[channel] = new ScaleComponent(model.scaleName(`${channel}`, true), { - value: sType, - explicit: specifiedScale.type === sType - }); - } - } - return scaleComponents; - } - const scaleTypeTieBreaker = tieBreakByComparing((st1, st2) => scaleTypePrecedence(st1) - scaleTypePrecedence(st2)); - function parseNonUnitScaleCore(model) { - const scaleComponents = model.component.scales = {}; - const scaleTypeWithExplicitIndex = {}; - const resolve = model.component.resolve; - - // Parse each child scale and determine if a particular channel can be merged. - for (const child of model.children) { - parseScaleCore(child); - - // Instead of always merging right away -- check if it is compatible to merge first! - for (const channel of keys(child.component.scales)) { - // if resolve is undefined, set default first - resolve.scale[channel] ??= defaultScaleResolve(channel, model); - if (resolve.scale[channel] === 'shared') { - const explicitScaleType = scaleTypeWithExplicitIndex[channel]; - const childScaleType = child.component.scales[channel].getWithExplicit('type'); - if (explicitScaleType) { - if (scaleCompatible(explicitScaleType.value, childScaleType.value)) { - // merge scale component if type are compatible - scaleTypeWithExplicitIndex[channel] = mergeValuesWithExplicit(explicitScaleType, childScaleType, 'type', 'scale', scaleTypeTieBreaker); - } else { - // Otherwise, update conflicting channel to be independent - resolve.scale[channel] = 'independent'; - // Remove from the index so they don't get merged - delete scaleTypeWithExplicitIndex[channel]; - } - } else { - scaleTypeWithExplicitIndex[channel] = childScaleType; - } - } - } - } - - // Merge each channel listed in the index - for (const channel of keys(scaleTypeWithExplicitIndex)) { - // Create new merged scale component - const name = model.scaleName(channel, true); - const typeWithExplicit = scaleTypeWithExplicitIndex[channel]; - scaleComponents[channel] = new ScaleComponent(name, typeWithExplicit); - - // rename each child and mark them as merged - for (const child of model.children) { - const childScale = child.component.scales[channel]; - if (childScale) { - child.renameScale(childScale.get('name'), name); - childScale.merged = true; - } - } - } - return scaleComponents; - } - - /** - * Composable Components that are intermediate results of the parsing phase of the - * compilations. The components represents parts of the specification in a form that - * can be easily merged (during parsing for composite specs). - * In addition, these components are easily transformed into Vega specifications - * during the "assemble" phase, which is the last phase of the compilation step. - */ - - class NameMap { - constructor() { - this.nameMap = {}; - } - rename(oldName, newName) { - this.nameMap[oldName] = newName; - } - has(name) { - return this.nameMap[name] !== undefined; - } - get(name) { - // If the name appears in the _nameMap, we need to read its new name. - // We have to loop over the dict just in case the new name also gets renamed. - while (this.nameMap[name] && name !== this.nameMap[name]) { - name = this.nameMap[name]; - } - return name; - } - } - - /* - We use type guards instead of `instanceof` as `instanceof` makes - different parts of the compiler depend on the actual implementation of - the model classes, which in turn depend on different parts of the compiler. - Thus, `instanceof` leads to circular dependency problems. - - On the other hand, type guards only make different parts of the compiler - depend on the type of the model classes, but not the actual implementation. - */ - - function isUnitModel(model) { - return model?.type === 'unit'; - } - function isFacetModel(model) { - return model?.type === 'facet'; - } - function isConcatModel(model) { - return model?.type === 'concat'; - } - function isLayerModel(model) { - return model?.type === 'layer'; - } - class Model { - /** Name map for scales, which can be renamed by a model's parent. */ - - /** Name map for projections, which can be renamed by a model's parent. */ - - /** Name map for signals, which can be renamed by a model's parent. */ - - constructor(spec, type, parent, parentGivenName, config, resolve, view) { - this.type = type; - this.parent = parent; - this.config = config; - this.parent = parent; - this.config = config; - this.view = replaceExprRef(view); - - // If name is not provided, always use parent's givenName to avoid name conflicts. - this.name = spec.name ?? parentGivenName; - this.title = isText(spec.title) ? { - text: spec.title - } : spec.title ? replaceExprRef(spec.title) : undefined; - - // Shared name maps - this.scaleNameMap = parent ? parent.scaleNameMap : new NameMap(); - this.projectionNameMap = parent ? parent.projectionNameMap : new NameMap(); - this.signalNameMap = parent ? parent.signalNameMap : new NameMap(); - this.data = spec.data; - this.description = spec.description; - this.transforms = normalizeTransform(spec.transform ?? []); - this.layout = type === 'layer' || type === 'unit' ? {} : extractCompositionLayout(spec, type, config); - this.component = { - data: { - sources: parent ? parent.component.data.sources : [], - outputNodes: parent ? parent.component.data.outputNodes : {}, - outputNodeRefCounts: parent ? parent.component.data.outputNodeRefCounts : {}, - // data is faceted if the spec is a facet spec or the parent has faceted data and data is undefined - isFaceted: isFacetSpec(spec) || parent?.component.data.isFaceted && spec.data === undefined - }, - layoutSize: new Split(), - layoutHeaders: { - row: {}, - column: {}, - facet: {} - }, - mark: null, - resolve: { - scale: {}, - axis: {}, - legend: {}, - ...(resolve ? duplicate(resolve) : {}) - }, - selection: null, - scales: null, - projection: null, - axes: {}, - legends: {} - }; - } - get width() { - return this.getSizeSignalRef('width'); - } - get height() { - return this.getSizeSignalRef('height'); - } - parse() { - this.parseScale(); - this.parseLayoutSize(); // depends on scale - this.renameTopLevelLayoutSizeSignal(); - this.parseSelections(); - this.parseProjection(); - this.parseData(); // (pathorder) depends on markDef; selection filters depend on parsed selections; depends on projection because some transforms require the finalized projection name. - this.parseAxesAndHeaders(); // depends on scale and layout size - this.parseLegends(); // depends on scale, markDef - this.parseMarkGroup(); // depends on data name, scale, layout size, axisGroup, and children's scale, axis, legend and mark. - } - parseScale() { - parseScales(this); - } - parseProjection() { - parseProjection(this); - } - /** - * Rename top-level spec's size to be just width / height, ignoring model name. - * This essentially merges the top-level spec's width/height signals with the width/height signals - * to help us reduce redundant signals declaration. - */ - renameTopLevelLayoutSizeSignal() { - if (this.getName('width') !== 'width') { - this.renameSignal(this.getName('width'), 'width'); - } - if (this.getName('height') !== 'height') { - this.renameSignal(this.getName('height'), 'height'); - } - } - parseLegends() { - parseLegend(this); - } - assembleEncodeFromView(view) { - // Exclude "style" - const { - style: _, - ...baseView - } = view; - const e = {}; - for (const property of keys(baseView)) { - const value = baseView[property]; - if (value !== undefined) { - e[property] = signalOrValueRef(value); - } - } - return e; - } - assembleGroupEncodeEntry(isTopLevel) { - let encodeEntry = {}; - if (this.view) { - encodeEntry = this.assembleEncodeFromView(this.view); - } - if (!isTopLevel) { - // Descriptions are already added to the top-level description so we only need to add them to the inner views. - if (this.description) { - encodeEntry['description'] = signalOrValueRef(this.description); - } - - // For top-level spec, we can set the global width and height signal to adjust the group size. - // For other child specs, we have to manually set width and height in the encode entry. - if (this.type === 'unit' || this.type === 'layer') { - return { - width: this.getSizeSignalRef('width'), - height: this.getSizeSignalRef('height'), - ...encodeEntry - }; - } - } - return isEmpty(encodeEntry) ? undefined : encodeEntry; - } - assembleLayout() { - if (!this.layout) { - return undefined; - } - const { - spacing, - ...layout - } = this.layout; - const { - component, - config - } = this; - const titleBand = assembleLayoutTitleBand(component.layoutHeaders, config); - return { - padding: spacing, - ...this.assembleDefaultLayout(), - ...layout, - ...(titleBand ? { - titleBand - } : {}) - }; - } - assembleDefaultLayout() { - return {}; - } - assembleHeaderMarks() { - const { - layoutHeaders - } = this.component; - let headerMarks = []; - for (const channel of FACET_CHANNELS) { - if (layoutHeaders[channel].title) { - headerMarks.push(assembleTitleGroup(this, channel)); - } - } - for (const channel of HEADER_CHANNELS) { - headerMarks = headerMarks.concat(assembleHeaderGroups(this, channel)); - } - return headerMarks; - } - assembleAxes() { - return assembleAxes(this.component.axes, this.config); - } - assembleLegends() { - return assembleLegends(this); - } - assembleProjections() { - return assembleProjections(this); - } - assembleTitle() { - const { - encoding, - ...titleNoEncoding - } = this.title ?? {}; - const title = { - ...extractTitleConfig(this.config.title).nonMarkTitleProperties, - ...titleNoEncoding, - ...(encoding ? { - encode: { - update: encoding - } - } : {}) - }; - if (title.text) { - if (contains(['unit', 'layer'], this.type)) { - // Unit/Layer - if (contains(['middle', undefined], title.anchor)) { - title.frame ??= 'group'; - } - } else { - // composition with Vega layout - - // Set title = "start" by default for composition as "middle" does not look nice - // https://github.com/vega/vega/issues/960#issuecomment-471360328 - title.anchor ??= 'start'; - } - return isEmpty(title) ? undefined : title; - } - return undefined; - } - - /** - * 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. - */ - assembleGroup() { - let signals = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; - const group = {}; - signals = signals.concat(this.assembleSignals()); - if (signals.length > 0) { - group.signals = signals; - } - const layout = this.assembleLayout(); - if (layout) { - group.layout = layout; - } - group.marks = [].concat(this.assembleHeaderMarks(), this.assembleMarks()); - - // Only include scales if this spec is top-level or if parent is facet. - // (Otherwise, it will be merged with upper-level's scope.) - const scales = !this.parent || isFacetModel(this.parent) ? assembleScales(this) : []; - if (scales.length > 0) { - group.scales = scales; - } - const axes = this.assembleAxes(); - if (axes.length > 0) { - group.axes = axes; - } - const legends = this.assembleLegends(); - if (legends.length > 0) { - group.legends = legends; - } - return group; - } - getName(text) { - return varName((this.name ? `${this.name}_` : '') + text); - } - getDataName(type) { - return this.getName(DataSourceType[type].toLowerCase()); - } - - /** - * 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(). - * You can lookup the correct dataset name in assemble with `lookupDataSource`. - */ - requestDataName(name) { - const fullName = this.getDataName(name); - - // Increase ref count. This is critical because otherwise we won't create a data source. - // We also increase the ref counts on OutputNode.getSource() calls. - const refCounts = this.component.data.outputNodeRefCounts; - refCounts[fullName] = (refCounts[fullName] || 0) + 1; - return fullName; - } - getSizeSignalRef(layoutSizeType) { - if (isFacetModel(this.parent)) { - const sizeType = getSizeTypeFromLayoutSizeType(layoutSizeType); - const channel = getPositionScaleChannel(sizeType); - const scaleComponent = this.component.scales[channel]; - if (scaleComponent && !scaleComponent.merged) { - // independent scale - const type = scaleComponent.get('type'); - const range = scaleComponent.get('range'); - if (hasDiscreteDomain(type) && isVgRangeStep(range)) { - const scaleName = scaleComponent.get('name'); - const domain = assembleDomain(this, channel); - const field = getFieldFromDomain(domain); - if (field) { - const fieldRef = vgField({ - aggregate: 'distinct', - field - }, { - expr: 'datum' - }); - return { - signal: sizeExpr(scaleName, scaleComponent, fieldRef) - }; - } else { - warn(unknownField(channel)); - return null; - } - } - } - } - return { - signal: this.signalNameMap.get(this.getName(layoutSizeType)) - }; - } - - /** - * Lookup the name of the datasource for an output node. You probably want to call this in assemble. - */ - lookupDataSource(name) { - const node = this.component.data.outputNodes[name]; - if (!node) { - // Name not found in map so let's just return what we got. - // This can happen if we already have the correct name. - return name; - } - return node.getSource(); - } - getSignalName(oldSignalName) { - return this.signalNameMap.get(oldSignalName); - } - renameSignal(oldName, newName) { - this.signalNameMap.rename(oldName, newName); - } - renameScale(oldName, newName) { - this.scaleNameMap.rename(oldName, newName); - } - renameProjection(oldName, newName) { - this.projectionNameMap.rename(oldName, newName); - } - - /** - * @return scale name for a given channel after the scale has been parsed and named. - */ - scaleName(originalScaleName, parse) { - if (parse) { - // During the parse phase always return a value - // No need to refer to rename map because a scale can't be renamed - // before it has the original name. - return this.getName(originalScaleName); - } - - // If there is a scale for the channel, it should either - // be in the scale component or exist in the name map - if ( - // If there is a scale for the channel, there should be a local scale component for it - isChannel(originalScaleName) && isScaleChannel(originalScaleName) && this.component.scales[originalScaleName] || - // in the scale name map (the scale get merged by its parent) - this.scaleNameMap.has(this.getName(originalScaleName))) { - return this.scaleNameMap.get(this.getName(originalScaleName)); - } - return undefined; - } - - /** - * @return projection name after the projection has been parsed and named. - */ - projectionName(parse) { - if (parse) { - // During the parse phase always return a value - // No need to refer to rename map because a projection can't be renamed - // before it has the original name. - return this.getName('projection'); - } - if (this.component.projection && !this.component.projection.merged || this.projectionNameMap.has(this.getName('projection'))) { - return this.projectionNameMap.get(this.getName('projection')); - } - return undefined; - } - - /** - * Corrects the data references in marks after assemble. - */ - correctDataNames = mark => { - // TODO: make this correct - - // for normal data references - if (mark.from?.data) { - mark.from.data = this.lookupDataSource(mark.from.data); - } - - // for access to facet data - if (mark.from?.facet?.data) { - mark.from.facet.data = this.lookupDataSource(mark.from.facet.data); - } - return mark; - }; - - /** - * Traverse a model's hierarchy to get the scale component for a particular channel. - */ - getScaleComponent(channel) { - /* istanbul ignore next: This is warning for debugging test */ - if (!this.component.scales) { - throw new Error('getScaleComponent cannot be called before parseScale(). Make sure you have called parseScale or use parseUnitModelWithScale().'); - } - const localScaleComponent = this.component.scales[channel]; - if (localScaleComponent && !localScaleComponent.merged) { - return localScaleComponent; - } - return this.parent ? this.parent.getScaleComponent(channel) : undefined; - } - getScaleType(channel) { - const scaleComponent = this.getScaleComponent(channel); - return scaleComponent ? scaleComponent.get('type') : undefined; - } - - /** - * Traverse a model's hierarchy to get a particular selection component. - */ - getSelectionComponent(variableName, origName) { - let sel = this.component.selection[variableName]; - if (!sel && this.parent) { - sel = this.parent.getSelectionComponent(variableName, origName); - } - if (!sel) { - throw new Error(selectionNotFound(origName)); - } - return sel; - } - - /** - * Returns true if the model has a signalRef for an axis orient. - */ - hasAxisOrientSignalRef() { - return this.component.axes.x?.some(a => a.hasOrientSignalRef()) || this.component.axes.y?.some(a => a.hasOrientSignalRef()); - } - } - - /** Abstract class for UnitModel and FacetModel. Both of which can contain fieldDefs as a part of its own specification. */ - class ModelWithField extends Model { - /** Get "field" reference for Vega */ - vgField(channel) { - let opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - const fieldDef = this.fieldDef(channel); - if (!fieldDef) { - return undefined; - } - return vgField(fieldDef, opt); - } - reduceFieldDef(f, init) { - return reduce(this.getMapping(), (acc, cd, c) => { - const fieldDef = getFieldDef(cd); - if (fieldDef) { - return f(acc, fieldDef, c); - } - return acc; - }, init); - } - forEachFieldDef(f, t) { - forEach(this.getMapping(), (cd, c) => { - const fieldDef = getFieldDef(cd); - if (fieldDef) { - f(fieldDef, c); - } - }, t); - } - } - - /** - * A class for density transform nodes - */ - class DensityTransformNode extends DataFlowNode { - clone() { - return new DensityTransformNode(null, duplicate(this.transform)); - } - constructor(parent, transform) { - super(parent); - this.transform = transform; - this.transform = duplicate(transform); // duplicate to prevent side effects - const specifiedAs = this.transform.as ?? [undefined, undefined]; - this.transform.as = [specifiedAs[0] ?? 'value', specifiedAs[1] ?? 'density']; - const resolve = this.transform.resolve ?? 'shared'; - this.transform.resolve = resolve; - } - dependentFields() { - return new Set([this.transform.density, ...(this.transform.groupby ?? [])]); - } - producedFields() { - return new Set(this.transform.as); - } - hash() { - return `DensityTransform ${hash(this.transform)}`; - } - assemble() { - const { - density, - ...rest - } = this.transform; - const result = { - type: 'kde', - field: density, - ...rest - }; - result.resolve = this.transform.resolve; - return result; - } - } - - /** - * A class for flatten transform nodes - */ - class ExtentTransformNode extends DataFlowNode { - clone() { - return new ExtentTransformNode(null, duplicate(this.transform)); - } - constructor(parent, transform) { - super(parent); - this.transform = transform; - this.transform = duplicate(transform); - } - dependentFields() { - return new Set([this.transform.extent]); - } - producedFields() { - return new Set([]); - } - hash() { - return `ExtentTransform ${hash(this.transform)}`; - } - assemble() { - const { - extent, - param - } = this.transform; - const result = { - type: 'extent', - field: extent, - signal: param - }; - return result; - } - } - - /** - * A class for flatten transform nodes - */ - class FlattenTransformNode extends DataFlowNode { - clone() { - return new FlattenTransformNode(this.parent, duplicate(this.transform)); - } - constructor(parent, transform) { - super(parent); - this.transform = transform; - this.transform = duplicate(transform); // duplicate to prevent side effects - const { - flatten, - as = [] - } = this.transform; - this.transform.as = flatten.map((f, i) => as[i] ?? f); - } - dependentFields() { - return new Set(this.transform.flatten); - } - producedFields() { - return new Set(this.transform.as); - } - hash() { - return `FlattenTransform ${hash(this.transform)}`; - } - assemble() { - const { - flatten: fields, - as - } = this.transform; - const result = { - type: 'flatten', - fields, - as - }; - return result; - } - } - - /** - * A class for flatten transform nodes - */ - class FoldTransformNode extends DataFlowNode { - clone() { - return new FoldTransformNode(null, duplicate(this.transform)); - } - constructor(parent, transform) { - super(parent); - this.transform = transform; - this.transform = duplicate(transform); // duplicate to prevent side effects - const specifiedAs = this.transform.as ?? [undefined, undefined]; - this.transform.as = [specifiedAs[0] ?? 'key', specifiedAs[1] ?? 'value']; - } - dependentFields() { - return new Set(this.transform.fold); - } - producedFields() { - return new Set(this.transform.as); - } - hash() { - return `FoldTransform ${hash(this.transform)}`; - } - assemble() { - const { - fold, - as - } = this.transform; - const result = { - type: 'fold', - fields: fold, - as - }; - return result; - } - } - - class GeoJSONNode extends DataFlowNode { - clone() { - return new GeoJSONNode(null, duplicate(this.fields), this.geojson, this.signal); - } - static parseAll(parent, model) { - if (model.component.projection && !model.component.projection.isFit) { - return parent; - } - let geoJsonCounter = 0; - for (const coordinates of [[LONGITUDE, LATITUDE], [LONGITUDE2, LATITUDE2]]) { - const pair = coordinates.map(channel => { - const def = getFieldOrDatumDef(model.encoding[channel]); - return isFieldDef(def) ? def.field : isDatumDef(def) ? { - expr: `${def.datum}` - } : isValueDef(def) ? { - expr: `${def['value']}` - } : undefined; - }); - if (pair[0] || pair[1]) { - parent = new GeoJSONNode(parent, pair, null, model.getName(`geojson_${geoJsonCounter++}`)); - } - } - if (model.channelHasField(SHAPE)) { - const fieldDef = model.typedFieldDef(SHAPE); - if (fieldDef.type === GEOJSON) { - parent = new GeoJSONNode(parent, null, fieldDef.field, model.getName(`geojson_${geoJsonCounter++}`)); - } - } - return parent; - } - constructor(parent, fields, geojson, signal) { - super(parent); - this.fields = fields; - this.geojson = geojson; - this.signal = signal; - } - dependentFields() { - const fields = (this.fields ?? []).filter(vega.isString); - return new Set([...(this.geojson ? [this.geojson] : []), ...fields]); - } - producedFields() { - return new Set(); - } - hash() { - return `GeoJSON ${this.geojson} ${this.signal} ${hash(this.fields)}`; - } - assemble() { - return [...(this.geojson ? [{ - type: 'filter', - expr: `isValid(datum["${this.geojson}"])` - }] : []), { - type: 'geojson', - ...(this.fields ? { - fields: this.fields - } : {}), - ...(this.geojson ? { - geojson: this.geojson - } : {}), - signal: this.signal - }]; - } - } - - class GeoPointNode extends DataFlowNode { - clone() { - return new GeoPointNode(null, this.projection, duplicate(this.fields), duplicate(this.as)); - } - constructor(parent, projection, fields, as) { - super(parent); - this.projection = projection; - this.fields = fields; - this.as = as; - } - static parseAll(parent, model) { - if (!model.projectionName()) { - return parent; - } - for (const coordinates of [[LONGITUDE, LATITUDE], [LONGITUDE2, LATITUDE2]]) { - const pair = coordinates.map(channel => { - const def = getFieldOrDatumDef(model.encoding[channel]); - return isFieldDef(def) ? def.field : isDatumDef(def) ? { - expr: `${def.datum}` - } : isValueDef(def) ? { - expr: `${def['value']}` - } : undefined; - }); - const suffix = coordinates[0] === LONGITUDE2 ? '2' : ''; - if (pair[0] || pair[1]) { - parent = new GeoPointNode(parent, model.projectionName(), pair, [model.getName(`x${suffix}`), model.getName(`y${suffix}`)]); - } - } - return parent; - } - dependentFields() { - return new Set(this.fields.filter(vega.isString)); - } - producedFields() { - return new Set(this.as); - } - hash() { - return `Geopoint ${this.projection} ${hash(this.fields)} ${hash(this.as)}`; - } - assemble() { - return { - type: 'geopoint', - projection: this.projection, - fields: this.fields, - as: this.as - }; - } - } - - class ImputeNode extends DataFlowNode { - clone() { - return new ImputeNode(null, duplicate(this.transform)); - } - constructor(parent, transform) { - super(parent); - this.transform = transform; - } - dependentFields() { - return new Set([this.transform.impute, this.transform.key, ...(this.transform.groupby ?? [])]); - } - producedFields() { - return new Set([this.transform.impute]); - } - processSequence(keyvals) { - const { - start = 0, - stop, - step - } = keyvals; - const result = [start, stop, ...(step ? [step] : [])].join(','); - return { - signal: `sequence(${result})` - }; - } - static makeFromTransform(parent, imputeTransform) { - return new ImputeNode(parent, imputeTransform); - } - static makeFromEncoding(parent, model) { - const encoding = model.encoding; - const xDef = encoding.x; - const yDef = encoding.y; - if (isFieldDef(xDef) && isFieldDef(yDef)) { - const imputedChannel = xDef.impute ? xDef : yDef.impute ? yDef : undefined; - if (imputedChannel === undefined) { - return undefined; - } - const keyChannel = xDef.impute ? yDef : yDef.impute ? xDef : undefined; - const { - method, - value, - frame, - keyvals - } = imputedChannel.impute; - const groupbyFields = pathGroupingFields(model.mark, encoding); - return new ImputeNode(parent, { - impute: imputedChannel.field, - key: keyChannel.field, - ...(method ? { - method - } : {}), - ...(value !== undefined ? { - value - } : {}), - ...(frame ? { - frame - } : {}), - ...(keyvals !== undefined ? { - keyvals - } : {}), - ...(groupbyFields.length ? { - groupby: groupbyFields - } : {}) - }); - } - return null; - } - hash() { - return `Impute ${hash(this.transform)}`; - } - assemble() { - const { - impute, - key, - keyvals, - method, - groupby, - value, - frame = [null, null] - } = this.transform; - const imputeTransform = { - type: 'impute', - field: impute, - key, - ...(keyvals ? { - keyvals: isImputeSequence(keyvals) ? this.processSequence(keyvals) : keyvals - } : {}), - method: 'value', - ...(groupby ? { - groupby - } : {}), - value: !method || method === 'value' ? value : null - }; - if (method && method !== 'value') { - const deriveNewField = { - type: 'window', - as: [`imputed_${impute}_value`], - ops: [method], - fields: [impute], - frame, - ignorePeers: false, - ...(groupby ? { - groupby - } : {}) - }; - const replaceOriginal = { - type: 'formula', - expr: `datum.${impute} === null ? datum.imputed_${impute}_value : datum.${impute}`, - as: impute - }; - return [imputeTransform, deriveNewField, replaceOriginal]; - } else { - return [imputeTransform]; - } - } - } - - /** - * A class for loess transform nodes - */ - class LoessTransformNode extends DataFlowNode { - clone() { - return new LoessTransformNode(null, duplicate(this.transform)); - } - constructor(parent, transform) { - super(parent); - this.transform = transform; - this.transform = duplicate(transform); // duplicate to prevent side effects - const specifiedAs = this.transform.as ?? [undefined, undefined]; - this.transform.as = [specifiedAs[0] ?? transform.on, specifiedAs[1] ?? transform.loess]; - } - dependentFields() { - return new Set([this.transform.loess, this.transform.on, ...(this.transform.groupby ?? [])]); - } - producedFields() { - return new Set(this.transform.as); - } - hash() { - return `LoessTransform ${hash(this.transform)}`; - } - assemble() { - const { - loess, - on, - ...rest - } = this.transform; - const result = { - type: 'loess', - x: on, - y: loess, - ...rest - }; - return result; - } - } - - class LookupNode extends DataFlowNode { - clone() { - return new LookupNode(null, duplicate(this.transform), this.secondary); - } - constructor(parent, transform, secondary) { - super(parent); - this.transform = transform; - this.secondary = secondary; - } - static make(parent, model, transform, counter) { - const sources = model.component.data.sources; - const { - from - } = transform; - let fromOutputNode = null; - if (isLookupData(from)) { - let fromSource = findSource(from.data, sources); - if (!fromSource) { - fromSource = new SourceNode(from.data); - sources.push(fromSource); - } - const fromOutputName = model.getName(`lookup_${counter}`); - fromOutputNode = new OutputNode(fromSource, fromOutputName, DataSourceType.Lookup, model.component.data.outputNodeRefCounts); - model.component.data.outputNodes[fromOutputName] = fromOutputNode; - } else if (isLookupSelection(from)) { - const selName = from.param; - transform = { - as: selName, - ...transform - }; - let selCmpt; - try { - selCmpt = model.getSelectionComponent(varName(selName), selName); - } catch (e) { - throw new Error(cannotLookupVariableParameter(selName)); - } - fromOutputNode = selCmpt.materialized; - if (!fromOutputNode) { - throw new Error(noSameUnitLookup(selName)); - } - } - return new LookupNode(parent, transform, fromOutputNode.getSource()); - } - dependentFields() { - return new Set([this.transform.lookup]); - } - producedFields() { - return new Set(this.transform.as ? vega.array(this.transform.as) : this.transform.from.fields); - } - hash() { - return `Lookup ${hash({ - transform: this.transform, - secondary: this.secondary - })}`; - } - assemble() { - let foreign; - if (this.transform.from.fields) { - // lookup a few fields and add create a flat output - foreign = { - values: this.transform.from.fields, - ...(this.transform.as ? { - as: vega.array(this.transform.as) - } : {}) - }; - } else { - // lookup full record and nest it - let asName = this.transform.as; - if (!vega.isString(asName)) { - warn(NO_FIELDS_NEEDS_AS); - asName = '_lookup'; - } - foreign = { - as: [asName] - }; - } - return { - type: 'lookup', - from: this.secondary, - key: this.transform.from.key, - fields: [this.transform.lookup], - ...foreign, - ...(this.transform.default ? { - default: this.transform.default - } : {}) - }; - } - } - - /** - * A class for quantile transform nodes - */ - class QuantileTransformNode extends DataFlowNode { - clone() { - return new QuantileTransformNode(null, duplicate(this.transform)); - } - constructor(parent, transform) { - super(parent); - this.transform = transform; - this.transform = duplicate(transform); // duplicate to prevent side effects - const specifiedAs = this.transform.as ?? [undefined, undefined]; - this.transform.as = [specifiedAs[0] ?? 'prob', specifiedAs[1] ?? 'value']; - } - dependentFields() { - return new Set([this.transform.quantile, ...(this.transform.groupby ?? [])]); - } - producedFields() { - return new Set(this.transform.as); - } - hash() { - return `QuantileTransform ${hash(this.transform)}`; - } - assemble() { - const { - quantile, - ...rest - } = this.transform; - const result = { - type: 'quantile', - field: quantile, - ...rest - }; - return result; - } - } - - /** - * A class for regression transform nodes - */ - class RegressionTransformNode extends DataFlowNode { - clone() { - return new RegressionTransformNode(null, duplicate(this.transform)); - } - constructor(parent, transform) { - super(parent); - this.transform = transform; - this.transform = duplicate(transform); // duplicate to prevent side effects - const specifiedAs = this.transform.as ?? [undefined, undefined]; - this.transform.as = [specifiedAs[0] ?? transform.on, specifiedAs[1] ?? transform.regression]; - } - dependentFields() { - return new Set([this.transform.regression, this.transform.on, ...(this.transform.groupby ?? [])]); - } - producedFields() { - return new Set(this.transform.as); - } - hash() { - return `RegressionTransform ${hash(this.transform)}`; - } - assemble() { - const { - regression, - on, - ...rest - } = this.transform; - const result = { - type: 'regression', - x: on, - y: regression, - ...rest - }; - return result; - } - } - - /** - * A class for pivot transform nodes. - */ - class PivotTransformNode extends DataFlowNode { - clone() { - return new PivotTransformNode(null, duplicate(this.transform)); - } - constructor(parent, transform) { - super(parent); - this.transform = transform; - } - addDimensions(fields) { - this.transform.groupby = unique((this.transform.groupby ?? []).concat(fields), d => d); - } - producedFields() { - return undefined; // return undefined so that potentially everything can depend on the pivot - } - dependentFields() { - return new Set([this.transform.pivot, this.transform.value, ...(this.transform.groupby ?? [])]); - } - hash() { - return `PivotTransform ${hash(this.transform)}`; - } - assemble() { - const { - pivot, - value, - groupby, - limit, - op - } = this.transform; - return { - type: 'pivot', - field: pivot, - value, - ...(limit !== undefined ? { - limit - } : {}), - ...(op !== undefined ? { - op - } : {}), - ...(groupby !== undefined ? { - groupby - } : {}) - }; - } - } - - /** - * A class for the sample transform nodes - */ - class SampleTransformNode extends DataFlowNode { - clone() { - return new SampleTransformNode(null, duplicate(this.transform)); - } - constructor(parent, transform) { - super(parent); - this.transform = transform; - } - dependentFields() { - return new Set(); - } - producedFields() { - return new Set(); - } - hash() { - return `SampleTransform ${hash(this.transform)}`; - } - assemble() { - return { - type: 'sample', - size: this.transform.sample - }; - } - } - - function makeWalkTree(data) { - // to name datasources - let datasetIndex = 0; - - /** - * Recursively walk down the tree. - */ - function walkTree(node, dataSource) { - if (node instanceof SourceNode) { - // If the source is a named data source or a data source with values, we need - // to put it in a different data source. Otherwise, Vega may override the data. - if (!node.isGenerator && !isUrlData(node.data)) { - data.push(dataSource); - const newData = { - name: null, - source: dataSource.name, - transform: [] - }; - dataSource = newData; - } - } - if (node instanceof ParseNode) { - if (node.parent instanceof SourceNode && !dataSource.source) { - // If node's parent is a root source and the data source does not refer to another data source, use normal format parse - dataSource.format = { - ...dataSource.format, - parse: node.assembleFormatParse() - }; - - // add calculates for all nested fields - dataSource.transform.push(...node.assembleTransforms(true)); - } else { - // Otherwise use Vega expression to parse - dataSource.transform.push(...node.assembleTransforms()); - } - } - if (node instanceof FacetNode) { - if (!dataSource.name) { - dataSource.name = `data_${datasetIndex++}`; - } - if (!dataSource.source || dataSource.transform.length > 0) { - data.push(dataSource); - node.data = dataSource.name; - } else { - node.data = dataSource.source; - } - data.push(...node.assemble()); - - // break here because the rest of the tree has to be taken care of by the facet. - return; - } - if (node instanceof GraticuleNode || node instanceof SequenceNode || node instanceof FilterInvalidNode || node instanceof FilterNode || node instanceof CalculateNode || node instanceof GeoPointNode || node instanceof AggregateNode || node instanceof LookupNode || node instanceof WindowTransformNode || node instanceof JoinAggregateTransformNode || node instanceof FoldTransformNode || node instanceof FlattenTransformNode || node instanceof DensityTransformNode || node instanceof LoessTransformNode || node instanceof QuantileTransformNode || node instanceof RegressionTransformNode || node instanceof IdentifierNode || node instanceof SampleTransformNode || node instanceof PivotTransformNode || node instanceof ExtentTransformNode) { - dataSource.transform.push(node.assemble()); - } - if (node instanceof BinNode || node instanceof TimeUnitNode || node instanceof ImputeNode || node instanceof StackNode || node instanceof GeoJSONNode) { - dataSource.transform.push(...node.assemble()); - } - if (node instanceof OutputNode) { - if (dataSource.source && dataSource.transform.length === 0) { - node.setSource(dataSource.source); - } else if (node.parent instanceof OutputNode) { - // Note that an output node may be required but we still do not assemble a - // separate data source for it. - node.setSource(dataSource.name); - } else { - if (!dataSource.name) { - dataSource.name = `data_${datasetIndex++}`; - } - - // Here we set the name of the datasource we generated. From now on - // other assemblers can use it. - node.setSource(dataSource.name); - - // if this node has more than one child, we will add a datasource automatically - if (node.numChildren() === 1) { - data.push(dataSource); - const newData = { - name: null, - source: dataSource.name, - transform: [] - }; - dataSource = newData; - } - } - } - switch (node.numChildren()) { - case 0: - // done - if (node instanceof OutputNode && (!dataSource.source || dataSource.transform.length > 0)) { - // do not push empty datasources that are simply references - data.push(dataSource); - } - break; - case 1: - walkTree(node.children[0], dataSource); - break; - default: - { - if (!dataSource.name) { - dataSource.name = `data_${datasetIndex++}`; - } - let source = dataSource.name; - if (!dataSource.source || dataSource.transform.length > 0) { - data.push(dataSource); - } else { - source = dataSource.source; - } - for (const child of node.children) { - const newData = { - name: null, - source, - transform: [] - }; - walkTree(child, newData); - } - break; - } - } - } - return walkTree; - } - - /** - * Assemble data sources that are derived from faceted data. - */ - function assembleFacetData(root) { - const data = []; - const walkTree = makeWalkTree(data); - for (const child of root.children) { - walkTree(child, { - source: root.name, - name: null, - transform: [] - }); - } - return data; - } - - /** - * Create Vega data array from a given compiled model and append all of them to the given array - * - * @param model - * @param data array - * @return modified data array - */ - function assembleRootData(dataComponent, datasets) { - const data = []; - - // dataComponent.sources.forEach(debug); - // draw(dataComponent.sources); - - const walkTree = makeWalkTree(data); - let sourceIndex = 0; - for (const root of dataComponent.sources) { - // assign a name if the source does not have a name yet - if (!root.hasName()) { - root.dataName = `source_${sourceIndex++}`; - } - const newData = root.assemble(); - walkTree(root, newData); - } - - // remove empty transform arrays for cleaner output - for (const d of data) { - if (d.transform.length === 0) { - delete d.transform; - } - } - - // move sources without transforms (the ones that are potentially used in lookups) to the beginning - let whereTo = 0; - for (const [i, d] of data.entries()) { - if ((d.transform ?? []).length === 0 && !d.source) { - data.splice(whereTo++, 0, data.splice(i, 1)[0]); - } - } - - // now fix the from references in lookup transforms - for (const d of data) { - for (const t of d.transform ?? []) { - if (t.type === 'lookup') { - t.from = dataComponent.outputNodes[t.from].getSource(); - } - } - } - - // inline values for datasets that are in the datastore - for (const d of data) { - if (d.name in datasets) { - d.values = datasets[d.name]; - } - } - return data; - } - - function getHeaderType(orient) { - if (orient === 'top' || orient === 'left' || isSignalRef(orient)) { - // we always use header for orient signal since we can't dynamically make header becomes footer - return 'header'; - } - return 'footer'; - } - function parseFacetHeaders(model) { - for (const channel of FACET_CHANNELS) { - parseFacetHeader(model, channel); - } - mergeChildAxis(model, 'x'); - mergeChildAxis(model, 'y'); - } - function parseFacetHeader(model, channel) { - const { - facet, - config, - child, - component - } = model; - if (model.channelHasField(channel)) { - const fieldDef = facet[channel]; - const titleConfig = getHeaderProperty('title', null, config, channel); - let title$1 = title(fieldDef, config, { - allowDisabling: true, - includeDefault: titleConfig === undefined || !!titleConfig - }); - if (child.component.layoutHeaders[channel].title) { - // TODO: better handle multiline titles - title$1 = vega.isArray(title$1) ? title$1.join(', ') : title$1; - - // merge title with child to produce "Title / Subtitle / Sub-subtitle" - title$1 += ` / ${child.component.layoutHeaders[channel].title}`; - child.component.layoutHeaders[channel].title = null; - } - const labelOrient = getHeaderProperty('labelOrient', fieldDef.header, config, channel); - const labels = fieldDef.header !== null ? getFirstDefined(fieldDef.header?.labels, config.header.labels, true) : false; - const headerType = contains(['bottom', 'right'], labelOrient) ? 'footer' : 'header'; - component.layoutHeaders[channel] = { - title: fieldDef.header !== null ? title$1 : null, - facetFieldDef: fieldDef, - [headerType]: channel === 'facet' ? [] : [makeHeaderComponent(model, channel, labels)] - }; - } - } - function makeHeaderComponent(model, channel, labels) { - const sizeType = channel === 'row' ? 'height' : 'width'; - return { - labels, - sizeSignal: model.child.component.layoutSize.get(sizeType) ? model.child.getSizeSignalRef(sizeType) : undefined, - axes: [] - }; - } - function mergeChildAxis(model, channel) { - const { - child - } = model; - if (child.component.axes[channel]) { - const { - layoutHeaders, - resolve - } = model.component; - resolve.axis[channel] = parseGuideResolve(resolve, channel); - if (resolve.axis[channel] === 'shared') { - // For shared axis, move the axes to facet's header or footer - const headerChannel = channel === 'x' ? 'column' : 'row'; - const layoutHeader = layoutHeaders[headerChannel]; - for (const axisComponent of child.component.axes[channel]) { - const headerType = getHeaderType(axisComponent.get('orient')); - layoutHeader[headerType] ??= [makeHeaderComponent(model, headerChannel, false)]; - - // FIXME: assemble shouldn't be called here, but we do it this way so we only extract the main part of the axes - const mainAxis = assembleAxis(axisComponent, 'main', model.config, { - header: true - }); - if (mainAxis) { - // LayoutHeader no longer keep track of property precedence, thus let's combine. - layoutHeader[headerType][0].axes.push(mainAxis); - } - axisComponent.mainExtracted = true; - } - } - } - } - - function parseLayerLayoutSize(model) { - parseChildrenLayoutSize(model); - parseNonUnitLayoutSizeForChannel(model, 'width'); - parseNonUnitLayoutSizeForChannel(model, 'height'); - } - function parseConcatLayoutSize(model) { - parseChildrenLayoutSize(model); - - // for columns === 1 (vconcat), we can completely merge width. Otherwise, we can treat merged width as childWidth. - const widthType = model.layout.columns === 1 ? 'width' : 'childWidth'; - - // for columns === undefined (hconcat), we can completely merge height. Otherwise, we can treat merged height as childHeight. - const heightType = model.layout.columns === undefined ? 'height' : 'childHeight'; - parseNonUnitLayoutSizeForChannel(model, widthType); - parseNonUnitLayoutSizeForChannel(model, heightType); - } - function parseChildrenLayoutSize(model) { - for (const child of model.children) { - child.parseLayoutSize(); - } - } - - /** - * Merge child layout size (width or height). - */ - function parseNonUnitLayoutSizeForChannel(model, layoutSizeType) { - /* - * For concat, the parent width or height might not be the same as the children's shared height. - * For example, hconcat's subviews may share width, but the shared width is not the hconcat view's width. - * - * layoutSizeType represents the output of the view (could be childWidth/childHeight/width/height) - * while the sizeType represents the properties of the child. - */ - const sizeType = getSizeTypeFromLayoutSizeType(layoutSizeType); - const channel = getPositionScaleChannel(sizeType); - const resolve = model.component.resolve; - const layoutSizeCmpt = model.component.layoutSize; - let mergedSize; - // Try to merge layout size - for (const child of model.children) { - const childSize = child.component.layoutSize.getWithExplicit(sizeType); - const scaleResolve = resolve.scale[channel] ?? defaultScaleResolve(channel, model); - if (scaleResolve === 'independent' && childSize.value === 'step') { - // Do not merge independent scales with range-step as their size depends - // on the scale domains, which can be different between scales. - mergedSize = undefined; - break; - } - if (mergedSize) { - if (scaleResolve === 'independent' && mergedSize.value !== childSize.value) { - // For independent scale, only merge if all the sizes are the same. - // If the values are different, abandon the merge! - mergedSize = undefined; - break; - } - mergedSize = mergeValuesWithExplicit(mergedSize, childSize, sizeType, ''); - } else { - mergedSize = childSize; - } - } - if (mergedSize) { - // If merged, rename size and set size of all children. - for (const child of model.children) { - model.renameSignal(child.getName(sizeType), model.getName(layoutSizeType)); - child.component.layoutSize.set(sizeType, 'merged', false); - } - layoutSizeCmpt.setWithExplicit(layoutSizeType, mergedSize); - } else { - layoutSizeCmpt.setWithExplicit(layoutSizeType, { - explicit: false, - value: undefined - }); - } - } - function parseUnitLayoutSize(model) { - const { - size, - component - } = model; - for (const channel of POSITION_SCALE_CHANNELS) { - const sizeType = getSizeChannel(channel); - if (size[sizeType]) { - const specifiedSize = size[sizeType]; - component.layoutSize.set(sizeType, isStep(specifiedSize) ? 'step' : specifiedSize, true); - } else { - const defaultSize = defaultUnitSize(model, sizeType); - component.layoutSize.set(sizeType, defaultSize, false); - } - } - } - function defaultUnitSize(model, sizeType) { - const channel = sizeType === 'width' ? 'x' : 'y'; - const config = model.config; - const scaleComponent = model.getScaleComponent(channel); - if (scaleComponent) { - const scaleType = scaleComponent.get('type'); - const range = scaleComponent.get('range'); - if (hasDiscreteDomain(scaleType)) { - const size = getViewConfigDiscreteSize(config.view, sizeType); - if (isVgRangeStep(range) || isStep(size)) { - // For discrete domain with range.step, use dynamic width/height - return 'step'; - } else { - return size; - } - } else { - return getViewConfigContinuousSize(config.view, sizeType); - } - } else if (model.hasProjection || model.mark === 'arc') { - // arc should use continuous size by default otherwise the pie is extremely small - return getViewConfigContinuousSize(config.view, sizeType); - } else { - const size = getViewConfigDiscreteSize(config.view, sizeType); - return isStep(size) ? size.step : size; - } - } - - function facetSortFieldName(fieldDef, sort, opt) { - return vgField(sort, { - suffix: `by_${vgField(fieldDef)}`, - ...opt - }); - } - class FacetModel extends ModelWithField { - constructor(spec, parent, parentGivenName, config) { - super(spec, 'facet', parent, parentGivenName, config, spec.resolve); - this.child = buildModel(spec.spec, this, this.getName('child'), undefined, config); - this.children = [this.child]; - this.facet = this.initFacet(spec.facet); - } - initFacet(facet) { - // clone to prevent side effect to the original spec - if (!isFacetMapping(facet)) { - return { - facet: this.initFacetFieldDef(facet, 'facet') - }; - } - const channels = keys(facet); - const normalizedFacet = {}; - for (const channel of channels) { - if (![ROW, COLUMN].includes(channel)) { - // Drop unsupported channel - warn(incompatibleChannel(channel, 'facet')); - break; - } - const fieldDef = facet[channel]; - if (fieldDef.field === undefined) { - warn(emptyFieldDef(fieldDef, channel)); - break; - } - normalizedFacet[channel] = this.initFacetFieldDef(fieldDef, channel); - } - return normalizedFacet; - } - initFacetFieldDef(fieldDef, channel) { - // Cast because we call initFieldDef, which assumes general FieldDef. - // However, FacetFieldDef is a bit more constrained than the general FieldDef - const facetFieldDef = initFieldDef(fieldDef, channel); - if (facetFieldDef.header) { - facetFieldDef.header = replaceExprRef(facetFieldDef.header); - } else if (facetFieldDef.header === null) { - facetFieldDef.header = null; - } - return facetFieldDef; - } - channelHasField(channel) { - return !!this.facet[channel]; - } - fieldDef(channel) { - return this.facet[channel]; - } - parseData() { - this.component.data = parseData(this); - this.child.parseData(); - } - parseLayoutSize() { - parseChildrenLayoutSize(this); - } - parseSelections() { - // As a facet has a single child, the selection components are the same. - // The child maintains its selections to assemble signals, which remain - // within its unit. - this.child.parseSelections(); - this.component.selection = this.child.component.selection; - } - parseMarkGroup() { - this.child.parseMarkGroup(); - } - parseAxesAndHeaders() { - this.child.parseAxesAndHeaders(); - parseFacetHeaders(this); - } - assembleSelectionTopLevelSignals(signals) { - return this.child.assembleSelectionTopLevelSignals(signals); - } - assembleSignals() { - this.child.assembleSignals(); - return []; - } - assembleSelectionData(data) { - return this.child.assembleSelectionData(data); - } - getHeaderLayoutMixins() { - const layoutMixins = {}; - for (const channel of FACET_CHANNELS) { - for (const headerType of HEADER_TYPES) { - const layoutHeaderComponent = this.component.layoutHeaders[channel]; - const headerComponent = layoutHeaderComponent[headerType]; - const { - facetFieldDef - } = layoutHeaderComponent; - if (facetFieldDef) { - const titleOrient = getHeaderProperty('titleOrient', facetFieldDef.header, this.config, channel); - if (['right', 'bottom'].includes(titleOrient)) { - const headerChannel = getHeaderChannel(channel, titleOrient); - layoutMixins.titleAnchor ??= {}; - layoutMixins.titleAnchor[headerChannel] = 'end'; - } - } - if (headerComponent?.[0]) { - // set header/footerBand - const sizeType = channel === 'row' ? 'height' : 'width'; - const bandType = headerType === 'header' ? 'headerBand' : 'footerBand'; - if (channel !== 'facet' && !this.child.component.layoutSize.get(sizeType)) { - // If facet child does not have size signal, then apply headerBand - layoutMixins[bandType] ??= {}; - layoutMixins[bandType][channel] = 0.5; - } - if (layoutHeaderComponent.title) { - layoutMixins.offset ??= {}; - layoutMixins.offset[channel === 'row' ? 'rowTitle' : 'columnTitle'] = 10; - } - } - } - } - return layoutMixins; - } - assembleDefaultLayout() { - const { - column, - row - } = this.facet; - const columns = column ? this.columnDistinctSignal() : row ? 1 : undefined; - let align = 'all'; - - // Do not align the cells if the scale corresponding to the direction is indepent. - // We always align when we facet into both row and column. - if (!row && this.component.resolve.scale.x === 'independent') { - align = 'none'; - } else if (!column && this.component.resolve.scale.y === 'independent') { - align = 'none'; - } - return { - ...this.getHeaderLayoutMixins(), - ...(columns ? { - columns - } : {}), - bounds: 'full', - align - }; - } - assembleLayoutSignals() { - // FIXME(https://github.com/vega/vega-lite/issues/1193): this can be incorrect if we have independent scales. - return this.child.assembleLayoutSignals(); - } - columnDistinctSignal() { - if (this.parent && this.parent instanceof FacetModel) { - // For nested facet, we will add columns to group mark instead - // See discussion in https://github.com/vega/vega/issues/952 - // and https://github.com/vega/vega-view/releases/tag/v1.2.6 - return undefined; - } else { - // In facetNode.assemble(), the name is always this.getName('column') + '_layout'. - const facetLayoutDataName = this.getName('column_domain'); - return { - signal: `length(data('${facetLayoutDataName}'))` - }; - } - } - assembleGroupStyle() { - return undefined; - } - assembleGroup(signals) { - if (this.parent && this.parent instanceof FacetModel) { - // Provide number of columns for layout. - // See discussion in https://github.com/vega/vega/issues/952 - // and https://github.com/vega/vega-view/releases/tag/v1.2.6 - return { - ...(this.channelHasField('column') ? { - encode: { - update: { - // TODO(https://github.com/vega/vega-lite/issues/2759): - // Correct the signal for facet of concat of facet_column - columns: { - field: vgField(this.facet.column, { - prefix: 'distinct' - }) - } - } - } - } : {}), - ...super.assembleGroup(signals) - }; - } - return super.assembleGroup(signals); - } - - /** - * Aggregate cardinality for calculating size - */ - getCardinalityAggregateForChild() { - const fields = []; - const ops = []; - const as = []; - if (this.child instanceof FacetModel) { - if (this.child.channelHasField('column')) { - const field = vgField(this.child.facet.column); - fields.push(field); - ops.push('distinct'); - as.push(`distinct_${field}`); - } - } else { - for (const channel of POSITION_SCALE_CHANNELS) { - const childScaleComponent = this.child.component.scales[channel]; - if (childScaleComponent && !childScaleComponent.merged) { - const type = childScaleComponent.get('type'); - const range = childScaleComponent.get('range'); - if (hasDiscreteDomain(type) && isVgRangeStep(range)) { - const domain = assembleDomain(this.child, channel); - const field = getFieldFromDomain(domain); - if (field) { - fields.push(field); - ops.push('distinct'); - as.push(`distinct_${field}`); - } else { - warn(unknownField(channel)); - } - } - } - } - } - return { - fields, - ops, - as - }; - } - assembleFacet() { - const { - name, - data - } = this.component.data.facetRoot; - const { - row, - column - } = this.facet; - const { - fields, - ops, - as - } = this.getCardinalityAggregateForChild(); - const groupby = []; - for (const channel of FACET_CHANNELS) { - const fieldDef = this.facet[channel]; - if (fieldDef) { - groupby.push(vgField(fieldDef)); - const { - bin, - sort - } = fieldDef; - if (isBinning(bin)) { - groupby.push(vgField(fieldDef, { - binSuffix: 'end' - })); - } - if (isSortField(sort)) { - const { - field, - op = DEFAULT_SORT_OP - } = sort; - const outputName = facetSortFieldName(fieldDef, sort); - if (row && column) { - // For crossed facet, use pre-calculate field as it requires a different groupby - // For each calculated field, apply max and assign them to the same name as - // all values of the same group should be the same anyway. - fields.push(outputName); - ops.push('max'); - as.push(outputName); - } else { - fields.push(field); - ops.push(op); - as.push(outputName); - } - } else if (vega.isArray(sort)) { - const outputName = sortArrayIndexField(fieldDef, channel); - fields.push(outputName); - ops.push('max'); - as.push(outputName); - } - } - } - const cross = !!row && !!column; - return { - name, - data, - groupby, - ...(cross || fields.length > 0 ? { - aggregate: { - ...(cross ? { - cross - } : {}), - ...(fields.length ? { - fields, - ops, - as - } : {}) - } - } : {}) - }; - } - facetSortFields(channel) { - const { - facet - } = this; - const fieldDef = facet[channel]; - if (fieldDef) { - if (isSortField(fieldDef.sort)) { - return [facetSortFieldName(fieldDef, fieldDef.sort, { - expr: 'datum' - })]; - } else if (vega.isArray(fieldDef.sort)) { - return [sortArrayIndexField(fieldDef, channel, { - expr: 'datum' - })]; - } - return [vgField(fieldDef, { - expr: 'datum' - })]; - } - return []; - } - facetSortOrder(channel) { - const { - facet - } = this; - const fieldDef = facet[channel]; - if (fieldDef) { - const { - sort - } = fieldDef; - const order = (isSortField(sort) ? sort.order : !vega.isArray(sort) && sort) || 'ascending'; - return [order]; - } - return []; - } - assembleLabelTitle() { - const { - facet, - config - } = this; - if (facet.facet) { - // Facet always uses title to display labels - return assembleLabelTitle(facet.facet, 'facet', config); - } - const ORTHOGONAL_ORIENT = { - row: ['top', 'bottom'], - column: ['left', 'right'] - }; - for (const channel of HEADER_CHANNELS) { - if (facet[channel]) { - const labelOrient = getHeaderProperty('labelOrient', facet[channel]?.header, config, channel); - if (ORTHOGONAL_ORIENT[channel].includes(labelOrient)) { - // Row/Column with orthogonal labelOrient must use title to display labels - return assembleLabelTitle(facet[channel], channel, config); - } - } - } - return undefined; - } - assembleMarks() { - const { - child - } = this; - - // If we facet by two dimensions, we need to add a cross operator to the aggregation - // so that we create all groups - const facetRoot = this.component.data.facetRoot; - const data = assembleFacetData(facetRoot); - const encodeEntry = child.assembleGroupEncodeEntry(false); - const title = this.assembleLabelTitle() || child.assembleTitle(); - const style = child.assembleGroupStyle(); - const markGroup = { - name: this.getName('cell'), - type: 'group', - ...(title ? { - title - } : {}), - ...(style ? { - style - } : {}), - from: { - facet: this.assembleFacet() - }, - // TODO: move this to after data - sort: { - field: FACET_CHANNELS.map(c => this.facetSortFields(c)).flat(), - order: FACET_CHANNELS.map(c => this.facetSortOrder(c)).flat() - }, - ...(data.length > 0 ? { - data - } : {}), - ...(encodeEntry ? { - encode: { - update: encodeEntry - } - } : {}), - ...child.assembleGroup(assembleFacetSignals(this, [])) - }; - return [markGroup]; - } - getMapping() { - return this.facet; - } - } - - function makeJoinAggregateFromFacet(parent, facet) { - const { - row, - column - } = facet; - if (row && column) { - let newParent = null; - // only need to make one for crossed facet - for (const fieldDef of [row, column]) { - if (isSortField(fieldDef.sort)) { - const { - field, - op = DEFAULT_SORT_OP - } = fieldDef.sort; - parent = newParent = new JoinAggregateTransformNode(parent, { - joinaggregate: [{ - op, - field, - as: facetSortFieldName(fieldDef, fieldDef.sort, { - forAs: true - }) - }], - groupby: [vgField(fieldDef)] - }); - } - } - return newParent; - } - return null; - } - - function findSource(data, sources) { - for (const other of sources) { - const otherData = other.data; - - // if both datasets have a name defined, we cannot merge - if (data.name && other.hasName() && data.name !== other.dataName) { - continue; - } - const formatMesh = data['format']?.mesh; - const otherFeature = otherData.format?.feature; - - // feature and mesh are mutually exclusive - if (formatMesh && otherFeature) { - continue; - } - - // we have to extract the same feature or mesh - const formatFeature = data['format']?.feature; - if ((formatFeature || otherFeature) && formatFeature !== otherFeature) { - continue; - } - const otherMesh = otherData.format?.mesh; - if ((formatMesh || otherMesh) && formatMesh !== otherMesh) { - continue; - } - if (isInlineData(data) && isInlineData(otherData)) { - if (deepEqual(data.values, otherData.values)) { - return other; - } - } else if (isUrlData(data) && isUrlData(otherData)) { - if (data.url === otherData.url) { - return other; - } - } else if (isNamedData(data)) { - if (data.name === other.dataName) { - return other; - } - } - } - return null; - } - function parseRoot(model, sources) { - if (model.data || !model.parent) { - // if the model defines a data source or is the root, create a source node - - if (model.data === null) { - // data: null means we should ignore the parent's data so we just create a new data source - const source = new SourceNode({ - values: [] - }); - sources.push(source); - return source; - } - const existingSource = findSource(model.data, sources); - if (existingSource) { - if (!isGenerator(model.data)) { - existingSource.data.format = mergeDeep({}, model.data.format, existingSource.data.format); - } - - // if the new source has a name but the existing one does not, we can set it - if (!existingSource.hasName() && model.data.name) { - existingSource.dataName = model.data.name; - } - return existingSource; - } else { - const source = new SourceNode(model.data); - sources.push(source); - return source; - } - } else { - // If we don't have a source defined (overriding parent's data), use the parent's facet root or main. - return model.parent.component.data.facetRoot ? model.parent.component.data.facetRoot : model.parent.component.data.main; - } - } - - /** - * Parses a transform array into a chain of connected dataflow nodes. - */ - function parseTransformArray(head, model, ancestorParse) { - let lookupCounter = 0; - for (const t of model.transforms) { - let derivedType = undefined; - let transformNode; - if (isCalculate(t)) { - transformNode = head = new CalculateNode(head, t); - derivedType = 'derived'; - } else if (isFilter(t)) { - const implicit = getImplicitFromFilterTransform(t); - transformNode = head = ParseNode.makeWithAncestors(head, {}, implicit, ancestorParse) ?? head; - head = new FilterNode(head, model, t.filter); - } else if (isBin(t)) { - transformNode = head = BinNode.makeFromTransform(head, t, model); - derivedType = 'number'; - } else if (isTimeUnit(t)) { - derivedType = 'date'; - const parsedAs = ancestorParse.getWithExplicit(t.field); - // Create parse node because the input to time unit is always date. - if (parsedAs.value === undefined) { - head = new ParseNode(head, { - [t.field]: derivedType - }); - ancestorParse.set(t.field, derivedType, false); - } - transformNode = head = TimeUnitNode.makeFromTransform(head, t); - } else if (isAggregate(t)) { - transformNode = head = AggregateNode.makeFromTransform(head, t); - derivedType = 'number'; - if (requiresSelectionId(model)) { - head = new IdentifierNode(head); - } - } else if (isLookup(t)) { - transformNode = head = LookupNode.make(head, model, t, lookupCounter++); - derivedType = 'derived'; - } else if (isWindow(t)) { - transformNode = head = new WindowTransformNode(head, t); - derivedType = 'number'; - } else if (isJoinAggregate(t)) { - transformNode = head = new JoinAggregateTransformNode(head, t); - derivedType = 'number'; - } else if (isStack(t)) { - transformNode = head = StackNode.makeFromTransform(head, t); - derivedType = 'derived'; - } else if (isFold(t)) { - transformNode = head = new FoldTransformNode(head, t); - derivedType = 'derived'; - } else if (isExtent(t)) { - transformNode = head = new ExtentTransformNode(head, t); - derivedType = 'derived'; - } else if (isFlatten(t)) { - transformNode = head = new FlattenTransformNode(head, t); - derivedType = 'derived'; - } else if (isPivot(t)) { - transformNode = head = new PivotTransformNode(head, t); - derivedType = 'derived'; - } else if (isSample(t)) { - head = new SampleTransformNode(head, t); - } else if (isImpute(t)) { - transformNode = head = ImputeNode.makeFromTransform(head, t); - derivedType = 'derived'; - } else if (isDensity(t)) { - transformNode = head = new DensityTransformNode(head, t); - derivedType = 'derived'; - } else if (isQuantile(t)) { - transformNode = head = new QuantileTransformNode(head, t); - derivedType = 'derived'; - } else if (isRegression(t)) { - transformNode = head = new RegressionTransformNode(head, t); - derivedType = 'derived'; - } else if (isLoess(t)) { - transformNode = head = new LoessTransformNode(head, t); - derivedType = 'derived'; - } else { - warn(invalidTransformIgnored(t)); - continue; - } - if (transformNode && derivedType !== undefined) { - for (const field of transformNode.producedFields() ?? []) { - ancestorParse.set(field, derivedType, false); - } - } - } - return head; - } - - /* - Description of the dataflow (http://asciiflow.com/): - +--------+ - | Source | - +---+----+ - | - v - FormatParse - (explicit) - | - v - Transforms - (Filter, Calculate, Binning, TimeUnit, Aggregate, Window, ...) - | - v - FormatParse - (implicit) - | - v - Binning (in `encoding`) - | - v - Timeunit (in `encoding`) - | - v - Formula From Sort Array - | - v - +--+--+ - | Raw | - +-----+ - | - v - Aggregate (in `encoding`) - | - v - Stack (in `encoding`) - | - v - +- - - - - - - - - - -+ - | PreFilterInvalid | - - - -> scale domains - |(when scales need it)| - +- - - - - - - - - - -+ - | - v - Invalid Filter (if the main data source needs it) - | - v - +----------+ - | Main | - - - -> scale domains - +----------+ - | - v - +- - - - - - - - - - -+ - | PostFilterInvalid | - - - -> scale domains - |(when scales need it)| - +- - - - - - - - - - -+ - | - v - +-------+ - | Facet |----> "column", "column-layout", and "row" - +-------+ - | - v - ...Child data... - */ - - function parseData(model) { - let head = parseRoot(model, model.component.data.sources); - const { - outputNodes, - outputNodeRefCounts - } = model.component.data; - const data = model.data; - const newData = data && (isGenerator(data) || isUrlData(data) || isInlineData(data)); - const ancestorParse = !newData && model.parent ? model.parent.component.data.ancestorParse.clone() : new AncestorParse(); - if (isGenerator(data)) { - // insert generator transform - if (isSequenceGenerator(data)) { - head = new SequenceNode(head, data.sequence); - } else if (isGraticuleGenerator(data)) { - head = new GraticuleNode(head, data.graticule); - } - // no parsing necessary for generator - ancestorParse.parseNothing = true; - } else if (data?.format?.parse === null) { - // format.parse: null means disable parsing - ancestorParse.parseNothing = true; - } - head = ParseNode.makeExplicit(head, model, ancestorParse) ?? head; - - // Default discrete selections require an identifer transform to - // uniquely identify data points. Add this transform at the head of - // the pipeline such that the identifier field is available for all - // subsequent datasets. During optimization, we will remove this - // transform if it proves to be unnecessary. Additional identifier - // transforms will be necessary when new tuples are constructed - // (e.g., post-aggregation). - head = new IdentifierNode(head); - - // HACK: This is equivalent for merging bin extent for union scale. - // FIXME(https://github.com/vega/vega-lite/issues/2270): Correctly merge extent / bin node for shared bin scale - const parentIsLayer = model.parent && isLayerModel(model.parent); - if (isUnitModel(model) || isFacetModel(model)) { - if (parentIsLayer) { - head = BinNode.makeFromEncoding(head, model) ?? head; - } - } - if (model.transforms.length > 0) { - head = parseTransformArray(head, model, ancestorParse); - } - - // create parse nodes for fields that need to be parsed (or flattened) implicitly - const implicitSelection = getImplicitFromSelection(model); - const implicitEncoding = getImplicitFromEncoding(model); - head = ParseNode.makeWithAncestors(head, {}, { - ...implicitSelection, - ...implicitEncoding - }, ancestorParse) ?? head; - if (isUnitModel(model)) { - head = GeoJSONNode.parseAll(head, model); - head = GeoPointNode.parseAll(head, model); - } - if (isUnitModel(model) || isFacetModel(model)) { - if (!parentIsLayer) { - head = BinNode.makeFromEncoding(head, model) ?? head; - } - head = TimeUnitNode.makeFromEncoding(head, model) ?? head; - head = CalculateNode.parseAllForSortIndex(head, model); - } - - // add an output node pre aggregation - const raw = head = makeOutputNode(DataSourceType.Raw, model, head); - if (isUnitModel(model)) { - const agg = AggregateNode.makeFromEncoding(head, model); - if (agg) { - head = agg; - if (requiresSelectionId(model)) { - head = new IdentifierNode(head); - } - } - head = ImputeNode.makeFromEncoding(head, model) ?? head; - head = StackNode.makeFromEncoding(head, model) ?? head; - } - let preFilterInvalid; - let dataSourcesForHandlingInvalidValues; - if (isUnitModel(model)) { - const { - markDef, - mark, - config - } = model; - const invalid = getMarkPropOrConfig('invalid', markDef, config); - const { - marks, - scales - } = dataSourcesForHandlingInvalidValues = getDataSourcesForHandlingInvalidValues({ - invalid, - isPath: isPathMark(mark) - }); - if (marks !== scales && scales === 'include-invalid-values') { - // Create a seperate preFilterInvalid dataSource if scales need pre-filter data but marks needs post-filter. - preFilterInvalid = head = makeOutputNode(DataSourceType.PreFilterInvalid, model, head); - } - if (marks === 'exclude-invalid-values') { - head = FilterInvalidNode.make(head, model, dataSourcesForHandlingInvalidValues) ?? head; - } - } - - // output "main" node for marks - const main = head = makeOutputNode(DataSourceType.Main, model, head); - let postFilterInvalid; - if (isUnitModel(model) && dataSourcesForHandlingInvalidValues) { - const { - marks, - scales - } = dataSourcesForHandlingInvalidValues; - if (marks === 'include-invalid-values' && scales === 'exclude-invalid-values') { - // Create a seperate postFilterInvalid dataSource if scales need post-filter data but marks needs pre-filter. - head = FilterInvalidNode.make(head, model, dataSourcesForHandlingInvalidValues) ?? head; - postFilterInvalid = head = makeOutputNode(DataSourceType.PostFilterInvalid, model, head); - } - } - if (isUnitModel(model)) { - materializeSelections(model, main); - } - - // add facet marker - let facetRoot = null; - if (isFacetModel(model)) { - const facetName = model.getName('facet'); - - // Derive new aggregate for facet's sort field - // augment data source with new fields for crossed facet - head = makeJoinAggregateFromFacet(head, model.facet) ?? head; - facetRoot = new FacetNode(head, model, facetName, main.getSource()); - outputNodes[facetName] = facetRoot; - } - return { - ...model.component.data, - outputNodes, - outputNodeRefCounts, - raw, - main, - facetRoot, - ancestorParse, - preFilterInvalid, - postFilterInvalid - }; - } - function makeOutputNode(dataSourceType, model, head) { - const { - outputNodes, - outputNodeRefCounts - } = model.component.data; - const name = model.getDataName(dataSourceType); - const node = new OutputNode(head, name, dataSourceType, outputNodeRefCounts); - outputNodes[name] = node; - return node; - } - - class ConcatModel extends Model { - constructor(spec, parent, parentGivenName, config) { - super(spec, 'concat', parent, parentGivenName, config, spec.resolve); - if (spec.resolve?.axis?.x === 'shared' || spec.resolve?.axis?.y === 'shared') { - warn(CONCAT_CANNOT_SHARE_AXIS); - } - this.children = this.getChildren(spec).map((child, i) => { - return buildModel(child, this, this.getName(`concat_${i}`), undefined, config); - }); - } - parseData() { - this.component.data = parseData(this); - for (const child of this.children) { - child.parseData(); - } - } - parseSelections() { - // Merge selections up the hierarchy so that they may be referenced - // across unit specs. Persist their definitions within each child - // to assemble signals which remain within output Vega unit groups. - this.component.selection = {}; - for (const child of this.children) { - child.parseSelections(); - for (const key of keys(child.component.selection)) { - this.component.selection[key] = child.component.selection[key]; - } - } - } - parseMarkGroup() { - for (const child of this.children) { - child.parseMarkGroup(); - } - } - parseAxesAndHeaders() { - for (const child of this.children) { - child.parseAxesAndHeaders(); - } - - // TODO(#2415): support shared axes - } - getChildren(spec) { - if (isVConcatSpec(spec)) { - return spec.vconcat; - } else if (isHConcatSpec(spec)) { - return spec.hconcat; - } - return spec.concat; - } - parseLayoutSize() { - parseConcatLayoutSize(this); - } - parseAxisGroup() { - return null; - } - assembleSelectionTopLevelSignals(signals) { - return this.children.reduce((sg, child) => child.assembleSelectionTopLevelSignals(sg), signals); - } - assembleSignals() { - this.children.forEach(child => child.assembleSignals()); - return []; - } - assembleLayoutSignals() { - const layoutSignals = assembleLayoutSignals(this); - for (const child of this.children) { - layoutSignals.push(...child.assembleLayoutSignals()); - } - return layoutSignals; - } - assembleSelectionData(data) { - return this.children.reduce((db, child) => child.assembleSelectionData(db), data); - } - assembleMarks() { - // only children have marks - return this.children.map(child => { - const title = child.assembleTitle(); - const style = child.assembleGroupStyle(); - const encodeEntry = child.assembleGroupEncodeEntry(false); - return { - type: 'group', - name: child.getName('group'), - ...(title ? { - title - } : {}), - ...(style ? { - style - } : {}), - ...(encodeEntry ? { - encode: { - update: encodeEntry - } - } : {}), - ...child.assembleGroup() - }; - }); - } - assembleGroupStyle() { - return undefined; - } - assembleDefaultLayout() { - const columns = this.layout.columns; - return { - ...(columns != null ? { - columns - } : {}), - bounds: 'full', - // Use align each so it can work with multiple plots with different size - align: 'each' - }; - } - } - - function isFalseOrNull(v) { - return v === false || v === null; - } - const AXIS_COMPONENT_PROPERTIES_INDEX = { - disable: 1, - gridScale: 1, - scale: 1, - ...COMMON_AXIS_PROPERTIES_INDEX, - labelExpr: 1, - encode: 1 - }; - const AXIS_COMPONENT_PROPERTIES = keys(AXIS_COMPONENT_PROPERTIES_INDEX); - class AxisComponent extends Split { - constructor() { - let explicit = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - let implicit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - let mainExtracted = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; - super(); - this.explicit = explicit; - this.implicit = implicit; - this.mainExtracted = mainExtracted; - } - clone() { - return new AxisComponent(duplicate(this.explicit), duplicate(this.implicit), this.mainExtracted); - } - hasAxisPart(part) { - // FIXME(https://github.com/vega/vega-lite/issues/2552) this method can be wrong if users use a Vega theme. - - if (part === 'axis') { - // always has the axis container part - return true; - } - if (part === 'grid' || part === 'title') { - return !!this.get(part); - } - // Other parts are enabled by default, so they should not be false or null. - return !isFalseOrNull(this.get(part)); - } - hasOrientSignalRef() { - return isSignalRef(this.explicit.orient); - } - } - - function labels(model, channel, specifiedLabelsSpec) { - const { - encoding, - config - } = model; - const fieldOrDatumDef = getFieldOrDatumDef(encoding[channel]) ?? getFieldOrDatumDef(encoding[getSecondaryRangeChannel(channel)]); - const axis = model.axis(channel) || {}; - const { - format, - formatType - } = axis; - if (isCustomFormatType(formatType)) { - return { - text: formatCustomType({ - fieldOrDatumDef, - field: 'datum.value', - format, - formatType, - config - }), - ...specifiedLabelsSpec - }; - } else if (format === undefined && formatType === undefined && config.customFormatTypes) { - if (channelDefType(fieldOrDatumDef) === 'quantitative') { - if (isPositionFieldOrDatumDef(fieldOrDatumDef) && fieldOrDatumDef.stack === 'normalize' && config.normalizedNumberFormatType) { - return { - text: formatCustomType({ - fieldOrDatumDef, - field: 'datum.value', - format: config.normalizedNumberFormat, - formatType: config.normalizedNumberFormatType, - config - }), - ...specifiedLabelsSpec - }; - } else if (config.numberFormatType) { - return { - text: formatCustomType({ - fieldOrDatumDef, - field: 'datum.value', - format: config.numberFormat, - formatType: config.numberFormatType, - config - }), - ...specifiedLabelsSpec - }; - } - } - if (channelDefType(fieldOrDatumDef) === 'temporal' && config.timeFormatType && isFieldDef(fieldOrDatumDef) && !fieldOrDatumDef.timeUnit) { - return { - text: formatCustomType({ - fieldOrDatumDef, - field: 'datum.value', - format: config.timeFormat, - formatType: config.timeFormatType, - config - }), - ...specifiedLabelsSpec - }; - } - } - return specifiedLabelsSpec; - } - - function parseUnitAxes(model) { - return POSITION_SCALE_CHANNELS.reduce((axis, channel) => { - if (model.component.scales[channel]) { - axis[channel] = [parseAxis(channel, model)]; - } - return axis; - }, {}); - } - const OPPOSITE_ORIENT = { - bottom: 'top', - top: 'bottom', - left: 'right', - right: 'left' - }; - function parseLayerAxes(model) { - const { - axes, - resolve - } = model.component; - const axisCount = { - top: 0, - bottom: 0, - right: 0, - left: 0 - }; - for (const child of model.children) { - child.parseAxesAndHeaders(); - for (const channel of keys(child.component.axes)) { - resolve.axis[channel] = parseGuideResolve(model.component.resolve, channel); - if (resolve.axis[channel] === 'shared') { - // If the resolve says shared (and has not been overridden) - // We will try to merge and see if there is a conflict - - axes[channel] = mergeAxisComponents(axes[channel], child.component.axes[channel]); - if (!axes[channel]) { - // If merge returns nothing, there is a conflict so we cannot make the axis shared. - // Thus, mark axis as independent and remove the axis component. - resolve.axis[channel] = 'independent'; - delete axes[channel]; - } - } - } - } - - // Move axes to layer's axis component and merge shared axes - for (const channel of POSITION_SCALE_CHANNELS) { - for (const child of model.children) { - if (!child.component.axes[channel]) { - // skip if the child does not have a particular axis - continue; - } - if (resolve.axis[channel] === 'independent') { - // If axes are independent, concat the axisComponent array. - axes[channel] = (axes[channel] ?? []).concat(child.component.axes[channel]); - - // Automatically adjust orient - for (const axisComponent of child.component.axes[channel]) { - const { - value: orient, - explicit - } = axisComponent.getWithExplicit('orient'); - if (isSignalRef(orient)) { - continue; - } - if (axisCount[orient] > 0 && !explicit) { - // Change axis orient if the number do not match - const oppositeOrient = OPPOSITE_ORIENT[orient]; - if (axisCount[orient] > axisCount[oppositeOrient]) { - axisComponent.set('orient', oppositeOrient, false); - } - } - axisCount[orient]++; - - // TODO(https://github.com/vega/vega-lite/issues/2634): automatically add extra offset? - } - } - - // After merging, make sure to remove axes from child - delete child.component.axes[channel]; - } - - // Show gridlines for first axis only for dual-axis chart - if (resolve.axis[channel] === 'independent' && axes[channel] && axes[channel].length > 1) { - for (const [index, axisCmpt] of (axes[channel] || []).entries()) { - if (index > 0 && !!axisCmpt.get('grid') && !axisCmpt.explicit.grid) { - axisCmpt.implicit.grid = false; - } - } - } - } - } - function mergeAxisComponents(mergedAxisCmpts, childAxisCmpts) { - if (mergedAxisCmpts) { - // FIXME: this is a bit wrong once we support multiple axes - if (mergedAxisCmpts.length !== childAxisCmpts.length) { - return undefined; // Cannot merge axis component with different number of axes. - } - const length = mergedAxisCmpts.length; - for (let i = 0; i < length; i++) { - const merged = mergedAxisCmpts[i]; - const child = childAxisCmpts[i]; - if (!!merged !== !!child) { - return undefined; - } else if (merged && child) { - const mergedOrient = merged.getWithExplicit('orient'); - const childOrient = child.getWithExplicit('orient'); - if (mergedOrient.explicit && childOrient.explicit && mergedOrient.value !== childOrient.value) { - // TODO: throw warning if resolve is explicit (We don't have info about explicit/implicit resolve yet.) - - // Cannot merge due to inconsistent orient - return undefined; - } else { - mergedAxisCmpts[i] = mergeAxisComponent(merged, child); - } - } - } - } else { - // For first one, return a copy of the child - return childAxisCmpts.map(axisComponent => axisComponent.clone()); - } - return mergedAxisCmpts; - } - function mergeAxisComponent(merged, child) { - for (const prop of AXIS_COMPONENT_PROPERTIES) { - const mergedValueWithExplicit = mergeValuesWithExplicit(merged.getWithExplicit(prop), child.getWithExplicit(prop), prop, 'axis', - // Tie breaker function - (v1, v2) => { - switch (prop) { - case 'title': - return mergeTitleComponent(v1, v2); - case 'gridScale': - return { - explicit: v1.explicit, - // keep the old explicit - value: getFirstDefined(v1.value, v2.value) - }; - } - return defaultTieBreaker(v1, v2, prop, 'axis'); - }); - merged.setWithExplicit(prop, mergedValueWithExplicit); - } - return merged; - } - function isExplicit(value, property, axis, model, channel) { - if (property === 'disable') { - return axis !== undefined; // if axis is specified or null/false, then its enable/disable state is explicit - } - axis = axis || {}; - switch (property) { - case 'titleAngle': - case 'labelAngle': - return value === (isSignalRef(axis.labelAngle) ? axis.labelAngle : normalizeAngle(axis.labelAngle)); - case 'values': - return !!axis.values; - // specified axis.values is already respected, but may get transformed. - case 'encode': - // both VL axis.encoding and axis.labelAngle affect VG axis.encode - return !!axis.encoding || !!axis.labelAngle; - case 'title': - // title can be explicit if fieldDef.title is set - if (value === getFieldDefTitle(model, channel)) { - return true; - } - } - // Otherwise, things are explicit if the returned value matches the specified property - return value === axis[property]; - } - - /** - * Properties to always include values from config - */ - const propsToAlwaysIncludeConfig = new Set(['grid', - // Grid is an exception because we need to set grid = true to generate another grid axis - 'translate', - // translate has dependent logic for bar's bin position and it's 0.5 by default in Vega. If a config overrides this value, we need to know. - // the rest are not axis configs in Vega, but are in VL, so we need to set too. - 'format', 'formatType', 'orient', 'labelExpr', 'tickCount', 'position', 'tickMinStep']); - function parseAxis(channel, model) { - let axis = model.axis(channel); - const axisComponent = new AxisComponent(); - const fieldOrDatumDef = getFieldOrDatumDef(model.encoding[channel]); - const { - mark, - config - } = model; - const orient = axis?.orient || config[channel === 'x' ? 'axisX' : 'axisY']?.orient || config.axis?.orient || defaultOrient(channel); - const scaleType = model.getScaleComponent(channel).get('type'); - const axisConfigs = getAxisConfigs(channel, scaleType, orient, model.config); - const disable = axis !== undefined ? !axis : getAxisConfig('disable', config.style, axis?.style, axisConfigs).configValue; - axisComponent.set('disable', disable, axis !== undefined); - if (disable) { - return axisComponent; - } - axis = axis || {}; - const labelAngle = getLabelAngle(fieldOrDatumDef, axis, channel, config.style, axisConfigs); - const formatType = guideFormatType(axis.formatType, fieldOrDatumDef, scaleType); - const format = guideFormat(fieldOrDatumDef, fieldOrDatumDef.type, axis.format, axis.formatType, config, true); - const ruleParams = { - fieldOrDatumDef, - axis, - channel, - model, - scaleType, - orient, - labelAngle, - format, - formatType, - mark, - config - }; - // 1.2. Add properties - for (const property of AXIS_COMPONENT_PROPERTIES) { - const value = property in axisRules ? axisRules[property](ruleParams) : isAxisProperty(property) ? axis[property] : undefined; - const hasValue = value !== undefined; - const explicit = isExplicit(value, property, axis, model, channel); - if (hasValue && explicit) { - axisComponent.set(property, value, explicit); - } else { - const { - configValue = undefined, - configFrom = undefined - } = isAxisProperty(property) && property !== 'values' ? getAxisConfig(property, config.style, axis.style, axisConfigs) : {}; - const hasConfigValue = configValue !== undefined; - if (hasValue && !hasConfigValue) { - // only set property if it is explicitly set or has no config value (otherwise we will accidentally override config) - axisComponent.set(property, value, explicit); - } else if ( - // Cases need implicit values - // 1. Axis config that aren't available in Vega - !(configFrom === 'vgAxisConfig') || - // 2. Certain properties are always included (see `propsToAlwaysIncludeConfig`'s declaration for more details) - propsToAlwaysIncludeConfig.has(property) && hasConfigValue || - // 3. Conditional axis values and signals - isConditionalAxisValue(configValue) || isSignalRef(configValue)) { - // If a config is specified and is conditional, copy conditional value from axis config - axisComponent.set(property, configValue, false); - } - } - } - - // 2) Add guide encode definition groups - const axisEncoding = axis.encoding ?? {}; - const axisEncode = AXIS_PARTS.reduce((e, part) => { - if (!axisComponent.hasAxisPart(part)) { - // No need to create encode for a disabled part. - return e; - } - const axisEncodingPart = guideEncodeEntry(axisEncoding[part] ?? {}, model); - const value = part === 'labels' ? labels(model, channel, axisEncodingPart) : axisEncodingPart; - if (value !== undefined && !isEmpty(value)) { - e[part] = { - update: value - }; - } - return e; - }, {}); - - // FIXME: By having encode as one property, we won't have fine grained encode merging. - if (!isEmpty(axisEncode)) { - axisComponent.set('encode', axisEncode, !!axis.encoding || axis.labelAngle !== undefined); - } - return axisComponent; - } - - function initLayoutSize(_ref) { - let { - encoding, - size - } = _ref; - for (const channel of POSITION_SCALE_CHANNELS) { - const sizeType = getSizeChannel(channel); - if (isStep(size[sizeType])) { - if (isContinuousFieldOrDatumDef(encoding[channel])) { - delete size[sizeType]; - warn(stepDropped(sizeType)); - } - } - } - return size; - } - - const arc = { - vgMark: 'arc', - encodeEntry: model => { - return { - ...baseEncodeEntry(model, { - align: 'ignore', - baseline: 'ignore', - color: 'include', - size: 'ignore', - orient: 'ignore', - theta: 'ignore' - }), - ...pointPosition('x', model, { - defaultPos: 'mid' - }), - ...pointPosition('y', model, { - defaultPos: 'mid' - }), - // arcs are rectangles in polar coordinates - ...rectPosition(model, 'radius'), - ...rectPosition(model, 'theta') - }; - } - }; - - const area = { - vgMark: 'area', - encodeEntry: model => { - return { - ...baseEncodeEntry(model, { - align: 'ignore', - baseline: 'ignore', - color: 'include', - orient: 'include', - size: 'ignore', - theta: 'ignore' - }), - ...pointOrRangePosition('x', model, { - defaultPos: 'zeroOrMin', - defaultPos2: 'zeroOrMin', - range: model.markDef.orient === 'horizontal' - }), - ...pointOrRangePosition('y', model, { - defaultPos: 'zeroOrMin', - defaultPos2: 'zeroOrMin', - range: model.markDef.orient === 'vertical' - }), - ...defined(model) - }; - } - }; - - const bar = { - vgMark: 'rect', - encodeEntry: model => { - return { - ...baseEncodeEntry(model, { - align: 'ignore', - baseline: 'ignore', - color: 'include', - orient: 'ignore', - size: 'ignore', - theta: 'ignore' - }), - ...rectPosition(model, 'x'), - ...rectPosition(model, 'y') - }; - } - }; - - const geoshape = { - vgMark: 'shape', - encodeEntry: model => { - return { - ...baseEncodeEntry(model, { - align: 'ignore', - baseline: 'ignore', - color: 'include', - size: 'ignore', - orient: 'ignore', - theta: 'ignore' - }) - }; - }, - postEncodingTransform: model => { - const { - encoding - } = model; - const shapeDef = encoding.shape; - const transform = { - type: 'geoshape', - projection: model.projectionName(), - // as: 'shape', - ...(shapeDef && isFieldDef(shapeDef) && shapeDef.type === GEOJSON ? { - field: vgField(shapeDef, { - expr: 'datum' - }) - } : {}) - }; - return [transform]; - } - }; - - const image = { - vgMark: 'image', - encodeEntry: model => { - return { - ...baseEncodeEntry(model, { - align: 'ignore', - baseline: 'ignore', - color: 'ignore', - orient: 'ignore', - size: 'ignore', - theta: 'ignore' - }), - ...rectPosition(model, 'x'), - ...rectPosition(model, 'y'), - ...text$1(model, 'url') - }; - } - }; - - const line = { - vgMark: 'line', - encodeEntry: model => { - return { - ...baseEncodeEntry(model, { - align: 'ignore', - baseline: 'ignore', - color: 'include', - size: 'ignore', - orient: 'ignore', - theta: 'ignore' - }), - ...pointPosition('x', model, { - defaultPos: 'mid' - }), - ...pointPosition('y', model, { - defaultPos: 'mid' - }), - ...nonPosition('size', model, { - vgChannel: 'strokeWidth' // VL's line size is strokeWidth - }), - ...defined(model) - }; - } - }; - const trail = { - vgMark: 'trail', - encodeEntry: model => { - return { - ...baseEncodeEntry(model, { - align: 'ignore', - baseline: 'ignore', - color: 'include', - size: 'include', - orient: 'ignore', - theta: 'ignore' - }), - ...pointPosition('x', model, { - defaultPos: 'mid' - }), - ...pointPosition('y', model, { - defaultPos: 'mid' - }), - ...nonPosition('size', model), - ...defined(model) - }; - } - }; - - function encodeEntry(model, fixedShape) { - const { - config - } = model; - return { - ...baseEncodeEntry(model, { - align: 'ignore', - baseline: 'ignore', - color: 'include', - size: 'include', - orient: 'ignore', - theta: 'ignore' - }), - ...pointPosition('x', model, { - defaultPos: 'mid' - }), - ...pointPosition('y', model, { - defaultPos: 'mid' - }), - ...nonPosition('size', model), - ...nonPosition('angle', model), - ...shapeMixins(model, config, fixedShape) - }; - } - function shapeMixins(model, config, fixedShape) { - if (fixedShape) { - return { - shape: { - value: fixedShape - } - }; - } - return nonPosition('shape', model); - } - const point = { - vgMark: 'symbol', - encodeEntry: model => { - return encodeEntry(model); - } - }; - const circle = { - vgMark: 'symbol', - encodeEntry: model => { - return encodeEntry(model, 'circle'); - } - }; - const square = { - vgMark: 'symbol', - encodeEntry: model => { - return encodeEntry(model, 'square'); - } - }; - - const rect = { - vgMark: 'rect', - encodeEntry: model => { - return { - ...baseEncodeEntry(model, { - align: 'ignore', - baseline: 'ignore', - color: 'include', - orient: 'ignore', - size: 'ignore', - theta: 'ignore' - }), - ...rectPosition(model, 'x'), - ...rectPosition(model, 'y') - }; - } - }; - - const rule = { - vgMark: 'rule', - encodeEntry: model => { - const { - markDef - } = model; - const orient = markDef.orient; - if (!model.encoding.x && !model.encoding.y && !model.encoding.latitude && !model.encoding.longitude) { - // Show nothing if we have none of x, y, lat, and long. - return {}; - } - return { - ...baseEncodeEntry(model, { - align: 'ignore', - baseline: 'ignore', - color: 'include', - orient: 'ignore', - size: 'ignore', - theta: 'ignore' - }), - ...pointOrRangePosition('x', model, { - defaultPos: orient === 'horizontal' ? 'zeroOrMax' : 'mid', - defaultPos2: 'zeroOrMin', - range: orient !== 'vertical' // include x2 for horizontal or line segment rule - }), - ...pointOrRangePosition('y', model, { - defaultPos: orient === 'vertical' ? 'zeroOrMax' : 'mid', - defaultPos2: 'zeroOrMin', - range: orient !== 'horizontal' // include y2 for vertical or line segment rule - }), - ...nonPosition('size', model, { - vgChannel: 'strokeWidth' // VL's rule size is strokeWidth - }) - }; - } - }; - - const text = { - vgMark: 'text', - encodeEntry: model => { - const { - config, - encoding - } = model; - return { - ...baseEncodeEntry(model, { - align: 'include', - baseline: 'include', - color: 'include', - size: 'ignore', - orient: 'ignore', - theta: 'include' - }), - ...pointPosition('x', model, { - defaultPos: 'mid' - }), - ...pointPosition('y', model, { - defaultPos: 'mid' - }), - ...text$1(model), - ...nonPosition('size', model, { - vgChannel: 'fontSize' // VL's text size is fontSize - }), - ...nonPosition('angle', model), - ...valueIfDefined('align', align(model.markDef, encoding, config)), - ...valueIfDefined('baseline', baseline(model.markDef, encoding, config)), - ...pointPosition('radius', model, { - defaultPos: null - }), - ...pointPosition('theta', model, { - defaultPos: null - }) - }; - } - }; - function align(markDef, encoding, config) { - const a = getMarkPropOrConfig('align', markDef, config); - if (a === undefined) { - return 'center'; - } - // If there is a config, Vega-parser will process this already. - return undefined; - } - function baseline(markDef, encoding, config) { - const b = getMarkPropOrConfig('baseline', markDef, config); - if (b === undefined) { - return 'middle'; - } - // If there is a config, Vega-parser will process this already. - return undefined; - } - - const tick = { - vgMark: 'rect', - encodeEntry: model => { - const { - config, - markDef - } = model; - const orient = markDef.orient; - const vgSizeChannel = orient === 'horizontal' ? 'width' : 'height'; - const vgThicknessChannel = orient === 'horizontal' ? 'height' : 'width'; - return { - ...baseEncodeEntry(model, { - align: 'ignore', - baseline: 'ignore', - color: 'include', - orient: 'ignore', - size: 'ignore', - theta: 'ignore' - }), - ...pointPosition('x', model, { - defaultPos: 'mid', - vgChannel: 'xc' - }), - ...pointPosition('y', model, { - defaultPos: 'mid', - vgChannel: 'yc' - }), - // size / thickness => width / height - ...nonPosition('size', model, { - defaultRef: defaultSize(model), - vgChannel: vgSizeChannel - }), - [vgThicknessChannel]: signalOrValueRef(getMarkPropOrConfig('thickness', markDef, config)) - }; - } - }; - function defaultSize(model) { - const { - config, - markDef - } = model; - const { - orient - } = markDef; - const vgSizeChannel = orient === 'horizontal' ? 'width' : 'height'; - const positionChannel = orient === 'horizontal' ? 'x' : 'y'; - const offsetScaleChannel = getOffsetScaleChannel(positionChannel); - - // Use offset scale if exists - const scale = model.getScaleComponent(offsetScaleChannel) || model.getScaleComponent(positionChannel); - const scaleName = model.scaleName(offsetScaleChannel) || model.scaleName(positionChannel); - const markPropOrConfig = getMarkPropOrConfig('size', markDef, config, { - vgChannel: vgSizeChannel - }) ?? config.tick.bandSize; - if (markPropOrConfig !== undefined) { - return signalOrValueRef(markPropOrConfig); - } else if (scale?.get('type') === 'band') { - return { - scale: scaleName, - band: 1 - }; - } - const scaleRange = scale?.get('range'); - const { - tickBandPaddingInner - } = config.scale; - const step = scaleRange && isVgRangeStep(scaleRange) ? scaleRange.step : getViewConfigDiscreteStep(config.view, vgSizeChannel); - if (vega.isNumber(step) && vega.isNumber(tickBandPaddingInner)) { - return { - value: step * (1 - tickBandPaddingInner) - }; - } else { - return { - signal: `${exprFromSignalRefOrValue(tickBandPaddingInner)} * ${exprFromSignalRefOrValue(step)}` - }; - } - } - - const markCompiler = { - arc, - area, - bar, - circle, - geoshape, - image, - line, - point, - rect, - rule, - square, - text, - tick, - trail - }; - function parseMarkGroups(model) { - if (contains([LINE, AREA, TRAIL], model.mark)) { - const details = pathGroupingFields(model.mark, model.encoding); - if (details.length > 0) { - return getPathGroups(model, details); - } - // otherwise use standard mark groups - } else if (model.mark === BAR) { - const hasCornerRadius = VG_CORNERRADIUS_CHANNELS.some(prop => getMarkPropOrConfig(prop, model.markDef, model.config)); - if (model.stack && !model.fieldDef('size') && hasCornerRadius) { - return getGroupsForStackedBarWithCornerRadius(model); - } - } - return getMarkGroup(model); - } - const FACETED_PATH_PREFIX = 'faceted_path_'; - function getPathGroups(model, details) { - // TODO: for non-stacked plot, map order to zindex. (Maybe rename order for layer to zindex?) - - return [{ - name: model.getName('pathgroup'), - type: 'group', - from: { - facet: { - name: FACETED_PATH_PREFIX + model.requestDataName(DataSourceType.Main), - data: model.requestDataName(DataSourceType.Main), - groupby: details - } - }, - encode: { - update: { - width: { - field: { - group: 'width' - } - }, - height: { - field: { - group: 'height' - } - } - } - }, - // With subfacet for line/area group, need to use faceted data from above. - marks: getMarkGroup(model, { - fromPrefix: FACETED_PATH_PREFIX - }) - }]; - } - const STACK_GROUP_PREFIX = 'stack_group_'; - - /** - * We need to put stacked bars into groups in order to enable cornerRadius for stacks. - * If stack is used and the model doesn't have size encoding, we put the mark into groups, - * and apply cornerRadius properties at the group. - */ - function getGroupsForStackedBarWithCornerRadius(model) { - // Generate the mark - const [mark] = getMarkGroup(model, { - fromPrefix: STACK_GROUP_PREFIX - }); - - // Get the scale for the stacked field - const fieldScale = model.scaleName(model.stack.fieldChannel); - const stackField = function () { - let opt = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - return model.vgField(model.stack.fieldChannel, opt); - }; - // Find the min/max of the pixel value on the stacked direction - const stackFieldGroup = (func, expr) => { - const vgFieldMinMax = [stackField({ - prefix: 'min', - suffix: 'start', - expr - }), stackField({ - prefix: 'max', - suffix: 'start', - expr - }), stackField({ - prefix: 'min', - suffix: 'end', - expr - }), stackField({ - prefix: 'max', - suffix: 'end', - expr - })]; - return `${func}(${vgFieldMinMax.map(field => `scale('${fieldScale}',${field})`).join(',')})`; - }; - let groupUpdate; - let innerGroupUpdate; - - // Build the encoding for group and an inner group - if (model.stack.fieldChannel === 'x') { - // Move cornerRadius, y/yc/y2/height properties to group - // Group x/x2 should be the min/max of the marks within - groupUpdate = { - ...pick(mark.encode.update, ['y', 'yc', 'y2', 'height', ...VG_CORNERRADIUS_CHANNELS]), - x: { - signal: stackFieldGroup('min', 'datum') - }, - x2: { - signal: stackFieldGroup('max', 'datum') - }, - clip: { - value: true - } - }; - // Inner group should revert the x translation, and pass height through - innerGroupUpdate = { - x: { - field: { - group: 'x' - }, - mult: -1 - }, - height: { - field: { - group: 'height' - } - } - }; - // The marks should use the same height as group, without y/yc/y2 properties (because it's already done by group) - // This is why size encoding is not supported yet - mark.encode.update = { - ...omit(mark.encode.update, ['y', 'yc', 'y2']), - height: { - field: { - group: 'height' - } - } - }; - } else { - groupUpdate = { - ...pick(mark.encode.update, ['x', 'xc', 'x2', 'width']), - y: { - signal: stackFieldGroup('min', 'datum') - }, - y2: { - signal: stackFieldGroup('max', 'datum') - }, - clip: { - value: true - } - }; - innerGroupUpdate = { - y: { - field: { - group: 'y' - }, - mult: -1 - }, - width: { - field: { - group: 'width' - } - } - }; - mark.encode.update = { - ...omit(mark.encode.update, ['x', 'xc', 'x2']), - width: { - field: { - group: 'width' - } - } - }; - } - - // Deal with cornerRadius properties - for (const key of VG_CORNERRADIUS_CHANNELS) { - const configValue = getMarkConfig(key, model.markDef, model.config); - // Move from mark to group - if (mark.encode.update[key]) { - groupUpdate[key] = mark.encode.update[key]; - delete mark.encode.update[key]; - } else if (configValue) { - groupUpdate[key] = signalOrValueRef(configValue); - } - // Overwrite any cornerRadius on mark set by config --- they are already moved to the group - if (configValue) { - mark.encode.update[key] = { - value: 0 - }; - } - } - const groupby = []; - if (model.stack.groupbyChannels?.length > 0) { - for (const groupbyChannel of model.stack.groupbyChannels) { - // For bin and time unit, we have to add bin/timeunit -end channels. - const groupByField = model.fieldDef(groupbyChannel); - const field = vgField(groupByField); - if (field) { - groupby.push(field); - } - if (groupByField?.bin || groupByField?.timeUnit) { - groupby.push(vgField(groupByField, { - binSuffix: 'end' - })); - } - } - } - const strokeProperties = ['stroke', 'strokeWidth', 'strokeJoin', 'strokeCap', 'strokeDash', 'strokeDashOffset', 'strokeMiterLimit', 'strokeOpacity']; - - // Generate stroke properties for the group - groupUpdate = strokeProperties.reduce((encode, prop) => { - if (mark.encode.update[prop]) { - return { - ...encode, - [prop]: mark.encode.update[prop] - }; - } else { - const configValue = getMarkConfig(prop, model.markDef, model.config); - if (configValue !== undefined) { - return { - ...encode, - [prop]: signalOrValueRef(configValue) - }; - } else { - return encode; - } - } - }, groupUpdate); - - // Apply strokeForeground and strokeOffset if stroke is used - if (groupUpdate.stroke) { - groupUpdate.strokeForeground = { - value: true - }; - groupUpdate.strokeOffset = { - value: 0 - }; - } - return [{ - type: 'group', - from: { - facet: { - data: model.requestDataName(DataSourceType.Main), - name: STACK_GROUP_PREFIX + model.requestDataName(DataSourceType.Main), - groupby, - aggregate: { - fields: [stackField({ - suffix: 'start' - }), stackField({ - suffix: 'start' - }), stackField({ - suffix: 'end' - }), stackField({ - suffix: 'end' - })], - ops: ['min', 'max', 'min', 'max'] - } - } - }, - encode: { - update: groupUpdate - }, - marks: [{ - type: 'group', - encode: { - update: innerGroupUpdate - }, - marks: [mark] - }] - }]; - } - function getSort(model) { - const { - encoding, - stack, - mark, - markDef, - config - } = model; - const order = encoding.order; - if (!vega.isArray(order) && isValueDef(order) && isNullOrFalse(order.value) || !order && isNullOrFalse(getMarkPropOrConfig('order', markDef, config))) { - return undefined; - } else if ((vega.isArray(order) || isFieldDef(order)) && !stack) { - // Sort by the order field if it is specified and the field is not stacked. (For stacked field, order specify stack order.) - return sortParams(order, { - expr: 'datum' - }); - } else if (isPathMark(mark)) { - // For both line and area, we sort values based on dimension by default - const dimensionChannel = markDef.orient === 'horizontal' ? 'y' : 'x'; - const dimensionChannelDef = encoding[dimensionChannel]; - if (isFieldDef(dimensionChannelDef)) { - return { - field: dimensionChannel - }; - } - } - return undefined; - } - function getMarkGroup(model) { - let opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { - fromPrefix: '' - }; - const { - mark, - markDef, - encoding, - config - } = model; - const clip = getFirstDefined(markDef.clip, scaleClip(model), projectionClip(model)); - const style = getStyles(markDef); - const key = encoding.key; - const sort = getSort(model); - const interactive = interactiveFlag(model); - const aria = getMarkPropOrConfig('aria', markDef, config); - const postEncodingTransform = markCompiler[mark].postEncodingTransform ? markCompiler[mark].postEncodingTransform(model) : null; - return [{ - name: model.getName('marks'), - type: markCompiler[mark].vgMark, - ...(clip ? { - clip - } : {}), - ...(style ? { - style - } : {}), - ...(key ? { - key: key.field - } : {}), - ...(sort ? { - sort - } : {}), - ...(interactive ? interactive : {}), - ...(aria === false ? { - aria - } : {}), - from: { - data: opt.fromPrefix + model.requestDataName(DataSourceType.Main) - }, - encode: { - update: markCompiler[mark].encodeEntry(model) - }, - ...(postEncodingTransform ? { - transform: postEncodingTransform - } : {}) - }]; - } - - /** - * If scales are bound to interval selections, we want to automatically clip - * marks to account for panning/zooming interactions. We identify bound scales - * by the selectionExtent property, which gets added during scale parsing. - */ - function scaleClip(model) { - const xScale = model.getScaleComponent('x'); - const yScale = model.getScaleComponent('y'); - return xScale?.get('selectionExtent') || yScale?.get('selectionExtent') ? true : undefined; - } - - /** - * If we use a custom projection with auto-fitting to the geodata extent, - * we need to clip to ensure the chart size doesn't explode. - */ - function projectionClip(model) { - const projection = model.component.projection; - return projection && !projection.isFit ? true : undefined; - } - - /** - * Only output interactive flags if we have selections defined somewhere in our model hierarchy. - */ - function interactiveFlag(model) { - if (!model.component.selection) return null; - const unitCount = keys(model.component.selection).length; - let parentCount = unitCount; - let parent = model.parent; - while (parent && parentCount === 0) { - parentCount = keys(parent.component.selection).length; - parent = parent.parent; - } - return parentCount ? { - interactive: unitCount > 0 || model.mark === 'geoshape' || !!model.encoding.tooltip || !!model.markDef.tooltip - } : null; - } - - /** - * Internal model of Vega-Lite specification for the compiler. - */ - class UnitModel extends ModelWithField { - specifiedScales = {}; - specifiedAxes = {}; - specifiedLegends = {}; - specifiedProjection = {}; - selection = []; - children = []; - constructor(spec, parent, parentGivenName) { - let parentGivenSize = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; - let config = arguments.length > 4 ? arguments[4] : undefined; - super(spec, 'unit', parent, parentGivenName, config, undefined, isFrameMixins(spec) ? spec.view : undefined); - const markDef = isMarkDef(spec.mark) ? { - ...spec.mark - } : { - type: spec.mark - }; - const mark = markDef.type; - - // Need to init filled before other mark properties because encoding depends on filled but other mark properties depend on types inside encoding - if (markDef.filled === undefined) { - markDef.filled = defaultFilled(markDef, config, { - graticule: spec.data && isGraticuleGenerator(spec.data) - }); - } - const encoding = this.encoding = initEncoding(spec.encoding || {}, mark, markDef.filled, config); - this.markDef = initMarkdef(markDef, encoding, config); - this.size = initLayoutSize({ - encoding, - size: isFrameMixins(spec) ? { - ...parentGivenSize, - ...(spec.width ? { - width: spec.width - } : {}), - ...(spec.height ? { - height: spec.height - } : {}) - } : parentGivenSize - }); - - // calculate stack properties - this.stack = stack(this.markDef, encoding); - this.specifiedScales = this.initScales(mark, encoding); - this.specifiedAxes = this.initAxes(encoding); - this.specifiedLegends = this.initLegends(encoding); - this.specifiedProjection = spec.projection; - - // Selections will be initialized upon parse. - this.selection = (spec.params ?? []).filter(p => isSelectionParameter(p)); - } - get hasProjection() { - const { - encoding - } = this; - const isGeoShapeMark = this.mark === GEOSHAPE; - const hasGeoPosition = encoding && GEOPOSITION_CHANNELS.some(channel => isFieldOrDatumDef(encoding[channel])); - return isGeoShapeMark || hasGeoPosition; - } - - /** - * Return specified Vega-Lite scale domain for a particular channel - * @param channel - */ - scaleDomain(channel) { - const scale = this.specifiedScales[channel]; - return scale ? scale.domain : undefined; - } - axis(channel) { - return this.specifiedAxes[channel]; - } - legend(channel) { - return this.specifiedLegends[channel]; - } - initScales(mark, encoding) { - return SCALE_CHANNELS.reduce((scales, channel) => { - const fieldOrDatumDef = getFieldOrDatumDef(encoding[channel]); - if (fieldOrDatumDef) { - scales[channel] = this.initScale(fieldOrDatumDef.scale ?? {}); - } - return scales; - }, {}); - } - initScale(scale) { - const { - domain, - range - } = scale; - // TODO: we could simplify this function if we had a recursive replace function - const scaleInternal = replaceExprRef(scale); - if (vega.isArray(domain)) { - scaleInternal.domain = domain.map(signalRefOrValue); - } - if (vega.isArray(range)) { - scaleInternal.range = range.map(signalRefOrValue); - } - return scaleInternal; - } - initAxes(encoding) { - return POSITION_SCALE_CHANNELS.reduce((_axis, channel) => { - // Position Axis - - // TODO: handle ConditionFieldDef - const channelDef = encoding[channel]; - if (isFieldOrDatumDef(channelDef) || channel === X && isFieldOrDatumDef(encoding.x2) || channel === Y && isFieldOrDatumDef(encoding.y2)) { - const axisSpec = isFieldOrDatumDef(channelDef) ? channelDef.axis : undefined; - _axis[channel] = axisSpec ? this.initAxis({ - ...axisSpec - }) // convert truthy value to object - : axisSpec; - } - return _axis; - }, {}); - } - initAxis(axis) { - const props = keys(axis); - const axisInternal = {}; - for (const prop of props) { - const val = axis[prop]; - axisInternal[prop] = isConditionalAxisValue(val) ? signalOrValueRefWithCondition(val) : signalRefOrValue(val); - } - return axisInternal; - } - initLegends(encoding) { - return NONPOSITION_SCALE_CHANNELS.reduce((_legend, channel) => { - const fieldOrDatumDef = getFieldOrDatumDef(encoding[channel]); - if (fieldOrDatumDef && supportLegend(channel)) { - const legend = fieldOrDatumDef.legend; - _legend[channel] = legend ? replaceExprRef(legend) // convert truthy value to object - : legend; - } - return _legend; - }, {}); - } - parseData() { - this.component.data = parseData(this); - } - parseLayoutSize() { - parseUnitLayoutSize(this); - } - parseSelections() { - this.component.selection = parseUnitSelection(this, this.selection); - } - parseMarkGroup() { - this.component.mark = parseMarkGroups(this); - } - parseAxesAndHeaders() { - this.component.axes = parseUnitAxes(this); - } - assembleSelectionTopLevelSignals(signals) { - return assembleTopLevelSignals(this, signals); - } - assembleSignals() { - return [...assembleAxisSignals(this), ...assembleUnitSelectionSignals(this, [])]; - } - assembleSelectionData(data) { - return assembleUnitSelectionData(this, data); - } - assembleLayout() { - return null; - } - assembleLayoutSignals() { - return assembleLayoutSignals(this); - } - assembleMarks() { - let marks = this.component.mark ?? []; - - // If this unit is part of a layer, selections should augment - // all in concert rather than each unit individually. This - // ensures correct interleaving of clipping and brushed marks. - if (!this.parent || !isLayerModel(this.parent)) { - marks = assembleUnitSelectionMarks(this, marks); - } - return marks.map(this.correctDataNames); - } - assembleGroupStyle() { - const { - style - } = this.view || {}; - if (style !== undefined) { - return style; - } - if (this.encoding.x || this.encoding.y) { - return 'cell'; - } else { - return 'view'; - } - } - getMapping() { - return this.encoding; - } - get mark() { - return this.markDef.type; - } - channelHasField(channel) { - return channelHasField(this.encoding, channel); - } - fieldDef(channel) { - const channelDef = this.encoding[channel]; - return getFieldDef(channelDef); - } - typedFieldDef(channel) { - const fieldDef = this.fieldDef(channel); - if (isTypedFieldDef(fieldDef)) { - return fieldDef; - } - return null; - } - } - - class LayerModel extends Model { - // HACK: This should be (LayerModel | UnitModel)[], but setting the correct type leads to weird error. - // So I'm just putting generic Model for now - - constructor(spec, parent, parentGivenName, parentGivenSize, config) { - super(spec, 'layer', parent, parentGivenName, config, spec.resolve, spec.view); - const layoutSize = { - ...parentGivenSize, - ...(spec.width ? { - width: spec.width - } : {}), - ...(spec.height ? { - height: spec.height - } : {}) - }; - this.children = spec.layer.map((layer, i) => { - if (isLayerSpec(layer)) { - return new LayerModel(layer, this, this.getName(`layer_${i}`), layoutSize, config); - } else if (isUnitSpec(layer)) { - return new UnitModel(layer, this, this.getName(`layer_${i}`), layoutSize, config); - } - throw new Error(invalidSpec(layer)); - }); - } - parseData() { - this.component.data = parseData(this); - for (const child of this.children) { - child.parseData(); - } - } - parseLayoutSize() { - parseLayerLayoutSize(this); - } - parseSelections() { - // Merge selections up the hierarchy so that they may be referenced - // across unit specs. Persist their definitions within each child - // to assemble signals which remain within output Vega unit groups. - this.component.selection = {}; - for (const child of this.children) { - child.parseSelections(); - for (const key of keys(child.component.selection)) { - this.component.selection[key] = child.component.selection[key]; - } - } - } - parseMarkGroup() { - for (const child of this.children) { - child.parseMarkGroup(); - } - } - parseAxesAndHeaders() { - parseLayerAxes(this); - } - assembleSelectionTopLevelSignals(signals) { - return this.children.reduce((sg, child) => child.assembleSelectionTopLevelSignals(sg), signals); - } - - // TODO: Support same named selections across children. - assembleSignals() { - return this.children.reduce((signals, child) => { - return signals.concat(child.assembleSignals()); - }, assembleAxisSignals(this)); - } - assembleLayoutSignals() { - return this.children.reduce((signals, child) => { - return signals.concat(child.assembleLayoutSignals()); - }, assembleLayoutSignals(this)); - } - assembleSelectionData(data) { - return this.children.reduce((db, child) => child.assembleSelectionData(db), data); - } - assembleGroupStyle() { - const uniqueStyles = new Set(); - for (const child of this.children) { - for (const style of vega.array(child.assembleGroupStyle())) { - uniqueStyles.add(style); - } - } - const styles = Array.from(uniqueStyles); - return styles.length > 1 ? styles : styles.length === 1 ? styles[0] : undefined; - } - assembleTitle() { - let title = super.assembleTitle(); - if (title) { - return title; - } - // If title does not provide layer, look into children - for (const child of this.children) { - title = child.assembleTitle(); - if (title) { - return title; - } - } - return undefined; - } - assembleLayout() { - return null; - } - assembleMarks() { - return assembleLayerSelectionMarks(this, this.children.flatMap(child => { - return child.assembleMarks(); - })); - } - assembleLegends() { - return this.children.reduce((legends, child) => { - return legends.concat(child.assembleLegends()); - }, assembleLegends(this)); - } - } - - function buildModel(spec, parent, parentGivenName, unitSize, config) { - if (isFacetSpec(spec)) { - return new FacetModel(spec, parent, parentGivenName, config); - } else if (isLayerSpec(spec)) { - return new LayerModel(spec, parent, parentGivenName, unitSize, config); - } else if (isUnitSpec(spec)) { - return new UnitModel(spec, parent, parentGivenName, unitSize, config); - } else if (isAnyConcatSpec(spec)) { - return new ConcatModel(spec, parent, parentGivenName, config); - } - throw new Error(invalidSpec(spec)); - } - - /** - * Vega-Lite's main function, for compiling Vega-Lite spec into Vega spec. - * - * At a high-level, we make the following transformations in different phases: - * - * Input spec - * | - * | (Normalization) - * v - * Normalized Spec (Row/Column channels in single-view specs becomes faceted specs, composite marks becomes layered specs.) - * | - * | (Build Model) - * v - * A model tree of the spec - * | - * | (Parse) - * v - * A model tree with parsed components (intermediate structure of visualization primitives in a format that can be easily merged) - * | - * | (Optimize) - * v - * A model tree with parsed components with the data component optimized - * | - * | (Assemble) - * v - * Vega spec - * - * @param inputSpec The Vega-Lite specification. - * @param opt Optional arguments passed to the Vega-Lite compiler. - * @returns An object containing the compiled Vega spec and normalized Vega-Lite spec. - */ - function compile(inputSpec) { - let opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - // 0. Augment opt with default opts - if (opt.logger) { - // set the singleton logger to the provided logger - set(opt.logger); - } - if (opt.fieldTitle) { - // set the singleton field title formatter - setTitleFormatter(opt.fieldTitle); - } - try { - // 1. Initialize config by deep merging default config with the config provided via option and the input spec. - const config = initConfig(vega.mergeConfig(opt.config, inputSpec.config)); - - // 2. Normalize: Convert input spec -> normalized spec - - // - 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. - // - Normalize autosize and width or height spec - const spec = normalize(inputSpec, config); - - // 3. Build Model: normalized spec -> Model (a tree structure) - - // 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. - // See the abstract `Model` class and its children (UnitModel, LayerModel, FacetModel, ConcatModel) for different types of models. - const model = buildModel(spec, null, '', undefined, config); - - // 4 Parse: Model --> Model with components - - // Note that components = intermediate representations that are equivalent to Vega specs. - // We need these intermediate representation because we need to merge many visualization "components" like projections, scales, axes, and legends. - // We will later convert these components into actual Vega specs in the assemble phase. - - // In this phase, we do a bottom-up traversal over the whole tree to - // parse for each type of components once (e.g., data, layout, mark, scale). - // By doing bottom-up traversal, we start parsing components of unit specs and - // then merge child components of parent composite specs. - // - // Please see inside model.parse() for order of different components parsed. - model.parse(); - - // drawDataflow(model.component.data.sources); - - // 5. Optimize the dataflow. This will modify the data component of the model. - optimizeDataflow(model.component.data, model); - - // drawDataflow(model.component.data.sources); - - // 6. Assemble: convert model components --> Vega Spec. - const vgSpec = assembleTopLevelModel(model, getTopLevelProperties(inputSpec, spec.autosize, config, model), inputSpec.datasets, inputSpec.usermeta); - return { - spec: vgSpec, - normalized: spec - }; - } finally { - // Reset the singleton logger if a logger is provided - if (opt.logger) { - reset(); - } - // Reset the singleton field title formatter if provided - if (opt.fieldTitle) { - resetTitleFormatter(); - } - } - } - function getTopLevelProperties(inputSpec, autosize, config, model) { - const width = model.component.layoutSize.get('width'); - const height = model.component.layoutSize.get('height'); - if (autosize === undefined) { - autosize = { - type: 'pad' - }; - if (model.hasAxisOrientSignalRef()) { - autosize.resize = true; - } - } else if (vega.isString(autosize)) { - autosize = { - type: autosize - }; - } - if (width && height && isFitType(autosize.type)) { - if (width === 'step' && height === 'step') { - warn(droppingFit()); - autosize.type = 'pad'; - } else if (width === 'step' || height === 'step') { - // effectively XOR, because else if - - // get step dimension - const sizeType = width === 'step' ? 'width' : 'height'; - // log that we're dropping fit for respective channel - warn(droppingFit(getPositionScaleChannel(sizeType))); - - // setting type to inverse fit (so if we dropped fit-x, type is now fit-y) - const inverseSizeType = sizeType === 'width' ? 'height' : 'width'; - autosize.type = getFitType(inverseSizeType); - } - } - return { - ...(keys(autosize).length === 1 && autosize.type ? autosize.type === 'pad' ? {} : { - autosize: autosize.type - } : { - autosize - }), - ...extractTopLevelProperties(config, false), - ...extractTopLevelProperties(inputSpec, true) - }; - } - - /* - * Assemble the top-level model to a Vega spec. - * - * Note: this couldn't be `model.assemble()` since the top-level model - * needs some special treatment to generate top-level properties. - */ - function assembleTopLevelModel(model, topLevelProperties) { - let datasets = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - let usermeta = arguments.length > 3 ? arguments[3] : undefined; - // Config with Vega-Lite only config removed. - const vgConfig = model.config ? stripAndRedirectConfig(model.config) : undefined; - const data = [].concat(model.assembleSelectionData([]), - // only assemble data in the root - assembleRootData(model.component.data, datasets)); - const projections = model.assembleProjections(); - const title = model.assembleTitle(); - const style = model.assembleGroupStyle(); - const encodeEntry = model.assembleGroupEncodeEntry(true); - let layoutSignals = model.assembleLayoutSignals(); - - // move width and height signals with values to top level - layoutSignals = layoutSignals.filter(signal => { - if ((signal.name === 'width' || signal.name === 'height') && signal.value !== undefined) { - topLevelProperties[signal.name] = +signal.value; - return false; - } - return true; - }); - const { - params, - ...otherTopLevelProps - } = topLevelProperties; - return { - $schema: 'https://vega.github.io/schema/vega/v5.json', - ...(model.description ? { - description: model.description - } : {}), - ...otherTopLevelProps, - ...(title ? { - title - } : {}), - ...(style ? { - style - } : {}), - ...(encodeEntry ? { - encode: { - update: encodeEntry - } - } : {}), - data, - ...(projections.length > 0 ? { - projections - } : {}), - ...model.assembleGroup([...layoutSignals, ...model.assembleSelectionTopLevelSignals([]), ...assembleParameterSignals(params)]), - ...(vgConfig ? { - config: vgConfig - } : {}), - ...(usermeta ? { - usermeta - } : {}) - }; - } - - const version = pkg.version; - - exports.accessPathDepth = accessPathDepth; - exports.accessPathWithDatum = accessPathWithDatum; - exports.compile = compile; - exports.contains = contains; - exports.deepEqual = deepEqual; - exports.deleteNestedProperty = deleteNestedProperty; - exports.duplicate = duplicate; - exports.entries = entries$1; - exports.every = every; - exports.fieldIntersection = fieldIntersection; - exports.flatAccessWithDatum = flatAccessWithDatum; - exports.getFirstDefined = getFirstDefined; - exports.hasIntersection = hasIntersection; - exports.hash = hash; - exports.internalField = internalField; - exports.isBoolean = isBoolean; - exports.isEmpty = isEmpty; - exports.isEqual = isEqual; - exports.isInternalField = isInternalField; - exports.isNullOrFalse = isNullOrFalse; - exports.isNumeric = isNumeric; - exports.keys = keys; - exports.logicalExpr = logicalExpr; - exports.mergeDeep = mergeDeep; - exports.never = never; - exports.normalize = normalize; - exports.normalizeAngle = normalizeAngle; - exports.omit = omit; - exports.pick = pick; - exports.prefixGenerator = prefixGenerator; - exports.removePathFromField = removePathFromField; - exports.replaceAll = replaceAll; - exports.replacePathInField = replacePathInField; - exports.resetIdCounter = resetIdCounter; - exports.setEqual = setEqual; - exports.some = some; - exports.stringify = stringify; - exports.titleCase = titleCase; - exports.unique = unique; - exports.uniqueId = uniqueId; - exports.vals = vals; - exports.varName = varName; - exports.version = version; - -})); -//# sourceMappingURL=vega-lite.js.map From 163647263ccbdbc253ba4461fbbcacaba68efd02 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Fri, 2 Aug 2024 01:27:18 +0000 Subject: [PATCH 23/24] chore: update schema [CI] --- build/vega-lite-schema.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/vega-lite-schema.json b/build/vega-lite-schema.json index ca2e5d045d..446bb1cd95 100644 --- a/build/vega-lite-schema.json +++ b/build/vega-lite-schema.json @@ -4752,7 +4752,8 @@ "set2", "set3", "tableau10", - "tableau20" + "tableau20", + "observable10" ], "type": "string" }, From 7d5888f2a5bb34d5274c6e231152f69030f03a52 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Fri, 2 Aug 2024 01:33:02 +0000 Subject: [PATCH 24/24] chore: update examples [CI] --- examples/compiled/area_cumulative_freq.svg | 2 +- examples/compiled/area_invalid_null.svg | 2 +- examples/compiled/bar_invalid_color_show.svg | 2 +- examples/compiled/bar_invalid_color_show_override.svg | 2 +- examples/compiled/line_invalid_null.svg | 2 +- examples/compiled/point_invalid_color.svg | 2 +- examples/compiled/point_invalid_size_show.svg | 2 +- examples/compiled/test_invalid_break_paths_filter_domains.svg | 2 +- examples/compiled/test_invalid_break_paths_show_domains.svg | 2 +- .../compiled/test_invalid_break_paths_show_path_domains.svg | 2 +- .../test_invalid_color_filter_but_include_in_scale_invalid.svg | 2 +- examples/compiled/test_invalid_color_show.svg | 2 +- examples/compiled/test_invalid_color_size_config_scale.svg | 2 +- examples/compiled/test_invalid_color_size_mark_show_only.svg | 2 +- examples/compiled/test_invalid_default.svg | 2 +- examples/compiled/test_invalid_null.svg | 2 +- ...test_invalid_opacity_filter_but_include_in_scale_invalid.svg | 2 +- examples/compiled/test_invalid_opacity_show.svg | 2 +- examples/compiled/test_invalid_show.svg | 2 +- examples/compiled/vega_version | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/examples/compiled/area_cumulative_freq.svg b/examples/compiled/area_cumulative_freq.svg index 5c526a87b2..0b4b253af4 100644 --- a/examples/compiled/area_cumulative_freq.svg +++ b/examples/compiled/area_cumulative_freq.svg @@ -1 +1 @@ -246810IMDB Rating05001,0001,5002,0002,5003,0003,500Cumulative Count \ No newline at end of file +246810IMDB Rating05001,0001,5002,0002,5003,0003,500Cumulative Count \ No newline at end of file diff --git a/examples/compiled/area_invalid_null.svg b/examples/compiled/area_invalid_null.svg index 558fd24cd9..80e45e7a01 100644 --- a/examples/compiled/area_invalid_null.svg +++ b/examples/compiled/area_invalid_null.svg @@ -1 +1 @@ -0246810x010203040y \ No newline at end of file +0246810x010203040y \ No newline at end of file diff --git a/examples/compiled/bar_invalid_color_show.svg b/examples/compiled/bar_invalid_color_show.svg index 7034b097ee..4120b21fc6 100644 --- a/examples/compiled/bar_invalid_color_show.svg +++ b/examples/compiled/bar_invalid_color_show.svg @@ -1 +1 @@ -1.01.52.02.53.0a020406080100b1.01.21.41.61.82.0c \ No newline at end of file +1.01.52.02.53.0a020406080100b1.01.21.41.61.82.0c \ No newline at end of file diff --git a/examples/compiled/bar_invalid_color_show_override.svg b/examples/compiled/bar_invalid_color_show_override.svg index 862b351951..f1b0eacc53 100644 --- a/examples/compiled/bar_invalid_color_show_override.svg +++ b/examples/compiled/bar_invalid_color_show_override.svg @@ -1 +1 @@ -1.01.52.02.53.0a020406080100b1.01.21.41.61.82.0c \ No newline at end of file +1.01.52.02.53.0a020406080100b1.01.21.41.61.82.0c \ No newline at end of file diff --git a/examples/compiled/line_invalid_null.svg b/examples/compiled/line_invalid_null.svg index 8fe8462a54..91d2307543 100644 --- a/examples/compiled/line_invalid_null.svg +++ b/examples/compiled/line_invalid_null.svg @@ -1 +1 @@ -0246810x010203040y \ No newline at end of file +0246810x010203040y \ No newline at end of file diff --git a/examples/compiled/point_invalid_color.svg b/examples/compiled/point_invalid_color.svg index a0151281c8..99b571f873 100644 --- a/examples/compiled/point_invalid_color.svg +++ b/examples/compiled/point_invalid_color.svg @@ -1 +1 @@ -0246810IMDB Rating020406080100Rotten Tomatoes Rating \ No newline at end of file +0246810IMDB Rating020406080100Rotten Tomatoes Rating \ No newline at end of file diff --git a/examples/compiled/point_invalid_size_show.svg b/examples/compiled/point_invalid_size_show.svg index 2ae4ee19d6..b045566a3e 100644 --- a/examples/compiled/point_invalid_size_show.svg +++ b/examples/compiled/point_invalid_size_show.svg @@ -1 +1 @@ -0123a02040bQuantitative X0.00.51.01.52.0c \ No newline at end of file +0123a02040bQuantitative X0.00.51.01.52.0c \ No newline at end of file diff --git a/examples/compiled/test_invalid_break_paths_filter_domains.svg b/examples/compiled/test_invalid_break_paths_filter_domains.svg index c91dd809d5..8af37fd9c4 100644 --- a/examples/compiled/test_invalid_break_paths_filter_domains.svg +++ b/examples/compiled/test_invalid_break_paths_filter_domains.svg @@ -1 +1 @@ -−505a−2002040b−505a−2002040b−505a−2002040b−505a−2002040bQuantitative Xnull-5-115a050100bnull-5-115a050100bnull-5-115a050100bnull-5-115a050100bOrdinal X \ No newline at end of file +−505a−2002040b−505a−2002040b−505a−2002040b−505a−2002040bQuantitative Xnull-5-115a050100bnull-5-115a050100bnull-5-115a050100bnull-5-115a050100bOrdinal X \ No newline at end of file diff --git a/examples/compiled/test_invalid_break_paths_show_domains.svg b/examples/compiled/test_invalid_break_paths_show_domains.svg index 8bd25d26d9..9e26282bfb 100644 --- a/examples/compiled/test_invalid_break_paths_show_domains.svg +++ b/examples/compiled/test_invalid_break_paths_show_domains.svg @@ -1 +1 @@ -−10−50510a050100b−10010a050100b−10−50510a050100b−10−50510a050100bQuantitative Xnull-10-5-101510a050100bnull-10-5-101510a050100bnull-10-5-101510a050100bnull-10-5-101510a050100bOrdinal X \ No newline at end of file +−10−50510a050100b−10010a050100b−10−50510a050100b−10−50510a050100bQuantitative Xnull-10-5-101510a050100bnull-10-5-101510a050100bnull-10-5-101510a050100bnull-10-5-101510a050100bOrdinal X \ No newline at end of file diff --git a/examples/compiled/test_invalid_break_paths_show_path_domains.svg b/examples/compiled/test_invalid_break_paths_show_path_domains.svg index fd3f096e5f..ac6f9c6ab0 100644 --- a/examples/compiled/test_invalid_break_paths_show_path_domains.svg +++ b/examples/compiled/test_invalid_break_paths_show_path_domains.svg @@ -1 +1 @@ -−505a−2002040b−505a−2002040b−10−50510a050100b−10−50510a050100bQuantitative Xnull-5-115a050100bnull-5-115a050100bnull-10-5-101510a050100bnull-10-5-101510a050100bOrdinal X \ No newline at end of file +−505a−2002040b−505a−2002040b−10−50510a050100b−10−50510a050100bQuantitative Xnull-5-115a050100bnull-5-115a050100bnull-10-5-101510a050100bnull-10-5-101510a050100bOrdinal X \ No newline at end of file diff --git a/examples/compiled/test_invalid_color_filter_but_include_in_scale_invalid.svg b/examples/compiled/test_invalid_color_filter_but_include_in_scale_invalid.svg index bb48b40d80..25a209cc7e 100644 --- a/examples/compiled/test_invalid_color_filter_but_include_in_scale_invalid.svg +++ b/examples/compiled/test_invalid_color_filter_but_include_in_scale_invalid.svg @@ -1 +1 @@ -0123a02040b123a050100b1.01.52.02.53.0a02040b1.01.52.02.53.0a050100bQuantitative X123a02040b123a050100b123a02040b123a050100bOrdinal X1.01.21.41.61.82.0c \ No newline at end of file +0123a02040b123a050100b1.01.52.02.53.0a02040b1.01.52.02.53.0a050100bQuantitative X123a02040b123a050100b123a02040b123a050100bOrdinal X1.01.21.41.61.82.0c \ No newline at end of file diff --git a/examples/compiled/test_invalid_color_show.svg b/examples/compiled/test_invalid_color_show.svg index 0da6eb7437..e727c52953 100644 --- a/examples/compiled/test_invalid_color_show.svg +++ b/examples/compiled/test_invalid_color_show.svg @@ -1 +1 @@ -0123a02040b123a050100b1.01.52.02.53.0a02040b1.01.52.02.53.0a050100bQuantitative X123a02040b123a050100b123a02040b123a050100bOrdinal X1.01.21.41.61.82.0c \ No newline at end of file +0123a02040b123a050100b1.01.52.02.53.0a02040b1.01.52.02.53.0a050100bQuantitative X123a02040b123a050100b123a02040b123a050100bOrdinal X1.01.21.41.61.82.0c \ No newline at end of file diff --git a/examples/compiled/test_invalid_color_size_config_scale.svg b/examples/compiled/test_invalid_color_size_config_scale.svg index 641c8d1c4d..0ab4f73e43 100644 --- a/examples/compiled/test_invalid_color_size_config_scale.svg +++ b/examples/compiled/test_invalid_color_size_config_scale.svg @@ -1 +1 @@ -0123a02040bColor0123a02040bSize0.00.51.01.52.0c \ No newline at end of file +0123a02040bColor0123a02040bSize0.00.51.01.52.0c \ No newline at end of file diff --git a/examples/compiled/test_invalid_color_size_mark_show_only.svg b/examples/compiled/test_invalid_color_size_mark_show_only.svg index fbe2695055..547fad019c 100644 --- a/examples/compiled/test_invalid_color_size_mark_show_only.svg +++ b/examples/compiled/test_invalid_color_size_mark_show_only.svg @@ -1 +1 @@ -0123a02040bColor0123a02040bSize0.00.51.01.52.0c \ No newline at end of file +0123a02040bColor0123a02040bSize0.00.51.01.52.0c \ No newline at end of file diff --git a/examples/compiled/test_invalid_default.svg b/examples/compiled/test_invalid_default.svg index fd3f096e5f..ac6f9c6ab0 100644 --- a/examples/compiled/test_invalid_default.svg +++ b/examples/compiled/test_invalid_default.svg @@ -1 +1 @@ -−505a−2002040b−505a−2002040b−10−50510a050100b−10−50510a050100bQuantitative Xnull-5-115a050100bnull-5-115a050100bnull-10-5-101510a050100bnull-10-5-101510a050100bOrdinal X \ No newline at end of file +−505a−2002040b−505a−2002040b−10−50510a050100b−10−50510a050100bQuantitative Xnull-5-115a050100bnull-5-115a050100bnull-10-5-101510a050100bnull-10-5-101510a050100bOrdinal X \ No newline at end of file diff --git a/examples/compiled/test_invalid_null.svg b/examples/compiled/test_invalid_null.svg index f4635611ae..a1ca2d816b 100644 --- a/examples/compiled/test_invalid_null.svg +++ b/examples/compiled/test_invalid_null.svg @@ -1 +1 @@ -−10−50510a050100b−10010a050100b−10−50510a050100b−10−50510a050100bQuantitative Xnull-10-5-101510a050100bnull-10-5-101510a050100bnull-10-5-101510a050100bnull-10-5-101510a050100bOrdinal X \ No newline at end of file +−10−50510a050100b−10010a050100b−10−50510a050100b−10−50510a050100bQuantitative Xnull-10-5-101510a050100bnull-10-5-101510a050100bnull-10-5-101510a050100bnull-10-5-101510a050100bOrdinal X \ No newline at end of file diff --git a/examples/compiled/test_invalid_opacity_filter_but_include_in_scale_invalid.svg b/examples/compiled/test_invalid_opacity_filter_but_include_in_scale_invalid.svg index 67540caf76..37ab9c2e33 100644 --- a/examples/compiled/test_invalid_opacity_filter_but_include_in_scale_invalid.svg +++ b/examples/compiled/test_invalid_opacity_filter_but_include_in_scale_invalid.svg @@ -1 +1 @@ -0123a02040b123a050100b1.01.52.02.53.0a02040b1.01.52.02.53.0a050100bQuantitative X123a02040b123a050100b123a02040b123a050100bOrdinal X1.01.21.41.61.82.0c \ No newline at end of file +0123a02040b123a050100b1.01.52.02.53.0a02040b1.01.52.02.53.0a050100bQuantitative X123a02040b123a050100b123a02040b123a050100bOrdinal X1.01.21.41.61.82.0c \ No newline at end of file diff --git a/examples/compiled/test_invalid_opacity_show.svg b/examples/compiled/test_invalid_opacity_show.svg index 3330670d5f..1638d52e7a 100644 --- a/examples/compiled/test_invalid_opacity_show.svg +++ b/examples/compiled/test_invalid_opacity_show.svg @@ -1 +1 @@ -0123a02040b123a050100b1.01.52.02.53.0a02040b1.01.52.02.53.0a050100bQuantitative X123a02040b123a050100b123a02040b123a050100bOrdinal X1.01.21.41.61.82.0c \ No newline at end of file +0123a02040b123a050100b1.01.52.02.53.0a02040b1.01.52.02.53.0a050100bQuantitative X123a02040b123a050100b123a02040b123a050100bOrdinal X1.01.21.41.61.82.0c \ No newline at end of file diff --git a/examples/compiled/test_invalid_show.svg b/examples/compiled/test_invalid_show.svg index f4635611ae..a1ca2d816b 100644 --- a/examples/compiled/test_invalid_show.svg +++ b/examples/compiled/test_invalid_show.svg @@ -1 +1 @@ -−10−50510a050100b−10010a050100b−10−50510a050100b−10−50510a050100bQuantitative Xnull-10-5-101510a050100bnull-10-5-101510a050100bnull-10-5-101510a050100bnull-10-5-101510a050100bOrdinal X \ No newline at end of file +−10−50510a050100b−10010a050100b−10−50510a050100b−10−50510a050100bQuantitative Xnull-10-5-101510a050100bnull-10-5-101510a050100bnull-10-5-101510a050100bnull-10-5-101510a050100bOrdinal X \ No newline at end of file diff --git a/examples/compiled/vega_version b/examples/compiled/vega_version index 5fee08105a..d78a1a83e5 100644 --- a/examples/compiled/vega_version +++ b/examples/compiled/vega_version @@ -1 +1 @@ -vega: 5.29.0 +vega: 5.30.0